feat(contracts): versioned schema validation + contract tests for webhooks#82
feat(contracts): versioned schema validation + contract tests for webhooks#82adm01-debug wants to merge 1 commit into
Conversation
…test runner
Establishes a contract-testing framework for Supabase Edge Functions and
applies it to the 3 webhook endpoints (product-webhook, webhook-inbound,
webhook-dispatcher) with full v1/v2 versioning.
Foundation:
- _shared/version-dispatch.ts: path-based version resolver (/v1, /v2);
defaults to v1 for back-compat. Distinguishes the Supabase mount prefix
/functions/v1 from the contract version suffix.
- _shared/error-response.ts: dual error builders. V1 preserves the legacy
{error, details} 400 shape byte-for-byte (n8n compat). V2 introduces
RFC-7807-inspired {code, message, fields[]} at 422 with
Content-Type: application/problem+json. Includes a snapshot regression
guard test for V1.
- _shared/zod-validate.ts: adds parseBodyVersioned() alongside the unchanged
parseBodyWithSchema(); dispatches v1/v2 by path.
Webhooks (T1):
- product-webhook: extracted handler; v1 keeps n8n shape; v2 adds required
idempotency_key + metadata.source + strict() + errors_by_sku in response.
- webhook-inbound: introduces Zod validation (first time); v1 is lenient
(passthrough); v2 requires request_id (uuid) + strict envelope.
- webhook-dispatcher: extracted schema to schemas.ts; v1 matches old inline
BodySchema; v2 adds required correlation_id + dispatch_options (parallel,
timeout_ms).
All 3 expose 'export const handler' so unit tests can invoke without binding
a port; Deno.serve runs only when import.meta.main is true (prod entry).
Test layers:
- Deno unit tests (78 cases) cover happy path, missing fields, wrong types,
empty values, invalid enums/UUIDs, unknown keys (strict), CORS preflight,
auth failure, and version-aware response shape. Sanitizers disabled per
test via a small t() wrapper because supabase-js starts internal timers.
- Node runner scripts/contract-testing.mjs auto-discovers contract.json
manifests and runs a 31-scenario v1/v2 matrix. Modes: --simulate (default,
no network, CI-safe), --live (POST against deployment), --baseline
(records v1 response hashes).
- scripts/check-contract-coverage.mjs reports schema/test/manifest coverage
per tier and gates CI on the webhook tier. Exceptions live in
scripts/contract-exceptions.json (21 functions, mostly crons + diagnostics).
- scripts/generate-contract-stub.mjs scaffolds schemas.ts, index.test.ts and
contract.json for new functions — used by T2-T4 PRs.
npm scripts: test:contracts[:live|:baseline|:verbose|:deno],
check:contract-coverage[:strict], generate:contract-stub.
Docs: docs/CONTRACT_TESTING.md explains the pattern and the rollout plan
(T0 helpers + T1 webhooks land here; T2 13 public functions, T3 12 crons,
T4 ~52 JWT functions follow in subsequent PRs).
V1 anti-regression:
- error-response.test.ts pins the {error, details} shape with status 400
and Content-Type application/json.
- scripts/__contracts__/v1-baseline.json records SHA-256 of v1 responses
per scenario; PRs that change v1 must add label 'breaking-v1'.
https://claude.ai/code/session_01TZcZo79a7Wwr4rUVtwyfB3
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Updates to Preview Branch (claude/webhook-contract-tests-swzNU) ↗︎
Tasks are run on every commit but only new migration files are pushed.
❌ Branch Error • Fri, 22 May 2026 02:49:55 UTC View logs for this Workflow Run ↗︎. |
Summary
Establishes a contract-testing framework for the ~80 Supabase Edge Functions and applies it end-to-end to the 3 webhook endpoints (
product-webhook,webhook-inbound,webhook-dispatcher) with full v1/v2 path-based versioning.This PR delivers Tier 0 (foundation) and Tier 1 (webhooks) from the rollout plan in
docs/CONTRACT_TESTING.md. Tiers T2-T4 will follow in subsequent PRs usingnpm run generate:contract-stub.Foundation (
_shared/)version-dispatch.ts/v1,/v2); defaults tov1. Correctly distinguishes the Supabase mount prefix/functions/v1from the contract version suffix by taking the last/vNsegment.error-response.ts{error, details}400 shape byte-for-byte (n8n compat, with snapshot regression guard). V2 introduces RFC-7807-inspired{code, message, fields[]}at 422 withContent-Type: application/problem+json.zod-validate.tsparseBodyVersioned()alongside the unchangedparseBodyWithSchema(). Dispatches v1/v2 by path.Webhooks (Tier 1)
product-webhookidempotency_key, requiredmetadata.source,.strict(), response addserrors_by_skuwebhook-inboundrequest_id(UUID), requiredpayload,.strict()webhook-dispatcherschemas.tscorrelation_id(UUID), optionaldispatch_options.{parallel, timeout_ms},.strict(), response addscorrelation_idEach handler is now exported (
export const handler) so unit tests can invoke it without binding a port.Deno.serve(handler)runs only whenimport.meta.mainistrue(production entry).Test layers
X-Contract-Version-Servedheader). Sanitizers disabled per test via a smallt()wrapper becausesupabase-jsstarts internal keep-alive timers.scripts/contract-testing.mjs(31 scenarios, all passing in simulate mode) — auto-discoverscontract.jsonmanifests and runs a v1/v2 matrix. Modes:--simulate(default, no network, CI-safe)--live(POST against deployment, verifies status + response shape)--baseline(records SHA-256 of v1 responses toscripts/__contracts__/v1-baseline.json)scripts/check-contract-coverage.mjs— reports schema/test/manifest coverage per tier; fails CI on the webhook tier (T1), warn-only on the rest until T2-T4 land. Exceptions live inscripts/contract-exceptions.json(21 functions, mostly crons + diagnostics).scripts/generate-contract-stub.mjs— scaffoldsschemas.ts,index.test.tsandcontract.jsonfor a new function withnpm run generate:contract-stub <name> [--v2].NPM scripts added
V1 anti-regression
error-response.test.tsincludes a snapshot test pinning the{error, details}shape at HTTP 400 /application/json.scripts/__contracts__/v1-baseline.jsonwill accumulate SHA-256 of v1 responses per scenario (populated by--baselinemode). PRs altering v1 must add abreaking-v1label.parseBodyWithSchemahelper is unchanged; the ~40 functions using it are not impacted.Coverage report at HEAD
Hard fails: 0. Webhook tier is fully covered.
Test plan
npm run check:contract-coverage— passes, 0 hard failsnpm run test:contracts— 31/31 scenarios pass in simulate modedeno test --allow-env --allow-net --allow-read supabase/functions/_shared/*.test.ts supabase/functions/{product-webhook,webhook-inbound,webhook-dispatcher}/index.test.ts— 78/78 passnpm run test:contracts:liveagainst staging — verify real 400/422 responsesnpm run test:contracts:baselineand commit the resulting baseline JSONFollow-up PRs
verify_jwt=false) functions get v1 schemas + tests (usegenerate:contract-stub)contract-exceptions.json)See
docs/CONTRACT_TESTING.mdfor the full pattern and roadmap.https://claude.ai/code/session_01TZcZo79a7Wwr4rUVtwyfB3
Generated by Claude Code
Summary by cubic
Adds path-based v1/v2 schema validation and a contract-testing framework for our webhooks, keeping v1 behavior stable while enabling stricter v2 contracts. This sets the foundation for rolling the pattern across all Edge Functions.
New Features
{error,details}; v2 422application/problem+jsonwith{code,message,fields[]}) viaparseBodyVersioned().product-webhook,webhook-inbound, andwebhook-dispatcher.--simulate|--live|--baseline), coverage gate for webhook tier, stub generator, and docs indocs/CONTRACT_TESTING.md.test:contracts*,check:contract-coverage*, andgenerate:contract-stubinpackage.json.Migration
/functions/v1/<endpoint>/v2and update payloads:product-webhook: addidempotency_keyandmetadata.source; unknown keys rejected.webhook-inbound: addrequest_id(UUID); strict envelope.webhook-dispatcher: addcorrelation_id(UUID); optionaldispatch_options.{parallel,timeout_ms}; response echoescorrelation_id.npm run test:contracts:liveand thennpm run test:contracts:baselineto capture v1 baselines.Written for commit 66783e3. Summary will update on new commits. Review in cubic