
Configure Api Tests
- 52 installs
- 49 repo stars
- Updated August 4, 2026
- laurigates/claude-plugins
Helps with testing & qa tasks.
About
configure-api-tests is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- configure-api-tests
- Testing & QA
- AI-coding skill
Configure Api Tests by the numbers
- 52 all-time installs (skills.sh)
- Ranked #1,213 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/laurigates/claude-plugins --skill configure-api-testsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 52 |
|---|---|
| repo stars | ★ 49 |
| Last updated | August 4, 2026 |
| Repository | laurigates/claude-plugins ↗ |
What it does
Helps with testing & qa tasks.
Files
/configure:api-tests
Check and configure API contract testing infrastructure for validating API contracts, schemas, and consumer-provider agreements.
When to Use This Skill
| Use this skill when... | Use another approach when... |
|---|---|
| Setting up Pact consumer/provider contract tests | Writing individual unit tests (/configure:tests) |
| Configuring OpenAPI request/response validation | Validating a single API endpoint manually |
| Adding JSON Schema or Zod schema testing | Checking general test coverage (/configure:coverage) |
| Detecting breaking API changes in CI | Reviewing API design decisions (code-review agent) |
| Auditing API testing compliance across a project | Configuring general CI/CD workflows (/configure:workflows) |
Context
- Project root: !
pwd - Package files: !
find . -maxdepth 1 \( -name 'package.json' -o -name 'pyproject.toml' \) - Pact deps: !
find . -maxdepth 1 \( -name package.json -o -name pyproject.toml \) -exec grep -l 'pact' {} + - OpenAPI spec: !
find . -maxdepth 1 \( -name 'openapi.yaml' -o -name 'openapi.json' -o -name 'swagger.json' \) - Pact dir: !
find . -maxdepth 1 -type d -name 'pacts' - Contract tests: !
find tests -maxdepth 2 -type d -name 'contract' - Project standards: !
find . -maxdepth 1 -name '.project-standards.yaml'
Parameters
Parse from command arguments:
--check-only: Report compliance status without modifications (CI/CD mode)--fix: Apply fixes automatically without prompting--type <pact|openapi|schema>: Focus on specific type
API Testing Types:
| Type | Use Case |
|---|---|
| Pact | Microservices, multiple consumers, breaking change detection |
| OpenAPI | API-first development, documentation-driven testing |
| Schema | Simple validation, GraphQL APIs, single service |
Execution
Execute this API testing compliance check:
Step 1: Detect existing API testing infrastructure
Check for these indicators:
| Indicator | Component | Status |
|---|---|---|
pact in dependencies | Pact contract testing | Installed |
openapi.yaml or swagger.json | OpenAPI specification | Present |
@apidevtools/swagger-parser | OpenAPI validation | Configured |
ajv in dependencies | JSON Schema validation | Configured |
pacts/ directory | Pact contracts | Present |
Step 2: Analyze current state
For each detected component, check completeness:
Contract Testing (Pact):
- [ ]
@pact-foundation/pactinstalled (JS) orpact-python(Python) - [ ] Consumer tests defined
- [ ] Provider verification configured
- [ ] Pact Broker or PactFlow configured (optional)
- [ ] CI/CD pipeline integration
OpenAPI Validation:
- [ ] OpenAPI specification file exists
- [ ] Request validation middleware configured
- [ ] Response validation in tests
- [ ] Schema auto-generation configured
- [ ] Breaking change detection
Schema Testing:
- [ ] JSON Schema definitions exist
- [ ]
ajvor similar validator installed - [ ] Response validation helpers
- [ ] Schema versioning strategy
Step 3: Generate compliance report
Print a formatted compliance report:
API Testing Compliance Report
==============================
Project: [name]
API Type: [REST | GraphQL | gRPC]
Contract Testing (Pact):
@pact-foundation/pact package.json [INSTALLED | MISSING]
Consumer tests tests/contract/consumer/ [FOUND | NONE]
Provider tests tests/contract/provider/ [FOUND | NONE]
Pact Broker CI configuration [CONFIGURED | OPTIONAL]
can-i-deploy CI gate [CONFIGURED | OPTIONAL]
OpenAPI Validation:
OpenAPI spec openapi.yaml [EXISTS | MISSING]
Spec version OpenAPI 3.1 [CURRENT | OUTDATED]
Request validation middleware [CONFIGURED | MISSING]
Response validation test helpers [CONFIGURED | MISSING]
Breaking change CI oasdiff [CONFIGURED | OPTIONAL]
Schema Testing:
JSON Schemas schemas/ [EXISTS | N/A]
Schema validator ajv/zod [INSTALLED | MISSING]
Response validation test helpers [CONFIGURED | MISSING]
Overall: [X issues found]
Recommendations:
- Add Pact consumer tests for service dependencies
- Configure OpenAPI response validation in tests
- Add breaking change detection to CIIf --check-only, stop here.
Step 4: Configure API testing (if --fix or user confirms)
Apply fixes based on detected project type. Use templates from REFERENCE.md for:
1. Pact Contract Testing - Install dependencies and create consumer/provider test files 2. OpenAPI Validation - Install validator and create test helpers 3. Schema Testing with Zod - Install Zod and create schema definitions 4. Breaking Change Detection - Install oasdiff and add CI check
For JavaScript/TypeScript projects, install with bun add --dev. For Python, install with uv add --group dev.
Step 5: Configure CI/CD integration
Create .github/workflows/api-tests.yml with jobs for:
- Consumer contract tests
- Provider verification (with service containers if needed)
- OpenAPI spec validation
- Breaking change detection on PRs
Add npm/bun scripts to package.json for local testing. Use workflow templates from REFERENCE.md.
Step 6: Update standards tracking
Update .project-standards.yaml:
standards_version: "2025.1"
last_configured: "[timestamp]"
components:
api_tests: "2025.1"
api_tests_contract: "[pact|none]"
api_tests_openapi: true
api_tests_schema: "[zod|ajv|none]"
api_tests_breaking_change_ci: trueStep 7: Print final report
Print a summary of all changes applied, scripts added, and next steps for the user to verify the configuration.
For detailed templates and code examples, see REFERENCE.md.
Agentic Optimizations
| Context | Command |
|---|---|
| Quick compliance check | /configure:api-tests --check-only |
| Auto-fix all issues | /configure:api-tests --fix |
| Pact contracts only | /configure:api-tests --fix --type pact |
| OpenAPI validation only | /configure:api-tests --fix --type openapi |
| Schema testing only | /configure:api-tests --fix --type schema |
Flags
| Flag | Description |
|---|---|
--check-only | Report status without offering fixes |
--fix | Apply all fixes automatically without prompting |
--type <type> | Focus on specific type (pact, openapi, schema) |
Examples
# Check compliance and offer fixes
/configure:api-tests
# Check only, no modifications
/configure:api-tests --check-only
# Auto-fix all issues
/configure:api-tests --fix
# Configure Pact only
/configure:api-tests --fix --type pact
# Configure OpenAPI validation only
/configure:api-tests --fix --type openapiError Handling
- No OpenAPI spec found: Offer to create template
- Pact version mismatch: Suggest upgrade path
- Schema validation fails: Report specific errors
- Pact Broker not configured: Provide setup instructions
See Also
/configure:tests- Unit testing configuration/configure:integration-tests- Integration testing/configure:all- Run all compliance checks- Pact documentation: https://docs.pact.io
- OpenAPI specification: https://swagger.io/specification
- Zod documentation: https://zod.dev
API Tests Reference
Templates and code examples for API contract testing configuration.
Pact Contract Testing (JavaScript/TypeScript)
Install Dependencies
bun add --dev @pact-foundation/pact @pact-foundation/pact-coreConsumer Test Template (tests/contract/consumer/userService.pact.ts)
import { PactV4, MatchersV3 } from '@pact-foundation/pact';
import { resolve } from 'path';
const { like, eachLike, regex, datetime } = MatchersV3;
const provider = new PactV4({
consumer: 'frontend-app',
provider: 'user-service',
dir: resolve(__dirname, '../../../pacts'),
logLevel: 'warn',
});
describe('User Service Contract', () => {
describe('GET /api/users/:id', () => {
it('returns a user when user exists', async () => {
await provider
.addInteraction()
.given('a user with ID 1 exists')
.uponReceiving('a request to get user 1')
.withRequest({
method: 'GET',
path: '/api/users/1',
headers: {
Accept: 'application/json',
},
})
.willRespondWith({
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: {
id: like(1),
name: like('John Doe'),
email: regex(/^[\w.-]+@[\w.-]+\.\w+$/, 'john@example.com'),
createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
},
})
.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/users/1`, {
headers: { Accept: 'application/json' },
});
expect(response.status).toBe(200);
const user = await response.json();
expect(user).toHaveProperty('id');
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('email');
});
});
it('returns 404 when user does not exist', async () => {
await provider
.addInteraction()
.given('no user with ID 999 exists')
.uponReceiving('a request to get non-existent user')
.withRequest({
method: 'GET',
path: '/api/users/999',
headers: {
Accept: 'application/json',
},
})
.willRespondWith({
status: 404,
headers: {
'Content-Type': 'application/json',
},
body: {
error: like('User not found'),
code: like('USER_NOT_FOUND'),
},
})
.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/users/999`, {
headers: { Accept: 'application/json' },
});
expect(response.status).toBe(404);
});
});
});
describe('POST /api/users', () => {
it('creates a new user', async () => {
await provider
.addInteraction()
.uponReceiving('a request to create a user')
.withRequest({
method: 'POST',
path: '/api/users',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: {
name: like('Jane Doe'),
email: like('jane@example.com'),
},
})
.willRespondWith({
status: 201,
headers: {
'Content-Type': 'application/json',
},
body: {
id: like(1),
name: like('Jane Doe'),
email: like('jane@example.com'),
createdAt: datetime("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"),
},
})
.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
name: 'Jane Doe',
email: 'jane@example.com',
}),
});
expect(response.status).toBe(201);
});
});
});
});Provider Verification Template (tests/contract/provider/userService.provider.ts)
import { Verifier } from '@pact-foundation/pact';
import { resolve } from 'path';
import { app } from '../../../src/app'; // Your Express/Fastify app
import { setupTestDatabase, seedProviderStates } from '../helpers/database';
describe('User Service Provider Verification', () => {
let server: any;
beforeAll(async () => {
await setupTestDatabase();
server = app.listen(3001);
});
afterAll(async () => {
server.close();
});
it('validates the expectations of the consumer', async () => {
const verifier = new Verifier({
providerBaseUrl: 'http://localhost:3001',
pactUrls: [resolve(__dirname, '../../../pacts/frontend-app-user-service.json')],
// Or use Pact Broker:
// pactBrokerUrl: process.env.PACT_BROKER_URL,
// providerVersion: process.env.GIT_SHA,
// publishVerificationResult: process.env.CI === 'true',
stateHandlers: {
'a user with ID 1 exists': async () => {
await seedProviderStates({
users: [{ id: 1, name: 'John Doe', email: 'john@example.com' }],
});
},
'no user with ID 999 exists': async () => {
// Ensure user 999 doesn't exist (default state after cleanup)
},
},
});
await verifier.verifyProvider();
});
});Pact Contract Testing (Python)
Install Dependencies
uv add --group dev pact-pythonConsumer Test Template (tests/contract/consumer/test_user_service.py)
import pytest
from pact import Consumer, Provider, Like, EachLike, Term
import requests
pact = Consumer('frontend-app').has_pact_with(
Provider('user-service'),
pact_dir='./pacts',
log_dir='./logs',
)
@pytest.fixture(scope='module')
def pact_setup():
pact.start_service()
yield pact
pact.stop_service()
def test_get_user(pact_setup):
"""Test getting a user by ID."""
expected = {
'id': Like(1),
'name': Like('John Doe'),
'email': Term(r'^[\w.-]+@[\w.-]+\.\w+$', 'john@example.com'),
}
(pact_setup
.given('a user with ID 1 exists')
.upon_receiving('a request to get user 1')
.with_request('GET', '/api/users/1')
.will_respond_with(200, body=expected))
with pact_setup:
result = requests.get(f'{pact_setup.uri}/api/users/1')
assert result.status_code == 200
assert 'id' in result.json()
assert 'name' in result.json()
def test_get_nonexistent_user(pact_setup):
"""Test 404 response for non-existent user."""
(pact_setup
.given('no user with ID 999 exists')
.upon_receiving('a request to get non-existent user')
.with_request('GET', '/api/users/999')
.will_respond_with(404, body={
'error': Like('User not found'),
'code': Like('USER_NOT_FOUND'),
}))
with pact_setup:
result = requests.get(f'{pact_setup.uri}/api/users/999')
assert result.status_code == 404OpenAPI Validation (JavaScript/TypeScript)
Install Dependencies
bun add --dev @apidevtools/swagger-parser ajv ajv-formats
bun add --dev openapi-typescript # For TypeScript types from OpenAPIOpenAPI Validator (tests/api/openapi-validator.ts)
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import SwaggerParser from '@apidevtools/swagger-parser';
import { OpenAPIV3 } from 'openapi-types';
export class OpenAPIValidator {
private ajv: Ajv;
private spec: OpenAPIV3.Document | null = null;
private schemas: Map<string, object> = new Map();
constructor() {
this.ajv = new Ajv({ allErrors: true, strict: false });
addFormats(this.ajv);
}
async loadSpec(specPath: string): Promise<void> {
this.spec = await SwaggerParser.validate(specPath) as OpenAPIV3.Document;
if (this.spec.components?.schemas) {
for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
this.schemas.set(name, schema);
this.ajv.addSchema(schema, `#/components/schemas/${name}`);
}
}
}
validateResponse(
path: string,
method: string,
statusCode: number,
body: unknown
): { valid: boolean; errors: string[] } {
if (!this.spec) {
throw new Error('OpenAPI spec not loaded. Call loadSpec() first.');
}
const pathItem = this.spec.paths?.[path];
if (!pathItem) {
return { valid: false, errors: [`Path ${path} not found in spec`] };
}
const operation = pathItem[method.toLowerCase() as keyof OpenAPIV3.PathItemObject] as OpenAPIV3.OperationObject;
if (!operation) {
return { valid: false, errors: [`Method ${method} not found for path ${path}`] };
}
const response = operation.responses?.[statusCode] || operation.responses?.['default'];
if (!response) {
return { valid: false, errors: [`Status ${statusCode} not defined for ${method} ${path}`] };
}
const responseObj = response as OpenAPIV3.ResponseObject;
const content = responseObj.content?.['application/json'];
if (!content?.schema) {
return { valid: true, errors: [] };
}
const validate = this.ajv.compile(content.schema);
const valid = validate(body);
return {
valid: !!valid,
errors: validate.errors?.map(e => `${e.instancePath} ${e.message}`) || [],
};
}
validateRequest(
path: string,
method: string,
body: unknown
): { valid: boolean; errors: string[] } {
if (!this.spec) {
throw new Error('OpenAPI spec not loaded. Call loadSpec() first.');
}
const pathItem = this.spec.paths?.[path];
if (!pathItem) {
return { valid: false, errors: [`Path ${path} not found in spec`] };
}
const operation = pathItem[method.toLowerCase() as keyof OpenAPIV3.PathItemObject] as OpenAPIV3.OperationObject;
if (!operation?.requestBody) {
return { valid: true, errors: [] };
}
const requestBody = operation.requestBody as OpenAPIV3.RequestBodyObject;
const content = requestBody.content?.['application/json'];
if (!content?.schema) {
return { valid: true, errors: [] };
}
const validate = this.ajv.compile(content.schema);
const valid = validate(body);
return {
valid: !!valid,
errors: validate.errors?.map(e => `${e.instancePath} ${e.message}`) || [],
};
}
}
export async function createValidator(specPath: string = './openapi.yaml'): Promise<OpenAPIValidator> {
const validator = new OpenAPIValidator();
await validator.loadSpec(specPath);
return validator;
}OpenAPI Test Template (tests/api/users.openapi.test.ts)
import { describe, it, expect, beforeAll } from 'vitest';
import request from 'supertest';
import { app } from '../../src/app';
import { createValidator, OpenAPIValidator } from './openapi-validator';
describe('Users API - OpenAPI Compliance', () => {
let validator: OpenAPIValidator;
beforeAll(async () => {
validator = await createValidator('./openapi.yaml');
});
describe('GET /api/users', () => {
it('response matches OpenAPI schema', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
const result = validator.validateResponse('/api/users', 'GET', 200, response.body);
expect(result.valid).toBe(true);
if (!result.valid) {
console.error('Validation errors:', result.errors);
}
});
});
describe('POST /api/users', () => {
it('request matches OpenAPI schema', async () => {
const requestBody = {
name: 'Test User',
email: 'test@example.com',
};
const requestValidation = validator.validateRequest('/api/users', 'POST', requestBody);
expect(requestValidation.valid).toBe(true);
const response = await request(app)
.post('/api/users')
.send(requestBody)
.expect(201);
const responseValidation = validator.validateResponse('/api/users', 'POST', 201, response.body);
expect(responseValidation.valid).toBe(true);
});
it('rejects invalid request body', async () => {
const invalidBody = {
name: 123, // Should be string
// Missing required email
};
const validation = validator.validateRequest('/api/users', 'POST', invalidBody);
expect(validation.valid).toBe(false);
expect(validation.errors.length).toBeGreaterThan(0);
});
});
describe('GET /api/users/:id', () => {
it('404 response matches OpenAPI schema', async () => {
const response = await request(app)
.get('/api/users/99999')
.expect(404);
const result = validator.validateResponse('/api/users/{id}', 'GET', 404, response.body);
expect(result.valid).toBe(true);
});
});
});OpenAPI Breaking Change Detection
Install oasdiff
bun add --dev @oasdiff/oasdiff
# Or via homebrew
brew install oasdiffCI Workflow Step
- name: Check for breaking API changes
run: |
git fetch origin main
git show origin/main:openapi.yaml > openapi-main.yaml
oasdiff breaking openapi-main.yaml openapi.yaml --fail-on ERR
oasdiff changelog openapi-main.yaml openapi.yamlSchema Testing with Zod
Install Dependencies
bun add zod
bun add --dev @anatine/zod-openapi # Optional: generate OpenAPI from ZodSchema Definitions (src/schemas/user.ts)
import { z } from 'zod';
export const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1).max(100),
email: z.string().email(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime().optional(),
});
export const CreateUserSchema = UserSchema.omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const UpdateUserSchema = CreateUserSchema.partial();
export const UserListSchema = z.array(UserSchema);
export type User = z.infer<typeof UserSchema>;
export type CreateUser = z.infer<typeof CreateUserSchema>;
export type UpdateUser = z.infer<typeof UpdateUserSchema>;Schema Test Template (tests/api/schema.test.ts)
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { app } from '../../src/app';
import { UserSchema, UserListSchema } from '../../src/schemas/user';
describe('API Schema Validation', () => {
describe('GET /api/users', () => {
it('response matches User list schema', async () => {
const response = await request(app)
.get('/api/users')
.expect(200);
const result = UserListSchema.safeParse(response.body);
expect(result.success).toBe(true);
if (!result.success) {
console.error('Schema errors:', result.error.format());
}
});
});
describe('GET /api/users/:id', () => {
it('response matches User schema', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
const result = UserSchema.safeParse(response.body);
expect(result.success).toBe(true);
});
});
});CI/CD Workflow Template
.github/workflows/api-tests.yml
name: API Contract Tests
on:
push:
branches: [main]
pull_request:
paths:
- 'openapi.yaml'
- 'src/api/**'
- 'tests/contract/**'
jobs:
consumer-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run consumer contract tests
run: bun run test:contract:consumer
- name: Upload pacts
uses: actions/upload-artifact@v4
with:
name: pacts
path: pacts/
provider-tests:
runs-on: ubuntu-latest
needs: consumer-tests
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_db
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Download pacts
uses: actions/download-artifact@v4
with:
name: pacts
path: pacts/
- name: Run provider verification
run: bun run test:contract:provider
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
openapi-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate OpenAPI spec
run: |
bunx @apidevtools/swagger-cli validate openapi.yaml
- name: Check for breaking changes
if: github.event_name == 'pull_request'
run: |
git fetch origin main
git show origin/main:openapi.yaml > openapi-main.yaml || echo "No existing spec"
if [ -f openapi-main.yaml ]; then
bunx oasdiff breaking openapi-main.yaml openapi.yaml --fail-on ERR
fi
# Optional: Publish to Pact Broker
publish-pacts:
runs-on: ubuntu-latest
needs: [consumer-tests, provider-tests]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Download pacts
uses: actions/download-artifact@v4
with:
name: pacts
path: pacts/
- name: Publish to Pact Broker
run: |
curl -X PUT \
-H "Content-Type: application/json" \
-d @pacts/frontend-app-user-service.json \
"${{ secrets.PACT_BROKER_URL }}/pacts/provider/user-service/consumer/frontend-app/version/${{ github.sha }}"
env:
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}Package.json Scripts
{
"scripts": {
"test:contract": "bun run test:contract:consumer && bun run test:contract:provider",
"test:contract:consumer": "vitest run tests/contract/consumer/",
"test:contract:provider": "vitest run tests/contract/provider/",
"test:openapi": "vitest run tests/api/*.openapi.test.ts",
"test:schema": "vitest run tests/api/schema.test.ts",
"openapi:validate": "bunx @apidevtools/swagger-cli validate openapi.yaml",
"openapi:bundle": "bunx @apidevtools/swagger-cli bundle openapi.yaml -o dist/openapi.json",
"openapi:types": "bunx openapi-typescript openapi.yaml -o src/types/api.d.ts"
}
}