Auth Login Smoke Test #31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auth Login Smoke Test | |
| # Verifies the auth login contract by building and running the Go backend | |
| # in dev mode. No external dependencies — everything runs against localhost. | |
| # | |
| # Catches regressions like commit 25e464fa (#6593) which removed the | |
| # "token" field from /auth/refresh and broke login for 24 hours. | |
| # | |
| # What it checks: | |
| # 1. /health returns JSON with status and oauth_configured fields | |
| # 2. GET /auth/github (dev mode, no OAuth client) sets a kc_auth cookie | |
| # 3. POST /auth/refresh with that cookie returns { token, onboarded } | |
| # — the exact contract the frontend depends on | |
| # | |
| # The test uses DEV_MODE (no GitHub OAuth credentials) so the backend | |
| # auto-creates a dev-user on /auth/github. This takes ~60 seconds. | |
| on: | |
| schedule: | |
| # Every hour | |
| - cron: "0 * * * *" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| concurrency: | |
| group: auth-login-smoke | |
| cancel-in-progress: true | |
| jobs: | |
| auth-smoke: | |
| if: github.repository == 'kubestellar/console' | |
| name: Auth Login Smoke | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| # ── 1. Build the Go backend ────────────────────────────────── | |
| - name: Build backend | |
| run: go build -o console-bin ./cmd/console | |
| # ── 2. Start backend in dev mode ───────────────────────────── | |
| - name: Start backend | |
| run: | | |
| JWT_SECRET="smoke-test-$(openssl rand -hex 16)" \ | |
| DEV_MODE=true \ | |
| ./console-bin --dev --port 8081 & | |
| echo $! > /tmp/backend.pid | |
| # ── 3. Wait for health ─────────────────────────────────────── | |
| - name: Wait for backend health | |
| run: | | |
| MAX_WAIT_SECONDS=30 | |
| for i in $(seq 1 "$MAX_WAIT_SECONDS"); do | |
| if curl -sf http://localhost:8081/health > /dev/null 2>&1; then | |
| echo "Backend healthy after ${i}s" | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| echo "::error::Backend did not become healthy within ${MAX_WAIT_SECONDS}s" | |
| exit 1 | |
| # ── 4. Test /health returns JSON with expected fields ──────── | |
| - name: Test /health endpoint | |
| run: | | |
| echo "Checking /health endpoint..." | |
| HEALTH_RESP=$(curl -sS --max-time 5 http://localhost:8081/health) | |
| echo "Response: $HEALTH_RESP" | |
| STATUS=$(echo "$HEALTH_RESP" | jq -r '.status // empty') | |
| if [ -z "$STATUS" ]; then | |
| echo "::error::/health response missing 'status' field" | |
| exit 1 | |
| fi | |
| echo "status=$STATUS" | |
| # In dev mode with no OAuth creds, oauth_configured should be false | |
| OAUTH=$(echo "$HEALTH_RESP" | jq -r '.oauth_configured // empty') | |
| echo "oauth_configured=$OAUTH" | |
| echo "/health OK" | |
| # ── 5. Dev-mode login via /auth/github ─────────────────────── | |
| - name: Get dev-mode auth cookie | |
| run: | | |
| echo "Calling GET /auth/github (dev mode login)..." | |
| # Dev mode redirects and sets kc_auth HttpOnly cookie. | |
| # Use -c to capture the cookie jar, -L to NOT follow redirects | |
| # (we just need the Set-Cookie header). | |
| COOKIE_JAR=/tmp/cookies.txt | |
| HTTP_CODE=$(curl -sS --max-time 5 \ | |
| -o /dev/null -w '%{http_code}' \ | |
| -c "$COOKIE_JAR" \ | |
| http://localhost:8081/auth/github) | |
| echo "HTTP status: $HTTP_CODE (expect 307 redirect)" | |
| # Extract the kc_auth cookie value | |
| KC_AUTH=$(grep 'kc_auth' "$COOKIE_JAR" | awk '{print $NF}') | |
| if [ -z "$KC_AUTH" ]; then | |
| echo "::error::Dev-mode login did not set kc_auth cookie" | |
| echo "Cookie jar contents:" | |
| cat "$COOKIE_JAR" | |
| exit 1 | |
| fi | |
| echo "Got kc_auth cookie (${#KC_AUTH} chars)" | |
| echo "$KC_AUTH" > /tmp/kc_auth_token.txt | |
| # ── 6. Test /auth/refresh contract ─────────────────────────── | |
| - name: Test /auth/refresh contract | |
| run: | | |
| KC_AUTH=$(cat /tmp/kc_auth_token.txt) | |
| echo "Testing POST /auth/refresh..." | |
| REFRESH_RESP=$(curl -sS --max-time 5 \ | |
| -X POST \ | |
| -H "Authorization: Bearer $KC_AUTH" \ | |
| -H "X-Requested-With: XMLHttpRequest" \ | |
| http://localhost:8081/auth/refresh) | |
| echo "Response: $REFRESH_RESP" | |
| # CONTRACT: response must contain "token" (non-empty string) | |
| # This is the exact regression that broke us in commit 25e464fa (#6593) | |
| HAS_TOKEN=$(echo "$REFRESH_RESP" | jq -r '.token // empty') | |
| if [ -z "$HAS_TOKEN" ]; then | |
| echo "::error::CONTRACT VIOLATION: /auth/refresh response missing 'token' field — OAuth login is BROKEN" | |
| echo "This is the same regression as commit 25e464fa (#6593)." | |
| echo "Response was: $REFRESH_RESP" | |
| exit 1 | |
| fi | |
| echo "token field present (${#HAS_TOKEN} chars)" | |
| # CONTRACT: response must contain "onboarded" (boolean) | |
| HAS_ONBOARDED=$(echo "$REFRESH_RESP" | jq 'has("onboarded")') | |
| if [ "$HAS_ONBOARDED" != "true" ]; then | |
| echo "::error::CONTRACT VIOLATION: /auth/refresh response missing 'onboarded' field" | |
| echo "Response was: $REFRESH_RESP" | |
| exit 1 | |
| fi | |
| echo "onboarded field present" | |
| # CONTRACT: response must contain "refreshed" (boolean, true) | |
| HAS_REFRESHED=$(echo "$REFRESH_RESP" | jq -r '.refreshed // empty') | |
| if [ "$HAS_REFRESHED" != "true" ]; then | |
| echo "::error::CONTRACT VIOLATION: /auth/refresh response missing 'refreshed' field" | |
| echo "Response was: $REFRESH_RESP" | |
| exit 1 | |
| fi | |
| echo "refreshed field present" | |
| echo "" | |
| echo "Auth login smoke test PASSED" | |
| # ── 7. Cleanup ─────────────────────────────────────────────── | |
| - name: Stop backend | |
| if: always() | |
| run: | | |
| if [ -f /tmp/backend.pid ]; then | |
| kill "$(cat /tmp/backend.pid)" 2>/dev/null || true | |
| fi | |
| # ── 8. Alert on failure ────────────────────────────────────── | |
| - name: Create issue on failure | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const title = `Auth login smoke test failed — OAuth login contract may be broken`; | |
| const body = [ | |
| '## Auth Login Smoke Test Failure', | |
| '', | |
| `**Run:** ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, | |
| `**Time:** ${new Date().toISOString()}`, | |
| '', | |
| 'The auth contract test failed against a local backend in dev mode.', | |
| 'This means the `/auth/refresh` endpoint is not returning the expected response shape.', | |
| '', | |
| '### What broke last time', | |
| 'Commit `25e464fa` (#6593) removed the `token` field from the response body.', | |
| 'Fixed in `be946db9`. See contract test in `auth_contract_test.go`.', | |
| '', | |
| '### Action required', | |
| '1. Check the workflow run logs above for the specific failure', | |
| '2. Verify the `/auth/refresh` response includes `token`, `onboarded`, and `refreshed`', | |
| '3. Run `go test ./pkg/api/handlers/ -run TestAuthRefreshContract` locally', | |
| ].join('\n'); | |
| // Don't create duplicate issues | |
| const existing = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| labels: 'auth-smoke-failure', | |
| per_page: 1, | |
| }); | |
| if (existing.data.length > 0) { | |
| console.log(`Issue already open: #${existing.data[0].number}`); | |
| return; | |
| } | |
| await github.rest.issues.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title, | |
| body, | |
| labels: ['auth-smoke-failure', 'priority/critical', 'ai-needs-human'], | |
| }); |