diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 15b9be0..7eaab1a 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -56,10 +56,40 @@ jobs: - run: npm run test:run - run: npm run migrate:smoke - run: npm run build - - run: docker build --target runner . + - run: docker build --target runner -t auth-runner:ci . - run: docker build --target worker . + # The image that ships is the one under test: nonce CSP, HSTS, the + # standalone server and migrate-on-boot only exist in production mode. + # --network host lets the container reach the service containers on + # localhost and lets Playwright reach it on 127.0.0.1:3000. The + # Turnstile pair is Cloudflare's documented always-pass test keypair. + - name: Boot the runner image + run: | + docker run -d --name auth-runner --network host \ + -e DATABASE_URL -e REDIS_URL -e OIDC_PRIVATE_KEY_PEM -e OIDC_KEY_ID \ + -e AUTH_BASE_URL=http://127.0.0.1:3000 \ + -e OAUTH_CSRF_SECRET=ci-oauth-csrf-secret \ + -e TELEGRAM_BOT_WEBHOOK_SECRET=ci-telegram-webhook-secret \ + -e TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA \ + -e TURNSTILE_SITE_KEY=1x00000000000000000000AA \ + auth-runner:ci - run: npx playwright install --with-deps chromium + - name: Wait for the runner to be ready + run: | + for i in $(seq 1 45); do + if curl -fsS http://127.0.0.1:3000/api/health/ready; then exit 0; fi + sleep 2 + done + echo "runner image did not become ready" >&2 + docker logs auth-runner + exit 1 - run: npm run test:e2e + env: + PLAYWRIGHT_BASE_URL: http://127.0.0.1:3000 + - if: failure() + run: docker logs auth-runner + - if: always() + run: docker rm -f auth-runner sdk: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 7b98d54..cf15a47 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,14 @@ With `REDIS_URL` set, rate-limit counters live in Redis and are shared across test files within one run, so a test that exercises a rate-limited path must use keys unique to itself. -CI (`.github/workflows/security.yml`) runs the unit and integration suites, -the Playwright end-to-end scenarios against Postgres and Redis services, `npm audit`, +`npm run test:e2e` starts the dev server on port 3100 by itself. Set +`PLAYWRIGHT_BASE_URL` to point the suite at an already-running server +instead (CI sets it to the booted production image). + +CI (`.github/workflows/security.yml`) runs the unit and integration suites +against Postgres and Redis services, boots the production runner image +against the same services and runs the Playwright end-to-end scenarios +against it, and runs `npm audit`, the migration smoke test, the production build, and both Docker image builds on every push. The `sdk` job separately builds and type-checks the Node SDK. diff --git a/playwright.config.ts b/playwright.config.ts index a23641c..e4d3b23 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,22 +1,23 @@ import { defineConfig, devices } from "@playwright/test"; +// CI points PLAYWRIGHT_BASE_URL at the booted production image and manages +// that process itself; locally the dev server on 3100 is started (or reused). +const baseURL = process.env.PLAYWRIGHT_BASE_URL || "http://127.0.0.1:3100"; + export default defineConfig({ testDir: "./tests/e2e", timeout: 30_000, use: { - baseURL: "http://127.0.0.1:3100", + baseURL, trace: "on-first-retry", }, - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - ], - webServer: { - command: "npm run dev -- -H 127.0.0.1 -p 3100", - url: "http://127.0.0.1:3100/login", - reuseExistingServer: !process.env.CI, - timeout: 120_000, - }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: process.env.PLAYWRIGHT_BASE_URL + ? undefined + : { + command: "npm run dev -- -H 127.0.0.1 -p 3100", + url: "http://127.0.0.1:3100/login", + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, });