Skip to main content

Advanced Patterns

This guide covers advanced CVT workflows where AI coding agents can provide significant help.


Schema Evolution Workflows

Managing API changes safely requires understanding breaking changes and coordinating with consumers.

Comparing Schemas for Breaking Changes

Use compareSchemas to understand the impact of API changes:

// Register both schema versions first
await validator.registerSchemaWithVersion('user-api', './v1/openapi.json', '1.0.0');
await validator.registerSchemaWithVersion('user-api', './v2/openapi.json', '2.0.0');

// Compare schema versions by ID
const comparison = await validator.compareSchemas('user-api', '1.0.0', '2.0.0');

console.log('Compatible:', comparison.compatible);
console.log('Breaking changes:', comparison.breakingChanges);

AI agents can help interpret the results:

User: I'm getting breaking changes when comparing my schemas. Here's the output:
[paste comparison result]

AI: Looking at the comparison, I see two breaking changes:
1. The `email` field was removed from the User response - this will break consumers expecting that field
2. The `status` field changed from optional to required - consumers not sending this field will fail

Recommended actions:
- For the email field: Add it back or coordinate deprecation with consumers
- For the status field: Make it optional, or provide a default value

Consumer Registry Setup

Help AI understand your consumer registration needs:

User: Set up our service as a consumer of the user-api. We call GET /users/{id}
and POST /users, and we use the id, name, and email fields.

AI: I'll register your service as a consumer:

await validator.registerConsumer({
consumerId: 'your-service',
consumerVersion: process.env.APP_VERSION || '1.0.0',
schemaId: 'user-api',
schemaVersion: '1.0.0',
environment: process.env.NODE_ENV || 'dev',
usedEndpoints: [
{
method: 'GET',
path: '/users/{id}',
usedFields: ['id', 'name', 'email']
},
{
method: 'POST',
path: '/users',
usedFields: ['id', 'name', 'email']
}
]
});

Deployment Safety Checks

Before deploying schema changes, verify safety:

const result = await validator.canIDeploy('user-api', '2.0.0', 'prod');

if (!result.safeToDeploy) {
console.error('UNSAFE TO DEPLOY');
console.error('Summary:', result.summary);

for (const consumer of result.affectedConsumers) {
if (consumer.willBreak) {
console.error(`- ${consumer.consumerId} v${consumer.consumerVersion} will break`);
consumer.relevantChanges.forEach(c => console.error(` - ${c.description}`));
}
}
process.exit(1);
}

console.log('Safe to deploy!');

CI/CD Integration

AI agents can help set up contract testing in your CI/CD pipelines.

GitHub Actions

note

The health check in the example below uses grpc-health-probe. Ensure the CVT server image includes this binary, or use an alternative health check method like checking the metrics endpoint.

name: Contract Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
contract-tests:
runs-on: ubuntu-latest

services:
cvt:
image: ghcr.io/sahina/cvt:latest
ports:
- 9550:9550
- 9551:9551
options: >-
--health-cmd "grpc-health-probe -addr=localhost:9550 || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run contract tests
run: npm run test:contract
env:
CVT_SERVER: localhost:9550

- name: Register consumer (main branch only)
if: github.ref == 'refs/heads/main'
run: |
npm run register-consumer -- \
--consumer-id ${{ github.repository }} \
--consumer-version ${{ github.sha }} \
--environment staging

GitLab CI

stages:
- test
- register
- deploy

variables:
CVT_SERVER: cvt-server:9550

contract-tests:
stage: test
services:
- name: ghcr.io/sahina/cvt:latest
alias: cvt-server
script:
- npm ci
- npm run test:contract
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

register-consumer:
stage: register
services:
- name: ghcr.io/sahina/cvt:latest
alias: cvt-server
script:
- npm run register-consumer -- \
--consumer-id $CI_PROJECT_PATH \
--consumer-version $CI_COMMIT_SHA \
--environment staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

check-deploy-safety:
stage: deploy
script:
- |
cvt can-i-deploy \
--schema user-api \
--version $NEW_SCHEMA_VERSION \
--env prod \
--server $CVT_SERVER
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual

Integrating with Existing Test Pipelines

If you already have tests, add contract validation:

// jest.setup.ts
import { ContractValidator } from '@sahina/cvt-sdk';

let validator: ContractValidator;

beforeAll(async () => {
validator = new ContractValidator(process.env.CVT_SERVER || 'localhost:9550');

// Register all schemas your tests need (pass a file path or URL)
await validator.registerSchema('user-api', './contracts/user-api.json');

// Make validator available globally
(global as any).cvtValidator = validator;
});

afterAll(async () => {
validator.close();
});
// user.contract.test.ts
describe('User API Contract', () => {
const validator = (global as any).cvtValidator;

it('GET /users/{id} returns valid response', async () => {
// Your actual API call
const response = await fetch('http://user-service/users/123');
const body = await response.json();

// Validate against contract
const result = await validator.validate(
{ method: 'GET', path: '/users/123' },
{ statusCode: response.status, body }
);

expect(result.valid).toBe(true);
});
});

Fixture Generation Power Pattern

CVT can generate schema-compliant test data. This is powerful when combined with AI customization.

The Pattern: Generate → Customize → Validate

Instead of hand-crafting test data (error-prone and drifts from schema), use this workflow:

  1. Generate a valid fixture from the schema
  2. Customize specific fields for your test scenario
  3. Validate to ensure customizations didn't break compliance
// 1. Generate base request body
const baseRequestBody = await validator.generateRequestBody('POST', '/users');

// 2. Customize for your scenario
const testUser = {
...baseRequestBody,
name: 'Test User for Edge Case',
email: 'edge-case@test.com'
};

// 3. Validate customized fixture still complies
const preValidation = await validator.validate(
{ method: 'POST', path: '/users', body: testUser },
{ statusCode: 201, body: { id: 'generated-id' } }
);

expect(preValidation.valid).toBe(true);

Generating Response Fixtures

Use generated responses for mock services:

import nock from 'nock';

// Generate a valid response
const response = await validator.generateResponse('GET', '/users/{id}', {
statusCode: 200
});

// Use in mock
nock('http://user-service')
.get('/users/123')
.reply(200, response.body);

Using Schema Examples

If your OpenAPI schema has examples, prefer those:

const response = await validator.generateResponse('GET', '/users/{id}', {
useExamples: true // Use examples from schema if available
});

Listing Available Endpoints

Ask AI to help explore what fixtures can be generated:

const endpoints = await validator.listEndpoints();

for (const endpoint of endpoints) {
console.log(`${endpoint.method} ${endpoint.path}`);
console.log(` Summary: ${endpoint.summary}`);
}

Working with AI on Advanced Tasks

When asking AI for help with advanced patterns, provide context:

User: Help me set up consumer registration that runs after our contract tests.
We're using Jest and the Node.js SDK. Our service is "order-service" and we
depend on "user-api" and "inventory-api".

AI: I'll help you set up post-test consumer registration. Here's the approach:

1. Capture interactions during tests using the HTTP adapter
2. After all tests pass, register with captured endpoints

[AI provides implementation...]
User: Our canIDeploy check is failing. Here's the output:
[paste canIDeploy result]

AI: Looking at the results, I see order-service v2.1.0 will break because:
- It uses GET /users/{id} which is being removed in the new schema

Options:
1. Coordinate with order-service team to update before deploying
2. Keep the endpoint but mark it deprecated
3. Version the API (v2) rather than breaking v1

Next Steps