# Pact.js Utils Provider Verifier ## Principle Use `buildVerifierOptions`, `buildMessageVerifierOptions`, `handlePactBrokerUrlAndSelectors`, and `getProviderVersionTags` from `@seontechnologies/pactjs-utils` to assemble complete provider verification configuration in a single call. These utilities handle local/remote flow detection, broker URL resolution, consumer version selector strategy, and CI-aware version tagging. The caller controls breaking change behavior via the required `includeMainAndDeployed` parameter. ## Rationale ### Problems with manual VerifierOptions - **30+ lines of scattered config**: Assembling `VerifierOptions` manually requires broker URL, token, selectors, state handlers, request filters, version info, publish flags — all in one object - **Environment variable logic**: Different env vars for local vs remote, CI vs local dev, breaking change vs normal flow - **Consumer version selector complexity**: Choosing between `mainBranch`, `deployedOrReleased`, `matchingBranch`, and `includeMainAndDeployed` requires understanding Pact Broker semantics - **Breaking change coordination**: When a provider intentionally breaks a contract, manual selector switching is error-prone - **Cross-execution protection**: `PACT_PAYLOAD_URL` webhook payloads need special handling to verify only the triggering pact ### Solutions - **`buildVerifierOptions`**: Single function that reads env vars, selects the right flow, and returns complete `VerifierOptions` - **`buildMessageVerifierOptions`**: Same as above for message/Kafka provider verification - **`handlePactBrokerUrlAndSelectors`**: Pure function for broker URL + selector resolution (used internally, also exported for advanced use) - **`getProviderVersionTags`**: Extracts CI branch/tag info from environment for provider version tagging ## Pattern Examples ### Example 1: HTTP Provider Verification (Remote Flow) ```typescript import { Verifier } from '@pact-foundation/pact'; import { buildVerifierOptions, createRequestFilter } from '@seontechnologies/pactjs-utils'; import type { StateHandlers } from '@seontechnologies/pactjs-utils'; const stateHandlers: StateHandlers = { 'movie with id 1 exists': { setup: async (params) => { await db.seed({ movies: [{ id: params?.id ?? 1, name: 'Inception' }] }); }, teardown: async () => { await db.clean('movies'); }, }, 'no movies exist': async () => { await db.clean('movies'); }, }; // buildVerifierOptions reads these env vars automatically: // - PACT_BROKER_BASE_URL (broker URL) // - PACT_BROKER_TOKEN (broker auth) // - PACT_PAYLOAD_URL (webhook trigger — cross-execution protection) // - PACT_BREAKING_CHANGE (if "true", uses includeMainAndDeployed selectors) // - GITHUB_SHA (provider version) // - CI (publish verification results if "true") const opts = buildVerifierOptions({ provider: 'SampleMoviesAPI', port: '3001', includeMainAndDeployed: process.env.PACT_BREAKING_CHANGE !== 'true', stateHandlers, requestFilter: createRequestFilter({ tokenGenerator: () => process.env.TEST_AUTH_TOKEN ?? 'test-token', }), }); await new Verifier(opts).verifyProvider(); ``` **Key Points**: - Set `PACT_BROKER_BASE_URL` and `PACT_BROKER_TOKEN` as env vars — `buildVerifierOptions` reads them automatically - `port` is a string (e.g., `'3001'`) — the function builds `providerBaseUrl: http://localhost:${port}` internally - `includeMainAndDeployed` is **required** — set `true` for normal flow, `false` for breaking changes - State handlers support both simple functions and `{ setup, teardown }` objects - `params` in state handlers correspond to the `JsonMap` from consumer's `createProviderState` - Verification results are published by default (`publishVerificationResult` defaults to `true`) ### Example 2: Local Flow (Monorepo, No Broker) ```typescript import { Verifier } from '@pact-foundation/pact'; import { buildVerifierOptions } from '@seontechnologies/pactjs-utils'; // When PACT_BROKER_BASE_URL is NOT set, buildVerifierOptions // falls back to local pact file verification const opts = buildVerifierOptions({ provider: 'SampleMoviesAPI', port: '3001', includeMainAndDeployed: true, // Specify local pact files directly — skips broker entirely pactUrls: ['./pacts/movie-web-SampleMoviesAPI.json'], stateHandlers: { 'movie exists': async (params) => { await db.seed({ movies: [{ id: params?.id }] }); }, }, }); await new Verifier(opts).verifyProvider(); ``` ### Example 3: Message Provider Verification (Kafka/Async) ```typescript import { Verifier } from '@pact-foundation/pact'; import { buildMessageVerifierOptions } from '@seontechnologies/pactjs-utils'; const opts = buildMessageVerifierOptions({ provider: 'OrderEventsProducer', includeMainAndDeployed: process.env.PACT_BREAKING_CHANGE !== 'true', // Message handlers return the message content that the provider would produce messageProviders: { 'an order created event': async () => ({ orderId: 'order-123', userId: 'user-456', items: [{ productId: 'prod-789', quantity: 2 }], createdAt: new Date().toISOString(), }), 'an order cancelled event': async () => ({ orderId: 'order-123', reason: 'customer_request', cancelledAt: new Date().toISOString(), }), }, stateHandlers: { 'order exists': async (params) => { await db.seed({ orders: [{ id: params?.orderId }] }); }, }, }); await new Verifier(opts).verifyProvider(); ``` **Key Points**: - `buildMessageVerifierOptions` adds `messageProviders` to the verifier config - Each message provider function returns the expected message payload - State handlers work the same as HTTP verification - Broker integration works identically (same env vars) ### Example 4: Breaking Change Coordination ```typescript // When a provider intentionally introduces a breaking change: // // 1. Set PACT_BREAKING_CHANGE=true in CI environment // 2. Your test reads the env var and passes includeMainAndDeployed: false // to buildVerifierOptions — this verifies ONLY against the matching // branch, skipping main/deployed consumers that would fail // 3. Coordinate with consumer team to update their pact on a matching branch // 4. Remove PACT_BREAKING_CHANGE flag after consumer updates // In CI environment (.github/workflows/provider-verify.yml): // env: // PACT_BREAKING_CHANGE: 'true' // Your provider test code reads the env var: const isBreakingChange = process.env.PACT_BREAKING_CHANGE === 'true'; const opts = buildVerifierOptions({ provider: 'SampleMoviesAPI', port: '3001', includeMainAndDeployed: !isBreakingChange, // false during breaking changes stateHandlers: { /* ... */ }, }); // When includeMainAndDeployed is false (breaking change): // selectors = [{ matchingBranch: true }] // When includeMainAndDeployed is true (normal): // selectors = [{ matchingBranch: true }, { mainBranch: true }, { deployedOrReleased: true }] ``` ### Example 5: handlePactBrokerUrlAndSelectors (Advanced) ```typescript import { handlePactBrokerUrlAndSelectors } from '@seontechnologies/pactjs-utils'; import type { VerifierOptions } from '@pact-foundation/pact'; // For advanced use cases — mutates the options object in-place (returns void) const options: VerifierOptions = { provider: 'SampleMoviesAPI', providerBaseUrl: 'http://localhost:3001', }; handlePactBrokerUrlAndSelectors({ pactPayloadUrl: process.env.PACT_PAYLOAD_URL, pactBrokerUrl: process.env.PACT_BROKER_BASE_URL, consumer: undefined, // or specific consumer name includeMainAndDeployed: true, options, // mutated in-place: sets pactBrokerUrl, consumerVersionSelectors, or pactUrls }); // After call, options has been mutated with: // - options.pactBrokerUrl (from pactBrokerUrl param) // - options.consumerVersionSelectors (based on includeMainAndDeployed) // OR if pactPayloadUrl matches: options.pactUrls = [pactPayloadUrl] ``` **Note**: `handlePactBrokerUrlAndSelectors` is called internally by `buildVerifierOptions`. You rarely need it directly — use it only for advanced custom verifier assembly. ### Example 6: getProviderVersionTags ```typescript import { getProviderVersionTags } from '@seontechnologies/pactjs-utils'; // Extracts version tags from CI environment const tags = getProviderVersionTags(); // In GitHub Actions on branch "feature/add-movies" (non-breaking): // tags = ['dev', 'feature/add-movies'] // // In GitHub Actions on main branch (non-breaking): // tags = ['dev', 'main'] // // In GitHub Actions with PACT_BREAKING_CHANGE=true: // tags = ['feature/add-movies'] (no 'dev' tag) // // Locally (no CI): // tags = ['local'] ``` ## Environment Variables Reference | Variable | Required | Description | Default | | ---------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- | | `PACT_BROKER_BASE_URL` | For remote flow | Pact Broker / PactFlow URL | — | | `PACT_BROKER_TOKEN` | For remote flow | API token for broker authentication | — | | `GITHUB_SHA` | Recommended | Provider version for verification result publishing (auto-set by GitHub Actions) | `'unknown'` | | `GITHUB_BRANCH` | Recommended | Branch name for provider version branch and version tags (**not auto-set** — define as `${{ github.head_ref \|\| github.ref_name }}`) | `'main'` | | `PACT_PAYLOAD_URL` | Optional | Webhook payload URL — triggers verification of specific pact only | — | | `PACT_BREAKING_CHANGE` | Optional | Set to `"true"` to use breaking change selector strategy | `'false'` | | `CI` | Auto-detected | When `"true"`, enables verification result publishing | — | ## Key Points - **Flow auto-detection**: If `PACT_BROKER_BASE_URL` is set → remote flow; otherwise → local flow (requires `pactUrls`) - **`port` is a string**: Pass port number as string (e.g., `'3001'`); function builds `http://localhost:${port}` internally - **`includeMainAndDeployed` is required**: `true` = verify matchingBranch + mainBranch + deployedOrReleased; `false` = verify matchingBranch only (for breaking changes) - **Selector strategy**: Normal flow (`includeMainAndDeployed: true`) includes all selectors; breaking change flow (`false`) includes only `matchingBranch` - **Webhook support**: `PACT_PAYLOAD_URL` takes precedence — verifies only the specific pact that triggered the webhook - **State handler types**: Both `async (params) => void` and `{ setup: async (params) => void, teardown: async () => void }` are supported - **Version publishing**: Verification results are published by default (`publishVerificationResult` defaults to `true`) ## Related Fragments - `pactjs-utils-overview.md` — installation, decision tree, design philosophy - `pactjs-utils-consumer-helpers.md` — consumer-side state parameter creation - `pactjs-utils-request-filter.md` — auth injection for provider verification - `contract-testing.md` — foundational patterns with raw Pact.js ## Anti-Patterns ### Wrong: Manual broker URL and selector assembly ```typescript // ❌ Manual environment variable handling const opts: VerifierOptions = { provider: 'my-api', providerBaseUrl: 'http://localhost:3001', pactBrokerUrl: process.env.PACT_BROKER_BASE_URL, pactBrokerToken: process.env.PACT_BROKER_TOKEN, publishVerificationResult: process.env.CI === 'true', providerVersion: process.env.GIT_SHA || process.env.GITHUB_SHA || 'dev', providerVersionBranch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME, consumerVersionSelectors: process.env.PACT_BREAKING_CHANGE === 'true' ? [{ matchingBranch: true }] : [{ matchingBranch: true }, { mainBranch: true }, { deployedOrReleased: true }], pactUrls: process.env.PACT_PAYLOAD_URL ? [process.env.PACT_PAYLOAD_URL] : undefined, stateHandlers: { /* ... */ }, requestFilter: (req, res, next) => { req.headers['authorization'] = `Bearer ${process.env.TEST_TOKEN}`; next(); }, }; ``` ### Right: Use buildVerifierOptions ```typescript // ✅ All env var logic handled internally const opts = buildVerifierOptions({ provider: 'my-api', port: '3001', includeMainAndDeployed: process.env.PACT_BREAKING_CHANGE !== 'true', stateHandlers: { /* ... */ }, requestFilter: createRequestFilter({ tokenGenerator: () => process.env.TEST_TOKEN ?? 'test-token', }), }); ``` ### Wrong: Hardcoding consumer version selectors ```typescript // ❌ Hardcoded selectors — breaks when flow changes consumerVersionSelectors: [{ mainBranch: true }, { deployedOrReleased: true }], ``` ### Right: Let buildVerifierOptions choose selectors ```typescript // ✅ Selector strategy adapts to PACT_BREAKING_CHANGE env var const opts = buildVerifierOptions({ /* ... */ }); // Selectors chosen automatically based on environment ``` _Source: @seontechnologies/pactjs-utils provider-verifier module, pact-js-example-provider CI workflows_