name: "Tests" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: COMPOSE_FILE: docker-compose.yml IMAGE: appwrite-dev CACHE_KEY: appwrite-dev-${{ github.event.pull_request.head.sha }} on: pull_request: workflow_dispatch: inputs: response_format: description: 'Response format version to test (e.g., 1.5.0, 1.4.0)' required: false type: string default: '' jobs: matrix: name: Generate test matrix runs-on: ubuntu-latest outputs: databases: ${{ steps.generate.outputs.databases }} modes: ${{ steps.generate.outputs.modes }} steps: - name: Generate matrix id: generate uses: actions/github-script@v7 with: script: | const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB']; const allModes = ['dedicated', 'shared_v1', 'shared_v2']; const defaultDatabases = ['MongoDB']; const defaultModes = ['dedicated']; const pr = context.payload.pull_request; if (!pr) { core.setOutput('databases', JSON.stringify(allDatabases)); core.setOutput('modes', JSON.stringify(allModes)); return; } const files = await github.paginate(github.rest.pulls.listFiles, { owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, }); const lockFile = files.find(f => f.filename === 'composer.lock'); const databaseChanged = lockFile?.patch?.includes('"name": "utopia-php/database"') ?? false; core.setOutput('databases', JSON.stringify(databaseChanged ? allDatabases : defaultDatabases)); core.setOutput('modes', JSON.stringify(databaseChanged ? allModes : defaultModes)); setup: name: Setup & Build Appwrite Image runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v6 with: submodules: recursive - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build Appwrite uses: docker/build-push-action@v6 with: context: . push: false tags: ${{ env.IMAGE }} load: true cache-from: type=gha cache-to: type=gha,mode=max outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar target: development build-args: | DEBUG=false TESTING=true VERSION=dev - name: Cache Docker Image uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar unit_test: name: Unit Test runs-on: ubuntu-latest needs: setup permissions: contents: read pull-requests: write steps: - name: checkout uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Load and Start Appwrite timeout-minutes: 5 run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose pull --quiet --ignore-buildable docker compose up -d --quiet-pull --wait - name: Environment Variables run: docker compose exec -T appwrite vars - name: Run Unit Tests uses: itznotabug/php-retry@v3 with: max_attempts: 2 retry_wait_seconds: 60 timeout_minutes: 15 job_id: ${{ job.check_run_id }} github_token: ${{ secrets.GITHUB_TOKEN }} test_dir: tests/unit command: >- docker compose exec -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" appwrite test /usr/src/code/tests/unit e2e_general_test: name: E2E General Test runs-on: ubuntu-latest needs: setup permissions: contents: read pull-requests: write steps: - name: checkout uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Load and Start Appwrite timeout-minutes: 5 run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose pull --quiet --ignore-buildable docker compose up -d --quiet-pull --wait - 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 General Tests uses: itznotabug/php-retry@v3 with: max_attempts: 2 retry_wait_seconds: 60 timeout_minutes: 15 job_id: ${{ job.check_run_id }} github_token: ${{ secrets.GITHUB_TOKEN }} test_dir: tests/e2e/General command: >- docker compose exec -T -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" appwrite test /usr/src/code/tests/e2e/General - name: Failure Logs if: failure() run: | echo "=== Appwrite Logs ===" docker compose logs e2e_service_test: name: E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }} runs-on: ubuntu-latest needs: [setup, matrix] permissions: contents: read pull-requests: write strategy: fail-fast: false matrix: database: ${{ fromJSON(needs.matrix.outputs.databases) }} mode: ${{ fromJSON(needs.matrix.outputs.modes) }} service: [ Account, Avatars, Console, Databases, Functions, FunctionsSchedule, GraphQL, Health, Locale, Projects, Realtime, Sites, Proxy, Storage, Tokens, Teams, Users, Webhooks, VCS, Messaging, Migrations ] steps: - name: Checkout repository uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Set database environment run: | if [ "${{ matrix.database }}" = "MariaDB" ]; then echo "COMPOSE_PROFILES=mariadb" >> $GITHUB_ENV echo "_APP_DB_ADAPTER=mariadb" >> $GITHUB_ENV echo "_APP_DB_HOST=mariadb" >> $GITHUB_ENV echo "_APP_DB_PORT=3306" >> $GITHUB_ENV elif [ "${{ matrix.database }}" = "MongoDB" ]; then echo "COMPOSE_PROFILES=mongodb" >> $GITHUB_ENV echo "_APP_DB_ADAPTER=mongodb" >> $GITHUB_ENV echo "_APP_DB_HOST=mongodb" >> $GITHUB_ENV echo "_APP_DB_PORT=27017" >> $GITHUB_ENV elif [ "${{ matrix.database }}" = "PostgreSQL" ]; then echo "COMPOSE_PROFILES=postgresql" >> $GITHUB_ENV echo "_APP_DB_ADAPTER=postgresql" >> $GITHUB_ENV echo "_APP_DB_HOST=postgresql" >> $GITHUB_ENV echo "_APP_DB_PORT=5432" >> $GITHUB_ENV fi - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Load and Start Appwrite timeout-minutes: 5 env: _APP_BROWSER_HOST: http://invalid-browser/v1 _APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }} _APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }} run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose pull --quiet --ignore-buildable docker compose up -d --quiet-pull --wait - 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 tests uses: itznotabug/php-retry@v3 with: max_attempts: 2 retry_wait_seconds: 60 timeout_minutes: 20 job_id: ${{ job.check_run_id }} github_token: ${{ secrets.GITHUB_TOKEN }} test_dir: tests/e2e/Services/${{ matrix.service }} command: | SERVICE_PATH="/usr/src/code/tests/e2e/Services/${{ matrix.service }}" # Services that rely on sequential test method execution (shared static state) FUNCTIONAL_FLAG="--functional" case "${{ matrix.service }}" in Databases|Functions|Realtime) FUNCTIONAL_FLAG="" ;; esac docker compose exec -T \ -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \ appwrite vendor/bin/paratest --processes $(nproc) $FUNCTIONAL_FLAG "$SERVICE_PATH" --exclude-group abuseEnabled --exclude-group screenshots --log-junit tests/e2e/Services/${{ matrix.service }}/junit.xml - name: Failure Logs if: failure() run: | echo "=== Appwrite Logs ===" docker compose logs e2e_abuse_enabled: name: E2E / Abuse (${{ matrix.mode }}) runs-on: ubuntu-latest needs: [setup, matrix] permissions: contents: read pull-requests: write strategy: fail-fast: false matrix: mode: ${{ fromJSON(needs.matrix.outputs.modes) }} steps: - name: Checkout repository uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Load and Start Appwrite timeout-minutes: 5 env: _APP_OPTIONS_ABUSE: enabled _APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }} _APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }} run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose pull --quiet --ignore-buildable docker compose up -d --quiet-pull --wait - name: Run tests uses: itznotabug/php-retry@v3 with: max_attempts: 2 retry_wait_seconds: 60 timeout_minutes: 15 job_id: ${{ job.check_run_id }} github_token: ${{ secrets.GITHUB_TOKEN }} test_dir: tests/e2e command: >- docker compose exec -T -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" appwrite test /usr/src/code/tests/e2e --group=abuseEnabled - name: Failure Logs if: failure() run: | echo "=== Appwrite Logs ===" docker compose logs e2e_screenshots: name: E2E / Screenshots (${{ matrix.mode }}) runs-on: ubuntu-latest needs: [setup, matrix] permissions: contents: read pull-requests: write strategy: fail-fast: false matrix: mode: ${{ fromJSON(needs.matrix.outputs.modes) }} steps: - name: Checkout repository uses: actions/checkout@v6 - name: Load Cache uses: actions/cache@v5 with: key: ${{ env.CACHE_KEY }} path: /tmp/${{ env.IMAGE }}.tar fail-on-cache-miss: true - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Load and Start Appwrite timeout-minutes: 5 env: _APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }} _APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }} run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose pull --quiet --ignore-buildable docker compose up -d --quiet-pull --wait - 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 tests uses: itznotabug/php-retry@v3 with: max_attempts: 2 retry_wait_seconds: 60 timeout_minutes: 15 job_id: ${{ job.check_run_id }} github_token: ${{ secrets.GITHUB_TOKEN }} test_dir: tests/e2e/Services/Sites command: >- docker compose exec -T -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" appwrite test /usr/src/code/tests/e2e/Services/Sites --group=screenshots - name: Failure Logs if: failure() run: | echo "=== Appwrite Logs ===" docker compose logs