Skip to content

chore(deps)(deps-dev): bump the lint group across 1 directory with 2 updates #128

chore(deps)(deps-dev): bump the lint group across 1 directory with 2 updates

chore(deps)(deps-dev): bump the lint group across 1 directory with 2 updates #128

name: full-stack-smoke
# Boots the entire dev stack (postgres + valkey + api-dev + ui-dev + traefik)
# via docker compose and exercises the register → login → /me happy path.
# Requires monorepo apps/api and apps/ui (compose build contexts).
# Triggered manually or on relevant pushes; slower than validate-compose.
on:
push:
branches: [main]
paths:
- "infra/compose/**"
- "apps/api/**"
- "apps/ui/**"
- ".github/workflows/infra-compose-full-stack-smoke.yml"
pull_request:
paths:
- "infra/compose/**"
- "apps/api/**"
- "apps/ui/**"
- ".github/workflows/infra-compose-full-stack-smoke.yml"
workflow_dispatch:
concurrency:
group: infra-compose-full-stack-smoke-full-stack-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
smoke:
name: register → login → /me through the full stack
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout monorepo
uses: actions/checkout@v4
- name: Seed compose/.env
working-directory: infra/compose/compose
run: |
cp .env.example .env
# Inject the demo-user credentials the api-migrate one-shot seeds on
# first boot. The smoke steps below log in as this user; without
# these vars the seed script silently skips and login returns 401.
{
echo ""
echo "# Smoke-test only — never use these in real deployments."
echo "SUPERUSER_EMAIL=demo@example.com"
echo "SUPERUSER_PASSWORD=password123"
} >> .env
- name: Make scripts executable
run: |
chmod +x infra/compose/compose/dev.sh
chmod +x infra/compose/scripts/*.sh
- name: Boot the dev stack
working-directory: infra/compose/compose
run: ./dev.sh up -d --build
- name: Wait for API health
run: |
for i in {1..60}; do
if curl -fsS http://localhost:3000/health > /dev/null 2>&1; then
echo "API healthy after ${i}s"
break
fi
sleep 1
done
curl -fsS http://localhost:3000/health
- name: Wait for UI
run: |
for i in {1..60}; do
if curl -fsS http://localhost:3001/ > /dev/null 2>&1; then
echo "UI ready after ${i}s"
break
fi
sleep 1
done
curl -fsSI http://localhost:3001/ | head -1
- name: Smoke — demo user can log in (seeded by api-migrate)
run: |
# The api-migrate one-shot seeds demo@example.com / password123 on
# first boot. We verify the login round-trip through the Vite
# proxy (so we also confirm the proxy path works end-to-end).
#
# We deliberately do NOT use `curl -f` here: when the server returns
# a 4xx/5xx the response body almost always tells us why, and -f
# discards it. Instead we capture the status code via -w and assert
# on it explicitly, then dump body + status if anything looks wrong.
set -euo pipefail
run_curl() {
local label="$1" url="$2" expected_status="$3"
shift 3
local out status body
out=$(curl -sS -o /tmp/body -w '%{http_code}' "$url" "$@") || {
echo "❌ ${label}: curl failed (exit $?). url=${url}" >&2
cat /tmp/body >&2 || true
return 1
}
status="$out"
body="$(cat /tmp/body)"
if [ "$status" != "$expected_status" ]; then
echo "❌ ${label}: expected HTTP ${expected_status}, got ${status}" >&2
echo "URL: ${url}" >&2
echo "BODY: ${body}" >&2
return 1
fi
echo "✅ ${label}: HTTP ${status}"
echo "$body"
}
LOGIN_BODY=$(run_curl "login" \
"http://localhost:3001/api/v1/auth/login" 200 \
-c /tmp/cookie.jar \
-X POST -H "Content-Type: application/json" \
-d '{"email":"demo@example.com","password":"password123"}')
echo "$LOGIN_BODY" | grep -q '"success":true' || {
echo "❌ login body missing success:true — got: $LOGIN_BODY" >&2
exit 1
}
ME_BODY=$(run_curl "me" \
"http://localhost:3001/api/v1/users/me" 200 \
-b /tmp/cookie.jar)
echo "$ME_BODY" | grep -q "demo@example.com" || {
echo "❌ /me body missing demo@example.com — got: $ME_BODY" >&2
exit 1
}
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14
- name: Check UI OpenAPI schema is up to date
working-directory: apps/ui
run: |
# The ui-dev container runs as root and writes into the bind-mounted
# apps/ui node_modules from the container, so the host runner can't recreate it.
# Drop root-owned bits before installing as the runner user.
sudo rm -rf node_modules
bun install --frozen-lockfile
bun run generate:api:check
- name: Run UI Playwright E2E
working-directory: apps/ui
timeout-minutes: 12
env:
PLAYWRIGHT_REUSE_SERVER: "true"
run: |
bun run e2e:install:chromium
bun run e2e:ci:chromium
- name: Smoke — register a brand-new user
run: |
set -e
BODY='{"email":"ci-smoke@example.com","password":"Hunter2Strong!","firstName":"CI","lastName":"Smoke"}'
OUT=$(curl -fsS -X POST -H "Content-Type: application/json" -d "$BODY" \
http://localhost:3001/api/v1/auth/register)
echo "$OUT" | grep -q '"success":true'
- name: Smoke — dashboard endpoints return real data
run: |
set -e
# Log in to get a cookie:
curl -fsS -c /tmp/cookie.jar -X POST -H "Content-Type: application/json" \
-d '{"email":"demo@example.com","password":"password123"}' \
http://localhost:3001/api/v1/auth/login > /dev/null
# Summary should now reflect >=1 user + >=1 widget after seed/register.
SUM=$(curl -fsS -b /tmp/cookie.jar http://localhost:3001/api/v1/dashboard/summary)
echo "$SUM" | grep -q '"totalEvents"'
echo "$SUM" | grep -q '"recentActivity"'
- name: Compose ps on failure
if: failure()
working-directory: infra/compose/compose
run: |
docker compose -f docker-compose.yml -f docker-compose.development-labels.yml --profile dev ps
- name: Container logs on failure
if: failure()
run: |
# Full logs (not --tail) so request-time errors aren't hidden
# behind startup chatter.
for c in $(docker ps -aq); do
echo "===== $(docker inspect --format '{{.Name}}' "$c") ====="
docker logs "$c" 2>&1 || true
done
- name: Tear down
if: always()
working-directory: infra/compose/compose
run: |
./dev.sh down -v || true