Feat/contract tests - #419
Merged
Merged
Conversation
Ibk-Bless
force-pushed
the
feat/contract-tests
branch
from
July 17, 2026 11:23
453e643 to
91d01a7
Compare
Contributor
|
Really good addition, @Ibk-Bless — contract tests are a huge help in catching API drift between services before it becomes a runtime problem. CI is all green. Merging! 🚀 |
Contributor
|
Hey @Ibk-Bless, contract tests are a great addition and CI is green ✅! The branch has conflicts with git fetch origin
git rebase origin/main
# resolve conflicts, then
git push --force-with-leaseOnce updated, this is good to merge! |
Ibk-Bless
force-pushed
the
feat/contract-tests
branch
2 times, most recently
from
July 22, 2026 10:11
f5ff318 to
d6a2e46
Compare
Author
|
@Xhristin3 conflict resolved, please review |
Ibk-Bless
force-pushed
the
feat/contract-tests
branch
from
July 22, 2026 15:51
d6a2e46 to
f2f4f2a
Compare
Contributor
|
@Ibk-Bless please resolve conflicts |
Adds tests/contracts/ (@xstreamroll/contract-tests) as a lightweight, custom alternative to Pact — no broker or provider-state infra needed, built entirely on tooling already in the repo (Jest, supertest, zod). Each Contract object in tests/contracts/src/*.contract.ts describes one SDK/API interaction: the request the SDK sends and a zod schema for the response it's entitled to expect. The schemas are pinned to @xstreamroll/types at compile time via a small typed<T>() helper, so a schema that stops matching the shared types fails to build before any test runs. Two suites import the same Contract objects and can't independently drift the way the plain interfaces they're checking used to: - api/src/contract-provider.spec.ts boots the real controllers/services behind supertest (in-memory repositories instead of Postgres, no DB needed) and asserts each actual HTTP response satisfies its contract. - xstreamroll-sdk/__tests__/contract.consumer.test.ts asserts the SDK sends the request a contract describes and correctly parses a schema-valid response. Both run as part of each package's normal `npm run test:cov`, so a contract violation fails CI the same way any other test failure would — no new CI job needed, just build/lint/typecheck steps for the new workspace package (see .github/workflows/ci.yml). One real finding from building this: a stream id that doesn't exist returns 403, not 404, because StreamOwnershipGuard rejects before the controller's not-found branch is reachable. The contract now documents that as intentional behavior instead of leaving it implicit. Closes XStreamRollz#399
Ibk-Bless
force-pushed
the
feat/contract-tests
branch
from
July 23, 2026 15:10
f2f4f2a to
2c1f91b
Compare
Author
|
@Xhristin3 conflict has been resolved |
Author
|
@Xhristin3 please review |
Contributor
|
Nice contract coverage @Ibk-Bless, thanks! Merged. |
5 tasks
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds tests/contracts/ (@xstreamroll/contract-tests) as a lightweight, custom alternative to Pact — no broker or provider-state infrastructure needed, built entirely on tooling already in the repo (Jest, supertest — already a root devDependency — and zod, already used across api/app).
Each Contract object in tests/contracts/src/*.contract.ts describes one SDK/API interaction: the request the SDK sends, and a zod schema for the response it's entitled to expect.
The schemas are pinned to @xstreamroll/types at compile time via a small typed() helper — if a schema stops matching the shared types, the package fails to build, before any test even runs.
Two suites import the same Contract objects, so they can't independently drift the way the plain TS interfaces they're checking used to:
Provider verification — api/src/contract-provider.spec.ts boots the real controllers/services behind supertest (in-memory repositories instead of Postgres, no DB needed) and asserts every actual HTTP response satisfies its contract.
Consumer verification — xstreamroll-sdk/tests/contract.consumer.test.ts asserts the SDK sends the request a contract describes, and correctly parses a schema-valid response.
Both run as part of each package's normal npm run test:cov, so a contract violation fails CI the same way any other test failure would — no new CI job needed, just install/build/lint/typecheck steps for the new workspace package.
A real finding from building this
A stream id that doesn't exist returns 403, not 404 — StreamOwnershipGuard rejects before the controller's not-found branch is ever reachable (the ownership query returns false the same way for "doesn't exist" and "not yours"). The contract now documents that as intentional, pinned-down behavior instead of leaving it as an implicit assumption.
Coverage
create-stream: provider yes, consumer no (SDK doesn't implement this call yet)
list-streams: provider yes, consumer no (SDK doesn't implement this call yet)
get-stream-by-id: provider yes, consumer yes
get-stream-by-id-not-found: provider yes, consumer no (SDK doesn't implement this call yet)
update-stream: provider yes, consumer no (SDK doesn't implement this call yet)
register: provider yes, consumer yes
login: provider yes, consumer yes
See tests/contracts/README.md for the full design rationale (including why a custom suite over Pact) and the update workflow for future API changes.
Test plan
tests/contracts: npm run build / npm run typecheck / npm run lint all pass
api: full Jest suite (126 tests, including 8 new provider-verification cases) passes; npx tsc --noEmit and npm run build (Nest build) pass
xstreamroll-sdk: full Jest suite (44 tests, including 3 new consumer-verification cases) passes; npx tsc --noEmit passes
Root aggregate npm run lint (--max-warnings=0) has the same 39 pre-existing warnings as main — no new warnings introduced
Sanity-checked the compile-time schema pinning actually works: temporarily added a field to a contract's request body that doesn't exist on CreateUserDto and confirmed tsc fails the build; reverted
Closes #399