Merge pull request #8891 from owncloud/tests/flaky-sharing-tests

[tests-only] retry if auto-sync is enabled but client.synchronize is false
This commit is contained in:
Sawjan Gurung
2024-07-08 10:09:25 +05:45
committed by GitHub
9 changed files with 415 additions and 106 deletions
+11
View File
@@ -685,4 +685,15 @@ class HttpRequestHelper {
$timeout = \getenv("REQUEST_TIMEOUT");
return (int)$timeout ?: 60;
}
/**
* returns json decoded body content of a json response as an object
*
* @param ResponseInterface $response
*
* @return mixed
*/
public static function getJsonDecodedResponseBodyContent(ResponseInterface $response): mixed {
return json_decode($response->getBody()->getContents(), null, 512, JSON_THROW_ON_ERROR);
}
}
+281
View File
@@ -0,0 +1,281 @@
<?php declare(strict_types=1);
/**
* ownCloud
*
* @author Sajan Gurung <sajan@jankaritech.com>
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License,
* as published by the Free Software Foundation;
* either version 3 of the License, or any later version.
*
* 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
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace TestHelpers;
use TestHelpers\HttpRequestHelper;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
use PHPUnit\Framework\Assert;
/**
* A helper class for ocis settings
*/
class SettingsHelper {
private static string $settingsEndpoint = '/api/v0/settings/';
/**
* @param string $baseUrl
* @param string $path
*
* @return string
*/
public static function buildFullUrl(string $baseUrl, string $path): string {
return $baseUrl . self::$settingsEndpoint . $path;
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function getBundlesList(string $baseUrl, string $user, string $password, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "bundles-list");
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
"{}"
);
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $bundleName
* @param string $xRequestId
*
* @return array
*
* @throws GuzzleException
* @throws Exception
*/
public static function getBundleByName(string $baseUrl, string $user, string $password, string $bundleName, string $xRequestId): array {
$response = self::getBundlesList($baseUrl, $user, $password, $xRequestId);
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get bundles list");
$bundlesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
foreach ($bundlesList->bundles as $value) {
if ($value->displayName === $bundleName) {
return json_decode(json_encode($value), true);
}
}
return [];
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function getRolesList(string $baseUrl, string $user, string $password, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "roles-list");
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
"{}"
);
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $assigneeId
* @param string $roleId
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function assignRoleToUser(string $baseUrl, string $user, string $password, string $assigneeId, string $roleId, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "assignments-add");
$body = json_encode(["account_uuid" => $assigneeId, "role_id" => $roleId], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
$body
);
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $userId
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function getAssignmentsList(string $baseUrl, string $user, string $password, string $userId, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "assignments-list");
$body = json_encode(["account_uuid" => $userId], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
$body
);
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function getValuesList(string $baseUrl, string $user, string $password, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "values-list");
$body = json_encode(["account_uuid" => "me"], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
$body
);
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
*
* @return bool
*
* @throws GuzzleException
* @throws Exception
*/
public static function getAutoAcceptSharesSettingValue(string $baseUrl, string $user, string $password, string $xRequestId): bool {
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
if (empty($valuesList)) {
return true;
}
foreach ($valuesList->values as $value) {
if ($value->identifier->setting === "auto-accept-shares") {
return $value->value->boolValue;
}
}
return true;
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $xRequestId
*
* @return string
*
* @throws GuzzleException
* @throws Exception
*/
public static function getLanguageSettingValue(string $baseUrl, string $user, string $password, string $xRequestId): string {
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
// if no language is set, the request body is empty return English as the default language
if (empty($valuesList)) {
return "en";
}
foreach ($valuesList->values as $value) {
if ($value->identifier->setting === "language") {
return $value->value->listValue->values[0]->stringValue;
}
}
// if a language setting was still not found, return English
return "en";
}
/**
* @param string $baseUrl
* @param string $user
* @param string $password
* @param string $body
* @param string $xRequestId
* @param array $headers
*
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public static function updateSettings(string $baseUrl, string $user, string $password, string $body, string $xRequestId, array $headers = []): ResponseInterface {
$fullUrl = self::buildFullUrl($baseUrl, "values-save");
return HttpRequestHelper::post(
$fullUrl,
$xRequestId,
$user,
$password,
$headers,
$body
);
}
}
@@ -20,7 +20,7 @@ Feature: an user gets the resources shared to them
| sharee | Brian |
| shareType | user |
| permissionsRole | <permissions-role> |
When user "Brian" lists the shares shared with him after clearing user cache using the Graph API
When user "Brian" lists the shares shared with him using the Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
@@ -42,6 +42,7 @@ use TestHelpers\HttpLogger;
use TestHelpers\OcisHelper;
use TestHelpers\GraphHelper;
use TestHelpers\WebDavHelper;
use TestHelpers\SettingsHelper;
require_once 'bootstrap.php';
@@ -167,6 +168,41 @@ class FeatureContext extends BehatVariablesContext {
private array $lastHttpStatusCodesArray = [];
private array $lastOCSStatusCodesArray = [];
/**
* Store for auto-sync settings for users
*/
private array $autoSyncSettings = [];
/**
* @param string $user
*
* @return bool
*/
public function getUserAutoSyncSetting(string $user): bool {
if (\array_key_exists($user, $this->autoSyncSettings)) {
return $this->autoSyncSettings[$user];
}
$autoSyncSetting = SettingsHelper::getAutoAcceptSharesSettingValue(
$this->baseUrl,
$user,
$this->getPasswordForUser($user),
$this->getStepLineRef()
);
$this->autoSyncSettings[$user] = $autoSyncSetting;
return $autoSyncSetting;
}
/**
* @param string $user
* @param bool $value
*
* @return void
*/
public function rememberUserAutoSyncSetting(string $user, bool $value): void {
$this->autoSyncSettings[$user] = $value;
}
public const SHARES_SPACE_ID = 'a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668';
private bool $useSharingNG = false;
@@ -1432,15 +1468,12 @@ class FeatureContext extends BehatVariablesContext {
*
* @param ResponseInterface|null $response
*
* @return object
* @return mixed
*/
public function getJsonDecodedResponseBodyContent(ResponseInterface $response = null):?object {
public function getJsonDecodedResponseBodyContent(ResponseInterface $response = null): mixed {
$response = $response ?? $this->response;
if ($response !== null) {
$response->getBody()->rewind();
return json_decode($response->getBody()->getContents());
}
return null;
$response->getBody()->rewind();
return HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
}
/**
@@ -2503,14 +2503,38 @@ class GraphContext implements Context {
}
$credentials = $this->getAdminOrUserCredentials($user);
$this->featureContext->setResponse(
GraphHelper::getSharesSharedWithMe(
// Sometimes listing shares might not return the updated shares list
// so try again until @client.synchronize is true for the max. number of retries (i.e. 10)
// and do not retry when the share is expected to be not synced
$tryAgain = false;
$retried = 0;
do {
$response = GraphHelper::getSharesSharedWithMe(
$this->featureContext->getBaseUrl(),
$this->featureContext->getStepLineRef(),
$credentials['username'],
$credentials['password']
)
);
);
$jsonBody = $this->featureContext->getJsonDecodedResponseBodyContent($response);
foreach ($jsonBody->value as $share) {
$autoSync = $this->featureContext->getUserAutoSyncSetting($credentials['username']);
$tryAgain = !$share->{'@client.synchronize'} && $autoSync && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
if ($tryAgain) {
$retried += 1;
echo "auto-sync share for user '$user' is enabled\n";
echo "but share '$share->name' was not auto-synced, retrying ($retried)...\n";
// wait 500ms and try again
\usleep(500 * 1000);
break;
}
}
} while ($tryAgain);
$this->featureContext->setResponse($response);
$this->featureContext->pushToLastStatusCodesArrays();
}
@@ -13,6 +13,7 @@ use Behat\Gherkin\Node\PyStringNode;
use TestHelpers\EmailHelper;
use PHPUnit\Framework\Assert;
use TestHelpers\GraphHelper;
use TestHelpers\SettingsHelper;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
@@ -109,7 +110,13 @@ class NotificationContext implements Context {
*/
public function listAllNotifications(string $user):ResponseInterface {
$this->setUserRecipient($user);
$headers = ["accept-language" => $this->settingsContext->getSettingLanguageValue($user)];
$language = SettingsHelper::getLanguageSettingValue(
$this->featureContext->getBaseUrl(),
$this->featureContext->getActualUsername($user),
$this->featureContext->getPasswordForUser($user),
$this->featureContext->getStepLineRef()
);
$headers = ["accept-language" => $language];
return OcsApiHelper::sendRequest(
$this->featureContext->getBaseUrl(),
$this->featureContext->getActualUsername($user),
@@ -553,7 +553,12 @@ class OCSContext implements Context {
* @throws Exception
*/
public function getOCSResponseStatusCode(ResponseInterface $response):string {
$jsonResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
try {
$jsonResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
} catch (JsonException $e) {
$jsonResponse = null;
}
if (\is_object($jsonResponse) && $jsonResponse->ocs->meta->statuscode) {
return (string) $jsonResponse->ocs->meta->statuscode;
}
@@ -15,6 +15,7 @@ use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use PHPUnit\Framework\Assert;
use Psr\Http\Message\ResponseInterface;
use TestHelpers\HttpRequestHelper;
use TestHelpers\SettingsHelper;
use Behat\Gherkin\Node\TableNode;
require_once 'bootstrap.php';
@@ -54,14 +55,11 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function getRoles(string $user): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "roles-list";
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::getRolesList(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
null,
"{}"
$this->featureContext->getStepLineRef()
);
}
@@ -91,15 +89,13 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function assignRoleToUser(string $user, string $userId, string $roleId): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "assignments-add";
$body = json_encode(["account_uuid" => $userId, "role_id" => $roleId], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::assignRoleToUser(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
null,
$body
$userId,
$roleId,
$this->featureContext->getStepLineRef(),
);
}
@@ -107,21 +103,18 @@ class SettingsContext implements Context {
* @param string $user
* @param string $userId
*
* @return void
* @return ResponseInterface
*
* @throws GuzzleException
* @throws Exception
*/
public function getAssignmentsList(string $user, string $userId): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "assignments-list";
$body = json_encode(["account_uuid" => $userId], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::getAssignmentsList(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
null,
$body
$userId,
$this->featureContext->getStepLineRef(),
);
}
@@ -300,14 +293,11 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function sendRequestGetBundlesList(string $user): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "bundles-list";
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::getBundlesList(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
null,
"{}"
$this->featureContext->getStepLineRef(),
);
}
@@ -320,21 +310,14 @@ class SettingsContext implements Context {
* @throws GuzzleException
* @throws Exception
*/
public function getBundlesList(string $user, string $bundleName): array {
$response = $this->sendRequestGetBundlesList($user);
$this->featureContext->theHTTPStatusCodeShouldBe(
201,
"Expected response status code should be 201",
$response
public function getBundleByName(string $user, string $bundleName): array {
return SettingsHelper::getBundleByName(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
$bundleName,
$this->featureContext->getStepLineRef()
);
$body = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
foreach ($body["bundles"] as $value) {
if ($value["displayName"] === $bundleName) {
return $value;
}
}
return [];
}
/**
@@ -347,15 +330,12 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function sendRequestGetSettingsValuesList(string $user, array $headers = null): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "values-list";
$body = json_encode(["account_uuid" => "me"], JSON_THROW_ON_ERROR);
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::getValuesList(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
$headers,
$body
$this->featureContext->getStepLineRef(),
$headers
);
}
@@ -382,37 +362,6 @@ class SettingsContext implements Context {
$this->featureContext->setResponse($this->sendRequestGetSettingsValuesList($user, $headers));
}
/**
* @param string $user
*
* @return string
*
* @throws GuzzleException
* @throws Exception
*/
public function getSettingLanguageValue(string $user): string {
$response = $this->sendRequestGetSettingsValuesList($user);
$this->featureContext->theHTTPStatusCodeShouldBe(
201,
"Expected response status code should be 201",
$response
);
$body = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
// if no language is set, the request body is empty return English as the default language
if (empty($body)) {
return "en";
}
foreach ($body["values"] as $value) {
if ($value["identifier"]["setting"] === "language") {
return $value["value"]["listValue"]["values"][0]["stringValue"];
}
}
// if a language setting was still not found, return English
return "en";
}
/**
* @param string $user
* @param string $language
@@ -423,7 +372,7 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function sendRequestToSwitchSystemLanguage(string $user, string $language): ResponseInterface {
$profileBundlesList = $this->getBundlesList($user, "Profile");
$profileBundlesList = $this->getBundleByName($user, "Profile");
Assert::assertNotEmpty($profileBundlesList, "bundles list is empty");
$settingId = '';
@@ -435,7 +384,6 @@ class SettingsContext implements Context {
}
Assert::assertNotEmpty($settingId, "settingId is empty");
$fullUrl = $this->baseUrl . $this->settingsUrl . "values-save";
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
$body = json_encode(
[
@@ -458,13 +406,12 @@ class SettingsContext implements Context {
],
JSON_THROW_ON_ERROR
);
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::updateSettings(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
null,
$body
$body,
$this->featureContext->getStepLineRef()
);
}
@@ -497,7 +444,6 @@ class SettingsContext implements Context {
* @throws Exception
*/
public function sendRequestToDisableAutoAccepting(string $user): ResponseInterface {
$fullUrl = $this->baseUrl . $this->settingsUrl . "values-save";
$body = json_encode(
[
"value" => [
@@ -513,13 +459,12 @@ class SettingsContext implements Context {
JSON_THROW_ON_ERROR
);
return HttpRequestHelper::post(
$fullUrl,
$this->featureContext->getStepLineRef(),
return SettingsHelper::updateSettings(
$this->baseUrl,
$user,
$this->featureContext->getPasswordForUser($user),
[],
$body
$body,
$this->featureContext->getStepLineRef()
);
}
@@ -541,5 +486,6 @@ class SettingsContext implements Context {
"Expected response status code should be 201",
$response
);
$this->featureContext->rememberUserAutoSyncSetting($user, false);
}
}
@@ -1132,6 +1132,8 @@ class SharingNgContext implements Context {
);
$this->featureContext->setResponse($response);
$this->featureContext->pushToLastStatusCodesArrays();
// disable check for client.synchronize
$this->featureContext->rememberUserAutoSyncSetting($user, false);
}
/**