Copied acceptance tests infrastructures from oC/core

Signed-off-by: Kiran Parajuli <kiranparajuli589@gmail.com>
This commit is contained in:
Kiran Parajuli
2022-12-21 17:22:22 +05:45
committed by Phil Davis
parent b84f1f2048
commit 7d152e2ad1
61 changed files with 39555 additions and 28 deletions

View File

@@ -16,5 +16,5 @@ exclude_paths:
- 'tests/acceptance/expected-failures-*.md'
- 'tests/acceptance/features/bootstrap/**'
- 'tests/TestHelpers/**'
- 'tests/acceptance/run.sh'
...

View File

@@ -716,7 +716,6 @@ def localApiTestPipeline(ctx):
"steps": skipIfUnchanged(ctx, "acceptance-tests") +
restoreBuildArtifactCache(ctx, "ocis-binary-amd64", "ocis/bin") +
ocisServer(storage, params["accounts_hash_difficulty"], extra_server_environment = params["extraServerEnvironment"]) +
restoreBuildArtifactCache(ctx, "testrunner", dirs["core"]) +
localApiTests(suite, storage, params["extraEnvironment"]) +
failEarly(ctx, early_fail),
"services": redisForOCStorage(storage),
@@ -736,7 +735,6 @@ def localApiTests(suite, storage, extra_environment = {}):
environment = {
"TEST_WITH_GRAPH_API": "true",
"PATH_TO_OCIS": dirs["base"],
"PATH_TO_CORE": "%s/%s" % (dirs["base"], dirs["core"]),
"TEST_SERVER_URL": "https://ocis-server:9200",
"OCIS_REVA_DATA_ROOT": "%s" % (dirs["ocisRevaDataRoot"] if storage == "owncloud" else ""),
"OCIS_SKELETON_STRATEGY": "%s" % ("copy" if storage == "owncloud" else "upload"),

View File

@@ -106,11 +106,11 @@ PARALLEL_BEHAT_YML=tests/parallelDeployAcceptance/config/behat.yml
.PHONY: test-acceptance-api
test-acceptance-api: vendor-bin/behat/vendor
BEHAT_BIN=$(BEHAT_BIN) $(PATH_TO_CORE)/tests/acceptance/run.sh --remote --type api
BEHAT_BIN=$(BEHAT_BIN) $(PWD)/tests/acceptance/run.sh --type api
.PHONY: test-paralleldeployment-api
test-paralleldeployment-api: vendor-bin/behat/vendor
BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(PARALLEL_BEHAT_YML) $(PATH_TO_CORE)/tests/acceptance/run.sh --type api
BEHAT_BIN=$(BEHAT_BIN) BEHAT_YML=$(PARALLEL_BEHAT_YML) $(PWD)/tests/acceptance/run.sh --type api
vendor/bamarni/composer-bin-plugin: composer.lock
composer install

View File

@@ -9,9 +9,8 @@
"bamarni/composer-bin-plugin": true
}
},
"require": {
},
"require-dev": {
"ext-simplexml": "*",
"bamarni/composer-bin-plugin": "^1.4"
},
"extra": {

View File

@@ -84,7 +84,7 @@ class GraphHelper {
*/
public static function getFullUrl(string $baseUrl, string $path): string {
$fullUrl = $baseUrl;
if (\substr($fullUrl, -1) !== '/') {
if (!str_ends_with($fullUrl, '/')) {
$fullUrl .= '/';
}
$fullUrl .= 'graph/v1.0/' . $path;

View File

@@ -0,0 +1,622 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use SimpleXMLElement;
use Sabre\Xml\LibXMLException;
use Sabre\Xml\Reader;
use GuzzleHttp\Pool;
/**
* Helper for HTTP requests
*/
class HttpRequestHelper {
public const HTTP_TOO_EARLY = 425;
/**
* @var string
*/
private static $oCSelectorCookie = null;
/**
* @return string
*/
public static function getOCSelectorCookie(): string {
return self::$oCSelectorCookie;
}
/**
* @param string $oCSelectorCookie "owncloud-selector=oc10;path=/;"
*
* @return void
*/
public static function setOCSelectorCookie(string $oCSelectorCookie): void {
self::$oCSelectorCookie = $oCSelectorCookie;
}
/**
* Some systems-under-test do async post-processing of operations like upload,
* move etc. If a client does a request on the resource before the post-processing
* is finished, then the server should return HTTP_TOO_EARLY "425". Clients are
* expected to retry the request "some time later" (tm).
*
* On such systems, when HTTP_TOO_EARLY status is received, the test code will
* retry the request at 1-second intervals until either some other HTTP status
* is received or the retry-limit is reached.
*
* @return int
*/
public static function numRetriesOnHttpTooEarly():int {
if (OcisHelper::isTestingOnOcisOrReva()) {
// Currently reva and oCIS may return HTTP_TOO_EARLY
// So try up to 10 times before giving up.
return 10;
}
return 0;
}
/**
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $method
* @param string|null $user
* @param string|null $password
* @param array|null $headers ['X-MyHeader' => 'value']
* @param mixed $body
* @param array|null $config
* @param CookieJar|null $cookies
* @param bool $stream Set to true to stream a response rather
* than download it all up-front.
* @param int|null $timeout
* @param Client|null $client
*
* @return ResponseInterface
* @throws GuzzleException
*/
public static function sendRequest(
?string $url,
?string $xRequestId,
?string $method = 'GET',
?string $user = null,
?string $password = null,
?array $headers = null,
$body = null,
?array $config = null,
?CookieJar $cookies = null,
bool $stream = false,
?int $timeout = 0,
?Client $client = null
):ResponseInterface {
if ($client === null) {
$client = self::createClient(
$user,
$password,
$config,
$cookies,
$stream,
$timeout
);
}
/**
* @var RequestInterface $request
*/
$request = self::createRequest(
$url,
$xRequestId,
$method,
$headers,
$body
);
if ((\getenv('DEBUG_ACCEPTANCE_REQUESTS') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
$debugRequests = true;
} else {
$debugRequests = false;
}
if ((\getenv('DEBUG_ACCEPTANCE_RESPONSES') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
$debugResponses = true;
} else {
$debugResponses = false;
}
if ($debugRequests) {
self::debugRequest($request, $user, $password);
}
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
$sendCount = 0;
$sendExceptionHappened = false;
do {
// The exceptions that might happen here include:
// ConnectException - in that case there is no response. Don't catch the exception.
// RequestException - if there is something in the response then pass it back.
// otherwise re-throw the exception.
// GuzzleException - something else unexpected happened. Don't catch the exception.
try {
$response = $client->send($request);
} catch (RequestException $ex) {
$sendExceptionHappened = true;
$response = $ex->getResponse();
//if the response was null for some reason do not return it but re-throw
if ($response === null) {
throw $ex;
}
}
if ($debugResponses) {
self::debugResponse($response);
}
$sendCount = $sendCount + 1;
$loopAgain = !$sendExceptionHappened && ($response->getStatusCode() === self::HTTP_TOO_EARLY) && ($sendCount <= $sendRetryLimit);
if ($loopAgain) {
// we need to repeat the send request, because we got HTTP_TOO_EARLY
// wait 1 second before sending again, to give the server some time
// to finish whatever post-processing it might be doing.
\sleep(1);
}
} while ($loopAgain);
return $response;
}
/**
* Print details about the request.
*
* @param RequestInterface|null $request
* @param string|null $user
* @param string|null $password
*
* @return void
*/
private static function debugRequest(?RequestInterface $request, ?string $user, ?string $password):void {
print("### AUTH: $user:$password\n");
print("### REQUEST: " . $request->getMethod() . " " . $request->getUri() . "\n");
self::printHeaders($request->getHeaders());
self::printBody($request->getBody());
print("\n### END REQUEST\n");
}
/**
* Print details about the response.
*
* @param ResponseInterface|null $response
*
* @return void
*/
private static function debugResponse(?ResponseInterface $response):void {
print("### RESPONSE\n");
print("Status: " . $response->getStatusCode() . "\n");
self::printHeaders($response->getHeaders());
self::printBody($response->getBody());
print("\n### END RESPONSE\n");
}
/**
* Print details about the headers.
*
* @param array|null $headers
*
* @return void
*/
private static function printHeaders(?array $headers):void {
if ($headers) {
print("Headers:\n");
foreach ($headers as $header => $value) {
if (\is_array($value)) {
print($header . ": " . \implode(', ', $value) . "\n");
} else {
print($header . ": " . $value . "\n");
}
}
} else {
print("Headers: none\n");
}
}
/**
* Print details about the body.
*
* @param StreamInterface|null $body
*
* @return void
*/
private static function printBody(?StreamInterface $body):void {
print("Body:\n");
\var_dump($body->getContents());
// Rewind the stream so that later code can read from the start.
$body->rewind();
}
/**
* Send the requests to the server in parallel.
* This function takes an array of requests and an optional client.
* It will send all the requests to the server using the Pool object in guzzle.
*
* @param array|null $requests
* @param Client|null $client
*
* @return array
*/
public static function sendBatchRequest(
?array $requests,
?Client $client
):array {
$results = Pool::batch($client, $requests);
return $results;
}
/**
* Create a Guzzle Client
* This creates a client object that can be used later to send a request object(s)
*
* @param string|null $user
* @param string|null $password
* @param array|null $config
* @param CookieJar|null $cookies
* @param bool $stream Set to true to stream a response rather
* than download it all up-front.
* @param int|null $timeout
*
* @return Client
*/
public static function createClient(
?string $user = null,
?string $password = null,
?array $config = null,
?CookieJar $cookies = null,
?bool $stream = false,
?int $timeout = 0
):Client {
$options = [];
if ($user !== null) {
$options['auth'] = [$user, $password];
}
if ($config !== null) {
$options['config'] = $config;
}
if ($cookies !== null) {
$options['cookies'] = $cookies;
}
$options['stream'] = $stream;
$options['verify'] = false;
$options['timeout'] = $timeout;
$client = new Client($options);
return $client;
}
/**
* Create an http request based on given parameters.
* This creates a RequestInterface object that can be used with a client to send a request.
* This enables us to create multiple requests in advance so that we can send them to the server at once in parallel.
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $method
* @param array|null $headers ['X-MyHeader' => 'value']
* @param string|array $body either the actual string to send in the body,
* or an array of key-value pairs to be converted
* into a body with http_build_query.
*
* @return RequestInterface
*/
public static function createRequest(
?string $url,
?string $xRequestId = '',
?string $method = 'GET',
?array $headers = null,
$body = null
):RequestInterface {
if ($headers === null) {
$headers = [];
}
if ($xRequestId !== '') {
$headers['X-Request-ID'] = $xRequestId;
}
if (\is_array($body)) {
// when creating the client, it is possible to set 'form_params' and
// the Client constructor sorts out doing this http_build_query stuff.
// But 'new Request' does not have the flexibility to do that.
// So we need to do it here.
$body = \http_build_query($body, '', '&');
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
if (OcisHelper::isTestingParallelDeployment()) {
// oCIS cannot handle '/apps/testing' endpoints
// so those requests must be redirected to oC10 server
// change server to oC10 if the request url has `/apps/testing`
if (strpos($url, "/apps/testing") !== false) {
$oCISServerUrl = \getenv('TEST_SERVER_URL');
$oC10ServerUrl = \getenv('TEST_OC10_URL');
$url = str_replace($oCISServerUrl, $oC10ServerUrl, $url);
} else {
// set 'owncloud-server' selector cookie for oCIS requests
$headers['Cookie'] = self::getOCSelectorCookie();
}
}
$request = new Request(
$method,
$url,
$headers,
$body
);
return $request;
}
/**
* same as HttpRequestHelper::sendRequest() but with "GET" as method
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $user
* @param string|null $password
* @param array|null $headers ['X-MyHeader' => 'value']
* @param mixed $body
* @param array|null $config
* @param CookieJar|null $cookies
* @param boolean $stream
*
* @return ResponseInterface
* @throws GuzzleException
* @see HttpRequestHelper::sendRequest()
*/
public static function get(
?string $url,
?string $xRequestId,
?string $user = null,
?string $password = null,
?array $headers = null,
$body = null,
?array $config = null,
?CookieJar $cookies = null,
?bool $stream = false
):ResponseInterface {
return self::sendRequest(
$url,
$xRequestId,
'GET',
$user,
$password,
$headers,
$body,
$config,
$cookies,
$stream
);
}
/**
* same as HttpRequestHelper::sendRequest() but with "POST" as method
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $user
* @param string|null $password
* @param array|null $headers ['X-MyHeader' => 'value']
* @param mixed $body
* @param array|null $config
* @param CookieJar|null $cookies
* @param boolean $stream
*
* @return ResponseInterface
* @throws GuzzleException
* @see HttpRequestHelper::sendRequest()
*/
public static function post(
?string $url,
?string $xRequestId,
?string $user = null,
?string $password = null,
?array $headers = null,
$body = null,
?array $config = null,
?CookieJar $cookies = null,
?bool $stream = false
):ResponseInterface {
return self::sendRequest(
$url,
$xRequestId,
'POST',
$user,
$password,
$headers,
$body,
$config,
$cookies,
$stream
);
}
/**
* same as HttpRequestHelper::sendRequest() but with "PUT" as method
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $user
* @param string|null $password
* @param array|null $headers ['X-MyHeader' => 'value']
* @param mixed $body
* @param array|null $config
* @param CookieJar|null $cookies
* @param boolean $stream
*
* @return ResponseInterface
* @throws GuzzleException
* @see HttpRequestHelper::sendRequest()
*/
public static function put(
?string $url,
?string $xRequestId,
?string $user = null,
?string $password = null,
?array $headers = null,
$body = null,
?array $config = null,
?CookieJar $cookies = null,
?bool $stream = false
):ResponseInterface {
return self::sendRequest(
$url,
$xRequestId,
'PUT',
$user,
$password,
$headers,
$body,
$config,
$cookies,
$stream
);
}
/**
* same as HttpRequestHelper::sendRequest() but with "DELETE" as method
*
* @param string|null $url
* @param string|null $xRequestId
* @param string|null $user
* @param string|null $password
* @param array|null $headers ['X-MyHeader' => 'value']
* @param mixed $body
* @param array|null $config
* @param CookieJar|null $cookies
* @param boolean $stream
*
* @return ResponseInterface
* @throws GuzzleException
* @see HttpRequestHelper::sendRequest()
*
*/
public static function delete(
?string $url,
?string $xRequestId,
?string $user = null,
?string $password = null,
?array $headers = null,
$body = null,
?array $config = null,
?CookieJar $cookies = null,
?bool $stream = false
):ResponseInterface {
return self::sendRequest(
$url,
$xRequestId,
'DELETE',
$user,
$password,
$headers,
$body,
$config,
$cookies,
$stream
);
}
/**
* Parses the response as XML and returns a SimpleXMLElement with these
* registered namespaces:
* | prefix | namespace |
* | d | DAV: |
* | oc | http://owncloud.org/ns |
* | ocs | http://open-collaboration-services.org/ns |
*
* @param ResponseInterface $response
* @param string|null $exceptionText text to put at the front of exception messages
*
* @return SimpleXMLElement
* @throws Exception
*/
public static function getResponseXml(ResponseInterface $response, ?string $exceptionText = ''):SimpleXMLElement {
// rewind just to make sure we can re-parse it in case it was parsed already...
$response->getBody()->rewind();
$contents = $response->getBody()->getContents();
try {
$responseXmlObject = new SimpleXMLElement($contents);
$responseXmlObject->registerXPathNamespace(
'ocs',
'http://open-collaboration-services.org/ns'
);
$responseXmlObject->registerXPathNamespace(
'oc',
'http://owncloud.org/ns'
);
$responseXmlObject->registerXPathNamespace(
'd',
'DAV:'
);
return $responseXmlObject;
} catch (Exception $e) {
if ($exceptionText !== '') {
$exceptionText = $exceptionText . ' ';
}
if ($contents === '') {
throw new Exception($exceptionText . "Received empty response where XML was expected");
}
$message = $exceptionText . "Exception parsing response body: \"" . $contents . "\"";
throw new Exception($message, 0, $e);
}
}
/**
* parses the body content of $response and returns an array representing the XML
* This function returns an array with the following three elements:
* * name - The root element name.
* * value - The value for the root element.
* * attributes - An array of attributes.
*
* @param ResponseInterface $response
*
* @return array
*/
public static function parseResponseAsXml(ResponseInterface $response):array {
$body = $response->getBody()->getContents();
$parsedResponse = [];
if ($body && \substr($body, 0, 1) === '<') {
try {
$reader = new Reader();
$reader->xml($body);
$parsedResponse = $reader->parse();
} catch (LibXMLException $e) {
// Sometimes the body can be a real page of HTML and text.
// So it may not be a complete ordinary piece of XML.
// The XML parse might fail with an exception message like:
// Opening and ending tag mismatch: link line 31 and head.
}
}
return $parsedResponse;
}
}

View File

@@ -0,0 +1,364 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2020 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
/**
* Class OcisHelper
*
* Helper functions that are needed to run tests on OCIS
*
* @package TestHelpers
*/
class OcisHelper {
/**
* @return bool
*/
public static function isTestingOnOcis():bool {
return (\getenv("TEST_OCIS") === "true");
}
/**
* @return bool
*/
public static function isTestingOnReva():bool {
return (\getenv("TEST_REVA") === "true");
}
/**
* @return bool
*/
public static function isTestingOnOcisOrReva():bool {
return (self::isTestingOnOcis() || self::isTestingOnReva());
}
/**
* @return bool
*/
public static function isTestingOnOc10():bool {
return (!self::isTestingOnOcisOrReva());
}
/**
* @return bool
*/
public static function isTestingParallelDeployment(): bool {
return (\getenv("TEST_PARALLEL_DEPLOYMENT") === "true");
}
/**
* @return bool
*/
public static function isTestingWithGraphApi(): bool {
return \getenv('TEST_WITH_GRAPH_API') === 'true';
}
/**
* @return bool|string false if no command given or the command as string
*/
public static function getDeleteUserDataCommand() {
$cmd = \getenv("DELETE_USER_DATA_CMD");
if ($cmd === false || \trim($cmd) === "") {
return false;
}
return $cmd;
}
/**
* @return string
* @throws Exception
*/
public static function getStorageDriver():string {
$storageDriver = (\getenv("STORAGE_DRIVER"));
if ($storageDriver === false) {
return "OWNCLOUD";
}
$storageDriver = \strtoupper($storageDriver);
if ($storageDriver !== "OCIS" && $storageDriver !== "EOS" && $storageDriver !== "OWNCLOUD" && $storageDriver !== "S3NG") {
throw new Exception(
"Invalid storage driver. " .
"STORAGE_DRIVER must be OCIS|EOS|OWNCLOUD|S3NG"
);
}
return $storageDriver;
}
/**
* @param string|null $user
*
* @return void
* @throws Exception
*/
public static function deleteRevaUserData(?string $user = ""):void {
$deleteCmd = self::getDeleteUserDataCommand();
if ($deleteCmd === false) {
if (self::getStorageDriver() === "OWNCLOUD") {
self::recurseRmdir(self::getOcisRevaDataRoot() . $user);
}
return;
}
if (self::getStorageDriver() === "EOS") {
$deleteCmd = \str_replace(
"%s",
$user[0] . '/' . $user,
$deleteCmd
);
} else {
$deleteCmd = \sprintf($deleteCmd, $user);
}
\exec($deleteCmd);
}
/**
* Helper for Recursive Copy of file/folder
* For more info check this out https://gist.github.com/gserrano/4c9648ec9eb293b9377b
*
* @param string|null $source
* @param string|null $destination
*
* @return void
*/
public static function recurseCopy(?string $source, ?string $destination):void {
$dir = \opendir($source);
@\mkdir($destination);
while (($file = \readdir($dir)) !== false) {
if (($file != '.') && ($file != '..')) {
if (\is_dir($source . '/' . $file)) {
self::recurseCopy($source . '/' . $file, $destination . '/' . $file);
} else {
\copy($source . '/' . $file, $destination . '/' . $file);
}
}
}
\closedir($dir);
}
/**
* Helper for Recursive Upload of file/folder
*
* @param string|null $baseUrl
* @param string|null $source
* @param string|null $userId
* @param string|null $password
* @param string|null $xRequestId
* @param string|null $destination
*
* @return void
* @throws Exception
*/
public static function recurseUpload(
?string $baseUrl,
?string $source,
?string $userId,
?string $password,
?string $xRequestId = '',
?string $destination = ''
):void {
if ($destination !== '') {
$response = WebDavHelper::makeDavRequest(
$baseUrl,
$userId,
$password,
"MKCOL",
$destination,
[],
$xRequestId
);
if ($response->getStatusCode() !== 201) {
throw new Exception("Could not create folder destination" . $response->getBody()->getContents());
}
}
$dir = \opendir($source);
while (($file = \readdir($dir)) !== false) {
if (($file != '.') && ($file != '..')) {
$sourcePath = $source . '/' . $file;
$destinationPath = $destination . '/' . $file;
if (\is_dir($sourcePath)) {
self::recurseUpload(
$baseUrl,
$sourcePath,
$userId,
$password,
$xRequestId,
$destinationPath
);
} else {
$response = UploadHelper::upload(
$baseUrl,
$userId,
$password,
$sourcePath,
$destinationPath,
$xRequestId
);
$responseStatus = $response->getStatusCode();
if ($responseStatus !== 201) {
throw new Exception(
"Could not upload skeleton file $sourcePath to $destinationPath for user '$userId' status '$responseStatus' response body: '"
. $response->getBody()->getContents() . "'"
);
}
}
}
}
\closedir($dir);
}
/**
* @return int
*/
public static function getLdapPort():int {
$port = \getenv("REVA_LDAP_PORT");
return $port ? (int)$port : 636;
}
/**
* @return bool
*/
public static function useSsl():bool {
$useSsl = \getenv("REVA_LDAP_USESSL");
if ($useSsl === false) {
return (self::getLdapPort() === 636);
} else {
return $useSsl === "true";
}
}
/**
* @return string
*/
public static function getBaseDN():string {
$dn = \getenv("REVA_LDAP_BASE_DN");
return $dn ? $dn : "dc=owncloud,dc=com";
}
/**
* @return string
*/
public static function getGroupsOU():string {
$ou = \getenv("REVA_LDAP_GROUPS_OU");
return $ou ? $ou : "TestGroups";
}
/**
* @return string
*/
public static function getUsersOU():string {
$ou = \getenv("REVA_LDAP_USERS_OU");
return $ou ? $ou : "TestUsers";
}
/**
* @return string
*/
public static function getGroupSchema():string {
$schema = \getenv("REVA_LDAP_GROUP_SCHEMA");
return $schema ? $schema : "rfc2307";
}
/**
* @return string
*/
public static function getHostname():string {
$hostname = \getenv("REVA_LDAP_HOSTNAME");
return $hostname ? $hostname : "localhost";
}
/**
* @return string
*/
public static function getBindDN():string {
$dn = \getenv("REVA_LDAP_BIND_DN");
return $dn ? $dn : "cn=admin,dc=owncloud,dc=com";
}
/**
* @return string
*/
public static function getBindPassword():string {
$pw = \getenv("REVA_LDAP_BIND_PASSWORD");
return $pw ? $pw : "";
}
/**
* @return string
*/
private static function getOcisRevaDataRoot():string {
$root = \getenv("OCIS_REVA_DATA_ROOT");
if (($root === false || $root === "") && self::isTestingOnOcisOrReva()) {
$root = "/var/tmp/ocis/owncloud/";
}
if (!\file_exists($root)) {
echo "WARNING: reva data root folder ($root) does not exist\n";
}
return $root;
}
/**
* @param string|null $dir
*
* @return bool
*/
private static function recurseRmdir(?string $dir):bool {
if (\file_exists($dir) === true) {
$files = \array_diff(\scandir($dir), ['.', '..']);
foreach ($files as $file) {
if (\is_dir("$dir/$file")) {
self::recurseRmdir("$dir/$file");
} else {
\unlink("$dir/$file");
}
}
return \rmdir($dir);
}
return true;
}
/**
* On Eos storage backend when the user data is cleared after test run
* Running another test immediately fails. So Send this request to create user home directory
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string|null $xRequestId
*
* @return void
* @throws GuzzleException
*/
public static function createEOSStorageHome(
?string $baseUrl,
?string $user,
?string $password,
?string $xRequestId = ''
):void {
HttpRequestHelper::get(
$baseUrl . "/ocs/v2.php/apps/notifications/api/v1/notifications",
$xRequestId,
$user,
$password
);
}
}

View File

@@ -0,0 +1,103 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Helper to make requests to the OCS API
*
* @author Artur Neumann <artur@jankaritech.com>
*
*/
class OcsApiHelper {
/**
* @param string|null $baseUrl
* @param string|null $user if set to null no authentication header will be sent
* @param string|null $password
* @param string|null $method HTTP Method
* @param string|null $path
* @param string|null $xRequestId
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
* @param int|null $ocsApiVersion (1|2) default 2
* @param array|null $headers
*
* @return ResponseInterface
* @throws GuzzleException
*/
public static function sendRequest(
?string $baseUrl,
?string $user,
?string $password,
?string $method,
?string $path,
?string $xRequestId = '',
$body = [],
?int $ocsApiVersion = 2,
?array $headers = []
):ResponseInterface {
$fullUrl = $baseUrl;
if (\substr($fullUrl, -1) !== '/') {
$fullUrl .= '/';
}
$fullUrl .= "ocs/v{$ocsApiVersion}.php" . $path;
$headers['OCS-APIREQUEST'] = true;
return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers, $body);
}
/**
*
* @param string|null $baseUrl
* @param string|null $method HTTP Method
* @param string|null $path
* @param string|null $xRequestId
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
* @param int|null $ocsApiVersion (1|2) default 2
* @param array|null $headers
*
* @return RequestInterface
*/
public static function createOcsRequest(
?string $baseUrl,
?string $method,
?string $path,
?string $xRequestId = '',
$body = [],
?int $ocsApiVersion = 2,
?array $headers = []
):RequestInterface {
$fullUrl = $baseUrl;
if (\substr($fullUrl, -1) !== '/') {
$fullUrl .= '/';
}
$fullUrl .= "ocs/v{$ocsApiVersion}.php" . $path;
return HttpRequestHelper::createRequest(
$fullUrl,
$xRequestId,
$method,
$headers,
$body
);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Exception;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use SimpleXMLElement;
/**
* manage Shares via OCS API
*
* @author Artur Neumann <artur@jankaritech.com>
*
*/
class SharingHelper {
public const PERMISSION_TYPES = [
'read' => 1,
'update' => 2,
'create' => 4,
'delete' => 8,
'share' => 16,
];
public const SHARE_TYPES = [
'user' => 0,
'group' => 1,
'public_link' => 3,
'federated' => 6,
];
public const SHARE_STATES = [
'accepted' => 0,
'pending' => 1,
'rejected' => 2,
'declined' => 2, // declined is a synonym for rejected
];
/**
*
* @param string $baseUrl baseURL of the ownCloud installation without /ocs.
* @param string $user user that creates the share.
* @param string $password password of the user that creates the share.
* @param string $path The path to the file or folder which should be shared.
* @param string|int $shareType The type of the share. This can be one of:
* 0 = user, 1 = group, 3 = public (link),
* 6 = federated (cloud share).
* Pass either the number or the keyword.
* @param string $xRequestId
* @param string|null $shareWith The user or group id with which the file should
* be shared.
* @param boolean|null $publicUpload Whether to allow public upload to a public
* shared folder.
* @param string|null $sharePassword The password to protect the public link
* share with.
* @param string|int|string[]|int[]|null $permissions The permissions to set on the share.
* 1 = read; 2 = update; 4 = create;
* 8 = delete; 16 = share
* (default: 31, for public shares: 1)
* Pass either the (total) number or array of numbers,
* or any of the above keywords or array of keywords.
* @param string|null $linkName A (human-readable) name for the share,
* which can be up to 64 characters in length.
* @param string|null $expireDate An expire date for public link shares.
* This argument expects a date string
* in the format 'YYYY-MM-DD'.
* @param int $ocsApiVersion
* @param int $sharingApiVersion
* @param string $sharingApp
*
* @return ResponseInterface
* @throws InvalidArgumentException
*/
public static function createShare(
string $baseUrl,
string $user,
string $password,
string $path,
$shareType,
string $xRequestId = '',
?string $shareWith = null,
?bool $publicUpload = false,
string $sharePassword = null,
$permissions = null,
?string $linkName = null,
?string $expireDate = null,
int $ocsApiVersion = 1,
int $sharingApiVersion = 1,
string $sharingApp = 'files_sharing'
): ResponseInterface {
$fd = [];
foreach ([$path, $baseUrl, $user, $password] as $variableToCheck) {
if (!\is_string($variableToCheck)) {
throw new InvalidArgumentException(
"mandatory argument missing or wrong type ($variableToCheck => "
. \gettype($variableToCheck) . ")"
);
}
}
if ($permissions !== null) {
$fd['permissions'] = self::getPermissionSum($permissions);
}
if (!\in_array($ocsApiVersion, [1, 2], true)) {
throw new InvalidArgumentException(
"invalid ocsApiVersion ($ocsApiVersion)"
);
}
if (!\in_array($sharingApiVersion, [1, 2], true)) {
throw new InvalidArgumentException(
"invalid sharingApiVersion ($sharingApiVersion)"
);
}
$fullUrl = $baseUrl;
if (\substr($fullUrl, -1) !== '/') {
$fullUrl .= '/';
}
$fullUrl .= "ocs/v{$ocsApiVersion}.php/apps/{$sharingApp}/api/v{$sharingApiVersion}/shares";
$fd['path'] = $path;
$fd['shareType'] = self::getShareType($shareType);
if ($shareWith !== null) {
$fd['shareWith'] = $shareWith;
}
if ($publicUpload !== null) {
$fd['publicUpload'] = (bool) $publicUpload;
}
if ($sharePassword !== null) {
$fd['password'] = $sharePassword;
}
if ($linkName !== null) {
$fd['name'] = $linkName;
}
if ($expireDate !== null) {
$fd['expireDate'] = $expireDate;
}
$headers = ['OCS-APIREQUEST' => 'true'];
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
$fd
);
}
/**
* calculates the permission sum (int) from given permissions
* permissions can be passed in as int, string or array of int or string
* 'read' => 1
* 'update' => 2
* 'create' => 4
* 'delete' => 8
* 'share' => 16
*
* @param string[]|string|int|int[] $permissions
*
* @return int
* @throws InvalidArgumentException
*
*/
public static function getPermissionSum($permissions):int {
if (\is_numeric($permissions)) {
// Allow any permission number so that test scenarios can
// specifically test invalid permission values
return (int) $permissions;
}
if (!\is_array($permissions)) {
$permissions = [$permissions];
}
$permissionSum = 0;
foreach ($permissions as $permission) {
if (\array_key_exists($permission, self::PERMISSION_TYPES)) {
$permissionSum += self::PERMISSION_TYPES[$permission];
} elseif (\in_array($permission, self::PERMISSION_TYPES, true)) {
$permissionSum += (int) $permission;
} else {
throw new InvalidArgumentException(
"invalid permission type ($permission)"
);
}
}
if ($permissionSum < 1 || $permissionSum > 31) {
throw new InvalidArgumentException(
"invalid permission total ($permissionSum)"
);
}
return $permissionSum;
}
/**
* returns the share type number corresponding to the share type keyword
*
* @param string|int $shareType a keyword from SHARE_TYPES or a share type number
*
* @return int
* @throws InvalidArgumentException
*
*/
public static function getShareType($shareType):int {
if (\array_key_exists($shareType, self::SHARE_TYPES)) {
return self::SHARE_TYPES[$shareType];
} else {
if (\ctype_digit($shareType)) {
$shareType = (int) $shareType;
}
$key = \array_search($shareType, self::SHARE_TYPES, true);
if ($key !== false) {
return self::SHARE_TYPES[$key];
}
throw new InvalidArgumentException(
"invalid share type ($shareType)"
);
}
}
/**
*
* @param SimpleXMLElement $responseXmlObject
* @param string $errorMessage
*
* @return string
* @throws Exception
*
*/
public static function getLastShareIdFromResponse(
SimpleXMLElement $responseXmlObject,
string $errorMessage = "cannot find share id in response"
):string {
$xmlPart = $responseXmlObject->xpath("//data/element[last()]/id");
if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) {
throw new Exception($errorMessage);
}
return $xmlPart[0]->__toString();
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Sagar Gurung <sagar@jankatitech.com>
*
*/
namespace TestHelpers;
use Exception;
/**
* Class SpaceNotFoundException
* Exception when space id for a user is not found
*/
class SpaceNotFoundException extends Exception {
}

View File

@@ -0,0 +1,46 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Talank Baral <talank@jankaritech.com>
* @copyright Copyright (c) 2021 Talank Baral talank@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
/**
* Class TranslationHelper
*
* Helper functions that are needed to run tests on different languages
*
* @package TestHelpers
*/
class TranslationHelper {
/**
* @param string|null $language
*
* @return string|null
*/
public static function getLanguage(?string $language): ?string {
if (!isset($language)) {
if (\getenv('OC_LANGUAGE') !== false) {
$language = \getenv('OC_LANGUAGE');
}
}
return $language;
}
}

View File

@@ -0,0 +1,318 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Psr\Http\Message\ResponseInterface;
/**
* Helper for Uploads
*
* @author Artur Neumann <artur@jankaritech.com>
*
*/
class UploadHelper extends \PHPUnit\Framework\Assert {
/**
*
* @param string|null $baseUrl URL of owncloud
* e.g. http://localhost:8080
* should include the subfolder
* if owncloud runs in a subfolder
* e.g. http://localhost:8080/owncloud-core
* @param string|null $user
* @param string|null $password
* @param string|null $source
* @param string|null $destination
* @param string|null $xRequestId
* @param array|null $headers
* @param int|null $davPathVersionToUse (1|2)
* @param int|null $chunkingVersion (1|2|null)
* if set to null chunking will not be used
* @param int|null $noOfChunks how many chunks do we want to upload
*
* @return ResponseInterface
*/
public static function upload(
?string $baseUrl,
?string $user,
?string $password,
?string $source,
?string $destination,
?string $xRequestId = '',
?array $headers = [],
?int $davPathVersionToUse = 1,
?int $chunkingVersion = null,
?int $noOfChunks = 1
): ResponseInterface {
//simple upload with no chunking
if ($chunkingVersion === null) {
$data = \file_get_contents($source);
return WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
"PUT",
$destination,
$headers,
$xRequestId,
$data,
$davPathVersionToUse
);
} else {
//prepare chunking
$chunks = self::chunkFile($source, $noOfChunks);
$chunkingId = 'chunking-' . (string)\rand(1000, 9999);
$v2ChunksDestination = '/uploads/' . $user . '/' . $chunkingId;
}
//prepare chunking version specific stuff
if ($chunkingVersion === 1) {
$headers['OC-Chunked'] = '1';
} elseif ($chunkingVersion === 2) {
$result = WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
'MKCOL',
$v2ChunksDestination,
$headers,
$xRequestId,
null,
$davPathVersionToUse,
"uploads"
);
if ($result->getStatusCode() >= 400) {
return $result;
}
}
//upload chunks
foreach ($chunks as $index => $chunk) {
if ($chunkingVersion === 1) {
$filename = $destination . "-" . $chunkingId . "-" .
\count($chunks) . '-' . ( string ) $index;
$davRequestType = "files";
} elseif ($chunkingVersion === 2) {
$filename = $v2ChunksDestination . '/' . (string)($index);
$davRequestType = "uploads";
}
$result = WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
"PUT",
$filename,
$headers,
$xRequestId,
$chunk,
$davPathVersionToUse,
$davRequestType
);
if ($result->getStatusCode() >= 400) {
return $result;
}
}
//finish upload for new chunking
if ($chunkingVersion === 2) {
$source = $v2ChunksDestination . '/.file';
$headers['Destination'] = $baseUrl . "/" .
WebDavHelper::getDavPath($user, $davPathVersionToUse) .
$destination;
$result = WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
'MOVE',
$source,
$headers,
$xRequestId,
null,
$davPathVersionToUse,
"uploads"
);
if ($result->getStatusCode() >= 400) {
return $result;
}
}
return $result;
}
/**
* Upload the same file multiple times with different mechanisms.
*
* @param string|null $baseUrl URL of owncloud
* @param string|null $user user who uploads
* @param string|null $password
* @param string|null $source source file path
* @param string|null $destination destination path on the server
* @param string|null $xRequestId
* @param bool $overwriteMode when false creates separate files to test uploading brand new files,
* when true it just overwrites the same file over and over again with the same name
* @param string|null $exceptChunkingType empty string or "old" or "new"
*
* @return array of ResponseInterface
*/
public static function uploadWithAllMechanisms(
?string $baseUrl,
?string $user,
?string $password,
?string $source,
?string $destination,
?string $xRequestId = '',
?bool $overwriteMode = false,
?string $exceptChunkingType = ''
):array {
$responses = [];
foreach ([1, 2] as $davPathVersion) {
if ($davPathVersion === 1) {
$davHuman = 'old';
} else {
$davHuman = 'new';
}
switch ($exceptChunkingType) {
case 'old':
$exceptChunkingVersion = 1;
break;
case 'new':
$exceptChunkingVersion = 2;
break;
default:
$exceptChunkingVersion = -1;
break;
}
foreach ([null, 1, 2] as $chunkingVersion) {
if ($chunkingVersion === $exceptChunkingVersion) {
continue;
}
$valid = WebDavHelper::isValidDavChunkingCombination(
$davPathVersion,
$chunkingVersion
);
if ($valid === false) {
continue;
}
$finalDestination = $destination;
if (!$overwriteMode && $chunkingVersion !== null) {
$finalDestination .= "-{$davHuman}dav-{$davHuman}chunking";
} elseif (!$overwriteMode && $chunkingVersion === null) {
$finalDestination .= "-{$davHuman}dav-regular";
}
$responses[] = self::upload(
$baseUrl,
$user,
$password,
$source,
$finalDestination,
$xRequestId,
[],
$davPathVersion,
$chunkingVersion,
2
);
}
}
return $responses;
}
/**
* cut the file in multiple chunks
* returns an array of chunks with the content of the file
*
* @param string|null $file
* @param int|null $noOfChunks
*
* @return array $string
*/
public static function chunkFile(?string $file, ?int $noOfChunks = 1):array {
$size = \filesize($file);
$chunkSize = \ceil($size / $noOfChunks);
$chunks = [];
$fp = \fopen($file, 'r');
while (!\feof($fp) && \ftell($fp) < $size) {
$chunks[] = \fread($fp, (int)$chunkSize);
}
\fclose($fp);
if (\count($chunks) === 0) {
// chunk an empty file
$chunks[] = '';
}
return $chunks;
}
/**
* creates a File with a specific size
*
* @param string|null $name full path of the file to create
* @param int|null $size
*
* @return void
*/
public static function createFileSpecificSize(?string $name, ?int $size):void {
if (\file_exists($name)) {
\unlink($name);
}
$file = \fopen($name, 'w');
\fseek($file, \max($size - 1, 0), SEEK_CUR);
if ($size) {
\fwrite($file, 'a'); // write a dummy char at SIZE position
}
\fclose($file);
self::assertEquals(
1,
\file_exists($name)
);
self::assertEquals(
$size,
\filesize($name)
);
}
/**
* creates a File with a specific text content
*
* @param string|null $name full path of the file to create
* @param string|null $text
*
* @return void
*/
public static function createFileWithText(?string $name, ?string $text):void {
$file = \fopen($name, 'w');
\fwrite($file, $text);
\fclose($file);
self::assertEquals(
1,
\file_exists($name)
);
}
/**
* get the path of a file from FilesForUpload directory
*
* @param string|null $name name of the file to upload
*
* @return string
*/
public static function getUploadFilesDir(?string $name):string {
return \getenv("FILES_FOR_UPLOAD") . $name;
}
}

View File

@@ -0,0 +1,375 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Exception;
use GuzzleHttp\Exception\ClientException;
use Psr\Http\Message\ResponseInterface;
/**
* Helper to administrate users (and groups) through the provisioning API
*
* @author Artur Neumann <artur@jankaritech.com>
*
*/
class UserHelper {
/**
*
* @param string|null $baseUrl
* @param string $user
* @param string $key
* @param string $value
* @param string $adminUser
* @param string $adminPassword
* @param string $xRequestId
* @param int|null $ocsApiVersion
*
* @return ResponseInterface
*/
public static function editUser(
?string $baseUrl,
string $user,
string $key,
string $value,
string $adminUser,
string $adminPassword,
string $xRequestId = '',
?int $ocsApiVersion = 2
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"PUT",
"/cloud/users/" . $user,
$xRequestId,
["key" => $key, "value" => $value],
$ocsApiVersion
);
}
/**
* Send batch requests to edit the user.
* This will send multiple requests in parallel to the server which will be faster in comparison to waiting for each request to complete.
*
* @param string|null $baseUrl
* @param array|null $editData ['user' => '', 'key' => '', 'value' => '']
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion
*
* @return array
* @throws Exception
*/
public static function editUserBatch(
?string $baseUrl,
?array $editData,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?int $ocsApiVersion = 2
):array {
$requests = [];
$client = HttpRequestHelper::createClient(
$adminUser,
$adminPassword
);
foreach ($editData as $data) {
$path = "/cloud/users/" . $data['user'];
$body = ["key" => $data['key'], 'value' => $data["value"]];
// Create the OCS API requests and push them to an array.
\array_push(
$requests,
OcsApiHelper::createOcsRequest(
$baseUrl,
'PUT',
$path,
$xRequestId,
$body
)
);
}
// Send the array of requests at once in parallel.
$results = HttpRequestHelper::sendBatchRequest($requests, $client);
foreach ($results as $e) {
if ($e instanceof ClientException) {
$httpStatusCode = $e->getResponse()->getStatusCode();
$reasonPhrase = $e->getResponse()->getReasonPhrase();
throw new Exception(
"Unexpected failure when editing a user: HTTP status $httpStatusCode HTTP reason $reasonPhrase"
);
}
}
return $results;
}
/**
*
* @param string|null $baseUrl
* @param string|null $userName
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion
*
* @return ResponseInterface
*/
public static function getUser(
?string $baseUrl,
?string $userName,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?int $ocsApiVersion = 2
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"GET",
"/cloud/users/" . $userName,
$xRequestId,
[],
$ocsApiVersion
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $userName
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion
*
* @return ResponseInterface
*/
public static function deleteUser(
?string $baseUrl,
?string $userName,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?int $ocsApiVersion = 2
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"DELETE",
"/cloud/users/" . $userName,
$xRequestId,
[],
$ocsApiVersion
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $group
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
*
* @return ResponseInterface
*/
public static function createGroup(
?string $baseUrl,
?string $group,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = ''
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"POST",
"/cloud/groups",
$xRequestId,
['groupid' => $group]
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $group
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion
*
* @return ResponseInterface
*/
public static function deleteGroup(
?string $baseUrl,
?string $group,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?int $ocsApiVersion = 2
):ResponseInterface {
$group = \rawurlencode($group);
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"DELETE",
"/cloud/groups/" . $group,
$xRequestId,
[],
$ocsApiVersion
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $group
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion (1|2)
*
* @return ResponseInterface
*/
public static function addUserToGroup(
?string $baseUrl,
?string $user,
?string $group,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?int $ocsApiVersion = 2
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"POST",
"/cloud/users/" . $user . "/groups",
$xRequestId,
['groupid' => $group],
$ocsApiVersion
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $group
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param int|null $ocsApiVersion (1|2)
*
* @return ResponseInterface
*/
public static function removeUserFromGroup(
?string $baseUrl,
?string $user,
?string $group,
?string $adminUser,
?string $adminPassword,
?string $xRequestId,
?int $ocsApiVersion = 2
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"DELETE",
"/cloud/users/" . $user . "/groups",
$xRequestId,
['groupid' => $group],
$ocsApiVersion
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param string|null $search
*
* @return ResponseInterface
*/
public static function getGroups(
?string $baseUrl,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?string $search =""
):ResponseInterface {
return OcsApiHelper::sendRequest(
$baseUrl,
$adminUser,
$adminPassword,
"GET",
"/cloud/groups?search=" . $search,
$xRequestId
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $adminUser
* @param string|null $adminPassword
* @param string|null $xRequestId
* @param string|null $search
*
* @return string[]
* @throws Exception
*/
public static function getGroupsAsArray(
?string $baseUrl,
?string $adminUser,
?string $adminPassword,
?string $xRequestId = '',
?string $search = ""
):array {
$result = self::getGroups(
$baseUrl,
$adminUser,
$adminPassword,
$xRequestId,
$search
);
$groups = HttpRequestHelper::getResponseXml($result, __METHOD__)->xpath(".//groups")[0];
$return = [];
foreach ($groups as $group) {
$return[] = $group->__toString();
}
return $return;
}
}

View File

@@ -0,0 +1,898 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use InvalidArgumentException;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use DateTime;
use TestHelpers\SpaceNotFoundException;
/**
* Helper to make WebDav Requests
*
* @author Artur Neumann <artur@jankaritech.com>
*
*/
class WebDavHelper {
public const DAV_VERSION_OLD = 1;
public const DAV_VERSION_NEW = 2;
public const DAV_VERSION_SPACES = 3;
public static $SPACE_ID_FROM_OCIS = '';
/**
* @var array of users with their different spaces ids
*/
public static $spacesIdRef = [];
/**
* clear space id reference for user
*
* @param string|null $user
*
* @return void
* @throws Exception
*/
public static function removeSpaceIdReferenceForUser(
?string $user
):void {
if (\array_key_exists($user, self::$spacesIdRef)) {
unset(self::$spacesIdRef[$user]);
}
}
/**
* returns the id of a file
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string|null $path
* @param string|null $xRequestId
* @param int|null $davPathVersionToUse
*
* @return string
* @throws Exception
*/
public static function getFileIdForPath(
?string $baseUrl,
?string $user,
?string $password,
?string $path,
?string $xRequestId = '',
?int $davPathVersionToUse = self::DAV_VERSION_NEW
): string {
$body
= '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:fileid />
</d:prop>
</d:propfind>';
$response = self::makeDavRequest(
$baseUrl,
$user,
$password,
"PROPFIND",
$path,
null,
$xRequestId,
$body,
$davPathVersionToUse
);
\preg_match(
'/\<oc:fileid\>([^\<]*)\<\/oc:fileid\>/',
$response->getBody()->getContents(),
$matches
);
if (!isset($matches[1])) {
throw new Exception("could not find fileId of $path");
}
return $matches[1];
}
/**
* returns body for propfind
*
* @param array|null $properties
*
* @return string
* @throws Exception
*/
public static function getBodyForPropfind(?array $properties): string {
$propertyBody = "";
$extraNamespaces = "";
foreach ($properties as $namespaceString => $property) {
if (\is_int($namespaceString)) {
//default namespace prefix if the property has no array key
//also used if no prefix is given in the property value
$namespacePrefix = "d";
} else {
//calculate the namespace prefix and namespace from the array key
$matches = [];
\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
$nameSpace = $matches[2];
$namespacePrefix = $matches[1];
$extraNamespaces .= " xmlns:$namespacePrefix=\"$nameSpace\" ";
}
//if a namespace prefix is given in the property value use that
if (\strpos($property, ":") !== false) {
$propertyParts = \explode(":", $property);
$namespacePrefix = $propertyParts[0];
$property = $propertyParts[1];
}
$propertyBody .= "<$namespacePrefix:$property/>";
}
$body = "<?xml version=\"1.0\"?>
<d:propfind
xmlns:d=\"DAV:\"
xmlns:oc=\"http://owncloud.org/ns\"
xmlns:ocs=\"http://open-collaboration-services.org/ns\"
$extraNamespaces>
<d:prop>$propertyBody</d:prop>
</d:propfind>";
return $body;
}
/**
* sends a PROPFIND request
* with these registered namespaces:
* | prefix | namespace |
* | d | DAV: |
* | oc | http://owncloud.org/ns |
* | ocs | http://open-collaboration-services.org/ns |
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string|null $path
* @param string[] $properties
* string can contain namespace prefix,
* if no prefix is given 'd:' is used as prefix
* if associated array is used then the key will be used as namespace
* @param string|null $xRequestId
* @param string|null $folderDepth
* @param string|null $type
* @param int|null $davPathVersionToUse
* @param string|null $doDavRequestAsUser
*
* @return ResponseInterface
*/
public static function propfind(
?string $baseUrl,
?string $user,
?string $password,
?string $path,
?array $properties,
?string $xRequestId = '',
?string $folderDepth = '0',
?string $type = "files",
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
?string $doDavRequestAsUser = null
):ResponseInterface {
$body = self::getBodyForPropfind($properties);
$folderDepth = (string) $folderDepth;
if ($folderDepth !== '0' && $folderDepth !== '1' && $folderDepth !== 'infinity') {
throw new InvalidArgumentException('Invalid depth value ' . $folderDepth);
}
$headers = ['Depth' => $folderDepth];
return self::makeDavRequest(
$baseUrl,
$user,
$password,
"PROPFIND",
$path,
$headers,
$xRequestId,
$body,
$davPathVersionToUse,
$type,
null,
null,
false,
null,
null,
[],
$doDavRequestAsUser
);
}
/**
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string|null $path
* @param string|null $propertyName
* @param string|null $propertyValue
* @param string|null $xRequestId
* @param string|null $namespaceString string containing prefix and namespace
* e.g "x1='http://whatever.org/ns'"
* @param int|null $davPathVersionToUse
* @param string|null $type
*
* @return ResponseInterface
*/
public static function proppatch(
?string $baseUrl,
?string $user,
?string $password,
?string $path,
?string $propertyName,
?string $propertyValue,
?string $xRequestId = '',
?string $namespaceString = "oc='http://owncloud.org/ns'",
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
?string $type="files"
):ResponseInterface {
$matches = [];
\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
$namespace = $matches[2];
$namespacePrefix = $matches[1];
$propertyBody = "<$namespacePrefix:$propertyName" .
" xmlns:$namespacePrefix=\"$namespace\">" .
"$propertyValue" .
"</$namespacePrefix:$propertyName>";
$body = "<?xml version=\"1.0\"?>
<d:propertyupdate xmlns:d=\"DAV:\"
xmlns:oc=\"http://owncloud.org/ns\">
<d:set>
<d:prop>$propertyBody</d:prop>
</d:set>
</d:propertyupdate>";
return self::makeDavRequest(
$baseUrl,
$user,
$password,
"PROPPATCH",
$path,
[],
$xRequestId,
$body,
$davPathVersionToUse,
$type
);
}
/**
* gets namespace-prefix, namespace url and propName from provided namespaceString or property
* or otherwise use default
*
* @param string|null $namespaceString
* @param string|null $property
*
* @return array
*/
public static function getPropertyWithNamespaceInfo(?string $namespaceString = "", ?string $property = ""):array {
$namespace = "";
$namespacePrefix = "";
if (\is_int($namespaceString)) {
//default namespace prefix if the property has no array key
//also used if no prefix is given in the property value
$namespacePrefix = "d";
$namespace = "DAV:";
} elseif ($namespaceString) {
//calculate the namespace prefix and namespace from the array key
$matches = [];
\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
$namespacePrefix = $matches[1];
$namespace = $matches[2];
}
//if a namespace prefix is given in the property value use that
if ($property && \strpos($property, ":")) {
$propertyParts = \explode(":", $property);
$namespacePrefix = $propertyParts[0];
$property = $propertyParts[1];
}
return [$namespacePrefix, $namespace, $property];
}
/**
* sends HTTP request PROPPATCH method with multiple properties
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string $path
* @param array|null $propertiesArray
* @param string|null $xRequestId
* @param int|null $davPathVersion
* @param string|null $namespaceString
* @param string|null $type
*
* @return ResponseInterface
* @throws GuzzleException
*/
public static function proppatchWithMultipleProps(
?string $baseUrl,
?string $user,
?string $password,
string $path,
?array $propertiesArray,
?string $xRequestId = '',
?int $davPathVersion = null,
?string $namespaceString = "oc='http://owncloud.org/ns'",
?string $type="files"
):ResponseInterface {
$propertyBody = "";
foreach ($propertiesArray as $propertyArray) {
$property = $propertyArray["propertyName"];
$value = $propertyArray["propertyValue"];
[$namespacePrefix, $namespace, $property] = self::getPropertyWithNamespaceInfo(
$namespaceString,
$property
);
$propertyBody .= "\n\t<$namespacePrefix:$property>" .
"$value" .
"</$namespacePrefix:$property>";
}
$body = "<?xml version=\"1.0\"?>
<d:propertyupdate xmlns:d=\"DAV:\"
xmlns:oc=\"http://owncloud.org/ns\">
<d:set>
<d:prop>$propertyBody
</d:prop>
</d:set>
</d:propertyupdate>";
return self::makeDavRequest(
$baseUrl,
$user,
$password,
"PROPPATCH",
$path,
[],
$xRequestId,
$body,
$davPathVersion,
$type
);
}
/**
* returns the response to listing a folder (collection)
*
* @param string|null $baseUrl
* @param string|null $user
* @param string|null $password
* @param string|null $path
* @param string|null $folderDepth
* @param string|null $xRequestId
* @param string[] $properties
* @param string|null $type
* @param int|null $davPathVersionToUse
*
* @return ResponseInterface
*/
public static function listFolder(
?string $baseUrl,
?string $user,
?string $password,
?string $path,
?string $folderDepth,
?string $xRequestId = '',
?array $properties = null,
?string $type = "files",
?int $davPathVersionToUse = self::DAV_VERSION_NEW
):ResponseInterface {
if (!$properties) {
$properties = [
'getetag', 'resourcetype'
];
}
return self::propfind(
$baseUrl,
$user,
$password,
$path,
$properties,
$xRequestId,
$folderDepth,
$type,
$davPathVersionToUse
);
}
/**
* Generates UUIDv4
* Example: 123e4567-e89b-12d3-a456-426614174000
*
* @return string
* @throws Exception
*/
public static function generateUUIDv4():string {
// generate 16 bytes (128 bits) of random data or use the data passed into the function.
$data = random_bytes(16);
\assert(\strlen($data) == 16);
$data[6] = \chr(\ord($data[6]) & 0x0f | 0x40); // set version to 0100
$data[8] = \chr(\ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* fetches personal space id for provided user
*
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
*
* @return string
* @throws GuzzleException
* @throws Exception
*/
public static function getPersonalSpaceIdForUser(string $baseUrl, string $user, string $password, string $xRequestId):string {
if (\array_key_exists($user, self::$spacesIdRef) && \array_key_exists("personal", self::$spacesIdRef[$user])) {
return self::$spacesIdRef[$user]["personal"];
}
$trimmedBaseUrl = \trim($baseUrl, "/");
$drivesPath = '/graph/v1.0/me/drives';
$fullUrl = $trimmedBaseUrl . $drivesPath;
$response = HttpRequestHelper::get(
$fullUrl,
$xRequestId,
$user,
$password
);
$bodyContents = $response->getBody()->getContents();
$json = \json_decode($bodyContents);
$personalSpaceId = '';
if ($json === null) {
// the graph endpoint did not give a useful answer
// try getting the information from the webdav endpoint
$fullUrl = $trimmedBaseUrl . '/remote.php/webdav';
$response = HttpRequestHelper::sendRequest(
$fullUrl,
$xRequestId,
'PROPFIND',
$user,
$password
);
// we expect to get a multipart XML response with status 207
$status = $response->getStatusCode();
if ($status === 401) {
throw new SpaceNotFoundException(__METHOD__ . " Personal space not found for user " . $user);
} elseif ($status !== 207) {
throw new Exception(
__METHOD__ . " webdav propfind for user $user failed with status $status - so the personal space id cannot be discovered"
);
}
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
$xmlPart = $responseXmlObject->xpath("/d:multistatus/d:response[1]/d:propstat/d:prop/oc:id");
if ($xmlPart === false) {
throw new Exception(
__METHOD__ . " oc:id not found in webdav propfind for user $user - so the personal space id cannot be discovered"
);
}
$ocIdRawString = $xmlPart[0]->__toString();
$separator = "!";
if (\strpos($ocIdRawString, $separator) !== false) {
// The string is not base64-encoded, because the exclamation mark is not in the base64 alphabet.
// We expect to have a string with 2 parts separated by the exclamation mark.
// This is the format introduced in 2022-02
// oc:id should be something like:
// "7464caf6-1799-103c-9046-c7b74deb5f63!7464caf6-1799-103c-9046-c7b74deb5f63"
// There is no encoding to decode.
$decodedId = $ocIdRawString;
} else {
// fall-back to assuming that the oc:id is base64-encoded
// That is the format used before and up to 2022-02
// This can be removed after both the edge and master branches of cs3org/reva are using the new format.
// oc:id should be some base64 encoded string like:
// "NzQ2NGNhZjYtMTc5OS0xMDNjLTkwNDYtYzdiNzRkZWI1ZjYzOjc0NjRjYWY2LTE3OTktMTAzYy05MDQ2LWM3Yjc0ZGViNWY2Mw=="
// That should decode to something like:
// "7464caf6-1799-103c-9046-c7b74deb5f63:7464caf6-1799-103c-9046-c7b74deb5f63"
$decodedId = base64_decode($ocIdRawString);
$separator = ":";
}
$ocIdParts = \explode($separator, $decodedId);
if (\count($ocIdParts) !== 2) {
throw new Exception(
__METHOD__ . " the oc:id $decodedId for user $user does not have 2 parts separated by '$separator', so the personal space id cannot be discovered"
);
}
$personalSpaceId = $ocIdParts[0];
} else {
foreach ($json->value as $spaces) {
if ($spaces->driveType === "personal") {
$personalSpaceId = $spaces->id;
break;
}
}
}
if ($personalSpaceId) {
// If env var LOG_PERSONAL_SPACE_ID is defined, then output the details of the personal space id.
// This is a useful debugging tool to have confidence that the personal space id is found correctly.
if (\getenv('LOG_PERSONAL_SPACE_ID') !== false) {
echo __METHOD__ . " personal space id of user $user is $personalSpaceId\n";
}
self::$spacesIdRef[$user] = [];
self::$spacesIdRef[$user]["personal"] = $personalSpaceId;
return $personalSpaceId;
} else {
throw new SpaceNotFoundException(__METHOD__ . " Personal space not found for user " . $user);
}
}
/**
* First checks if a user exist to return its space ID
* In case of any exception, it returns a fake space ID
*
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
*
* @return string
* @throws Exception
*/
public static function getPersonalSpaceIdForUserOrFakeIfNotFound(string $baseUrl, string $user, string $password, string $xRequestId):string {
try {
$spaceId = self::getPersonalSpaceIdForUser(
$baseUrl,
$user,
$password,
$xRequestId,
);
} catch (SpaceNotFoundException $e) {
// if the fetch fails, and the user is not found, then a fake space id is prepared
// this is useful for testing when the personal space is of a non-existing user
$fakeSpaceId = self::generateUUIDv4();
self::$spacesIdRef[$user]["personal"] = $fakeSpaceId;
$spaceId = $fakeSpaceId;
}
return $spaceId;
}
/**
* sends a DAV request
*
* @param string|null $baseUrl
* URL of owncloud e.g. http://localhost:8080
* should include the subfolder if owncloud runs in a subfolder
* e.g. http://localhost:8080/owncloud-core
* @param string|null $user
* @param string|null $password or token when bearer auth is used
* @param string|null $method PUT, GET, DELETE, etc.
* @param string|null $path
* @param array|null $headers
* @param string|null $xRequestId
* @param string|null|resource|StreamInterface $body
* @param int|null $davPathVersionToUse (1|2|3)
* @param string|null $type of request
* @param string|null $sourceIpAddress to initiate the request from
* @param string|null $authType basic|bearer
* @param bool $stream Set to true to stream a response rather
* than download it all up-front.
* @param int|null $timeout
* @param Client|null $client
* @param array|null $urlParameter to concatenate with path
* @param string|null $doDavRequestAsUser run the DAV as this user, if null its same as $user
*
* @return ResponseInterface
* @throws GuzzleException
* @throws Exception
*/
public static function makeDavRequest(
?string $baseUrl,
?string $user,
?string $password,
?string $method,
?string $path,
?array $headers,
?string $xRequestId = '',
$body = null,
?int $davPathVersionToUse = self::DAV_VERSION_OLD,
?string $type = "files",
?string $sourceIpAddress = null,
?string $authType = "basic",
?bool $stream = false,
?int $timeout = 0,
?Client $client = null,
?array $urlParameter = [],
?string $doDavRequestAsUser = null
):ResponseInterface {
$baseUrl = self::sanitizeUrl($baseUrl, true);
// We need to manipulate and use path as a string.
// So ensure that it is a string to avoid any type-conversion errors.
if ($path === null) {
$path = "";
}
// get space id if testing with spaces dav
if (self::$SPACE_ID_FROM_OCIS === '' && $davPathVersionToUse === self::DAV_VERSION_SPACES) {
$spaceId = self::getPersonalSpaceIdForUserOrFakeIfNotFound(
$baseUrl,
$doDavRequestAsUser ?? $user,
$password,
$xRequestId
);
} else {
$spaceId = self::$SPACE_ID_FROM_OCIS;
}
$davPath = self::getDavPath($doDavRequestAsUser ?? $user, $davPathVersionToUse, $type, $spaceId);
//replace %, # and ? and in the path, Guzzle will not encode them
$urlSpecialChar = [['%', '#', '?'], ['%25', '%23', '%3F']];
$path = \str_replace($urlSpecialChar[0], $urlSpecialChar[1], $path);
if (!empty($urlParameter)) {
$urlParameter = \http_build_query($urlParameter, '', '&');
$path .= '?' . $urlParameter;
}
$fullUrl = self::sanitizeUrl($baseUrl . $davPath . $path);
if ($authType === 'bearer') {
$headers['Authorization'] = 'Bearer ' . $password;
$user = null;
$password = null;
}
if ($type === "public-files-new") {
if ($password === null || $password === "") {
$user = null;
} else {
$user = "public";
}
}
$config = null;
if ($sourceIpAddress !== null) {
$config = [ 'curl' => [ CURLOPT_INTERFACE => $sourceIpAddress ]];
}
if ($headers !== null) {
foreach ($headers as $key => $value) {
//? and # need to be encoded in the Destination URL
if ($key === "Destination") {
$headers[$key] = \str_replace(
$urlSpecialChar[0],
$urlSpecialChar[1],
$value
);
break;
}
}
}
//Clear the space ID from ocis after each request
self::$SPACE_ID_FROM_OCIS = '';
return HttpRequestHelper::sendRequest(
$fullUrl,
$xRequestId,
$method,
$user,
$password,
$headers,
$body,
$config,
null,
$stream,
$timeout,
$client
);
}
/**
* get the dav path
*
* @param string|null $user
* @param int|null $davPathVersionToUse (1|2)
* @param string|null $type
* @param string|null $spaceId
*
* @return string
*/
public static function getDavPath(
?string $user,
?int $davPathVersionToUse = null,
?string $type = "files",
?string $spaceId = null
):string {
$newTrashbinDavPath = "remote.php/dav/trash-bin/$user/";
if ($type === "public-files" || $type === "public-files-old") {
return "public.php/webdav/";
}
if ($type === "public-files-new") {
return "remote.php/dav/public-files/$user/";
}
if ($type === "archive") {
return "remote.php/dav/archive/$user/files";
}
if ($type === "customgroups") {
return "remote.php/dav/";
}
if ($davPathVersionToUse === self::DAV_VERSION_SPACES) {
if (($spaceId === null) || (\strlen($spaceId) === 0)) {
throw new InvalidArgumentException(
__METHOD__ . " A spaceId must be passed when using DAV path version 3 (spaces)"
);
}
if ($type === "trash-bin") {
return "/remote.php/dav/spaces/trash-bin/" . $spaceId . '/';
}
return "dav/spaces/" . $spaceId . '/';
} else {
if ($davPathVersionToUse === self::DAV_VERSION_OLD) {
if ($type === "trash-bin") {
// Since there is no trash bin endpoint for old dav version, new dav version's endpoint is used here.
return $newTrashbinDavPath;
}
return "remote.php/webdav/";
} elseif ($davPathVersionToUse === self::DAV_VERSION_NEW) {
if ($type === "files") {
$path = 'remote.php/dav/files/';
return $path . $user . '/';
} elseif ($type === "trash-bin") {
return $newTrashbinDavPath;
} else {
return "remote.php/dav";
}
} else {
throw new InvalidArgumentException(
"DAV path version $davPathVersionToUse is unknown"
);
}
}
}
/**
* make sure there are no double slash in the URL
*
* @param string|null $url
* @param bool|null $trailingSlash forces a trailing slash
*
* @return string
*/
public static function sanitizeUrl(?string $url, ?bool $trailingSlash = false):string {
if ($trailingSlash === true) {
$url = $url . "/";
} else {
$url = \rtrim($url, "/");
}
$url = \preg_replace("/([^:]\/)\/+/", '$1', $url);
return $url;
}
/**
* decides if the proposed dav version and chunking version are
* a valid combination.
* If no chunkingVersion is specified, then any dav version is valid.
* If a chunkingVersion is specified, then it has to match the dav version.
* Note: in future the dav and chunking versions might or might not
* move together and/or be supported together. So a more complex
* matrix could be needed here.
*
* @param string|int $davPathVersion
* @param string|int|null $chunkingVersion
*
* @return boolean is this a valid combination
*/
public static function isValidDavChunkingCombination(
$davPathVersion,
$chunkingVersion
): bool {
if ($davPathVersion === self::DAV_VERSION_SPACES) {
// allow only old chunking version when using the spaces dav
return $chunkingVersion === 1;
}
return (
($chunkingVersion === 'no' || $chunkingVersion === null) ||
($davPathVersion === $chunkingVersion)
);
}
/**
* get Mtime of File in a public link share
*
* @param string|null $baseUrl
* @param string|null $fileName
* @param string|null $token
* @param string|null $xRequestId
* @param int|null $davVersionToUse
*
* @return string
* @throws Exception
*/
public static function getMtimeOfFileinPublicLinkShare(
?string $baseUrl,
?string $fileName,
?string $token,
?string $xRequestId = '',
?int $davVersionToUse = self::DAV_VERSION_NEW
):string {
$response = self::propfind(
$baseUrl,
null,
null,
"/public-files/{$token}/{$fileName}",
['d:getlastmodified'],
$xRequestId,
'1',
null,
$davVersionToUse
);
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
$xmlPart = $responseXmlObject->xpath("//d:getlastmodified");
return $xmlPart[0]->__toString();
}
/**
* get Mtime of a resource
*
* @param string|null $user
* @param string|null $password
* @param string|null $baseUrl
* @param string|null $resource
* @param string|null $xRequestId
* @param int|null $davPathVersionToUse
*
* @return string
* @throws Exception
*/
public static function getMtimeOfResource(
?string $user,
?string $password,
?string $baseUrl,
?string $resource,
?string $xRequestId = '',
?int $davPathVersionToUse = self::DAV_VERSION_NEW
):string {
$response = self::propfind(
$baseUrl,
$user,
$password,
$resource,
["getlastmodified"],
$xRequestId,
"0",
"files",
$davPathVersionToUse
);
$responseXmlObject = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
$xmlpart = $responseXmlObject->xpath("//d:getlastmodified");
Assert::assertArrayHasKey(
0,
$xmlpart,
__METHOD__ . " XML part does not have key 0. Expected a value at index 0 of 'xmlPart' but, found: " . (string) json_encode($xmlpart)
);
$mtime = new DateTime($xmlpart[0]->__toString());
return $mtime->format('U');
}
}

View File

@@ -73,7 +73,7 @@ default:
- WebDavPropertiesContext:
- TUSContext:
- SpacesTUSContext:
apiContract:
paths:
- '%paths.base%/../features/apiContract'
@@ -152,4 +152,6 @@ default:
- TrashbinContext:
extensions:
rdx\behatvars\BehatVariablesExtension: ~
Cjm\Behat\StepThroughExtension: ~

View File

@@ -0,0 +1,591 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Joas Schilling <coding@schilljs.com>
* @author Sergio Bertolin <sbertolin@owncloud.com>
* @author Phillip Davis <phil@jankaritech.com>
* @copyright Copyright (c) 2018, ownCloud GmbH
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use GuzzleHttp\Exception\GuzzleException;
use PHPUnit\Framework\Assert;
use TestHelpers\AppConfigHelper;
use TestHelpers\OcsApiHelper;
use Behat\Gherkin\Node\TableNode;
use Behat\Behat\Context\Context;
/**
* AppConfiguration trait
*/
class AppConfigurationContext implements Context {
/**
* @var FeatureContext
*/
private $featureContext;
/**
* @When /^the administrator sets parameter "([^"]*)" of app "([^"]*)" to ((?:'[^']*')|(?:"[^"]*"))$/
*
* @param string $parameter
* @param string $app
* @param string $value
*
* @return void
* @throws Exception
*/
public function adminSetsServerParameterToUsingAPI(
string $parameter,
string $app,
string $value
):void {
// The capturing group of the regex always includes the quotes at each
// end of the captured string, so trim them.
$value = \trim($value, $value[0]);
$this->modifyAppConfig($app, $parameter, $value);
}
/**
* @Given /^parameter "([^"]*)" of app "([^"]*)" has been set to ((?:'[^']*')|(?:"[^"]*"))$/
*
* @param string $parameter
* @param string $app
* @param string $value
*
* @return void
* @throws Exception
*/
public function serverParameterHasBeenSetTo(string $parameter, string $app, string $value):void {
// The capturing group of the regex always includes the quotes at each
// end of the captured string, so trim them.
if (\TestHelpers\OcisHelper::isTestingOnOcisOrReva()) {
return;
}
$value = \trim($value, $value[0]);
$this->modifyAppConfig($app, $parameter, $value);
$this->featureContext->clearStatusCodeArrays();
}
/**
* @Then the capabilities setting of :capabilitiesApp path :capabilitiesPath should be :expectedValue
* @Given the capabilities setting of :capabilitiesApp path :capabilitiesPath has been confirmed to be :expectedValue
*
* @param string $capabilitiesApp the "app" name in the capabilities response
* @param string $capabilitiesPath the path to the element
* @param string $expectedValue
*
* @return void
* @throws Exception
*/
public function theCapabilitiesSettingOfAppParameterShouldBe(
string $capabilitiesApp,
string $capabilitiesPath,
string $expectedValue
):void {
$this->theAdministratorGetsCapabilitiesCheckResponse();
$actualValue = $this->getAppParameter($capabilitiesApp, $capabilitiesPath);
Assert::assertEquals(
$expectedValue,
$actualValue,
__METHOD__
. " $capabilitiesApp path $capabilitiesPath should be $expectedValue but is $actualValue"
);
}
/**
* @param string $capabilitiesApp the "app" name in the capabilities response
* @param string $capabilitiesPath the path to the element
*
* @return string
* @throws Exception
*/
public function getAppParameter(string $capabilitiesApp, string $capabilitiesPath):string {
return $this->getParameterValueFromXml(
$this->getCapabilitiesXml(__METHOD__),
$capabilitiesApp,
$capabilitiesPath
);
}
/**
* @When user :username retrieves the capabilities using the capabilities API
*
* @param string $username
*
* @return void
* @throws GuzzleException
* @throws JsonException
*/
public function userGetsCapabilities(string $username):void {
$user = $this->featureContext->getActualUsername($username);
$password = $this->featureContext->getPasswordForUser($user);
$this->featureContext->setResponse(
OcsApiHelper::sendRequest(
$this->featureContext->getBaseUrl(),
$user,
$password,
'GET',
'/cloud/capabilities',
$this->featureContext->getStepLineRef(),
[],
$this->featureContext->getOcsApiVersion()
)
);
}
/**
* @Given user :username has retrieved the capabilities
*
* @param string $username
*
* @return void
* @throws Exception
*/
public function userGetsCapabilitiesCheckResponse(string $username):void {
$this->userGetsCapabilities($username);
$statusCode = $this->featureContext->getResponse()->getStatusCode();
if ($statusCode !== 200) {
throw new \Exception(
__METHOD__
. " user $username returned unexpected status $statusCode"
);
}
}
/**
* @When the user retrieves the capabilities using the capabilities API
*
* @return void
*/
public function theUserGetsCapabilities():void {
$this->userGetsCapabilities($this->featureContext->getCurrentUser());
}
/**
* @Given the user has retrieved the capabilities
*
* @return void
* @throws Exception
*/
public function theUserGetsCapabilitiesCheckResponse():void {
$this->userGetsCapabilitiesCheckResponse($this->featureContext->getCurrentUser());
}
/**
* @return string
* @throws Exception
*/
public function getAdminUsernameForCapabilitiesCheck():string {
if (\TestHelpers\OcisHelper::isTestingOnReva()) {
// When testing on reva we don't have a user called "admin" to use
// to access the capabilities. So create an ordinary user on-the-fly
// with a default password. That user should be able to get a
// capabilities response that the test can process.
$adminUsername = "PseudoAdminForRevaTest";
$createdUsers = $this->featureContext->getCreatedUsers();
if (!\array_key_exists($adminUsername, $createdUsers)) {
$this->featureContext->createUser($adminUsername);
}
} else {
$adminUsername = $this->featureContext->getAdminUsername();
}
return $adminUsername;
}
/**
* @When the administrator retrieves the capabilities using the capabilities API
*
* @return void
*/
public function theAdministratorGetsCapabilities():void {
$this->userGetsCapabilities($this->getAdminUsernameForCapabilitiesCheck());
}
/**
* @Given the administrator has retrieved the capabilities
*
* @return void
* @throws Exception
*/
public function theAdministratorGetsCapabilitiesCheckResponse():void {
$this->userGetsCapabilitiesCheckResponse($this->getAdminUsernameForCapabilitiesCheck());
}
/**
* @param string $exceptionText text to put at the front of exception messages
*
* @return SimpleXMLElement latest retrieved capabilities in XML format
* @throws Exception
*/
public function getCapabilitiesXml(string $exceptionText = ''): SimpleXMLElement {
if ($exceptionText === '') {
$exceptionText = __METHOD__;
}
return $this->featureContext->getResponseXml(null, $exceptionText)->data->capabilities;
}
/**
* @param string $exceptionText text to put at the front of exception messages
*
* @return SimpleXMLElement latest retrieved version data in XML format
* @throws Exception
*/
public function getVersionXml(string $exceptionText = ''): SimpleXMLElement {
if ($exceptionText === '') {
$exceptionText = __METHOD__;
}
return $this->featureContext->getResponseXml(null, $exceptionText)->data->version;
}
/**
* @param SimpleXMLElement $xml of the capabilities
* @param string $capabilitiesApp the "app" name in the capabilities response
* @param string $capabilitiesPath the path to the element
*
* @return string
*/
public function getParameterValueFromXml(
SimpleXMLElement $xml,
string $capabilitiesApp,
string $capabilitiesPath
):string {
$path_to_element = \explode('@@@', $capabilitiesPath);
$answeredValue = $xml->{$capabilitiesApp};
foreach ($path_to_element as $element) {
$nameIndexParts = \explode('[', $element);
if (isset($nameIndexParts[1])) {
// This part of the path should be something like "some_element[1]"
// Separately extract the name and the index
$name = $nameIndexParts[0];
$index = (int) \explode(']', $nameIndexParts[1])[0];
// and use those to construct the reference into the next XML level
$answeredValue = $answeredValue->{$name}[$index];
} else {
if ($element !== "") {
$answeredValue = $answeredValue->{$element};
}
}
}
return (string) $answeredValue;
}
/**
* @param SimpleXMLElement $xml of the capabilities
* @param string $capabilitiesApp the "app" name in the capabilities response
* @param string $capabilitiesPath the path to the element
*
* @return boolean
*/
public function parameterValueExistsInXml(
SimpleXMLElement $xml,
string $capabilitiesApp,
string $capabilitiesPath
):bool {
$path_to_element = \explode('@@@', $capabilitiesPath);
$answeredValue = $xml->{$capabilitiesApp};
foreach ($path_to_element as $element) {
$nameIndexParts = \explode('[', $element);
if (isset($nameIndexParts[1])) {
// This part of the path should be something like "some_element[1]"
// Separately extract the name and the index
$name = $nameIndexParts[0];
$index = (int) \explode(']', $nameIndexParts[1])[0];
// and use those to construct the reference into the next XML level
if (isset($answeredValue->{$name}[$index])) {
$answeredValue = $answeredValue->{$name}[$index];
} else {
// The path ends at this level
return false;
}
} else {
if (isset($answeredValue->{$element})) {
$answeredValue = $answeredValue->{$element};
} else {
// The path ends at this level
return false;
}
}
}
return true;
}
/**
* @param string $app
* @param string $parameter
* @param string $value
*
* @return void
* @throws Exception
*/
public function modifyAppConfig(string $app, string $parameter, string $value):void {
AppConfigHelper::modifyAppConfig(
$this->featureContext->getBaseUrl(),
$this->featureContext->getAdminUsername(),
$this->featureContext->getAdminPassword(),
$app,
$parameter,
$value,
$this->featureContext->getStepLineRef(),
$this->featureContext->getOcsApiVersion()
);
}
/**
* @param array $appParameterValues
*
* @return void
* @throws Exception
*/
public function modifyAppConfigs(array $appParameterValues):void {
AppConfigHelper::modifyAppConfigs(
$this->featureContext->getBaseUrl(),
$this->featureContext->getAdminUsername(),
$this->featureContext->getAdminPassword(),
$appParameterValues,
$this->featureContext->getStepLineRef(),
$this->featureContext->getOcsApiVersion()
);
}
/**
* @When the administrator adds url :url as trusted server using the testing API
*
* @param string $url
*
* @return void
* @throws GuzzleException
*/
public function theAdministratorAddsUrlAsTrustedServerUsingTheTestingApi(string $url):void {
$adminUser = $this->featureContext->getAdminUsername();
$response = OcsApiHelper::sendRequest(
$this->featureContext->getBaseUrl(),
$adminUser,
$this->featureContext->getAdminPassword(),
'POST',
"/apps/testing/api/v1/trustedservers",
$this->featureContext->getStepLineRef(),
['url' => $this->featureContext->substituteInLineCodes($url)]
);
$this->featureContext->setResponse($response);
$this->featureContext->pushToLastStatusCodesArrays();
}
/**
* Return text that contains the details of the URL, including any differences due to inline codes
*
* @param string $url
*
* @return string
*/
private function getUrlStringForMessage(string $url):string {
$text = $url;
$expectedUrl = $this->featureContext->substituteInLineCodes($url);
if ($expectedUrl !== $url) {
$text .= " ($expectedUrl)";
}
return $text;
}
/**
* @param string $url
*
* @return string
*/
private function getNotTrustedServerMessage(string $url):string {
return
"URL "
. $this->getUrlStringForMessage($url)
. " is not a trusted server but should be";
}
/**
* @Then url :url should be a trusted server
*
* @param string $url
*
* @return void
* @throws Exception
*/
public function urlShouldBeATrustedServer(string $url):void {
$trustedServers = $this->featureContext->getTrustedServers();
foreach ($trustedServers as $server => $id) {
if ($server === $this->featureContext->substituteInLineCodes($url)) {
return;
}
}
Assert::fail($this->getNotTrustedServerMessage($url));
}
/**
* @Then the trusted server list should include these urls:
*
* @param TableNode $table
*
* @return void
* @throws Exception
*/
public function theTrustedServerListShouldIncludeTheseUrls(TableNode $table):void {
$trustedServers = $this->featureContext->getTrustedServers();
$expected = $table->getColumnsHash();
foreach ($expected as $server) {
$found = false;
foreach ($trustedServers as $url => $id) {
if ($url === $this->featureContext->substituteInLineCodes($server['url'])) {
$found = true;
break;
}
}
if (!$found) {
Assert::fail($this->getNotTrustedServerMessage($server['url']));
}
}
}
/**
* @Given the administrator has added url :url as trusted server
*
* @param string $url
*
* @return void
* @throws Exception
* @throws GuzzleException
*/
public function theAdministratorHasAddedUrlAsTrustedServer(string $url):void {
$this->theAdministratorAddsUrlAsTrustedServerUsingTheTestingApi($url);
$status = $this->featureContext->getResponse()->getStatusCode();
if ($status !== 201) {
throw new \Exception(
__METHOD__ .
" Could not add trusted server " . $this->getUrlStringForMessage($url)
. ". The request failed with status $status"
);
}
}
/**
* @When the administrator deletes url :url from trusted servers using the testing API
*
* @param string $url
*
* @return void
* @throws GuzzleException
*/
public function theAdministratorDeletesUrlFromTrustedServersUsingTheTestingApi(string $url):void {
$adminUser = $this->featureContext->getAdminUsername();
$response = OcsApiHelper::sendRequest(
$this->featureContext->getBaseUrl(),
$adminUser,
$this->featureContext->getAdminPassword(),
'DELETE',
"/apps/testing/api/v1/trustedservers",
$this->featureContext->getStepLineRef(),
['url' => $this->featureContext->substituteInLineCodes($url)]
);
$this->featureContext->setResponse($response);
}
/**
* @Then url :url should not be a trusted server
*
* @param string $url
*
* @return void
* @throws Exception
*/
public function urlShouldNotBeATrustedServer(string $url):void {
$trustedServers = $this->featureContext->getTrustedServers();
foreach ($trustedServers as $server => $id) {
if ($server === $this->featureContext->substituteInLineCodes($url)) {
Assert::fail(
"URL " . $this->getUrlStringForMessage($url)
. " is a trusted server but is not expected to be"
);
}
}
}
/**
* @When the administrator deletes all trusted servers using the testing API
*
* @return void
* @throws GuzzleException
*/
public function theAdministratorDeletesAllTrustedServersUsingTheTestingApi():void {
$adminUser = $this->featureContext->getAdminUsername();
$response = OcsApiHelper::sendRequest(
$this->featureContext->getBaseUrl(),
$adminUser,
$this->featureContext->getAdminPassword(),
'DELETE',
"/apps/testing/api/v1/trustedservers/all",
$this->featureContext->getStepLineRef()
);
$this->featureContext->setResponse($response);
}
/**
* @Given the trusted server list is cleared
*
* @return void
* @throws Exception
*/
public function theTrustedServerListIsCleared():void {
$this->theAdministratorDeletesAllTrustedServersUsingTheTestingApi();
$statusCode = $this->featureContext->getResponse()->getStatusCode();
if ($statusCode !== 204) {
$contents = $this->featureContext->getResponse()->getBody()->getContents();
throw new \Exception(
__METHOD__
. " Failed to clear all trusted servers" . $contents
);
}
}
/**
* @Then the trusted server list should be empty
*
* @return void
* @throws Exception
*/
public function theTrustedServerListShouldBeEmpty():void {
$trustedServers = $this->featureContext->getTrustedServers();
Assert::assertEmpty(
$trustedServers,
__METHOD__ . " Trusted server list is not empty"
);
}
/**
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function setUpScenario(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Joas Schilling <coding@schilljs.com>
* @author Sergio Bertolin <sbertolin@owncloud.com>
* @author Phillip Davis <phil@jankaritech.com>
* @copyright Copyright (c) 2018, ownCloud GmbH
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
require_once 'bootstrap.php';
/**
* Capabilities context.
*/
class CapabilitiesContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
/**
* @Then the capabilities should contain
*
* @param TableNode|null $formData
*
* @return void
* @throws Exception
*/
public function checkCapabilitiesResponse(TableNode $formData):void {
$capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__);
$assertedSomething = false;
$this->featureContext->verifyTableNodeColumns($formData, ['value', 'path_to_element', 'capability']);
foreach ($formData->getHash() as $row) {
$row['value'] = $this->featureContext->substituteInLineCodes($row['value']);
Assert::assertEquals(
$row['value'] === "EMPTY" ? '' : $row['value'],
$this->featureContext->appConfigurationContext->getParameterValueFromXml(
$capabilitiesXML,
$row['capability'],
$row['path_to_element']
),
"Failed field {$row['capability']} {$row['path_to_element']}"
);
$assertedSomething = true;
}
Assert::assertTrue(
$assertedSomething,
'there was nothing in the table of expected capabilities'
);
}
/**
* @Then the version data in the response should contain
*
* @param TableNode|null $formData
*
* @return void
* @throws Exception
*/
public function checkVersionResponse(TableNode $formData):void {
$versionXML = $this->featureContext->appConfigurationContext->getVersionXml(__METHOD__);
$assertedSomething = false;
$this->featureContext->verifyTableNodeColumns($formData, ['name', 'value']);
foreach ($formData->getHash() as $row) {
$row['value'] = $this->featureContext->substituteInLineCodes($row['value']);
$actualValue = $versionXML->{$row['name']};
Assert::assertEquals(
$row['value'] === "EMPTY" ? '' : $row['value'],
$actualValue,
"Failed field {$row['name']}"
);
$assertedSomething = true;
}
Assert::assertTrue(
$assertedSomething,
'there was nothing in the table of expected version data'
);
}
/**
* @Then the major-minor-micro version data in the response should match the version string
*
* @return void
* @throws Exception
*/
public function checkVersionMajorMinorMicroResponse():void {
$versionXML = $this->featureContext->appConfigurationContext->getVersionXml(__METHOD__);
$versionString = (string) $versionXML->string;
// We expect that versionString will be in a format like "10.9.2 beta" or "10.9.2-alpha" or "10.9.2"
$result = \preg_match('/^[0-9]+\.[0-9]+\.[0-9]+/', $versionString, $matches);
Assert::assertSame(
1,
$result,
__METHOD__ . " version string '$versionString' does not start with a semver version"
);
// semVerParts should have an array with the 3 semver components of the version, e.g. "1", "9" and "2".
$semVerParts = \explode('.', $matches[0]);
$expectedMajor = $semVerParts[0];
$expectedMinor = $semVerParts[1];
$expectedMicro = $semVerParts[2];
$actualMajor = (string) $versionXML->major;
$actualMinor = (string) $versionXML->minor;
$actualMicro = (string) $versionXML->micro;
Assert::assertSame(
$expectedMajor,
$actualMajor,
__METHOD__ . "'major' data item does not match with major version in string '$versionString'"
);
Assert::assertSame(
$expectedMinor,
$actualMinor,
__METHOD__ . "'minor' data item does not match with minor version in string '$versionString'"
);
Assert::assertSame(
$expectedMicro,
$actualMicro,
__METHOD__ . "'micro' data item does not match with micro (patch) version in string '$versionString'"
);
}
/**
* @Then the :pathToElement capability of files sharing app should be :value
*
* @param string $pathToElement
* @param string $value
*
* @return void
* @throws Exception
*/
public function theCapabilityOfFilesSharingAppShouldBe(
string $pathToElement,
string $value
):void {
$this->featureContext->appConfigurationContext->userGetsCapabilitiesCheckResponse(
$this->featureContext->getCurrentUser()
);
$capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__);
$actualValue = $this->featureContext->appConfigurationContext->getParameterValueFromXml(
$capabilitiesXML,
"files_sharing",
$pathToElement
);
Assert::assertEquals(
$value === "EMPTY" ? '' : $value,
$actualValue,
"Expected {$pathToElement} capability of files sharing app to be {$value}, but got {$actualValue}"
);
}
/**
* @Then the capabilities should not contain
*
* @param TableNode|null $formData
*
* @return void
*/
public function theCapabilitiesShouldNotContain(TableNode $formData):void {
$capabilitiesXML = $this->featureContext->appConfigurationContext->getCapabilitiesXml(__METHOD__);
$assertedSomething = false;
foreach ($formData->getHash() as $row) {
Assert::assertFalse(
$this->featureContext->appConfigurationContext->parameterValueExistsInXml(
$capabilitiesXML,
$row['capability'],
$row['path_to_element']
),
"Capability {$row['capability']} {$row['path_to_element']} exists but it should not exist"
);
$assertedSomething = true;
}
Assert::assertTrue(
$assertedSomething,
'there was nothing in the table of not expected capabilities'
);
}
/**
* This will run before EVERY scenario.
* It will set the properties for this object.
*
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function before(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
}

View File

@@ -0,0 +1,464 @@
<?php declare(strict_types=1);
/**
* @author Roeland Jago Douma <rullzer@owncloud.com>
*
* @copyright Copyright (c) 2018, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use PHPUnit\Framework\Assert;
use TestHelpers\WebDavHelper;
require_once 'bootstrap.php';
/**
* Checksum functions
*/
class ChecksumContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
/**
* @When user :user uploads file :source to :destination with checksum :checksum using the WebDAV API
*
* @param string $user
* @param string $source
* @param string $destination
* @param string $checksum
*
* @return void
*/
public function userUploadsFileToWithChecksumUsingTheAPI(
string $user,
string $source,
string $destination,
string $checksum
):void {
$file = \file_get_contents(
$this->featureContext->acceptanceTestsDirLocation() . $source
);
$response = $this->featureContext->makeDavRequest(
$user,
'PUT',
$destination,
['OC-Checksum' => $checksum],
$file,
"files"
);
$this->featureContext->setResponse($response);
}
/**
* @Given user :user has uploaded file :source to :destination with checksum :checksum
*
* @param string $user
* @param string $source
* @param string $destination
* @param string $checksum
*
* @return void
*/
public function userHasUploadedFileToWithChecksumUsingTheAPI(
string $user,
string $source,
string $destination,
string $checksum
):void {
$user = $this->featureContext->getActualUsername($user);
$this->userUploadsFileToWithChecksumUsingTheAPI(
$user,
$source,
$destination,
$checksum
);
$this->featureContext->theHTTPStatusCodeShouldBeSuccess();
}
/**
* @When user :user uploads file with content :content and checksum :checksum to :destination using the WebDAV API
*
* @param string $user
* @param string $content
* @param string $checksum
* @param string $destination
*
* @return void
*/
public function userUploadsFileWithContentAndChecksumToUsingTheAPI(
string $user,
string $content,
string $checksum,
string $destination
):void {
$response = $this->featureContext->makeDavRequest(
$user,
'PUT',
$destination,
['OC-Checksum' => $checksum],
$content,
"files"
);
$this->featureContext->setResponse($response);
}
/**
* @Given user :user has uploaded file with content :content and checksum :checksum to :destination
*
* @param string $user
* @param string $content
* @param string $checksum
* @param string $destination
*
* @return void
*/
public function userHasUploadedFileWithContentAndChecksumToUsingTheAPI(
string $user,
string $content,
string $checksum,
string $destination
):void {
$user = $this->featureContext->getActualUsername($user);
$this->userUploadsFileWithContentAndChecksumToUsingTheAPI(
$user,
$content,
$checksum,
$destination
);
$this->featureContext->theHTTPStatusCodeShouldBeSuccess();
}
/**
* @When user :user requests the checksum of :path via propfind
*
* @param string $user
* @param string $path
*
* @return void
*/
public function userRequestsTheChecksumOfViaPropfind(string $user, string $path):void {
$user = $this->featureContext->getActualUsername($user);
$body = '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:checksums />
</d:prop>
</d:propfind>';
$password = $this->featureContext->getPasswordForUser($user);
$response = WebDavHelper::makeDavRequest(
$this->featureContext->getBaseUrl(),
$user,
$password,
'PROPFIND',
$path,
null,
$this->featureContext->getStepLineRef(),
$body,
$this->featureContext->getDavPathVersion()
);
$this->featureContext->setResponse($response);
}
/**
* @Then the webdav checksum should match :expectedChecksum
*
* @param string $expectedChecksum
*
* @return void
* @throws Exception
*/
public function theWebdavChecksumShouldMatch(string $expectedChecksum):void {
$service = new Sabre\Xml\Service();
$bodyContents = $this->featureContext->getResponse()->getBody()->getContents();
$parsed = $service->parse($bodyContents);
/*
* Fetch the checksum array
* The checksums are way down in the array:
* $checksums = $parsed[0]['value'][1]['value'][0]['value'][0];
* And inside is the actual checksum string:
* $checksums['value'][0]['value']
* The Asserts below check the existence of the expected key at every level
* of the nested array. This helps to see what happened if a test fails
* because the response structure is not as expected.
*/
Assert::assertIsArray(
$parsed,
__METHOD__ . " could not parse response as XML. Expected parsed XML to be an array but found " . $bodyContents
);
Assert::assertArrayHasKey(
0,
$parsed,
__METHOD__ . " parsed XML does not have key 0"
);
$parsed0 = $parsed[0];
Assert::assertArrayHasKey(
'value',
$parsed0,
__METHOD__ . " parsed XML parsed0 does not have key value"
);
$parsed0Value = $parsed0['value'];
Assert::assertArrayHasKey(
1,
$parsed0Value,
__METHOD__ . " parsed XML parsed0Value does not have key 1"
);
$parsed0Value1 = $parsed0Value[1];
Assert::assertArrayHasKey(
'value',
$parsed0Value1,
__METHOD__ . " parsed XML parsed0Value1 does not have key value after key 1"
);
$parsed0Value1Value = $parsed0Value1['value'];
Assert::assertArrayHasKey(
0,
$parsed0Value1Value,
__METHOD__ . " parsed XML parsed0Value1Value does not have key 0"
);
$parsed0Value1Value0 = $parsed0Value1Value[0];
Assert::assertArrayHasKey(
'value',
$parsed0Value1Value0,
__METHOD__ . " parsed XML parsed0Value1Value0 does not have key value"
);
$parsed0Value1Value0Value = $parsed0Value1Value0['value'];
Assert::assertArrayHasKey(
0,
$parsed0Value1Value0Value,
__METHOD__ . " parsed XML parsed0Value1Value0Value does not have key 0"
);
$checksums = $parsed0Value1Value0Value[0];
Assert::assertArrayHasKey(
'value',
$checksums,
__METHOD__ . " parsed XML checksums does not have key value"
);
$checksumsValue = $checksums['value'];
Assert::assertArrayHasKey(
0,
$checksumsValue,
__METHOD__ . " parsed XML checksumsValue does not have key 0"
);
$checksumsValue0 = $checksumsValue[0];
Assert::assertArrayHasKey(
'value',
$checksumsValue0,
__METHOD__ . " parsed XML checksumsValue0 does not have key value"
);
$actualChecksum = $checksumsValue0['value'];
Assert::assertEquals(
$expectedChecksum,
$actualChecksum,
"Expected: webDav checksum should be {$expectedChecksum} but got {$actualChecksum}"
);
}
/**
* @Then as user :user the webdav checksum of :path via propfind should match :expectedChecksum
*
* @param string $user
* @param string $path
* @param string $expectedChecksum
*
* @return void
* @throws Exception
*/
public function theWebdavChecksumOfViaPropfindShouldMatch(string $user, string $path, string $expectedChecksum):void {
$user = $this->featureContext->getActualUsername($user);
$this->userRequestsTheChecksumOfViaPropfind($user, $path);
$this->theWebdavChecksumShouldMatch($expectedChecksum);
}
/**
* @Then the header checksum should match :expectedChecksum
*
* @param string $expectedChecksum
*
* @return void
* @throws Exception
*/
public function theHeaderChecksumShouldMatch(string $expectedChecksum):void {
$headerChecksums
= $this->featureContext->getResponse()->getHeader('OC-Checksum');
Assert::assertIsArray(
$headerChecksums,
__METHOD__ . " getHeader('OC-Checksum') did not return an array"
);
Assert::assertNotEmpty(
$headerChecksums,
__METHOD__ . " getHeader('OC-Checksum') returned an empty array. No checksum header was found."
);
$checksumCount = \count($headerChecksums);
Assert::assertTrue(
$checksumCount === 1,
__METHOD__ . " Expected 1 checksum in the header but found $checksumCount checksums"
);
$headerChecksum
= $headerChecksums[0];
Assert::assertEquals(
$expectedChecksum,
$headerChecksum,
"Expected: header checksum should match {$expectedChecksum} but got {$headerChecksum}"
);
}
/**
* @Then the header checksum when user :arg1 downloads file :arg2 using the WebDAV API should match :arg3
*
* @param string $user
* @param string $fileName
* @param string $expectedChecksum
*
* @return void
* @throws Exception
*/
public function theHeaderChecksumWhenUserDownloadsFileUsingTheWebdavApiShouldMatch(string $user, string $fileName, string $expectedChecksum):void {
$this->featureContext->userDownloadsFileUsingTheAPI($user, $fileName);
$this->theHeaderChecksumShouldMatch($expectedChecksum);
}
/**
* @Then the webdav checksum should be empty
*
* @return void
* @throws Exception
*/
public function theWebdavChecksumShouldBeEmpty():void {
$service = new Sabre\Xml\Service();
$parsed = $service->parse(
$this->featureContext->getResponse()->getBody()->getContents()
);
/*
* Fetch the checksum array
* Maybe we want to do this a bit cleaner ;)
*/
$status = $parsed[0]['value'][1]['value'][1]['value'];
$expectedStatus = 'HTTP/1.1 404 Not Found';
Assert::assertEquals(
$expectedStatus,
$status,
"Expected status to be {$expectedStatus} but got {$status}"
);
}
/**
* @Then the OC-Checksum header should not be there
*
* @return void
* @throws Exception
*/
public function theOcChecksumHeaderShouldNotBeThere():void {
$isHeader = $this->featureContext->getResponse()->hasHeader('OC-Checksum');
Assert::assertFalse(
$isHeader,
"Expected no checksum header but got "
. $this->featureContext->getResponse()->getHeader('OC-Checksum')
);
}
/**
* @When user :user uploads chunk file :num of :total with :data to :destination with checksum :expectedChecksum using the WebDAV API
*
* @param string $user
* @param int $num
* @param int $total
* @param string $data
* @param string $destination
* @param string $expectedChecksum
*
* @return void
*/
public function userUploadsChunkFileOfWithToWithChecksum(
string $user,
int $num,
int $total,
string $data,
string $destination,
string $expectedChecksum
):void {
$user = $this->featureContext->getActualUsername($user);
$num -= 1;
$file = "$destination-chunking-42-$total-$num";
$response = $this->featureContext->makeDavRequest(
$user,
'PUT',
$file,
['OC-Checksum' => $expectedChecksum, 'OC-Chunked' => '1'],
$data,
"files"
);
$this->featureContext->setResponse($response);
}
/**
* @Given user :user has uploaded chunk file :num of :total with :data to :destination with checksum :expectedChecksum
*
* @param string $user
* @param int $num
* @param int $total
* @param string $data
* @param string $destination
* @param string $expectedChecksum
*
* @return void
*/
public function userHasUploadedChunkFileOfWithToWithChecksum(
string $user,
int $num,
int $total,
string $data,
string $destination,
string $expectedChecksum
):void {
$this->userUploadsChunkFileOfWithToWithChecksum(
$user,
$num,
$total,
$data,
$destination,
$expectedChecksum
);
$this->featureContext->theHTTPStatusCodeShouldBeOr(201, 206);
}
/**
* This will run before EVERY scenario.
* It will set the properties for this object.
*
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function before(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
}

View File

@@ -0,0 +1,361 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use Psr\Http\Message\ResponseInterface;
use TestHelpers\WebDavHelper;
require_once 'bootstrap.php';
/**
* context containing favorites related API steps
*/
class FavoritesContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
/**
*
* @var WebDavPropertiesContext
*/
private $webDavPropertiesContext;
/**
* @param string$user
* @param string $path
*
* @return void
*/
public function userFavoritesElement(string $user, string $path):void {
$response = $this->changeFavStateOfAnElement(
$user,
$path,
1
);
$this->featureContext->setResponse($response);
}
/**
* @When user :user favorites element :path using the WebDAV API
*
* @param string $user
* @param string $path
*
* @return void
*/
public function userFavoritesElementUsingWebDavApi(string $user, string $path):void {
$this->userFavoritesElement($user, $path);
}
/**
* @Given user :user has favorited element :path
*
* @param string $user
* @param string $path
*
* @return void
*/
public function userHasFavoritedElementUsingWebDavApi(string $user, string $path):void {
$this->userFavoritesElement($user, $path);
$this->featureContext->theHTTPStatusCodeShouldBeSuccess();
}
/**
* @When the user favorites element :path using the WebDAV API
*
* @param string $path
*
* @return void
*/
public function theUserFavoritesElement(string $path):void {
$this->userFavoritesElement(
$this->featureContext->getCurrentUser(),
$path
);
}
/**
* @Given the user has favorited element :path
*
* @param string $path
*
* @return void
*/
public function theUserHasFavoritedElement(string $path):void {
$this->userFavoritesElement(
$this->featureContext->getCurrentUser(),
$path
);
$this->featureContext->theHTTPStatusCodeShouldBe(
207,
"Expected response status code to be 207 (Multi-status), but not found! "
);
}
/**
* @param $user
* @param $path
*
* @return void
*/
public function userUnfavoritesElement(string $user, string $path):void {
$response = $this->changeFavStateOfAnElement(
$user,
$path,
0
);
$this->featureContext->setResponse($response);
}
/**
* @When user :user unfavorites element :path using the WebDAV API
*
* @param string $user
* @param string $path
*
* @return void
*/
public function userUnfavoritesElementUsingWebDavApi(string $user, string $path):void {
$this->userUnfavoritesElement($user, $path);
}
/**
* @Given user :user has unfavorited element :path
*
* @param string $user
* @param string $path
*
* @return void
*/
public function userHasUnfavoritedElementUsingWebDavApi(string $user, string $path):void {
$this->userUnfavoritesElement($user, $path);
$this->featureContext->theHTTPStatusCodeShouldBeSuccess();
}
/**
* @Then /^user "([^"]*)" should (not|)\s?have favorited the following elements$/
*
* @param string $user
* @param string $shouldOrNot (not|)
* @param TableNode $expectedElements
*
* @return void
*/
public function checkFavoritedElements(
string $user,
string $shouldOrNot,
TableNode $expectedElements
):void {
$user = $this->featureContext->getActualUsername($user);
$this->userListsFavorites($user, null);
$this->featureContext->propfindResultShouldContainEntries(
$shouldOrNot,
$expectedElements,
$user
);
}
/**
* @When /^user "([^"]*)" lists the favorites and limits the result to ([\d*]) elements using the WebDAV API$/
*
* @param string $user
* @param int|null $limit
*
* @return void
*/
public function userListsFavorites(string $user, ?int $limit = null):void {
$renamedUser = $this->featureContext->getActualUsername($user);
$baseUrl = $this->featureContext->getBaseUrl();
$password = $this->featureContext->getPasswordForUser($user);
$body
= "<?xml version='1.0' encoding='utf-8' ?>\n" .
" <oc:filter-files xmlns:a='DAV:' xmlns:oc='http://owncloud.org/ns' >\n" .
" <a:prop><oc:favorite/></a:prop>\n" .
" <oc:filter-rules><oc:favorite>1</oc:favorite></oc:filter-rules>\n";
if ($limit !== null) {
$body .= " <oc:search>\n" .
" <oc:limit>$limit</oc:limit>\n" .
" </oc:search>\n";
}
$body .= " </oc:filter-files>";
$response = WebDavHelper::makeDavRequest(
$baseUrl,
$renamedUser,
$password,
"REPORT",
"/",
null,
$this->featureContext->getStepLineRef(),
$body,
$this->featureContext->getDavPathVersion()
);
$this->featureContext->setResponse($response);
}
/**
* @param string $path
*
* @return void
*/
public function theUserUnfavoritesElement(string $path):void {
$this->userUnfavoritesElement(
$this->featureContext->getCurrentUser(),
$path
);
}
/**
* @When the user unfavorites element :path using the WebDAV API
*
* @param string $path
*
* @return void
*/
public function theUserUnfavoritesElementUsingWebDavApi(string $path):void {
$this->theUserUnfavoritesElement($path);
}
/**
* @Given the user has unfavorited element :path
*
* @param string $path
*
* @return void
*/
public function theUserHasUnfavoritedElementUsingWebDavApi(string $path):void {
$this->theUserUnfavoritesElement($path);
$this->featureContext->theHTTPStatusCodeShouldBeSuccess();
}
/**
* @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should be favorited$/
*
* @param string $user
* @param string $path
* @param integer $expectedValue 0|1
*
* @return void
*/
public function asUserFileOrFolderShouldBeFavorited(string $user, string $path, int $expectedValue = 1):void {
$property = "oc:favorite";
$this->webDavPropertiesContext->asUserFolderShouldContainAPropertyWithValue(
$user,
$path,
$property,
(string)$expectedValue
);
}
/**
* @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should not be favorited$/
*
* @param string $user
* @param string $path
*
* @return void
*/
public function asUserFileShouldNotBeFavorited(string $user, string $path):void {
$this->asUserFileOrFolderShouldBeFavorited($user, $path, 0);
}
/**
* @Then /^as the user (?:file|folder|entry) "([^"]*)" should be favorited$/
*
* @param string $path
* @param integer $expectedValue 0|1
*
* @return void
*/
public function asTheUserFileOrFolderShouldBeFavorited(string $path, int $expectedValue = 1):void {
$this->asUserFileOrFolderShouldBeFavorited(
$this->featureContext->getCurrentUser(),
$path,
$expectedValue
);
}
/**
* @Then /^as the user (?:file|folder|entry) "([^"]*)" should not be favorited$/
*
* @param string $path
*
* @return void
*/
public function asTheUserFileOrFolderShouldNotBeFavorited(string $path):void {
$this->asTheUserFileOrFolderShouldBeFavorited($path, 0);
}
/**
* Set the elements of a proppatch
*
* @param string $user
* @param string $path
* @param int|null $favOrUnfav 1 = favorite, 0 = unfavorite
*
* @return ResponseInterface
*/
public function changeFavStateOfAnElement(
string $user,
string $path,
?int $favOrUnfav
):ResponseInterface {
$renamedUser = $this->featureContext->getActualUsername($user);
return WebDavHelper::proppatch(
$this->featureContext->getBaseUrl(),
$renamedUser,
$this->featureContext->getPasswordForUser($user),
$path,
'favorite',
(string)$favOrUnfav,
$this->featureContext->getStepLineRef(),
"oc='http://owncloud.org/ns'",
$this->featureContext->getDavPathVersion()
);
}
/**
* This will run before EVERY scenario.
* It will set the properties for this object.
*
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function before(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
$this->webDavPropertiesContext = $environment->getContext(
'WebDavPropertiesContext'
);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,443 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2018, ownCloud GmbH
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
use TestHelpers\HttpRequestHelper;
use TestHelpers\WebDavHelper;
require_once 'bootstrap.php';
/**
* Steps that relate to files_versions app
*/
class FilesVersionsContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
/**
* @param string $fileId
*
* @return string
*/
private function getVersionsPathForFileId(string $fileId):string {
return "/meta/$fileId/v";
}
/**
* @When user :user tries to get versions of file :file from :fileOwner
*
* @param string $user
* @param string $file
* @param string $fileOwner
*
* @return void
* @throws Exception
*/
public function userTriesToGetFileVersions(string $user, string $file, string $fileOwner):void {
$user = $this->featureContext->getActualUsername($user);
$fileOwner = $this->featureContext->getActualUsername($fileOwner);
$fileId = $this->featureContext->getFileIdForPath($fileOwner, $file);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $fileOwner not found (the file may not exist)");
$response = $this->featureContext->makeDavRequest(
$user,
"PROPFIND",
$this->getVersionsPathForFileId($fileId),
null,
null,
null,
'2'
);
$this->featureContext->setResponse($response, $user);
}
/**
* @When user :user gets the number of versions of file :file
*
* @param string $user
* @param string $file
*
* @return void
* @throws Exception
*/
public function userGetsFileVersions(string $user, string $file):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $file);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $user not found (the file may not exist)");
$response = $this->featureContext->makeDavRequest(
$user,
"PROPFIND",
$this->getVersionsPathForFileId($fileId),
null,
null,
null,
'2'
);
$this->featureContext->setResponse($response, $user);
}
/**
* @When user :user gets the version metadata of file :file
*
* @param string $user
* @param string $file
*
* @return void
* @throws Exception
*/
public function userGetsVersionMetadataOfFile(string $user, string $file):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $file);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $file user $user not found (the file may not exist)");
$body = '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:meta-version-edited-by />
<oc:meta-version-edited-by-name />
</d:prop>
</d:propfind>';
$response = $this->featureContext->makeDavRequest(
$user,
"PROPFIND",
$this->getVersionsPathForFileId($fileId),
null,
$body,
null,
'2'
);
$this->featureContext->setResponse($response, $user);
}
/**
* @When user :user restores version index :versionIndex of file :path using the WebDAV API
* @Given user :user has restored version index :versionIndex of file :path
*
* @param string $user
* @param int $versionIndex
* @param string $path
*
* @return void
* @throws Exception
*/
public function userRestoresVersionIndexOfFile(string $user, int $versionIndex, string $path):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $path);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)");
$responseXml = $this->listVersionFolder($user, $fileId, 1);
$xmlPart = $responseXml->xpath("//d:response/d:href");
//restoring the version only works with DAV path v2
$destinationUrl = $this->featureContext->getBaseUrl() . "/" .
WebDavHelper::getDavPath($user, 2) . \trim($path, "/");
$fullUrl = $this->featureContext->getBaseUrlWithoutPath() .
$xmlPart[$versionIndex];
$response = HttpRequestHelper::sendRequest(
$fullUrl,
$this->featureContext->getStepLineRef(),
'COPY',
$user,
$this->featureContext->getPasswordForUser($user),
['Destination' => $destinationUrl]
);
$this->featureContext->setResponse($response, $user);
}
/**
* @Then the version folder of file :path for user :user should contain :count element(s)
*
* @param string $path
* @param string $user
* @param int $count
*
* @return void
* @throws Exception
*/
public function theVersionFolderOfFileShouldContainElements(
string $path,
string $user,
int $count
):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $path);
Assert::assertNotNull($fileId, __METHOD__ . " file $path user $user not found (the file may not exist)");
$this->theVersionFolderOfFileIdShouldContainElements($fileId, $user, $count);
}
/**
* @Then the version folder of fileId :fileId for user :user should contain :count element(s)
*
* @param string $fileId
* @param string $user
* @param int $count
*
* @return void
* @throws Exception
*/
public function theVersionFolderOfFileIdShouldContainElements(
string $fileId,
string $user,
int $count
):void {
$responseXml = $this->listVersionFolder($user, $fileId, 1);
$xmlPart = $responseXml->xpath("//d:prop/d:getetag");
Assert::assertEquals(
$count,
\count($xmlPart) - 1,
"could not find $count version element(s) in \n" . $responseXml->asXML()
);
}
/**
* @Then the content length of file :path with version index :index for user :user in versions folder should be :length
*
* @param string $path
* @param int $index
* @param string $user
* @param int $length
*
* @return void
* @throws Exception
*/
public function theContentLengthOfFileForUserInVersionsFolderIs(
string $path,
int $index,
string $user,
int $length
):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $path);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)");
$responseXml = $this->listVersionFolder(
$user,
$fileId,
1,
['getcontentlength']
);
$xmlPart = $responseXml->xpath("//d:prop/d:getcontentlength");
Assert::assertEquals(
$length,
(int) $xmlPart[$index],
"The content length of file {$path} with version {$index} for user {$user} was
expected to be {$length} but the actual content length is {$xmlPart[$index]}"
);
}
/**
* @Then /^as (?:users|user) "([^"]*)" the authors of the versions of file "([^"]*)" should be:$/
*
* @param string $users comma-separated list of usernames
* @param string $filename
* @param TableNode $table
*
* @return void
* @throws Exception
*/
public function asUsersAuthorsOfVersionsOfFileShouldBe(
string $users,
string $filename,
TableNode $table
): void {
$this->featureContext->verifyTableNodeColumns(
$table,
['index', 'author']
);
$requiredVersionMetadata = $table->getHash();
$usersArray = \explode(",", $users);
foreach ($usersArray as $username) {
$actualUsername = $this->featureContext->getActualUsername($username);
$this->userGetsVersionMetadataOfFile($actualUsername, $filename);
foreach ($requiredVersionMetadata as $versionMetadata) {
$this->featureContext->theAuthorOfEditedVersionFile(
$versionMetadata['index'],
$versionMetadata['author']
);
}
}
}
/**
* @When user :user downloads the version of file :path with the index :index
*
* @param string $user
* @param string $path
* @param string $index
*
* @return void
* @throws Exception
*/
public function downloadVersion(string $user, string $path, string $index):void {
$user = $this->featureContext->getActualUsername($user);
$fileId = $this->featureContext->getFileIdForPath($user, $path);
Assert::assertNotNull($fileId, __METHOD__ . " fileid of file $path user $user not found (the file may not exist)");
$index = (int)$index;
$responseXml = $this->listVersionFolder($user, $fileId, 1);
$xmlPart = $responseXml->xpath("//d:response/d:href");
if (!isset($xmlPart[$index])) {
Assert::fail(
'could not find version of path "' . $path . '" with index "' . $index . '"'
);
}
// the href already contains the path
$url = WebDavHelper::sanitizeUrl(
$this->featureContext->getBaseUrlWithoutPath() . $xmlPart[$index]
);
$response = HttpRequestHelper::get(
$url,
$this->featureContext->getStepLineRef(),
$user,
$this->featureContext->getPasswordForUser($user)
);
$this->featureContext->setResponse($response, $user);
}
/**
* @Then /^the content of version index "([^"]*)" of file "([^"]*)" for user "([^"]*)" should be "([^"]*)"$/
*
* @param string $index
* @param string $path
* @param string $user
* @param string $content
*
* @return void
* @throws Exception
*/
public function theContentOfVersionIndexOfFileForUserShouldBe(
string $index,
string $path,
string $user,
string $content
): void {
$this->downloadVersion($user, $path, $index);
$this->featureContext->theHTTPStatusCodeShouldBe("200");
$this->featureContext->downloadedContentShouldBe($content);
}
/**
* @When /^user "([^"]*)" retrieves the meta information of (file|fileId) "([^"]*)" using the meta API$/
*
* @param string $user
* @param string $fileOrFileId
* @param string $path
*
* @return void
*/
public function userGetMetaInfo(string $user, string $fileOrFileId, string $path):void {
$user = $this->featureContext->getActualUsername($user);
$baseUrl = $this->featureContext->getBaseUrl();
$password = $this->featureContext->getPasswordForUser($user);
if ($fileOrFileId === "file") {
$fileId = $this->featureContext->getFileIdForPath($user, $path);
$metaPath = "/meta/$fileId/";
} else {
$metaPath = "/meta/$path/";
}
$body = '<?xml version="1.0" encoding="utf-8"?>
<a:propfind xmlns:a="DAV:" xmlns:oc="http://owncloud.org/ns">
<a:prop>
<oc:meta-path-for-user />
</a:prop>
</a:propfind>';
$response = WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
"PROPFIND",
$metaPath,
['Content-Type' => 'text/xml','Depth' => '0'],
$this->featureContext->getStepLineRef(),
$body,
$this->featureContext->getDavPathVersion(),
null
);
$this->featureContext->setResponse($response);
$responseXml = HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
$this->featureContext->setResponseXmlObject($responseXml);
}
/**
* returns the result parsed into an SimpleXMLElement
* with an registered namespace with 'd' as prefix and 'DAV:' as namespace
*
* @param string $user
* @param string $fileId
* @param int $folderDepth
* @param string[]|null $properties
*
* @return SimpleXMLElement
* @throws Exception
*/
public function listVersionFolder(
string $user,
string $fileId,
int $folderDepth,
?array $properties = null
):SimpleXMLElement {
if (!$properties) {
$properties = [
'getetag'
];
}
$user = $this->featureContext->getActualUsername($user);
$password = $this->featureContext->getPasswordForUser($user);
$response = WebDavHelper::propfind(
$this->featureContext->getBaseUrl(),
$user,
$password,
$this->getVersionsPathForFileId($fileId),
$properties,
$this->featureContext->getStepLineRef(),
(string) $folderDepth,
"versions"
);
return HttpRequestHelper::getResponseXml(
$response,
__METHOD__
);
}
/**
* This will run before EVERY scenario.
* It will set the properties for this object.
*
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function before(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
}

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,192 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Artur Neumann <artur@jankaritech.com>
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use PHPUnit\Framework\Assert;
use TestHelpers\OcisHelper;
use TestHelpers\WebDavHelper;
require_once 'bootstrap.php';
/**
* context containing search related API steps
*/
class SearchContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
/**
* @When user :user searches for :pattern using the WebDAV API
* @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API
* @When user :user searches for :pattern using the WebDAV API requesting these properties:
* @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API requesting these properties:
*
* @param string $user
* @param string $pattern
* @param string|null $limit
* @param TableNode|null $properties
*
* @return void
*/
public function userSearchesUsingWebDavAPI(
string $user,
string $pattern,
?string $limit = null,
TableNode $properties = null
):void {
// Because indexing of newly uploaded files or directories with ocis is decoupled and occurs asynchronously, a short wait is necessary before searching files or folders.
if (OcisHelper::isTestingOnOcis()) {
sleep(4);
}
$user = $this->featureContext->getActualUsername($user);
$baseUrl = $this->featureContext->getBaseUrl();
$password = $this->featureContext->getPasswordForUser($user);
$body
= "<?xml version='1.0' encoding='utf-8' ?>\n" .
" <oc:search-files xmlns:a='DAV:' xmlns:oc='http://owncloud.org/ns' >\n" .
" <oc:search>\n" .
" <oc:pattern>$pattern</oc:pattern>\n";
if ($limit !== null) {
$body .= " <oc:limit>$limit</oc:limit>\n";
}
$body .= " </oc:search>\n";
if ($properties !== null) {
$propertiesRows = $properties->getRows();
$body .= " <a:prop>";
foreach ($propertiesRows as $property) {
$body .= "<$property[0]/>";
}
$body .= " </a:prop>";
}
$body .= " </oc:search-files>";
$response = WebDavHelper::makeDavRequest(
$baseUrl,
$user,
$password,
"REPORT",
"/",
null,
$this->featureContext->getStepLineRef(),
$body,
$this->featureContext->getDavPathVersion()
);
$this->featureContext->setResponse($response);
}
/**
* @Then file/folder :path in the search result of user :user should contain these properties:
*
* @param string $path
* @param string $user
* @param TableNode $properties
*
* @return void
* @throws Exception
*/
public function fileOrFolderInTheSearchResultShouldContainProperties(
string $path,
string $user,
TableNode $properties
):void {
$user = $this->featureContext->getActualUsername($user);
$this->featureContext->verifyTableNodeColumns($properties, ['name', 'value']);
$properties = $properties->getHash();
$fileResult = $this->featureContext->findEntryFromPropfindResponse(
$path,
$user,
"REPORT",
);
Assert::assertNotFalse(
$fileResult,
"could not find file/folder '$path'"
);
$fileProperties = $fileResult['value'][1]['value'][0]['value'];
foreach ($properties as $property) {
$foundProperty = false;
$property['value'] = $this->featureContext->substituteInLineCodes(
$property['value'],
$user
);
foreach ($fileProperties as $fileProperty) {
if ($fileProperty['name'] === $property['name']) {
Assert::assertMatchesRegularExpression(
"/" . $property['value'] . "/",
$fileProperty['value']
);
$foundProperty = true;
break;
}
}
Assert::assertTrue(
$foundProperty,
"could not find property '" . $property['name'] . "'"
);
}
}
/**
* This will run before EVERY scenario.
* It will set the properties for this object.
*
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function before(BeforeScenarioScope $scope):void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
/**
* @Then the search result by tags for user :user should contain these entries:
*
* @param string|null $user
* @param TableNode $expectedEntries
*
* @return void
* @throws Exception
*/
public function theSearchResultByTagsForUserShouldContainTheseEntries(
?string $user,
TableNode $expectedEntries
):void {
$user = $this->featureContext->getActualUsername($user);
$this->featureContext->verifyTableNodeColumnsCount($expectedEntries, 1);
$expectedEntries = $expectedEntries->getRows();
$expectedEntriesArray = [];
$responseResourcesArray = $this->featureContext->findEntryFromReportResponse($user);
foreach ($expectedEntries as $item) {
\array_push($expectedEntriesArray, $item[0]);
}
Assert::assertEqualsCanonicalizing($expectedEntriesArray, $responseResourcesArray);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,489 @@
<?php declare(strict_types=1);
/**
* @author Artur Neumann <artur@jankaritech.com>
*
* @copyright Copyright (c) 2020, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Exception\GuzzleException;
use TestHelpers\HttpRequestHelper;
use TestHelpers\WebDavHelper;
use TusPhp\Exception\ConnectionException;
use TusPhp\Exception\TusException;
use TusPhp\Tus\Client;
use PHPUnit\Framework\Assert;
require_once 'bootstrap.php';
/**
* TUS related test steps
*/
class TUSContext implements Context {
/**
*
* @var FeatureContext
*/
private $featureContext;
private $resourceLocation = null;
/**
* @When user :user creates a new TUS resource on the WebDAV API with these headers:
*
* @param string $user
* @param TableNode $headers
* @param string $content
*
* @return void
*
* @throws Exception
* @throws GuzzleException
*/
public function createNewTUSResourceWithHeaders(string $user, TableNode $headers, string $content = ''): void {
$this->featureContext->verifyTableNodeColumnsCount($headers, 2);
$user = $this->featureContext->getActualUsername($user);
$password = $this->featureContext->getUserPassword($user);
$this->resourceLocation = null;
$this->featureContext->setResponse(
$this->featureContext->makeDavRequest(
$user,
"POST",
null,
$headers->getRowsHash(),
$content,
"files",
null,
false,
$password
)
);
$locationHeader = $this->featureContext->getResponse()->getHeader('Location');
if (\sizeof($locationHeader) > 0) {
$this->resourceLocation = $locationHeader[0];
}
}
/**
* @Given user :user has created a new TUS resource on the WebDAV API with these headers:
*
* @param string $user
* @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically
*
* @return void
*
* @throws Exception
* @throws GuzzleException
*/
public function createNewTUSResource(string $user, TableNode $headers): void {
$rows = $headers->getRows();
$rows[] = ['Tus-Resumable', '1.0.0'];
$this->createNewTUSResourceWithHeaders($user, new TableNode($rows));
$this->featureContext->theHTTPStatusCodeShouldBe(201);
}
/**
* @When /^user "([^"]*)" sends a chunk to the last created TUS Location with offset "([^"]*)" and data "([^"]*)" using the WebDAV API$/
*
* @param string $user
* @param string $offset
* @param string $data
* @param string $checksum
*
* @return void
*
* @throws GuzzleException
* @throws JsonException
*/
public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data, string $checksum = ''): void {
$user = $this->featureContext->getActualUsername($user);
$password = $this->featureContext->getUserPassword($user);
$this->featureContext->setResponse(
HttpRequestHelper::sendRequest(
$this->resourceLocation,
$this->featureContext->getStepLineRef(),
'PATCH',
$user,
$password,
[
'Content-Type' => 'application/offset+octet-stream',
'Tus-Resumable' => '1.0.0',
'Upload-Checksum' => $checksum,
'Upload-Offset' => $offset
],
$data
)
);
WebDavHelper::$SPACE_ID_FROM_OCIS = '';
}
/**
* @When user :user uploads file :source to :destination using the TUS protocol on the WebDAV API
*
* @param string|null $user
* @param string $source
* @param string $destination
* @param array $uploadMetadata array of metadata to be placed in the
* `Upload-Metadata` header.
* see https://tus.io/protocols/resumable-upload.html#upload-metadata
* Don't Base64 encode the value.
* @param int $noOfChunks
* @param int|null $bytes
* @param string $checksum
*
* @return void
* @throws ConnectionException
* @throws GuzzleException
* @throws JsonException
* @throws ReflectionException
* @throws TusException
*/
public function userUploadsUsingTusAFileTo(
?string $user,
string $source,
string $destination,
array $uploadMetadata = [],
int $noOfChunks = 1,
int $bytes = null,
string $checksum = ''
): void {
$user = $this->featureContext->getActualUsername($user);
$password = $this->featureContext->getUserPassword($user);
$headers = [
'Authorization' => 'Basic ' . \base64_encode($user . ':' . $password)
];
if ($bytes !== null) {
$creationWithUploadHeader = [
'Content-Type' => 'application/offset+octet-stream',
'Tus-Resumable' => '1.0.0'
];
$headers = \array_merge($headers, $creationWithUploadHeader);
}
if ($checksum != '') {
$checksumHeader = [
'Upload-Checksum' => $checksum
];
$headers = \array_merge($headers, $checksumHeader);
}
$client = new Client(
$this->featureContext->getBaseUrl(),
['verify' => false,
'headers' => $headers
]
);
$client->setApiPath(
WebDavHelper::getDavPath(
$user,
$this->featureContext->getDavPathVersion(),
"files",
WebDavHelper::$SPACE_ID_FROM_OCIS
? WebDavHelper::$SPACE_ID_FROM_OCIS
: $this->featureContext->getPersonalSpaceIdForUser($user)
)
);
WebDavHelper::$SPACE_ID_FROM_OCIS = '';
$client->setMetadata($uploadMetadata);
$sourceFile = $this->featureContext->acceptanceTestsDirLocation() . $source;
$client->setKey((string)rand())->file($sourceFile, $destination);
$this->featureContext->pauseUploadDelete();
if ($bytes !== null) {
$client->file($sourceFile, $destination)->createWithUpload($client->getKey(), $bytes);
} elseif ($noOfChunks === 1) {
$client->file($sourceFile, $destination)->upload();
} else {
$bytesPerChunk = (int)\ceil(\filesize($sourceFile) / $noOfChunks);
for ($i = 0; $i < $noOfChunks; $i++) {
$client->upload($bytesPerChunk);
}
}
$this->featureContext->setLastUploadDeleteTime(\time());
}
/**
* @When user :user uploads file with content :content to :destination using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $content
* @param string $destination
*
* @return void
* @throws GuzzleException
* @throws Exception
*/
public function userUploadsAFileWithContentToUsingTus(
string $user,
string $content,
string $destination
): void {
$tmpfname = $this->writeDataToTempFile($content);
try {
$this->userUploadsUsingTusAFileTo(
$user,
\basename($tmpfname),
$destination
);
} catch (Exception $e) {
Assert::assertStringContainsString('TusPhp\Exception\FileException: Unable to create resource', (string)$e);
}
\unlink($tmpfname);
}
/**
* @When user :user uploads file with content :content in :noOfChunks chunks to :destination using the TUS protocol on the WebDAV API
*
* @param string|null $user
* @param string $content
* @param int|null $noOfChunks
* @param string $destination
*
* @return void
* @throws ConnectionException
* @throws GuzzleException
* @throws JsonException
* @throws ReflectionException
* @throws TusException
* @throws Exception
* @throws GuzzleException
*/
public function userUploadsAFileWithContentInChunksUsingTus(
?string $user,
string $content,
?int $noOfChunks,
string $destination
): void {
$tmpfname = $this->writeDataToTempFile($content);
$this->userUploadsUsingTusAFileTo(
$user,
\basename($tmpfname),
$destination,
[],
$noOfChunks
);
\unlink($tmpfname);
}
/**
* @When user :user uploads file :source to :destination with mtime :mtime using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $source
* @param string $destination
* @param string $mtime Time in human readable format is taken as input which is converted into milliseconds that is used by API
*
* @return void
* @throws Exception
* @throws GuzzleException
*/
public function userUploadsFileWithContentToWithMtimeUsingTUS(
string $user,
string $source,
string $destination,
string $mtime
): void {
$mtime = new DateTime($mtime);
$mtime = $mtime->format('U');
$user = $this->featureContext->getActualUsername($user);
$this->userUploadsUsingTusAFileTo(
$user,
$source,
$destination,
['mtime' => $mtime]
);
}
/**
* @param string $content
*
* @return string the file name
* @throws Exception
*/
private function writeDataToTempFile(string $content): string {
$tmpfname = \tempnam(
$this->featureContext->acceptanceTestsDirLocation(),
"tus-upload-test-"
);
if ($tmpfname === false) {
throw new \Exception("could not create a temporary filename");
}
$tempfile = \fopen($tmpfname, "w");
if ($tempfile === false) {
throw new \Exception("could not open " . $tmpfname . " for write");
}
\fwrite($tempfile, $content);
\fclose($tempfile);
return $tmpfname;
}
/**
* @BeforeScenario
*
* @param BeforeScenarioScope $scope
*
* @return void
*/
public function setUpScenario(BeforeScenarioScope $scope): void {
// Get the environment
$environment = $scope->getEnvironment();
// Get all the contexts you need in this context
$this->featureContext = $environment->getContext('FeatureContext');
}
/**
* @When user :user creates a new TUS resource with content :content on the WebDAV API with these headers:
*
* @param string $user
* @param string $content
* @param TableNode $headers
*
* @return void
* @throws Exception
* @throws GuzzleException
*/
public function userCreatesWithUpload(
string $user,
string $content,
TableNode $headers
): void {
$this->createNewTUSResourceWithHeaders($user, $headers, $content);
}
/**
* @When user :user creates file :source and uploads content :content in the same request using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $source
* @param string $content
*
* @return void
* @throws Exception
* @throws GuzzleException
*/
public function userUploadsWithCreatesWithUpload(
string $user,
string $source,
string $content
): void {
$tmpfname = $this->writeDataToTempFile($content);
$this->userUploadsUsingTusAFileTo(
$user,
\basename($tmpfname),
$source,
[],
1,
-1
);
\unlink($tmpfname);
}
/**
* @When user :user uploads file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $checksum
* @param string $offset
* @param string $content
*
* @return void
* @throws Exception
*/
public function userUploadsFileWithChecksum(
string $user,
string $checksum,
string $offset,
string $content
): void {
$this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum);
}
/**
* @Given user :user has uploaded file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $checksum
* @param string $offset
* @param string $content
*
* @return void
* @throws Exception
*/
public function userHasUploadedFileWithChecksum(
string $user,
string $checksum,
string $offset,
string $content
): void {
$this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum);
$this->featureContext->theHTTPStatusCodeShouldBe(204, "");
}
/**
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $offset
* @param string $data
* @param string $checksum
*
* @return void
* @throws Exception
*/
public function userUploadsChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void {
$this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum);
}
/**
* @Given user :user has uploaded a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API
*
* @param string $user
* @param string $offset
* @param string $data
* @param string $checksum
*
* @return void
* @throws Exception
*/
public function userHasUploadedChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void {
$this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum);
$this->featureContext->theHTTPStatusCodeShouldBe(204, "");
}
/**
* @When user :user overwrites recently shared file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers:
* @When user :user overwrites existing file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers:
*
* @param string $user
* @param string $offset
* @param string $data
* @param string $checksum
* @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically
*
* @return void
*
* @throws GuzzleException
* @throws Exception
*/
public function userOverwritesFileWithChecksum(string $user, string $offset, string $data, string $checksum, TableNode $headers): void {
$this->createNewTUSResource($user, $headers);
$this->userHasUploadedChunkFileWithChecksum($user, $offset, $data, $checksum);
}
}

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -20,19 +20,44 @@
*
*/
$pathToCore = \getenv('PATH_TO_CORE');
if ($pathToCore === false) {
$pathToCore = "../core";
}
use Composer\Autoload\ClassLoader;
require_once $pathToCore . '/tests/acceptance/features/bootstrap/bootstrap.php';
$classLoader = new ClassLoader();
$classLoader = new \Composer\Autoload\ClassLoader();
$classLoader->addPsr4(
"",
$pathToCore . "/tests/acceptance/features/bootstrap",
true
);
$classLoader->addPsr4("TestHelpers\\", __DIR__ . "/../../../TestHelpers", true);
$classLoader->register();
// Sleep for 10 milliseconds
const STANDARD_SLEEP_TIME_MILLISEC = 10;
const STANDARD_SLEEP_TIME_MICROSEC = STANDARD_SLEEP_TIME_MILLISEC * 1000;
// Long timeout for use in code that needs to wait for known slow UI
const LONG_UI_WAIT_TIMEOUT_MILLISEC = 60000;
// Default timeout for use in code that needs to wait for the UI
const STANDARD_UI_WAIT_TIMEOUT_MILLISEC = 10000;
// Minimum timeout for use in code that needs to wait for the UI
const MINIMUM_UI_WAIT_TIMEOUT_MILLISEC = 500;
const MINIMUM_UI_WAIT_TIMEOUT_MICROSEC = MINIMUM_UI_WAIT_TIMEOUT_MILLISEC * 1000;
// Minimum timeout for emails
const EMAIL_WAIT_TIMEOUT_SEC = 10;
const EMAIL_WAIT_TIMEOUT_MILLISEC = EMAIL_WAIT_TIMEOUT_SEC * 1000;
// Default number of times to retry where retries are useful
const STANDARD_RETRY_COUNT = 5;
// Minimum number of times to retry where retries are useful
const MINIMUM_RETRY_COUNT = 2;
// The remote server-under-test might or might not happen to have this directory.
// If it does not exist, then the tests may end up creating it.
const ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER = "tests/acceptance";
// The following directory should NOT already exist on the remote server-under-test.
// Acceptance tests are free to do anything needed in this directory, and to
// delete it during or at the end of testing.
const TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER = ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER . "/server_tmp";
// The following directory is created, used, and deleted by tests that need to
// use some "local external storage" on the server.
const LOCAL_STORAGE_DIR_ON_REMOTE_SERVER = TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER . "/local_storage";

View File

@@ -0,0 +1,9 @@
a file with a single quote i its name
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1 @@
Dav-Test

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

View File

@@ -0,0 +1 @@
BLABLABLA

View File

@@ -0,0 +1,93 @@
a big lorem file
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,7 @@
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,9 @@
a file with a single quote i its name, but different to the original one
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,93 @@
a big lorem file but different to the original one
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,9 @@
lorem file that is different from the original one
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,9 @@
a file with funny characters in the file name but different from the original one
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,9 @@
a file with funny characters in the file name
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,3 @@
This is a testfile.
Cheers.

View File

View File

@@ -0,0 +1,11 @@
This must be the last skeleton file in this folder, when sorted alphabetically.
Tests for performing actions on the last file in the folder try to find and use
this file name.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,11 @@
This file has a name so it would be the last after the upload, when sorted alphabetically.
Tests for performing actions on the last file in the folder try to find and use
this file name.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est
qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora
incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum
exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?

View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
log_error() {
echo -e "\e[31m$1\e[0m"
}
log_info() {
echo -e "\e[34m$1\e[0m"
}
log_success() {
echo -e "\e[32m$1\e[0m"
}
declare -A scenarioLines
if [ -n "${EXPECTED_FAILURES_FILE}" ]
then
if [ -f "${EXPECTED_FAILURES_FILE}" ]
then
log_info "Checking expected failures in ${EXPECTED_FAILURES_FILE}"
else
log_error "Expected failures file ${EXPECTED_FAILURES_FILE} not found"
log_error "Check the setting of EXPECTED_FAILURES_FILE environment variable"
exit 1
fi
FINAL_EXIT_STATUS=0
# If the last line of the expected-failures file ends without a newline character
# then that line may not get processed by some of the bash code in this script
# So check that the last character in the file is a newline
if [ "$(tail -c1 "${EXPECTED_FAILURES_FILE}" | wc -l)" -eq 0 ]
then
log_error "Expected failures file ${EXPECTED_FAILURES_FILE} must end with a newline"
log_error "Put a newline at the end of the last line and try again"
FINAL_EXIT_STATUS=1
fi
# Check the expected-failures file to ensure that the lines are self-consistent
# In most cases the features that are being run are in owncloud/core,
# so assume that by default.
FEATURE_FILE_REPO="owncloud/core"
FEATURE_FILE_PATH="tests/acceptance/features"
LINE_NUMBER=0
while read -r INPUT_LINE
do
LINE_NUMBER=$(("$LINE_NUMBER" + 1))
# Ignore comment lines (starting with hash)
if [[ "${INPUT_LINE}" =~ ^# ]]
then
continue
fi
# A line of text in the feature file can be used to indicate that the
# features being run are actually from some other repo. For example:
# "The expected failures in this file are from features in the owncloud/ocis repo."
# Write a line near the top of the expected-failures file to "declare" this,
# overriding the default "owncloud/core"
FEATURE_FILE_SPEC_LINE_FOUND="false"
if [[ "${INPUT_LINE}" =~ features[[:blank:]]in[[:blank:]]the[[:blank:]]([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)[[:blank:]]repo ]]; then
FEATURE_FILE_REPO="${BASH_REMATCH[1]}"
log_info "Features are expected to be in the ${FEATURE_FILE_REPO} repo\n"
FEATURE_FILE_SPEC_LINE_FOUND="true"
fi
if [[ "${INPUT_LINE}" =~ repo[[:blank:]]in[[:blank:]]the[[:blank:]]([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)[[:blank:]]folder[[:blank:]]tree ]]; then
FEATURE_FILE_PATH="${BASH_REMATCH[1]}"
log_info "Features are expected to be in the ${FEATURE_FILE_PATH} folder tree\n"
FEATURE_FILE_SPEC_LINE_FOUND="true"
fi
if [[ $FEATURE_FILE_SPEC_LINE_FOUND == "true" ]]; then
continue
fi
# Match lines that have "- [someSuite/someName.feature:n]" pattern on start
# the part inside the brackets is the suite, feature and line number of the expected failure.
if [[ "${INPUT_LINE}" =~ ^-[[:space:]]\[([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+)] ]]; then
SUITE_SCENARIO_LINE="${BASH_REMATCH[1]}"
elif [[
# report for lines like: " - someSuite/someName.feature:n"
"${INPUT_LINE}" =~ ^[[:space:]]*-[[:space:]][a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+[[:space:]]*$ ||
# report for lines starting with: "[someSuite/someName.feature:n]"
"${INPUT_LINE}" =~ ^[[:space:]]*\[([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\.feature:[0-9]+)]
]]; then
log_error "> Line ${LINE_NUMBER}: Not in the correct format."
log_error " + Actual Line : '${INPUT_LINE}'"
log_error " - Expected Format : '- [suite/scenario.feature:line_number](scenario_line_url)'"
FINAL_EXIT_STATUS=1
continue
else
# otherwise, ignore the line
continue
fi
# Find the link in round-brackets that should be after the SUITE_SCENARIO_LINE
if [[ "${INPUT_LINE}" =~ \(([a-zA-Z0-9:/.#_-]+)\) ]]; then
ACTUAL_LINK="${BASH_REMATCH[1]}"
else
log_error "Line ${LINE_NUMBER}: ${INPUT_LINE} : Link is empty"
FINAL_EXIT_STATUS=1
continue
fi
if [[ -n "${scenarioLines[${SUITE_SCENARIO_LINE}]:-}" ]];
then
log_error "> Line ${LINE_NUMBER}: Scenario line ${SUITE_SCENARIO_LINE} is duplicated"
FINAL_EXIT_STATUS=1
fi
scenarioLines[${SUITE_SCENARIO_LINE}]="exists"
OLD_IFS=${IFS}
IFS=':'
read -ra FEATURE_PARTS <<< "${SUITE_SCENARIO_LINE}"
IFS=${OLD_IFS}
SUITE_FEATURE="${FEATURE_PARTS[0]}"
FEATURE_LINE="${FEATURE_PARTS[1]}"
EXPECTED_LINK="https://github.com/${FEATURE_FILE_REPO}/blob/master/${FEATURE_FILE_PATH}/${SUITE_FEATURE}#L${FEATURE_LINE}"
if [[ "${ACTUAL_LINK}" != "${EXPECTED_LINK}" ]]; then
log_error "> Line ${LINE_NUMBER}: Link is not correct for ${SUITE_SCENARIO_LINE}"
log_error " + Actual link : ${ACTUAL_LINK}"
log_error " - Expected link : ${EXPECTED_LINK}"
FINAL_EXIT_STATUS=1
fi
done < "${EXPECTED_FAILURES_FILE}"
else
log_error "Environment variable EXPECTED_FAILURES_FILE must be defined to be the file to check"
exit 1
fi
if [ ${FINAL_EXIT_STATUS} == 1 ]
then
log_error "\nErrors were found in the expected failures file - see the messages above!"
else
log_success "\nNo problems were found in the expected failures file."
fi
exit ${FINAL_EXIT_STATUS}

1376
tests/acceptance/run.sh Executable file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -20,18 +20,14 @@
*
*/
$pathToCore = \getenv('PATH_TO_CORE');
if ($pathToCore === false) {
$pathToCore = "../core";
}
use Composer\Autoload\ClassLoader;
require_once $pathToCore . '/tests/acceptance/features/bootstrap/bootstrap.php';
$classLoader = new \Composer\Autoload\ClassLoader();
$classLoader = new ClassLoader();
$classLoader->addPsr4(
"",
$pathToCore . "/tests/acceptance/features/bootstrap",
__DIR__ . "/../../../tests/acceptance/features/bootstrap",
true
);
$classLoader->addPsr4("TestHelpers\\", __DIR__ . "/../../../TestHelpers", true);
$classLoader->register();

View File

@@ -9,6 +9,7 @@
},
"require": {
"behat/behat": "^3.9",
"behat/gherkin": "^4.9",
"behat/mink": "1.7.1",
"friends-of-behat/mink-extension": "^2.5",
"ciaranmcnulty/behat-stepthroughextension" : "dev-master",