# Pact.js Utils Overview

## Principle

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.

## Rationale

### Problems with raw @pact-foundation/pact

- **JsonMap casting**: Provider state parameters require `JsonMap` type — manually casting every value is error-prone and verbose
- **Repeated builder lambdas**: PactV4 interactions often repeat inline callbacks with `builder.query(...)`, `builder.headers(...)`, and `builder.jsonBody(...)`
- **Verifier configuration sprawl**: `VerifierOptions` requires 30+ lines of scattered configuration (broker URL, selectors, state handlers, request filters, version tags)
- **Environment variable juggling**: Different env vars for local vs remote flows, breaking change coordination, payload URL matching
- **Express middleware types**: Request filter requires Express types that aren't re-exported from Pact
- **Bearer prefix bugs**: Easy to double-prefix tokens as `Bearer Bearer ...` in request filters
- **CI version tagging**: Manual logic to extract branch/tag info from CI environment

### Solutions from pactjs-utils

- **`createProviderState`**: One-call tuple builder for `.given()` — handles all JsonMap conversion automatically
- **`toJsonMap`**: Explicit type coercion (null→"null", Date→ISO string, nested objects flattened)
- **`setJsonContent`**: Curried callback helper for PactV4 `.withRequest(...)` / `.willRespondWith(...)` builders (query/headers/body)
- **`setJsonBody`**: Body-only shorthand alias of `setJsonContent({ body })`
- **`buildVerifierOptions`**: Single function assembles complete VerifierOptions from minimal inputs — handles local/remote/BDCT flows
- **`buildMessageVerifierOptions`**: Same as above but for message/Kafka provider verification
- **`handlePactBrokerUrlAndSelectors`**: Resolves broker URL and consumer version selectors from env vars with breaking change awareness
- **`getProviderVersionTags`**: CI-aware version tagging (extracts branch/tag from GitHub Actions, GitLab CI, etc.)
- **`createRequestFilter`**: Pluggable token generator pattern — prevents double-Bearer bugs by contract
- **`noOpRequestFilter`**: Pass-through for providers that don't require auth injection
- **`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

## Installation

```bash
npm install -D @seontechnologies/pactjs-utils

# Peer dependency
npm install -D @pact-foundation/pact
```

**Requirements**: `@pact-foundation/pact` >= 16.2.0, Node.js >= 18

## Available Utilities

| Category          | Function                          | Description                                          | Use Case                                                                                                  |
| ----------------- | --------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Consumer Helpers  | `createProviderState`             | Builds `[stateName, JsonMap]` tuple from typed input | Consumer tests: `.given(...createProviderState(input))`                                                   |
| Consumer Helpers  | `toJsonMap`                       | Converts any object to Pact-compatible `JsonMap`     | Explicit type coercion for provider state params                                                          |
| Consumer Helpers  | `setJsonContent`                  | Curried request/response JSON callback helper        | PactV4 `.withRequest(...)` and `.willRespondWith(...)` builders                                           |
| Consumer Helpers  | `setJsonBody`                     | Body-only alias of `setJsonContent`                  | Body-only `.willRespondWith(...)` responses                                                               |
| Provider Verifier | `buildVerifierOptions`            | Assembles complete HTTP `VerifierOptions`            | Provider verification: `new Verifier(buildVerifierOptions(...))`                                          |
| Provider Verifier | `buildMessageVerifierOptions`     | Assembles message `VerifierOptions`                  | Kafka/async provider verification                                                                         |
| Provider Verifier | `handlePactBrokerUrlAndSelectors` | Resolves broker URL + selectors from env vars        | Env-aware broker configuration                                                                            |
| Provider Verifier | `getProviderVersionTags`          | CI-aware version tag extraction                      | Provider version tagging in CI                                                                            |
| Request Filter    | `createRequestFilter`             | Express middleware with pluggable token generator    | Auth injection for provider verification                                                                  |
| Request Filter    | `noOpRequestFilter`               | Pass-through filter (no-op)                          | Providers without auth requirements                                                                       |
| 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 |

## Decision Tree: Which Flow?

```
Is this a monorepo (consumer + provider in same repo)?
├── YES → Local Flow
│   - Consumer generates pact files to ./pacts/
│   - Provider reads pact files from ./pacts/ (no broker needed)
│   - Use buildVerifierOptions with pactUrls option
│
└── NO → Do you have a Pact Broker / PactFlow?
    ├── YES → Remote (CDCT) Flow
    │   - Consumer publishes pacts to broker
    │   - Provider verifies from broker
    │   - Use buildVerifierOptions with broker config
    │   - Set PACT_BROKER_BASE_URL + PACT_BROKER_TOKEN
    │
    └── Do you have an OpenAPI spec?
        ├── YES → BDCT Flow (PactFlow only)
        │   - Provider publishes OpenAPI spec to PactFlow
        │   - PactFlow cross-validates consumer pacts against spec
        │   - No provider verification test needed
        │
        └── NO → Start with Local Flow, migrate to Remote later
```

## Design Philosophy

1. **One-call setup**: Each utility does one thing completely — no multi-step assembly required
2. **Environment-aware**: Utilities read env vars for CI/CD integration without manual wiring
3. **Type-safe**: Full TypeScript types for all inputs and outputs, exported for consumer use
4. **Fail-safe defaults**: Sensible defaults that work locally; env vars override for CI
5. **Composable**: Utilities work independently — use only what you need

## Pattern Examples

### Example 1: Minimal Consumer Test

```typescript
import { PactV3 } from '@pact-foundation/pact';
import { createProviderState } from '@seontechnologies/pactjs-utils';

const provider = new PactV3({
  consumer: 'my-frontend',
  provider: 'my-api',
  dir: './pacts',
});

it('should get user by id', async () => {
  await provider
    .given(...createProviderState({ name: 'user exists', params: { id: 1 } }))
    .uponReceiving('a request for user 1')
    .withRequest({ method: 'GET', path: '/users/1' })
    .willRespondWith({ status: 200, body: { id: 1, name: 'John' } })
    .executeTest(async (mockServer) => {
      const res = await fetch(`${mockServer.url}/users/1`);
      expect(res.status).toBe(200);
    });
});
```

### Example 2: Minimal Provider Verification

```typescript
import { Verifier } from '@pact-foundation/pact';
import { buildVerifierOptions, createRequestFilter } from '@seontechnologies/pactjs-utils';

const opts = buildVerifierOptions({
  provider: 'my-api',
  port: '3001',
  includeMainAndDeployed: true,
  stateHandlers: {
    'user exists': async (params) => {
      await db.seed({ users: [{ id: params?.id }] });
    },
  },
  requestFilter: createRequestFilter({
    tokenGenerator: () => 'test-token-123',
  }),
});

await new Verifier(opts).verifyProvider();
```

## Key Points

- **Import path**: Always use `@seontechnologies/pactjs-utils` (no subpath exports)
- **Peer dependency**: `@pact-foundation/pact` must be installed separately
- **Local flow**: No broker needed — set `pactUrls` in verifier options pointing to local pact files
- **Remote flow**: Set `PACT_BROKER_BASE_URL` and `PACT_BROKER_TOKEN` env vars
- **Breaking changes**: Set `includeMainAndDeployed: false` when coordinating breaking changes (verifies only matchingBranch)
- **Builder helpers**: Use `setJsonContent` when you need query/headers/body together; use `setJsonBody` for body-only callbacks
- **Type exports**: Library exports `StateHandlers`, `RequestFilter`, `JsonMap`, `JsonContentInput`, `ConsumerVersionSelector` types

## Related Fragments

- `pactjs-utils-consumer-helpers.md` — detailed createProviderState, toJsonMap, setJsonContent, and setJsonBody usage
- `pactjs-utils-provider-verifier.md` — detailed buildVerifierOptions and broker configuration
- `pactjs-utils-request-filter.md` — detailed createRequestFilter and auth patterns
- `pactjs-utils-zod-to-pact.md` — detailed zodToPactMatchers usage, consumer-curated schema pattern, and anti-patterns
- `contract-testing.md` — foundational contract testing patterns (raw Pact.js approach)
- `test-levels-framework.md` — where contract tests fit in the testing pyramid

## Anti-Patterns

### Wrong: Manual VerifierOptions assembly when pactjs-utils is available

```typescript
// ❌ Don't assemble VerifierOptions manually
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 || 'dev',
  consumerVersionSelectors: [{ mainBranch: true }, { deployedOrReleased: true }],
  stateHandlers: {
    /* ... */
  },
  requestFilter: (req, res, next) => {
    /* ... */
  },
  // ... 20 more lines
};
```

### Right: Use buildVerifierOptions

```typescript
// ✅ Single call handles all configuration
const opts = buildVerifierOptions({
  provider: 'my-api',
  port: '3001',
  includeMainAndDeployed: true,
  stateHandlers: {
    /* ... */
  },
  requestFilter: createRequestFilter({ tokenGenerator: () => 'token' }),
});
```

### Wrong: Importing raw Pact types for JsonMap conversion

```typescript
// ❌ Manual JsonMap casting
import type { JsonMap } from '@pact-foundation/pact';

provider.given('user exists', { id: 1 as unknown as JsonMap['id'] });
```

### Right: Use createProviderState

```typescript
// ✅ Automatic type conversion
import { createProviderState } from '@seontechnologies/pactjs-utils';

provider.given(...createProviderState({ name: 'user exists', params: { id: 1 } }));
```

_Source: @seontechnologies/pactjs-utils library, pactjs-utils README, pact-js-example-provider workflows_
