mirror of
https://github.com/owncloud/ocis.git
synced 2025-04-18 23:44:07 +03:00
4747 lines
127 KiB
PHP
4747 lines
127 KiB
PHP
<?php declare(strict_types=1);
|
|
/**
|
|
* @author Sergio Bertolin <sbertolin@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\Gherkin\Node\PyStringNode;
|
|
use Behat\Gherkin\Node\TableNode;
|
|
use GuzzleHttp\Exception\BadResponseException;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use PHPUnit\Framework\Assert;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use GuzzleHttp\Stream\StreamInterface;
|
|
use TestHelpers\OcisHelper;
|
|
use TestHelpers\UploadHelper;
|
|
use TestHelpers\WebDavHelper;
|
|
use TestHelpers\HttpRequestHelper;
|
|
use TestHelpers\Asserts\WebDav as WebDavAssert;
|
|
use TestHelpers\GraphHelper;
|
|
|
|
/**
|
|
* WebDav functions
|
|
*/
|
|
trait WebDav {
|
|
private string $davPath = "remote.php/webdav";
|
|
private bool $usingOldDavPath = true;
|
|
private bool $usingSpacesDavPath = false;
|
|
|
|
/**
|
|
* @var ResponseInterface[]
|
|
*/
|
|
private array $uploadResponses;
|
|
|
|
/**
|
|
* @var int|string|null
|
|
*/
|
|
private $storedFileID = null;
|
|
|
|
private ?int $lastUploadDeleteTime = null;
|
|
|
|
/**
|
|
* a variable that contains the DAV path without "remote.php/(web)dav"
|
|
* when setting $this->davPath directly by usingDavPath()
|
|
*/
|
|
private ?string $customDavPath = null;
|
|
|
|
/**
|
|
* response content parsed from XML to an array
|
|
*/
|
|
private array $responseXml = [];
|
|
|
|
/**
|
|
* add resource created by admin in an array
|
|
* This array is used while cleaning up the resource created by admin during test run
|
|
* As of now it tracks only for (files|folder) creation
|
|
* This can be expanded and modified to track other actions like (upload, deleted.)
|
|
*/
|
|
private array $adminResources = [];
|
|
|
|
/**
|
|
* response content parsed into a SimpleXMLElement
|
|
*/
|
|
private ?SimpleXMLElement $responseXmlObject = null;
|
|
|
|
private int $httpRequestTimeout = 0;
|
|
|
|
private ?int $chunkingToUse = null;
|
|
|
|
/**
|
|
* The ability to do requests with depth infinity is disabled by default.
|
|
* This remembers when the setting dav.propfind.depth_infinity has been
|
|
* enabled, so that test code can make use of it as appropriate.
|
|
*/
|
|
private bool $davPropfindDepthInfinityEnabled = false;
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function davPropfindDepthInfinityEnabled():void {
|
|
$this->davPropfindDepthInfinityEnabled = true;
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function davPropfindDepthInfinityDisabled():void {
|
|
$this->davPropfindDepthInfinityEnabled = false;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function davPropfindDepthInfinityIsEnabled():bool {
|
|
return $this->davPropfindDepthInfinityEnabled;
|
|
}
|
|
|
|
/**
|
|
* @param int $lastUploadDeleteTime
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setLastUploadDeleteTime(int $lastUploadDeleteTime):void {
|
|
$this->lastUploadDeleteTime = $lastUploadDeleteTime;
|
|
}
|
|
|
|
/**
|
|
* @return number
|
|
*/
|
|
public function getLastUploadDeleteTime():int {
|
|
return $this->lastUploadDeleteTime;
|
|
}
|
|
|
|
/**
|
|
* @return SimpleXMLElement|null
|
|
*/
|
|
public function getResponseXmlObject():?SimpleXMLElement {
|
|
return $this->responseXmlObject;
|
|
}
|
|
|
|
/**
|
|
* @param SimpleXMLElement $responseXmlObject
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setResponseXmlObject(SimpleXMLElement $responseXmlObject):void {
|
|
$this->responseXmlObject = $responseXmlObject;
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function clearResponseXmlObject():void {
|
|
$this->responseXmlObject = null;
|
|
}
|
|
|
|
/**
|
|
* @param string $fileID
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setStoredFileID(string $fileID):void {
|
|
$this->storedFileID = $fileID;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getStoredFileID():string {
|
|
return $this->storedFileID;
|
|
}
|
|
|
|
/**
|
|
* @param SimpleXMLElement|null $xmlObject
|
|
*
|
|
* @return string the etag or an empty string if the getetag property does not exist
|
|
*/
|
|
public function getEtagFromResponseXmlObject(?SimpleXMLElement $xmlObject = null): string {
|
|
$xmlObject = $xmlObject ?? $this->getResponseXml();
|
|
$xmlPart = $xmlObject->xpath("//d:prop/d:getetag");
|
|
if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) {
|
|
return '';
|
|
}
|
|
return $xmlPart[0]->__toString();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $eTag
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isEtagValid(string $eTag): bool {
|
|
if (\preg_match("/^\"[a-f0-9:.]{1,32}\"$/", $eTag)
|
|
) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $responseXml
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setResponseXml(array $responseXml):void {
|
|
$this->responseXml = $responseXml;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getOldDavPath():string {
|
|
return "remote.php/webdav";
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getNewDavPath():string {
|
|
return "remote.php/dav";
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSpacesDavPath():string {
|
|
return "dav/spaces";
|
|
}
|
|
|
|
/**
|
|
* @Given /^using (old|new|spaces) (?:dav|DAV) path$/
|
|
*
|
|
* @param string $davChoice
|
|
*
|
|
* @return void
|
|
*/
|
|
public function usingOldOrNewDavPath(string $davChoice):void {
|
|
if ($davChoice === 'old') {
|
|
$this->usingOldDavPath();
|
|
} elseif ($davChoice === 'new') {
|
|
$this->usingNewDavPath();
|
|
} else {
|
|
$this->usingSpacesDavPath();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select the old DAV path as the default for later scenario steps
|
|
*
|
|
* @return void
|
|
*/
|
|
public function usingOldDavPath():void {
|
|
$this->davPath = $this->getOldDavPath();
|
|
$this->usingOldDavPath = true;
|
|
$this->customDavPath = null;
|
|
$this->usingSpacesDavPath = false;
|
|
}
|
|
|
|
/**
|
|
* Select the new DAV path as the default for later scenario steps
|
|
*
|
|
* @return void
|
|
*/
|
|
public function usingNewDavPath():void {
|
|
$this->davPath = $this->getNewDavPath();
|
|
$this->usingOldDavPath = false;
|
|
$this->customDavPath = null;
|
|
$this->usingSpacesDavPath = false;
|
|
}
|
|
|
|
/**
|
|
* Select the spaces dav path as the default for later scenario steps
|
|
*
|
|
* @return void
|
|
*/
|
|
public function usingSpacesDavPath():void {
|
|
$this->davPath = $this->getSpacesDavPath();
|
|
$this->usingOldDavPath = false;
|
|
$this->customDavPath = null;
|
|
$this->usingSpacesDavPath = true;
|
|
}
|
|
|
|
/**
|
|
* gives the DAV path of a file including the subfolder of the webserver
|
|
* e.g. when the server runs in `http://localhost/owncloud/`
|
|
* this function will return `owncloud/remote.php/webdav/prueba.txt`
|
|
*
|
|
* @param string $user
|
|
*
|
|
* @return string
|
|
* @throws GuzzleException
|
|
*/
|
|
public function getFullDavFilesPath(string $user):string {
|
|
$spaceId = null;
|
|
if ($this->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES) {
|
|
$spaceId = (WebDavHelper::$SPACE_ID_FROM_OCIS) ?: WebDavHelper::getPersonalSpaceIdForUser(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
$this->getStepLineRef()
|
|
);
|
|
}
|
|
$path = $this->getBasePath() . "/" .
|
|
WebDavHelper::getDavPath($user, $this->getDavPathVersion(), "files", $spaceId);
|
|
$path = WebDavHelper::sanitizeUrl($path);
|
|
return \ltrim($path, "/");
|
|
}
|
|
|
|
/**
|
|
* @param string $token
|
|
* @param string $type
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getPublicLinkDavPath(string $token, string $type):string {
|
|
$path = $this->getBasePath() . "/" .
|
|
WebDavHelper::getDavPath($token, $this->getDavPathVersion(), $type);
|
|
$path = WebDavHelper::sanitizeUrl($path);
|
|
return \ltrim($path, "/");
|
|
}
|
|
|
|
/**
|
|
* Select a suitable DAV path version number.
|
|
* Some endpoints have only existed since a certain point in time, so for
|
|
* those make sure to return a DAV path version that works for that endpoint.
|
|
* Otherwise return the currently selected DAV path version.
|
|
*
|
|
* @param string|null $for the category of endpoint that the DAV path will be used for
|
|
*
|
|
* @return int DAV path version (1, 2 or 3) selected, or appropriate for the endpoint
|
|
*/
|
|
public function getDavPathVersion(?string $for = null):?int {
|
|
if ($this->usingSpacesDavPath) {
|
|
return WebDavHelper::DAV_VERSION_SPACES;
|
|
}
|
|
if ($for === 'systemtags') {
|
|
// systemtags only exists since DAV v2
|
|
return WebDavHelper::DAV_VERSION_NEW;
|
|
}
|
|
if ($for === 'file_versions') {
|
|
// file_versions only exists since DAV v2
|
|
return WebDavHelper::DAV_VERSION_NEW;
|
|
}
|
|
if ($this->usingOldDavPath === true) {
|
|
return WebDavHelper::DAV_VERSION_OLD;
|
|
} else {
|
|
return WebDavHelper::DAV_VERSION_NEW;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select a suitable DAV path.
|
|
* Some endpoints have only existed since a certain point in time, so for
|
|
* those make sure to return a DAV path that works for that endpoint.
|
|
* Otherwise return the currently selected DAV path.
|
|
*
|
|
* @param string|null $for the category of endpoint that the DAV path will be used for
|
|
*
|
|
* @return string DAV path selected, or appropriate for the endpoint
|
|
*/
|
|
public function getDavPath(?string $for = null):string {
|
|
$davPathVersion = $this->getDavPathVersion($for);
|
|
if ($davPathVersion === WebDavHelper::DAV_VERSION_OLD) {
|
|
return $this->getOldDavPath();
|
|
}
|
|
|
|
if ($davPathVersion === WebDavHelper::DAV_VERSION_NEW) {
|
|
return $this->getNewDavPath();
|
|
}
|
|
|
|
return $this->getSpacesDavPath();
|
|
}
|
|
|
|
/**
|
|
* @param string|null $user
|
|
* @param string|null $method
|
|
* @param string|null $path
|
|
* @param array|null $headers
|
|
* @param StreamInterface|null $body
|
|
* @param string|null $type
|
|
* @param string|null $davPathVersion
|
|
* @param bool $stream Set to true to stream a response rather
|
|
* than download it all up-front.
|
|
* @param string|null $password
|
|
* @param array|null $urlParameter
|
|
* @param string|null $doDavRequestAsUser
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*
|
|
* @throws GuzzleException
|
|
* @throws JsonException
|
|
*/
|
|
public function makeDavRequest(
|
|
?string $user,
|
|
?string $method,
|
|
?string $path,
|
|
?array $headers,
|
|
$body = null,
|
|
?string $type = "files",
|
|
?string $davPathVersion = null,
|
|
bool $stream = false,
|
|
?string $password = null,
|
|
?array $urlParameter = [],
|
|
?string $doDavRequestAsUser = null,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
if ($this->customDavPath !== null) {
|
|
$path = $this->customDavPath . $path;
|
|
}
|
|
|
|
if ($davPathVersion === null) {
|
|
$davPathVersion = $this->getDavPathVersion();
|
|
} else {
|
|
$davPathVersion = (int) $davPathVersion;
|
|
}
|
|
|
|
if ($password === null) {
|
|
$password = $this->getPasswordForUser($user);
|
|
}
|
|
|
|
return WebDavHelper::makeDavRequest(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$password,
|
|
$method,
|
|
$path,
|
|
$headers,
|
|
$this->getStepLineRef(),
|
|
$body,
|
|
$davPathVersion,
|
|
$type,
|
|
null,
|
|
"basic",
|
|
$stream,
|
|
$this->httpRequestTimeout,
|
|
null,
|
|
$urlParameter,
|
|
$doDavRequestAsUser,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $user
|
|
* @param string $folder
|
|
* @param bool|null $isGivenStep
|
|
* @param string|null $password
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws JsonException | GuzzleException
|
|
* @throws GuzzleException | JsonException
|
|
*/
|
|
public function createFolder(string $user, string $folder, ?bool $isGivenStep = false, ?string $password = null): ResponseInterface {
|
|
$folder = '/' . \ltrim($folder, '/');
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
"MKCOL",
|
|
$folder,
|
|
[],
|
|
null,
|
|
"files",
|
|
null,
|
|
false,
|
|
$password,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string|null $path
|
|
* @param string|null $doDavRequestAsUser
|
|
* @param string|null $width
|
|
* @param string|null $height
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
public function downloadPreviews(string $user, ?string $path, ?string $doDavRequestAsUser, ?string $width, ?string $height):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$doDavRequestAsUser = $this->getActualUsername($doDavRequestAsUser);
|
|
$urlParameter = [
|
|
'x' => $width,
|
|
'y' => $height,
|
|
'forceIcon' => '0',
|
|
'preview' => '1'
|
|
];
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
"GET",
|
|
$path,
|
|
[],
|
|
null,
|
|
"files",
|
|
null,
|
|
false,
|
|
null,
|
|
$urlParameter,
|
|
$doDavRequestAsUser
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the number of versions should be :arg1
|
|
*
|
|
* @param int $number
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theNumberOfVersionsShouldBe(int $number):void {
|
|
$resXml = $this->getResponseXmlObject();
|
|
if ($resXml === null) {
|
|
$resXml = HttpRequestHelper::getResponseXml(
|
|
$this->getResponse(),
|
|
__METHOD__
|
|
);
|
|
$this->setResponseXmlObject($resXml);
|
|
}
|
|
$xmlPart = $resXml->xpath("//d:getlastmodified");
|
|
$actualNumber = \count($xmlPart);
|
|
Assert::assertEquals(
|
|
$number,
|
|
$actualNumber,
|
|
"Expected number of versions was '$number', but got '$actualNumber'"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the number of etag elements in the response should be :number
|
|
*
|
|
* @param int $number
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theNumberOfEtagElementInTheResponseShouldBe(int $number):void {
|
|
$resXml = $this->getResponseXmlObject();
|
|
if ($resXml === null) {
|
|
$resXml = HttpRequestHelper::getResponseXml(
|
|
$this->getResponse(),
|
|
__METHOD__
|
|
);
|
|
}
|
|
$xmlPart = $resXml->xpath("//d:getetag");
|
|
$actualNumber = \count($xmlPart);
|
|
Assert::assertEquals(
|
|
$number,
|
|
$actualNumber,
|
|
"Expected number of etag elements was '$number', but got '$actualNumber'"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $fileDestination
|
|
*
|
|
* @return string
|
|
* @throws GuzzleException
|
|
*/
|
|
public function destinationHeaderValue(string $user, string $fileDestination):string {
|
|
$fileDestination = \ltrim($fileDestination, "/");
|
|
$spaceId = $this->getPersonalSpaceIdForUser($user);
|
|
// If the destination is a share, we need to get the space ID for the Shares space
|
|
if (\str_starts_with($fileDestination, "Shares/")) {
|
|
$spaceId = $this->spacesContext->getSpaceIdByName($user, "Shares");
|
|
if ($this->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES) {
|
|
$fileDestination = \preg_replace("/^Shares\//", "", $fileDestination);
|
|
}
|
|
}
|
|
$fullUrl = $this->getBaseUrl() . '/' .
|
|
WebDavHelper::getDavPath($user, $this->getDavPathVersion(), "files", $spaceId);
|
|
return \rtrim($fullUrl, '/') . '/' . $fileDestination;
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has moved (?:file|folder|entry) "([^"]*)" to "([^"]*)"$/
|
|
*
|
|
* @param string|null $user
|
|
* @param string|null $fileSource
|
|
* @param string|null $fileDestination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasMovedFile(
|
|
?string $user,
|
|
?string $fileSource,
|
|
?string $fileDestination
|
|
):void {
|
|
$response = $this->moveResource($user, $fileSource, $fileDestination);
|
|
$actualStatusCode = $response->getStatusCode();
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
201,
|
|
" Failed moving resource '$fileSource' to '$fileDestination'." .
|
|
" Expected status code was 201 but got '$actualStatusCode' ",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function moveResource(string $user, string $source, string $destination) {
|
|
$user = $this->getActualUsername($user);
|
|
$headers['Destination'] = $this->destinationHeaderValue(
|
|
$user,
|
|
$destination
|
|
);
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
"MOVE",
|
|
$source,
|
|
$headers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user moves file :source to :destination using the WebDAV API
|
|
* @When user :user moves folder :source to :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
public function userMovesFileOrFolderUsingTheWebDavAPI(
|
|
string $user,
|
|
string $source,
|
|
string $destination
|
|
):void {
|
|
$response = $this->moveResource($user, $source, $destination);
|
|
$this->setResponse($response);
|
|
$this->pushToLastHttpStatusCodesArray();
|
|
}
|
|
|
|
/**
|
|
* @When user :user moves the following file using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userMovesTheFollowingFileUsingTheWebdavApi(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["source", "destination"]);
|
|
$rows = $table->getHash();
|
|
foreach ($rows as $row) {
|
|
$response = $this->moveResource($user, $row["source"], $row["destination"]);
|
|
$this->setResponse($response);
|
|
$this->pushToLastHttpStatusCodesArray(
|
|
(string) $response->getStatusCode()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" moves the following (?:files|folders|entries)\s?(asynchronously|) using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $type "asynchronously" or empty
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userMovesFollowingFileUsingTheAPI(
|
|
string $user,
|
|
string $type,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["from", "to"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $file) {
|
|
$response = $this->moveResource($user, $file['from'], $file['to']);
|
|
$this->pushToLastHttpStatusCodesArray(
|
|
(string) $response->getStatusCode()
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should be able to rename (file|folder|entry) "([^"]*)" to "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theUserShouldBeAbleToRenameEntryTo(string $user, string $entry, string $source, string $destination):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
$response = $this->moveResource($user, $source, $destination);
|
|
$this->theHTTPStatusCodeShouldBeBetween(201, 204, $response);
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, $entry, $source);
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $destination);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should not be able to rename (file|folder|entry) "([^"]*)" to "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theUserShouldNotBeAbleToRenameEntryTo(string $user, string $entry, string $source, string $destination):void {
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
$response = $this->moveResource($user, $source, $destination);
|
|
$this->theHTTPStatusCodeShouldBeBetween(400, 499, $response);
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, $entry, $destination);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $fileSource
|
|
* @param string $fileDestination
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function copyFile(
|
|
string $user,
|
|
string $fileSource,
|
|
string $fileDestination
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$headers['Destination'] = $this->destinationHeaderValue(
|
|
$user,
|
|
$fileDestination
|
|
);
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
"COPY",
|
|
$fileSource,
|
|
$headers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" copies (?:file|folder) "([^"]*)" to "([^"]*)" using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $fileSource
|
|
* @param string $fileDestination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userCopiesFileUsingTheAPI(
|
|
string $user,
|
|
string $fileSource,
|
|
string $fileDestination
|
|
):void {
|
|
$response = $this->copyFile($user, $fileSource, $fileDestination);
|
|
$this->setResponse($response);
|
|
$this->pushToLastHttpStatusCodesArray();
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has copied file "([^"]*)" to "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $fileSource
|
|
* @param string $fileDestination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasCopiedFileUsingTheAPI(
|
|
string $user,
|
|
string $fileSource,
|
|
string $fileDestination
|
|
):void {
|
|
$response = $this->copyFile($user, $fileSource, $fileDestination);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to copy file '$fileSource' to '$fileDestination' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $fileSource
|
|
* @param string $range
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function downloadFileWithRange(string $user, string $fileSource, string $range):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$headers['Range'] = $range;
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
"GET",
|
|
$fileSource,
|
|
$headers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" downloads file "([^"]*)" with range "([^"]*)" using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $fileSource
|
|
* @param string $range
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDownloadsFileWithRangeUsingWebDavApi(string $user, string $fileSource, string $range):void {
|
|
$this->setResponse($this->downloadFileWithRange($user, $fileSource, $range));
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" using password "([^"]*)" should not be able to download file "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $password
|
|
* @param string $fileName
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUsingPasswordShouldNotBeAbleToDownloadFile(
|
|
string $user,
|
|
string $password,
|
|
string $fileName
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getActualPassword($password);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName, $password);
|
|
Assert::assertGreaterThanOrEqual(
|
|
400,
|
|
$response->getStatusCode(),
|
|
__METHOD__
|
|
. ' download must fail'
|
|
);
|
|
Assert::assertLessThanOrEqual(
|
|
499,
|
|
$response->getStatusCode(),
|
|
__METHOD__
|
|
. ' 4xx error expected but got status code "'
|
|
. $response->getStatusCode() . '"'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should not be able to download file "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $fileName
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
*/
|
|
public function userShouldNotBeAbleToDownloadFile(
|
|
string $user,
|
|
string $fileName
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getPasswordForUser($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName, $password);
|
|
Assert::assertGreaterThanOrEqual(
|
|
400,
|
|
$response->getStatusCode(),
|
|
__METHOD__
|
|
. ' download must fail'
|
|
);
|
|
Assert::assertLessThanOrEqual(
|
|
499,
|
|
$response->getStatusCode(),
|
|
__METHOD__
|
|
. ' 4xx error expected but got status code "'
|
|
. $response->getStatusCode() . '"'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should be able to access a skeleton file$/
|
|
*
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userShouldBeAbleToAccessASkeletonFile(string $user):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, "textfile0.txt");
|
|
$actualStatus = $response->getStatusCode();
|
|
Assert::assertEquals(
|
|
200,
|
|
$actualStatus,
|
|
"Expected status code to be '200', but got '$actualStatus'"
|
|
);
|
|
$this->checkDownloadedContentMatches("ownCloud test text file 0\n", '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Then the size of the downloaded file should be :size bytes
|
|
*
|
|
* @param string $size
|
|
*
|
|
* @return void
|
|
*/
|
|
public function sizeOfDownloadedFileShouldBe(string $size):void {
|
|
$actualSize = \strlen((string) $this->response->getBody());
|
|
Assert::assertEquals(
|
|
$size,
|
|
$actualSize,
|
|
"Expected size of the downloaded file was '$size' but got '$actualSize'"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the downloaded content should end with "([^"]*)"$/
|
|
*
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function downloadedContentShouldEndWith(string $content):void {
|
|
$actualContent = \substr((string) $this->response->getBody(), -\strlen($content));
|
|
Assert::assertEquals(
|
|
$content,
|
|
$actualContent,
|
|
"The downloaded content was expected to end with '$content', but actually ended with '$actualContent'."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the downloaded content should be "([^"]*)"$/
|
|
*
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function downloadedContentShouldBe(string $content):void {
|
|
$this->checkDownloadedContentMatches($content, '', $this->getResponse());
|
|
}
|
|
|
|
/**
|
|
* @param string $expectedContent
|
|
* @param string $extraErrorText
|
|
* @param ResponseInterface|null $response
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkDownloadedContentMatches(
|
|
string $expectedContent,
|
|
string $extraErrorText = "",
|
|
?ResponseInterface $response = null
|
|
):void {
|
|
$response = $response ?? $this->response;
|
|
$actualContent = (string) $response->getBody();
|
|
// For this test we really care about the content.
|
|
// A separate "Then" step can specifically check the HTTP status.
|
|
// But if the content is wrong (e.g. empty) then it is useful to
|
|
// report the HTTP status to give some clue what might be the problem.
|
|
$actualStatus = $response->getStatusCode();
|
|
if ($extraErrorText !== "") {
|
|
$extraErrorText .= "\n";
|
|
}
|
|
Assert::assertEquals(
|
|
$expectedContent,
|
|
$actualContent,
|
|
$extraErrorText . "The content was expected to be '$expectedContent', but actually is '$actualContent'. HTTP status was $actualStatus"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the content in the response should match the following content:
|
|
*
|
|
* @param PyStringNode $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theContentInTheResponseShouldMatchTheFollowingContent(PyStringNode $content): void {
|
|
$this->checkDownloadedContentMatches($content->getRaw(), '', $this->getResponse());
|
|
}
|
|
|
|
/**
|
|
* @Then the content in the response should include the following content:
|
|
*
|
|
* @param PyStringNode $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theContentInTheResponseShouldIncludeTheFollowingContent(PyStringNode $content): void {
|
|
Assert::assertStringContainsString(
|
|
$content->getRaw(),
|
|
(string) $this->response->getBody()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^if the HTTP status code was "([^"]*)" then the downloaded content for multipart byterange should be:$/
|
|
*
|
|
* @param int $statusCode
|
|
* @param PyStringNode $content
|
|
*
|
|
* @return void
|
|
*
|
|
*/
|
|
public function theDownloadedContentForMultipartByteRangeShouldBe(int $statusCode, PyStringNode $content):void {
|
|
$actualStatusCode = $this->response->getStatusCode();
|
|
if ($actualStatusCode === $statusCode) {
|
|
$actualContent = (string) $this->response->getBody();
|
|
$pattern = ["/--\w*/", "/\s*/m"];
|
|
$actualContent = \preg_replace($pattern, "", $actualContent);
|
|
$content = \preg_replace("/\s*/m", '', $content->getRaw());
|
|
Assert::assertEquals(
|
|
$content,
|
|
$actualContent,
|
|
"The downloaded content was expected to be '$content', but actually is '$actualContent'. HTTP status was $actualStatusCode"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^if the HTTP status code was "([^"]*)" then the downloaded content should be "([^"]*)"$/
|
|
*
|
|
* @param int $statusCode
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkStatusCodeForDownloadedContentShouldBe(int $statusCode, string $content):void {
|
|
$actualStatusCode = $this->response->getStatusCode();
|
|
if ($actualStatusCode === $statusCode) {
|
|
$this->checkDownloadedContentMatches($content, '', $this->getResponse());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the content of file :fileName for user :user should be :content
|
|
*
|
|
* @param string $fileName
|
|
* @param string $user
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function contentOfFileForUserShouldBe(string $fileName, string $user, string $content):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName);
|
|
$actualStatus = $response->getStatusCode();
|
|
if ($actualStatus !== 200) {
|
|
throw new Exception(
|
|
"Expected status code to be '200', but got '$actualStatus'"
|
|
);
|
|
}
|
|
$this->checkDownloadedContentMatches($content, '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the content of the following files for user "([^"]*)" should be "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $content
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function contentOfFollowingFilesShouldBe(string $user, string $content, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
$user = $this->getActualUsername($user);
|
|
foreach ($paths as $file) {
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $file["path"]);
|
|
$actualStatus = $response->getStatusCode();
|
|
Assert::assertEquals(
|
|
200,
|
|
$actualStatus,
|
|
"Expected status code to be '200', but got '$actualStatus'"
|
|
);
|
|
$this->checkDownloadedContentMatches($content, '', $response);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^the content of file "([^"]*)" for user "([^"]*)" using password "([^"]*)" should be "([^"]*)"$/
|
|
*
|
|
* @param string $fileName
|
|
* @param string $user
|
|
* @param string|null $password
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function contentOfFileForUserUsingPasswordShouldBe(
|
|
string $fileName,
|
|
string $user,
|
|
?string $password,
|
|
string $content
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getActualPassword($password);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName, $password);
|
|
$this->checkDownloadedContentMatches($content, '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the content of file "([^"]*)" for user "([^"]*)" should be:$/
|
|
*
|
|
* @param string $fileName
|
|
* @param string $user
|
|
* @param PyStringNode $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function contentOfFileForUserShouldBePyString(
|
|
string $fileName,
|
|
string $user,
|
|
PyStringNode $content
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName);
|
|
$actualStatus = $response->getStatusCode();
|
|
Assert::assertEquals(
|
|
200,
|
|
$actualStatus,
|
|
"Expected status code to be '200', but got '$actualStatus'"
|
|
);
|
|
$this->checkDownloadedContentMatches($content->getRaw(), '', $response);
|
|
}
|
|
|
|
/**
|
|
* @When user :user downloads file :fileName using the WebDAV API
|
|
* @When user :user tries to download file :fileName using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $fileName
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDownloadsFileUsingTheAPI(
|
|
string $user,
|
|
string $fileName
|
|
):void {
|
|
$this->setResponse($this->downloadFileAsUserUsingPassword($user, $fileName));
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $fileName
|
|
* @param string|null $password
|
|
* @param array|null $headers
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function downloadFileAsUserUsingPassword(
|
|
string $user,
|
|
string $fileName,
|
|
?string $password = null,
|
|
?array $headers = []
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getActualPassword($password);
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
'GET',
|
|
$fileName,
|
|
$headers,
|
|
null,
|
|
"files",
|
|
null,
|
|
false,
|
|
$password
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When the public gets the size of the last shared public link using the WebDAV API
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function publicGetsSizeOfLastSharedPublicLinkUsingTheWebdavApi():void {
|
|
$token = ($this->isUsingSharingNG()) ? $this->shareNgGetLastCreatedLinkShareToken() : $this->getLastCreatedPublicShareToken();
|
|
$url = $this->getBaseUrl() . "/remote.php/dav/public-files/$token";
|
|
$this->response = HttpRequestHelper::sendRequest(
|
|
$url,
|
|
$this->getStepLineRef(),
|
|
"PROPFIND"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user gets the size of file :resource using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $resource
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userGetsSizeOfFileUsingTheWebdavApi(string $user, string $resource):void {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getPasswordForUser($user);
|
|
$this->response = WebDavHelper::propfind(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$password,
|
|
$resource,
|
|
[],
|
|
$this->getStepLineRef(),
|
|
"0",
|
|
"files",
|
|
$this->getDavPathVersion()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the size of the file should be :size
|
|
*
|
|
* @param string $size
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theSizeOfTheFileShouldBe(string $size):void {
|
|
$responseXml = HttpRequestHelper::getResponseXml(
|
|
$this->response,
|
|
__METHOD__
|
|
);
|
|
$xmlPart = $responseXml->xpath("//d:prop/d:getcontentlength");
|
|
$actualSize = (string) $xmlPart[0];
|
|
Assert::assertEquals(
|
|
$size,
|
|
$actualSize,
|
|
__METHOD__
|
|
. " Expected size of the file was '$size', but got '$actualSize' instead."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the content of file "([^"]*)" for user "([^"]*)" should be "([^"]*)" plus end-of-line$/
|
|
*
|
|
* @param string $fileName
|
|
* @param string $user
|
|
* @param string $content
|
|
*
|
|
* @return void
|
|
*/
|
|
public function contentOfFileForUserShouldBePlusEndOfLine(string $fileName, string $user, string $content):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName);
|
|
$actualStatus = $response->getStatusCode();
|
|
Assert::assertEquals(
|
|
200,
|
|
$actualStatus,
|
|
"Expected status code to be '200', but got '$actualStatus'"
|
|
);
|
|
$this->checkDownloadedContentMatches("$content\n", '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Then the following headers should be set
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theFollowingHeadersShouldBeSet(TableNode $table):void {
|
|
$this->verifyTableNodeColumns(
|
|
$table,
|
|
['header', 'value']
|
|
);
|
|
foreach ($table->getColumnsHash() as $header) {
|
|
$headerName = $header['header'];
|
|
$expectedHeaderValue = $header['value'];
|
|
$returnedHeader = $this->response->getHeader($headerName);
|
|
$expectedHeaderValue = $this->substituteInLineCodes($expectedHeaderValue);
|
|
|
|
if (empty($returnedHeader)) {
|
|
throw new Exception(
|
|
\sprintf(
|
|
"Missing expected header '%s'",
|
|
$headerName
|
|
)
|
|
);
|
|
}
|
|
$headerValue = $returnedHeader[0];
|
|
|
|
Assert::assertEquals(
|
|
$expectedHeaderValue,
|
|
$headerValue,
|
|
__METHOD__
|
|
. " Expected value for header '$headerName' was '$expectedHeaderValue', but got '$headerValue' instead."
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then as :user :entry :path should not exist
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $path
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws Exception
|
|
*/
|
|
public function asFileOrFolderShouldNotExist(
|
|
string $user,
|
|
string $entry,
|
|
string $path
|
|
):void {
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, $entry, $path);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string|null $path
|
|
* @param string $type
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkFileOrFolderDoesNotExistsForUser(
|
|
string $user,
|
|
string $entry = "file",
|
|
?string $path = null,
|
|
string $type = "files"
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$path = $this->substituteInLineCodes($path);
|
|
$response = $this->listFolder(
|
|
$user,
|
|
$path,
|
|
'0',
|
|
null,
|
|
$type
|
|
);
|
|
$statusCode = $response->getStatusCode();
|
|
if ($statusCode < 400 || $statusCode > 499) {
|
|
try {
|
|
$responseXml = HttpRequestHelper::getResponseXml(
|
|
$response,
|
|
__METHOD__
|
|
);
|
|
} catch (Exception $e) {
|
|
Assert::fail(
|
|
"$entry '$path' should not exist. But API returned $statusCode without XML in the body"
|
|
);
|
|
}
|
|
Assert::assertTrue(
|
|
$this->isEtagValid($this->getEtagFromResponseXmlObject($responseXml)),
|
|
"$entry '$path' should not exist. But API returned $statusCode without an etag in the body"
|
|
);
|
|
$isCollection = $responseXml->xpath("//d:prop/d:resourcetype/d:collection");
|
|
if (\count($isCollection) === 0) {
|
|
$actualResourceType = "file";
|
|
} else {
|
|
$actualResourceType = "folder";
|
|
}
|
|
|
|
if ($entry === $actualResourceType) {
|
|
Assert::fail(
|
|
"$entry '$path' should not exist. But it does."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the following (files|folders) should not exist$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function followingFilesShouldNotExist(
|
|
string $user,
|
|
string $entry,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
$entry = \rtrim($entry, "s");
|
|
|
|
foreach ($paths as $file) {
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, $entry, $file["path"]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then as :user :entry :path should exist
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $path
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function asFileOrFolderShouldExist(
|
|
string $user,
|
|
string $entry,
|
|
string $path
|
|
):void {
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $path);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $path
|
|
* @param string|null $type
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkFileOrFolderExistsForUser(
|
|
string $user,
|
|
string $entry,
|
|
string $path,
|
|
?string $type = "files"
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$path = $this->substituteInLineCodes($path);
|
|
$responseXml = $this->listFolderAndReturnResponseXml(
|
|
$user,
|
|
$path,
|
|
'0',
|
|
null,
|
|
$type
|
|
);
|
|
Assert::assertTrue(
|
|
$this->isEtagValid($this->getEtagFromResponseXmlObject($responseXml)),
|
|
"$entry '$path' expected to exist for user $user but not found"
|
|
);
|
|
$isCollection = $responseXml->xpath("//d:prop/d:resourcetype/d:collection");
|
|
if ($entry === "folder") {
|
|
Assert::assertEquals(\count($isCollection), 1, "Unexpectedly, `$path` is not a folder");
|
|
} elseif ($entry === "file") {
|
|
Assert::assertEquals(\count($isCollection), 0, "Unexpectedly, `$path` is not a file");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the following (files|folders) should exist$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function followingFilesOrFoldersShouldExist(
|
|
string $user,
|
|
string $entry,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
$entry = \rtrim($entry, "s");
|
|
|
|
foreach ($paths as $file) {
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $file["path"]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $path
|
|
* @param string $type
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function fileOrFolderExists(
|
|
string $user,
|
|
string $entry,
|
|
string $path,
|
|
string $type = "files"
|
|
):bool {
|
|
try {
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $path, $type);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $folderDepth requires 1 to see elements without children
|
|
* @param array|null $properties
|
|
* @param string $type
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws Exception
|
|
*/
|
|
public function listFolder(
|
|
string $user,
|
|
string $path,
|
|
string $folderDepth,
|
|
?array $properties = null,
|
|
string $type = "files"
|
|
):ResponseInterface {
|
|
if ($this->customDavPath !== null) {
|
|
$path = $this->customDavPath . $path;
|
|
}
|
|
|
|
return WebDavHelper::listFolder(
|
|
$this->getBaseUrl(),
|
|
$this->getActualUsername($user),
|
|
$this->getPasswordForUser($user),
|
|
$path,
|
|
$folderDepth,
|
|
$this->getStepLineRef(),
|
|
$properties,
|
|
$type,
|
|
$this->getDavPathVersion()
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $folderDepth requires 1 to see elements without children
|
|
* @param array|null $properties
|
|
* @param string $type
|
|
*
|
|
* @return SimpleXMLElement
|
|
* @throws Exception
|
|
*/
|
|
public function listFolderAndReturnResponseXml(
|
|
string $user,
|
|
string $path,
|
|
string $folderDepth,
|
|
?array $properties = null,
|
|
string $type = "files"
|
|
):SimpleXMLElement {
|
|
return HttpRequestHelper::getResponseXml(
|
|
$this->listFolder(
|
|
$user,
|
|
$path,
|
|
$folderDepth,
|
|
$properties,
|
|
$type
|
|
),
|
|
__METHOD__
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should (not|)\s?see the following elements$/
|
|
*
|
|
* @param string $user
|
|
* @param string $shouldOrNot
|
|
* @param TableNode $elements
|
|
*
|
|
* @return void
|
|
* @throws InvalidArgumentException|Exception
|
|
*
|
|
*/
|
|
public function userShouldSeeTheElements(string $user, string $shouldOrNot, TableNode $elements):void {
|
|
$should = ($shouldOrNot !== "not");
|
|
$this->checkElementList($user, $elements, $should);
|
|
}
|
|
|
|
/**
|
|
* asserts that the user can or cannot see a list of files/folders by propfind
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $elements
|
|
* @param boolean $expectedToBeListed
|
|
*
|
|
* @return void
|
|
* @throws InvalidArgumentException
|
|
* @throws Exception
|
|
*
|
|
*/
|
|
public function checkElementList(
|
|
string $user,
|
|
TableNode $elements,
|
|
bool $expectedToBeListed = true
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->verifyTableNodeColumnsCount($elements, 1);
|
|
$elementRows = $elements->getRows();
|
|
$elementsSimplified = $this->simplifyArray($elementRows);
|
|
if ($this->davPropfindDepthInfinityIsEnabled()) {
|
|
// get a full "infinite" list of the user's root folder in one request
|
|
// and process that to check the elements (resources)
|
|
$responseXmlObject = $this->listFolderAndReturnResponseXml(
|
|
$user,
|
|
"/",
|
|
"infinity"
|
|
);
|
|
foreach ($elementsSimplified as $expectedElement) {
|
|
// Allow the table of expected elements to have entries that do
|
|
// not have to specify the "implied" leading slash, or have multiple
|
|
// leading slashes, to make scenario outlines more flexible
|
|
$expectedElement = $this->encodePath($expectedElement);
|
|
$expectedElement = "/" . \ltrim($expectedElement, "/");
|
|
$webdavPath = "/" . $this->getFullDavFilesPath($user) . $expectedElement;
|
|
$element = $responseXmlObject->xpath(
|
|
"//d:response/d:href[text() = \"$webdavPath\"]"
|
|
);
|
|
if ($expectedToBeListed
|
|
&& (!isset($element[0]) || urldecode($element[0]->__toString()) !== urldecode($webdavPath))
|
|
) {
|
|
Assert::fail(
|
|
"$webdavPath is not in propfind answer but should be"
|
|
);
|
|
} elseif (!$expectedToBeListed && isset($element[0])
|
|
) {
|
|
Assert::fail(
|
|
"$webdavPath is in propfind answer but should not be"
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// do a PROPFIND for each element
|
|
foreach ($elementsSimplified as $elementToRequest) {
|
|
// Allow the table of expected elements to have entries that do
|
|
// not have to specify the "implied" leading slash, or have multiple
|
|
// leading slashes, to make scenario outlines more flexible
|
|
$elementToRequest = "/" . \ltrim($elementToRequest, "/");
|
|
// Note: in the request we ask to do a PROPFIND on a resource like:
|
|
// /some-folder with spaces/sub-folder
|
|
// but the response has encoded values for the special characters like:
|
|
// /some-folder%20with%20spaces/sub-folder
|
|
// So we need both $elementToRequest and $expectedElement
|
|
$expectedElement = $this->encodePath($elementToRequest);
|
|
$responseXmlObject = $this->listFolderAndReturnResponseXml(
|
|
$user,
|
|
$elementToRequest,
|
|
"1"
|
|
);
|
|
|
|
// TODO: make it work for folder entries
|
|
// Doesn't work for folder entries
|
|
// as the folder entry has trailing '/' in d:href
|
|
$webdavPath = "/" . $this->getFullDavFilesPath($user) . $expectedElement;
|
|
$element = $responseXmlObject->xpath(
|
|
"//d:response/d:href[text() = \"$webdavPath\"]"
|
|
);
|
|
|
|
if ($expectedToBeListed
|
|
&& (!isset($element[0]) || urldecode($element[0]->__toString()) !== urldecode($webdavPath))
|
|
) {
|
|
Assert::fail(
|
|
"$webdavPath is not in propfind answer but should be"
|
|
);
|
|
} elseif (!$expectedToBeListed && isset($element[0])
|
|
) {
|
|
Assert::fail(
|
|
"$webdavPath is in propfind answer but should not be"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function uploadFile(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$file = \fopen($this->acceptanceTestsDirLocation() . $source, 'r');
|
|
$this->pauseUploadDelete();
|
|
$response = $this->makeDavRequest(
|
|
$user,
|
|
"PUT",
|
|
$destination,
|
|
[],
|
|
$file,
|
|
"files",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
$this->lastUploadDeleteTime = \time();
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads file :source to :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUploadsAFileToUsingWebDavApi(
|
|
string $user,
|
|
string $source,
|
|
string $destination
|
|
):void {
|
|
$response = $this->uploadFile($user, $source, $destination);
|
|
$this->setResponse($response);
|
|
$this->setResponseXml(
|
|
HttpRequestHelper::parseResponseAsXml($response)
|
|
);
|
|
$this->pushToLastHttpStatusCodesArray(
|
|
(string) $this->getResponse()->getStatusCode()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file :source to :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return array
|
|
*/
|
|
public function userHasUploadedAFileTo(string $user, string $source, string $destination):array {
|
|
$response = $this->uploadFile($user, $source, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to upload file '$source' to '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
return $response->getHeader('oc-fileid');
|
|
}
|
|
|
|
/**
|
|
* Upload file as a user with different headers
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
* @param array|null $headers
|
|
* @param int|null $noOfChunks Only use for chunked upload when $this->chunkingToUse is not null
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function uploadFileWithHeaders(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
?array $headers = [],
|
|
?int $noOfChunks = 0
|
|
):void {
|
|
$chunkingVersion = $this->chunkingToUse;
|
|
if ($noOfChunks <= 0) {
|
|
$chunkingVersion = null;
|
|
}
|
|
try {
|
|
$this->responseXml = [];
|
|
$this->pauseUploadDelete();
|
|
$this->response = UploadHelper::upload(
|
|
$this->getBaseUrl(),
|
|
$this->getActualUsername($user),
|
|
$this->getUserPassword($user),
|
|
$source,
|
|
$destination,
|
|
$this->getStepLineRef(),
|
|
$headers,
|
|
$this->getDavPathVersion(),
|
|
$chunkingVersion,
|
|
$noOfChunks
|
|
);
|
|
$this->lastUploadDeleteTime = \time();
|
|
} catch (BadResponseException $e) {
|
|
// 4xx and 5xx responses cause an exception
|
|
$this->response = $e->getResponse();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
* @param integer $noOfChunks
|
|
* @param string|null $chunkingVersion
|
|
* @param boolean $async
|
|
* @param array|null $headers
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUploadsAFileInChunk(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
int $noOfChunks = 2,
|
|
?string $chunkingVersion = null,
|
|
bool $async = false,
|
|
?array $headers = []
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
Assert::assertGreaterThan(
|
|
0,
|
|
$noOfChunks,
|
|
"What does it mean to have $noOfChunks chunks?"
|
|
);
|
|
//use the chunking version that works with the set DAV version
|
|
if ($chunkingVersion === null) {
|
|
if ($this->usingOldDavPath || $this->usingSpacesDavPath) {
|
|
$chunkingVersion = "v1";
|
|
} else {
|
|
$chunkingVersion = "v2";
|
|
}
|
|
}
|
|
$this->useSpecificChunking($chunkingVersion);
|
|
Assert::assertTrue(
|
|
WebDavHelper::isValidDavChunkingCombination(
|
|
$this->getDavPathVersion(),
|
|
$this->chunkingToUse
|
|
),
|
|
"invalid chunking/webdav version combination"
|
|
);
|
|
|
|
if ($async === true) {
|
|
$headers['OC-LazyOps'] = 'true';
|
|
}
|
|
$this->uploadFileWithHeaders(
|
|
$user,
|
|
$this->acceptanceTestsDirLocation() . $source,
|
|
$destination,
|
|
$headers,
|
|
$noOfChunks
|
|
);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
|
|
/**
|
|
* sets the chunking version from human-readable format
|
|
*
|
|
* @param string $version (no|v1|v2|new|old)
|
|
*
|
|
* @return void
|
|
*/
|
|
public function useSpecificChunking(string $version):void {
|
|
if ($version === "v1" || $version === "old") {
|
|
$this->chunkingToUse = 1;
|
|
} elseif ($version === "v2" || $version === "new") {
|
|
$this->chunkingToUse = 2;
|
|
} elseif ($version === "no") {
|
|
$this->chunkingToUse = null;
|
|
} else {
|
|
throw new InvalidArgumentException(
|
|
"cannot set chunking version to $version"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Uploading with old/new DAV and chunked/non-chunked.
|
|
* Except do not do the new-DAV-new-chunking combination. That is not being
|
|
* supported on all implementations.
|
|
*
|
|
* @When user :user uploads file :source to filenames based on :destination with all mechanisms except new chunking using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsAFileToWithAllMechanismsExceptNewChunking(
|
|
string $user,
|
|
string $source,
|
|
string $destination
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->uploadResponses = UploadHelper::uploadWithAllMechanisms(
|
|
$this->getBaseUrl(),
|
|
$this->getActualUsername($user),
|
|
$this->getUserPassword($user),
|
|
$this->acceptanceTestsDirLocation() . $source,
|
|
$destination,
|
|
$this->getStepLineRef(),
|
|
false,
|
|
'new'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" uploads file "([^"]*)" to "([^"]*)" in (\d+) chunks (?:with (new|old|v1|v2) chunking and)?\s?using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
* @param int $noOfChunks
|
|
* @param string|null $chunkingVersion old|v1|new|v2 null for autodetect
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsAFileToWithChunks(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
int $noOfChunks = 2,
|
|
?string $chunkingVersion = null
|
|
):void {
|
|
$this->userUploadsAFileInChunk($user, $source, $destination, $noOfChunks, $chunkingVersion);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the HTTP status code of all upload responses should be "([^"]*)"$/
|
|
*
|
|
* @param int $statusCode
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theHTTPStatusCodeOfAllUploadResponsesShouldBe(int $statusCode):void {
|
|
foreach ($this->uploadResponses as $response) {
|
|
Assert::assertEquals(
|
|
$statusCode,
|
|
$response->getStatusCode(),
|
|
'Response did not return expected status code'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the HTTP status code of responses on all endpoints should be :statusCode
|
|
*
|
|
* @param int $statusCode
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theHTTPStatusCodeOfResponsesOnAllEndpointsShouldBe(int $statusCode):void {
|
|
$duplicateRemovedStatusCodes = \array_unique($this->lastHttpStatusCodesArray);
|
|
if (\count($duplicateRemovedStatusCodes) === 1) {
|
|
Assert::assertSame(
|
|
$statusCode,
|
|
\intval($duplicateRemovedStatusCodes[0]),
|
|
'Responses did not return expected http status code'
|
|
);
|
|
$this->emptyLastHTTPStatusCodesArray();
|
|
} else {
|
|
throw new Exception(
|
|
'Expected same but found different http status codes of last requested responses.' .
|
|
'Found status codes: ' . \implode(',', $this->lastHttpStatusCodesArray)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $statusCodes a comma-separated string of expected HTTP status codes
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function checkTheHTTPStatusCodeOfResponsesOnEachEndpoint(string $statusCodes):void {
|
|
$expectedStatusCodes = \explode(',', $statusCodes);
|
|
$actualStatusCodes = $this->lastHttpStatusCodesArray;
|
|
$count = \count($expectedStatusCodes);
|
|
$statusCodesAreAllOk = true;
|
|
if ($count === \count($actualStatusCodes)) {
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$expectedCode = (int)\trim($expectedStatusCodes[$i]);
|
|
$actualCode = (int)$actualStatusCodes[$i];
|
|
if ($expectedCode !== $actualCode) {
|
|
$statusCodesAreAllOk = false;
|
|
}
|
|
}
|
|
} else {
|
|
$statusCodesAreAllOk = false;
|
|
}
|
|
$this->emptyLastHTTPStatusCodesArray();
|
|
Assert::assertTrue(
|
|
$statusCodesAreAllOk,
|
|
'Expected HTTP status codes: "' . $statusCodes .
|
|
'". Found HTTP status codes: "' . \implode(',', $actualStatusCodes) . '"'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the HTTP status code of responses on each endpoint should be :statusCodes respectively
|
|
*
|
|
* @param string $statusCodes a comma-separated string of expected HTTP status codes
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theHTTPStatusCodeOfResponsesOnEachEndpointShouldBe(string $statusCodes):void {
|
|
$this->checkTheHTTPStatusCodeOfResponsesOnEachEndpoint($statusCodes);
|
|
}
|
|
|
|
/**
|
|
* @Then the HTTP status code of responses on each endpoint should be :ocisStatusCodes on oCIS or :revaStatusCodes on reva
|
|
*
|
|
* @param string $ocisStatusCodes a comma-separated string of expected HTTP status codes when running on oCIS
|
|
* @param string $revaStatusCodes a comma-separated string of expected HTTP status codes when running on reva
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theHTTPStatusCodeOfResponsesOnEachEndpointShouldBeOcisReva(string $ocisStatusCodes, string $revaStatusCodes):void {
|
|
if (OcisHelper::isTestingOnReva()) {
|
|
$expectedStatusCodes = $revaStatusCodes;
|
|
} else {
|
|
$expectedStatusCodes = $ocisStatusCodes;
|
|
}
|
|
$this->checkTheHTTPStatusCodeOfResponsesOnEachEndpoint($expectedStatusCodes);
|
|
}
|
|
|
|
/**
|
|
* @Then the OCS status code of responses on each endpoint should be :statusCode respectively
|
|
*
|
|
* @param string $statusCodes
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theOCStatusCodeOfResponsesOnEachEndpointShouldBe(string $statusCodes):void {
|
|
$statusCodes = \explode(',', $statusCodes);
|
|
$count = \count($statusCodes);
|
|
if ($count === \count($this->lastOCSStatusCodesArray)) {
|
|
for ($i = 0; $i < $count; $i++) {
|
|
Assert::assertSame(
|
|
(int)\trim($statusCodes[$i]),
|
|
(int)$this->lastOCSStatusCodesArray[$i],
|
|
'Responses did not return expected OCS status code'
|
|
);
|
|
}
|
|
} else {
|
|
throw new Exception(
|
|
'Expected OCS status codes: "' . \implode(',', $statusCodes) .
|
|
'". Found OCS status codes: "' . \implode(',', $this->lastOCSStatusCodesArray) . '"'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the OCS status code of responses on all endpoints should be :statusCode
|
|
*
|
|
* @param string $statusCode
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theOCSStatusCodeOfResponsesOnAllEndpointsShouldBe(string $statusCode):void {
|
|
$duplicateRemovedStatusCodes = \array_unique($this->lastOCSStatusCodesArray);
|
|
if (\count($duplicateRemovedStatusCodes) === 1) {
|
|
Assert::assertSame(
|
|
\intval($statusCode),
|
|
\intval($duplicateRemovedStatusCodes[0]),
|
|
'Responses did not return expected ocs status code'
|
|
);
|
|
$this->emptyLastOCSStatusCodesArray();
|
|
} else {
|
|
throw new Exception(
|
|
'Expected same but found different ocs status codes of last requested responses.' .
|
|
'Found status codes: ' . \implode(',', $this->lastOCSStatusCodesArray)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should be able to upload file :source to :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldBeAbleToUploadFileTo(string $user, string $source, string $destination):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->uploadFile($user, $source, $destination);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to upload file '$destination'",
|
|
$response
|
|
);
|
|
$this->checkFileOrFolderExistsForUser($user, "file", $destination);
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should not be able to upload file :source to :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $source
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theUserShouldNotBeAbleToUploadFileTo(string $user, string $source, string $destination):void {
|
|
$fileAlreadyExists = $this->fileOrFolderExists($user, "file", $destination);
|
|
if ($fileAlreadyExists) {
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $destination);
|
|
$initialContent = (string) $response->getBody();
|
|
}
|
|
$response = $this->uploadFile($user, $source, $destination);
|
|
$this->theHTTPStatusCodeShouldBe(["403", "423"], "", $response);
|
|
if ($fileAlreadyExists) {
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $destination);
|
|
$currentContent = (string) $response->getBody();
|
|
Assert::assertSame(
|
|
$initialContent,
|
|
$currentContent,
|
|
__METHOD__ . " user $user was unexpectedly able to upload $source to $destination - the content has changed:"
|
|
);
|
|
} else {
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, "file", $destination);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^the HTTP status code of all upload responses should be between "(\d+)" and "(\d+)"$/
|
|
*
|
|
* @param int $minStatusCode
|
|
* @param int $maxStatusCode
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theHTTPStatusCodeOfAllUploadResponsesShouldBeBetween(
|
|
int $minStatusCode,
|
|
int $maxStatusCode
|
|
):void {
|
|
foreach ($this->uploadResponses as $response) {
|
|
Assert::assertGreaterThanOrEqual(
|
|
$minStatusCode,
|
|
$response->getStatusCode(),
|
|
'Response did not return expected status code'
|
|
);
|
|
Assert::assertLessThanOrEqual(
|
|
$maxStatusCode,
|
|
$response->getStatusCode(),
|
|
'Response did not return expected status code'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string $shouldOrNot
|
|
* @param string|null $exceptChunkingType
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkIfFilesExist(
|
|
string $user,
|
|
string $destination,
|
|
string $shouldOrNot,
|
|
?string $exceptChunkingType = ''
|
|
):void {
|
|
switch ($exceptChunkingType) {
|
|
case 'old':
|
|
$exceptChunkingSuffix = 'olddav-oldchunking';
|
|
break;
|
|
case 'new':
|
|
$exceptChunkingSuffix = 'newdav-newchunking';
|
|
break;
|
|
default:
|
|
$exceptChunkingSuffix = '';
|
|
break;
|
|
}
|
|
|
|
if ($shouldOrNot !== "not") {
|
|
foreach (['old', 'new'] as $davVersion) {
|
|
foreach (["{$davVersion}dav-regular", "{$davVersion}dav-{$davVersion}chunking"] as $suffix) {
|
|
if ($suffix !== $exceptChunkingSuffix) {
|
|
$this->checkFileOrFolderExistsForUser(
|
|
$user,
|
|
'file',
|
|
"$destination-$suffix"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach (['old', 'new'] as $davVersion) {
|
|
foreach (["{$davVersion}dav-regular", "{$davVersion}dav-{$davVersion}chunking"] as $suffix) {
|
|
if ($suffix !== $exceptChunkingSuffix) {
|
|
$this->checkFileOrFolderDoesNotExistsForUser(
|
|
$user,
|
|
'file',
|
|
"$destination-$suffix"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file :destination of size :bytes bytes
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string $bytes
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasUploadedFileToOfSizeBytes(string $user, string $destination, string $bytes):void {
|
|
$user = $this->getActualUsername($user);
|
|
$filename = "filespecificSize.txt";
|
|
$this->createLocalFileOfSpecificSize($filename, $bytes, 'a');
|
|
Assert::assertFileExists($this->workStorageDirLocation() . $filename);
|
|
$response = $this->uploadFile($user, $this->temporaryStorageSubfolderName() . "/$filename", $destination);
|
|
$expectedElements = new TableNode([["$destination"]]);
|
|
$this->checkElementList($user, $expectedElements);
|
|
$this->theHTTPStatusCodeShouldBe([201, 204], '', $response);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file :destination ending with :text of size :bytes bytes
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string $text
|
|
* @param string $bytes
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasUploadedFileToEndingWithOfSizeBytes(string $user, string $destination, string $text, string $bytes):void {
|
|
$filename = "filespecificSize.txt";
|
|
$this->createLocalFileOfSpecificSize($filename, $bytes, $text);
|
|
Assert::assertFileExists($this->workStorageDirLocation() . $filename);
|
|
$response = $this->uploadFile($user, $this->temporaryStorageSubfolderName() . "/$filename", $destination);
|
|
$this->theHTTPStatusCodeShouldBeBetween(200, 299, $response);
|
|
$this->removeFile($this->workStorageDirLocation(), $filename);
|
|
$expectedElements = new TableNode([["$destination"]]);
|
|
$this->checkElementList($user, $expectedElements);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param string $destination
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
public function uploadFileWithContent(
|
|
string $user,
|
|
?string $content,
|
|
string $destination,
|
|
?bool $isGivenStep = false
|
|
): ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$this->pauseUploadDelete();
|
|
$response = $this->makeDavRequest(
|
|
$user,
|
|
"PUT",
|
|
$destination,
|
|
[],
|
|
$content,
|
|
"files",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
$this->lastUploadDeleteTime = \time();
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads file with content :content to :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
* @throws JsonException
|
|
*/
|
|
public function userUploadsAFileWithContentTo(
|
|
string $user,
|
|
?string $content,
|
|
string $destination
|
|
):void {
|
|
$response = $this->uploadFileWithContent($user, $content, $destination);
|
|
$this->setResponse($response);
|
|
$this->pushToLastHttpStatusCodesArray();
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" uploads the following files with content "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception|GuzzleException
|
|
*/
|
|
public function userUploadsFollowingFilesWithContentTo(
|
|
string $user,
|
|
?string $content,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $destination) {
|
|
$response = $this->uploadFileWithContent($user, $content, $destination["path"]);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads file :source to :destination with mtime :mtime using 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
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsFileToWithMtimeUsingTheWebdavApi(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
string $mtime,
|
|
?bool $isGivenStep = false
|
|
):void {
|
|
$mtime = new DateTime($mtime);
|
|
$mtime = $mtime->format('U');
|
|
$user = $this->getActualUsername($user);
|
|
$this->response = UploadHelper::upload(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
$this->acceptanceTestsDirLocation() . $source,
|
|
$destination,
|
|
$this->getStepLineRef(),
|
|
["X-OC-Mtime" => $mtime],
|
|
$this->getDavPathVersion(),
|
|
null,
|
|
1,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file :source to :destination with mtime :mtime using 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
|
|
*/
|
|
public function userHasUploadedFileToWithMtimeUsingTheWebdavApi(
|
|
string $user,
|
|
string $source,
|
|
string $destination,
|
|
string $mtime
|
|
):void {
|
|
$mtime = new DateTime($mtime);
|
|
$mtime = $mtime->format('U');
|
|
$user = $this->getActualUsername($user);
|
|
$response = UploadHelper::upload(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
$this->acceptanceTestsDirLocation() . $source,
|
|
$destination,
|
|
$this->getStepLineRef(),
|
|
["X-OC-Mtime" => $mtime],
|
|
$this->getDavPathVersion(),
|
|
null,
|
|
1,
|
|
true
|
|
);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then as :user the mtime of the file :resource should be :mtime
|
|
*
|
|
* @param string $user
|
|
* @param string $resource
|
|
* @param string $mtime
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theMtimeOfTheFileShouldBe(
|
|
string $user,
|
|
string $resource,
|
|
string $mtime
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getPasswordForUser($user);
|
|
$baseUrl = $this->getBaseUrl();
|
|
$mtime = new DateTime($mtime);
|
|
$mtime = $mtime->format('U');
|
|
Assert::assertEquals(
|
|
$mtime,
|
|
WebDavHelper::getMtimeOfResource(
|
|
$user,
|
|
$password,
|
|
$baseUrl,
|
|
$resource,
|
|
$this->getStepLineRef(),
|
|
$this->getDavPathVersion()
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file with content :content to :destination
|
|
*
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
* @throws JsonException
|
|
*/
|
|
public function userHasUploadedAFileWithContentTo(
|
|
string $user,
|
|
?string $content,
|
|
string $destination
|
|
):array {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->uploadFileWithContent($user, $content, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to upload file '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
return $response->getHeader('oc-fileid');
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has uploaded the following files with content "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception|GuzzleException
|
|
*/
|
|
public function userHasUploadedFollowingFilesWithContent(
|
|
string $user,
|
|
?string $content,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$files = $table->getHash();
|
|
|
|
foreach ($files as $destination) {
|
|
$destination = $destination['path'];
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->uploadFileWithContent($user, $content, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to upload file '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" downloads the following files using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userDownloadsFollowingFiles(
|
|
string $user,
|
|
TableNode $table
|
|
):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$files = $table->getHash();
|
|
$this->emptyLastHTTPStatusCodesArray();
|
|
foreach ($files as $fileName) {
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $fileName["path"]);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads a file with content :content and mtime :mtime to :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string|null $content
|
|
* @param string $mtime
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsAFileWithContentAndMtimeTo(
|
|
string $user,
|
|
?string $content,
|
|
string $mtime,
|
|
string $destination
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$mtime = new DateTime($mtime);
|
|
$mtime = $mtime->format('U');
|
|
$response = $this->makeDavRequest(
|
|
$user,
|
|
"PUT",
|
|
$destination,
|
|
["X-OC-Mtime" => $mtime],
|
|
$content
|
|
);
|
|
$this->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $checksum
|
|
* @param string|null $content
|
|
* @param string $destination
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function uploadFileWithChecksumAndContent(
|
|
string $user,
|
|
string $checksum,
|
|
?string $content,
|
|
string $destination,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$this->pauseUploadDelete();
|
|
$response = $this->makeDavRequest(
|
|
$user,
|
|
"PUT",
|
|
$destination,
|
|
['OC-Checksum' => $checksum],
|
|
$content,
|
|
"files",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
$this->lastUploadDeleteTime = \time();
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads file with checksum :checksum and content :content to :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $checksum
|
|
* @param string $content
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUploadsAFileWithChecksumAndContentTo(
|
|
string $user,
|
|
string $checksum,
|
|
string $content,
|
|
string $destination
|
|
):void {
|
|
$response = $this->uploadFileWithChecksumAndContent($user, $checksum, $content, $destination);
|
|
$this->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has uploaded file with checksum :checksum and content :content to :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $checksum
|
|
* @param string|null $content
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasUploadedAFileWithChecksumAndContentTo(
|
|
string $user,
|
|
string $checksum,
|
|
?string $content,
|
|
string $destination
|
|
):void {
|
|
$response = $this->uploadFileWithChecksumAndContent(
|
|
$user,
|
|
$checksum,
|
|
$content,
|
|
$destination,
|
|
true
|
|
);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to upload file with checksum '$checksum' to '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should be able to delete (file|folder|entry) "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $source
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldBeAbleToDeleteEntry(string $user, string $entry, string $source):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
$this->deleteFile($user, $source);
|
|
$this->checkFileOrFolderDoesNotExistsForUser($user, $entry, $source);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" should not be able to delete (file|folder|entry) "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $entry
|
|
* @param string $source
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theUserShouldNotBeAbleToDeleteEntry(string $user, string $entry, string $source):void {
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
$this->deleteFile($user, $source);
|
|
$this->checkFileOrFolderExistsForUser($user, $entry, $source);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $resource
|
|
*
|
|
* @return void
|
|
*/
|
|
public function deleteFile(string $user, string $resource):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$this->pauseUploadDelete();
|
|
$response = $this->makeDavRequest($user, 'DELETE', $resource, []);
|
|
$this->lastUploadDeleteTime = \time();
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @When user :user deletes file/folder :resource using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $resource
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDeletesFile(string $user, string $resource):void {
|
|
$response = $this->deleteFile($user, $resource);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
|
|
/**
|
|
* @When user :user deletes file :filename from space :space using file-id path :davPath
|
|
*
|
|
* @param string $user
|
|
* @param string $filename
|
|
* @param string $space
|
|
* @param string $davPath
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDeletesFileFromSpaceUsingFileIdPath(string $user, string $filename, string $space, string $davPath):void {
|
|
$requestUrl = $this->getBaseUrl() . $davPath;
|
|
$user = $this->getActualUsername($user);
|
|
$password = $this->getPasswordForUser($user);
|
|
$response = HttpRequestHelper::sendRequest(
|
|
$requestUrl,
|
|
null,
|
|
'DELETE',
|
|
$user,
|
|
$password
|
|
);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has deleted (?:file|folder|entity) "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $resource
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasDeletedResource(string $user, string $resource):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->deleteFile($user, $resource);
|
|
// If the file or folder was there and got deleted then we get a 204
|
|
// That is good and the expected status
|
|
// If the file or folder was already not there then we get a 404
|
|
// That is not expected. Scenarios that use "Given user has deleted..."
|
|
// should only be using such steps when it is a file that exists and needs
|
|
// to be deleted.
|
|
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["204"],
|
|
"HTTP status code was not 204 while trying to delete resource '$resource' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has deleted the following (?:files|folders|resources)$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasDeletedFollowingFiles(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $file) {
|
|
$file = $file["path"];
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->deleteFile($user, $file);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["204"],
|
|
"HTTP status code was not 204 while trying to delete resource '$file' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" deletes the following (?:files|folders)$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userDeletesFollowingFiles(string $user, TableNode $table):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $file) {
|
|
$response = $this->deleteFile($user, $file["path"]);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" deletes these (?:files|folders|entries) without delays using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table of files or folders to delete
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userDeletesFilesFoldersWithoutDelays(string $user, TableNode $table):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->verifyTableNodeColumnsCount($table, 1);
|
|
foreach ($table->getTable() as $entry) {
|
|
$entryName = $entry[0];
|
|
$this->response = $this->makeDavRequest($user, 'DELETE', $entryName, []);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
$this->lastUploadDeleteTime = \time();
|
|
}
|
|
|
|
/**
|
|
* @When user :user creates folder :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
public function userCreatesFolder(string $user, string $destination):void {
|
|
$response = $this->createFolder($user, $destination);
|
|
$this->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has created folder :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
public function userHasCreatedFolder(string $user, string $destination):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to create folder '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given admin has created folder :destination
|
|
*
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
public function adminHasCreatedFolder(string $destination):void {
|
|
$admin = $this->getAdminUsername();
|
|
Assert::assertEquals(
|
|
"admin",
|
|
$admin,
|
|
__METHOD__ . "The provided user is not admin but '" . $admin . "'"
|
|
);
|
|
$response = $this->createFolder($admin, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to create folder '$destination' for admin '$admin'",
|
|
$response
|
|
);
|
|
$this->adminResources[] = $destination;
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has created the following folders$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasCreatedFollowingFolders(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $path) {
|
|
$destination = $path["path"];
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to create folder '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should be able to create folder :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldBeAbleToCreateFolder(string $user, string $destination):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination, true);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to create folder '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
$this->checkFileOrFolderExistsForUser(
|
|
$user,
|
|
"folder",
|
|
$destination
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should be able to create folder :destination using password :password
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string $password
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldBeAbleToCreateFolderUsingPassword(string $user, string $destination, string $password):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination, true, $password);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["201", "204"],
|
|
"HTTP status code was not 201 or 204 while trying to create folder '$destination' for user '$user'",
|
|
$response
|
|
);
|
|
$this->checkFileOrFolderExistsForUser(
|
|
$user,
|
|
"folder",
|
|
$destination
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should not be able to create folder :destination
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldNotBeAbleToCreateFolder(string $user, string $destination):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination);
|
|
$this->theHTTPStatusCodeShouldBeBetween(400, 499, $response);
|
|
$this->checkFileOrFolderDoesNotExistsForUser(
|
|
$user,
|
|
"folder",
|
|
$destination
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then user :user should not be able to create folder :destination using password :password
|
|
*
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string $password
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userShouldNotBeAbleToCreateFolderUsingPassword(string $user, string $destination, string $password):void {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->createFolder($user, $destination, false, $password);
|
|
$this->theHTTPStatusCodeShouldBeBetween(400, 499, $response);
|
|
$this->checkFileOrFolderDoesNotExistsForUser(
|
|
$user,
|
|
"folder",
|
|
$destination
|
|
);
|
|
}
|
|
/**
|
|
* Old style chunking upload
|
|
*
|
|
* @When user :user uploads the following :total chunks to :file with old chunking and using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $total
|
|
* @param string $file
|
|
* @param TableNode $chunkDetails table of 2 columns, chunk number and chunk
|
|
* content with column headings, e.g.
|
|
* | number | content |
|
|
* | 1 | first data |
|
|
* | 2 | followed by second data |
|
|
* Chunks may be numbered out-of-order if desired.
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsTheFollowingTotalChunksUsingOldChunking(
|
|
string $user,
|
|
string $total,
|
|
string $file,
|
|
TableNode $chunkDetails
|
|
):void {
|
|
$this->verifyTableNodeColumns($chunkDetails, ['number', 'content']);
|
|
foreach ($chunkDetails->getHash() as $chunkDetail) {
|
|
$chunkNumber = (int)$chunkDetail['number'];
|
|
$chunkContent = $chunkDetail['content'];
|
|
$this->setResponse($this->userUploadChunkedFile($user, $chunkNumber, (int)$total, $chunkContent, $file));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Old style chunking upload
|
|
*
|
|
* @When user :user uploads the following chunks to :file with old chunking and using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $file
|
|
* @param TableNode $chunkDetails table of 2 columns, chunk number and chunk
|
|
* content with column headings, e.g.
|
|
* | number | content |
|
|
* | 1 | first data |
|
|
* | 2 | followed by second data |
|
|
* Chunks may be numbered out-of-order if desired.
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userUploadsTheFollowingChunksUsingOldChunking(
|
|
string $user,
|
|
string $file,
|
|
TableNode $chunkDetails
|
|
):void {
|
|
$total = (string) \count($chunkDetails->getHash());
|
|
$this->verifyTableNodeColumns($chunkDetails, ['number', 'content']);
|
|
foreach ($chunkDetails->getHash() as $chunkDetail) {
|
|
$chunkNumber = (int)$chunkDetail['number'];
|
|
$chunkContent = $chunkDetail['content'];
|
|
$this->setResponse($this->userUploadChunkedFile($user, $chunkNumber, (int)$total, $chunkContent, $file));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Old style chunking upload
|
|
*
|
|
* @param string $user
|
|
* @param int $num
|
|
* @param int $total
|
|
* @param string|null $data
|
|
* @param string $destination
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userUploadChunkedFile(
|
|
string $user,
|
|
int $num,
|
|
int $total,
|
|
?string $data,
|
|
string $destination,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$num -= 1;
|
|
$file = "$destination-chunking-42-$total-$num";
|
|
$this->pauseUploadDelete();
|
|
$response = $this->makeDavRequest(
|
|
$user,
|
|
'PUT',
|
|
$file,
|
|
['OC-Chunked' => '1'],
|
|
$data,
|
|
"uploads",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
$this->lastUploadDeleteTime = \time();
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* New style chunking upload
|
|
*
|
|
* @param string $user
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $file
|
|
* @param TableNode $chunkDetails table of 2 columns, chunk number and chunk
|
|
* content with column headings, e.g.
|
|
* | number | content |
|
|
* | 1 | first data |
|
|
* | 2 | second data |
|
|
* Chunks may be numbered out-of-order if desired.
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function uploadTheFollowingChunksUsingNewChunking(
|
|
string $user,
|
|
string $type,
|
|
string $file,
|
|
TableNode $chunkDetails,
|
|
?bool $isGivenStep = false
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$async = false;
|
|
if ($type === "asynchronously") {
|
|
$async = true;
|
|
}
|
|
$this->verifyTableNodeColumns($chunkDetails, ["number", "content"]);
|
|
$this->userUploadsChunksUsingNewChunking(
|
|
$user,
|
|
$file,
|
|
'chunking-42',
|
|
$chunkDetails->getHash(),
|
|
$async,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* New style chunking upload
|
|
*
|
|
* @param string $user
|
|
* @param string $file
|
|
* @param string $chunkingId
|
|
* @param array $chunkDetails of chunks of the file. Each array entry is
|
|
* itself an array of 2 items:
|
|
* [number] the chunk number
|
|
* [content] data content of the chunk
|
|
* Chunks may be numbered out-of-order if desired.
|
|
* @param bool $async use asynchronous MOVE at the end or not
|
|
* @param bool $isGivenStep
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUploadsChunksUsingNewChunking(
|
|
string $user,
|
|
string $file,
|
|
string $chunkingId,
|
|
array $chunkDetails,
|
|
bool $async = false,
|
|
bool $isGivenStep = false
|
|
):void {
|
|
$this->pauseUploadDelete();
|
|
if ($isGivenStep) {
|
|
$response = $this->userCreateANewChunkingUploadWithId($user, $chunkingId, true);
|
|
$this->theHTTPStatusCodeShouldBeBetween(200, 299, $response);
|
|
} else {
|
|
$this->setResponse($this->userCreateANewChunkingUploadWithId($user, $chunkingId));
|
|
}
|
|
foreach ($chunkDetails as $chunkDetail) {
|
|
$chunkNumber = (int)$chunkDetail['number'];
|
|
$chunkContent = $chunkDetail['content'];
|
|
if ($isGivenStep) {
|
|
$response = $this->userUploadNewChunkFileOfWithToId($user, $chunkNumber, $chunkContent, $chunkingId, true);
|
|
$this->theHTTPStatusCodeShouldBeBetween(200, 299, $response);
|
|
} else {
|
|
$response = $this->userUploadNewChunkFileOfWithToId($user, $chunkNumber, $chunkContent, $chunkingId);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
$headers = [];
|
|
if ($async === true) {
|
|
$headers = ['OC-LazyOps' => 'true'];
|
|
}
|
|
$response = $this->moveNewDavChunkToFinalFile($user, $chunkingId, $file, $headers, $isGivenStep);
|
|
if ($isGivenStep) {
|
|
$this->theHTTPStatusCodeShouldBeBetween(200, 299, $response);
|
|
} else {
|
|
$this->setResponse($response);
|
|
}
|
|
$this->lastUploadDeleteTime = \time();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userCreateANewChunkingUploadWithId(
|
|
string $user,
|
|
string $id,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$destination = "/uploads/$user/$id";
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
'MKCOL',
|
|
$destination,
|
|
[],
|
|
null,
|
|
"uploads",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param int $num
|
|
* @param string|null $data
|
|
* @param string $id
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userUploadNewChunkFileOfWithToId(
|
|
string $user,
|
|
int $num,
|
|
?string $data,
|
|
string $id,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$destination = "/uploads/$user/$id/$num";
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
'PUT',
|
|
$destination,
|
|
[],
|
|
$data,
|
|
"uploads",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $dest
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userMoveNewChunkFileWithIdToMychunkedfile(
|
|
string $user,
|
|
string $id,
|
|
string $type,
|
|
string $dest
|
|
):ResponseInterface {
|
|
$headers = [];
|
|
if ($type === "asynchronously") {
|
|
$headers = ['OC-LazyOps' => 'true'];
|
|
}
|
|
return $this->moveNewDavChunkToFinalFile($user, $id, $dest, $headers);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" moves new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $dest
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userMovesNewChunkFileWithIdToMychunkedfile(
|
|
string $user,
|
|
string $id,
|
|
string $type,
|
|
string $dest
|
|
):void {
|
|
$this->setResponse($this->userMoveNewChunkFileWithIdToMychunkedfile($user, $id, $type, $dest));
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $dest
|
|
* @param int $size
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userMoveNewChunkFileWithIdToMychunkedfileWithSize(
|
|
string $user,
|
|
string $id,
|
|
string $type,
|
|
string $dest,
|
|
int $size
|
|
):ResponseInterface {
|
|
$headers = ['OC-Total-Length' => $size];
|
|
if ($type === "asynchronously") {
|
|
$headers['OC-LazyOps'] = 'true';
|
|
}
|
|
return $this->moveNewDavChunkToFinalFile(
|
|
$user,
|
|
$id,
|
|
$dest,
|
|
$headers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $dest
|
|
* @param string $checksum
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function userMoveNewChunkFileWithIdToMychunkedfileWithChecksum(
|
|
string $user,
|
|
string $id,
|
|
string $type,
|
|
string $dest,
|
|
string $checksum
|
|
):ResponseInterface {
|
|
$headers = ['OC-Checksum' => $checksum];
|
|
if ($type === "asynchronously") {
|
|
$headers['OC-LazyOps'] = 'true';
|
|
}
|
|
return $this->moveNewDavChunkToFinalFile(
|
|
$user,
|
|
$id,
|
|
$dest,
|
|
$headers
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has moved new chunk file with id "([^"]*)"\s?(asynchronously|) to "([^"]*)" with checksum "([^"]*)"
|
|
*
|
|
* @param string $user
|
|
* @param string $id
|
|
* @param string $type "asynchronously" or empty
|
|
* @param string $dest
|
|
* @param string $checksum
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasMovedNewChunkFileWithIdToMychunkedfileWithChecksum(
|
|
string $user,
|
|
string $id,
|
|
string $type,
|
|
string $dest,
|
|
string $checksum
|
|
):void {
|
|
$response = $this->userMoveNewChunkFileWithIdToMychunkedfileWithChecksum(
|
|
$user,
|
|
$id,
|
|
$type,
|
|
$dest,
|
|
$checksum
|
|
);
|
|
$this->theHTTPStatusCodeShouldBe("201", "", $response);
|
|
}
|
|
|
|
/**
|
|
* Move chunked new DAV file to final file
|
|
*
|
|
* @param string $user user
|
|
* @param string $id upload id
|
|
* @param string $destination destination path
|
|
* @param array $headers extra headers
|
|
* @param bool|null $isGivenStep
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
private function moveNewDavChunkToFinalFile(
|
|
string $user,
|
|
string $id,
|
|
string $destination,
|
|
array $headers,
|
|
?bool $isGivenStep = false
|
|
):ResponseInterface {
|
|
$user = $this->getActualUsername($user);
|
|
$source = "/uploads/$user/$id/.file";
|
|
$headers['Destination'] = $this->destinationHeaderValue(
|
|
$user,
|
|
$destination
|
|
);
|
|
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
'MOVE',
|
|
$source,
|
|
$headers,
|
|
null,
|
|
"uploads",
|
|
null,
|
|
false,
|
|
null,
|
|
[],
|
|
null,
|
|
$isGivenStep
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Delete chunked-upload directory
|
|
*
|
|
* @param string $user user
|
|
* @param string $id upload id
|
|
* @param array $headers extra headers
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
private function deleteUpload(string $user, string $id, array $headers):ResponseInterface {
|
|
$source = "/uploads/$user/$id";
|
|
return $this->makeDavRequest(
|
|
$user,
|
|
'DELETE',
|
|
$source,
|
|
$headers,
|
|
null,
|
|
"uploads"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* URL encodes the given path but keeps the slashes
|
|
*
|
|
* @param string $path to encode
|
|
*
|
|
* @return string encoded path
|
|
*/
|
|
public function encodePath(string $path):string {
|
|
// slashes need to stay
|
|
// in ocis even brackets are encoded
|
|
return \str_replace('%2F', '/', \rawurlencode($path));
|
|
}
|
|
|
|
/**
|
|
* an unauthenticated client connects to the DAV endpoint using the WebDAV API
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function connectToDavEndpoint():ResponseInterface {
|
|
return $this->makeDavRequest(
|
|
null,
|
|
'PROPFIND',
|
|
'',
|
|
[]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When an unauthenticated client connects to the DAV endpoint using the WebDAV API
|
|
*
|
|
* @return void
|
|
*/
|
|
public function connectingToDavEndpoint():void {
|
|
$this->setResponse($this->connectToDavEndpoint());
|
|
}
|
|
|
|
/**
|
|
* @Then there should be no duplicate headers
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function thereAreNoDuplicateHeaders():void {
|
|
$headers = $this->response->getHeaders();
|
|
foreach ($headers as $headerName => $headerValues) {
|
|
// if a header has multiple values, they must be different
|
|
if (\count($headerValues) > 1
|
|
&& \count(\array_unique($headerValues)) < \count($headerValues)
|
|
) {
|
|
throw new Exception("Duplicate header found: $headerName");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the following headers should not be set
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theFollowingHeadersShouldNotBeSet(TableNode $table):void {
|
|
$this->verifyTableNodeColumns(
|
|
$table,
|
|
['header']
|
|
);
|
|
foreach ($table->getColumnsHash() as $header) {
|
|
$headerName = $header['header'];
|
|
$headerValue = $this->response->getHeader($headerName);
|
|
//Note: getHeader returns an empty array if the named header does not exist
|
|
$headerValue0 = $headerValue[0] ?? '';
|
|
Assert::assertEmpty(
|
|
$headerValue,
|
|
"header $headerName should not exist " .
|
|
"but does and is set to $headerValue0"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the following headers should match these regular expressions
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theFollowingHeadersShouldMatchTheseRegularExpressions(TableNode $table):void {
|
|
$this->headersShouldMatchRegularExpressions($table);
|
|
}
|
|
|
|
/**
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
*/
|
|
public function headersShouldMatchRegularExpressions(TableNode $table):void {
|
|
$this->verifyTableNodeColumnsCount($table, 2);
|
|
foreach ($table->getTable() as $header) {
|
|
$headerName = $header[0];
|
|
$expectedHeaderValue = $header[1];
|
|
$expectedHeaderValue = $this->substituteInLineCodes(
|
|
$expectedHeaderValue,
|
|
null,
|
|
['preg_quote' => ['/']]
|
|
);
|
|
|
|
$returnedHeaders = $this->response->getHeader($headerName);
|
|
$returnedHeader = $returnedHeaders[0];
|
|
Assert::assertNotFalse(
|
|
(bool) \preg_match($expectedHeaderValue, $returnedHeader),
|
|
"'$expectedHeaderValue' does not match '$returnedHeader'"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^if the HTTP status code was "([^"]*)" then the following headers should match these regular expressions$/
|
|
*
|
|
* @param int $statusCode
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function statusCodeShouldMatchTheseRegularExpressions(int $statusCode, TableNode $table):void {
|
|
$actualStatusCode = $this->response->getStatusCode();
|
|
if ($actualStatusCode === $statusCode) {
|
|
$this->headersShouldMatchRegularExpressions($table);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the following headers should match these regular expressions for user :user
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function headersShouldMatchRegularExpressionsForUser(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumnsCount($table, 2);
|
|
$user = $this->getActualUsername($user);
|
|
foreach ($table->getTable() as $header) {
|
|
$headerName = $header[0];
|
|
$expectedHeaderValue = $header[1];
|
|
$expectedHeaderValue = $this->substituteInLineCodes(
|
|
$expectedHeaderValue,
|
|
$user,
|
|
['preg_quote' => ['/']]
|
|
);
|
|
|
|
$returnedHeaders = $this->response->getHeader($headerName);
|
|
$returnedHeader = $returnedHeaders[0];
|
|
Assert::assertNotFalse(
|
|
(bool) \preg_match($expectedHeaderValue, $returnedHeader),
|
|
"'$expectedHeaderValue' does not match '$returnedHeader'"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" deletes everything from folder "([^"]*)" using the WebDAV API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $folder
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userDeletesEverythingInFolder(
|
|
string $user,
|
|
string $folder
|
|
):void {
|
|
$this->deleteEverythingInFolder($user, $folder, false);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $folder
|
|
* @param boolean $checkEachDelete
|
|
*
|
|
* @return void
|
|
*/
|
|
public function deleteEverythingInFolder(
|
|
string $user,
|
|
string $folder,
|
|
bool $checkEachDelete = false
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$responseXmlObject = $this->listFolderAndReturnResponseXml(
|
|
$user,
|
|
$folder,
|
|
'1'
|
|
);
|
|
$elementList = $responseXmlObject->xpath("//d:response/d:href");
|
|
if (\is_array($elementList) && \count($elementList)) {
|
|
\array_shift($elementList); //don't delete the folder itself
|
|
$davPrefix = "/" . $this->getFullDavFilesPath($user);
|
|
foreach ($elementList as $element) {
|
|
$element = \substr((string)$element, \strlen($davPrefix));
|
|
if ($checkEachDelete) {
|
|
$user = $this->getActualUsername($user);
|
|
$response = $this->deleteFile($user, $element);
|
|
$this->theHTTPStatusCodeShouldBe(
|
|
["204"],
|
|
"HTTP status code was not 204 while trying to delete resource '$element' for user '$user'",
|
|
$response
|
|
);
|
|
} else {
|
|
$this->setResponse($this->deleteFile($user, $element));
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :user downloads the preview of :path with width :width and height :height using the WebDAV API
|
|
* @When user :user tries to download the preview of nonexistent file :path with width :width and height :height using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function downloadPreviewOfFiles(string $user, string $path, string $width, string $height):void {
|
|
$response = $this->downloadPreviews(
|
|
$user,
|
|
$path,
|
|
null,
|
|
$width,
|
|
$height
|
|
);
|
|
$this->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @When user :user downloads the preview of shared resource :path with width :width and height :height using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDownloadsThePreviewOfSharedResourceWithWidthAndHeightUsingTheWebdavApi(string $user, string $path, string $width, string $height): void {
|
|
if ($this->getDavPathVersion() === 3) {
|
|
$this->setResponse($this->downloadSharedFilePreview($user, $path, $width, $height));
|
|
} else {
|
|
$this->setResponse($this->downloadPreviews($user, $path, null, $width, $height));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has downloaded the preview of shared resource :path with width :width and height :height
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasDownloadedThePreviewOfSharedResourceWithWidthAndHeight(string $user, string $path, string $width, string $height): void {
|
|
if ($this->getDavPathVersion() === 3) {
|
|
$response = $this->downloadSharedFilePreview($user, $path, $width, $height);
|
|
} else {
|
|
$response = $this->downloadPreviews($user, $path, null, $width, $height);
|
|
}
|
|
$this->setResponse($response);
|
|
$this->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
$this->checkImageDimensions($width, $height);
|
|
// save response to user response dictionary for further comparisons
|
|
$this->userResponseBodyContents[$user] = $this->responseBodyContent;
|
|
}
|
|
|
|
/**
|
|
* @Then as user :user the preview of shared resource :path with width :width and height :height should have been changed
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function asUserThePreviewOfSharedResourceWithWidthAndHeightShouldHaveBeenChanged(string $user, string $path, string $width, string $height):void {
|
|
if ($this->getDavPathVersion() === 3) {
|
|
$response = $this->downloadSharedFilePreview($user, $path, $width, $height);
|
|
} else {
|
|
$response = $this->downloadPreviews($user, $path, null, $width, $height);
|
|
}
|
|
$this->setResponse($response);
|
|
$this->theHTTPStatusCodeShouldBe(200, '', $response);
|
|
$newResponseBodyContents = $this->response->getBody()->getContents();
|
|
Assert::assertNotEquals(
|
|
$newResponseBodyContents,
|
|
// different users can download files before and after an update is made to a file
|
|
// previous response content is fetched from the user response body content array entry for that user
|
|
$this->userResponseBodyContents[$user],
|
|
__METHOD__ . " previous and current previews content is same but expected to be different",
|
|
);
|
|
// update the saved content for the next comparison
|
|
$this->userResponseBodyContents[$user] = $newResponseBodyContents;
|
|
}
|
|
|
|
/**
|
|
* @When user :user uploads file with content :content to shared resource :destination using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $content
|
|
* @param string $destination
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userUploadsFileWithContentSharedResourceToUsingTheWebdavApi(string $user, string $content, string $destination): void {
|
|
if ($this->getDavPathVersion() === 3) {
|
|
$this->setResponse($this->uploadToSharedFolder($user, $destination, $content));
|
|
} else {
|
|
$this->setResponse($this->uploadFileWithContent($user, $content, $destination));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $path
|
|
*
|
|
* @return string
|
|
* @throws GuzzleException
|
|
*/
|
|
public function getSharesMountPath(
|
|
string $user,
|
|
string $path
|
|
): string {
|
|
$user = $this->getActualUsername($user);
|
|
$path = trim($path, "/");
|
|
$pathArray = explode("/", $path);
|
|
$sharedFolder = $pathArray[0] === "Shares" ? $pathArray[1] : $pathArray[0];
|
|
|
|
$shareMountId = GraphHelper::getShareMountId(
|
|
$this->getBaseUrl(),
|
|
$this->getStepLineRef(),
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
$sharedFolder
|
|
);
|
|
|
|
$path = \array_slice($pathArray, array_search($sharedFolder, $pathArray) + 1);
|
|
$path = \implode("/", $path);
|
|
|
|
return "$shareMountId/$path";
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string|null $width
|
|
* @param string|null $height
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
public function downloadSharedFilePreview(
|
|
string $user,
|
|
string $path,
|
|
?string $width = null,
|
|
?string $height = null
|
|
): ResponseInterface {
|
|
if ($width !== null && $height !== null) {
|
|
$urlParameter = [
|
|
'x' => $width,
|
|
'y' => $height,
|
|
'forceIcon' => '0',
|
|
'preview' => '1'
|
|
];
|
|
$urlParameter = \http_build_query($urlParameter, '', '&');
|
|
} else {
|
|
$urlParameter = null;
|
|
}
|
|
$sharesPath = $this->getSharesMountPath($user, $path) . '/?' . $urlParameter;
|
|
|
|
$davPath = WebDavHelper::getDavPath($user, $this->getDavPathVersion());
|
|
$fullUrl = $this->getBaseUrl() . "/$davPath" . $sharesPath;
|
|
|
|
return HttpRequestHelper::sendRequest(
|
|
$fullUrl,
|
|
$this->getStepLineRef(),
|
|
'GET',
|
|
$user,
|
|
$this->getPasswordForUser($user)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $destination
|
|
* @param string|null $content
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws GuzzleException
|
|
*/
|
|
public function uploadToSharedFolder(
|
|
string $user,
|
|
string $destination,
|
|
?string $content = null
|
|
): ResponseInterface {
|
|
$sharesPath = $this->getSharesMountPath($user, $destination);
|
|
|
|
$davPath = WebDavHelper::getDavPath($user, $this->getDavPathVersion());
|
|
$fullUrl = $this->getBaseUrl() . "/$davPath" . $sharesPath;
|
|
|
|
return HttpRequestHelper::sendRequest(
|
|
$fullUrl,
|
|
$this->getStepLineRef(),
|
|
'PUT',
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
null,
|
|
$content
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user1 downloads the preview of :path of :user2 with width :width and height :height using the WebDAV API
|
|
*
|
|
* @param string $user1
|
|
* @param string $path
|
|
* @param string $doDavRequestAsUser
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function downloadPreviewOfOtherUser(string $user1, string $path, string $doDavRequestAsUser, string $width, string $height):void {
|
|
$response = $this->downloadPreviews(
|
|
$user1,
|
|
$path,
|
|
$doDavRequestAsUser,
|
|
$width,
|
|
$height
|
|
);
|
|
$this->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @Then the downloaded image should be :width pixels wide and :height pixels high
|
|
*
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function imageDimensionsShouldBe(string $width, string $height): void {
|
|
$this->checkImageDimensions($width, $height);
|
|
}
|
|
|
|
/**
|
|
* @param string $width
|
|
* @param string $height
|
|
* @param ResponseInterface|null $response
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkImageDimensions(string $width, string $height, ?ResponseInterface $response = null) : void {
|
|
$response = $response ?? $this->getResponse();
|
|
if ($this->responseBodyContent === null) {
|
|
$this->responseBodyContent = $response->getBody()->getContents();
|
|
}
|
|
$size = \getimagesizefromstring($this->responseBodyContent);
|
|
Assert::assertNotFalse($size, "could not get size of image");
|
|
Assert::assertEquals($width, $size[0], "width not as expected");
|
|
Assert::assertEquals($height, $size[1], "height not as expected");
|
|
}
|
|
|
|
/**
|
|
* @Then the downloaded preview content should match with :preview fixtures preview content
|
|
*
|
|
* @param string $filename relative path from fixtures directory
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theDownloadedPreviewContentShouldMatchWithFixturesPreviewContentFor(string $filename):void {
|
|
$expectedPreview = \file_get_contents(__DIR__ . "/../fixtures/" . $filename);
|
|
Assert::assertEquals($expectedPreview, $this->responseBodyContent);
|
|
}
|
|
|
|
/**
|
|
* @Given user :user has downloaded the preview of :path with width :width and height :height
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userDownloadsThePreviewOfWithWidthAndHeight(string $user, string $path, string $width, string $height):void {
|
|
$response = $this->downloadPreviews(
|
|
$user,
|
|
$path,
|
|
null,
|
|
$width,
|
|
$height
|
|
);
|
|
$this->theHTTPStatusCodeShouldBe(200, "", $response);
|
|
$this->checkImageDimensions($width, $height, $response);
|
|
// save response to user response dictionary for further comparisons
|
|
$this->userResponseBodyContents[$user] = $this->responseBodyContent;
|
|
}
|
|
|
|
/**
|
|
* @Then as user :user the preview of :path with width :width and height :height should have been changed
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $width
|
|
* @param string $height
|
|
*
|
|
* @return void
|
|
*/
|
|
public function asUserThePreviewOfPathWithHeightAndWidthShouldHaveBeenChanged(string $user, string $path, string $width, string $height):void {
|
|
$response = $this->downloadPreviews(
|
|
$user,
|
|
$path,
|
|
null,
|
|
$width,
|
|
$height
|
|
);
|
|
$this->theHTTPStatusCodeShouldBe(200, "", $response);
|
|
$newResponseBodyContents = $response->getBody()->getContents();
|
|
Assert::assertNotEquals(
|
|
$newResponseBodyContents,
|
|
// different users can download files before and after an update is made to a file
|
|
// previous response content is fetched from the user response body content array entry for that user
|
|
$this->userResponseBodyContents[$user],
|
|
__METHOD__ . " previous and current previews content is same but expected to be different",
|
|
);
|
|
// update the saved content for the next comparison
|
|
$this->userResponseBodyContents[$user] = $newResponseBodyContents;
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $path
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getFileIdForPath(string $user, string $path): ?string {
|
|
$user = $this->getActualUsername($user);
|
|
try {
|
|
return WebDavHelper::getFileIdForPath(
|
|
$this->getBaseUrl(),
|
|
$user,
|
|
$this->getPasswordForUser($user),
|
|
$path,
|
|
$this->getStepLineRef(),
|
|
$this->getDavPathVersion()
|
|
);
|
|
} catch (Exception $e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has stored id of (?:file|folder) "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userStoresFileIdForPath(string $user, string $path):void {
|
|
$this->storedFileID = $this->getFileIdForPath($user, $path);
|
|
}
|
|
|
|
/**
|
|
* @Then /^user "([^"]*)" (file|folder) "([^"]*)" should have the previously stored id$/
|
|
*
|
|
* @param string $user
|
|
* @param string $fileOrFolder
|
|
* @param string $path
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userFileShouldHaveStoredId(string $user, string $fileOrFolder, string $path):void {
|
|
$user = $this->getActualUsername($user);
|
|
$currentFileID = $this->getFileIdForPath($user, $path);
|
|
Assert::assertEquals(
|
|
$currentFileID,
|
|
$this->storedFileID,
|
|
__METHOD__
|
|
. " User '$user' $fileOrFolder '$path' does not have the previously stored id '$this->storedFileID', but has '$currentFileID'."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the (?:Cal|Card)?DAV (exception|message|reason) should be "([^"]*)"$/
|
|
*
|
|
* @param string $element exception|message|reason
|
|
* @param string $message
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theDavElementShouldBe(string $element, string $message):void {
|
|
$resXmlArray = HttpRequestHelper::parseResponseAsXml($this->getResponse());
|
|
WebDavAssert::assertDavResponseElementIs(
|
|
$element,
|
|
$message,
|
|
$resXmlArray,
|
|
__METHOD__
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $shouldOrNot (not|)
|
|
* @param TableNode $expectedFiles
|
|
* @param string|null $user
|
|
* @param string|null $method
|
|
* @param string|null $folderpath
|
|
*
|
|
* @return void
|
|
* @throws GuzzleException
|
|
*/
|
|
public function propfindResultShouldContainEntries(
|
|
string $shouldOrNot,
|
|
TableNode $expectedFiles,
|
|
?string $user = null,
|
|
?string $method = 'REPORT',
|
|
?string $folderpath = ''
|
|
):void {
|
|
if ($folderpath === "/") {
|
|
$folderpath = "";
|
|
}
|
|
$this->verifyTableNodeColumnsCount($expectedFiles, 1);
|
|
$elementRows = $expectedFiles->getRows();
|
|
$should = ($shouldOrNot !== "not");
|
|
foreach ($elementRows as $expectedFile) {
|
|
$resource = $expectedFile[0];
|
|
$resource = $this->substituteInLineCodes($resource, $user);
|
|
if ($resource === '') {
|
|
continue;
|
|
}
|
|
if ($method === "REPORT") {
|
|
$fileFound = $this->findEntryFromSearchResponse(
|
|
$resource
|
|
);
|
|
if (\is_object($fileFound)) {
|
|
$fileFound = $fileFound->xpath("d:propstat//oc:name");
|
|
}
|
|
} else {
|
|
$fileFound = $this->findEntryFromPropfindResponse(
|
|
$resource,
|
|
$user,
|
|
"files",
|
|
$folderpath
|
|
);
|
|
}
|
|
if ($should) {
|
|
Assert::assertNotEmpty(
|
|
$fileFound,
|
|
"response does not contain the entry '$resource'"
|
|
);
|
|
} else {
|
|
Assert::assertFalse(
|
|
$fileFound,
|
|
"response does contain the entry '$resource' but should not"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^the (?:propfind|search) result of user "([^"]*)" should (not|)\s?contain these (?:files|entries):$/
|
|
*
|
|
* @param string $user
|
|
* @param string $shouldOrNot (not|)
|
|
* @param TableNode $expectedFiles
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function thePropfindResultShouldContainEntries(
|
|
string $user,
|
|
string $shouldOrNot,
|
|
TableNode $expectedFiles
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->propfindResultShouldContainEntries(
|
|
$shouldOrNot,
|
|
$expectedFiles,
|
|
$user
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the (?:propfind|search) result of user "([^"]*)" should contain only these (?:files|entries):$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $expectedFiles
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function thePropfindResultShouldContainOnlyEntries(
|
|
string $user,
|
|
TableNode $expectedFiles
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
|
|
Assert::assertEquals(
|
|
\count($expectedFiles->getTable()),
|
|
$this->getNumberOfEntriesInPropfindResponse(
|
|
$user
|
|
),
|
|
"The number of elements in the response doesn't matches with expected number of elements"
|
|
);
|
|
$this->propfindResultShouldContainEntries(
|
|
'',
|
|
$expectedFiles,
|
|
$user
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the propfind/search result should contain :numFiles files/entries
|
|
*
|
|
* @param int $numFiles
|
|
*
|
|
* @return void
|
|
*/
|
|
public function propfindResultShouldContainNumEntries(int $numFiles):void {
|
|
$this->checkIFResponseContainsNumberEntries($numFiles);
|
|
}
|
|
|
|
/**
|
|
* @param integer $numFiles
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkIFResponseContainsNumberEntries(int $numFiles):void {
|
|
//if we are using that step the second time in a scenario e.g. 'But ... should not'
|
|
//then don't parse the result again, because the result in a ResponseInterface
|
|
if (empty($this->responseXml)) {
|
|
$this->setResponseXml(
|
|
HttpRequestHelper::parseResponseAsXml($this->response)
|
|
);
|
|
}
|
|
Assert::assertIsArray(
|
|
$this->responseXml,
|
|
__METHOD__ . " responseXml is not an array"
|
|
);
|
|
Assert::assertArrayHasKey(
|
|
"value",
|
|
$this->responseXml,
|
|
__METHOD__ . " responseXml does not have key 'value'"
|
|
);
|
|
$multistatusResults = $this->responseXml["value"];
|
|
if ($multistatusResults === null) {
|
|
$multistatusResults = [];
|
|
}
|
|
Assert::assertEquals(
|
|
$numFiles,
|
|
\count($multistatusResults),
|
|
__METHOD__
|
|
. " Expected result to contain '"
|
|
. $numFiles
|
|
. "' files/entries, but got '"
|
|
. \count($multistatusResults)
|
|
. "' files/entries."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the propfind/search result should contain any :expectedNumber of these files/entries:
|
|
*
|
|
* @param integer $expectedNumber
|
|
* @param TableNode $expectedFiles
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theSearchResultShouldContainAnyOfTheseEntries(
|
|
int $expectedNumber,
|
|
TableNode $expectedFiles
|
|
):void {
|
|
$this->checkIfSearchResultContainsFiles(
|
|
$this->getCurrentUser(),
|
|
$expectedNumber,
|
|
$expectedFiles
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then the propfind/search result of user :user should contain any :expectedNumber of these files/entries:
|
|
*
|
|
* @param string $user
|
|
* @param integer $expectedNumber
|
|
* @param TableNode $expectedFiles
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theSearchResultOfUserShouldContainAnyOfTheseEntries(
|
|
string $user,
|
|
int $expectedNumber,
|
|
TableNode $expectedFiles
|
|
):void {
|
|
$this->checkIfSearchResultContainsFiles(
|
|
$user,
|
|
$expectedNumber,
|
|
$expectedFiles
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param integer $expectedNumber
|
|
* @param TableNode $expectedFiles
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkIfSearchResultContainsFiles(
|
|
string $user,
|
|
int $expectedNumber,
|
|
TableNode $expectedFiles
|
|
):void {
|
|
$user = $this->getActualUsername($user);
|
|
$this->verifyTableNodeColumnsCount($expectedFiles, 1);
|
|
$this->checkIFResponseContainsNumberEntries($expectedNumber);
|
|
$elementRows = $expectedFiles->getColumn(0);
|
|
// Remove any "/" from the front (or back) of the expected values passed
|
|
// into the step. findEntryFromPropfindResponse returns entries without
|
|
// any leading (or trailing) slash
|
|
$expectedEntries = \array_map(
|
|
function ($value) {
|
|
return \trim($value, "/");
|
|
},
|
|
$elementRows
|
|
);
|
|
$resultEntries = $this->findEntryFromSearchResponse();
|
|
foreach ($resultEntries as $resultEntry) {
|
|
Assert::assertContains($resultEntry, $expectedEntries);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :arg1 lists the resources in :path with depth :depth using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $depth
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userListsTheResourcesInPathWithDepthUsingTheWebdavApi(string $user, string $path, string $depth):void {
|
|
$response = $this->listFolder(
|
|
$user,
|
|
$path,
|
|
$depth
|
|
);
|
|
$this->setResponse($response);
|
|
$this->setResponseXml(HttpRequestHelper::parseResponseAsXml($this->response));
|
|
}
|
|
|
|
/**
|
|
* @Then the last DAV response for user :user should contain these nodes/elements
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theLastDavResponseShouldContainTheseNodes(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["name"]);
|
|
foreach ($table->getHash() as $row) {
|
|
$path = $this->substituteInLineCodes($row['name']);
|
|
$res = $this->findEntryFromPropfindResponse($path, $user);
|
|
Assert::assertNotFalse($res, "expected $path to be in DAV response but was not found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the last DAV response for user :user should not contain these nodes/elements
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theLastDavResponseShouldNotContainTheseNodes(string $user, TableNode $table):void {
|
|
$this->verifyTableNodeColumns($table, ["name"]);
|
|
foreach ($table->getHash() as $row) {
|
|
$path = $this->substituteInLineCodes($row['name']);
|
|
$res = $this->findEntryFromPropfindResponse($path, $user);
|
|
Assert::assertFalse($res, "expected $path to not be in DAV response but was found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the last public link DAV response should contain these nodes/elements
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theLastPublicDavResponseShouldContainTheseNodes(TableNode $table):void {
|
|
$token = ($this->isUsingSharingNG()) ? $this->shareNgGetLastCreatedLinkShareToken() : $this->getLastCreatedPublicShareToken();
|
|
$this->verifyTableNodeColumns($table, ["name"]);
|
|
$type = $this->usingOldDavPath ? "public-files" : "public-files-new";
|
|
foreach ($table->getHash() as $row) {
|
|
$path = $this->substituteInLineCodes($row['name']);
|
|
$res = $this->findEntryFromPropfindResponse($path, $token, $type);
|
|
Assert::assertNotFalse($res, "expected $path to be in DAV response but was not found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the last public link DAV response should not contain these nodes/elements
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theLastPublicDavResponseShouldNotContainTheseNodes(TableNode $table):void {
|
|
$token = ($this->isUsingSharingNG()) ? $this->shareNgGetLastCreatedLinkShareToken() : $this->getLastCreatedPublicShareToken();
|
|
$this->verifyTableNodeColumns($table, ["name"]);
|
|
$type = $this->usingOldDavPath ? "public-files" : "public-files-new";
|
|
foreach ($table->getHash() as $row) {
|
|
$path = $this->substituteInLineCodes($row['name']);
|
|
$res = $this->findEntryFromPropfindResponse($path, $token, $type);
|
|
Assert::assertFalse($res, "expected $path to not be in DAV response but was found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When the public lists the resources in the last created public link with depth :depth using the WebDAV API
|
|
*
|
|
* @param string $depth
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function thePublicListsTheResourcesInTheLastCreatedPublicLinkWithDepthUsingTheWebdavApi(string $depth):void {
|
|
$token = ($this->isUsingSharingNG()) ? $this->shareNgGetLastCreatedLinkShareToken() : $this->getLastCreatedPublicShareToken();
|
|
$response = $this->listFolder(
|
|
$token,
|
|
'/',
|
|
$depth,
|
|
null,
|
|
$this->usingOldDavPath ? "public-files" : "public-files-new"
|
|
);
|
|
$this->setResponse($response);
|
|
$this->setResponseXml(HttpRequestHelper::parseResponseAsXml($this->response));
|
|
}
|
|
|
|
/**
|
|
* @param string|null $user
|
|
*
|
|
* @return array
|
|
*/
|
|
public function findEntryFromReportResponse(?string $user):array {
|
|
$responseXmlObj = $this->getResponseXmlObject();
|
|
$responseResources = [];
|
|
$hrefs = $responseXmlObj->xpath('//d:href');
|
|
foreach ($hrefs as $href) {
|
|
$hrefParts = \explode("/", (string)$href[0]);
|
|
if (\in_array($user, $hrefParts)) {
|
|
$entry = \urldecode(\end($hrefParts));
|
|
$responseResources[] = $entry;
|
|
} else {
|
|
throw new Error("Expected user: $hrefParts[5] but found: $user");
|
|
}
|
|
}
|
|
return $responseResources;
|
|
}
|
|
|
|
/**
|
|
* parses a PROPFIND response from $this->response into xml
|
|
* and returns found search results if found else returns false
|
|
*
|
|
* @param string|null $user
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getNumberOfEntriesInPropfindResponse(
|
|
?string $user = null
|
|
):int {
|
|
$multistatusResults = $this->getMultiStatusResultFromPropfindResult($user);
|
|
return \count($multistatusResults);
|
|
}
|
|
|
|
/**
|
|
* parses a PROPFIND response from $this->response
|
|
* and returns multistatus data from the response
|
|
*
|
|
* @param string|null $user
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getMultiStatusResultFromPropfindResult(
|
|
?string $user = null
|
|
):array {
|
|
//if we are using that step the second time in a scenario e.g. 'But ... should not'
|
|
//then don't parse the result again, because the result in a ResponseInterface
|
|
if (empty($this->responseXml)) {
|
|
$this->setResponseXml(
|
|
HttpRequestHelper::parseResponseAsXml($this->response)
|
|
);
|
|
}
|
|
Assert::assertNotEmpty($this->responseXml, __METHOD__ . ' Response is empty');
|
|
if ($user === null) {
|
|
$user = $this->getCurrentUser();
|
|
}
|
|
|
|
Assert::assertIsArray(
|
|
$this->responseXml,
|
|
__METHOD__ . " responseXml for user $user is not an array"
|
|
);
|
|
Assert::assertArrayHasKey(
|
|
"value",
|
|
$this->responseXml,
|
|
__METHOD__ . " responseXml for user $user does not have key 'value'"
|
|
);
|
|
$multistatus = $this->responseXml["value"];
|
|
if ($multistatus == null) {
|
|
$multistatus = [];
|
|
}
|
|
return $multistatus;
|
|
}
|
|
|
|
/**
|
|
* Escapes the given string for
|
|
* 1. Space --> %20
|
|
* 2. Opening Small Bracket --> %28
|
|
* 3. Closing Small Bracket --> %29
|
|
*
|
|
* @param string $path - File path to parse
|
|
*
|
|
* @return string
|
|
*/
|
|
public function escapePath(string $path): string {
|
|
return \str_replace([" ", "(", ")"], ["%20", "%28", "%29"], $path);
|
|
}
|
|
|
|
/**
|
|
* parses a PROPFIND response from $this->response into xml
|
|
* and returns found search results if found else returns false
|
|
*
|
|
* @param string|null $entryNameToSearch
|
|
* @param string|null $user
|
|
* @param string $type
|
|
* @param string $folderPath
|
|
*
|
|
* @return string|array|boolean
|
|
*
|
|
* string if $entryNameToSearch is given and is found
|
|
* array if $entryNameToSearch is not given
|
|
* boolean false if $entryNameToSearch is given and is not found
|
|
*
|
|
* @throws GuzzleException
|
|
*/
|
|
public function findEntryFromPropfindResponse(
|
|
?string $entryNameToSearch = null,
|
|
?string $user = null,
|
|
string $type = "files",
|
|
string $folderPath = ''
|
|
) {
|
|
$trimmedEntryNameToSearch = '';
|
|
// trim any leading "/" passed by the caller, we can just match the "raw" name
|
|
if ($entryNameToSearch != null) {
|
|
$trimmedEntryNameToSearch = \trim($entryNameToSearch, "/");
|
|
}
|
|
// url encode for spaces and brackets that may appear in the filePath
|
|
$folderPath = $this->escapePath($folderPath);
|
|
// topWebDavPath should be something like /remote.php/webdav/ or
|
|
// /remote.php/dav/files/alice/
|
|
$topWebDavPath = "/" . $this->getFullDavFilesPath($user) . "/" . $folderPath;
|
|
switch ($type) {
|
|
case "files":
|
|
break;
|
|
case "public-files":
|
|
case "public-files-old":
|
|
case "public-files-new":
|
|
$topWebDavPath = "/" . $this->getPublicLinkDavPath($user, $type) . "/";
|
|
break;
|
|
default:
|
|
throw new Exception("error");
|
|
}
|
|
$multistatusResults = $this->getMultiStatusResultFromPropfindResult($user);
|
|
$results = [];
|
|
foreach ($multistatusResults as $multistatusResult) {
|
|
$entryPath = $multistatusResult['value'][0]['value'];
|
|
$entryName = \str_replace($topWebDavPath, "", $entryPath);
|
|
$entryName = \rawurldecode($entryName);
|
|
$entryName = \trim($entryName, "/");
|
|
if ($trimmedEntryNameToSearch === $entryName) {
|
|
return $multistatusResult;
|
|
}
|
|
$results[] = $entryName;
|
|
}
|
|
if ($entryNameToSearch === null) {
|
|
return $results;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* parses a REPORT response from $this->response into xml
|
|
* and returns found search results if found else returns false
|
|
*
|
|
* @param string|null $entryNameToSearch
|
|
* @param bool|null $searchForHighlightString
|
|
*
|
|
* @return string|array|boolean
|
|
*
|
|
* string if $entryNameToSearch is given and is found
|
|
* array if $entryNameToSearch is not given
|
|
* boolean false if $entryNameToSearch is given and is not found
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function findEntryFromSearchResponse(
|
|
?string $entryNameToSearch = null,
|
|
?bool $searchForHighlightString = false
|
|
) {
|
|
// trim any leading "/" passed by the caller, we can just match the "raw" name
|
|
if ($entryNameToSearch !== null) {
|
|
$entryNameToSearch = \trim($entryNameToSearch, "/");
|
|
}
|
|
$spacesBaseUrl = "/" . webDavHelper::getDavPath(null, webDavHelper::DAV_VERSION_SPACES);
|
|
$searchResults = $this->getResponseXml()->xpath("//d:multistatus/d:response");
|
|
$results = [];
|
|
foreach ($searchResults as $item) {
|
|
$href = (string)$item->xpath("d:href")[0];
|
|
$shareRootXml = $item->xpath("d:propstat//oc:shareroot");
|
|
$href = \str_replace($spacesBaseUrl, "", $href);
|
|
$resourcePath = $href;
|
|
// do not try to parse the resource path
|
|
// if the item to search is space itself
|
|
if (!GraphHelper::isSpaceId($entryNameToSearch ?? '')) {
|
|
$resourcePath = \substr($href, \strpos($href, '/') + 1);
|
|
}
|
|
if (\count($shareRootXml)) {
|
|
$shareroot = \trim((string)$shareRootXml[0], "/");
|
|
$resourcePath = $shareroot . "/" . $resourcePath;
|
|
}
|
|
$resourcePath = \rawurldecode($resourcePath);
|
|
if ($entryNameToSearch === $resourcePath) {
|
|
// If searching for a single entry,
|
|
// we return a SimpleXmlElement of found item
|
|
return $item;
|
|
}
|
|
if ($searchForHighlightString) {
|
|
// If searching for highlighted string,
|
|
// we return an array of entries with highlighted content as value
|
|
// Example:
|
|
// [
|
|
// "<entryName1>" => "<highlighted-content>"
|
|
// "<entryName2>" => "<highlighted-content>"
|
|
// ]
|
|
$actualHighlightString = $item->xpath("d:propstat//oc:highlights");
|
|
$results[$resourcePath] = (string)$actualHighlightString[0];
|
|
} else {
|
|
// If list all the entries i.e. $entryNameToSearch=null,
|
|
// we return an array of entries in the response
|
|
// Example:
|
|
// ["<entry1>", "<entry2>"]
|
|
$results[] = $resourcePath;
|
|
}
|
|
}
|
|
if ($entryNameToSearch === null) {
|
|
return $results;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Prevent creating two uploads and/or deletes with the same "stime"
|
|
* That is based on seconds in some implementations.
|
|
* This prevents duplication of etags or other problems with
|
|
* trashbin/versions save/restore.
|
|
*
|
|
* Set env var UPLOAD_DELETE_WAIT_TIME to 1 to activate a 1-second pause.
|
|
* By default, there is no pause. That allows testing of implementations
|
|
* which should be able to cope with multiple upload/delete actions in the
|
|
* same second.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function pauseUploadDelete():void {
|
|
$time = \time();
|
|
$uploadWaitTime = \getenv("UPLOAD_DELETE_WAIT_TIME");
|
|
|
|
$uploadWaitTime = $uploadWaitTime ? (int)$uploadWaitTime : 0;
|
|
|
|
if (($this->lastUploadDeleteTime !== null)
|
|
&& ($uploadWaitTime > 0)
|
|
&& (($time - $this->lastUploadDeleteTime) < $uploadWaitTime)
|
|
) {
|
|
\sleep($uploadWaitTime);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $index
|
|
* @param string $expectedUsername
|
|
*
|
|
* @return void
|
|
*/
|
|
public function checkAuthorOfAVersionOfFile(string $index, string $expectedUsername):void {
|
|
$expectedUserDisplayName = $this->getUserDisplayName($expectedUsername);
|
|
$resXml = $this->getResponseXmlObject();
|
|
if ($resXml === null) {
|
|
$resXml = HttpRequestHelper::getResponseXml(
|
|
$this->getResponse(),
|
|
__METHOD__
|
|
);
|
|
$this->setResponseXmlObject($resXml);
|
|
}
|
|
|
|
// the username should be in oc:meta-version-edited-by
|
|
$xmlPart = $resXml->xpath("//oc:meta-version-edited-by");
|
|
$authors = [];
|
|
foreach ($xmlPart as $idx => $author) {
|
|
// The first element is the root path element which is not a version
|
|
// So skipping it
|
|
if ($idx !== 0) {
|
|
$authors[] = $author->__toString();
|
|
}
|
|
}
|
|
if (!isset($authors[$index - 1])) {
|
|
Assert::fail(
|
|
'could not find version with index "' . $index . '" for oc:meta-version-edited-by property in response to user "' . $this->responseUser . '"'
|
|
);
|
|
}
|
|
$actualUser = $authors[$index - 1];
|
|
Assert::assertEquals(
|
|
$expectedUsername,
|
|
$actualUser,
|
|
"Expected user of version with index $index in response to user '$this->responseUser' was '$expectedUsername', but got '$actualUser'"
|
|
);
|
|
|
|
// the user's display name should be in oc:meta-version-edited-by-name
|
|
$xmlPart = $resXml->xpath("//oc:meta-version-edited-by-name");
|
|
$displaynames = [];
|
|
foreach ($xmlPart as $idx => $displayname) {
|
|
// The first element is the root path element which is not a version
|
|
// So skipping it
|
|
if ($idx !== 0) {
|
|
$displaynames[] = $displayname->__toString();
|
|
}
|
|
}
|
|
if (!isset($displaynames[$index - 1])) {
|
|
Assert::fail(
|
|
'could not find version with index "' . $index . '" for oc:meta-version-edited-by-name property in response to user "' . $this->responseUser . '"'
|
|
);
|
|
}
|
|
$actualUserDisplayName = $displaynames[$index - 1];
|
|
Assert::assertEquals(
|
|
$expectedUserDisplayName,
|
|
$actualUserDisplayName,
|
|
"Expected display name of version with index $index in response to user '$this->responseUser' was '$expectedUserDisplayName', but got '$actualUserDisplayName'"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user downloads the content of GDPR report :pathToFile
|
|
*
|
|
* @param string $user
|
|
* @param string $pathToFile
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userGetsTheContentOfGeneratedJsonReport(string $user, string $pathToFile): void {
|
|
$password = $this->getPasswordForUser($user);
|
|
$response = $this->downloadFileAsUserUsingPassword($user, $pathToFile, $password);
|
|
$this->setResponse($response);
|
|
$this->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|