Skip to content

removed if (applicationForm.value.questions.length >= 5) return #71

removed if (applicationForm.value.questions.length >= 5) return

removed if (applicationForm.value.questions.length >= 5) return #71

name: Docker Setup Integration Test
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: docker-setup-integration-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
integration:
name: Simulate new-user setup (setup.sh → docker compose up)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
# ── 1. Clone ─────────────────────────────────────────────────────────────
- name: Checkout
uses: actions/checkout@v4
# ── 2. setup.sh ──────────────────────────────────────────────────────────
- name: Run setup.sh (new-user step 1)
run: |
chmod +x ./setup.sh
./setup.sh
- name: Verify .env was generated with all required keys
run: |
set -euo pipefail
required=(
BETTER_AUTH_SECRET
BETTER_AUTH_URL
DATABASE_URL
DB_USER
DB_PASSWORD
DB_NAME
S3_ENDPOINT
S3_ACCESS_KEY
S3_SECRET_KEY
S3_BUCKET
STORAGE_USER
STORAGE_PASSWORD
)
missing=()
for key in "${required[@]}"; do
if ! grep -q "^${key}=" .env; then
missing+=("$key")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "❌ Missing keys in .env: ${missing[*]}"
exit 1
fi
echo "✅ All required keys present in .env"
- name: Verify STORAGE_PASSWORD and S3_SECRET_KEY match (prevents credential mismatch bug)
run: |
set -euo pipefail
storage_pass="$(grep '^STORAGE_PASSWORD=' .env | cut -d= -f2-)"
s3_key="$(grep '^S3_SECRET_KEY=' .env | cut -d= -f2-)"
if [ "$storage_pass" != "$s3_key" ]; then
echo "❌ STORAGE_PASSWORD and S3_SECRET_KEY do not match — MinIO uploads will fail"
exit 1
fi
echo "✅ MinIO credentials are consistent"
- name: Verify BETTER_AUTH_SECRET is at least 32 characters
run: |
set -euo pipefail
secret="$(grep '^BETTER_AUTH_SECRET=' .env | cut -d= -f2-)"
len="${#secret}"
if [ "$len" -lt 32 ]; then
echo "❌ BETTER_AUTH_SECRET is only ${len} chars (minimum 32)"
exit 1
fi
echo "✅ BETTER_AUTH_SECRET is ${len} chars"
- name: Verify setup.sh refuses to overwrite existing .env
run: |
set -euo pipefail
if ./setup.sh 2>&1; then
echo "❌ setup.sh should have refused to overwrite .env but exited 0"
exit 1
fi
echo "✅ setup.sh correctly refused to overwrite existing .env"
# ── 3. docker compose up --build ─────────────────────────────────────────
- name: Build image and start all services (new-user step 2)
run: docker compose up --build -d
- name: Wait for db to be healthy
run: |
set -euo pipefail
echo "Waiting for db..."
for i in $(seq 60); do
state="$(docker inspect --format='{{.State.Health.Status}}' reqcore_db 2>/dev/null || echo 'not-started')"
echo " db: $state"
[ "$state" = "healthy" ] && break
if [ "$i" -eq 60 ]; then
echo "❌ db did not become healthy in time"
docker compose logs db --tail=50
exit 1
fi
sleep 3
done
echo "✅ db is healthy"
- name: Wait for minio to be healthy
run: |
set -euo pipefail
echo "Waiting for minio..."
for i in $(seq 60); do
state="$(docker inspect --format='{{.State.Health.Status}}' reqcore_minio 2>/dev/null || echo 'not-started')"
echo " minio: $state"
[ "$state" = "healthy" ] && break
if [ "$i" -eq 60 ]; then
echo "❌ minio did not become healthy in time"
docker compose logs minio --tail=50
exit 1
fi
sleep 3
done
echo "✅ minio is healthy"
- name: Wait for app to be reachable on :3000
run: |
set -euo pipefail
echo "Waiting for app..."
for i in $(seq 60); do
if curl -fs http://localhost:3000 > /dev/null 2>&1; then
echo "✅ App is reachable on http://localhost:3000"
exit 0
fi
state="$(docker inspect --format='{{.State.Status}}' reqcore_app 2>/dev/null || echo 'missing')"
if [ "$state" = "exited" ] || [ "$state" = "dead" ]; then
echo "❌ App container exited unexpectedly"
docker compose logs app --tail=100
exit 1
fi
echo " attempt $i/60 — waiting..."
sleep 3
done
echo "❌ App did not become reachable in time"
docker compose logs app --tail=100
exit 1
# ── 4. Startup log assertions ─────────────────────────────────────────────
- name: Assert DB migrations ran successfully
run: |
set -euo pipefail
if ! docker compose logs app | grep -q "Database migrations applied successfully"; then
echo "❌ Migration success message not found in app logs"
docker compose logs app
exit 1
fi
echo "✅ Migrations applied successfully"
- name: Assert S3 bucket is ready
run: |
set -euo pipefail
if ! docker compose logs app | grep -q 'S3 bucket "reqcore" is ready'; then
echo "❌ S3 bucket ready message not found in app logs"
docker compose logs app
exit 1
fi
echo "✅ S3 bucket is ready"
# ── 5. HTTP smoke tests ───────────────────────────────────────────────────
- name: HTTP smoke tests
run: |
set -euo pipefail
fail=0
check() {
local label="$1" url="$2" expected="$3"
local actual
actual="$(curl -s -o /dev/null -w "%{http_code}" "$url")"
if [ "$actual" = "$expected" ]; then
echo "✅ $label → $actual"
else
echo "❌ $label → expected $expected, got $actual"
fail=1
fi
}
check "Home page" "http://localhost:3000" "200"
check "Sign-in page" "http://localhost:3000/auth/sign-in" "200"
check "Sign-up page" "http://localhost:3000/auth/sign-up" "200"
check "Public job board" "http://localhost:3000/jobs" "200"
check "Blog index" "http://localhost:3000/blog" "200"
check "API/jobs (no auth→401)" "http://localhost:3000/api/jobs" "401"
check "API/candidates (no auth)" "http://localhost:3000/api/candidates" "401"
exit $fail
# ── 6. Seed command (optional step from README) ───────────────────────────
- name: Seed demo data (docker compose exec app npm run db:seed)
run: |
set -euo pipefail
output="$(docker compose exec app npm run db:seed 2>&1)"
echo "$output"
if ! echo "$output" | grep -q "Seed complete"; then
echo "❌ Seed did not complete successfully"
exit 1
fi
echo "✅ Seed completed"
- name: Sign in with seeded demo account (auth smoke test)
run: |
set -euo pipefail
response="$(curl -s -X POST http://localhost:3000/api/auth/sign-in/email \
-H "Content-Type: application/json" \
-d '{"email":"demo@reqcore.com","password":"demo1234"}' \
-w "\n%{http_code}")"
body="$(echo "$response" | head -n -1)"
status="$(echo "$response" | tail -n 1)"
echo "Status: $status"
echo "Body: $body"
if [ "$status" != "200" ]; then
echo "❌ Sign-in failed — expected 200, got $status"
exit 1
fi
if ! echo "$body" | grep -q "demo@reqcore.com"; then
echo "❌ Response body does not contain expected email"
exit 1
fi
echo "✅ Demo account sign-in succeeded"
- name: Seed idempotency — re-running seed must not crash
run: |
set -euo pipefail
output="$(docker compose exec app npm run db:seed 2>&1)"
echo "$output"
if echo "$output" | grep -qi "^npm error\|unhandledRejection\|UnhandledPromiseRejection"; then
echo "❌ Seed second run produced an error"
exit 1
fi
echo "✅ Seed is idempotent"
# ── 7. Adminer --profile tools ────────────────────────────────────────────
- name: Start Adminer via --profile tools
run: docker compose --profile tools up -d adminer
- name: Wait for Adminer to respond on :8080
run: |
set -euo pipefail
for i in $(seq 20); do
if curl -fs http://localhost:8080 > /dev/null 2>&1; then
echo "✅ Adminer is reachable on http://localhost:8080"
exit 0
fi
sleep 2
done
echo "❌ Adminer did not become reachable"
docker compose logs adminer --tail=30
exit 1
# ── 8. Restart resilience (migrations must be idempotent) ────────────────
- name: Restart app and verify it comes back clean
run: |
set -euo pipefail
docker compose restart app
for i in $(seq 30); do
if curl -fs http://localhost:3000 > /dev/null 2>&1; then
echo "✅ App reachable again after restart"
break
fi
if [ "$i" -eq 30 ]; then
echo "❌ App did not come back after restart"
docker compose logs app --tail=50
exit 1
fi
sleep 3
done
if docker compose logs app | grep -q "Migration failed"; then
echo "❌ Migration error found in logs after restart"
docker compose logs app
exit 1
fi
echo "✅ Restart handled cleanly — no migration errors"
# ── Always: dump logs on failure ──────────────────────────────────────────
- name: Dump all service logs
if: always()
run: |
echo "=== docker compose ps ==="
docker compose --profile tools ps || true
echo ""
echo "=== app ==="
docker compose logs app --no-color --tail=200 || true
echo ""
echo "=== db ==="
docker compose logs db --no-color --tail=50 || true
echo ""
echo "=== minio ==="
docker compose logs minio --no-color --tail=50 || true
- name: Tear down all services and volumes
if: always()
run: docker compose --profile tools down -v || true