Initial Commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Consumer-side pact recording over envelope shapes
|
||||
|
||||
The `@suaveplan/api-schemas` package SHALL ship an `api-schemas.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record interactions exercising every envelope shape defined in the package: `common` (pagination, sort, filter, id-param, error-envelope), `auth` (login, register, refresh, logout, context), `admin` (ping, health, status), and `events` (domain-event, envelope, metadata). The resulting pact file MUST be committed to `pacts/api-schemas__enterprise-api.json`.
|
||||
|
||||
#### Scenario: Every envelope shape is exercised
|
||||
|
||||
- **WHEN** the consumer-side test suite runs
|
||||
- **THEN** `pacts/api-schemas__enterprise-api.json` contains at least one interaction whose request or response body matches each of `common`, `auth`, `admin`, and `events`
|
||||
|
||||
#### Scenario: Schema drift produces reviewable diff
|
||||
|
||||
- **WHEN** an `api-schemas` change renames a field on the `auth.login` request envelope
|
||||
- **THEN** the diff against `pacts/api-schemas__enterprise-api.json` is non-empty AND surfaces in PR review before the corresponding API plugin is updated
|
||||
|
||||
### Requirement: Pact-recorded shapes parse cleanly under the package's Zod schemas
|
||||
|
||||
Every recorded request and response body in the committed pact file MUST parse successfully under the matching Zod schema exported from `@suaveplan/api-schemas`. A validation step in the consumer-side test SHALL enforce this contract.
|
||||
|
||||
#### Scenario: Recorded body conforms to its Zod schema
|
||||
|
||||
- **WHEN** the test parses each `interactions[].request.body` and `interactions[].response.body` under the matching exported Zod schema
|
||||
- **THEN** every parse succeeds AND no `ZodError` is thrown
|
||||
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Consumer-side pact recording
|
||||
|
||||
The `@suaveplan/oauth-client` package SHALL ship a `oauth-client.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record at minimum the following interactions against any conforming OAuth 2.0 / OIDC provider: OIDC discovery (`GET /.well-known/openid-configuration`), Authorization Code token exchange (`POST /token` with `grant_type=authorization_code`), Client Credentials token exchange (`POST /token` with `grant_type=client_credentials`), token refresh (`POST /token` with `grant_type=refresh_token`). The resulting pact file MUST be committed to `pacts/oauth-client__oidc-issuer.json`.
|
||||
|
||||
#### Scenario: Pact file committed
|
||||
|
||||
- **WHEN** the consumer-side test suite runs
|
||||
- **THEN** `pacts/oauth-client__oidc-issuer.json` is produced AND its `interactions[]` covers discovery, authorization-code exchange, client-credentials exchange, and refresh
|
||||
|
||||
#### Scenario: Wire-format drift is reviewable
|
||||
|
||||
- **WHEN** an `oauth-client` change alters a request body field name
|
||||
- **THEN** the diff against `pacts/oauth-client__oidc-issuer.json` is non-empty AND surfaces in PR review
|
||||
|
||||
### Requirement: Injectable fetch surfaces pact wrapping
|
||||
|
||||
The package's public functions accepting `{ fetch?: FetchLike }` SHALL accept the `recordPactInteraction(fetch)` wrapper without requiring any public-API change.
|
||||
|
||||
#### Scenario: Wrapped fetch records all token endpoint traffic
|
||||
|
||||
- **WHEN** the consumer test wraps the package's injectable fetch via `recordPactInteraction` AND drives an Authorization Code exchange
|
||||
- **THEN** every request the package issues is captured into the pact file
|
||||
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Consumer-side pact recording
|
||||
|
||||
The `@suaveplan/schema-registry` package SHALL ship a `schema-registry.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record interactions against an upstream registry's lookup, register, and compatibility-check endpoints. The resulting pact file MUST be committed to `pacts/schema-registry__upstream-registry.json`.
|
||||
|
||||
#### Scenario: Lookup, register, compatibility recorded
|
||||
|
||||
- **WHEN** the consumer-side test exercises `getSchema`, `registerSchema`, and `checkCompatibility`
|
||||
- **THEN** `pacts/schema-registry__upstream-registry.json` contains at least one interaction per public method
|
||||
|
||||
### Requirement: Provider-side pact verification
|
||||
|
||||
The `@suaveplan/schema-registry` package SHALL ship a `schema-registry.provider.test.ts` that uses `verifyProviderAgainstPact` from `@suaveplan/testing-contract` to replay `pacts/schema-registry__upstream-registry.json` against the registry server implemented by the package itself. Provider states for "subject exists with N versions" SHALL be registered via `options.stateHandlers`.
|
||||
|
||||
#### Scenario: Provider state seeds version history
|
||||
|
||||
- **WHEN** an interaction declares `providerStates:[{ name:"subject 'orders' has 3 versions" }]` AND the corresponding state handler runs
|
||||
- **THEN** the registry under test reports `versions.length === 3` for subject `'orders'` before the interaction is replayed
|
||||
|
||||
#### Scenario: Compatibility break fails verification
|
||||
|
||||
- **WHEN** the registry server's `BACKWARD` compatibility check returns `false` for an interaction recorded as `true`
|
||||
- **THEN** `verifyProviderAgainstPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `pact:verify` fails the build
|
||||
@@ -0,0 +1,94 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Package isolation
|
||||
|
||||
The `@suaveplan/testing-contract` package `dependencies`, `peerDependencies`, and `devDependencies` MUST NOT list any `@suaveplan/*` scoped package other than `@suaveplan/types`. External catalog deps only (`zod`, build tooling). This mirrors the testing-infrastructure carve-out documented in `CLAUDE.md` rule 8.
|
||||
|
||||
#### Scenario: Dep-graph guard
|
||||
|
||||
- **WHEN** `bunx turbo ls --filter=@suaveplan/testing-contract...` is run
|
||||
- **THEN** no `@suaveplan/*` package other than `@suaveplan/types` appears in the forward closure of `@suaveplan/testing-contract`
|
||||
|
||||
### Requirement: Pact Specification v3 file shape
|
||||
|
||||
Pact files emitted and consumed by the package SHALL conform to Pact Specification v3 — a JSON document with `consumer.name`, `provider.name`, `interactions[]` (each carrying `description`, optional `providerStates[]`, `request`, `response`), and `metadata.pactSpecification.version === "3.0.0"`. Field order in serialised output MUST be deterministic so file diffs are reviewable.
|
||||
|
||||
#### Scenario: Round-trip preserves byte equality
|
||||
|
||||
- **WHEN** a pact file is read via `readPactFile`, then written via `writePactFile` to a new path
|
||||
- **THEN** the new file is byte-identical to the original
|
||||
|
||||
#### Scenario: Spec version asserted on read
|
||||
|
||||
- **WHEN** `readPactFile` is given a file whose `metadata.pactSpecification.version !== "3.0.0"`
|
||||
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.PACT_FILE.UNSUPPORTED_VERSION"`
|
||||
|
||||
### Requirement: Consumer-side recording API
|
||||
|
||||
`defineConsumerInteraction({ provider, request, response, state? })` SHALL register an expected interaction. `recordPactInteraction(httpClient)` SHALL wrap a `FetchLike` such that every request issued through the wrapped client is matched against registered interactions and accumulated into a pact file emitted by `flushPact(path)`. Unmatched interactions and interactions with no observed traffic MUST both be reported, distinctly.
|
||||
|
||||
#### Scenario: Recorded interaction lands in pact file
|
||||
|
||||
- **WHEN** `defineConsumerInteraction({ provider:"P", request:{ method:"GET", path:"/x" }, response:{ status:200, body:{ ok:true } } })` is registered AND a `recordPactInteraction`-wrapped fetch hits `GET /x` AND `flushPact("p.json")` runs
|
||||
- **THEN** `p.json` contains exactly one interaction matching the registered shape
|
||||
|
||||
#### Scenario: Unmatched traffic surfaces as error
|
||||
|
||||
- **WHEN** the wrapped fetch issues a request that matches no registered interaction AND `flushPact` is called
|
||||
- **THEN** `flushPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.RECORD.UNMATCHED_REQUEST"` AND `context.method` and `context.path` identify the unmatched request
|
||||
|
||||
#### Scenario: Unused interaction surfaces as error
|
||||
|
||||
- **WHEN** an interaction is registered AND the wrapped fetch issues no matching request AND `flushPact` is called
|
||||
- **THEN** `flushPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.RECORD.UNUSED_INTERACTION"` AND `context.description` identifies the interaction
|
||||
|
||||
### Requirement: Provider-side verification
|
||||
|
||||
`verifyProviderAgainstPact(pactFile, providerUrl, options)` SHALL replay each interaction in the pact file against the supplied `providerUrl`, asserting that each response's status, headers (where recorded), and body shape match the recorded shape modulo `options.ignoreFields`. The function MUST use an injectable `fetchFn`. State handlers registered via `options.stateHandlers` MUST run before each interaction whose `providerStates[]` includes the matching state name.
|
||||
|
||||
#### Scenario: Provider response matches recorded shape
|
||||
|
||||
- **WHEN** the provider returns `{ status:200, body:{ ok:true } }` for the interaction `GET /x` AND `verifyProviderAgainstPact` is called
|
||||
- **THEN** the function resolves with `{ passed: true, results: [{ description, ok: true }] }`
|
||||
|
||||
#### Scenario: Provider response diverges
|
||||
|
||||
- **WHEN** the provider returns `{ status:500 }` instead of the recorded `200`
|
||||
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `context.diff` contains the structured path → expected/actual diff
|
||||
|
||||
#### Scenario: State handler runs before matching interaction
|
||||
|
||||
- **WHEN** an interaction declares `providerStates:[{ name:"user-exists" }]` AND `options.stateHandlers["user-exists"]` is registered
|
||||
- **THEN** the registered handler is awaited exactly once before the interaction's request is replayed
|
||||
|
||||
### Requirement: Pact Broker integration
|
||||
|
||||
`publishPact(brokerUrl, version)` SHALL POST the pact file to the broker's `pacts/provider/<P>/consumer/<C>/version/<v>` endpoint. `pullPactsForVerification(brokerUrl, providerName)` SHALL GET the broker's verification endpoint and return `PactFile[]`. Both functions MUST authenticate via the `PACT_BROKER_TOKEN` env var (Bearer scheme); when unset, both MUST throw before any HTTP call.
|
||||
|
||||
#### Scenario: Publish includes Bearer token
|
||||
|
||||
- **WHEN** `PACT_BROKER_TOKEN=abc` is set AND `publishPact("https://broker", "1.2.3")` runs
|
||||
- **THEN** the issued POST has header `Authorization: Bearer abc`
|
||||
|
||||
#### Scenario: Missing token aborts before HTTP
|
||||
|
||||
- **WHEN** `PACT_BROKER_TOKEN` is unset AND `publishPact(...)` is called
|
||||
- **THEN** the function throws `TestingContractError` with `code === "TESTING_CONTRACT.BROKER.MISSING_TOKEN"` AND no HTTP request is issued
|
||||
|
||||
### Requirement: Error code prefix
|
||||
|
||||
Every error thrown by the package SHALL be a `TestingContractError` whose `code` begins with `TESTING_CONTRACT.`. Plain `Error` / `TypeError` MUST NOT be thrown from any public code path.
|
||||
|
||||
#### Scenario: Code namespace enforced
|
||||
|
||||
- **WHEN** any public function throws
|
||||
- **THEN** the thrown value `instanceof TestingContractError === true` AND `error.code.startsWith("TESTING_CONTRACT.") === true`
|
||||
|
||||
### Requirement: Coverage gate
|
||||
|
||||
Istanbul statement, branch, function, and line coverage across `packages/testing/testing-contract/src/` (excluding `*.bench.ts` and `*.schemas.ts`) MUST be 100%. The pre-publish gate MUST hard-fail below 100%.
|
||||
|
||||
#### Scenario: Coverage gate trips below 100%
|
||||
|
||||
- **WHEN** any line, branch, function, or statement is uncovered
|
||||
- **THEN** the pre-publish gate fails AND the package MUST NOT be published
|
||||
@@ -0,0 +1,24 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Consumer-side pact recording
|
||||
|
||||
The `@suaveplan/webhook-client` package SHALL ship a `webhook-client.contract.test.ts` that uses `recordPactInteraction` from `@suaveplan/testing-contract` to record signed-delivery POSTs to a webhook receiver. Recorded interactions MUST include the `X-Signature` HMAC-SHA256 header so receiver implementations can be verified against the canonical signing scheme. The resulting pact file MUST be committed to `pacts/webhook-client__webhook-receiver.json`.
|
||||
|
||||
#### Scenario: Pact records signed delivery
|
||||
|
||||
- **WHEN** the consumer-side test drives a signed delivery
|
||||
- **THEN** `pacts/webhook-client__webhook-receiver.json` contains an interaction whose `request.headers["X-Signature"]` is a valid HMAC-SHA256 of the recorded body under the recorded secret
|
||||
|
||||
### Requirement: Provider-side pact verification
|
||||
|
||||
The `@suaveplan/webhook-client` package SHALL ship a `webhook-client.provider.test.ts` that uses `verifyProviderAgainstPact` from `@suaveplan/testing-contract` to replay `pacts/webhook-client__webhook-receiver.json` against a running webhook-receiver instance and assert each response matches the recorded shape. Verification MUST run on every commit via the `pact:verify` Turborepo task.
|
||||
|
||||
#### Scenario: Receiver passes verification
|
||||
|
||||
- **WHEN** the receiver under test returns the recorded response shape for every interaction
|
||||
- **THEN** `verifyProviderAgainstPact` resolves with `passed: true`
|
||||
|
||||
#### Scenario: Receiver drift trips verification
|
||||
|
||||
- **WHEN** the receiver returns a 500 for an interaction recorded as 200
|
||||
- **THEN** `verifyProviderAgainstPact` throws `TestingContractError` with `code === "TESTING_CONTRACT.VERIFY.MISMATCH"` AND `pact:verify` fails the build
|
||||
Reference in New Issue
Block a user