Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

pactjs-utils-overview.md 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # Pact.js Utils Overview
  2. ## Principle
  3. Use production-ready utilities from `@seontechnologies/pactjs-utils` to eliminate boilerplate in consumer-driven contract testing. The library wraps `@pact-foundation/pact` with type-safe helpers for provider state creation, PactV4 JSON interaction builders, verifier configuration, and request filter injection — working equally well for HTTP and message (async/Kafka) contracts.
  4. ## Rationale
  5. ### Problems with raw @pact-foundation/pact
  6. - **JsonMap casting**: Provider state parameters require `JsonMap` type — manually casting every value is error-prone and verbose
  7. - **Repeated builder lambdas**: PactV4 interactions often repeat inline callbacks with `builder.query(...)`, `builder.headers(...)`, and `builder.jsonBody(...)`
  8. - **Verifier configuration sprawl**: `VerifierOptions` requires 30+ lines of scattered configuration (broker URL, selectors, state handlers, request filters, version tags)
  9. - **Environment variable juggling**: Different env vars for local vs remote flows, breaking change coordination, payload URL matching
  10. - **Express middleware types**: Request filter requires Express types that aren't re-exported from Pact
  11. - **Bearer prefix bugs**: Easy to double-prefix tokens as `Bearer Bearer ...` in request filters
  12. - **CI version tagging**: Manual logic to extract branch/tag info from CI environment
  13. ### Solutions from pactjs-utils
  14. - **`createProviderState`**: One-call tuple builder for `.given()` — handles all JsonMap conversion automatically
  15. - **`toJsonMap`**: Explicit type coercion (null→"null", Date→ISO string, nested objects flattened)
  16. - **`setJsonContent`**: Curried callback helper for PactV4 `.withRequest(...)` / `.willRespondWith(...)` builders (query/headers/body)
  17. - **`setJsonBody`**: Body-only shorthand alias of `setJsonContent({ body })`
  18. - **`buildVerifierOptions`**: Single function assembles complete VerifierOptions from minimal inputs — handles local/remote/BDCT flows
  19. - **`buildMessageVerifierOptions`**: Same as above but for message/Kafka provider verification
  20. - **`handlePactBrokerUrlAndSelectors`**: Resolves broker URL and consumer version selectors from env vars with breaking change awareness
  21. - **`getProviderVersionTags`**: CI-aware version tagging (extracts branch/tag from GitHub Actions, GitLab CI, etc.)
  22. - **`createRequestFilter`**: Pluggable token generator pattern — prevents double-Bearer bugs by contract
  23. - **`noOpRequestFilter`**: Pass-through for providers that don't require auth injection
  24. - **`zodToPactMatchers`**: Converts a Zod schema (+ optional example values or `.openapi({ example })` metadata) into Pact V3 matchers — single source of truth for response shape, no hand-written matcher helpers
  25. ## Installation
  26. ```bash
  27. npm install -D @seontechnologies/pactjs-utils
  28. # Peer dependency
  29. npm install -D @pact-foundation/pact
  30. ```
  31. **Requirements**: `@pact-foundation/pact` >= 16.2.0, Node.js >= 18
  32. ## Available Utilities
  33. | Category | Function | Description | Use Case |
  34. | ----------------- | --------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
  35. | Consumer Helpers | `createProviderState` | Builds `[stateName, JsonMap]` tuple from typed input | Consumer tests: `.given(...createProviderState(input))` |
  36. | Consumer Helpers | `toJsonMap` | Converts any object to Pact-compatible `JsonMap` | Explicit type coercion for provider state params |
  37. | Consumer Helpers | `setJsonContent` | Curried request/response JSON callback helper | PactV4 `.withRequest(...)` and `.willRespondWith(...)` builders |
  38. | Consumer Helpers | `setJsonBody` | Body-only alias of `setJsonContent` | Body-only `.willRespondWith(...)` responses |
  39. | Provider Verifier | `buildVerifierOptions` | Assembles complete HTTP `VerifierOptions` | Provider verification: `new Verifier(buildVerifierOptions(...))` |
  40. | Provider Verifier | `buildMessageVerifierOptions` | Assembles message `VerifierOptions` | Kafka/async provider verification |
  41. | Provider Verifier | `handlePactBrokerUrlAndSelectors` | Resolves broker URL + selectors from env vars | Env-aware broker configuration |
  42. | Provider Verifier | `getProviderVersionTags` | CI-aware version tag extraction | Provider version tagging in CI |
  43. | Request Filter | `createRequestFilter` | Express middleware with pluggable token generator | Auth injection for provider verification |
  44. | Request Filter | `noOpRequestFilter` | Pass-through filter (no-op) | Providers without auth requirements |
  45. | Schema → Matchers | `zodToPactMatchers` | Derives Pact V3 matchers from a Zod schema | Consumer tests: response body matchers from a consumer-curated Zod schema instead of hand-written helpers |
  46. ## Decision Tree: Which Flow?
  47. ```
  48. Is this a monorepo (consumer + provider in same repo)?
  49. ├── YES → Local Flow
  50. │ - Consumer generates pact files to ./pacts/
  51. │ - Provider reads pact files from ./pacts/ (no broker needed)
  52. │ - Use buildVerifierOptions with pactUrls option
  53. └── NO → Do you have a Pact Broker / PactFlow?
  54. ├── YES → Remote (CDCT) Flow
  55. │ - Consumer publishes pacts to broker
  56. │ - Provider verifies from broker
  57. │ - Use buildVerifierOptions with broker config
  58. │ - Set PACT_BROKER_BASE_URL + PACT_BROKER_TOKEN
  59. └── Do you have an OpenAPI spec?
  60. ├── YES → BDCT Flow (PactFlow only)
  61. │ - Provider publishes OpenAPI spec to PactFlow
  62. │ - PactFlow cross-validates consumer pacts against spec
  63. │ - No provider verification test needed
  64. └── NO → Start with Local Flow, migrate to Remote later
  65. ```
  66. ## Design Philosophy
  67. 1. **One-call setup**: Each utility does one thing completely — no multi-step assembly required
  68. 2. **Environment-aware**: Utilities read env vars for CI/CD integration without manual wiring
  69. 3. **Type-safe**: Full TypeScript types for all inputs and outputs, exported for consumer use
  70. 4. **Fail-safe defaults**: Sensible defaults that work locally; env vars override for CI
  71. 5. **Composable**: Utilities work independently — use only what you need
  72. ## Pattern Examples
  73. ### Example 1: Minimal Consumer Test
  74. ```typescript
  75. import { PactV3 } from '@pact-foundation/pact';
  76. import { createProviderState } from '@seontechnologies/pactjs-utils';
  77. const provider = new PactV3({
  78. consumer: 'my-frontend',
  79. provider: 'my-api',
  80. dir: './pacts',
  81. });
  82. it('should get user by id', async () => {
  83. await provider
  84. .given(...createProviderState({ name: 'user exists', params: { id: 1 } }))
  85. .uponReceiving('a request for user 1')
  86. .withRequest({ method: 'GET', path: '/users/1' })
  87. .willRespondWith({ status: 200, body: { id: 1, name: 'John' } })
  88. .executeTest(async (mockServer) => {
  89. const res = await fetch(`${mockServer.url}/users/1`);
  90. expect(res.status).toBe(200);
  91. });
  92. });
  93. ```
  94. ### Example 2: Minimal Provider Verification
  95. ```typescript
  96. import { Verifier } from '@pact-foundation/pact';
  97. import { buildVerifierOptions, createRequestFilter } from '@seontechnologies/pactjs-utils';
  98. const opts = buildVerifierOptions({
  99. provider: 'my-api',
  100. port: '3001',
  101. includeMainAndDeployed: true,
  102. stateHandlers: {
  103. 'user exists': async (params) => {
  104. await db.seed({ users: [{ id: params?.id }] });
  105. },
  106. },
  107. requestFilter: createRequestFilter({
  108. tokenGenerator: () => 'test-token-123',
  109. }),
  110. });
  111. await new Verifier(opts).verifyProvider();
  112. ```
  113. ## Key Points
  114. - **Import path**: Always use `@seontechnologies/pactjs-utils` (no subpath exports)
  115. - **Peer dependency**: `@pact-foundation/pact` must be installed separately
  116. - **Local flow**: No broker needed — set `pactUrls` in verifier options pointing to local pact files
  117. - **Remote flow**: Set `PACT_BROKER_BASE_URL` and `PACT_BROKER_TOKEN` env vars
  118. - **Breaking changes**: Set `includeMainAndDeployed: false` when coordinating breaking changes (verifies only matchingBranch)
  119. - **Builder helpers**: Use `setJsonContent` when you need query/headers/body together; use `setJsonBody` for body-only callbacks
  120. - **Type exports**: Library exports `StateHandlers`, `RequestFilter`, `JsonMap`, `JsonContentInput`, `ConsumerVersionSelector` types
  121. ## Related Fragments
  122. - `pactjs-utils-consumer-helpers.md` — detailed createProviderState, toJsonMap, setJsonContent, and setJsonBody usage
  123. - `pactjs-utils-provider-verifier.md` — detailed buildVerifierOptions and broker configuration
  124. - `pactjs-utils-request-filter.md` — detailed createRequestFilter and auth patterns
  125. - `pactjs-utils-zod-to-pact.md` — detailed zodToPactMatchers usage, consumer-curated schema pattern, and anti-patterns
  126. - `contract-testing.md` — foundational contract testing patterns (raw Pact.js approach)
  127. - `test-levels-framework.md` — where contract tests fit in the testing pyramid
  128. ## Anti-Patterns
  129. ### Wrong: Manual VerifierOptions assembly when pactjs-utils is available
  130. ```typescript
  131. // ❌ Don't assemble VerifierOptions manually
  132. const opts: VerifierOptions = {
  133. provider: 'my-api',
  134. providerBaseUrl: 'http://localhost:3001',
  135. pactBrokerUrl: process.env.PACT_BROKER_BASE_URL,
  136. pactBrokerToken: process.env.PACT_BROKER_TOKEN,
  137. publishVerificationResult: process.env.CI === 'true',
  138. providerVersion: process.env.GIT_SHA || 'dev',
  139. consumerVersionSelectors: [{ mainBranch: true }, { deployedOrReleased: true }],
  140. stateHandlers: {
  141. /* ... */
  142. },
  143. requestFilter: (req, res, next) => {
  144. /* ... */
  145. },
  146. // ... 20 more lines
  147. };
  148. ```
  149. ### Right: Use buildVerifierOptions
  150. ```typescript
  151. // ✅ Single call handles all configuration
  152. const opts = buildVerifierOptions({
  153. provider: 'my-api',
  154. port: '3001',
  155. includeMainAndDeployed: true,
  156. stateHandlers: {
  157. /* ... */
  158. },
  159. requestFilter: createRequestFilter({ tokenGenerator: () => 'token' }),
  160. });
  161. ```
  162. ### Wrong: Importing raw Pact types for JsonMap conversion
  163. ```typescript
  164. // ❌ Manual JsonMap casting
  165. import type { JsonMap } from '@pact-foundation/pact';
  166. provider.given('user exists', { id: 1 as unknown as JsonMap['id'] });
  167. ```
  168. ### Right: Use createProviderState
  169. ```typescript
  170. // ✅ Automatic type conversion
  171. import { createProviderState } from '@seontechnologies/pactjs-utils';
  172. provider.given(...createProviderState({ name: 'user exists', params: { id: 1 } }));
  173. ```
  174. _Source: @seontechnologies/pactjs-utils library, pactjs-utils README, pact-js-example-provider workflows_