Skip to content

Auth Login Smoke Test #28

Auth Login Smoke Test

Auth Login Smoke Test #28

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'],
});