* * @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 * */ 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; use TestHelpers\BehatHelper; 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, [], 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)); } /** * Get files list from the response from trashbin api * * @param SimpleXMLElement|null $responseXmlObject * * @return array */ public function getTrashbinContentFromResponseXml(?SimpleXMLElement $responseXmlObject): array { $xmlElements = $responseXmlObject->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() === "") ) { $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(); $suffixPath = $user; $spaceId = null; if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) { $spaceId = WebDavHelper::getPersonalSpaceIdForUser( $this->featureContext->getBaseUrl(), $user, $password, $this->featureContext->getStepLineRef() ); $suffixPath = $spaceId; } $response = WebDavHelper::listFolder( $this->featureContext->getBaseUrl(), $user, $password, "", $depth, $spaceId, $this->featureContext->getStepLineRef(), [ 'oc:trashbin-original-filename', 'oc:trashbin-original-location', 'oc:trashbin-delete-timestamp', 'd:getlastmodified' ], 'trash-bin', $davPathVersion ); $this->featureContext->setResponse($response); $files = $this->getTrashbinContentFromResponseXml( HttpRequestHelper::getResponseXml( $response, __METHOD__ ) ); // filter root element $files = \array_filter( $files, static function ($element) use ($davPathVersion, $suffixPath) { $davPath = WebDavHelper::getDavPath($davPathVersion, $suffixPath, "trash-bin"); return ($element['href'] !== "/" . $davPath . "/"); } ); 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, null, $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" ); $files = $this->getTrashbinContentFromResponseXml( HttpRequestHelper::getResponseXml( $response, __METHOD__ . " $collectionPath" ) ); $suffixPath = $user; if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) { $suffixPath = WebDavHelper::getPersonalSpaceIdForUser( $this->featureContext->getBaseUrl(), $user, $this->featureContext->getPasswordForUser($user), $this->featureContext->getStepLineRef() ); } $endpoint = WebDavHelper::getDavPath($davPathVersion, $suffixPath, "trash-bin"); // 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, "/"); $expectedStartLength = \strlen($endpoint); if ((\substr($trimmedTrashbinRef, 0, $expectedStartLength) !== $endpoint) || (\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']); $files = $this->getTrashbinContentFromResponseXml( HttpRequestHelper::getResponseXml($this->featureContext->getResponse(), __METHOD__) ); 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']); $files = $this->getTrashbinContentFromResponseXml( HttpRequestHelper::getResponseXml($this->featureContext->getResponse(), __METHOD__) ); 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 ResponseInterface * @throws Exception */ public function sendTrashbinListRequest( string $user, ?string $asUser = null, ?string $password = null ): ResponseInterface { $asUser = $asUser ?? $user; $password = $password ?? $this->featureContext->getPasswordForUser($asUser); return WebDavHelper::propfind( $this->featureContext->getBaseUrl(), $user, $password, null, [ 'oc:trashbin-original-filename', 'oc:trashbin-original-location', 'oc:trashbin-delete-timestamp', 'd:getlastmodified' ], $this->featureContext->getStepLineRef(), '1', null, 'trash-bin', $this->featureContext->getDavPathVersion(), $asUser ); } /** * @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); $response = $this->sendTrashbinListRequest($user, $asUser); $this->featureContext->setResponse($response); } /** * @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 { $response = $this->sendTrashbinListRequest($user, $asUser, $password); $this->featureContext->setResponse($response); } /** * @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( HttpRequestHelper::getResponseXml($this->featureContext->getResponse()) ); // '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 //dav/trash-bin/// to /trash-bin// * * @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) { $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; $password = $password ?? $this->featureContext->getPasswordForUser($asUser); $destinationPath = \trim($destinationPath, '/'); $baseUrl = $this->featureContext->getBaseUrl(); $davPathVersion = $this->featureContext->getDavPathVersion(); $suffixPath = $asUser; if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) { if (\str_starts_with($destinationPath, "Shares/")) { $suffixPath = $this->featureContext->spacesContext->getSpaceIdByName($user, "Shares"); $destinationPath = \str_replace("Shares/", "", $destinationPath); } else { $suffixPath = WebDavHelper::getPersonalSpaceIdForUser( $baseUrl, $asUser, $password, $this->featureContext->getStepLineRef() ); } } $destinationDavPath = WebDavHelper::getDavPath($davPathVersion, $suffixPath); $destination = "$baseUrl/$destinationDavPath/$destinationPath"; $trashItemHRef = \ltrim($this->convertTrashbinHref($trashItemHRef), "/"); $headers['Destination'] = $destination; return $this->featureContext->makeDavRequest( $user, 'MOVE', $trashItemHRef, $headers, null, null, 'trash-bin', $davPathVersion, false, $password, [], $asUser ); } /** * @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 void * @throws Exception */ public function userRestoresResourceWithOriginalPathWithoutSpecifyingDestinationUsingTrashbinApi( string $user, string $originalPath ): void { $listing = $this->listTrashbinFolder($user); $originalPath = \trim($originalPath, '/'); foreach ($listing as $entry) { if ($entry['original-location'] === $originalPath) { $trashItemHRef = $this->convertTrashbinHref($entry['href']); $this->featureContext->setResponse( $this->featureContext->makeDavRequest( $user, 'MOVE', $trashItemHRef, [], null, null, 'trash-bin' ) ); } } } /** * @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 = BehatHelper::getContext($scope, $environment, '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( HttpRequestHelper::getResponseXml($this->featureContext->getResponse()) ); $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'" ); } }