From 5963631c7083af4674716808d31fcc60710d8156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 22:25:52 +0100 Subject: [PATCH 1/9] Fix site tests --- docker-compose.yml | 2 +- tests/e2e/Services/Sites/SitesCustomServerTest.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1cf0af5078..e0a8b8ab3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -962,7 +962,7 @@ services: hostname: exc1 <<: *x-logging stop_signal: SIGINT - image: openruntimes/executor:0.7.8 + image: openruntimes/executor:0.7.9 restart: unless-stopped networks: - appwrite diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 07a9fcf46b..c5d38c1897 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2068,6 +2068,14 @@ class SitesCustomServerTest extends Scope $domain = $this->getDeploymentDomain($deploymentId); $this->assertNotEmpty($domain); + + // Create second deployment to make first one a preview + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId); + $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); From 1a271ff272dc1ddad6ca79d888d119b274b50530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 22:26:04 +0100 Subject: [PATCH 2/9] Temporarly simplify CI/CD --- .github/workflows/tests.yml | 265 +----------------------------------- 1 file changed, 2 insertions(+), 263 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f7c5fe7c5c..5313ff392b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,30 +11,6 @@ env: on: [ pull_request ] jobs: - check_database_changes: - name: Check if utopia-php/database changed - runs-on: ubuntu-latest - outputs: - database_changed: ${{ steps.check.outputs.database_changed }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Fetch base branch - run: git fetch origin ${{ github.event.pull_request.base.ref }} - - - name: Check for utopia-php/database changes - id: check - run: | - if git diff origin/${{ github.event.pull_request.base.ref }} HEAD -- composer.lock | grep -q '"name": "utopia-php/database"'; then - echo "Database version changed, going to run all mode tests." - echo "database_changed=true" >> "$GITHUB_ENV" - echo "database_changed=true" >> "$GITHUB_OUTPUT" - else - echo "database_changed=false" >> "$GITHUB_ENV" - echo "database_changed=false" >> "$GITHUB_OUTPUT" - fi - setup: name: Setup & Build Appwrite Image runs-on: ubuntu-latest @@ -68,64 +44,6 @@ jobs: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar - unit_test: - name: Unit Test - runs-on: ubuntu-latest - needs: setup - - steps: - - name: checkout - uses: actions/checkout@v4 - - - name: Load Cache - uses: actions/cache@v4 - with: - key: ${{ env.CACHE_KEY }} - path: /tmp/${{ env.IMAGE }}.tar - fail-on-cache-miss: true - - - name: Load and Start Appwrite - run: | - docker load --input /tmp/${{ env.IMAGE }}.tar - docker compose up -d - sleep 10 - - - name: Logs - run: docker compose logs appwrite - - - name: Doctor - run: docker compose exec -T appwrite doctor - - - name: Environment Variables - run: docker compose exec -T appwrite vars - - - name: Run Unit Tests - run: docker compose exec appwrite test /usr/src/code/tests/unit - - e2e_general_test: - name: E2E General Test - runs-on: ubuntu-latest - needs: setup - steps: - - name: checkout - uses: actions/checkout@v4 - - - name: Load Cache - uses: actions/cache@v4 - with: - key: ${{ env.CACHE_KEY }} - path: /tmp/${{ env.IMAGE }}.tar - fail-on-cache-miss: true - - - name: Load and Start Appwrite - run: | - docker load --input /tmp/${{ env.IMAGE }}.tar - docker compose up -d - sleep 10 - - - name: Run General Tests - run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug - e2e_service_test: name: E2E Service Test runs-on: ubuntu-latest @@ -134,26 +52,7 @@ jobs: fail-fast: false matrix: service: [ - Account, - Avatars, - Console, - Databases, - Functions, - FunctionsSchedule, - GraphQL, - Health, - Locale, - Projects, - Realtime, - Sites, - Proxy, - Storage, - Teams, - Users, - Webhooks, - VCS, - Messaging, - Migrations + Sites ] steps: - name: Checkout repository @@ -189,164 +88,4 @@ jobs: docker compose exec -T \ -e _APP_DATABASE_SHARED_TABLES \ -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug - - e2e_shared_mode_test: - name: E2E Shared Mode Service Test - runs-on: ubuntu-latest - needs: [ setup, check_database_changes ] - if: needs.check_database_changes.outputs.database_changed == 'true' - strategy: - fail-fast: false - matrix: - service: - [ - Account, - Avatars, - Console, - Databases, - Functions, - FunctionsSchedule, - GraphQL, - Health, - Locale, - Projects, - Realtime, - Sites, - Proxy, - Storage, - Teams, - Users, - Webhooks, - VCS, - Messaging, - Migrations - ] - tables-mode: [ - 'Shared V1', - 'Shared V2', - ] - - steps: - - name: checkout - uses: actions/checkout@v4 - - - name: Load Cache - uses: actions/cache@v4 - with: - key: ${{ env.CACHE_KEY }} - path: /tmp/${{ env.IMAGE }}.tar - fail-on-cache-miss: true - - - name: Load and Start Appwrite - run: | - docker load --input /tmp/${{ env.IMAGE }}.tar - docker compose up -d - sleep 30 - - - name: Wait for Open Runtimes - timeout-minutes: 3 - run: | - while ! docker compose logs openruntimes-executor | grep -q "Executor is ready."; do - echo "Waiting for Executor to come online" - sleep 1 - done - - - name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode - run: | - if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then - echo "Using shared tables V1" - export _APP_DATABASE_SHARED_TABLES=database_db_main - export _APP_DATABASE_SHARED_TABLES_V1=database_db_main - elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then - echo "Using shared tables V2" - export _APP_DATABASE_SHARED_TABLES=database_db_main - export _APP_DATABASE_SHARED_TABLES_V1= - fi - - docker compose exec -T \ - -e _APP_DATABASE_SHARED_TABLES \ - -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug - - benchmarking: - name: Benchmark - runs-on: ubuntu-latest - needs: setup - permissions: - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Load Cache - uses: actions/cache@v4 - with: - key: ${{ env.CACHE_KEY }} - path: /tmp/${{ env.IMAGE }}.tar - fail-on-cache-miss: true - - name: Load and Start Appwrite - run: | - sed -i 's/traefik/localhost/g' .env - docker load --input /tmp/${{ env.IMAGE }}.tar - docker compose up -d - sleep 10 - - name: Install Oha - run: | - echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list - sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg - sudo apt update - sudo apt install oha - - name: Benchmark PR - run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json - - name: Cleaning - run: docker compose down -v - - name: Installing latest version - run: | - rm docker-compose.yml - rm .env - curl https://appwrite.io/install/compose -o docker-compose.yml - curl https://appwrite.io/install/env -o .env - sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env - docker compose up -d - sleep 10 - - name: Benchmark Latest - run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json - - name: Prepare comment - run: | - echo '## :sparkles: Benchmark results' > benchmark.txt - echo ' ' >> benchmark.txt - echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt - echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt - echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt - echo " " >> benchmark.txt - echo " " >> benchmark.txt - echo "## :zap: Benchmark Comparison" >> benchmark.txt - echo " " >> benchmark.txt - echo "| Metric | This PR | Latest version | " >> benchmark.txt - echo "| --- | --- | --- | " >> benchmark.txt - echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt - echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt - echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt - - name: Save results - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: benchmark.json - path: benchmark.json - retention-days: 7 - - name: Find Comment - if: github.event.pull_request.head.repo.full_name == github.repository - uses: peter-evans/find-comment@v3 - id: fc - with: - issue-number: ${{ github.event.pull_request.number }} - comment-author: 'github-actions[bot]' - body-includes: Benchmark results - - name: Comment on PR - if: github.event.pull_request.head.repo.full_name == github.repository - uses: peter-evans/create-or-update-comment@v4 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number }} - body-path: benchmark.txt - edit-mode: replace + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug \ No newline at end of file From 7e460f61550c03311459bf09ca7a3fef0bcc50ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 22:43:35 +0100 Subject: [PATCH 3/9] Simplify tests further --- .../Services/Sites/SitesCustomServerTest.php | 2016 ----------------- 1 file changed, 2016 deletions(-) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index c5d38c1897..afbfd383ca 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2,9 +2,6 @@ namespace Tests\E2E\Services\Sites; -use Ahc\Jwt\JWT; -use Appwrite\Platform\Modules\Compute\Specification; -use Appwrite\Tests\Retry; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; @@ -12,8 +9,6 @@ use Tests\E2E\Scopes\SideServer; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; -use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\System\System; class SitesCustomServerTest extends Scope { @@ -21,463 +16,6 @@ class SitesCustomServerTest extends Scope use ProjectCustom; use SideServer; - public function testListSpecs(): void - { - $specifications = $this->listSpecifications(); - $this->assertEquals(200, $specifications['headers']['status-code']); - $this->assertGreaterThan(0, $specifications['body']['total']); - $this->assertArrayHasKey(0, $specifications['body']['specifications']); - $this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]); - $this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]); - $this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]); - $this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]); - - $site = $this->createSite([ - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Specs site', - 'siteId' => ID::unique(), - 'specification' => $specifications['body']['specifications'][0]['slug'] - ]); - $this->assertEquals(201, $site['headers']['status-code']); - $this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']); - - $site = $this->getSite($site['body']['$id']); - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']); - - $this->cleanupSite($site['body']['$id']); - - $site = $this->createSite([ - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Specs site', - 'siteId' => ID::unique(), - 'specification' => 'cheap-please' - ]); - $this->assertEquals(400, $site['headers']['status-code']); - } - - public function testCreateSite(): void - { - /** - * Test for SUCCESS - */ - $site = $this->createSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'siteId' => ID::unique() - ]); - - $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('other', $site['body']['framework']); - $this->assertEquals(true, $dateValidator->isValid($site['body']['$createdAt'])); - $this->assertEquals(true, $dateValidator->isValid($site['body']['$updatedAt'])); - $this->assertEquals('node-22', $site['body']['buildRuntime']); - $this->assertEquals(null, $site['body']['fallbackFile']); - $this->assertEquals('./', $site['body']['outputDirectory']); - - $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 testConsoleAvailabilityEndpoint(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Test Site', - 'framework' => 'other', - 'buildRuntime' => 'node-22', - 'outputDirectory' => './', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - - $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ], [ - 'type' => 'rules', - 'value' => $domain, - ]); - - $this->assertEquals(409, $response['headers']['status-code']); // domain unavailable - - $nonExistingDomain = "non-existent-subdomain.sites.localhost"; - - $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ], [ - 'type' => 'rules', - 'value' => $nonExistingDomain, - ]); - - $this->assertEquals(204, $response['headers']['status-code']); // domain available - - $this->cleanupSite($siteId); - - $this->assertEventually(function () use ($siteId) { - $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('deploymentResourceId', [$siteId]) - ] - ]); - - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals(0, $rule['body']['total']); - }, 5000, 500); - - $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => 'a_session_console=' . $this->getRoot()['session'], - 'x-appwrite-project' => 'console', - ], [ - 'type' => 'rules', - 'value' => $domain, - ]); - - $this->assertEquals(204, $response['headers']['status-code']); // domain available as site is deleted - } - - public function testVariables(): void - { - $site = $this->createSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'siteId' => ID::unique() - ]); - - $siteId = $site['body']['$id'] ?? ''; - - $this->assertEquals(201, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - $this->assertEquals('Test Site', $site['body']['name']); - - $variable = $this->createVariable($siteId, [ - 'key' => 'siteKey1', - 'value' => 'siteValue1', - 'secret' => false, - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertNotEmpty($variable['body']['$id']); - $this->assertEquals('siteKey1', $variable['body']['key']); - $this->assertEquals('siteValue1', $variable['body']['value']); - $this->assertEquals(false, $variable['body']['secret']); - - $variable2 = $this->createVariable($siteId, [ - 'key' => 'siteKey2', - 'value' => 'siteValue2', - 'secret' => false, - ]); - - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertNotEmpty($variable2['body']['$id']); - $this->assertEquals('siteKey2', $variable2['body']['key']); - $this->assertEquals('siteValue2', $variable2['body']['value']); - $this->assertEquals(false, $variable2['body']['secret']); - - $secretVariable = $this->createVariable($siteId, [ - 'key' => 'siteKey3', - 'value' => 'siteValue3', - 'secret' => true, - ]); - - $this->assertEquals(201, $secretVariable['headers']['status-code']); - $this->assertNotEmpty($secretVariable['body']['$id']); - $this->assertEquals('siteKey3', $secretVariable['body']['key']); - $this->assertEquals('', $secretVariable['body']['value']); - $this->assertEquals(true, $secretVariable['body']['secret']); - - $variable = $this->getVariable($siteId, $variable['body']['$id']); - - $this->assertEquals(200, $variable['headers']['status-code']); - $this->assertNotEmpty($variable['body']['$id']); - $this->assertEquals('siteKey1', $variable['body']['key']); - $this->assertEquals('siteValue1', $variable['body']['value']); - $this->assertEquals(false, $variable['body']['secret']); - - $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); - - $this->assertEquals(200, $secretVariable['headers']['status-code']); - $this->assertNotEmpty($secretVariable['body']['$id']); - $this->assertEquals('siteKey3', $secretVariable['body']['key']); - $this->assertEquals('', $secretVariable['body']['value']); - $this->assertEquals(true, $secretVariable['body']['secret']); - - $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ - 'key' => 'siteKey1Updated', - 'value' => 'siteValue1Updated', - ]); - - $this->assertEquals(200, $variable['headers']['status-code']); - $this->assertNotEmpty($variable['body']['$id']); - $this->assertEquals('siteKey1Updated', $variable['body']['key']); - $this->assertEquals('siteValue1Updated', $variable['body']['value']); - $this->assertEquals(false, $variable['body']['secret']); - - $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ - 'key' => 'siteKey1Updated', - 'secret' => true, - ]); - - $this->assertEquals(200, $variable['headers']['status-code']); - $this->assertNotEmpty($variable['body']['$id']); - $this->assertEquals('siteKey1Updated', $variable['body']['key']); - $this->assertEquals('', $variable['body']['value']); - $this->assertEquals(true, $variable['body']['secret']); - - $secretVariable = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ - 'key' => 'siteKey3', - 'value' => 'siteValue3Updated', - ]); - - $this->assertEquals(200, $secretVariable['headers']['status-code']); - $this->assertNotEmpty($secretVariable['body']['$id']); - $this->assertEquals('siteKey3', $secretVariable['body']['key']); - $this->assertEquals('', $secretVariable['body']['value']); - $this->assertEquals(true, $secretVariable['body']['secret']); - - $response = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ - 'key' => 'siteKey3', - 'secret' => false, - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); - - $this->assertEquals(200, $secretVariable['headers']['status-code']); - $this->assertNotEmpty($secretVariable['body']['$id']); - $this->assertEquals('siteKey3', $secretVariable['body']['key']); - $this->assertEquals('', $secretVariable['body']['value']); - $this->assertEquals(true, $secretVariable['body']['secret']); - - $variables = $this->listVariables($siteId); - - $this->assertEquals(200, $variables['headers']['status-code']); - $this->assertCount(3, $variables['body']['variables']); - - $response = $this->deleteVariable($siteId, $variable['body']['$id']); - $this->assertEquals(204, $response['headers']['status-code']); - $response = $this->deleteVariable($siteId, $variable2['body']['$id']); - $this->assertEquals(204, $response['headers']['status-code']); - $response = $this->deleteVariable($siteId, $secretVariable['body']['$id']); - $this->assertEquals(204, $response['headers']['status-code']); - - $variables = $this->listVariables($siteId); - - $this->assertEquals(200, $variables['headers']['status-code']); - $this->assertCount(0, $variables['body']['variables']); - - $this->cleanupSite($siteId); - } - - // This is first Sites test with Proxy - // If this fails, it may not be related to variables; but Router flow failing - public function testVariablesE2E(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Astro site', - 'framework' => 'astro', - 'adapter' => 'ssr', - 'buildRuntime' => 'node-22', - 'outputDirectory' => './dist', - 'buildCommand' => 'npm run build', - 'installCommand' => 'npm install', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - - $secretVariable = $this->createVariable($siteId, [ - 'key' => 'name', - 'value' => 'Appwrite', - ]); - - $this->assertEquals(201, $secretVariable['headers']['status-code']); - $this->assertNotEmpty($secretVariable['body']['$id']); - $this->assertEquals('name', $secretVariable['body']['key']); - $this->assertEquals('', $secretVariable['body']['value']); - $this->assertEquals(true, $secretVariable['body']['secret']); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('astro'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->getSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Env variable is Appwrite", $response['body']); - $this->assertStringNotContainsString("Variable not found", $response['body']); - - $this->cleanupSite($siteId); - } - - public function testAdapterDetectionAstroSSR(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Astro SSR site', - 'framework' => 'astro', - 'buildRuntime' => 'node-22', - 'outputDirectory' => './dist', - 'buildCommand' => 'npm run build', - 'installCommand' => 'npm install', - ]); - $this->assertNotEmpty($siteId); - - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - $this->assertArrayHasKey('adapter', $site['body']); - $this->assertEmpty($site['body']['adapter']); - - $domain = $this->setupSiteDomain($siteId); - $this->assertNotEmpty($domain); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('astro'), - 'activate' => 'true' - ]); - $this->assertNotEmpty($deploymentId); - - $site = $this->getSite($siteId); - $this->assertEquals('ssr', $site['body']['adapter']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - - $this->cleanupSite($siteId); - } - - public function testAdapterDetectionAstroStatic(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Astro static site', - 'framework' => 'astro', - 'buildRuntime' => 'node-22', - 'outputDirectory' => './dist', - 'buildCommand' => 'npm run build', - 'installCommand' => 'npm install', - ]); - $this->assertNotEmpty($siteId); - - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - $this->assertArrayHasKey('adapter', $site['body']); - $this->assertEmpty($site['body']['adapter']); - - $domain = $this->setupSiteDomain($siteId); - $this->assertNotEmpty($domain); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('astro-static'), - 'activate' => 'true' - ]); - $this->assertNotEmpty($deploymentId); - - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - $this->assertEquals('static', $site['body']['adapter']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - - $this->cleanupSite($siteId); - } - - public function testAdapterDetectionStatic(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Static site', - 'framework' => 'other', - 'buildRuntime' => 'node-22', - 'outputDirectory' => '', - 'buildCommand' => '', - 'installCommand' => '', - ]); - $this->assertNotEmpty($siteId); - - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - $this->assertArrayHasKey('adapter', $site['body']); - $this->assertEmpty($site['body']['adapter']); - - $domain = $this->setupSiteDomain($siteId); - $this->assertNotEmpty($domain); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'true' - ]); - $this->assertNotEmpty($deploymentId); - - $site = $this->getSite($siteId); - $this->assertEquals('200', $site['headers']['status-code']); - $this->assertEquals('static', $site['body']['adapter']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - - $this->cleanupSite($siteId); - } public function testAdapterDetectionStaticSPA(): void { @@ -663,1558 +201,4 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); $this->cleanupSite($siteId2); } - - public function testGetSite(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $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); - } - - public function testUpdateSite(): void - { - $site = $this->createSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $siteId = $site['body']['$id'] ?? ''; - - $this->assertEquals(201, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - $this->assertEquals('Test Site', $site['body']['name']); - - $site = $this->updateSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site Updated', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - '$id' => $siteId, - 'installCommand' => 'npm install' - ]); - - $dateValidator = new DatetimeValidator(); - - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - $this->assertEquals('Test Site Updated', $site['body']['name']); - $this->assertEquals(true, $dateValidator->isValid($site['body']['$createdAt'])); - $this->assertEquals(true, $dateValidator->isValid($site['body']['$updatedAt'])); - $this->assertEquals('npm install', $site['body']['installCommand']); - - $this->cleanupSite($siteId); - } - - // public function testCreateDeploymentFromCLI() { - // // TODO: Implement testCreateDeploymentFromCLI() later - // } - - public function testCreateSiteAndDeploymentFromTemplate() - { - $starterTemplate = $this->getTemplate('nextjs-starter'); - $this->assertEquals(200, $starterTemplate['headers']['status-code']); - - $nextjsFramework = array_values(array_filter($starterTemplate['body']['frameworks'], function ($framework) { - return $framework['key'] === 'nextjs'; - }))[0]; - - // If this fails, the template has variables, and this test needs to be updated - $this->assertEmpty($starterTemplate['body']['variables']); - - $site = $this->createSite( - [ - 'siteId' => ID::unique(), - 'name' => $starterTemplate['body']['name'], - 'framework' => $nextjsFramework['key'], - 'adapter' => $nextjsFramework['adapter'], - 'buildCommand' => $nextjsFramework['buildCommand'], - 'buildRuntime' => $nextjsFramework['buildRuntime'], - 'fallbackFile' => $nextjsFramework['fallbackFile'], - 'installCommand' => $nextjsFramework['installCommand'], - 'outputDirectory' => $nextjsFramework['outputDirectory'], - 'providerRootDirectory' => $nextjsFramework['providerRootDirectory'], - ] - ); - - $this->assertEquals(201, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - - $siteId = $site['body']['$id'] ?? ''; - - $deployment = $this->createTemplateDeployment( - $siteId, - [ - 'owner' => $starterTemplate['body']['providerOwner'], - 'repository' => $starterTemplate['body']['providerRepositoryId'], - 'rootDirectory' => $nextjsFramework['providerRootDirectory'], - 'version' => $starterTemplate['body']['providerVersion'], - 'activate' => true, - ] - ); - - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - - $deployments = $this->listDeployments($siteId); - - $this->assertEquals(200, $deployments['headers']['status-code']); - $this->assertEquals(1, $deployments['body']['total']); - - $lastDeployment = $deployments['body']['deployments'][0]; - - $this->assertNotEmpty($lastDeployment['$id']); - $this->assertEquals(0, $lastDeployment['sourceSize']); - - $deploymentId = $lastDeployment['$id']; - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('ready', $deployment['body']['status']); - }, 300000, 1000); - - $site = $this->getSite($siteId); - $this->assertEquals(200, $site['headers']['status-code']); - - $this->cleanupSite($siteId); - } - - public function testCreateDeployment() - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'siteId' => $siteId, - 'code' => $this->packageSite('static'), - 'activate' => true, - ]); - - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - - $deploymentIdActive = $deployment['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($siteId, $deploymentIdActive) { - $deployment = $this->getDeployment($siteId, $deploymentIdActive); - - $this->assertEquals('ready', $deployment['body']['status']); - }, 50000, 500); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - - $deploymentIdInactive = $deployment['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($siteId, $deploymentIdInactive) { - $deployment = $this->getDeployment($siteId, $deploymentIdInactive); - - $this->assertEquals('ready', $deployment['body']['status']); - }, 50000, 500); - - $site = $this->getSite($siteId); - - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertEquals($deploymentIdActive, $site['body']['deploymentId']); - $this->assertNotEquals($deploymentIdInactive, $site['body']['deploymentId']); - - $this->cleanupDeployment($siteId, $deploymentIdActive); - $this->cleanupDeployment($siteId, $deploymentIdInactive); - $this->cleanupSite($siteId); - } - - #[Retry(count: 3)] - public function testCancelDeploymentBuild(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('building', $deployment['body']['status']); - }, 100000, 250); - - $deployment = $this->cancelDeployment($siteId, $deploymentId); - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('canceled', $deployment['body']['status']); - - /** - * Build worker still runs the build. - * 30s sleep gives worker enough time to finish build. - * After build finished, it should still be canceled, not ready. - */ - \sleep(30); - - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('canceled', $deployment['body']['status']); - - $this->cleanupDeployment($siteId, $deploymentId); - $this->cleanupSite($siteId); - } - - public function testUpdateDeployment(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals('ready', $deployment['body']['status']); - }, 50000, 500); - - /** - * Test for SUCCESS - */ - $dateValidator = new DatetimeValidator(); - - $response = $this->updateSiteDeployment($siteId, $deploymentId); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt'])); - $this->assertEquals(true, $dateValidator->isValid($response['body']['$updatedAt'])); - $this->assertEquals($deploymentId, $response['body']['deploymentId']); - - $this->cleanupDeployment($siteId, $deploymentId); - $this->cleanupSite($siteId); - } - - public function testListDeployments(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $deploymentIdActive = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - - $deploymentIdInactive = $deployment['body']['$id'] ?? ''; - - $deployments = $this->listDeployments($siteId); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals($deployments['body']['total'], 2); - $this->assertIsArray($deployments['body']['deployments']); - $this->assertCount(2, $deployments['body']['deployments']); - $this->assertArrayHasKey('sourceSize', $deployments['body']['deployments'][0]); - $this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]); - - $deployments = $this->listDeployments($siteId, [ - 'queries' => [ - Query::limit(1)->toString(), - ], - ]); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertCount(1, $deployments['body']['deployments']); - - $deployments = $this->listDeployments($siteId, [ - 'queries' => [ - Query::offset(1)->toString(), - ], - ]); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertCount(1, $deployments['body']['deployments']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::equal('type', ['manual'])->toString(), - ], - ] - ); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(2, $deployments['body']['total']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::equal('type', ['vcs'])->toString(), - ], - ] - ); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(0, $deployments['body']['total']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::equal('type', ['invalid-string'])->toString(), - ], - ] - ); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(0, $deployments['body']['total']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::greaterThan('sourceSize', 10000)->toString(), - ], - ] - ); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(0, $deployments['body']['total']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::greaterThan('sourceSize', 0)->toString(), - ], - ] - ); - - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(2, $deployments['body']['total']); - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::greaterThan('sourceSize', -100)->toString(), - ], - ] - ); - $this->assertEquals($deployments['headers']['status-code'], 200); - $this->assertEquals(2, $deployments['body']['total']); - - /** - * Ensure size output and size filters work exactly. - * Prevents buildSize being counted towards deployment size - */ - $deployments = $this->listDeployments( - $siteId, - [ - Query::limit(1)->toString(), - ] - ); - - $this->assertEquals(200, $deployments['headers']['status-code']); - $this->assertGreaterThanOrEqual(1, $deployments['body']['total']); - $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); - $this->assertNotEmpty($deployments['body']['deployments'][0]['sourceSize']); - - $deploymentId = $deployments['body']['deployments'][0]['$id']; - $deploymentSize = $deployments['body']['deployments'][0]['sourceSize']; - - $deployments = $this->listDeployments( - $siteId, - [ - 'queries' => [ - Query::equal('sourceSize', [$deploymentSize])->toString(), - ], - ] - ); - - $this->assertEquals(200, $deployments['headers']['status-code']); - $this->assertGreaterThan(0, $deployments['body']['total']); - - $matchingDeployment = array_filter( - $deployments['body']['deployments'], - fn ($deployment) => $deployment['$id'] === $deploymentId - ); - - $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); - - if (!empty($matchingDeployment)) { - $deployment = reset($matchingDeployment); - $this->assertEquals($deploymentSize, $deployment['sourceSize']); - } - - $this->cleanupDeployment($siteId, $deploymentIdActive); - $this->cleanupDeployment($siteId, $deploymentIdInactive); - $this->cleanupSite($siteId); - } - - public function testGetDeployment(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals('ready', $deployment['body']['status']); - }, 50000, 500); - - /** - * Test for SUCCESS - */ - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertGreaterThan(0, $deployment['body']['buildDuration']); - $this->assertNotEmpty($deployment['body']['status']); - $this->assertNotEmpty($deployment['body']['buildLogs']); - $this->assertArrayHasKey('sourceSize', $deployment['body']); - $this->assertArrayHasKey('buildSize', $deployment['body']); - - /** - * Test for FAILURE - */ - $deployment = $this->getDeployment($siteId, 'x'); - - $this->assertEquals($deployment['headers']['status-code'], 404); - - $this->cleanupDeployment($siteId, $deploymentId); - $this->cleanupSite($siteId); - } - - public function testUpdateSpecs(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - /** - * Test for SUCCESS - */ - // Change the function specs - $site = $this->updateSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - '$id' => $siteId, - 'specification' => Specification::S_1VCPU_1GB, - ]); - - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - $this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['specification']); - - // Change the specs to 1vcpu 512mb - $site = $this->updateSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - '$id' => $siteId, - 'specification' => Specification::S_1VCPU_512MB, - ]); - - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - $this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['specification']); - - /** - * Test for FAILURE - */ - - $site = $this->updateSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - '$id' => $siteId, - 'specification' => 's-2vcpu-512mb', // Invalid specification - ]); - - $this->assertEquals(400, $site['headers']['status-code']); - $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $site['body']['message']); - - $this->cleanupSite($siteId); - } - - public function testDeleteDeployment(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'false' - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals('ready', $deployment['body']['status']); - }, 50000, 500); - - /** - * Test for SUCCESS - */ - $deployment = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId . '/deployments/' . $deploymentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(204, $deployment['headers']['status-code']); - $this->assertEmpty($deployment['body']); - - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(404, $deployment['headers']['status-code']); - } - - public function testDeleteSite(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $this->assertNotNull($siteId); - - $site = $this->deleteSite($siteId); - - $this->assertEquals(204, $site['headers']['status-code']); - $this->assertEmpty($site['body']); - - $function = $this->getSite($siteId); - - $this->assertEquals(404, $function['headers']['status-code']); - } - - public function testGetFrameworks(): void - { - $frameworks = $this->client->call(Client::METHOD_GET, '/sites/frameworks', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $frameworks['headers']['status-code']); - $this->assertGreaterThan(0, $frameworks['body']['total']); - - $framework = $frameworks['body']['frameworks'][0]; - - $this->assertArrayHasKey('name', $framework); - $this->assertArrayHasKey('key', $framework); - $this->assertArrayHasKey('buildRuntime', $framework); - $this->assertArrayHasKey('runtimes', $framework); - $this->assertArrayHasKey('adapters', $framework); - } - - public function testSiteStatic(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Non-SPA site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static-spa'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->setupSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Index page", $response['body']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Contact page", $response['body']); - - $response = $proxyClient->call(Client::METHOD_GET, '/non-existing', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString("Page not found", $response['body']); // Title - $this->assertStringContainsString("Go to homepage", $response['body']); // Button - $this->assertStringContainsString("Powered by", $response['body']); // Brand - - $this->cleanupSite($siteId); - } - - public function testSiteStaticSPA(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'SPA site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '404.html', - ]); - - $this->assertNotEmpty($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static-spa'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->setupSiteDomain($siteId); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Index page", $response['body']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Contact page", $response['body']); - - $response = $proxyClient->call(Client::METHOD_GET, '/non-existing', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ])); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Customized 404 page", $response['body']); - $this->assertStringNotContainsString("Powered by", $response['body']); // Brand - - $this->cleanupSite($siteId); - } - - public function testSiteTemplate(): void - { - $template = $this->getTemplate('astro-starter'); - $this->assertEquals(200, $template['headers']['status-code']); - - $template = $template['body']; - - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Template site', - 'framework' => $template['frameworks'][0]['key'], - 'adapter' => $template['frameworks'][0]['adapter'], - 'buildRuntime' => $template['frameworks'][0]['buildRuntime'], - 'outputDirectory' => $template['frameworks'][0]['outputDirectory'], - 'buildCommand' => $template['frameworks'][0]['buildCommand'], - 'installCommand' => $template['frameworks'][0]['installCommand'], - 'fallbackFile' => $template['frameworks'][0]['fallbackFile'], - ]); - - $this->assertNotEmpty($siteId); - - $deployment = $this->createTemplateDeployment($siteId, [ - 'repository' => $template['providerRepositoryId'], - 'owner' => $template['providerOwner'], - 'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'], - 'version' => $template['providerVersion'], - 'activate' => true - ]); - - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['$id']); - - $this->assertEventually(function () use ($siteId) { - $site = $this->getSite($siteId); - $this->assertNotEmpty($site['body']['deploymentId']); - }, 50000, 500); - - $domain = $this->setupSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Astro Blog", $response['body']); - $this->assertStringContainsString("Hello, Astronaut!", $response['body']); - - $response = $proxyClient->call(Client::METHOD_GET, '/about'); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Astro Blog", $response['body']); - $this->assertStringContainsString("About Me", $response['body']); - - $this->cleanupSite($siteId); - } - - public function testSiteDomainReclaiming(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Startup site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $subdomain = 'startup' . \uniqid(); - $domain = $this->setupSiteDomain($siteId, $subdomain); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->getSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']); - - $site2 = $this->createSite([ - 'siteId' => ID::unique(), - 'name' => 'Startup 2 site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $siteId2 = $site2['body']['$id']; - - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''), - 'siteId' => $siteId2, - ]); - - $this->assertEquals(409, $rule['headers']['status-code']); - - $this->cleanupSite($siteId); - - $this->assertEventually(function () use ($domain) { - $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('domain', [$domain])->toString(), - ], - ]); - - $this->assertEquals(200, $rules['headers']['status-code']); - $this->assertEquals(0, $rules['body']['total']); - }, 50000, 500); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(401, $response['headers']['status-code']); - $this->assertStringContainsString("This domain is not connected to any Appwrite resource yet", $response['body']); - - $site = $this->createSite([ - 'siteId' => ID::unique(), - 'name' => 'Startup 2 site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $this->assertEquals(201, $site['headers']['status-code']); - $this->assertNotEmpty($site['body']['$id']); - - $siteId = $site['body']['$id']; - - $domain = $this->setupSiteDomain($siteId, $subdomain); - - $this->assertNotEmpty($domain); - - $this->cleanupSite($site['body']['$id']); - } - - public function testSitePreviewBranding(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'A site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'true' - ]); - $this->assertNotEmpty($deploymentId); - - $oldDeploymentDomain = $this->getDeploymentDomain($deploymentId); - $this->assertNotEmpty($oldDeploymentDomain); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => 'true' - ]); - $this->assertNotEmpty($deploymentId); - - $newDeploymentDomain = $this->getDeploymentDomain($deploymentId); - $this->assertNotEmpty($newDeploymentDomain); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $newDeploymentDomain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Hello Appwrite", $response['body']); - $this->assertStringNotContainsString("Preview by", $response['body']); - $contentLength = $response['headers']['content-length']; - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $oldDeploymentDomain); - $response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); - - $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0); - $apiKey = $jwtObj->encode([ - 'projectCheckDisabled' => true, - 'previewAuthDisabled' => true, - ]); - $response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false, headers: [ - 'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey, - ]); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Hello Appwrite", $response['body']); - $this->assertStringContainsString("Preview by", $response['body']); - $this->assertGreaterThan($contentLength, $response['headers']['content-length']); - - $this->cleanupSite($siteId); - } - - public function testSiteCors(): void - { - // Create rule together with site - $subdomain = 'startup' . \uniqid(); - - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Startup site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - 'subdomain' => $subdomain - ]); - - $this->assertNotEmpty($siteId); - - $this->setupSiteDomain($siteId, $subdomain); - $domain = $this->getSiteDomain($siteId); - - $this->assertNotEmpty($domain); - - $url = 'http://' . $domain; - - $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'referer' => $url, - 'origin' => $url - ])); - - $this->assertEquals($url, $response['headers']['access-control-allow-origin']); - - $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => 'unknown', - 'referer' => $url, - 'origin' => $url - ])); - - $this->assertNotEquals($url, $response['headers']['access-control-allow-origin']); - $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); - - $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'referer' => 'http://unknown.com', - 'origin' => 'http://unknown.com' - ])); - - $this->assertNotEquals($url, $response['headers']['access-control-allow-origin']); - $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); - } - - public function testSiteScreenshot(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'Themed site', - 'framework' => 'other', - 'adapter' => 'static', - 'buildRuntime' => 'static-1', - 'outputDirectory' => './', - 'buildCommand' => '', - 'installCommand' => '', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static-themed'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->getSiteDomain($siteId); - $this->assertNotEmpty($domain); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Themed website", $response['body']); - $this->assertStringContainsString("@media (prefers-color-scheme: dark)", $response['body']); - - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertNotEmpty($deployment['body']['screenshotLight']); - $this->assertNotEmpty($deployment['body']['screenshotDark']); - - $screenshotId = $deployment['body']['screenshotLight']; - $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([ - ], $this->getHeaders())); - - $this->assertEquals(200, $file['headers']['status-code']); - $this->assertNotEmpty(200, $file['body']); - $this->assertGreaterThan(1, $file['headers']['content-length']); - $this->assertEquals('image/png', $file['headers']['content-type']); - - $screenshotHash = \md5($file['body']); - $this->assertNotEmpty($screenshotHash); - - $screenshotId = $deployment['body']['screenshotDark']; - $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([ - ], $this->getHeaders())); - - $this->assertEquals(200, $file['headers']['status-code']); - $this->assertNotEmpty(200, $file['body']); - $this->assertGreaterThan(1, $file['headers']['content-length']); - $this->assertEquals('image/png', $file['headers']['content-type']); - - $screenshotDarkHash = \md5($file['body']); - $this->assertNotEmpty($screenshotDarkHash); - - $this->assertNotEquals($screenshotDarkHash, $screenshotHash); - - $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin"); - $this->assertEquals(404, $file['headers']['status-code']); - - $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin"); - $this->assertEquals(404, $file['headers']['status-code']); - - $this->cleanupSite($siteId); - } - - public function testSiteDownload(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'fallbackFile' => '', - 'framework' => 'other', - 'name' => 'Test Site', - 'adapter' => 'static', - 'outputDirectory' => './', - 'providerBranch' => 'main', - 'providerRootDirectory' => './', - 'siteId' => ID::unique() - ]); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => true - ]); - - $this->assertNotEmpty($deploymentId); - - $response = $this->getDeploymentDownload($siteId, $deploymentId, 'source'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('application/gzip', $response['headers']['content-type']); - $this->assertGreaterThan(0, $response['headers']['content-length']); - $this->assertGreaterThan(0, \strlen($response['body'])); - - $deploymentMd5 = \md5($response['body']); - - $response = $this->getDeploymentDownload($siteId, $deploymentId, 'output'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals('application/gzip', $response['headers']['content-type']); - $this->assertGreaterThan(0, $response['headers']['content-length']); - $this->assertGreaterThan(0, \strlen($response['body'])); - - $buildMd5 = \md5($response['body']); - - $this->assertNotEquals($deploymentMd5, $buildMd5); - - $this->cleanupSite($siteId); - } - - public function testSSRLogs(): void - { - $siteId = $this->setupSite([ - 'siteId' => ID::unique(), - 'name' => 'SSR site', - 'framework' => 'astro', - 'adapter' => 'ssr', - 'buildRuntime' => 'node-22', - 'outputDirectory' => './dist', - 'buildCommand' => 'npm run build', - 'installCommand' => 'npm install', - 'fallbackFile' => '', - ]); - - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('astro'), - 'activate' => 'true' - ]); - - $this->assertNotEmpty($deploymentId); - - $domain = $this->getSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/logs-inline'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Inline logs printed.", $response['body']); - - $logs = $this->listLogs($siteId, [ - Query::orderDesc('$createdAt')->toString(), - Query::limit(1)->toString(), - ]); - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']); - $this->assertStringContainsString("/logs-inline", $logs['body']['executions'][0]['requestPath']); - $this->assertStringContainsString("Log1", $logs['body']['executions'][0]['logs']); - $this->assertStringContainsString("Log2", $logs['body']['executions'][0]['logs']); - $this->assertStringContainsString("Error1", $logs['body']['executions'][0]['errors']); - $this->assertStringContainsString("Error2", $logs['body']['executions'][0]['errors']); - $log1Id = $logs['body']['executions'][0]['$id']; - $this->assertNotEmpty($log1Id); - - $response = $proxyClient->call(Client::METHOD_GET, '/logs-action'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Action logs printed.", $response['body']); - - $logs = $this->listLogs($siteId, [ - Query::orderDesc('$createdAt')->toString(), - Query::limit(1)->toString(), - ]); - $this->assertEquals(200, $logs['headers']['status-code']); - $this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']); - $this->assertStringContainsString("/logs-action", $logs['body']['executions'][0]['requestPath']); - $this->assertStringContainsString("Log1", $logs['body']['executions'][0]['logs']); - $this->assertStringContainsString("Log2", $logs['body']['executions'][0]['logs']); - $this->assertStringContainsString("Error1", $logs['body']['executions'][0]['errors']); - $this->assertStringContainsString("Error2", $logs['body']['executions'][0]['errors']); - $log2Id = $logs['body']['executions'][0]['$id']; - $this->assertNotEmpty($log2Id); - - $this->assertNotEquals($log1Id, $log2Id); - - $this->cleanupSite($siteId); - } - - public function testDuplicateDeployment(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Duplicate deployment Site', - 'adapter' => 'static', - 'fallbackFile' => '404.html', - 'siteId' => ID::unique() - ]); - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - $this->assertNotEmpty($domain); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $deploymentId1 = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static-spa'), - 'activate' => true - ]); - $this->assertNotEmpty($deploymentId1); - - $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); - $this->assertStringContainsString("Customized 404 page", $response['body']); - - $site = $this->updateSite([ - '$id' => $siteId, - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Duplicate deployment Site', - 'adapter' => 'static', - 'fallbackFile' => 'index.html', - ]); - $this->assertEquals(200, $site['headers']['status-code']); - $this->assertEquals('index.html', $site['body']['fallbackFile']); - - $deploymentId2 = $this->setupDuplicateDeployment($siteId, $deploymentId1); - $this->assertNotEmpty($deploymentId2); - - $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); - $this->assertStringContainsString("Index page", $response['body']); - - $this->cleanupSite($siteId); - } - - public function testUpdateDeploymentStatus(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Activate test Site', - 'siteId' => ID::unique(), - 'adapter' => 'static', - ]); - $this->assertNotEmpty($siteId); - - $domain = $this->setupSiteDomain($siteId); - $this->assertNotEmpty($domain); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $deploymentId1 = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => true - ]); - $this->assertNotEmpty($deploymentId1); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString('Hello Appwrite', $response['body']); - - $deploymentId2 = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static-spa'), - 'activate' => true - ]); - $this->assertNotEmpty($deploymentId2); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString('Index page', $response['body']); - - $function = $this->getSite($siteId); - $this->assertEquals(200, $function['headers']['status-code']); - $this->assertEquals($deploymentId2, $function['body']['deploymentId']); - - $function = $this->updateSiteDeployment($siteId, $deploymentId1); - $this->assertEquals(200, $function['headers']['status-code']); - $this->assertEquals($deploymentId1, $function['body']['deploymentId']); - - $function = $this->getSite($siteId); - $this->assertEquals(200, $function['headers']['status-code']); - $this->assertEquals($deploymentId1, $function['body']['deploymentId']); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString('Hello Appwrite', $response['body']); - - $this->cleanupSite($siteId); - } - - public function testPreviewDomain(): void - { - $siteId = $this->setupSite([ - 'buildRuntime' => 'node-22', - 'framework' => 'other', - 'name' => 'Authorized preview site', - 'siteId' => ID::unique(), - 'adapter' => 'static', - ]); - $this->assertNotEmpty($siteId); - - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => true - ]); - $this->assertNotEmpty($deploymentId); - - $domain = $this->getDeploymentDomain($deploymentId); - $this->assertNotEmpty($domain); - - // Create second deployment to make first one a preview - $deploymentId = $this->setupDeployment($siteId, [ - 'code' => $this->packageSite('static'), - 'activate' => true - ]); - $this->assertNotEmpty($deploymentId); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); - $this->assertStringContainsString('projectId=' . $this->getProject()['$id'], $response['headers']['location']); - $this->assertStringContainsString('origin=', $response['headers']['location']); - $this->assertStringContainsString('path=%2Fcontact', $response['headers']['location']); - - $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => 'console', - ]), [ - 'email' => $this->getRoot()['email'], - 'password' => 'password' - ]); - $this->assertEquals(201, $session['headers']['status-code']); - $this->assertNotEmpty($session['cookies']['a_session_console']); - $this->assertNotEmpty($session['body']['$id']); - $cookie = 'a_session_console=' . $session['cookies']['a_session_console']; - - $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => $cookie, - 'x-appwrite-project' => 'console', - ]), []); - $this->assertEquals(201, $jwt['headers']['status-code']); - $this->assertNotEmpty($jwt['body']['jwt']); - - $response = $proxyClient->call(Client::METHOD_GET, '/_appwrite/authorize', params: [ - 'jwt' => $jwt['body']['jwt'], - 'path' => '/contact' - ], followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertArrayHasKey('set-cookie', $response['headers']); - $this->assertStringContainsString('a_jwt_console=', $response['headers']['set-cookie']); - $this->assertStringContainsString('httponly', $response['headers']['set-cookie']); - $this->assertStringContainsString('domain=' . $domain, $response['headers']['set-cookie']); - $this->assertStringContainsString('path=/', $response['headers']['set-cookie']); - $this->assertNotEmpty($response['cookies']['a_jwt_console']); - $this->assertEquals($jwt['body']['jwt'], $response['cookies']['a_jwt_console']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ - 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] - ], followRedirects: false); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Contact page", $response['body']); - $this->assertStringContainsString("Preview by", $response['body']); - - // Failure: Session missing (old bad, new ok) - $session = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => $cookie, - 'x-appwrite-project' => 'console', - ]), []); - $this->assertEquals(204, $session['headers']['status-code']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ - 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] - ], followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); - - // Failure: User missing - $cookie = 'a_session_console=' .$this->getRoot()['session']; - $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => $cookie, - 'x-appwrite-project' => 'console', - ]), []); - $this->assertEquals(201, $jwt['headers']['status-code']); - $this->assertNotEmpty($jwt['body']['jwt']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ - 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] - ], followRedirects: false); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertStringContainsString("Contact page", $response['body']); - $this->assertStringContainsString("Preview by", $response['body']); - - $user = $this->client->call(Client::METHOD_PATCH, '/account/status', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => $cookie, - 'x-appwrite-project' => 'console', - ]), []); - $this->assertEquals(200, $user['headers']['status-code']); - $this->assertFalse($user['body']['status']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ - 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] - ], followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); - - // Failure: Membership missing - $user = $this->client->call(Client::METHOD_POST, '/account', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => 'console', - ], [ - 'userId' => ID::unique(), - 'email' => 'newuser@appwrite.io', - 'password' => 'password' - ]); - $this->assertEquals(201, $user['headers']['status-code']); - - $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => 'console', - ], [ - 'email' => 'newuser@appwrite.io', - 'password' => 'password', - ]); - $this->assertEquals(201, $session['headers']['status-code']); - $this->assertNotEmpty($session['cookies']['a_session_console']); - $cookie = 'a_session_console=' . $session['cookies']['a_session_console']; - - $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'cookie' => $cookie, - 'x-appwrite-project' => 'console', - ]), []); - $this->assertEquals(201, $jwt['headers']['status-code']); - $this->assertNotEmpty($jwt['body']['jwt']); - - $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ - 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] - ], followRedirects: false); - $this->assertEquals(301, $response['headers']['status-code']); - $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); - - $this->cleanupSite($siteId); - } } From 65bcf7898eca5525d4c0c42d13c1e2ac128d0d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 22:56:16 +0100 Subject: [PATCH 4/9] Improve CI/CD logs --- .github/workflows/tests.yml | 4 ++- .../Modules/Functions/Workers/Builds.php | 28 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5313ff392b..6363c95e49 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,4 +88,6 @@ jobs: docker compose exec -T \ -e _APP_DATABASE_SHARED_TABLES \ -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug \ No newline at end of file + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + + docker compose logs appwrite-worker-builds \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 608c001b70..0dcb27a950 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -671,11 +671,16 @@ class Builds extends Action } }), ]); + + \var_dump("Finish"); if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') { Console::info('Build has been canceled'); return; } + + \var_dump("Problem"); + \var_dump($err); if ($err) { throw $err; @@ -686,33 +691,48 @@ class Builds extends Action $buildSizeLimit = (int)System::getEnv('_APP_COMPUTE_BUILD_SIZE_LIMIT', '2000000000'); if ($response['size'] > $buildSizeLimit) { + \var_dump("Limit issue"); throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.'); } - if ($resource->getCollection() === 'sites' && empty($resource->getAttribute('adapter'))) { + \var_dump($resource->getCollection() === 'sites'); + \var_dump($resource->getAttribute('adapter', '')); + if ($resource->getCollection() === 'sites' && empty($resource->getAttribute('adapter', ''))) { + \var_dump($resource->getAttribute('outputDirectory', './')); + \var_dump(\escapeshellarg($resource->getAttribute('outputDirectory', './'))); // TODO: Refactor with structured command in future, using utopia library (CLI) - $listFilesCommand = "cd /usr/local/build && cd " . \escapeshellarg($resource->getAttribute('outputDirectory')) . " && find . -name 'node_modules' -prune -o -type f -print"; + $listFilesCommand = "cd /usr/local/build && cd " . \escapeshellarg($resource->getAttribute('outputDirectory', './')) . " && find . -name 'node_modules' -prune -o -type f -print"; $command = $executor->createCommand( deploymentId: $deployment->getId(), projectId: $project->getId(), command: $listFilesCommand, timeout: 15 ); - + + \var_dump($command); + $files = \explode("\n", $command['output']); // Parse output $files = \array_filter($files); // Remove empty $files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces $files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./ + + \var_dump($files); $detector = new Rendering($files, $resource->getAttribute('framework', '')); $detector ->addOption(new SSR()) ->addOption(new XStatic()); $detection = $detector->detect(); - + + \var_dump($detection->getName()); + \var_dump($detection->getFallbackFile()); + $resource->setAttribute('adapter', $detection->getName()); $resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? ''); $resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource); + + \var_dump("Updated"); + \var_dump($resource->getAttribute('fallbackFile', '')); } $executor->deleteRuntime($project->getId(), $deployment->getId(), '-build'); From d21814c51fc3b1bcec0084b975143b7bf2ecd737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 23:04:32 +0100 Subject: [PATCH 5/9] Improve logs --- .github/workflows/tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6363c95e49..a289c10726 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -85,9 +85,6 @@ jobs: export _APP_DATABASE_SHARED_TABLES= export _APP_DATABASE_SHARED_TABLES_V1= - docker compose exec -T \ - -e _APP_DATABASE_SHARED_TABLES \ - -e _APP_DATABASE_SHARED_TABLES_V1 \ - appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + docker compose exec -T -e _APP_DATABASE_SHARED_TABLES -e _APP_DATABASE_SHARED_TABLES_V1 appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug || true docker compose logs appwrite-worker-builds \ No newline at end of file From 874f60dce60d334afc8328564204e24c0570c2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 23:26:25 +0100 Subject: [PATCH 6/9] Upgrade detection lib --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 442734db0f..4fc58cf873 100644 --- a/composer.lock +++ b/composer.lock @@ -3760,16 +3760,16 @@ }, { "name": "utopia-php/detector", - "version": "0.1.0", + "version": "0.1.1", "source": { "type": "git", "url": "https://github.com/utopia-php/detector.git", - "reference": "ddeee9c3e702ae10b3eb53cafe5210a0c4896c94" + "reference": "5dcabbfcf99731f3a37d86b7cb1e93c968baf64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/detector/zipball/ddeee9c3e702ae10b3eb53cafe5210a0c4896c94", - "reference": "ddeee9c3e702ae10b3eb53cafe5210a0c4896c94", + "url": "https://api.github.com/repos/utopia-php/detector/zipball/5dcabbfcf99731f3a37d86b7cb1e93c968baf64b", + "reference": "5dcabbfcf99731f3a37d86b7cb1e93c968baf64b", "shasum": "" }, "require": { @@ -3799,9 +3799,9 @@ ], "support": { "issues": "https://github.com/utopia-php/detector/issues", - "source": "https://github.com/utopia-php/detector/tree/0.1.0" + "source": "https://github.com/utopia-php/detector/tree/0.1.1" }, - "time": "2025-03-08T16:04:33+00:00" + "time": "2025-03-08T22:25:49+00:00" }, { "name": "utopia-php/domains", From eddeac6e4fd94175f2abab945430640480419812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 23:33:33 +0100 Subject: [PATCH 7/9] Revert temp changes --- .github/workflows/tests.yml | 270 +++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a289c10726..f7c5fe7c5c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,30 @@ env: on: [ pull_request ] jobs: + check_database_changes: + name: Check if utopia-php/database changed + runs-on: ubuntu-latest + outputs: + database_changed: ${{ steps.check.outputs.database_changed }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Fetch base branch + run: git fetch origin ${{ github.event.pull_request.base.ref }} + + - name: Check for utopia-php/database changes + id: check + run: | + if git diff origin/${{ github.event.pull_request.base.ref }} HEAD -- composer.lock | grep -q '"name": "utopia-php/database"'; then + echo "Database version changed, going to run all mode tests." + echo "database_changed=true" >> "$GITHUB_ENV" + echo "database_changed=true" >> "$GITHUB_OUTPUT" + else + echo "database_changed=false" >> "$GITHUB_ENV" + echo "database_changed=false" >> "$GITHUB_OUTPUT" + fi + setup: name: Setup & Build Appwrite Image runs-on: ubuntu-latest @@ -44,6 +68,64 @@ jobs: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar + unit_test: + name: Unit Test + runs-on: ubuntu-latest + needs: setup + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + + - name: Load and Start Appwrite + run: | + docker load --input /tmp/${{ env.IMAGE }}.tar + docker compose up -d + sleep 10 + + - name: Logs + run: docker compose logs appwrite + + - name: Doctor + run: docker compose exec -T appwrite doctor + + - name: Environment Variables + run: docker compose exec -T appwrite vars + + - name: Run Unit Tests + run: docker compose exec appwrite test /usr/src/code/tests/unit + + e2e_general_test: + name: E2E General Test + runs-on: ubuntu-latest + needs: setup + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + + - name: Load and Start Appwrite + run: | + docker load --input /tmp/${{ env.IMAGE }}.tar + docker compose up -d + sleep 10 + + - name: Run General Tests + run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/General --debug + e2e_service_test: name: E2E Service Test runs-on: ubuntu-latest @@ -52,7 +134,26 @@ jobs: fail-fast: false matrix: service: [ - Sites + Account, + Avatars, + Console, + Databases, + Functions, + FunctionsSchedule, + GraphQL, + Health, + Locale, + Projects, + Realtime, + Sites, + Proxy, + Storage, + Teams, + Users, + Webhooks, + VCS, + Messaging, + Migrations ] steps: - name: Checkout repository @@ -85,6 +186,167 @@ jobs: export _APP_DATABASE_SHARED_TABLES= export _APP_DATABASE_SHARED_TABLES_V1= - docker compose exec -T -e _APP_DATABASE_SHARED_TABLES -e _APP_DATABASE_SHARED_TABLES_V1 appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug || true - - docker compose logs appwrite-worker-builds \ No newline at end of file + docker compose exec -T \ + -e _APP_DATABASE_SHARED_TABLES \ + -e _APP_DATABASE_SHARED_TABLES_V1 \ + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + + e2e_shared_mode_test: + name: E2E Shared Mode Service Test + runs-on: ubuntu-latest + needs: [ setup, check_database_changes ] + if: needs.check_database_changes.outputs.database_changed == 'true' + strategy: + fail-fast: false + matrix: + service: + [ + Account, + Avatars, + Console, + Databases, + Functions, + FunctionsSchedule, + GraphQL, + Health, + Locale, + Projects, + Realtime, + Sites, + Proxy, + Storage, + Teams, + Users, + Webhooks, + VCS, + Messaging, + Migrations + ] + tables-mode: [ + 'Shared V1', + 'Shared V2', + ] + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + + - name: Load and Start Appwrite + run: | + docker load --input /tmp/${{ env.IMAGE }}.tar + docker compose up -d + sleep 30 + + - name: Wait for Open Runtimes + timeout-minutes: 3 + run: | + while ! docker compose logs openruntimes-executor | grep -q "Executor is ready."; do + echo "Waiting for Executor to come online" + sleep 1 + done + + - name: Run ${{ matrix.service }} tests with ${{ matrix.tables-mode }} table mode + run: | + if [ "${{ matrix.tables-mode }}" == "Shared V1" ]; then + echo "Using shared tables V1" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1=database_db_main + elif [ "${{ matrix.tables-mode }}" == "Shared V2" ]; then + echo "Using shared tables V2" + export _APP_DATABASE_SHARED_TABLES=database_db_main + export _APP_DATABASE_SHARED_TABLES_V1= + fi + + docker compose exec -T \ + -e _APP_DATABASE_SHARED_TABLES \ + -e _APP_DATABASE_SHARED_TABLES_V1 \ + appwrite test /usr/src/code/tests/e2e/Services/${{ matrix.service }} --debug + + benchmarking: + name: Benchmark + runs-on: ubuntu-latest + needs: setup + permissions: + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Load Cache + uses: actions/cache@v4 + with: + key: ${{ env.CACHE_KEY }} + path: /tmp/${{ env.IMAGE }}.tar + fail-on-cache-miss: true + - name: Load and Start Appwrite + run: | + sed -i 's/traefik/localhost/g' .env + docker load --input /tmp/${{ env.IMAGE }}.tar + docker compose up -d + sleep 10 + - name: Install Oha + run: | + echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list + sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg + sudo apt update + sudo apt install oha + - name: Benchmark PR + run: oha -z 180s http://localhost/v1/health/version -j > benchmark.json + - name: Cleaning + run: docker compose down -v + - name: Installing latest version + run: | + rm docker-compose.yml + rm .env + curl https://appwrite.io/install/compose -o docker-compose.yml + curl https://appwrite.io/install/env -o .env + sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env + docker compose up -d + sleep 10 + - name: Benchmark Latest + run: oha -z 180s http://localhost/v1/health/version -j > benchmark-latest.json + - name: Prepare comment + run: | + echo '## :sparkles: Benchmark results' > benchmark.txt + echo ' ' >> benchmark.txt + echo "- Requests per second: $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt + echo "- Requests with 200 status code: $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json)" >> benchmark.txt + echo "- P99 latency: $(jq -r '.latencyPercentiles.p99' benchmark.json )" >> benchmark.txt + echo " " >> benchmark.txt + echo " " >> benchmark.txt + echo "## :zap: Benchmark Comparison" >> benchmark.txt + echo " " >> benchmark.txt + echo "| Metric | This PR | Latest version | " >> benchmark.txt + echo "| --- | --- | --- | " >> benchmark.txt + echo "| RPS | $(jq -r '.summary.requestsPerSec|tonumber?|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.summary.requestsPerSec|tonumber|floor|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt + echo "| 200 | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark.json) | $(jq -r '.statusCodeDistribution."200"|tostring|[while(length>0;.[:-3])|.[-3:]]|reverse|join(",")' benchmark-latest.json) | " >> benchmark.txt + echo "| P99 | $(jq -r '.latencyPercentiles.p99' benchmark.json ) | $(jq -r '.latencyPercentiles.p99' benchmark-latest.json ) | " >> benchmark.txt + - name: Save results + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: benchmark.json + path: benchmark.json + retention-days: 7 + - name: Find Comment + if: github.event.pull_request.head.repo.full_name == github.repository + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark results + - name: Comment on PR + if: github.event.pull_request.head.repo.full_name == github.repository + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: benchmark.txt + edit-mode: replace From 084b6c799a4da2cf1216a1f39a1710404dd8cca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 23:33:37 +0100 Subject: [PATCH 8/9] Revert "Simplify tests further" This reverts commit 7e460f61550c03311459bf09ca7a3fef0bcc50ff. --- .../Services/Sites/SitesCustomServerTest.php | 2016 +++++++++++++++++ 1 file changed, 2016 insertions(+) diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index afbfd383ca..c5d38c1897 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2,6 +2,9 @@ namespace Tests\E2E\Services\Sites; +use Ahc\Jwt\JWT; +use Appwrite\Platform\Modules\Compute\Specification; +use Appwrite\Tests\Retry; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; @@ -9,6 +12,8 @@ use Tests\E2E\Scopes\SideServer; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; +use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class SitesCustomServerTest extends Scope { @@ -16,6 +21,463 @@ class SitesCustomServerTest extends Scope use ProjectCustom; use SideServer; + public function testListSpecs(): void + { + $specifications = $this->listSpecifications(); + $this->assertEquals(200, $specifications['headers']['status-code']); + $this->assertGreaterThan(0, $specifications['body']['total']); + $this->assertArrayHasKey(0, $specifications['body']['specifications']); + $this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]); + $this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]); + $this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]); + $this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]); + + $site = $this->createSite([ + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Specs site', + 'siteId' => ID::unique(), + 'specification' => $specifications['body']['specifications'][0]['slug'] + ]); + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']); + + $site = $this->getSite($site['body']['$id']); + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']); + + $this->cleanupSite($site['body']['$id']); + + $site = $this->createSite([ + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Specs site', + 'siteId' => ID::unique(), + 'specification' => 'cheap-please' + ]); + $this->assertEquals(400, $site['headers']['status-code']); + } + + public function testCreateSite(): void + { + /** + * Test for SUCCESS + */ + $site = $this->createSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'siteId' => ID::unique() + ]); + + $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('other', $site['body']['framework']); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$updatedAt'])); + $this->assertEquals('node-22', $site['body']['buildRuntime']); + $this->assertEquals(null, $site['body']['fallbackFile']); + $this->assertEquals('./', $site['body']['outputDirectory']); + + $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 testConsoleAvailabilityEndpoint(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Test Site', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + + $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ + 'type' => 'rules', + 'value' => $domain, + ]); + + $this->assertEquals(409, $response['headers']['status-code']); // domain unavailable + + $nonExistingDomain = "non-existent-subdomain.sites.localhost"; + + $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ + 'type' => 'rules', + 'value' => $nonExistingDomain, + ]); + + $this->assertEquals(204, $response['headers']['status-code']); // domain available + + $this->cleanupSite($siteId); + + $this->assertEventually(function () use ($siteId) { + $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('deploymentResourceId', [$siteId]) + ] + ]); + + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals(0, $rule['body']['total']); + }, 5000, 500); + + $response = $this->client->call(Client::METHOD_GET, '/console/resources', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => 'a_session_console=' . $this->getRoot()['session'], + 'x-appwrite-project' => 'console', + ], [ + 'type' => 'rules', + 'value' => $domain, + ]); + + $this->assertEquals(204, $response['headers']['status-code']); // domain available as site is deleted + } + + public function testVariables(): void + { + $site = $this->createSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'siteId' => ID::unique() + ]); + + $siteId = $site['body']['$id'] ?? ''; + + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals('Test Site', $site['body']['name']); + + $variable = $this->createVariable($siteId, [ + 'key' => 'siteKey1', + 'value' => 'siteValue1', + 'secret' => false, + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1', $variable['body']['key']); + $this->assertEquals('siteValue1', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $variable2 = $this->createVariable($siteId, [ + 'key' => 'siteKey2', + 'value' => 'siteValue2', + 'secret' => false, + ]); + + $this->assertEquals(201, $variable2['headers']['status-code']); + $this->assertNotEmpty($variable2['body']['$id']); + $this->assertEquals('siteKey2', $variable2['body']['key']); + $this->assertEquals('siteValue2', $variable2['body']['value']); + $this->assertEquals(false, $variable2['body']['secret']); + + $secretVariable = $this->createVariable($siteId, [ + 'key' => 'siteKey3', + 'value' => 'siteValue3', + 'secret' => true, + ]); + + $this->assertEquals(201, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variable = $this->getVariable($siteId, $variable['body']['$id']); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1', $variable['body']['key']); + $this->assertEquals('siteValue1', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ + 'key' => 'siteKey1Updated', + 'value' => 'siteValue1Updated', + ]); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1Updated', $variable['body']['key']); + $this->assertEquals('siteValue1Updated', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ + 'key' => 'siteKey1Updated', + 'secret' => true, + ]); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1Updated', $variable['body']['key']); + $this->assertEquals('', $variable['body']['value']); + $this->assertEquals(true, $variable['body']['secret']); + + $secretVariable = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ + 'key' => 'siteKey3', + 'value' => 'siteValue3Updated', + ]); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $response = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ + 'key' => 'siteKey3', + 'secret' => false, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variables = $this->listVariables($siteId); + + $this->assertEquals(200, $variables['headers']['status-code']); + $this->assertCount(3, $variables['body']['variables']); + + $response = $this->deleteVariable($siteId, $variable['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->deleteVariable($siteId, $variable2['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->deleteVariable($siteId, $secretVariable['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + + $variables = $this->listVariables($siteId); + + $this->assertEquals(200, $variables['headers']['status-code']); + $this->assertCount(0, $variables['body']['variables']); + + $this->cleanupSite($siteId); + } + + // This is first Sites test with Proxy + // If this fails, it may not be related to variables; but Router flow failing + public function testVariablesE2E(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro site', + 'framework' => 'astro', + 'adapter' => 'ssr', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './dist', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + + $secretVariable = $this->createVariable($siteId, [ + 'key' => 'name', + 'value' => 'Appwrite', + ]); + + $this->assertEquals(201, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('name', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->getSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Env variable is Appwrite", $response['body']); + $this->assertStringNotContainsString("Variable not found", $response['body']); + + $this->cleanupSite($siteId); + } + + public function testAdapterDetectionAstroSSR(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro SSR site', + 'framework' => 'astro', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './dist', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $site = $this->getSite($siteId); + $this->assertEquals('ssr', $site['body']['adapter']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + + $this->cleanupSite($siteId); + } + + public function testAdapterDetectionAstroStatic(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro static site', + 'framework' => 'astro', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './dist', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('astro-static'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertEquals('static', $site['body']['adapter']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + + $this->cleanupSite($siteId); + } + + public function testAdapterDetectionStatic(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Static site', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'outputDirectory' => '', + 'buildCommand' => '', + 'installCommand' => '', + ]); + $this->assertNotEmpty($siteId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertArrayHasKey('adapter', $site['body']); + $this->assertEmpty($site['body']['adapter']); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $site = $this->getSite($siteId); + $this->assertEquals('200', $site['headers']['status-code']); + $this->assertEquals('static', $site['body']['adapter']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + + $this->cleanupSite($siteId); + } public function testAdapterDetectionStaticSPA(): void { @@ -201,4 +663,1558 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); $this->cleanupSite($siteId2); } + + public function testGetSite(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $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); + } + + public function testUpdateSite(): void + { + $site = $this->createSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $siteId = $site['body']['$id'] ?? ''; + + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals('Test Site', $site['body']['name']); + + $site = $this->updateSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site Updated', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + '$id' => $siteId, + 'installCommand' => 'npm install' + ]); + + $dateValidator = new DatetimeValidator(); + + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals('Test Site Updated', $site['body']['name']); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($site['body']['$updatedAt'])); + $this->assertEquals('npm install', $site['body']['installCommand']); + + $this->cleanupSite($siteId); + } + + // public function testCreateDeploymentFromCLI() { + // // TODO: Implement testCreateDeploymentFromCLI() later + // } + + public function testCreateSiteAndDeploymentFromTemplate() + { + $starterTemplate = $this->getTemplate('nextjs-starter'); + $this->assertEquals(200, $starterTemplate['headers']['status-code']); + + $nextjsFramework = array_values(array_filter($starterTemplate['body']['frameworks'], function ($framework) { + return $framework['key'] === 'nextjs'; + }))[0]; + + // If this fails, the template has variables, and this test needs to be updated + $this->assertEmpty($starterTemplate['body']['variables']); + + $site = $this->createSite( + [ + 'siteId' => ID::unique(), + 'name' => $starterTemplate['body']['name'], + 'framework' => $nextjsFramework['key'], + 'adapter' => $nextjsFramework['adapter'], + 'buildCommand' => $nextjsFramework['buildCommand'], + 'buildRuntime' => $nextjsFramework['buildRuntime'], + 'fallbackFile' => $nextjsFramework['fallbackFile'], + 'installCommand' => $nextjsFramework['installCommand'], + 'outputDirectory' => $nextjsFramework['outputDirectory'], + 'providerRootDirectory' => $nextjsFramework['providerRootDirectory'], + ] + ); + + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + + $siteId = $site['body']['$id'] ?? ''; + + $deployment = $this->createTemplateDeployment( + $siteId, + [ + 'owner' => $starterTemplate['body']['providerOwner'], + 'repository' => $starterTemplate['body']['providerRepositoryId'], + 'rootDirectory' => $nextjsFramework['providerRootDirectory'], + 'version' => $starterTemplate['body']['providerVersion'], + 'activate' => true, + ] + ); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + + $deployments = $this->listDeployments($siteId); + + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertEquals(1, $deployments['body']['total']); + + $lastDeployment = $deployments['body']['deployments'][0]; + + $this->assertNotEmpty($lastDeployment['$id']); + $this->assertEquals(0, $lastDeployment['sourceSize']); + + $deploymentId = $lastDeployment['$id']; + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('ready', $deployment['body']['status']); + }, 300000, 1000); + + $site = $this->getSite($siteId); + $this->assertEquals(200, $site['headers']['status-code']); + + $this->cleanupSite($siteId); + } + + public function testCreateDeployment() + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'siteId' => $siteId, + 'code' => $this->packageSite('static'), + 'activate' => true, + ]); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); + + $deploymentIdActive = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($siteId, $deploymentIdActive) { + $deployment = $this->getDeployment($siteId, $deploymentIdActive); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + + $deploymentIdInactive = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($siteId, $deploymentIdInactive) { + $deployment = $this->getDeployment($siteId, $deploymentIdInactive); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); + + $site = $this->getSite($siteId); + + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertEquals($deploymentIdActive, $site['body']['deploymentId']); + $this->assertNotEquals($deploymentIdInactive, $site['body']['deploymentId']); + + $this->cleanupDeployment($siteId, $deploymentIdActive); + $this->cleanupDeployment($siteId, $deploymentIdInactive); + $this->cleanupSite($siteId); + } + + #[Retry(count: 3)] + public function testCancelDeploymentBuild(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('building', $deployment['body']['status']); + }, 100000, 250); + + $deployment = $this->cancelDeployment($siteId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('canceled', $deployment['body']['status']); + + /** + * Build worker still runs the build. + * 30s sleep gives worker enough time to finish build. + * After build finished, it should still be canceled, not ready. + */ + \sleep(30); + + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('canceled', $deployment['body']['status']); + + $this->cleanupDeployment($siteId, $deploymentId); + $this->cleanupSite($siteId); + } + + public function testUpdateDeployment(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + $this->assertEquals(202, $deployment['headers']['status-code']); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); + + /** + * Test for SUCCESS + */ + $dateValidator = new DatetimeValidator(); + + $response = $this->updateSiteDeployment($siteId, $deploymentId); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt'])); + $this->assertEquals(true, $dateValidator->isValid($response['body']['$updatedAt'])); + $this->assertEquals($deploymentId, $response['body']['deploymentId']); + + $this->cleanupDeployment($siteId, $deploymentId); + $this->cleanupSite($siteId); + } + + public function testListDeployments(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $deploymentIdActive = $deployment['body']['$id'] ?? ''; + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + + $deploymentIdInactive = $deployment['body']['$id'] ?? ''; + + $deployments = $this->listDeployments($siteId); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals($deployments['body']['total'], 2); + $this->assertIsArray($deployments['body']['deployments']); + $this->assertCount(2, $deployments['body']['deployments']); + $this->assertArrayHasKey('sourceSize', $deployments['body']['deployments'][0]); + $this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]); + + $deployments = $this->listDeployments($siteId, [ + 'queries' => [ + Query::limit(1)->toString(), + ], + ]); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(1, $deployments['body']['deployments']); + + $deployments = $this->listDeployments($siteId, [ + 'queries' => [ + Query::offset(1)->toString(), + ], + ]); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertCount(1, $deployments['body']['deployments']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::equal('type', ['manual'])->toString(), + ], + ] + ); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(2, $deployments['body']['total']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::equal('type', ['vcs'])->toString(), + ], + ] + ); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(0, $deployments['body']['total']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::equal('type', ['invalid-string'])->toString(), + ], + ] + ); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(0, $deployments['body']['total']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::greaterThan('sourceSize', 10000)->toString(), + ], + ] + ); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(0, $deployments['body']['total']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::greaterThan('sourceSize', 0)->toString(), + ], + ] + ); + + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(2, $deployments['body']['total']); + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::greaterThan('sourceSize', -100)->toString(), + ], + ] + ); + $this->assertEquals($deployments['headers']['status-code'], 200); + $this->assertEquals(2, $deployments['body']['total']); + + /** + * Ensure size output and size filters work exactly. + * Prevents buildSize being counted towards deployment size + */ + $deployments = $this->listDeployments( + $siteId, + [ + Query::limit(1)->toString(), + ] + ); + + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertGreaterThanOrEqual(1, $deployments['body']['total']); + $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); + $this->assertNotEmpty($deployments['body']['deployments'][0]['sourceSize']); + + $deploymentId = $deployments['body']['deployments'][0]['$id']; + $deploymentSize = $deployments['body']['deployments'][0]['sourceSize']; + + $deployments = $this->listDeployments( + $siteId, + [ + 'queries' => [ + Query::equal('sourceSize', [$deploymentSize])->toString(), + ], + ] + ); + + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertGreaterThan(0, $deployments['body']['total']); + + $matchingDeployment = array_filter( + $deployments['body']['deployments'], + fn ($deployment) => $deployment['$id'] === $deploymentId + ); + + $this->assertNotEmpty($matchingDeployment, "Deployment with ID {$deploymentId} not found"); + + if (!empty($matchingDeployment)) { + $deployment = reset($matchingDeployment); + $this->assertEquals($deploymentSize, $deployment['sourceSize']); + } + + $this->cleanupDeployment($siteId, $deploymentIdActive); + $this->cleanupDeployment($siteId, $deploymentIdInactive); + $this->cleanupSite($siteId); + } + + public function testGetDeployment(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); + + /** + * Test for SUCCESS + */ + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertGreaterThan(0, $deployment['body']['buildDuration']); + $this->assertNotEmpty($deployment['body']['status']); + $this->assertNotEmpty($deployment['body']['buildLogs']); + $this->assertArrayHasKey('sourceSize', $deployment['body']); + $this->assertArrayHasKey('buildSize', $deployment['body']); + + /** + * Test for FAILURE + */ + $deployment = $this->getDeployment($siteId, 'x'); + + $this->assertEquals($deployment['headers']['status-code'], 404); + + $this->cleanupDeployment($siteId, $deploymentId); + $this->cleanupSite($siteId); + } + + public function testUpdateSpecs(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + /** + * Test for SUCCESS + */ + // Change the function specs + $site = $this->updateSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + '$id' => $siteId, + 'specification' => Specification::S_1VCPU_1GB, + ]); + + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['specification']); + + // Change the specs to 1vcpu 512mb + $site = $this->updateSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + '$id' => $siteId, + 'specification' => Specification::S_1VCPU_512MB, + ]); + + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['specification']); + + /** + * Test for FAILURE + */ + + $site = $this->updateSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + '$id' => $siteId, + 'specification' => 's-2vcpu-512mb', // Invalid specification + ]); + + $this->assertEquals(400, $site['headers']['status-code']); + $this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $site['body']['message']); + + $this->cleanupSite($siteId); + } + + public function testDeleteDeployment(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $deployment = $this->createDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'false' + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals('ready', $deployment['body']['status']); + }, 50000, 500); + + /** + * Test for SUCCESS + */ + $deployment = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(204, $deployment['headers']['status-code']); + $this->assertEmpty($deployment['body']); + + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(404, $deployment['headers']['status-code']); + } + + public function testDeleteSite(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $this->assertNotNull($siteId); + + $site = $this->deleteSite($siteId); + + $this->assertEquals(204, $site['headers']['status-code']); + $this->assertEmpty($site['body']); + + $function = $this->getSite($siteId); + + $this->assertEquals(404, $function['headers']['status-code']); + } + + public function testGetFrameworks(): void + { + $frameworks = $this->client->call(Client::METHOD_GET, '/sites/frameworks', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $frameworks['headers']['status-code']); + $this->assertGreaterThan(0, $frameworks['body']['total']); + + $framework = $frameworks['body']['frameworks'][0]; + + $this->assertArrayHasKey('name', $framework); + $this->assertArrayHasKey('key', $framework); + $this->assertArrayHasKey('buildRuntime', $framework); + $this->assertArrayHasKey('runtimes', $framework); + $this->assertArrayHasKey('adapters', $framework); + } + + public function testSiteStatic(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Non-SPA site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-spa'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->setupSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Index page", $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Contact page", $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/non-existing', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString("Page not found", $response['body']); // Title + $this->assertStringContainsString("Go to homepage", $response['body']); // Button + $this->assertStringContainsString("Powered by", $response['body']); // Brand + + $this->cleanupSite($siteId); + } + + public function testSiteStaticSPA(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'SPA site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '404.html', + ]); + + $this->assertNotEmpty($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-spa'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->setupSiteDomain($siteId); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Index page", $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Contact page", $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/non-existing', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Customized 404 page", $response['body']); + $this->assertStringNotContainsString("Powered by", $response['body']); // Brand + + $this->cleanupSite($siteId); + } + + public function testSiteTemplate(): void + { + $template = $this->getTemplate('astro-starter'); + $this->assertEquals(200, $template['headers']['status-code']); + + $template = $template['body']; + + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Template site', + 'framework' => $template['frameworks'][0]['key'], + 'adapter' => $template['frameworks'][0]['adapter'], + 'buildRuntime' => $template['frameworks'][0]['buildRuntime'], + 'outputDirectory' => $template['frameworks'][0]['outputDirectory'], + 'buildCommand' => $template['frameworks'][0]['buildCommand'], + 'installCommand' => $template['frameworks'][0]['installCommand'], + 'fallbackFile' => $template['frameworks'][0]['fallbackFile'], + ]); + + $this->assertNotEmpty($siteId); + + $deployment = $this->createTemplateDeployment($siteId, [ + 'repository' => $template['providerRepositoryId'], + 'owner' => $template['providerOwner'], + 'rootDirectory' => $template['frameworks'][0]['providerRootDirectory'], + 'version' => $template['providerVersion'], + 'activate' => true + ]); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + + $this->assertEventually(function () use ($siteId) { + $site = $this->getSite($siteId); + $this->assertNotEmpty($site['body']['deploymentId']); + }, 50000, 500); + + $domain = $this->setupSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Astro Blog", $response['body']); + $this->assertStringContainsString("Hello, Astronaut!", $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/about'); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Astro Blog", $response['body']); + $this->assertStringContainsString("About Me", $response['body']); + + $this->cleanupSite($siteId); + } + + public function testSiteDomainReclaiming(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Startup site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $subdomain = 'startup' . \uniqid(); + $domain = $this->setupSiteDomain($siteId, $subdomain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->getSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringNotContainsString("This domain is not connected to any Appwrite resource yet", $response['body']); + + $site2 = $this->createSite([ + 'siteId' => ID::unique(), + 'name' => 'Startup 2 site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $siteId2 = $site2['body']['$id']; + + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'domain' => $subdomain . '.' . System::getEnv('_APP_DOMAIN_SITES', ''), + 'siteId' => $siteId2, + ]); + + $this->assertEquals(409, $rule['headers']['status-code']); + + $this->cleanupSite($siteId); + + $this->assertEventually(function () use ($domain) { + $rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::equal('domain', [$domain])->toString(), + ], + ]); + + $this->assertEquals(200, $rules['headers']['status-code']); + $this->assertEquals(0, $rules['body']['total']); + }, 50000, 500); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(401, $response['headers']['status-code']); + $this->assertStringContainsString("This domain is not connected to any Appwrite resource yet", $response['body']); + + $site = $this->createSite([ + 'siteId' => ID::unique(), + 'name' => 'Startup 2 site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + + $siteId = $site['body']['$id']; + + $domain = $this->setupSiteDomain($siteId, $subdomain); + + $this->assertNotEmpty($domain); + + $this->cleanupSite($site['body']['$id']); + } + + public function testSitePreviewBranding(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'A site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $oldDeploymentDomain = $this->getDeploymentDomain($deploymentId); + $this->assertNotEmpty($oldDeploymentDomain); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => 'true' + ]); + $this->assertNotEmpty($deploymentId); + + $newDeploymentDomain = $this->getDeploymentDomain($deploymentId); + $this->assertNotEmpty($newDeploymentDomain); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $newDeploymentDomain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Hello Appwrite", $response['body']); + $this->assertStringNotContainsString("Preview by", $response['body']); + $contentLength = $response['headers']['content-length']; + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $oldDeploymentDomain); + $response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); + + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0); + $apiKey = $jwtObj->encode([ + 'projectCheckDisabled' => true, + 'previewAuthDisabled' => true, + ]); + $response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false, headers: [ + 'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey, + ]); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Hello Appwrite", $response['body']); + $this->assertStringContainsString("Preview by", $response['body']); + $this->assertGreaterThan($contentLength, $response['headers']['content-length']); + + $this->cleanupSite($siteId); + } + + public function testSiteCors(): void + { + // Create rule together with site + $subdomain = 'startup' . \uniqid(); + + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Startup site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + 'subdomain' => $subdomain + ]); + + $this->assertNotEmpty($siteId); + + $this->setupSiteDomain($siteId, $subdomain); + $domain = $this->getSiteDomain($siteId); + + $this->assertNotEmpty($domain); + + $url = 'http://' . $domain; + + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'referer' => $url, + 'origin' => $url + ])); + + $this->assertEquals($url, $response['headers']['access-control-allow-origin']); + + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'unknown', + 'referer' => $url, + 'origin' => $url + ])); + + $this->assertNotEquals($url, $response['headers']['access-control-allow-origin']); + $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); + + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'referer' => 'http://unknown.com', + 'origin' => 'http://unknown.com' + ])); + + $this->assertNotEquals($url, $response['headers']['access-control-allow-origin']); + $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); + } + + public function testSiteScreenshot(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Themed site', + 'framework' => 'other', + 'adapter' => 'static', + 'buildRuntime' => 'static-1', + 'outputDirectory' => './', + 'buildCommand' => '', + 'installCommand' => '', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-themed'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->getSiteDomain($siteId); + $this->assertNotEmpty($domain); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Themed website", $response['body']); + $this->assertStringContainsString("@media (prefers-color-scheme: dark)", $response['body']); + + $deployment = $this->getDeployment($siteId, $deploymentId); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['screenshotLight']); + $this->assertNotEmpty($deployment['body']['screenshotDark']); + + $screenshotId = $deployment['body']['screenshotLight']; + $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([ + ], $this->getHeaders())); + + $this->assertEquals(200, $file['headers']['status-code']); + $this->assertNotEmpty(200, $file['body']); + $this->assertGreaterThan(1, $file['headers']['content-length']); + $this->assertEquals('image/png', $file['headers']['content-type']); + + $screenshotHash = \md5($file['body']); + $this->assertNotEmpty($screenshotHash); + + $screenshotId = $deployment['body']['screenshotDark']; + $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin", array_merge([ + ], $this->getHeaders())); + + $this->assertEquals(200, $file['headers']['status-code']); + $this->assertNotEmpty(200, $file['body']); + $this->assertGreaterThan(1, $file['headers']['content-length']); + $this->assertEquals('image/png', $file['headers']['content-type']); + + $screenshotDarkHash = \md5($file['body']); + $this->assertNotEmpty($screenshotDarkHash); + + $this->assertNotEquals($screenshotDarkHash, $screenshotHash); + + $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin"); + $this->assertEquals(404, $file['headers']['status-code']); + + $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console&mode=admin"); + $this->assertEquals(404, $file['headers']['status-code']); + + $this->cleanupSite($siteId); + } + + public function testSiteDownload(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'fallbackFile' => '', + 'framework' => 'other', + 'name' => 'Test Site', + 'adapter' => 'static', + 'outputDirectory' => './', + 'providerBranch' => 'main', + 'providerRootDirectory' => './', + 'siteId' => ID::unique() + ]); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + + $this->assertNotEmpty($deploymentId); + + $response = $this->getDeploymentDownload($siteId, $deploymentId, 'source'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('application/gzip', $response['headers']['content-type']); + $this->assertGreaterThan(0, $response['headers']['content-length']); + $this->assertGreaterThan(0, \strlen($response['body'])); + + $deploymentMd5 = \md5($response['body']); + + $response = $this->getDeploymentDownload($siteId, $deploymentId, 'output'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('application/gzip', $response['headers']['content-type']); + $this->assertGreaterThan(0, $response['headers']['content-length']); + $this->assertGreaterThan(0, \strlen($response['body'])); + + $buildMd5 = \md5($response['body']); + + $this->assertNotEquals($deploymentMd5, $buildMd5); + + $this->cleanupSite($siteId); + } + + public function testSSRLogs(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'SSR site', + 'framework' => 'astro', + 'adapter' => 'ssr', + 'buildRuntime' => 'node-22', + 'outputDirectory' => './dist', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->getSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/logs-inline'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Inline logs printed.", $response['body']); + + $logs = $this->listLogs($siteId, [ + Query::orderDesc('$createdAt')->toString(), + Query::limit(1)->toString(), + ]); + $this->assertEquals(200, $logs['headers']['status-code']); + $this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']); + $this->assertStringContainsString("/logs-inline", $logs['body']['executions'][0]['requestPath']); + $this->assertStringContainsString("Log1", $logs['body']['executions'][0]['logs']); + $this->assertStringContainsString("Log2", $logs['body']['executions'][0]['logs']); + $this->assertStringContainsString("Error1", $logs['body']['executions'][0]['errors']); + $this->assertStringContainsString("Error2", $logs['body']['executions'][0]['errors']); + $log1Id = $logs['body']['executions'][0]['$id']; + $this->assertNotEmpty($log1Id); + + $response = $proxyClient->call(Client::METHOD_GET, '/logs-action'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Action logs printed.", $response['body']); + + $logs = $this->listLogs($siteId, [ + Query::orderDesc('$createdAt')->toString(), + Query::limit(1)->toString(), + ]); + $this->assertEquals(200, $logs['headers']['status-code']); + $this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']); + $this->assertStringContainsString("/logs-action", $logs['body']['executions'][0]['requestPath']); + $this->assertStringContainsString("Log1", $logs['body']['executions'][0]['logs']); + $this->assertStringContainsString("Log2", $logs['body']['executions'][0]['logs']); + $this->assertStringContainsString("Error1", $logs['body']['executions'][0]['errors']); + $this->assertStringContainsString("Error2", $logs['body']['executions'][0]['errors']); + $log2Id = $logs['body']['executions'][0]['$id']; + $this->assertNotEmpty($log2Id); + + $this->assertNotEquals($log1Id, $log2Id); + + $this->cleanupSite($siteId); + } + + public function testDuplicateDeployment(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Duplicate deployment Site', + 'adapter' => 'static', + 'fallbackFile' => '404.html', + 'siteId' => ID::unique() + ]); + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $deploymentId1 = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-spa'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId1); + + $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); + $this->assertStringContainsString("Customized 404 page", $response['body']); + + $site = $this->updateSite([ + '$id' => $siteId, + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Duplicate deployment Site', + 'adapter' => 'static', + 'fallbackFile' => 'index.html', + ]); + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertEquals('index.html', $site['body']['fallbackFile']); + + $deploymentId2 = $this->setupDuplicateDeployment($siteId, $deploymentId1); + $this->assertNotEmpty($deploymentId2); + + $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); + $this->assertStringContainsString("Index page", $response['body']); + + $this->cleanupSite($siteId); + } + + public function testUpdateDeploymentStatus(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Activate test Site', + 'siteId' => ID::unique(), + 'adapter' => 'static', + ]); + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $deploymentId1 = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId1); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString('Hello Appwrite', $response['body']); + + $deploymentId2 = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-spa'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId2); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString('Index page', $response['body']); + + $function = $this->getSite($siteId); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertEquals($deploymentId2, $function['body']['deploymentId']); + + $function = $this->updateSiteDeployment($siteId, $deploymentId1); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertEquals($deploymentId1, $function['body']['deploymentId']); + + $function = $this->getSite($siteId); + $this->assertEquals(200, $function['headers']['status-code']); + $this->assertEquals($deploymentId1, $function['body']['deploymentId']); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString('Hello Appwrite', $response['body']); + + $this->cleanupSite($siteId); + } + + public function testPreviewDomain(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'node-22', + 'framework' => 'other', + 'name' => 'Authorized preview site', + 'siteId' => ID::unique(), + 'adapter' => 'static', + ]); + $this->assertNotEmpty($siteId); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId); + + $domain = $this->getDeploymentDomain($deploymentId); + $this->assertNotEmpty($domain); + + // Create second deployment to make first one a preview + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); + $this->assertStringContainsString('projectId=' . $this->getProject()['$id'], $response['headers']['location']); + $this->assertStringContainsString('origin=', $response['headers']['location']); + $this->assertStringContainsString('path=%2Fcontact', $response['headers']['location']); + + $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + ]), [ + 'email' => $this->getRoot()['email'], + 'password' => 'password' + ]); + $this->assertEquals(201, $session['headers']['status-code']); + $this->assertNotEmpty($session['cookies']['a_session_console']); + $this->assertNotEmpty($session['body']['$id']); + $cookie = 'a_session_console=' . $session['cookies']['a_session_console']; + + $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => $cookie, + 'x-appwrite-project' => 'console', + ]), []); + $this->assertEquals(201, $jwt['headers']['status-code']); + $this->assertNotEmpty($jwt['body']['jwt']); + + $response = $proxyClient->call(Client::METHOD_GET, '/_appwrite/authorize', params: [ + 'jwt' => $jwt['body']['jwt'], + 'path' => '/contact' + ], followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertArrayHasKey('set-cookie', $response['headers']); + $this->assertStringContainsString('a_jwt_console=', $response['headers']['set-cookie']); + $this->assertStringContainsString('httponly', $response['headers']['set-cookie']); + $this->assertStringContainsString('domain=' . $domain, $response['headers']['set-cookie']); + $this->assertStringContainsString('path=/', $response['headers']['set-cookie']); + $this->assertNotEmpty($response['cookies']['a_jwt_console']); + $this->assertEquals($jwt['body']['jwt'], $response['cookies']['a_jwt_console']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ + 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] + ], followRedirects: false); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Contact page", $response['body']); + $this->assertStringContainsString("Preview by", $response['body']); + + // Failure: Session missing (old bad, new ok) + $session = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => $cookie, + 'x-appwrite-project' => 'console', + ]), []); + $this->assertEquals(204, $session['headers']['status-code']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ + 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] + ], followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); + + // Failure: User missing + $cookie = 'a_session_console=' .$this->getRoot()['session']; + $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => $cookie, + 'x-appwrite-project' => 'console', + ]), []); + $this->assertEquals(201, $jwt['headers']['status-code']); + $this->assertNotEmpty($jwt['body']['jwt']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ + 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] + ], followRedirects: false); + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Contact page", $response['body']); + $this->assertStringContainsString("Preview by", $response['body']); + + $user = $this->client->call(Client::METHOD_PATCH, '/account/status', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => $cookie, + 'x-appwrite-project' => 'console', + ]), []); + $this->assertEquals(200, $user['headers']['status-code']); + $this->assertFalse($user['body']['status']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ + 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] + ], followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); + + // Failure: Membership missing + $user = $this->client->call(Client::METHOD_POST, '/account', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + ], [ + 'userId' => ID::unique(), + 'email' => 'newuser@appwrite.io', + 'password' => 'password' + ]); + $this->assertEquals(201, $user['headers']['status-code']); + + $session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + ], [ + 'email' => 'newuser@appwrite.io', + 'password' => 'password', + ]); + $this->assertEquals(201, $session['headers']['status-code']); + $this->assertNotEmpty($session['cookies']['a_session_console']); + $cookie = 'a_session_console=' . $session['cookies']['a_session_console']; + + $jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'cookie' => $cookie, + 'x-appwrite-project' => 'console', + ]), []); + $this->assertEquals(201, $jwt['headers']['status-code']); + $this->assertNotEmpty($jwt['body']['jwt']); + + $response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [ + 'cookie' => 'a_jwt_console=' . $jwt['body']['jwt'] + ], followRedirects: false); + $this->assertEquals(301, $response['headers']['status-code']); + $this->assertStringContainsString('/console/auth/preview', $response['headers']['location']); + + $this->cleanupSite($siteId); + } } From 852a16d01b1b807eeec97c3baee7a53683a82038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sat, 8 Mar 2025 23:34:37 +0100 Subject: [PATCH 9/9] Remove leftovers --- .../Modules/Functions/Workers/Builds.php | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 0dcb27a950..0e16a0040e 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -671,16 +671,11 @@ class Builds extends Action } }), ]); - - \var_dump("Finish"); if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') { Console::info('Build has been canceled'); return; } - - \var_dump("Problem"); - \var_dump($err); if ($err) { throw $err; @@ -691,15 +686,10 @@ class Builds extends Action $buildSizeLimit = (int)System::getEnv('_APP_COMPUTE_BUILD_SIZE_LIMIT', '2000000000'); if ($response['size'] > $buildSizeLimit) { - \var_dump("Limit issue"); throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.'); } - \var_dump($resource->getCollection() === 'sites'); - \var_dump($resource->getAttribute('adapter', '')); if ($resource->getCollection() === 'sites' && empty($resource->getAttribute('adapter', ''))) { - \var_dump($resource->getAttribute('outputDirectory', './')); - \var_dump(\escapeshellarg($resource->getAttribute('outputDirectory', './'))); // TODO: Refactor with structured command in future, using utopia library (CLI) $listFilesCommand = "cd /usr/local/build && cd " . \escapeshellarg($resource->getAttribute('outputDirectory', './')) . " && find . -name 'node_modules' -prune -o -type f -print"; $command = $executor->createCommand( @@ -708,31 +698,21 @@ class Builds extends Action command: $listFilesCommand, timeout: 15 ); - - \var_dump($command); - + $files = \explode("\n", $command['output']); // Parse output $files = \array_filter($files); // Remove empty $files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces $files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./ - - \var_dump($files); $detector = new Rendering($files, $resource->getAttribute('framework', '')); $detector ->addOption(new SSR()) ->addOption(new XStatic()); $detection = $detector->detect(); - - \var_dump($detection->getName()); - \var_dump($detection->getFallbackFile()); - + $resource->setAttribute('adapter', $detection->getName()); $resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? ''); $resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource); - - \var_dump("Updated"); - \var_dump($resource->getAttribute('fallbackFile', '')); } $executor->deleteRuntime($project->getId(), $deployment->getId(), '-build');