name: Preview smoke

# Runs Playwright smoke against the homelab deployment (Dokploy on CT 112,
# routed via Traefik at deckhearth.stillwell.cloud). Until per-PR preview
# deploy exists on Dokploy, smoke validates the live homelab app after merge.

on:
  pull_request:
    branches: [main]
    types: [labeled, opened, synchronize, reopened]
    # Skip the heavy Playwright smoke on doc-only PRs. Same set as
    # ci.yml's paths-ignore — see `slash-ci-minutes` convoy
    # (2026-06-04). Code/style/UI PRs still trigger smoke normally.
    paths-ignore:
      - '.convoys/**'
      - '**/*.md'
      - 'docs/**'
      - 'AGENTS.md'
      - '.cursor/**'
      - 'README.md'

concurrency:
  group: preview-smoke-${{ github.event.pull_request.number }}
  cancel-in-progress: true

permissions:
  contents: read
  pull-requests: read

jobs:
  gate:
    name: Should run?
    runs-on: [self-hosted, axiom]
    outputs:
      should_run: ${{ steps.check.outputs.should_run }}
    steps:
      - name: Decide
        id: check
        env:
          # Route PR body + fork flag through env vars instead of inline
          # ${{ }} interpolation. Direct ${{ github.event.pull_request.body }}
          # in a shell command pastes arbitrary user-controlled text (parens,
          # backticks, pipes, heredocs) directly into the script — both a
          # syntax-error risk AND a shell-injection vector. Quoting the env
          # vars below makes both safe.
          PR_BODY: ${{ github.event.pull_request.body }}
          PR_IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
        run: |
          if [[ "$PR_IS_FORK" == "true" ]]; then
            echo "should_run=false" >> $GITHUB_OUTPUT
            echo "::notice::Smoke skipped on fork PR (bypass secret unavailable to forks)"
          elif echo "$PR_BODY" | grep -qE 'pipeline:.*skip.*\bsmoke\b'; then
            echo "should_run=false" >> $GITHUB_OUTPUT
            echo "::notice::Smoke skipped via pipeline directive"
          else
            echo "should_run=true" >> $GITHUB_OUTPUT
          fi

  smoke:
    name: Playwright smoke
    needs: gate
    if: needs.gate.outputs.should_run == 'true'
    runs-on: [self-hosted, axiom]
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Wait for homelab app health
        run: |
          BASE_URL="${SMOKE_BASE_URL:-https://deckhearth.stillwell.cloud}"
          for i in $(seq 1 30); do
            if curl -fsS "${BASE_URL}/api/health" >/dev/null; then
              echo "Homelab app healthy at ${BASE_URL}"
              exit 0
            fi
            echo "Waiting for ${BASE_URL}/api/health (${i}/30)…"
            sleep 10
          done
          echo "::error::Homelab app did not become healthy in time"
          exit 1
        env:
          SMOKE_BASE_URL: ${{ vars.SMOKE_BASE_URL }}

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: npm

      - name: Cache node_modules
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: node_modules
          key: node-modules-${{ runner.os }}-node20-${{ hashFiles('package-lock.json') }}

      - run: npm ci
        if: steps.cache-node-modules.outputs.cache-hit != 'true'

      # Cache Playwright's browser binaries (~/.cache/ms-playwright) keyed
      # by the resolved @playwright/test version in the lockfile. Saves
      # ~15-25s/run of chromium download + system-deps install. Cache
      # invalidates automatically on any Playwright version bump.
      - name: Resolve Playwright version
        id: pw-version
        run: |
          VERSION=$(node -p "require('./package-lock.json').packages['node_modules/@playwright/test'].version")
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
      - name: Cache Playwright browsers
        id: cache-playwright
        uses: actions/cache@v4
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}-chromium

      - name: Install Playwright browsers
        run: |
          if [ "${{ steps.cache-playwright.outputs.cache-hit }}" = "true" ]; then
            npx playwright install-deps chromium
          else
            npx playwright install --with-deps chromium
          fi

      - name: Run smoke tests
        run: npx playwright test --project=smoke
        env:
          BASE_URL: ${{ vars.SMOKE_BASE_URL || 'https://deckhearth.stillwell.cloud' }}

      - name: Upload Playwright report on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7
