-
Notifications
You must be signed in to change notification settings - Fork 0
145 lines (129 loc) · 6.43 KB
/
api-contract.yml
File metadata and controls
145 lines (129 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# ═══════════════════════════════════════════════════════════════════════════════
# API Contract Guard — Prevent silent API shape breakage
# Issue #179 — Schema-to-UI Contract Validation (Quality Gate 9/9)
# ═══════════════════════════════════════════════════════════════════════════════
# Runs the RPC contract integration tests that validate the shape of every
# Supabase RPC function called by the frontend using Zod schemas.
# If a migration changes a column name, removes a field, or alters a return
# type, these tests FAIL before the frontend silently breaks.
#
# Contract schemas: frontend/src/lib/rpc-contracts/
# Integration tests: frontend/src/lib/rpc-contracts/__tests__/
# Unit tests: frontend/src/lib/rpc-contracts/__tests__/schema-validation.test.ts
#
# These tests require live Supabase connectivity (staging environment).
# Triggered on:
# • Every push to main (post-merge validation)
# • PRs that touch SQL migrations or contract schemas (pre-merge prevention)
# • Nightly (drift detection)
# ═══════════════════════════════════════════════════════════════════════════════
name: API Contract Guard
on:
push:
branches: [main]
paths:
- "supabase/migrations/**"
- "db/**"
- "frontend/src/lib/rpc-contract*"
- "frontend/src/lib/rpc-contracts/**"
- ".github/workflows/api-contract.yml"
pull_request:
branches: [main]
paths:
- "supabase/migrations/**"
- "db/**"
- "frontend/src/lib/rpc-contract*"
- "frontend/src/lib/rpc-contracts/**"
- ".github/workflows/api-contract.yml"
schedule:
# Nightly 03:30 UTC — detect drift from manual DB changes (staggered from quality-gate)
- cron: "30 3 * * *"
workflow_dispatch: {}
permissions:
contents: read
concurrency:
group: api-contract-${{ github.ref }}
cancel-in-progress: true
jobs:
contract-tests:
name: RPC Contract Validation
runs-on: ubuntu-latest
timeout-minutes: 10
# Skip forks — they never have staging secrets
if: github.event.pull_request.head.repo.fork != true
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# ── Fail-fast: validate required secrets before wasting CI minutes ──
- name: Validate required secrets
working-directory: .
env:
_URL: ${{ secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }}
_KEY: ${{ secrets.SUPABASE_ANON_KEY_STAGING || secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
run: |
missing=0
if [ -z "$_URL" ]; then
echo "::error::Neither SUPABASE_URL_STAGING nor NEXT_PUBLIC_SUPABASE_URL is set"
missing=1
fi
if [ -z "$_KEY" ]; then
echo "::error::Neither SUPABASE_ANON_KEY_STAGING nor NEXT_PUBLIC_SUPABASE_ANON_KEY is set"
missing=1
fi
if [ "$missing" -ne 0 ]; then
echo "::error::Contract tests require live Supabase connectivity — set the secrets above"
exit 1
fi
echo "All required secrets present."
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 20
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- name: Run RPC contract tests
env:
INTEGRATION: "1"
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY_STAGING || secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY_STAGING || secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: |
echo "## API Contract Test Results" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
set +e
npx vitest run rpc-contract 2>&1 | tee /tmp/contract-output.txt
exit_code=${PIPESTATUS[0]}
set -e
echo '```' >> "$GITHUB_STEP_SUMMARY"
cat /tmp/contract-output.txt >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
if [ "$exit_code" -ne 0 ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "❌ **API contract violation detected!**" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "One or more RPC functions returned unexpected shapes." >> "$GITHUB_STEP_SUMMARY"
echo "This means a migration likely changed the API contract." >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**To reproduce locally:**" >> "$GITHUB_STEP_SUMMARY"
echo '```bash' >> "$GITHUB_STEP_SUMMARY"
echo 'cd frontend && npm run test:integration' >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Fix options:**" >> "$GITHUB_STEP_SUMMARY"
echo "1. Fix the migration to preserve the existing contract" >> "$GITHUB_STEP_SUMMARY"
echo "2. Update the frontend to handle the new shape" >> "$GITHUB_STEP_SUMMARY"
echo "3. Update the contract tests if the change is intentional" >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "✅ All API contracts validated successfully." >> "$GITHUB_STEP_SUMMARY"
- name: Verify health endpoint contract
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL_STAGING || secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY_STAGING || secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY_STAGING || secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: |
# Also run the health route unit test which validates response shape
npx vitest run api/health