diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 7f84ace6f2..35ef605a1a 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -76,6 +76,8 @@ trait ProjectCustom 'buckets.write', 'functions.read', 'functions.write', + 'sites.read', + 'sites.write', 'execution.read', 'execution.write', 'locale.read', diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php new file mode 100644 index 0000000000..1a3fec86c6 --- /dev/null +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -0,0 +1,198 @@ +client->call(Client::METHOD_POST, '/sites', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + + $this->assertEquals($site['headers']['status-code'], 201, 'Setup site failed with status code: ' . $site['headers']['status-code'] . ' and response: ' . json_encode($site['body'], JSON_PRETTY_PRINT)); + + $siteId = $site['body']['$id']; + + return $siteId; + } + + protected function setupDeployment(string $siteId, mixed $params): string + { + $deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + $this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + $deploymentId = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 50000, 500); + + return $deploymentId; + } + + protected function cleanupSite(string $siteId): void + { + $site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + + $this->assertEquals($site['headers']['status-code'], 204); + } + + protected function createSite(mixed $params): mixed + { + $site = $this->client->call(Client::METHOD_POST, '/sites', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $site; + } + + protected function createVariable(string $siteId, mixed $params): mixed + { + $variable = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $variable; + } + + protected function getSite(string $siteId): mixed + { + $site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $site; + } + + protected function getDeployment(string $siteId, string $deploymentId): mixed + { + $deployment = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $deployment; + } + + protected function getLog(string $siteId, $logId): mixed + { + $log = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/logs/' . $logId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $log; + } + + protected function listSites(mixed $params = []): mixed + { + $sites = $this->client->call(Client::METHOD_GET, '/sites', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $sites; + } + + protected function listDeployments(string $siteId, $params = []): mixed + { + $deployments = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $deployments; + } + + protected function listLogs(string $siteId, mixed $params = []): mixed + { + $logs = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $logs; + } + + protected function packageSite(string $site): CURLFile + { + $folderPath = realpath(__DIR__ . '/../../../resources/sites') . "/$site"; + $tarPath = "$folderPath/code.tar.gz"; + + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + + if (filesize($tarPath) > 1024 * 1024 * 5) { + throw new \Exception('Code package is too large. Use the chunked upload method instead.'); + } + + return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); + } + + protected function createDeployment(string $siteId, mixed $params = []): mixed + { + $deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $deployment; + } + + protected function getSiteUsage(string $siteId, mixed $params): mixed + { + $usage = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/usage', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $usage; + } + + protected function getTemplate(string $templateId) + { + $template = $this->client->call(Client::METHOD_GET, '/sites/templates/' . $templateId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $template; + } + + protected function deleteSite(string $siteId): mixed + { + $site = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $site; + } +} diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php new file mode 100644 index 0000000000..f6c0bca889 --- /dev/null +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -0,0 +1,272 @@ +createSite([ + 'adapter' => 'ssr', + 'buildCommand' => 'npm run build', + 'buildRuntime' => 'node-22', + 'fallbackFile' => null, + 'framework' => 'nextjs', + 'installCommand' => 'npm install', + 'name' => 'Test Site', + 'outputDirectory' => './.next', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique(), + 'templateOwner' => 'appwrite', + 'templateRepository' => 'templates-for-sites', + 'templateRootDirectory' => './nextjs/starter', + 'templateVersion' => '0.2.*' + ]); + + $siteId = $site['body']['$id'] ?? ''; + + $dateValidator = new DateTimeValidator(); + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals('Test Site', $site['body']['name']); + $this->assertEquals('nextjs', $site['body']['framework']); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$updatedAt'])); + $this->assertEquals('ssr', $site['body']['adapter']); + $this->assertEquals('npm run build', $site['body']['buildCommand']); + $this->assertEquals('node-22', $site['body']['buildRuntime']); + $this->assertEquals(null, $site['body']['fallbackFile']); + $this->assertEquals('npm install', $site['body']['installCommand']); + $this->assertEquals('./.next', $site['body']['outputDirectory']); + $this->assertEquals('main', $site['body']['providerBranch']); + $this->assertEquals('./', $site['body']['providerRootDirectory']); + + $variable = $this->createVariable($siteId, [ + 'key' => 'siteKey1', + 'value' => 'siteValue1', + ]); + $variable2 = $this->createVariable($siteId, [ + 'key' => 'siteKey2', + 'value' => 'siteValue2', + ]); + $variable3 = $this->createVariable($siteId, [ + 'key' => 'siteKey3', + 'value' => 'siteValue3', + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertEquals(201, $variable2['headers']['status-code']); + $this->assertEquals(201, $variable3['headers']['status-code']); + + $this->cleanupSite($siteId); + } + + public function testListSites(): void + { + /** + * Test for SUCCESS + */ + $siteId = $this->setupSite([ + 'adapter' => 'ssr', + 'buildCommand' => 'npm run build', + 'buildRuntime' => 'node-22', + 'fallbackFile' => null, + 'framework' => 'nextjs', + 'installCommand' => 'npm install', + 'name' => 'Test Site', + 'outputDirectory' => './.next', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique(), + 'templateOwner' => 'appwrite', + 'templateRepository' => 'templates-for-sites', + 'templateRootDirectory' => './nextjs/starter', + 'templateVersion' => '0.2.*' + ]); + + $sites = $this->listSites([ + 'search' => $siteId, + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(1, $sites['body']['sites']); + $this->assertEquals($sites['body']['sites'][0]['name'], 'Test Site'); + + // Test pagination limit + $sites = $this->listSites([ + 'queries' => [ + Query::limit(1)->toString(), + ], + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(1, $sites['body']['sites']); + + // Test pagination offset + $sites = $this->listSites([ + 'queries' => [ + Query::offset(1)->toString(), + ], + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(0, $sites['body']['sites']); + + // Test filter enabled + $sites = $this->listSites([ + 'queries' => [ + Query::equal('enabled', [true])->toString(), + ], + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(1, $sites['body']['sites']); + + // Test filter disabled + $sites = $this->listSites([ + 'queries' => [ + Query::equal('enabled', [false])->toString(), + ], + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(0, $sites['body']['sites']); + + // Test search name + $sites = $this->listSites([ + 'search' => 'Test' + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(1, $sites['body']['sites']); + $this->assertEquals($sites['body']['sites'][0]['$id'], $siteId); + + // Test search framework + $sites = $this->listSites([ + 'search' => 'nextjs' + ]); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertCount(1, $sites['body']['sites']); + $this->assertEquals($sites['body']['sites'][0]['$id'], $siteId); + + /** + * Test pagination + */ + $siteId2 = $this->setupSite([ + 'adapter' => 'ssr', + 'buildCommand' => 'npm run build', + 'buildRuntime' => 'node-22', + 'fallbackFile' => null, + 'framework' => 'nextjs', + 'installCommand' => 'npm install', + 'name' => 'Test Site 2', + 'outputDirectory' => './.next', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique(), + 'templateOwner' => 'appwrite', + 'templateRepository' => 'templates-for-sites', + 'templateRootDirectory' => './nextjs/starter', + 'templateVersion' => '0.2.*' + ]); + + $sites = $this->listSites(); + + $this->assertEquals($sites['headers']['status-code'], 200); + $this->assertEquals($sites['body']['total'], 2); + $this->assertIsArray($sites['body']['sites']); + $this->assertCount(2, $sites['body']['sites']); + $this->assertEquals($sites['body']['sites'][0]['name'], 'Test Site'); + $this->assertEquals($sites['body']['sites'][1]['name'], 'Test Site 2'); + + $sites1 = $this->listSites([ + 'queries' => [ + Query::cursorAfter(new Document(['$id' => $sites['body']['sites'][0]['$id']]))->toString(), + ], + ]); + + $this->assertEquals($sites1['headers']['status-code'], 200); + $this->assertCount(1, $sites1['body']['sites']); + $this->assertEquals($sites1['body']['sites'][0]['name'], 'Test Site 2'); + + $sites2 = $this->listSites([ + 'queries' => [ + Query::cursorBefore(new Document(['$id' => $sites['body']['sites'][1]['$id']]))->toString(), + ], + ]); + + $this->assertEquals($sites2['headers']['status-code'], 200); + $this->assertCount(1, $sites2['body']['sites']); + $this->assertEquals($sites2['body']['sites'][0]['name'], 'Test Site'); + + /** + * Test for FAILURE + */ + $sites = $this->listSites([ + 'queries' => [ + Query::cursorAfter(new Document(['$id' => 'unknown']))->toString(), + ], + ]); + $this->assertEquals($sites['headers']['status-code'], 400); + + $this->cleanupSite($siteId); + $this->cleanupSite($siteId2); + } + + public function testGetSite(): void + { + $siteId = $this->setupSite([ + 'adapter' => 'ssr', + 'buildCommand' => 'npm run build', + 'buildRuntime' => 'node-22', + 'fallbackFile' => null, + 'framework' => 'nextjs', + 'installCommand' => 'npm install', + 'name' => 'Test Site', + 'outputDirectory' => './.next', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique(), + 'templateOwner' => 'appwrite', + 'templateRepository' => 'templates-for-sites', + 'templateRootDirectory' => './nextjs/starter', + 'templateVersion' => '0.2.*' + ]); + + $this->assertNotNull($siteId); + + /** + * Test for SUCCESS + */ + $site = $this->getSite($siteId); + + $this->assertEquals($site['headers']['status-code'], 200); + $this->assertEquals($site['body']['name'], 'Test Site'); + + /** + * Test for FAILURE + */ + $site = $this->getSite('x'); + + $this->assertEquals($site['headers']['status-code'], 404); + + $this->cleanupSite($siteId); + } +}