diff --git a/tests/acceptance/bootstrap/TUSContext.php b/tests/acceptance/bootstrap/TUSContext.php index 7d1f91aa59e..e231f1b23a0 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 * * @return ResponseInterface @@ -59,17 +95,17 @@ public function getTusResourceLocation(): string { * @throws Exception * @throws GuzzleException */ - public function createNewTUSResourceWithHeaders(string $user, TableNode $headers, string $content = ''): ResponseInterface { - $this->featureContext->verifyTableNodeColumnsCount($headers, 2); + public function createNewTUSResourceWithHeaders(string $user, TableNode $headersTable, string $content = ''): 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, "files", null, @@ -78,7 +114,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; } @@ -130,6 +167,7 @@ public function createNewTUSResource(string $user, TableNode $headers):ResponseI /** * @param string $user + * @param string $resourceLocation * @param string $offset * @param string $data * @param string $checksum @@ -140,7 +178,7 @@ public function createNewTUSResource(string $user, TableNode $headers):ResponseI * @throws GuzzleException * @throws JsonException */ - public function sendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data, string $checksum = '', ?array $extraHeaders = null): ResponseInterface { + public function sendsAChunkToTUSLocationWithOffsetAndData(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 = [ @@ -152,7 +190,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, @@ -175,7 +213,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->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $data); $this->featureContext->setResponse($response); } @@ -419,6 +458,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 = []; } /** @@ -486,7 +527,35 @@ public function userUploadsFileWithChecksum( string $offset, string $content ): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $content, $checksum); + $this->featureContext->setResponse($response); + } + + /** + * @When user :user uploads content :contnet 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->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->setResponse($response); } @@ -507,7 +576,8 @@ public function userHasUploadedFileWithChecksum( string $offset, string $content ): void { - $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $offset, $content, $checksum); + $resourceLocation = $this->getLastTusResourceLocation(); + $response = $this->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $content, $checksum); $this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response); } @@ -523,7 +593,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->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->setResponse($response); } @@ -539,7 +610,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->sendsAChunkToTUSLocationWithOffsetAndData($user, $resourceLocation, $offset, $data, $checksum); $this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response); } @@ -561,7 +633,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->sendsAChunkToTUSLocationWithOffsetAndData($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..b077eb6bdd5 100644 --- a/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature +++ b/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature @@ -211,3 +211,24 @@ 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 | + # dGV4dEZpbGUudHh0 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 | + # dGV4dEZpbGUudHh0 is the base64 encode of lorem.txt + | Upload-Metadata | filename bG9yZW0udHh0 | + When user "Alice" uploads content "lorem" with checksum "" and offset "0" to the index "1" location of file "lorem.txt" using the TUS protocol + Then the HTTP status code should be "204" + When user "Alice" uploads content "epsum" with checksum "" and offset "0" to the index "0" location of file "lorem.txt" using the TUS protocol + Then the HTTP status code should be "409" + Examples: + | dav-path-version | + | old | + | new | + | spaces |