mirror of
https://github.com/owncloud/ocis.git
synced 2025-04-18 23:44:07 +03:00
1153 lines
34 KiB
PHP
1153 lines
34 KiB
PHP
<?php declare(strict_types=1);
|
|
/**
|
|
* @author Vincent Petry <pvince81@owncloud.com>
|
|
*
|
|
* @copyright Copyright (c) 2018, ownCloud GmbH
|
|
* @license AGPL-3.0
|
|
*
|
|
* This code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License, version 3,
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
use Behat\Behat\Context\Context;
|
|
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
|
use Behat\Gherkin\Node\TableNode;
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
use PHPUnit\Framework\Assert;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use TestHelpers\HttpRequestHelper;
|
|
use TestHelpers\WebDavHelper;
|
|
|
|
require_once 'bootstrap.php';
|
|
|
|
/**
|
|
* Trashbin context
|
|
*/
|
|
class TrashbinContext implements Context {
|
|
private FeatureContext $featureContext;
|
|
|
|
/**
|
|
* @param string|null $user user
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function emptyTrashbin(?string $user):ResponseInterface {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$davPathVersion = $this->featureContext->getDavPathVersion();
|
|
return WebDavHelper::makeDavRequest(
|
|
$this->featureContext->getBaseUrl(),
|
|
$user,
|
|
$this->featureContext->getPasswordForUser($user),
|
|
'DELETE',
|
|
null,
|
|
[],
|
|
$this->featureContext->getStepLineRef(),
|
|
null,
|
|
$davPathVersion,
|
|
'trash-bin'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user empties the trashbin using the trashbin API
|
|
*
|
|
* @param string $user user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userEmptiesTrashbin(string $user): void {
|
|
$this->featureContext->setResponse($this->emptyTrashbin($user));
|
|
}
|
|
/**
|
|
* @Given user :user has emptied the trashbin
|
|
*
|
|
* @param string $user user
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userHasEmptiedTrashbin(string $user):void {
|
|
$response = $this->emptyTrashbin($user);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(204, '', $response);
|
|
}
|
|
|
|
/**
|
|
* Get files list from the response from trashbin api
|
|
*
|
|
* @param SimpleXMLElement|null $responseXml
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getTrashbinContentFromResponseXml(?SimpleXMLElement $responseXml): array {
|
|
$xmlElements = $responseXml->xpath('//d:response');
|
|
$files = \array_map(
|
|
static function (SimpleXMLElement $element) {
|
|
$href = $element->xpath('./d:href')[0];
|
|
|
|
$propStats = $element->xpath('./d:propstat');
|
|
$successPropStat = \array_filter(
|
|
$propStats,
|
|
static function (SimpleXMLElement $propStat) {
|
|
$status = $propStat->xpath('./d:status');
|
|
return (string) $status[0] === 'HTTP/1.1 200 OK';
|
|
}
|
|
);
|
|
if (isset($successPropStat[0])) {
|
|
$successPropStat = $successPropStat[0];
|
|
|
|
$name = $successPropStat->xpath('./d:prop/oc:trashbin-original-filename');
|
|
$mtime = $successPropStat->xpath('./d:prop/oc:trashbin-delete-timestamp');
|
|
$resourcetype = $successPropStat->xpath('./d:prop/d:resourcetype');
|
|
if (\array_key_exists(0, $resourcetype) && ($resourcetype[0]->asXML() === "<d:resourcetype><d:collection/></d:resourcetype>")) {
|
|
$collection[0] = true;
|
|
} else {
|
|
$collection[0] = false;
|
|
}
|
|
$originalLocation = $successPropStat->xpath('./d:prop/oc:trashbin-original-location');
|
|
} else {
|
|
$name = [];
|
|
$mtime = [];
|
|
$collection = [];
|
|
$originalLocation = [];
|
|
}
|
|
|
|
return [
|
|
'href' => (string) $href,
|
|
'name' => isset($name[0]) ? (string) $name[0] : null,
|
|
'mtime' => isset($mtime[0]) ? (string) $mtime[0] : null,
|
|
'collection' => $collection[0] ?? false,
|
|
'original-location' => isset($originalLocation[0]) ? (string) $originalLocation[0] : null
|
|
];
|
|
},
|
|
$xmlElements
|
|
);
|
|
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* List the top of the trashbin folder for a user
|
|
*
|
|
* @param string|null $user user
|
|
* @param string $depth
|
|
*
|
|
* @return array response
|
|
* @throws Exception
|
|
*/
|
|
public function listTopOfTrashbinFolder(?string $user, string $depth = "1"):array {
|
|
$password = $this->featureContext->getPasswordForUser($user);
|
|
$davPathVersion = $this->featureContext->getDavPathVersion();
|
|
$response = WebDavHelper::listFolder(
|
|
$this->featureContext->getBaseUrl(),
|
|
$user,
|
|
$password,
|
|
"",
|
|
$depth,
|
|
$this->featureContext->getStepLineRef(),
|
|
[
|
|
'oc:trashbin-original-filename',
|
|
'oc:trashbin-original-location',
|
|
'oc:trashbin-delete-timestamp',
|
|
'd:getlastmodified'
|
|
],
|
|
'trash-bin',
|
|
$davPathVersion
|
|
);
|
|
$this->featureContext->setResponse($response);
|
|
$responseXml = HttpRequestHelper::getResponseXml(
|
|
$response,
|
|
__METHOD__
|
|
);
|
|
|
|
$this->featureContext->setResponseXmlObject($responseXml);
|
|
$files = $this->getTrashbinContentFromResponseXml($responseXml);
|
|
// filter root element
|
|
$files = \array_filter(
|
|
$files,
|
|
static function ($element) use ($user) {
|
|
return ($element['href'] !== "/remote.php/dav/trash-bin/$user/");
|
|
}
|
|
);
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* List trashbin folder
|
|
*
|
|
* @param string|null $user user
|
|
* @param string $depth
|
|
*
|
|
* @return array of all the items in the trashbin of the user
|
|
* @throws Exception
|
|
*/
|
|
public function listTrashbinFolder(?string $user, string $depth = "1"):array {
|
|
return $this->listTrashbinFolderCollection(
|
|
$user,
|
|
"",
|
|
$depth
|
|
);
|
|
}
|
|
|
|
/**
|
|
* List a collection in the trashbin
|
|
*
|
|
* @param string|null $user user
|
|
* @param string|null $collectionPath the string of ids of the folder and sub-folders
|
|
* @param string $depth
|
|
* @param int $level
|
|
*
|
|
* @return array response
|
|
* @throws Exception
|
|
*/
|
|
public function listTrashbinFolderCollection(?string $user, ?string $collectionPath = "", string $depth = "1", int $level = 1):array {
|
|
// $collectionPath should be some list of file-ids like 2147497661/2147497662
|
|
// or the empty string, which will list the whole trashbin from the top.
|
|
$collectionPath = \trim($collectionPath, "/");
|
|
$password = $this->featureContext->getPasswordForUser($user);
|
|
$davPathVersion = $this->featureContext->getDavPathVersion();
|
|
$response = WebDavHelper::listFolder(
|
|
$this->featureContext->getBaseUrl(),
|
|
$user,
|
|
$password,
|
|
$collectionPath,
|
|
$depth,
|
|
$this->featureContext->getStepLineRef(),
|
|
[
|
|
'oc:trashbin-original-filename',
|
|
'oc:trashbin-original-location',
|
|
'oc:trashbin-delete-timestamp',
|
|
'd:resourcetype',
|
|
'd:getlastmodified'
|
|
],
|
|
'trash-bin',
|
|
$davPathVersion
|
|
);
|
|
$response->getBody()->rewind();
|
|
$statusCode = $response->getStatusCode();
|
|
$respBody = $response->getBody()->getContents();
|
|
Assert::assertEquals("207", $statusCode, "Expected status code to be '207' but got $statusCode \nResponse\n$respBody");
|
|
|
|
$responseXml = HttpRequestHelper::getResponseXml(
|
|
$response,
|
|
__METHOD__ . " $collectionPath"
|
|
);
|
|
|
|
$files = $this->getTrashbinContentFromResponseXml($responseXml);
|
|
|
|
// set endpoint according to webdav request (2 = new, 3 = spaces)
|
|
$endpoint = "/remote.php/dav/trash-bin/$user";
|
|
if ($davPathVersion === 3) {
|
|
$space_id = (WebDavHelper::$SPACE_ID_FROM_OCIS) ?: WebDavHelper::getPersonalSpaceIdForUser(
|
|
$this->featureContext->getBaseUrl(),
|
|
$user,
|
|
$this->featureContext->getPasswordForUser($user),
|
|
$this->featureContext->getStepLineRef()
|
|
);
|
|
$endpoint = "/remote.php/dav/spaces/trash-bin/$space_id";
|
|
}
|
|
|
|
// filter out the collection itself, we only want to return the members
|
|
$files = \array_filter(
|
|
$files,
|
|
static function ($element) use ($endpoint, $collectionPath) {
|
|
$path = $collectionPath;
|
|
if ($path !== "") {
|
|
$path = $path . "/";
|
|
}
|
|
return ($element['href'] !== "$endpoint/$path");
|
|
}
|
|
);
|
|
|
|
foreach ($files as $file) {
|
|
// check for unexpected/invalid href values and fail early in order to
|
|
// avoid "common" situations that could cause infinite recursion.
|
|
$trashbinRef = $file["href"];
|
|
$trimmedTrashbinRef = \trim($trashbinRef, "/");
|
|
if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) {
|
|
$expectedStart = "remote.php/dav/spaces/trash-bin";
|
|
} else {
|
|
$expectedStart = "remote.php/dav/trash-bin/$user";
|
|
}
|
|
$expectedStartLength = \strlen($expectedStart);
|
|
if ((\substr($trimmedTrashbinRef, 0, $expectedStartLength) !== $expectedStart)
|
|
|| (\strlen($trimmedTrashbinRef) === $expectedStartLength)
|
|
) {
|
|
// A top href (maybe without even the username) has been returned
|
|
// in the response. That should never happen, or have been filtered out
|
|
// by the code above.
|
|
throw new Exception(
|
|
__METHOD__ . " Error: unexpected href in trashbin propfind at level $level: '$trashbinRef'"
|
|
);
|
|
}
|
|
if ($file["collection"]) {
|
|
$trimmedHref = \trim($trashbinRef, "/");
|
|
$explodedHref = \explode("/", $trimmedHref);
|
|
$trashbinId = $collectionPath . "/" . end($explodedHref);
|
|
$nextFiles = $this->listTrashbinFolderCollection(
|
|
$user,
|
|
$trashbinId,
|
|
$depth,
|
|
$level + 1
|
|
);
|
|
// Filter the collection element. We only want the members.
|
|
$nextFiles = \array_filter(
|
|
$nextFiles,
|
|
static function ($element) use ($user, $trashbinRef) {
|
|
return ($element['href'] !== $trashbinRef);
|
|
}
|
|
);
|
|
\array_push($files, ...$nextFiles);
|
|
}
|
|
}
|
|
return $files;
|
|
}
|
|
|
|
/**
|
|
* @When user :user lists the resources in the trashbin with depth :depth using the WebDAV API
|
|
*
|
|
* @param string $user
|
|
* @param string $depth
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userGetsFilesInTheTrashbinWithDepthUsingTheWebdavApi(string $user, string $depth):void {
|
|
$this->listTopOfTrashbinFolder($user, $depth);
|
|
}
|
|
|
|
/**
|
|
* @Then the trashbin DAV response should not contain these nodes
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theTrashbinDavResponseShouldNotContainTheseNodes(TableNode $table):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ['name']);
|
|
$responseXml = $this->featureContext->getResponseXmlObject();
|
|
$files = $this->getTrashbinContentFromResponseXml($responseXml);
|
|
|
|
foreach ($table->getHash() as $row) {
|
|
$path = trim((string)$row['name'], "/");
|
|
foreach ($files as $file) {
|
|
if (trim((string)$file['original-location'], "/") === $path) {
|
|
throw new Exception("file $path was not expected in trashbin response but was found");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the trashbin DAV response should contain these nodes
|
|
*
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theTrashbinDavResponseShouldContainTheseNodes(TableNode $table):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ['name']);
|
|
$responseXml = $this->featureContext->getResponseXmlObject();
|
|
|
|
$files = $this->getTrashbinContentFromResponseXml($responseXml);
|
|
|
|
foreach ($table->getHash() as $row) {
|
|
$path = trim($row['name'], "/");
|
|
$found = false;
|
|
foreach ($files as $file) {
|
|
if (trim((string)$file['original-location'], "/") === $path) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!$found) {
|
|
throw new Exception("file $path was expected in trashbin response but was not found");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a webdav request to list the trashbin content
|
|
*
|
|
* @param string $user user
|
|
* @param string|null $asUser - To send request as another user
|
|
* @param string|null $password
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function sendTrashbinListRequest(string $user, ?string $asUser = null, ?string $password = null):void {
|
|
$asUser = $asUser ?? $user;
|
|
$password = $password ?? $this->featureContext->getPasswordForUser($asUser);
|
|
$davPathVersion = $this->featureContext->getDavPathVersion();
|
|
$response = WebDavHelper::propfind(
|
|
$this->featureContext->getBaseUrl(),
|
|
$asUser,
|
|
$password,
|
|
null,
|
|
[
|
|
'oc:trashbin-original-filename',
|
|
'oc:trashbin-original-location',
|
|
'oc:trashbin-delete-timestamp',
|
|
'd:getlastmodified'
|
|
],
|
|
$this->featureContext->getStepLineRef(),
|
|
'1',
|
|
'trash-bin',
|
|
$davPathVersion,
|
|
$user
|
|
);
|
|
$this->featureContext->setResponse($response);
|
|
try {
|
|
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
|
$response,
|
|
__METHOD__
|
|
);
|
|
$this->featureContext->setResponseXmlObject($responseXmlObject);
|
|
} catch (Exception $e) {
|
|
$this->featureContext->clearResponseXmlObject();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :asUser tries to list the trashbin content for user :user
|
|
*
|
|
* @param string $asUser
|
|
* @param string $user
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToListTheTrashbinContentForUser(string $asUser, string $user) {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$asUser = $this->featureContext->getActualUsername($asUser);
|
|
$this->sendTrashbinListRequest($user, $asUser);
|
|
}
|
|
|
|
/**
|
|
* @When user :asUser tries to list the trashbin content for user :user using password :password
|
|
*
|
|
* @param string $asUser
|
|
* @param string $user
|
|
* @param string $password
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToListTheTrashbinContentForUserUsingPassword(string $asUser, string $user, string $password):void {
|
|
$this->sendTrashbinListRequest($user, $asUser, $password);
|
|
}
|
|
|
|
/**
|
|
* @Then the last webdav response should contain the following elements
|
|
*
|
|
* @param TableNode $elements
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theLastWebdavResponseShouldContainFollowingElements(TableNode $elements):void {
|
|
$files = $this->getTrashbinContentFromResponseXml($this->featureContext->getResponseXmlObject());
|
|
$elementRows = $elements->getHash();
|
|
foreach ($elementRows as $expectedElement) {
|
|
$found = false;
|
|
$expectedPath = $expectedElement['path'];
|
|
foreach ($files as $file) {
|
|
if (\ltrim($expectedPath, "/") === \ltrim($file['original-location'], "/")) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
Assert::assertTrue($found, "$expectedPath expected to be listed in response but not found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then the last webdav response should not contain the following elements
|
|
*
|
|
* @param TableNode $elements
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function theLastWebdavResponseShouldNotContainFollowingElements(TableNode $elements):void {
|
|
$files = $this->getTrashbinContentFromResponseXml($this->featureContext->getResponseXmlObject());
|
|
|
|
// 'user' is also allowed in the table even though it is not used anywhere
|
|
// This for better readability in feature files
|
|
$this->featureContext->verifyTableNodeColumns($elements, ['path'], ['path', 'user']);
|
|
$elementRows = $elements->getHash();
|
|
foreach ($elementRows as $expectedElement) {
|
|
$notFound = true;
|
|
$expectedPath = "/" . \ltrim($expectedElement['path'], "/");
|
|
foreach ($files as $file) {
|
|
// 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
|
|
if ($expectedPath === $file['original-location']) {
|
|
$notFound = false;
|
|
}
|
|
}
|
|
Assert::assertTrue($notFound, "$expectedPath expected not to be listed in response but found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When user :user tries to delete the file with original path :path from the trashbin of user :ofUser using the trashbin API
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $ofUser
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToDeleteFromTrashbinOfUser(string $user, string $path, string $ofUser):void {
|
|
$response = $this->deleteItemFromTrashbin($user, $path, $ofUser);
|
|
$this->featureContext->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @When user :user tries to delete the file with original path :path from the trashbin of user :ofUser using the password :password and the trashbin API
|
|
*
|
|
* @param string $user
|
|
* @param string $path
|
|
* @param string $ofUser
|
|
* @param string $password
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToDeleteFromTrashbinOfUserUsingPassword(string $user, string $path, string $ofUser, string $password):void {
|
|
$response = $this->deleteItemFromTrashbin($user, $path, $ofUser, $password);
|
|
$this->featureContext->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @When user :asUser tries to restore the file with original path :path from the trashbin of user :user using the trashbin API
|
|
*
|
|
* @param string|null $asUser
|
|
* @param string|null $path
|
|
* @param string|null $user
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToRestoreFromTrashbinOfUser(?string $asUser, ?string $path, ?string $user):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$asUser = $this->featureContext->getActualUsername($asUser);
|
|
$response = $this->restoreElement($user, $path, null, $asUser);
|
|
$this->featureContext->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @When user :asUser tries to restore the file with original path :path from the trashbin of user :user using the password :password and the trashbin API
|
|
*
|
|
* @param string|null $asUser
|
|
* @param string|null $path
|
|
* @param string|null $user
|
|
* @param string|null $password
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function userTriesToRestoreFromTrashbinOfUserUsingPassword(?string $asUser, ?string $path, ?string $user, ?string $password):void {
|
|
$asUser = $this->featureContext->getActualUsername($asUser);
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$response = $this->restoreElement($user, $path, null, $asUser, $password);
|
|
$this->featureContext->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* converts the trashItemHRef from /<base>/remote.php/dav/trash-bin/<user>/<item_id>/ to /trash-bin/<user>/<item_id>
|
|
*
|
|
* @param string $href
|
|
*
|
|
* @return string
|
|
*/
|
|
private function convertTrashbinHref(string $href):string {
|
|
$trashItemHRef = \trim($href, '/');
|
|
$trashItemHRef = \strstr($trashItemHRef, '/trash-bin');
|
|
$trashItemHRef = \trim($trashItemHRef, '/');
|
|
$parts = \explode('/', $trashItemHRef);
|
|
$decodedParts = \array_slice($parts, 2);
|
|
return '/' . \join('/', $decodedParts);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" tries to delete the (?:file|folder|entry) with original path "([^"]*)" from the trashbin using the trashbin API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
*/
|
|
public function userTriesToDeleteFileWithOriginalPathFromTrashbinUsingTrashbinAPI(string $user, string $originalPath):void {
|
|
$response = $this->deleteItemFromTrashbin($user, $originalPath);
|
|
$this->featureContext->setResponse($response);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
* @param string|null $ofUser
|
|
* @param string|null $password
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
public function deleteItemFromTrashbin(string $user, string $originalPath, ?string $ofUser = null, ?string $password = null): ResponseInterface {
|
|
$ofUser = $ofUser ?? $user;
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
|
|
|
$listing = $this->listTrashbinFolder($ofUser);
|
|
|
|
$path = "";
|
|
$originalPath = \trim($originalPath, '/');
|
|
foreach ($listing as $entry) {
|
|
// The entry for the trashbin root can have original-location null.
|
|
// That is reasonable, because the trashbin root is not something that can be restored.
|
|
$originalLocation = $entry['original-location'] ?? '';
|
|
if (\trim($originalLocation, '/') === $originalPath) {
|
|
$path = $entry['href'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($path === "") {
|
|
throw new Exception(
|
|
__METHOD__
|
|
. " could not find the trashbin entry for original path '$originalPath' of user '$user'"
|
|
);
|
|
}
|
|
|
|
$password = $password ?? $this->featureContext->getPasswordForUser($user);
|
|
$fullUrl = $this->featureContext->getBaseUrl() . $path;
|
|
|
|
return HttpRequestHelper::sendRequest(
|
|
$fullUrl,
|
|
$this->featureContext->getStepLineRef(),
|
|
"DELETE",
|
|
$user,
|
|
$password
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" deletes the (?:file|folder|entry) with original path "([^"]*)" from the trashbin using the trashbin API$/
|
|
*
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function deleteFileFromTrashbin(string $user, string $originalPath):void {
|
|
$response = $this->deleteItemFromTrashbin($user, $originalPath);
|
|
$this->featureContext->setResponse($response);
|
|
$this->featureContext->pushToLastStatusCodesArrays();
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has deleted the (?:file|folder|entry) with original path "([^"]*)" from the trashbin$/
|
|
*
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userHasDeletedTheFolderWithOriginalPathFromTheTrashbin(string $user, string $originalPath):void {
|
|
$response = $this->deleteItemFromTrashbin($user, $originalPath);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(204, '', $response);
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" deletes the following (?:files|folders|entries) with original path from the trashbin$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function deleteFollowingFilesFromTrashbin(string $user, TableNode $table):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $path) {
|
|
$response = $this->deleteItemFromTrashbin($user, $path["path"]);
|
|
$this->featureContext->setResponse($response);
|
|
$this->featureContext->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" (?:file|folder|entry) "([^"]*)" should exist in the trashbin$/
|
|
*
|
|
* @param string|null $user
|
|
* @param string|null $path
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function asFileOrFolderExistsInTrash(?string $user, ?string $path):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$path = \trim($path, '/');
|
|
$sections = \explode('/', $path, 2);
|
|
|
|
$firstEntry = $this->findFirstTrashedEntry($user, \trim($sections[0], '/'));
|
|
|
|
Assert::assertNotNull(
|
|
$firstEntry,
|
|
"The first trash entry was not found while looking for trashbin entry '$path' of user '$user'"
|
|
);
|
|
|
|
if (\count($sections) !== 1) {
|
|
// TODO: handle deeper structures
|
|
$listing = $this->listTrashbinFolderCollection($user, \basename(\rtrim($firstEntry['href'], '/')));
|
|
} else {
|
|
$listing = [];
|
|
}
|
|
|
|
// query was on the main element?
|
|
if (\count($sections) === 1) {
|
|
// already found, return
|
|
return;
|
|
}
|
|
|
|
$checkedName = \basename($path);
|
|
|
|
$found = false;
|
|
foreach ($listing as $entry) {
|
|
if ($entry['name'] === $checkedName) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Assert::assertTrue(
|
|
$found,
|
|
__METHOD__
|
|
. " Could not find expected resource '$path' in the trash"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Function to check if an element is in the trashbin
|
|
*
|
|
* @param string|null $user
|
|
* @param string|null $originalPath
|
|
*
|
|
* @return bool
|
|
* @throws Exception
|
|
*/
|
|
private function isInTrash(?string $user, ?string $originalPath):bool {
|
|
$listing = $this->listTrashbinFolder($user);
|
|
|
|
// we don't care if the test step writes a leading "/" or not
|
|
$originalPath = \ltrim($originalPath, '/');
|
|
|
|
foreach ($listing as $entry) {
|
|
if ($entry['original-location'] !== null && \ltrim($entry['original-location'], '/') === $originalPath) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $trashItemHRef
|
|
* @param string $destinationPath
|
|
* @param string|null $asUser - To send request as another user
|
|
* @param string|null $password
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
private function sendUndeleteRequest(string $user, string $trashItemHRef, string $destinationPath, ?string $asUser = null, ?string $password = null):ResponseInterface {
|
|
$asUser = $asUser ?? $user;
|
|
$destinationPath = \trim($destinationPath, '/');
|
|
$destinationValue = $this->featureContext->getBaseUrl() . "/remote.php/dav/files/$user/$destinationPath";
|
|
|
|
$trashItemHRef = $this->convertTrashbinHref($trashItemHRef);
|
|
$headers['Destination'] = $destinationValue;
|
|
return $this->featureContext->makeDavRequest(
|
|
$asUser,
|
|
'MOVE',
|
|
$trashItemHRef,
|
|
$headers,
|
|
null,
|
|
'trash-bin',
|
|
'2',
|
|
false,
|
|
$password,
|
|
[],
|
|
$user
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
* @param string|null $destinationPath
|
|
* @param string|null $asUser - To send request as another user
|
|
* @param string|null $password
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws JsonException
|
|
* @throws GuzzleException
|
|
*/
|
|
private function restoreElement(string $user, string $originalPath, ?string $destinationPath = null, ?string $asUser = null, ?string $password = null):ResponseInterface {
|
|
$asUser = $asUser ?? $user;
|
|
$listing = $this->listTrashbinFolder($user);
|
|
$originalPath = \trim($originalPath, '/');
|
|
if ($destinationPath === null) {
|
|
$destinationPath = $originalPath;
|
|
}
|
|
foreach ($listing as $entry) {
|
|
if ($entry['original-location'] === $originalPath) {
|
|
return $this->sendUndeleteRequest(
|
|
$user,
|
|
$entry['href'],
|
|
$destinationPath,
|
|
$asUser,
|
|
$password
|
|
);
|
|
}
|
|
}
|
|
// The requested element to restore was not even in the trashbin.
|
|
// Throw an exception, because there was not any API call, and so there
|
|
// is also no up-to-date response to examine in later test steps.
|
|
throw new \Exception(
|
|
__METHOD__
|
|
. " cannot restore from trashbin because no element was found for user $user at original path $originalPath"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @When user :user restores the folder/file with original path :originalPath without specifying the destination using the trashbin API
|
|
*
|
|
* @param $user string
|
|
* @param $originalPath string
|
|
*
|
|
* @return ResponseInterface
|
|
* @throws Exception
|
|
*/
|
|
public function restoreFileWithoutDestination(string $user, string $originalPath):ResponseInterface {
|
|
$asUser = $asUser ?? $user;
|
|
$listing = $this->listTrashbinFolder($user);
|
|
$originalPath = \trim($originalPath, '/');
|
|
|
|
foreach ($listing as $entry) {
|
|
if ($entry['original-location'] === $originalPath) {
|
|
$trashItemHRef = $this->convertTrashbinHref($entry['href']);
|
|
$response = $this->featureContext->makeDavRequest(
|
|
$asUser,
|
|
'MOVE',
|
|
$trashItemHRef,
|
|
[],
|
|
null,
|
|
'trash-bin'
|
|
);
|
|
$this->featureContext->setResponse($response);
|
|
// this gives empty response in ocis
|
|
try {
|
|
$responseXml = HttpRequestHelper::getResponseXml(
|
|
$response,
|
|
__METHOD__
|
|
);
|
|
$this->featureContext->setResponseXmlObject($responseXml);
|
|
} catch (Exception $e) {
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
}
|
|
throw new \Exception(
|
|
__METHOD__
|
|
. " cannot restore from trashbin because no element was found for user $user at original path $originalPath"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^the content of file "([^"]*)" for user "([^"]*)" if the file is also in the trashbin should be "([^"]*)" otherwise "([^"]*)"$/
|
|
*
|
|
* Note: this is a special step for an unusual bug combination.
|
|
* Delete it when the bug is fixed and the step is no longer needed.
|
|
*
|
|
* @param string|null $fileName
|
|
* @param string|null $user
|
|
* @param string|null $content
|
|
* @param string|null $alternativeContent
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function contentOfFileForUserIfAlsoInTrashShouldBeOtherwise(
|
|
?string $fileName,
|
|
?string $user,
|
|
?string $content,
|
|
?string $alternativeContent
|
|
):void {
|
|
$isInTrash = $this->isInTrash($user, $fileName);
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$response = $this->featureContext->downloadFileAsUserUsingPassword($user, $fileName);
|
|
if ($isInTrash) {
|
|
$this->featureContext->checkDownloadedContentMatches($content, '', $response);
|
|
} else {
|
|
$this->featureContext->checkDownloadedContentMatches($alternativeContent, '', $response);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" restores the (?:file|folder|entry) with original path "([^"]*)" using the trashbin API$/
|
|
*
|
|
* @param string|null $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function elementInTrashIsRestored(?string $user, string $originalPath):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$this->featureContext->setResponse($this->restoreElement($user, $originalPath));
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" restores the following (?:files|folders|entries) with original path$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function userRestoresFollowingFiles(string $user, TableNode $table):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $originalPath) {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$this->featureContext->setResponse($this->restoreElement($user, $originalPath["path"]));
|
|
$this->featureContext->pushToLastStatusCodesArrays();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Given /^user "([^"]*)" has restored the (?:file|folder|entry) with original path "([^"]*)"$/
|
|
*
|
|
* @param string $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function elementInTrashHasBeenRestored(string $user, string $originalPath):void {
|
|
$response = $this->restoreElement($user, $originalPath);
|
|
$this->featureContext->theHTTPStatusCodeShouldBe(201, "", $response);
|
|
if ($this->isInTrash($user, $originalPath)) {
|
|
throw new Exception("File previously located at $originalPath is still in the trashbin");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @When /^user "([^"]*)" restores the (?:file|folder|entry) with original path "([^"]*)" to "([^"]*)" using the trashbin API$/
|
|
*
|
|
* @param string|null $user
|
|
* @param string|null $originalPath
|
|
* @param string|null $destinationPath
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function userRestoresTheFileWithOriginalPathToUsingTheTrashbinApi(
|
|
?string $user,
|
|
?string $originalPath,
|
|
?string $destinationPath
|
|
):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
$this->featureContext->setResponse($this->restoreElement($user, $originalPath, $destinationPath));
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the (?:file|folder|entry) with original path "([^"]*)" should exist in the trashbin$/
|
|
*
|
|
* @param string|null $user
|
|
* @param string|null $originalPath
|
|
*
|
|
* @return void
|
|
* @throws JsonException
|
|
* @throws Exception
|
|
*/
|
|
public function elementIsInTrashCheckingOriginalPath(
|
|
?string $user,
|
|
?string $originalPath
|
|
):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
Assert::assertTrue(
|
|
$this->isInTrash($user, $originalPath),
|
|
"File previously located at $originalPath wasn't found in the trashbin of user $user"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the (?:file|folder|entry) with original path "([^"]*)" should not exist in the trashbin/
|
|
*
|
|
* @param string|null $user
|
|
* @param string $originalPath
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function elementIsNotInTrashCheckingOriginalPath(
|
|
?string $user,
|
|
string $originalPath
|
|
):void {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
Assert::assertFalse(
|
|
$this->isInTrash($user, $originalPath),
|
|
"File previously located at $originalPath was found in the trashbin of user $user"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the (?:files|folders|entries) with following original paths should not exist in the trashbin$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function followingElementsAreNotInTrashCheckingOriginalPath(
|
|
string $user,
|
|
TableNode $table
|
|
):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $originalPath) {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
Assert::assertFalse(
|
|
$this->isInTrash($user, $originalPath["path"]),
|
|
"File previously located at " . $originalPath["path"] . " was found in the trashbin of user $user"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @Then /^as "([^"]*)" the (?:files|folders|entries) with following original paths should exist in the trashbin$/
|
|
*
|
|
* @param string $user
|
|
* @param TableNode $table
|
|
*
|
|
* @return void
|
|
* @throws Exception
|
|
*/
|
|
public function followingElementsAreInTrashCheckingOriginalPath(
|
|
string $user,
|
|
TableNode $table
|
|
):void {
|
|
$this->featureContext->verifyTableNodeColumns($table, ["path"]);
|
|
$paths = $table->getHash();
|
|
|
|
foreach ($paths as $originalPath) {
|
|
$user = $this->featureContext->getActualUsername($user);
|
|
Assert::assertTrue(
|
|
$this->isInTrash($user, $originalPath["path"]),
|
|
"File previously located at " . $originalPath["path"] . " wasn't found in the trashbin of user $user"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the first trashed entry matching the given name
|
|
*
|
|
* @param string $user
|
|
* @param string $name
|
|
*
|
|
* @return array|null real entry name with timestamp suffix or null if not found
|
|
* @throws Exception
|
|
*/
|
|
private function findFirstTrashedEntry(string $user, string $name):?array {
|
|
$listing = $this->listTrashbinFolder($user);
|
|
|
|
foreach ($listing as $entry) {
|
|
if ($entry['name'] === $name) {
|
|
return $entry;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* This will run before EVERY scenario.
|
|
* It will set the properties for this object.
|
|
*
|
|
* @BeforeScenario
|
|
*
|
|
* @param BeforeScenarioScope $scope
|
|
*
|
|
* @return void
|
|
*/
|
|
public function before(BeforeScenarioScope $scope):void {
|
|
// Get the environment
|
|
$environment = $scope->getEnvironment();
|
|
// Get all the contexts you need in this context
|
|
$this->featureContext = $environment->getContext('FeatureContext');
|
|
}
|
|
|
|
/**
|
|
* @Then /^the deleted (?:file|folder) "([^"]*)" should have the correct deletion mtime in the response$/
|
|
*
|
|
* @param string $resource file or folder in trashbin
|
|
*
|
|
* @return void
|
|
*/
|
|
public function theDeletedFileFolderShouldHaveCorrectDeletionMtimeInTheResponse(string $resource):void {
|
|
$files = $this->getTrashbinContentFromResponseXml(
|
|
$this->featureContext->getResponseXmlObject()
|
|
);
|
|
|
|
$found = false;
|
|
$expectedMtime = $this->featureContext->getLastUploadDeleteTime();
|
|
$responseMtime = '';
|
|
|
|
foreach ($files as $file) {
|
|
if (\ltrim($resource, "/") === \ltrim((string)$file['original-location'], "/")) {
|
|
$responseMtime = $file['mtime'];
|
|
$mtime_difference = \abs((int)\trim((string)$expectedMtime) - (int)\trim($responseMtime));
|
|
|
|
if ($mtime_difference <= 2) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Assert::assertTrue(
|
|
$found,
|
|
"$resource expected to be listed in response with mtime '$expectedMtime' but found '$responseMtime'"
|
|
);
|
|
}
|
|
}
|