From bb75036efef7e082c52a3a5eeb465a2af708eafa Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 13 Oct 2023 01:45:27 +0530 Subject: [PATCH] WIP: Mocked GitHub installation --- .env | 2 +- app/controllers/api/vcs.php | 3 +- app/controllers/mock.php | 66 ++++++++++ docker-compose.yml | 2 +- .../e2e/Services/VCS/VCSConsoleClientTest.php | 115 +++++++++++------- 5 files changed, 142 insertions(+), 46 deletions(-) diff --git a/.env b/.env index 6c97639b66..c34e165208 100644 --- a/.env +++ b/.env @@ -96,7 +96,7 @@ _APP_VCS_GITHUB_APP_ID= _APP_VCS_GITHUB_CLIENT_ID= _APP_VCS_GITHUB_CLIENT_SECRET= _APP_VCS_GITHUB_WEBHOOK_SECRET= -_APP_VCS_TEST_INSTALLATION_ID= +_APP_VCS_TEST_GITHUB_INSTALLATION_ID= _APP_VCS_TEST_PROVIDER_REPOSITORY_ID= _APP_MIGRATIONS_FIREBASE_CLIENT_ID= _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET= diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index b0050c61d4..f3dcceb887 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -22,7 +22,6 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; -use Utopia\Database\Validator\UID; use Utopia\Detector\Adapter\Bun; use Utopia\Detector\Adapter\CPP; use Utopia\Detector\Adapter\Dart; @@ -274,7 +273,7 @@ App::get('/v1/vcs/github/callback') ->label('scope', 'public') ->label('error', __DIR__ . '/../../views/general/error.phtml') ->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true) - ->param('setup_action', '', new Text(256, 0), 'GitHub setup actuon type', true) + ->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true) ->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true) ->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true) ->inject('gitHub') diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 593063ae84..319861d69b 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -17,6 +17,9 @@ use Utopia\Validator\WhiteList; use Utopia\Database\Helpers\ID; use Utopia\Database\Validator\UID; use Utopia\Validator\Nullable; +use Utopia\VCS\Adapter\Git\GitHub; +use Utopia\Database\Helpers\Permission; +use Utopia\Database\Helpers\Role; App::get('/v1/mock/tests/foo') ->desc('Get Foo') @@ -646,6 +649,69 @@ App::patch('/v1/mock/functions-v2') $response->noContent(); }); + App::get('/v1/mock/github/callback') + ->desc('Create installation document using GitHub installation id') + ->groups(['mock', 'api', 'vcs']) + ->label('scope', 'public') + ->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true) + ->param('projectId', '', new Text(2048), 'Project ID of the project where app is to be installed', true) + ->inject('gitHub') + ->inject('project') + ->inject('response') + ->inject('dbForConsole') + ->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) { + if (empty($projectId)) { + $error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.'; + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error); + } + + $project = $dbForConsole->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + $error = 'Project with the ID from state could not be found.'; + throw new Exception(Exception::PROJECT_NOT_FOUND, $error); + } + + // Create / Update installation + if (!empty($providerInstallationId)) { + $privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY'); + $githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID'); + $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); + $owner = $github->getOwnerName($providerInstallationId) ?? ''; + + $projectInternalId = $project->getInternalId(); + + $teamId = $project->getAttribute('teamId', ''); + + $installation = new Document([ + '$id' => ID::unique(), + '$permissions' => [ + Permission::read(Role::team(ID::custom($teamId))), + Permission::update(Role::team(ID::custom($teamId), 'owner')), + Permission::update(Role::team(ID::custom($teamId), 'developer')), + Permission::delete(Role::team(ID::custom($teamId), 'owner')), + Permission::delete(Role::team(ID::custom($teamId), 'developer')), + ], + 'providerInstallationId' => $providerInstallationId, + 'projectId' => $projectId, + 'projectInternalId' => $projectInternalId, + 'provider' => 'github', + 'organization' => $owner, + 'personal' => true + ]); + + $installation = $dbForConsole->createDocument('installations', $installation); + } else { + $error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.'; + + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error); + } + + $response->json([ + 'installationId' => $installation->getId(), + ]); + }); + App::shutdown() ->groups(['mock']) ->inject('utopia') diff --git a/docker-compose.yml b/docker-compose.yml index df4c2d756e..c6a3f56616 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -185,7 +185,7 @@ services: - _APP_VCS_GITHUB_WEBHOOK_SECRET - _APP_VCS_GITHUB_CLIENT_SECRET - _APP_VCS_GITHUB_CLIENT_ID - - _APP_VCS_TEST_INSTALLATION_ID + - _APP_VCS_TEST_GITHUB_INSTALLATION_ID - _APP_VCS_TEST_PROVIDER_REPOSITORY_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_ID - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET diff --git a/tests/e2e/Services/VCS/VCSConsoleClientTest.php b/tests/e2e/Services/VCS/VCSConsoleClientTest.php index b9df0197f4..14a4fc0272 100644 --- a/tests/e2e/Services/VCS/VCSConsoleClientTest.php +++ b/tests/e2e/Services/VCS/VCSConsoleClientTest.php @@ -13,27 +13,65 @@ class VCSConsoleClientTest extends Scope use ProjectCustom; use SideConsole; - public ?string $installationId = null; public ?string $providerRepositoryId = null; protected function setUp(): void { parent::setUp(); - $this->installationId = App::getEnv('_APP_VCS_TEST_INSTALLATION_ID'); $this->providerRepositoryId = App::getEnv('_APP_VCS_TEST_PROVIDER_REPOSITORY_ID'); } - public function testDetectRuntime() + public function testGitHubAuthorize() + { + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/mock/github/callback', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'installation_id' => App::getEnv('_APP_VCS_TEST_GITHUB_INSTALLATION_ID'), + 'projectId' => $this->getProject()['$id'], + ]); + + $this->assertNotEmpty($response['body']['installationId']); + $installationId = $response['body']['installationId']; + return $installationId; + } + + /** + * @depends testGitHubAuthorize + */ + public function testGetInstallation(string $installationId) { /** * Test for SUCCESS */ - $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/' . $this->providerRepositoryId . '/detection', array_merge([ + $installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $installationId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId, + 'installationId' => $installationId + ]); + + $this->assertEquals(200, $installation['headers']['status-code']); + } + + /** + * @depends testGitHubAuthorize + */ + public function testDetectRuntime(string $installationId) + { + /** + * Test for SUCCESS + */ + + $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/detection', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'installationId' => $installationId, 'providerRepositoryId' => $this->providerRepositoryId ]); @@ -44,22 +82,22 @@ class VCSConsoleClientTest extends Scope * Test for FAILURE */ - // $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/1234/detection', array_merge([ + // $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/1234/detection', array_merge([ // 'content-type' => 'application/json', // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders()), [ - // 'installationId' => $this->installationId, + // 'installationId' => $installationId, // 'providerRepositoryId' => 1234 // ]); // $this->assertEquals(404, $runtime['headers']['status-code']); // TODO: throw 404 from GitHub.php if repo not found - // $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/' . $this->providerRepositoryId .'/detection', array_merge([ + // $runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId .'/detection', array_merge([ // 'content-type' => 'application/json', // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders()), [ - // 'installationId' => $this->installationId, + // 'installationId' => $installationId, // 'providerRepositoryId' => $this->providerRepositoryId, // 'providerRootDirectory' => ''̦ // ]); @@ -67,27 +105,30 @@ class VCSConsoleClientTest extends Scope // $this->assertEquals(404, $runtime['headers']['status-code']); } - public function testListRepositories() + /** + * @depends testGitHubAuthorize + */ + public function testListRepositories(string $installationId) { /** * Test for SUCCESS */ - $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories', array_merge([ + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId + 'installationId' => $installationId ]); $this->assertEquals(200, $repositories['headers']['status-code']); $this->assertEquals($repositories['body']['total'], 3); - $searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories', array_merge([ + $searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId, + 'installationId' => $installationId, 'search' => 'func' ]); @@ -108,28 +149,31 @@ class VCSConsoleClientTest extends Scope $this->assertEquals(404, $repositories['headers']['status-code']); } - public function testGetRepository(string $providerRepositoryId2 = '700020051') + /** + * @depends testGitHubAuthorize + */ + public function testGetRepository(string $installationId, string $providerRepositoryId2 = '700020051') { /** * Test for SUCCESS */ - $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/' . $this->providerRepositoryId, array_merge([ + $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId, + 'installationId' => $installationId, 'providerRepositoryId' => $this->providerRepositoryId ]); $this->assertEquals(200, $repository['headers']['status-code']); $this->assertEquals($repository['body']['name'], 'ruby-starter'); - $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/' . $providerRepositoryId2, array_merge([ + $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $providerRepositoryId2, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId, + 'installationId' => $installationId, 'providerRepositoryId' => $providerRepositoryId2 ]); @@ -140,11 +184,11 @@ class VCSConsoleClientTest extends Scope * Test for FAILURE */ - // $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/1234', array_merge([ + // $repository = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/1234', array_merge([ // 'content-type' => 'application/json', // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders()), [ - // 'installationId' => $this->installationId, + // 'installationId' => $installationId, // 'providerRepositoryId' => 1234 // ]); @@ -152,17 +196,20 @@ class VCSConsoleClientTest extends Scope // TODO: Throw 404 if repository not found } - public function testListRepositoryBranches() + /** + * @depends testGitHubAuthorize + */ + public function testListRepositoryBranches(string $installationId) { /** * Test for SUCCESS */ - $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([ + $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/branches', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'installationId' => $this->installationId, + 'installationId' => $installationId, 'providerRepositoryId' => $this->providerRepositoryId ]); @@ -175,31 +222,15 @@ class VCSConsoleClientTest extends Scope * Test for FAILURE */ - // $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $this->installationId . '/providerRepositories/1234/branches', array_merge([ + // $repositoryBranches = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/1234/branches', array_merge([ // 'content-type' => 'application/json', // 'x-appwrite-project' => $this->getProject()['$id'], // ], $this->getHeaders()), [ - // 'installationId' => $this->installationId, + // 'installationId' => $installationId, // 'providerRepositoryId' => 1234 // ]); // $this->assertEquals(404, $repositoryBranches['headers']['status-code']); // TODO: Check why it's throwing 500 server error } - - // public function testGetInstallation() - // { - // /** - // * Test for SUCCESS - // */ - - // $installation = $this->client->call(Client::METHOD_GET, '/vcs/installations/' . $this->installationId, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'installationId' => $this->installationId - // ]); - - // $this->assertEquals(200, $installation['headers']['status-code']); - // } }