Skip to content

test(contracts): cover webhook, event-replay, analytics, and notification endpoints - #556

Merged
Xhristin3 merged 3 commits into
XStreamRollz:mainfrom
rozemary2026-a11y:feat/issue-534-webhook-contract-tests
Aug 27, 2026
Merged

test(contracts): cover webhook, event-replay, analytics, and notification endpoints#556
Xhristin3 merged 3 commits into
XStreamRollz:mainfrom
rozemary2026-a11y:feat/issue-534-webhook-contract-tests

Conversation

@rozemary2026-a11y

Copy link
Copy Markdown
Contributor

Summary

Closes #534

Adds contract coverage for the API surfaces that shipped after the contract suite was built: GET /webhooks/:id/deliveries, GET /streams/:id/events (replay, #396), GET /streams/:id/analytics, and GET /notifications (POST /webhooks was already pinned by the existing create-webhook contract). The delivery-log contract exercises the seeded subscription → deliveries chain through the existing placeholder mechanism, so no per-contract setup logic is duplicated in the provider suite. The contract schemas now assert the exact field types the SDK declares for WebhookSubscription/WebhookDelivery — the id/streamId string-vs-number choice and the closed event union — and the SDK consumer test pins that equality at compile time in both directions.

Why

The contract suite only pinned streams CRUD and auth. GET /webhooks/:id/deliveries, GET /streams/:id/events, GET /streams/:id/analytics, and GET /notifications were unprotected: a response-shape change (e.g. the id/streamId stringification that already bit Stream/User) would ship with green provider and consumer tests because neither side asserted the shape. The SDK's WebhookDelivery type was hand-maintained against the deliveries endpoint with zero machine-checked coverage.

What was built

File What it contains
tests/contracts/src/schemas.ts New streamEventTypeSchema (closed union), streamEventRecordSchema (typed<StreamEventRecord> — string ids pinned), paginatedStreamEventsSchema, streamAnalyticsSchema, notificationSchema, notificationsPageSchema, paginatedWebhookDeliveriesSchema; webhookSubscriptionSchema/webhookDeliverySchema events tightened from z.string() to the closed event union
tests/contracts/src/webhooks.contract.ts list-webhook-deliveries (seeded webhook → its delivery log), ordered before delete-webhook
tests/contracts/src/streams.contract.ts list-stream-events and get-stream-analytics against the seeded stream
tests/contracts/src/notifications.contract.ts New file: list-notifications
tests/contracts/src/index.ts Registers notificationsContracts
api/src/contract-provider.spec.ts Boots the real NotificationsController + in-memory NotificationsRepository; seeds one recorded stream event and one unread notification so the new contracts validate non-empty shapes
xstreamroll-sdk/src/types.ts New StreamAnalytics, Notification, NotificationsPage types
xstreamroll-sdk/src/client.ts New listDeliveries(), listStreamEvents(), getStreamAnalytics(), listNotifications() methods
xstreamroll-sdk/src/index.ts Exports the new types
xstreamroll-sdk/__tests__/contract.consumer.test.ts Consumer tests for all 4 new contracts + compile-time schema⇄SDK mutual-assignability assertions (AssertEqual in both directions, via the schema's zod _output type so the SDK needs no direct zod dependency)
xstreamroll-sdk/README.md Documents the new methods

Integration changes outside contracts//sdk/

  • Pre-existing bad-merge repairs (identical to PR feat(streams): add q search and tag filtering to stream listing #555's, required for the suite to compile at all): auth refresh() token-string contract + isAdmin claim, gateway authenticate() destructuring, streams pending-event id, duplicate module/import fixes in main.ts/streams.module.ts/audit.module.ts, and stale specs updated to the contracts their sources implement.
  • xstreamroll-sdk/__tests__/client.test.ts — deleted; it tested the removed axios-based client (did not compile) and is superseded by the nock-based client.integration.test.ts.

Acceptance criteria coverage

  • Contracts exist and pass (provider + consumer) for POST /webhooks, GET /webhooks/:id/deliveries, GET /streams/:id/events, GET /streams/:id/analytics, and GET /notifications (contract-provider.spec.ts 19/19; contract.consumer.test.ts 13/13)
  • Schemas assert the exact field types the SDK declares for WebhookSubscription/WebhookDelivery — including the id/streamId string-vs-number choice and the closed event union (contract.consumer.test.ts type-level AssertEqual assertions; verified during development: a deliberate schema narrowing of delivery.id to number fails the consumer suite with TS2344 and the provider suite's schema validation)
  • Dependent contracts (create stream → register webhook → list deliveries) run in order without duplicating setup logic in the provider suite (fixtures seeded once in beforeAll; contracts reference PLACEHOLDER ids and execute in array order)
  • Consumer mocks mirror the provider responses; both suites pass with cd api && npm test and cd xstreamroll-sdk && npm test
  • npm run build --workspace=tests/contracts succeeds; CI contract jobs (api provider, sdk consumer) are green
  • A deliberate breaking change to a covered endpoint fails the suite (manually verified during development — schema id: z.number() vs SDK string | number → TS2344; not committed)

Test plan

  • cd api && npx jest444/444 passing (39/39 suites) against a fresh Postgres 16 test DB
  • cd api && npx tsc --noEmit — 0 errors; npx eslint "src/**/*.ts" — 0 errors
  • cd api && npm run build — succeeds
  • cd xstreamroll-sdk && npx jest72/72 passing (6/6 suites); npm run typecheck — 0 errors; npm run lint — 0 errors
  • cd app && npx jest222/222 passing (19/20 suites); the one failing suite (fetch-json.test.ts) fails identically on main — a Node 24/jsdom incompatibility in a file this PR does not touch
  • npm run build --workspace=tests/contracts and --workspace=packages/types — succeed; contracts lint/typecheck — clean

Env vars / Notes

No new environment variables or migrations. The list-notifications contract pins numeric notification ids and ISO-string timestamps as they appear on the wire. The type-pinning assertions live in the SDK consumer test and run under ts-jest (CI runs with a fresh cache, so the compile-time check always fires there); npx tsc --noEmit in the SDK only covers src/, which is why the assertions live in __tests__.

…tion endpoints

Add contract coverage (issue XStreamRollz#534) for the API surfaces that shipped
after the contract suite was built: GET /webhooks/:id/deliveries,
GET /streams/:id/events (replay, XStreamRollz#396), GET /streams/:id/analytics,
and GET /notifications. POST /webhooks was already pinned by
create-webhook; the delivery-log contract now exercises the seeded
subscription → deliveries chain via the existing placeholder
mechanism, so no per-contract setup duplication is introduced.

The schemas are tightened to the exact field types the SDK declares
for WebhookSubscription/WebhookDelivery — the id/streamId
string-vs-number choice and the closed event union — and the SDK
consumer test asserts mutual assignability between each schema's
zod output type and the SDK interface at compile time, so a
server-side type change fails CI in either direction. The SDK also
gains listDeliveries(), listStreamEvents(), getStreamAnalytics(),
and listNotifications() so the new endpoints are exercised end to
end instead of hand-mocked.

Provider suite (api) now seeds a recorded stream event and an unread
notification so the replay/analytics/notification contracts validate
non-empty shapes, and wires the real NotificationsController +
in-memory repository into the module.

Also carries the same pre-existing bad-merge repairs the contract
suite needs to compile and run (auth refresh/isAdmin, gateway
authenticate shape, streams pending-event id, duplicate module
imports) and removes the stale axios-based SDK client test that the
nock-based integration suite supersedes.

Closes XStreamRollz#534
api/package.json gained @types/cookie-parser@^1.4.10 (auth refresh
work) without a lockfile regeneration, so every `npm ci` in CI fails
with EUSAGE. Regenerate the root lockfile to add the missing
resolution, sync the stale api/sdk workspace version entries, and
prune an unreferenced nested conventional-commits-parser entry.

Copy link
Copy Markdown
Contributor Author

CI status update

New commit pushed: fix(ci): sync root package-lock.json so npm ci resolves dependencies

The CI quality matrix was blocked at the very first step (npm ci fails with EUSAGE — Missing: @types/[email protected] from lock file). api/package.json gained @types/cookie-parser@^1.4.10 in the auth-refresh work without a lockfile regeneration, so this failed on main and every PR. Regenerated the root lockfile (adds the missing resolution, syncs stale workspace version entries, prunes one unreferenced nested entry). Verified locally: npm ci clean, verify-lockfiles.sh passes.

Current check status:

  • CI quality matrix: awaiting maintainer approval — GitHub's fork-PR gate (action_required); the run executes once approved. All gates were validated locally: api 444/444 tests, sdk 72/72, app 222/222, typecheck/lint/build clean.
  • Trivy filesystem scan: passed on the earlier commit; this commit's scan run was cancelled by GitHub's concurrency queueing (a higher-priority main push), it will re-run on the next event or can be re-run by a maintainer.
  • Trivy image scans (api/app/processing): fail for pre-existing Dockerfile breakage on mainapi/pnpm-lock.yaml is out of sync with api/package.json (opentelemetry ^0.57.0 vs ^0.57.2), and the app/processing Dockerfiles cannot resolve workspace deps (@xstreamroll/types). These failures are identical on main and every other open PR; this PR touches no Dockerfiles. Happy to open a follow-up PR to repair the Docker build pipeline if that's wanted.

@Xhristin3
Xhristin3 merged commit 2f3caa0 into XStreamRollz:main Aug 27, 2026
5 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webhook and event-replay endpoints have no contract tests: breaking changes go undetected

2 participants