name: E2E (preview)

# Pre-merge ephemeral e2e (E2E-6, repointed to the TS server in #805). On a PR to main, stand up
# the WHOLE stack inside the runner - apps/server (fake LLM provider + non-prod test-login) over
# an in-memory ephemeral database, and the web dev server proxying /api to it - then run the
# Playwright suite against it. Fully isolated: nothing is shared, there is no durable database,
# and everything dies with the job. So this catches regressions BEFORE merge without ever
# touching the dev environment or its Supabase data (resolves #159's pollution concern for the
# pre-merge path). The post-deploy `e2e.yml` still guards the deployed dev environment.
#
# The ephemeral database is PGlite (in-memory WASM Postgres) served over a real TCP socket -
# the same harness apps/server's own integration suites use. The server's /api routes are
# DB-gated (app.ts mounts them only when a DSN is configured), so "in-memory" here means an
# in-memory Postgres, not a storeless boot. Schema + the deterministic e2e seed come from the
# same provisioner the per-PR preview lane runs (apps/server/scripts/provision-preview-db.ts).
#
# Make this a required status check (branch protection) to gate merges on it.

on:
  pull_request:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: e2e-preview-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  e2e-preview:
    runs-on: bloom-arc
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4

      # --- Web + server + Playwright (one toolchain: the whole stack is TS now) ---
      - uses: pnpm/action-setup@v4
        with:
          version: 10.6.3

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
          cache-dependency-path: pnpm-lock.yaml

      # Workspace-root install (single lockfile, #481); the server runs from apps/server via
      # tsx and Playwright runs from apps/web.
      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright browser
        working-directory: apps/web
        # ARC ephemeral runners are a minimal image lacking browser system libs; they run as root, so --with-deps installs them per job. (Static-VM runner had libs pre-provisioned + no sudo; that runner is decommissioned by this PR.)
        run: pnpm exec playwright install --with-deps chromium

      # Mirror the hardened preview deployments (#346 follow-up): gate the stack's test-login
      # behind a generated throwaway password so the suite exercises the password path pre-merge.
      # The one shared env var reaches both the server (which requires it) and Playwright (whose
      # signIn fixture / sign-in spec send it); a local run without it stays open and one-step.
      - name: Generate a test-login password for this run
        run: |
          set -euo pipefail
          PW="$(openssl rand -hex 16)"
          echo "::add-mask::$PW"
          echo "BLOOM_TEST_LOGIN_PASSWORD=$PW" >> "$GITHUB_ENV"

      - name: Run e2e against the ephemeral stack
        env:
          # Playwright starts the vite dev server itself (webServer in playwright.config.ts) and its
          # /api proxy points here; E2E_BASE_URL stays unset so the local server is used.
          VITE_DEV_API_TARGET: http://127.0.0.1:8090
          EPHEMERAL_DB_URL: postgres://postgres:postgres@127.0.0.1:55432/postgres
        run: |
          set -euo pipefail
          # Ephemeral in-memory Postgres: PGlite behind a TCP socket (apps/server devDeps). The
          # bootstrap file lives inside apps/server so its imports resolve from that package's
          # node_modules; it is written here, never committed.
          cat > apps/server/pglite-e2e.mjs <<'EOF'
          import { PGlite } from "@electric-sql/pglite";
          import { PGLiteSocketServer } from "@electric-sql/pglite-socket";
          const db = await PGlite.create();
          // maxConnections: the server pools 4 connections (src/db.ts) and the provisioner adds
          // one; the default (1) resets every concurrent connect (ECONNRESET at boot).
          const server = new PGLiteSocketServer({ db, host: "127.0.0.1", port: 55432, maxConnections: 10 });
          await server.start();
          console.log("pglite listening on 55432");
          EOF
          ( cd apps/server && node pglite-e2e.mjs ) &
          # Schema + deterministic seed via the shared provisioner; its connect retry doubles as
          # the wait-for-PGlite (short delay - this database starts in seconds, not Neon-minutes).
          ( cd apps/server && \
              BLOOM_SUPABASE_DB_URL="$EPHEMERAL_DB_URL" \
              PROVISION_CONNECT_DELAY_SECONDS=2 \
              pnpm run provision:preview-db )
          # Ephemeral server: fake LLM (deterministic, offline), test-login enabled
          # (password-gated via BLOOM_TEST_LOGIN_PASSWORD from the job env), the workflow spec
          # read from its apps/server/workflows source of truth (the Docker image bundles the same file).
          # BLOOM_DB_POOL_MAX=1: PGlite's socket multiplexer interleaves concurrent connections
          # over one session, which breaks the driver's prepared statements ("bind message
          # supplies N parameters..."); one pooled connection serializes them, the same `max: 1`
          # pinning the integration suites use.
          ( cd apps/server && \
              BLOOM_SUPABASE_DB_URL="$EPHEMERAL_DB_URL" \
              BLOOM_DB_POOL_MAX=1 \
              BLOOM_STORE_BACKEND=supabase \
              BLOOM_LLM_PROVIDER=fake \
              BLOOM_ENABLE_TEST_LOGIN=true \
              BLOOM_ENV=development \
              BLOOM_SERVER_PORT=8090 \
              BLOOM_WORKFLOW_SPEC_PATH=workflows/bloom-sdlc.workflow.json \
              pnpm exec tsx src/index.ts ) &
          for _ in $(seq 1 30); do
            curl -fsS http://127.0.0.1:8090/health >/dev/null 2>&1 && break
            sleep 1
          done
          curl -fsS http://127.0.0.1:8090/health >/dev/null || { echo "server failed to start" >&2; exit 1; }
          cd apps/web && pnpm run test:e2e

      - name: Upload Playwright report and traces
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@v4
        with:
          name: e2e-preview-report
          path: |
            apps/web/playwright-report
            apps/web/test-results
          retention-days: 7
          if-no-files-found: ignore
