From 3b83221a874e96b65afd49d945edef55839a2597 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Tue, 8 Sep 2026 12:18:31 +0200 Subject: [PATCH] test(search): retry the driveItem GET until the extracted properties match Property extraction reaches the driveItem asynchronously, so the GET step previously waited only until any extraction facet key appeared and a separate step then validated the full schema, which could run on a partial payload and fail intermittently. The GET and the status check stay plain steps; a new explicit Then step re-fetches the item until the response satisfies the expected schema (a partial never matches) or the WaitHelper timeout elapses. --- tests/acceptance/bootstrap/SpacesContext.php | 75 ++++++++++++++++--- .../apiSearchContent/extractedProps.feature | 12 +-- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/tests/acceptance/bootstrap/SpacesContext.php b/tests/acceptance/bootstrap/SpacesContext.php index 2075dd37e0..e830413385 100644 --- a/tests/acceptance/bootstrap/SpacesContext.php +++ b/tests/acceptance/bootstrap/SpacesContext.php @@ -55,6 +55,9 @@ class SpacesContext implements Context { * key is space name and value is the username that created the space */ private array $createdSpaces = []; + // the request of the last driveItem GET, so a later "should eventually + // match" step can re-run it (the counterpart of the stored response) + private array $lastDriveItemRequest = []; private string $ocsApiUrl = '/ocs/v2.php/apps/files_sharing/api/v1/shares'; /** @@ -4533,17 +4536,16 @@ class SpacesContext implements Context { } /** + * resolve the Graph URL of a file in a space * * @param string $user * @param string $file * @param string $space * - * @return void + * @return string */ - #[When('user :user gets the file :file from space :space using the Graph API')] - public function userGetsTheDriveItemInSpace(string $user, string $file, string $space): void { + private function getDriveItemUrl(string $user, string $file, string $space): string { $spaceId = ($this->getSpaceByName($user, $space))["id"]; - $itemId = ''; if ($space === "Shares") { $itemId = GraphHelper::getShareMountId( $this->featureContext->getBaseUrl(), @@ -4555,10 +4557,51 @@ class SpacesContext implements Context { } else { $itemId = $this->getFileId($user, $space, $file); } - $url = $this->featureContext->getBaseUrl() . "/graph/v1.0/drives/$spaceId/items/$itemId"; + return $this->featureContext->getBaseUrl() . "/graph/v1.0/drives/$spaceId/items/$itemId"; + } - // NOTE: extracting properties occurs asynchronously after upload, so we need to wait until the properties are available - $extractionFacets = ["image", "photo", "location", "audio", "video"]; + /** + * + * @param string $user + * @param string $file + * @param string $space + * + * @return void + */ + #[When('user :user gets the file :file from space :space using the Graph API')] + public function userGetsTheDriveItemInSpace(string $user, string $file, string $space): void { + $this->lastDriveItemRequest = [ + "url" => $this->getDriveItemUrl($user, $file, $space), + "user" => $user, + ]; + $response = HttpRequestHelper::get( + $this->lastDriveItemRequest["url"], + $this->featureContext->getStepLineRef(), + $user, + $this->featureContext->getPasswordForUser($user), + ); + $this->featureContext->setResponse($response); + } + + /** + * + * @param PyStringNode $schemaString + * + * @return void + */ + #[Then('the JSON data of the response should eventually match')] + public function theJsonDataOfTheResponseShouldEventuallyMatch(PyStringNode $schemaString): void { + Assert::assertNotEmpty( + $this->lastDriveItemRequest, + 'no driveItem request to repeat, get the file using the Graph API first' + ); + $url = $this->lastDriveItemRequest["url"]; + $user = $this->lastDriveItemRequest["user"]; + $schema = $this->featureContext->getJSONSchema($schemaString); + + // Extraction is asynchronous, so re-fetch until the response satisfies the + // expected schema (a partial payload never matches) or the WaitHelper + // timeout elapses. $response = WaitHelper::waitUntil( fn () => HttpRequestHelper::get( $url, @@ -4566,16 +4609,24 @@ class SpacesContext implements Context { $user, $this->featureContext->getPasswordForUser($user), ), - function ($response) use ($extractionFacets) { + function ($response) use ($schema) { if ($response->getStatusCode() !== 200) { - return true; + return false; + } + try { + $body = $this->featureContext->getJsonDecodedResponseBodyContent($response); + $this->featureContext->assertJsonDocumentMatchesSchema($body, $schema); + return true; + } catch (\Throwable $e) { + return false; } - $body = $this->featureContext->getJsonDecodedResponseBodyContent($response); - return \is_object($body) - && !empty(\array_intersect($extractionFacets, \array_keys((array) $body))); } ); $this->featureContext->setResponse($response); + $this->featureContext->assertJsonDocumentMatchesSchema( + $this->featureContext->getJsonDecodedResponseBodyContent($response), + $schema + ); } } diff --git a/tests/acceptance/features/apiSearchContent/extractedProps.feature b/tests/acceptance/features/apiSearchContent/extractedProps.feature index 322854a6b7..393244330c 100644 --- a/tests/acceptance/features/apiSearchContent/extractedProps.feature +++ b/tests/acceptance/features/apiSearchContent/extractedProps.feature @@ -131,7 +131,7 @@ Feature: propfind extracted props Given user "Alice" has uploaded a file "filesForUpload/testaudio.mp3" to "testaudio.mp3" in space "Personal" When user "Alice" gets the file "testaudio.mp3" from space "Personal" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object", @@ -179,7 +179,7 @@ Feature: propfind extracted props Given user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "testavatar.jpg" in space "Personal" When user "Alice" gets the file "testavatar.jpg" from space "Personal" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object", @@ -251,7 +251,7 @@ Feature: propfind extracted props And user "Alice" has uploaded a file "filesForUpload/testaudio.mp3" to "testaudio.mp3" in space "new-space" When user "Alice" gets the file "testaudio.mp3" from space "new-space" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object", @@ -301,7 +301,7 @@ Feature: propfind extracted props And user "Alice" has uploaded a file "filesForUpload/testavatar.jpg" to "testavatar.jpg" in space "new-space" When user "Alice" gets the file "testavatar.jpg" from space "new-space" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object", @@ -379,7 +379,7 @@ Feature: propfind extracted props And user "Brian" has a share "testaudio.mp3" synced When user "Brian" gets the file "testaudio.mp3" from space "Shares" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object", @@ -435,7 +435,7 @@ Feature: propfind extracted props And user "Brian" has a share "testavatar.jpg" synced When user "Brian" gets the file "testavatar.jpg" from space "Shares" using the Graph API Then the HTTP status code should be "200" - And the JSON data of the response should match + And the JSON data of the response should eventually match """ { "type": "object",