From 54f09d2d5c96aaa8aaa131edaaffe984a36d7fad Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:10:16 +0200 Subject: [PATCH 1/3] chore: change acceptance test variable --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9dac231..f5139db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: run: make acceptance-test timeout-minutes: 20 env: - TF_ACC: "1" + TF_ACC: "0" APPWRITE_ENDPOINT: ${{ secrets.APPWRITE_ENDPOINT }} APPWRITE_PROJECT_ID: ${{ secrets.APPWRITE_PROJECT_ID }} APPWRITE_API_KEY: ${{ secrets.APPWRITE_API_KEY }} From 1d49ef2a136faf14b06175493c30ff45b7e869f3 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Tue, 21 Apr 2026 07:59:59 +0200 Subject: [PATCH 2/3] chore: add setup around running a testing environment --- .github/workflows/test.yml | 93 +++++++++--- Makefile | 14 +- testing/.gitignore | 2 + testing/bootstrap.sh | 167 ++++++++++++++++++++++ testing/docker-compose.yml | 284 +++++++++++++++++++++++++++++++++++++ 5 files changed, 541 insertions(+), 19 deletions(-) create mode 100644 testing/.gitignore create mode 100755 testing/bootstrap.sh create mode 100644 testing/docker-compose.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5139db..a448ecf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,26 +22,85 @@ jobs: with: terraform_wrapper: false - - name: Verify required secrets + - name: Start Appwrite + run: docker compose -f testing/docker-compose.yaml up -d --remove-orphans + + - name: Wait for Appwrite run: | - missing=() - [ -z "$APPWRITE_ENDPOINT" ] && missing+=("APPWRITE_ENDPOINT") - [ -z "$APPWRITE_PROJECT_ID" ] && missing+=("APPWRITE_PROJECT_ID") - [ -z "$APPWRITE_API_KEY" ] && missing+=("APPWRITE_API_KEY") - if [ ${#missing[@]} -ne 0 ]; then - echo "::error::Missing required secrets: ${missing[*]}" - exit 1 - fi - env: - APPWRITE_ENDPOINT: ${{ secrets.APPWRITE_ENDPOINT }} - APPWRITE_PROJECT_ID: ${{ secrets.APPWRITE_PROJECT_ID }} - APPWRITE_API_KEY: ${{ secrets.APPWRITE_API_KEY }} + echo "Waiting for Appwrite to be ready..." + for i in $(seq 1 60); do + if curl -sf http://localhost:20080/v1/health > /dev/null 2>&1; then + echo "Appwrite is ready!" + exit 0 + fi + sleep 2 + done + echo "Appwrite did not become ready" + docker compose -f testing/docker-compose.yml logs appwrite + exit 1 + + - name: Bootstrap project and API key + run: | + APPWRITE_URL="http://localhost:20080/v1" + COOKIE_JAR=$(mktemp) + + # Create admin account + curl -sf -X POST "$APPWRITE_URL/account" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -c "$COOKIE_JAR" \ + -d '{"userId":"unique()","email":"admin@test.local","password":"password1234","name":"Admin"}' \ + > /dev/null + + # Login + curl -sf -X POST "$APPWRITE_URL/account/sessions/email" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -c "$COOKIE_JAR" \ + -b "$COOKIE_JAR" \ + -d '{"email":"admin@test.local","password":"password1234"}' \ + > /dev/null + + # Create team + TEAM_ID=$(curl -sf -X POST "$APPWRITE_URL/teams" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -b "$COOKIE_JAR" \ + -d '{"teamId":"unique()","name":"Testing"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['\$id'])") + + # Create project + curl -sf -X POST "$APPWRITE_URL/projects" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -b "$COOKIE_JAR" \ + -d "{\"projectId\":\"tf-test\",\"name\":\"Terraform Tests\",\"teamId\":\"$TEAM_ID\"}" \ + > /dev/null + + # Create API key with all scopes + ALL_SCOPES='["sessions.write","users.read","users.write","teams.read","teams.write","databases.read","databases.write","collections.read","collections.write","attributes.read","attributes.write","indexes.read","indexes.write","documents.read","documents.write","files.read","files.write","buckets.read","buckets.write","functions.read","functions.write","execution.read","execution.write","locale.read","health.read","avatars.read","webhooks.read","webhooks.write","rules.read","rules.write","messaging.read","messaging.write","providers.read","providers.write","topics.read","topics.write","subscribers.read","subscribers.write","targets.read","targets.write","backups.read","backups.write","sites.read","sites.write","vcs.read","vcs.write","migrations.read","migrations.write"]' + + API_KEY=$(curl -sf -X POST "$APPWRITE_URL/projects/tf-test/keys" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -b "$COOKIE_JAR" \ + -d "{\"name\":\"tf-acceptance-tests\",\"scopes\":$ALL_SCOPES}" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret'])") + + echo "APPWRITE_ENDPOINT=$APPWRITE_URL" >> "$GITHUB_ENV" + echo "APPWRITE_PROJECT_ID=tf-test" >> "$GITHUB_ENV" + echo "APPWRITE_API_KEY=$API_KEY" >> "$GITHUB_ENV" + + rm -f "$COOKIE_JAR" - name: Run acceptance tests run: make acceptance-test timeout-minutes: 20 env: - TF_ACC: "0" - APPWRITE_ENDPOINT: ${{ secrets.APPWRITE_ENDPOINT }} - APPWRITE_PROJECT_ID: ${{ secrets.APPWRITE_PROJECT_ID }} - APPWRITE_API_KEY: ${{ secrets.APPWRITE_API_KEY }} + TF_ACC: "1" + + - name: Dump logs on failure + if: failure() + run: docker compose -f testing/docker-compose.yml logs --tail=100 + + - name: Tear down + if: always() + run: docker compose -f testing/docker-compose.yml down -v --remove-orphans diff --git a/Makefile b/Makefile index 1af8af9..95c953e 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ test: go test ./... -v -count=1 -timeout 10m acceptance-test: - TF_ACC=1 go test ./... -v -count=1 $(TESTARGS) -timeout 120m + TF_ACC=$${TF_ACC:-1} go test ./... -v -count=1 $(TESTARGS) -timeout 120m sweep: @echo "WARNING: This will destroy infrastructure. Use only in development." @@ -42,4 +42,14 @@ clean: docs: go generate ./... -.PHONY: build install test acceptance-test sweep test-compile vet fmt fmt-check lint clean docs +# Self-hosted Appwrite for testing +appwrite-up: + ./testing/bootstrap.sh --up + +appwrite-down: + ./testing/bootstrap.sh --down + +appwrite-test: + ./testing/bootstrap.sh + +.PHONY: build install test acceptance-test sweep test-compile vet fmt fmt-check lint clean docs appwrite-up appwrite-down appwrite-test diff --git a/testing/.gitignore b/testing/.gitignore new file mode 100644 index 0000000..1c783b8 --- /dev/null +++ b/testing/.gitignore @@ -0,0 +1,2 @@ +.env.test +cookies.txt diff --git a/testing/bootstrap.sh b/testing/bootstrap.sh new file mode 100755 index 0000000..632aafb --- /dev/null +++ b/testing/bootstrap.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# +# Bootstrap a self-hosted Appwrite instance for acceptance testing. +# +# Usage: +# ./testing/bootstrap.sh # Start Appwrite + create project + run tests +# ./testing/bootstrap.sh --up # Just start Appwrite and print env vars +# ./testing/bootstrap.sh --down # Tear down the instance +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" +APPWRITE_URL="http://localhost:20080/v1" +COOKIE_JAR=$(mktemp) + +cleanup() { rm -f "$COOKIE_JAR"; } +trap cleanup EXIT + +# ── Helpers ────────────────────────────────────────────────────────────────── + +log() { echo "==> $*"; } +fail() { echo "ERROR: $*" >&2; exit 1; } + +wait_for_appwrite() { + log "Waiting for Appwrite to be ready..." + local retries=60 + while [ $retries -gt 0 ]; do + if curl -sf "$APPWRITE_URL/health" > /dev/null 2>&1; then + log "Appwrite is ready!" + return 0 + fi + retries=$((retries - 1)) + sleep 2 + done + fail "Appwrite did not become ready within 120 seconds" +} + +api() { + local method="$1" path="$2" + shift 2 + curl -sf -X "$method" "$APPWRITE_URL$path" \ + -H "Content-Type: application/json" \ + -H "X-Appwrite-Project: console" \ + -b "$COOKIE_JAR" \ + -c "$COOKIE_JAR" \ + "$@" +} + +# ── Commands ───────────────────────────────────────────────────────────────── + +cmd_down() { + log "Tearing down Appwrite..." + docker compose -f "$COMPOSE_FILE" down -v --remove-orphans + log "Done." +} + +cmd_up() { + log "Starting Appwrite..." + docker compose -f "$COMPOSE_FILE" up -d --remove-orphans + + wait_for_appwrite + + # 1. Create admin account (first user = admin) + log "Creating admin account..." + api POST /account \ + -d '{"userId":"unique()","email":"admin@test.local","password":"password1234","name":"Admin"}' \ + > /dev/null 2>&1 || true + + # 2. Create session + log "Logging in..." + api POST /account/sessions/email \ + -d '{"email":"admin@test.local","password":"password1234"}' \ + > /dev/null || fail "Could not create session" + + # 3. Create a team (required for project) + log "Creating team..." + TEAM_ID=$(api POST /teams \ + -d '{"teamId":"unique()","name":"Testing"}' \ + 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['\$id'])" 2>/dev/null || echo "") + + if [ -z "$TEAM_ID" ]; then + # Team may already exist from a previous run, fetch it + TEAM_ID=$(api GET /teams \ + 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['teams'][0]['\$id'])" 2>/dev/null || echo "") + fi + + [ -z "$TEAM_ID" ] && fail "Could not create or find a team" + log "Team ID: $TEAM_ID" + + # 4. Create project + log "Creating project..." + PROJECT_ID=$(api POST /projects \ + -d "{\"projectId\":\"tf-test\",\"name\":\"Terraform Tests\",\"teamId\":\"$TEAM_ID\"}" \ + 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['\$id'])" 2>/dev/null || echo "") + + if [ -z "$PROJECT_ID" ]; then + PROJECT_ID="tf-test" + log "Project may already exist, using ID: $PROJECT_ID" + else + log "Project ID: $PROJECT_ID" + fi + + # 5. Create API key with all scopes + log "Creating API key..." + ALL_SCOPES='["sessions.write","users.read","users.write","teams.read","teams.write","databases.read","databases.write","collections.read","collections.write","attributes.read","attributes.write","indexes.read","indexes.write","documents.read","documents.write","files.read","files.write","buckets.read","buckets.write","functions.read","functions.write","execution.read","execution.write","locale.read","health.read","avatars.read","webhooks.read","webhooks.write","rules.read","rules.write","messaging.read","messaging.write","providers.read","providers.write","topics.read","topics.write","subscribers.read","subscribers.write","targets.read","targets.write","backups.read","backups.write","sites.read","sites.write","vcs.read","vcs.write","migrations.read","migrations.write"]' + + API_KEY=$(api POST "/projects/$PROJECT_ID/keys" \ + -d "{\"name\":\"tf-acceptance-tests\",\"scopes\":$ALL_SCOPES}" \ + 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['secret'])" 2>/dev/null || echo "") + + if [ -z "$API_KEY" ]; then + # Key may already exist, list keys + API_KEY=$(api GET "/projects/$PROJECT_ID/keys" \ + 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['keys'][0]['secret'])" 2>/dev/null || echo "") + fi + + [ -z "$API_KEY" ] && fail "Could not create or find an API key" + + # 6. Export env vars + cat < "$SCRIPT_DIR/.env.test" < + mysqld + --innodb-flush-method=fsync + --character-set-server=utf8mb4 + --collation-server=utf8mb4_unicode_ci + + redis: + image: redis:7.2.4-alpine + <<: *x-logging + restart: unless-stopped + networks: + - appwrite + command: > + redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru + volumes: + - appwrite-redis:/data + +networks: + appwrite: + driver: bridge + +volumes: + appwrite-mariadb: + appwrite-redis: + appwrite-cache: + appwrite-uploads: + appwrite-certificates: + appwrite-functions: + appwrite-builds: + appwrite-config: + appwrite-sites: + openruntimes-builds: + openruntimes-functions: From c35ba0ca8bb7a92d47ee9c4ac26039c2a713be5a Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Tue, 21 Apr 2026 08:00:25 +0200 Subject: [PATCH 3/3] chore: rename --- .github/workflows/test.yml | 6 +++--- testing/bootstrap.sh | 2 +- testing/{docker-compose.yml => docker-compose.yaml} | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename testing/{docker-compose.yml => docker-compose.yaml} (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a448ecf..6b44bf0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: sleep 2 done echo "Appwrite did not become ready" - docker compose -f testing/docker-compose.yml logs appwrite + docker compose -f testing/docker-compose.yaml logs appwrite exit 1 - name: Bootstrap project and API key @@ -99,8 +99,8 @@ jobs: - name: Dump logs on failure if: failure() - run: docker compose -f testing/docker-compose.yml logs --tail=100 + run: docker compose -f testing/docker-compose.yaml logs --tail=100 - name: Tear down if: always() - run: docker compose -f testing/docker-compose.yml down -v --remove-orphans + run: docker compose -f testing/docker-compose.yaml down -v --remove-orphans diff --git a/testing/bootstrap.sh b/testing/bootstrap.sh index 632aafb..c673a3c 100755 --- a/testing/bootstrap.sh +++ b/testing/bootstrap.sh @@ -10,7 +10,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" +COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yaml" APPWRITE_URL="http://localhost:20080/v1" COOKIE_JAR=$(mktemp) diff --git a/testing/docker-compose.yml b/testing/docker-compose.yaml similarity index 100% rename from testing/docker-compose.yml rename to testing/docker-compose.yaml