diff --git a/tests/acceptance/bootstrap/FeatureContext.php b/tests/acceptance/bootstrap/FeatureContext.php index 64019221457..0f05fac5c7a 100644 --- a/tests/acceptance/bootstrap/FeatureContext.php +++ b/tests/acceptance/bootstrap/FeatureContext.php @@ -2395,7 +2395,7 @@ public function substituteInLineCodes( "code" => "%tus_upload_location%", "function" => [ $this->tusContext, - "getTusResourceLocation" + "getLastTusResourceLocation" ], "parameter" => [] ], diff --git a/tests/acceptance/bootstrap/SpacesTUSContext.php b/tests/acceptance/bootstrap/SpacesTUSContext.php index 04f198a1bd8..f42503a3980 100644 --- a/tests/acceptance/bootstrap/SpacesTUSContext.php +++ b/tests/acceptance/bootstrap/SpacesTUSContext.php @@ -275,8 +275,8 @@ public function userHasUploadedFileWithChecksumToTheLastCreatedTusLocationWithOf string $content, string $spaceName ): void { - $this->spacesContext->setSpaceIDByName($user, $spaceName); - $response = $this->tusContext->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->tusContext->getLastTusResourceLocation(); + $response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response); } @@ -299,8 +299,8 @@ public function userUploadsFileWithChecksumToTheLastCreatedTusLocationWithOffset string $content, string $spaceName ): void { - $spaceId = $this->spacesContext->setSpaceIDByName($user, $spaceName); - $response = $this->tusContext->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->tusContext->getLastTusResourceLocation(); + $response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->setResponse($response); } @@ -323,8 +323,8 @@ public function userSendsAChunkToTheLastCreatedTusLocationWithOffsetAndDataWithC string $checksum, string $spaceName ): void { - $this->spacesContext->setSpaceIDByName($user, $spaceName); - $response = $this->tusContext->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $resourceLocation = $this->tusContext->getLastTusResourceLocation(); + $response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->setResponse($response); } @@ -345,9 +345,9 @@ public function userSendsAChunkToTheLastCreatedTusLocationWithDataInsideOfTheSpa string $spaceName, TableNode $headers ): void { - $this->spacesContext->setSpaceIDByName($user, $spaceName); $rows = $headers->getRowsHash(); - $response = $this->tusContext->sendsAChunkToTUSLocationWithOffsetAndData($user, $rows['Upload-Offset'], $data, $rows['Upload-Checksum'], ['Origin' => $rows['Origin']]); + $resourceLocation = $this->tusContext->getLastTusResourceLocation(); + $response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $rows['Upload-Offset'], $data, $rows['Upload-Checksum'], ['Origin' => $rows['Origin']]); $this->featureContext->setResponse($response); } @@ -375,7 +375,8 @@ public function userOverwritesRecentlySharedFileWithOffsetAndDataWithChecksumVia $spaceId = $this->spacesContext->setSpaceIDByName($user, $spaceName); $createResponse = $this->tusContext->createNewTUSResource($user, $headers, $spaceId); $this->featureContext->theHTTPStatusCodeShouldBe(201, "", $createResponse); - $response = $this->tusContext->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $resourceLocation = $this->tusContext->getLastTusResourceLocation(); + $response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->setResponse($response); } diff --git a/tests/acceptance/bootstrap/TUSContext.php b/tests/acceptance/bootstrap/TUSContext.php index 483904e20bb..92bd7b11ad6 100644 --- a/tests/acceptance/bootstrap/TUSContext.php +++ b/tests/acceptance/bootstrap/TUSContext.php @@ -40,18 +40,54 @@ class TUSContext implements Context { private FeatureContext $featureContext; - private ?string $resourceLocation = null; + private array $tusResourceLocations = []; /** + * @param string $filenameHash + * @param string $location + * + * @return void + */ + public function saveTusResourceLocation(string $filenameHash, string $location): void { + $this->tusResourceLocations[$filenameHash][] = $location; + } + + /** + * @param string $filenameHash + * @param int|null $index + * + * @return string + */ + public function getTusResourceLocation(string $filenameHash, ?int $index = null): string { + if ($index === null) { + // get the last one + $index = \count($this->tusResourceLocations[$filenameHash]) - 1; + } + return $this->tusResourceLocations[$filenameHash][$index]; + } + + /** + * @return string + */ + public function getLastTusResourceLocation(): string { + $lastKey = \array_key_last($this->tusResourceLocations); + $index = \count($this->tusResourceLocations[$lastKey]) - 1; + return $this->tusResourceLocations[$lastKey][$index]; + } + + /** + * @param string $uploadMetadata + * * @return string */ - public function getTusResourceLocation(): string { - return $this->resourceLocation ?: ""; + public function parseFilenameHash(string $uploadMetadata): string { + $filenameHash = \explode("filename ", $uploadMetadata)[1] ?? ''; + return \explode(" ", $filenameHash, 2)[0]; } /** * @param string $user - * @param TableNode $headers + * @param TableNode $headersTable * @param string $content * @param string|null $spaceId * @@ -60,17 +96,17 @@ public function getTusResourceLocation(): string { * @throws Exception * @throws GuzzleException */ - public function createNewTUSResourceWithHeaders(string $user, TableNode $headers, string $content = '', ?string $spaceId = null): ResponseInterface { - $this->featureContext->verifyTableNodeColumnsCount($headers, 2); + public function createNewTUSResourceWithHeaders(string $user, TableNode $headersTable, string $content = '', ?string $spaceId = null): ResponseInterface { + $this->featureContext->verifyTableNodeColumnsCount($headersTable, 2); $user = $this->featureContext->getActualUsername($user); $password = $this->featureContext->getUserPassword($user); - $this->resourceLocation = null; + $headers = $headersTable->getRowsHash(); $response = $this->featureContext->makeDavRequest( $user, "POST", null, - $headers->getRowsHash(), + $headers, $content, $spaceId, "files", @@ -80,7 +116,8 @@ public function createNewTUSResourceWithHeaders(string $user, TableNode $headers ); $locationHeader = $response->getHeader('Location'); if (\sizeof($locationHeader) > 0) { - $this->resourceLocation = $locationHeader[0]; + $filenameHash = $this->parseFilenameHash($headers['Upload-Metadata']); + $this->saveTusResourceLocation($filenameHash, $locationHeader[0]); } return $response; } @@ -133,6 +170,7 @@ public function createNewTUSResource(string $user, TableNode $headers, ?string $ /** * @param string $user + * @param string $resourceLocation * @param string $offset * @param string $data * @param string $checksum @@ -143,7 +181,7 @@ public function createNewTUSResource(string $user, TableNode $headers, ?string $ * @throws GuzzleException * @throws JsonException */ - public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data, string $checksum = '', ?array $extraHeaders = null): ResponseInterface { + public function uploadChunkToTUSLocation(string $user, string $resourceLocation, string $offset, string $data, string $checksum = '', ?array $extraHeaders = null): ResponseInterface { $user = $this->featureContext->getActualUsername($user); $password = $this->featureContext->getUserPassword($user); $headers = [ @@ -155,7 +193,7 @@ public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $ $headers = empty($extraHeaders) ? $headers : array_merge($headers, $extraHeaders); return HttpRequestHelper::sendRequest( - $this->resourceLocation, + $resourceLocation, $this->featureContext->getStepLineRef(), 'PATCH', $user, @@ -178,7 +216,8 @@ public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $ * @throws JsonException */ public function userSendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data); $this->featureContext->setResponse($response); } @@ -424,6 +463,8 @@ public function setUpScenario(BeforeScenarioScope $scope): void { $environment = $scope->getEnvironment(); // Get all the contexts you need in this context $this->featureContext = $environment->getContext('FeatureContext'); + // clear TUS locations cache + $this->tusResourceLocations = []; } /** @@ -492,7 +533,36 @@ public function userUploadsFileWithChecksum( string $offset, string $content ): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum); + $this->featureContext->setResponse($response); + } + + /** + * @When user :user uploads content :content with checksum :checksum and offset :offset to the index :locationIndex location of file :filename using the TUS protocol + * @When user :user tries to upload content :content with checksum :checksum and offset :offset to the index :locationIndex location of file :filename using the TUS protocol + * + * @param string $user + * @param string $content + * @param string $checksum + * @param string $offset + * @param string $locationIndex + * @param string $filename + * + * @return void + * @throws Exception + */ + public function userUploadsContentWithChecksumAndOffsetToIndexLocationUsingTUSProtocol( + string $user, + string $content, + string $checksum, + string $offset, + string $locationIndex, + string $filename + ): void { + $filenameHash = \base64_encode($filename); + $resourceLocation = $this->getTusResourceLocation($filenameHash, (int)$locationIndex); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->setResponse($response); } @@ -513,7 +583,8 @@ public function userHasUploadedFileWithChecksum( string $offset, string $content ): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response); } @@ -529,7 +600,8 @@ public function userHasUploadedFileWithChecksum( * @throws Exception */ public function userUploadsChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->setResponse($response); } @@ -545,7 +617,8 @@ public function userUploadsChunkFileWithChecksum(string $user, string $offset, s * @throws Exception */ public function userHasUploadedChunkFileWithChecksum(string $user, string $offset, string $data, string $checksum): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response); } @@ -567,7 +640,8 @@ public function userHasUploadedChunkFileWithChecksum(string $user, string $offse public function userOverwritesFileWithChecksum(string $user, string $offset, string $data, string $checksum, TableNode $headers): void { $createResponse = $this->createNewTUSResource($user, $headers); $this->featureContext->theHTTPStatusCodeShouldBe(201, "", $createResponse); - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $data, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->setResponse($response); } } diff --git a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature index ee868e4ac91..785e45a0269 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature @@ -211,3 +211,26 @@ Feature: upload file | old | | new | | spaces | + + @issue-8804 + Scenario Outline: multiple upload locations of the same file + Given using DAV path + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # bG9yZW0udHh0 is the base64 encode of lorem.txt + | Upload-Metadata | filename bG9yZW0udHh0 | + And user "Alice" has created a new TUS resource on the WebDAV API with these headers: + | Upload-Length | 5 | + # bG9yZW0udHh0 is the base64 encode of lorem.txt + | Upload-Metadata | filename bG9yZW0udHh0 | + When user "Alice" uploads content "lorem" with checksum "MD5 d2e16e6ef52a45b7468f1da56bba1953" and offset "0" to the index "1" location of file "lorem.txt" using the TUS protocol + Then the HTTP status code should be "204" + And the content of file "lorem.txt" for user "Alice" should be "lorem" + When user "Alice" tries to upload content "epsum" with checksum "MD5 d6145e3d2ced88009796acae1dc7929f" and offset "0" to the index "0" location of file "lorem.txt" using the TUS protocol + Then the HTTP status code should be "409" + And the content of file "lorem.txt" for user "Alice" should be "lorem" + Examples: + | dav-path-version | + | old | + | new | + | spaces |