From 97bce8edd4df6bbb62d3e888ac2a8d837ac0a01f Mon Sep 17 00:00:00 2001 From: Viktor Scharf Date: Wed, 3 Jul 2024 22:24:14 +0200 Subject: [PATCH] more ocm tests --- tests/TestHelpers/OcmHelper.php | 6 +- tests/acceptance/config/behat.yml | 2 + ...ected-failures-localAPI-on-OCIS-storage.md | 5 + .../features/apiOcm/acceptInvitation.feature | 137 +++++++++++ tests/acceptance/features/apiOcm/ocm.feature | 20 -- .../apiOcm/searchFederationUsers.feature | 212 ++++++++++++++++++ .../acceptance/features/apiOcm/share.feature | 165 ++++++++++++++ .../features/bootstrap/FeatureContext.php | 8 + .../features/bootstrap/OcmContext.php | 106 ++++++++- .../features/bootstrap/Provisioning.php | 15 +- tests/config/drone/.env-federation | 2 +- 11 files changed, 640 insertions(+), 38 deletions(-) create mode 100755 tests/acceptance/features/apiOcm/acceptInvitation.feature delete mode 100755 tests/acceptance/features/apiOcm/ocm.feature create mode 100755 tests/acceptance/features/apiOcm/searchFederationUsers.feature create mode 100755 tests/acceptance/features/apiOcm/share.feature diff --git a/tests/TestHelpers/OcmHelper.php b/tests/TestHelpers/OcmHelper.php index fac65dce0..166200a09 100644 --- a/tests/TestHelpers/OcmHelper.php +++ b/tests/TestHelpers/OcmHelper.php @@ -80,6 +80,7 @@ class OcmHelper { * @param string $user * @param string $password * @param string $token + * @param string $providerDomain * * @return ResponseInterface * @throws GuzzleException @@ -89,11 +90,12 @@ class OcmHelper { string $xRequestId, string $user, string $password, - string $token + string $token, + string $providerDomain, ): ResponseInterface { $body = [ "token" => $token, - "providerDomain" => 'ocis-server' + "providerDomain" => $providerDomain ]; $url = self::getFullUrl($baseUrl, 'accept-invite'); return HttpRequestHelper::post( diff --git a/tests/acceptance/config/behat.yml b/tests/acceptance/config/behat.yml index 5add91e69..e8ba919da 100644 --- a/tests/acceptance/config/behat.yml +++ b/tests/acceptance/config/behat.yml @@ -364,6 +364,8 @@ default: contexts: - FeatureContext: *common_feature_context_params - OcmContext: + - SharingNgContext: + - SpacesContext: extensions: rdx\behatvars\BehatVariablesExtension: ~ diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md index 3283e908c..9cf54ac7c 100644 --- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md @@ -307,5 +307,10 @@ The expected failures in this file are from features in the owncloud/ocis repo. - [apiSpacesDavOperation/moveByFileId.feature:209](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L209) +### [OCM. sharing issues](https://github.com/owncloud/ocis/issues/9534) + +- [apiOcm/share.feature:12](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiOcm/share.feature#L12) +- [apiOcm/share.feature:90](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiOcm/share.feature#L90) + - Note: always have an empty line at the end of this file. The bash script that processes this file requires that the last line has a newline on the end. diff --git a/tests/acceptance/features/apiOcm/acceptInvitation.feature b/tests/acceptance/features/apiOcm/acceptInvitation.feature new file mode 100755 index 000000000..edbf2ab3f --- /dev/null +++ b/tests/acceptance/features/apiOcm/acceptInvitation.feature @@ -0,0 +1,137 @@ +@ocm +Feature: accepting invitation + As a user + I can accept invitations from users of other ocis instances + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And using server "REMOTE" + Given these users have been created with default attributes and without skeleton files: + | username | + | Brian | + | Carol | + + + Scenario: user accepts invitation + Given using server "LOCAL" + When "Alice" creates the federation share invitation + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "expiration", + "token" + ], + "properties": { + "expiration": { + "type": "integer", + "pattern": "^[0-9]{10}$" + }, + "token": { + "type": "string", + "pattern": "^%fed_invitation_token_pattern%$" + } + } + } + """ + When using server "REMOTE" + And "Brian" accepts the last federation share invitation + Then the HTTP status code should be "200" + + + Scenario: user accepts invitation sent with email and description + Given using server "LOCAL" + When "Alice" creates the federation share invitation with email "alice@example.com" and description "a share invitation from Alice" + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "expiration", + "token" + ], + "properties": { + "expiration": { + "type": "integer", + "pattern": "^[0-9]{10}$" + }, + "token": { + "type": "string", + "pattern": "^%fed_invitation_token_pattern%$" + } + } + } + """ + When using server "REMOTE" + And "Brian" accepts the last federation share invitation + Then the HTTP status code should be "200" + + + Scenario: two users can accept one invitation + Given using server "LOCAL" + And "Alice" has created the federation share invitation + When using server "REMOTE" + And "Brian" accepts the last federation share invitation + Then the HTTP status code should be "200" + And "Carol" accepts the last federation share invitation + Then the HTTP status code should be "200" + + + Scenario: user tries to accept the invitation twice + Given using server "LOCAL" + And "Alice" has created the federation share invitation + When using server "REMOTE" + And "Brian" accepts the last federation share invitation + Then the HTTP status code should be "200" + When "Brian" tries to accept the last federation share invitation + Then the HTTP status code should be "409" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "const": "ALREADY_EXIST" + }, + "message": { + "const": "user already known" + } + } + } + """ + + + Scenario: users try to accept each other's invitation + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + And "Brian" has created the federation share invitation + When using server "LOCAL" + And "Alice" tries to accept the last federation share invitation + Then the HTTP status code should be "409" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "const": "ALREADY_EXIST" + }, + "message": { + "const": "user already known" + } + } + } + """ diff --git a/tests/acceptance/features/apiOcm/ocm.feature b/tests/acceptance/features/apiOcm/ocm.feature deleted file mode 100755 index 825d69fda..000000000 --- a/tests/acceptance/features/apiOcm/ocm.feature +++ /dev/null @@ -1,20 +0,0 @@ -@ocm -Feature: an user shares resources usin ScienceMesh application - As a user - I want to share resources between different ocis instances - - Background: - Given these users have been created with default attributes and without skeleton files: - | username | - | Alice | - And using server "REMOTE" - And user "Brian" has been created with default attributes and without skeleton files - - - Scenario: user generates invitation - Given using server "LOCAL" - When "Alice" generates invitation - Then the HTTP status code should be "200" - When using server "REMOTE" - And "Brian" accepts invitation - Then the HTTP status code should be "200" diff --git a/tests/acceptance/features/apiOcm/searchFederationUsers.feature b/tests/acceptance/features/apiOcm/searchFederationUsers.feature new file mode 100755 index 000000000..f51282953 --- /dev/null +++ b/tests/acceptance/features/apiOcm/searchFederationUsers.feature @@ -0,0 +1,212 @@ +@ocm +Feature: search federation users + As a user + I can find federation users after accepting an invitation to share resources + + + Background: + Given these users have been created with default attributes and without skeleton files: + | username | + | Alice | + | Carol | + And using server "REMOTE" + And user "Brian" has been created with default attributes and without skeleton files + + + Scenario: users search for federation users by display name + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + When user "Brian" searches for user "ali" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "const": "Alice Hansen" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + """ + And using server "LOCAL" + When user "Alice" searches for user "bri" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "const": "Brian Murphy" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + """ + + + Scenario: sers search for federation users by email + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + When user "Brian" searches for user "%22alice@example.org%22" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "const": "Alice Hansen" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + """ + And using server "LOCAL" + When user "Alice" searches for user "%22brian@example.org%22" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "displayName", + "id" + ], + "properties": { + "displayName": { + "const": "Brian Murphy" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + } + } + """ + + + Scenario: sers search for federation users without federated connection + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + When user "Brian" searches for user "%22carol@example.org%22" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 0, + "maxItems": 0 + } + } + } + """ + And using server "LOCAL" + When user "Carol" searches for user "bria" using Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 0, + "maxItems": 0 + } + } + } + """ + And using server "REMOTE" + +# TODO try to find federation users after deleting federated conection \ No newline at end of file diff --git a/tests/acceptance/features/apiOcm/share.feature b/tests/acceptance/features/apiOcm/share.feature new file mode 100755 index 000000000..de5ad44d5 --- /dev/null +++ b/tests/acceptance/features/apiOcm/share.feature @@ -0,0 +1,165 @@ +@ocm +Feature: an user shares resources usin ScienceMesh application + As a user + I want to share resources between different ocis instances + + Background: + Given user "Alice" has been created with default attributes and without skeleton files + And using server "REMOTE" + And user "Brian" has been created with default attributes and without skeleton files + + + Scenario: users shares folder to federation users after receiver accepted invitation + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + And using server "LOCAL" + And user "Alice" has created folder "folderToShare" + When user "Alice" sends the following resource share invitation using the Graph API: + | resource | folderToShare | + | space | Personal | + | sharee | Brian | + | shareType | user | + | permissionsRole | Viewer | + Then the HTTP status code should be "200" + When using server "REMOTE" + And 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 + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "name" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "const": "Alice Hansen" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "name": { + "const": "folderToShare" + } + } + } + } + } + } + """ + + Scenario: users shares folder to federation users after accepting invitation + Given using server "LOCAL" + And "Alice" has created the federation share invitation + And using server "REMOTE" + And "Brian" has accepted invitation + And user "Brian" has created folder "folderToShare" + When user "Brian" sends the following resource share invitation using the Graph API: + | resource | folderToShare | + | space | Personal | + | sharee | Alice | + | shareType | user | + | permissionsRole | Viewer | + Then the HTTP status code should be "200" + When using server "LOCAL" + And user "Alice" lists the shares shared with her using the Graph API + Then the HTTP status code should be "200" + And the JSON data of the response should match + """ + { + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "type": "object", + "required": [ + "@UI.Hidden", + "@client.synchronize", + "createdBy", + "name" + ], + "properties": { + "@UI.Hidden":{ + "type": "boolean", + "enum": [false] + }, + "@client.synchronize":{ + "type": "boolean", + "enum": [true] + }, + "createdBy": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "object", + "required": ["displayName", "id"], + "properties": { + "displayName": { + "type": "string", + "const": "Brian Murphy" + }, + "id": { + "type": "string", + "pattern": "^%user_id_pattern%$" + } + } + } + } + }, + "name": { + "const": "folderToShare" + } + } + } + } + } + } + """ \ No newline at end of file diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php index 84b187a93..d55888243 100644 --- a/tests/acceptance/features/bootstrap/FeatureContext.php +++ b/tests/acceptance/features/bootstrap/FeatureContext.php @@ -2250,6 +2250,14 @@ class FeatureContext extends BehatVariablesContext { "getTusResourceLocation" ], "parameter" => [] + ], + [ + "code" => "%fed_invitation_token_pattern%", + "function" => [ + __NAMESPACE__ . '\TestHelpers\GraphHelper', + "getUUIDv4Regex" + ], + "parameter" => [] ] ]; if ($user !== null) { diff --git a/tests/acceptance/features/bootstrap/OcmContext.php b/tests/acceptance/features/bootstrap/OcmContext.php index 8bbb6046b..417113483 100644 --- a/tests/acceptance/features/bootstrap/OcmContext.php +++ b/tests/acceptance/features/bootstrap/OcmContext.php @@ -22,11 +22,12 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\BeforeScenarioScope; use GuzzleHttp\Exception\GuzzleException; +use Psr\Http\Message\ResponseInterface; use TestHelpers\OcisHelper; use TestHelpers\OcmHelper; /** - * Acceptance test steps related to testing sharing ng features + * Acceptance test steps related to testing federation share(ocm) features */ class OcmContext implements Context { private FeatureContext $featureContext; @@ -53,17 +54,40 @@ class OcmContext implements Context { } /** - * @When :user generates invitation - * @When :user generates invitation with email :email and description :description + * @return string + */ + public function getOcisDomain(): string { + return $this->extractDomain(\getenv('TEST_SERVER_URL')); + } + + /** + * @return string + */ + public function getFedOcisDomain(): string { + return $this->extractDomain(\getenv('TEST_SERVER_FED_URL')); + } + + /** + * @param string $url * + * @return string + */ + public function extractDomain($url): string { + if (!$url) { + return "localhost"; + } + return parse_url($url)["host"]; + } + + /** * @param string $user * @param string $email * @param string $description * - * @return void + * @return ResponseInterface * @throws GuzzleException */ - public function userGeneratesInvitation(string $user, $email = null, $description = null): void { + public function createInvitation(string $user, $email = null, $description = null): ResponseInterface { $response = OcmHelper::createInvitation( $this->featureContext->getBaseUrl(), $this->featureContext->getStepLineRef(), @@ -72,26 +96,84 @@ class OcmContext implements Context { $email, $description ); - $this->featureContext->setResponse($response); - $this->invitationToken = $this->featureContext->getJsonDecodedResponse($this->featureContext->getResponse())['token']; + $responseData = \json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + if (isset($responseData["token"])) { + $this->invitationToken = $responseData["token"]; + } else { + throw new Exception(__METHOD__ . " response doesn't contain token"); + } + return $response; } /** - * @When :user accepts invitation + * @When :user creates the federation share invitation + * @When :user creates the federation share invitation with email :email and description :description + * + * @param string $user + * @param string $email + * @param string $description + * + * @return void + * @throws GuzzleException + */ + public function userCreatesTheFederationShareInvitation(string $user, $email = null, $description = null): void { + $this->featureContext->setResponse($this->createInvitation($user, $email, $description)); + } + + /** + * @Given :user has created the federation share invitation * * @param string $user * * @return void * @throws GuzzleException */ - public function userAcceptsInvitation(string $user): void { - $response = OcmHelper::acceptInvitation( + public function userHasCreatedTheFederationShareInvitation(string $user): void { + $response = $this->createInvitation($user); + $this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response); + } + + /** + * @param string $user + * + * @return ResponseInterface + * @throws GuzzleException + */ + public function acceptInvitation(string $user): ResponseInterface { + $providerDomain = ($this->featureContext->getCurrentServer() === "LOCAL") ? $this->getFedOcisDomain() : $this->getOcisDomain(); + return OcmHelper::acceptInvitation( $this->featureContext->getBaseUrl(), $this->featureContext->getStepLineRef(), $user, $this->featureContext->getPasswordForUser($user), - $this->invitationToken + $this->invitationToken, + $providerDomain ); - $this->featureContext->setResponse($response); + } + + /** + * @When :user accepts the last federation share invitation + * @When :user tries to accept the last federation share invitation + * + * @param string $user + * + * @return void + * @throws GuzzleException + */ + public function userAcceptsTheLastFederationShareInvitation(string $user): void { + $this->featureContext->setResponse($this->acceptInvitation($user)); + } + + /** + * @Given :user has accepted invitation + * + * @param string $user + * + * @return void + * @throws GuzzleException + */ + public function userHasAcceptedTheLastFederationShareInvitation(string $user): void { + $response = $this->acceptInvitation($user); + $this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response); } } diff --git a/tests/acceptance/features/bootstrap/Provisioning.php b/tests/acceptance/features/bootstrap/Provisioning.php index 2c0a6301d..0432b1121 100644 --- a/tests/acceptance/features/bootstrap/Provisioning.php +++ b/tests/acceptance/features/bootstrap/Provisioning.php @@ -83,6 +83,13 @@ trait Provisioning { return $this->createdUsers; } + /** + * @return array + */ + public function getAllCreatedUsers():array { + return array_merge($this->createdUsers, $this->createdRemoteUsers); + } + /** * @return boolean */ @@ -107,8 +114,9 @@ trait Provisioning { */ public function getUserDisplayName(string $username):string { $normalizedUsername = $this->normalizeUsername($username); - if (isset($this->createdUsers[$normalizedUsername]['displayname'])) { - $displayName = (string) $this->createdUsers[$normalizedUsername]['displayname']; + $users = $this->getAllCreatedUsers(); + if (isset($users[$normalizedUsername]['displayname'])) { + $displayName = (string) $users[$normalizedUsername]['displayname']; if ($displayName !== '') { return $displayName; } @@ -124,7 +132,7 @@ trait Provisioning { * @throws Exception */ public function getAttributeOfCreatedUser(string $user, string $attribute) { - $usersList = $this->getCreatedUsers(); + $usersList = $this->getAllCreatedUsers(); $normalizedUsername = $this->normalizeUsername($user); if (\array_key_exists($normalizedUsername, $usersList)) { if (\array_key_exists($attribute, $usersList[$normalizedUsername])) { @@ -1118,6 +1126,7 @@ trait Provisioning { // See comment above about the LOCAL case. The logic is the same for the remote case. if ($shouldExist || !\array_key_exists($normalizedUsername, $this->createdRemoteUsers)) { $this->createdRemoteUsers[$normalizedUsername] = $userData; + $this->createdUsers[$normalizedUsername] = $userData; } } } diff --git a/tests/config/drone/.env-federation b/tests/config/drone/.env-federation index 142258334..6e381baaa 100644 --- a/tests/config/drone/.env-federation +++ b/tests/config/drone/.env-federation @@ -1,4 +1,4 @@ -export OCM_OCM_PROVIDER_AUTHORIZER_PROVIDERS_FILE=${workspaceFolder}/tests/config/drone/providers.json +export OCM_OCM_PROVIDER_AUTHORIZER_PROVIDERS_FILE=tests/config/drone/providers.json export OCM_OCM_INVITE_MANAGER_INSECURE=true export OCM_OCM_SHARE_PROVIDER_INSECURE=true export OCM_OCM_STORAGE_PROVIDER_INSECURE=true