From 4fbf79d8150fe273fa81c16a13309478786c3d1a Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Sun, 23 Nov 2025 20:41:25 +0530 Subject: [PATCH] tests for memberships API --- app/controllers/api/teams.php | 2 +- app/controllers/shared/api.php | 2 +- src/Appwrite/Auth/Validator/Role.php | 2 +- .../Services/Teams/TeamsConsoleClientTest.php | 108 ++++++++++++++++++ 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 46cc6dbc61..095c2c1f92 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1091,7 +1091,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') array_filter($roles, function ($role) { return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]); }); - return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); + return new ArrayList(new RoleValidator($roles), APP_LIMIT_ARRAY_PARAMS_SIZE); } return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE); }, 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project']) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index df705258a1..530ec5f8ba 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -343,7 +343,7 @@ App::init() } else { $adminRole = $adminRole->getRole(); } - $scopes = \array_merge($scopes, $roles[$adminRole]['scopes']); + $scopes = \array_merge($scopes, $roles[$adminRole]['scopes'] ?? []); } Authorization::setDefaultStatus($hasProjectSpecificPermissions); // Cancel security segmentation for admin users. diff --git a/src/Appwrite/Auth/Validator/Role.php b/src/Appwrite/Auth/Validator/Role.php index b5be347589..36a97bd101 100644 --- a/src/Appwrite/Auth/Validator/Role.php +++ b/src/Appwrite/Auth/Validator/Role.php @@ -73,7 +73,7 @@ class Role extends Validator */ public function isValid(mixed $value): bool { - if (\is_array($value)) { + if (!\is_string($value)) { return false; } diff --git a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php index 7949761d7f..a8e3732d1e 100644 --- a/tests/e2e/Services/Teams/TeamsConsoleClientTest.php +++ b/tests/e2e/Services/Teams/TeamsConsoleClientTest.php @@ -2,10 +2,12 @@ namespace Tests\E2E\Services\Teams; +use Utopia\Database\Helpers\ID; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\Helpers\Role; class TeamsConsoleClientTest extends Scope { @@ -252,4 +254,110 @@ class TeamsConsoleClientTest extends Scope return []; } + + public function testPerProjectMembership() + { + // Create team. + $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'teamId' => ID::unique(), + 'name' => 'Arsenal', + 'roles' => ['player'], + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']['$id']); + $teamId = $team['body']['$id']; + + // Create user. + $user = $this->client->call(Client::METHOD_POST, '/account', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'userId' => 'unique()', + 'email' => uniqid() . 'friend@localhost.test', + 'password' => 'password', + 'name' => 'Friend User', + ], false); + + $this->assertEquals(201, $user['headers']['status-code']); + + // Create project. + $project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => ID::unique(), + 'name' => 'Test Project', + 'teamId' => $teamId + ]); + + $this->assertEquals(201, $project['headers']['status-code']); + $this->assertNotEmpty($project['body']['$id']); + $projectId = $project['body']['$id']; + + // Create per-project membership. + $response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamId . '/memberships', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'email' => uniqid() . 'friend@localhost.test', + 'name' => 'Friend User', + 'roles' => [Role::member('')->toString(), Role::project($projectId, 'owner')->toString()], + 'url' => 'http://localhost:5000/join-us#title' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $membershipId = $response['body']['$id']; + $membershipRoles = $response['body']['roles']; + $this->assertEquals(2, count($membershipRoles)); + $this->assertContains(Role::member('')->toString(), $membershipRoles); + $this->assertContains(Role::project($projectId, 'owner')->toString(), $membershipRoles); + + // Update the membership to team. + $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamId . '/memberships/' . $membershipId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'roles' => ['owner'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $membershipRoles = $response['body']['roles']; + $this->assertEquals(1, count($membershipRoles)); + $this->assertContains('owner', $membershipRoles); + + // Again update the membership to project. + $response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamId . '/memberships/' . $membershipId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'roles' => [Role::project($projectId, 'owner')->toString()], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $membershipRoles = $response['body']['roles']; + $this->assertEquals(1, count($membershipRoles)); + $this->assertContains(Role::project($projectId, 'owner')->toString(), $membershipRoles); + + // Delete the membership. + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId . '/memberships/' . $membershipId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + + // Cleanup team for other tests to work. + $response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $response['headers']['status-code']); + } }