监控 A 股基金实时估值与盘后净值,自动判断交易日并生成提醒或分析。
日本語の概要は準備中です。原文の説明を表示しています。
Consumer-Driven Contract Testing (CDC) using Pact framework and OpenAPI-based contract validation. Covers microservice-to-microservice contract verification, provider contract testing, consumer contract testing, contract publishing pipelines, and contract breaking-change detection. USE WHEN: designing microservice APIs, establishing service boundaries, verifying service-to-service contract compatibility, integrating with Pact framework, preventing contract-breaking changes, or setting up a contract testing pipeline in CI/CD. Triggers on "contract test", "Pact", "consumer- driven contract", "CDC", "service contract", "API contract", "breaking change".
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
Source: Pact framework + Martin Fowler + real-world microservice architectures at Google, ThoughtWorks, and ByteDance Core Philosophy: Integration tests are slow and brittle. Contract tests are fast, isolated, and the only practical way to verify microservice compatibility without deploying everything.
Without contract tests:
❌ Services A and B pass all unit tests but fail together on Sunday night
❌ "It worked in staging!" — because staging has different data/config
❌ Integration test suites take 45+ minutes and flake constantly
❌ API changes break consumers who were "supposed to be notified"
With contract tests:
✅ Each service is verified independently against its contracts
✅ Providers know exactly who depends on what
✅ CI fails in 30 seconds, not 45 minutes
✅ Breaking changes are caught before deployment, not after
┌──────────────────────────────────────────────────────────────┐
│ │
│ Consumer: The service that calls an API │
│ → Writes contract tests that document its expectations │
│ → "I expect POST /orders to return 201 with order ID" │
│ │
│ Provider: The service that serves the API │
│ → Verifies that it satisfies all consumer contracts │
│ → "Can I still satisfy all my consumers after this change?"│
│ │
│ Broker: The central repository of contracts │
│ → Stores all pacts from all consumers │
│ → Enables can-i-deploy checks │
│ → Shows dependency graph between services │
│ │
└──────────────────────────────────────────────────────────────┘
Consumer writes test
┌──────────┐ ┌──────────┐
│ Consumer │────→│ Pact │ Consumer defines expectations
│ Service │ │ File │ (what it expects from the API)
└──────────┘ └──────────┘
│
▼
┌──────────┐
│ Broker │ Pact file is published to broker
└──────────┘
│
▼
┌──────────┐ Provider downloads ALL consumer pacts
│ Provider │ and verifies each one against its
│ Service │ actual implementation
└──────────┘
│
▼
✅ All contracts pass → Safe to deploy
❌ Any contract fails → Must fix before deploy
npm install --save-dev @pact-foundation/pact @pact-foundation/pact-core
// test/contract/order-service.pact.test.ts
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { OrderApiClient } from '../src/clients/order-service';
const { like, eachLike, term, iso8601DateTime } = MatchersV3;
describe('Order Service Pact', () => {
const provider = new PactV3({
consumer: 'payment-service', // Who you are
provider: 'order-service', // Who you're calling
port: 4000, // Mock server port
});
describe('GET /orders/{id}', () => {
it('returns order details for valid id', async () => {
// Arrange: define what the provider should respond with
provider
.uponReceiving('a request for order ID 123')
.withRequest({
method: 'GET',
path: '/orders/123',
headers: { Accept: 'application/json' },
})
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
id: like('123'),
status: term({ generate: 'confirmed', matcher: '^(pending|confirmed|shipped)$' }),
items: eachLike({
productId: like('p001'),
quantity: like(2),
price: like(29.99),
}),
createdAt: iso8601DateTime(),
},
});
// Act: run the interaction through the mock provider
await provider.executeTest(async (mockServer) => {
// Point your real client to the mock server
const client = new OrderApiClient(mockServer.url);
const result = await client.getOrder('123');
// Assert: verify the client parses the response correctly
expect(result.id).toBe('123');
expect(result.status).toMatch(/^(pending|confirmed|shipped)$/);
expect(result.items.length).toBeGreaterThanOrEqual(0);
});
});
});
describe('POST /orders', () => {
it('creates an order successfully', async () => {
provider
.uponReceiving('a request to create an order')
.withRequest({
method: 'POST',
path: '/orders',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: {
customerId: like('c001'),
items: eachLike({ productId: like('p001'), quantity: like(1) }),
},
})
.willRespondWith({
status: 201,
headers: { 'Content-Type': 'application/json' },
body: {
id: like('new-order-id'),
status: term({ generate: 'pending', matcher: '^(pending|confirmed)$' }),
},
});
await provider.executeTest(async (mockServer) => {
const client = new OrderApiClient(mockServer.url);
const result = await client.createOrder({
customerId: 'c001',
items: [{ productId: 'p001', quantity: 1 }],
});
expect(result.id).toBeDefined();
expect(result.status).toBe('pending');
});
});
});
});
// scripts/publish-pacts.ts
import { Publisher } from '@pact-foundation/pact';
const publisher = new Publisher({
pactBrokerUrl: process.env.PACT_BROKER_URL || 'https://pact-broker.example.com',
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
consumerVersion: process.env.GIT_COMMIT || '1.0.0',
pactFilesOrDirs: ['./pacts/'],
});
publisher.publishPacts().then(() => console.log('Contracts published'));
# .github/workflows/publish-contracts.yml (consumer side)
name: Publish Consumer Contracts
on: push
jobs:
test-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run test:contract # Runs pact tests, generates pact files
- name: Publish pacts to broker
env:
PACT_BROKER_URL: ${{ vars.PACT_BROKER_URL }}
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
GIT_COMMIT: ${{ github.sha }}
run: npx ts-node scripts/publish-pacts.ts
// test/contract/order-service-verification.test.ts
import { Verifier } from '@pact-foundation/pact';
import { startServer, stopServer } from '../src/server';
describe('Order Service — Pact Provider Verification', () => {
beforeAll(async () => {
await startServer(4001); // Start real service on test port
});
afterAll(async () => {
await stopServer();
});
it('satisfies all consumer contracts', async () => {
const opts = {
provider: 'order-service',
providerBaseUrl: 'http://localhost:4001',
// Option A: Verify against broker (preferred)
pactBrokerUrl: process.env.PACT_BROKER_URL || 'https://pact-broker.example.com',
pactBrokerToken: process.env.PACT_BROKER_TOKEN,
// Option B: Verify against local pact files (for dev)
// pactUrls: [path.resolve(__dirname, '../../pacts/payment-service-order-service.json')],
// Provider states (setup data for test scenarios)
stateHandlers: {
'order 123 exists': async () => {
// Seed database with order ID 123
await seedOrder({ id: '123', status: 'confirmed' });
},
'no orders exist': async () => {
// Clear the orders table
await clearOrders();
},
},
// Custom verifications beyond default
requestFilter: (req, res, next) => {
// Add any custom headers needed
req.headers['x-request-id'] = 'pact-test';
next();
},
};
await new Verifier(opts).verifyProvider();
});
});
# .github/workflows/verify-contracts.yml (provider side)
name: Verify Consumer Contracts
on:
push:
schedule:
- cron: '0 */4 * * *' # Every 4 hours — catch new contracts from consumers
jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run test:provider-verification
env:
PACT_BROKER_URL: ${{ vars.PACT_BROKER_URL }}
PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
DB_URL: postgres://postgres:test@localhost:5432/test
The broker tracks which consumer versions have been verified against which provider versions. Can-I-Deploy checks this matrix:
# Before deploying a new provider version:
pact-broker can-i-deploy \
--pacticipant order-service \
--version $(git rev-parse HEAD) \
--to-environment production
# Before deploying a new consumer version:
pact-broker can-i-deploy \
--pacticipant payment-service \
--version $(git rev-parse HEAD) \
--to-environment production
Provider: order-service
┌────────────────────────────────────┐
│ v1.0 │ v1.1 │ v2.0 (proposed) │
┌─────────────────┼───────┼───────┼───────────────────┤
│ payment v2.1 │ ✅ │ ✅ │ ❌ │
│ notification v1.0 │ ✅ │ ❌ │ ❌ │
│ analytics v3.0 │ ✅ │ ⚠️ │ ✅ │
└─────────────────┴───────┴───────┴───────────────────┘
Result: payment v2.1 is NOT verified against order v2.0
→ Block deployment until contract is resolved
# Add to deployment workflow
deploy:
stage: deploy
script:
- pact-broker can-i-deploy
--pacticipant order-service
--version $CI_COMMIT_SHA
--to-environment production
- ./deploy.sh
only:
- main
# tests/contract/test_order_consumer.py
import atexit
from pact import Consumer, Provider
pact = Consumer('payment-service').has_pact_with(
Provider('order-service'),
host_name='localhost',
port=4000,
pact_dir='./pacts'
)
pact.start_service()
atexit.register(pact.stop_service)
def test_get_order():
expected = {
'id': '123',
'status': 'confirmed',
'items': [
{'product_id': 'p001', 'quantity': 2, 'price': 29.99}
]
}
(pact
.given('order 123 exists')
.upon_receiving('a request for order 123')
.with_request('GET', '/orders/123')
.will_respond_with(200, body=expected))
with pact:
client = OrderClient(f'http://localhost:{pact.port}')
result = client.get_order('123')
assert result['id'] == '123'
# tests/contract/test_order_provider.py
from pact import Verifier
def test_verify_consumer_contracts():
verifier = Verifier(
provider='order-service',
provider_base_url='http://localhost:4001',
)
result = verifier.verify_with_broker(
broker_url='https://pact-broker.example.com',
broker_token='token-here',
publish_version='1.0.0',
publish_verification_results=True,
provider_states_setup_url='http://localhost:4001/_pact/provider-states',
)
assert result == 0, "Contract verification failed!"
For simpler cases where full CDC is overkill:
// test/contract/openapi-contract-test.ts
import { validate } from 'openapi-validator-middleware';
import { spec } from '../../openapi/spec.yaml';
describe('API Contract Validation', () => {
it('GET /orders/:id response matches OpenAPI spec', async () => {
const response = await apiClient.getOrder('test-123');
// Validate response shape against OpenAPI schema
const result = validate.response(spec, '/orders/{id}', 'get', response);
expect(result.valid).toBe(true);
if (!result.valid) {
console.error('Contract violations:', result.errors);
}
});
});
import Ajv from 'ajv';
const ajv = new Ajv();
const orderSchema = {
type: 'object',
required: ['id', 'status', 'items', 'createdAt'],
properties: {
id: { type: 'string', pattern: '^[a-z0-9-]+$' },
status: { type: 'string', enum: ['pending', 'confirmed', 'shipped'] },
items: {
type: 'array',
items: {
type: 'object',
required: ['productId', 'quantity'],
properties: {
productId: { type: 'string' },
quantity: { type: 'integer', minimum: 1 },
price: { type: 'number' },
},
},
},
createdAt: { type: 'string', format: 'date-time' },
},
};
test('GET /orders/:id returns valid JSON Schema', async () => {
const response = await fetch('/orders/123').then(r => r.json());
const validate = ajv.compile(orderSchema);
const valid = validate(response);
expect(valid).toBe(true);
if (!valid) console.error(validate.errors);
});
┌──────────────────────────┐
│ Developer pushes │
│ code change │
└──────────┬───────────────┘
│
┌──────────────┴──────────────┐
│ Consumer Service Push │
│ ┌──────────────────────────┐│
│ │ 1. Unit/Integration ││
│ │ 2. Consumer Pact tests ││
│ │ 3. Publish pacts ││
│ │ 4. ✅ → Mark commit ││
│ │ as "pact published" ││
│ └──────────────────────────┘│
└──────────────────────────────┘
│
┌──────────────┴──────────────┐
│ Provider Service Push │
│ ┌──────────────────────────┐│
│ │ 1. Unit/Integration ││
│ │ 2. Download consumer ││
│ │ pacts from broker ││
│ │ 3. Verify all contracts ││
│ │ 4. ❌ → Fix breaking ││
│ │ change ││
│ │ 5. ✅ → Deploy ││
│ └──────────────────────────┘│
└──────────────────────────────┘
│
┌──────────────┴──────────────┐
│ Deployment Gate │
│ ┌──────────────────────────┐│
│ │ pact-broker can-i-deploy ││
│ │ ✅ → Deploy to staging ││
│ │ ❌ → Block & notify ││
│ └──────────────────────────┘│
└──────────────────────────────┘
DO:
✓ Test realistic data shapes (use matchers for flexible fields)
✓ Keep pact files version-controlled
✓ Verify provider contracts in CI on every push
✓ Use Pact Broker's webhooks to trigger provider verification
✓ Add can-i-deploy to deployment pipeline
DON'T:
✗ Test exhaustive responses — test the contract shape, not all data
✗ Forget to manage provider states — they are essential for meaningful testing
✗ Hardcode exact values unless they're contractually required
✗ Use contract tests for performance or load testing
✗ Let contract tests replace unit/integration tests — they are complementary
┌─────────────────────┬─────────────────────┬─────────────────┐
│ Aspect │ Contract Test │ Integration │
├─────────────────────┼─────────────────────┼─────────────────┤
│ Speed │ ~100ms per pact │ 5-60 min │
│ Isolation │ Full (mock provider)│ Partial (real) │
│ Network required │ No │ Yes │
│ Real data │ No │ Yes │
│ Deployment blocking │ Yes (with broker) │ No │
│ Flaky? │ Rarely │ Often │
│ Best for │ API compatibility │ Behavior & perf │
└─────────────────────┴─────────────────────┴─────────────────┘
See references/pact-patterns.md for advanced Pact patterns (webhooks, multi-provider verification, version compatibility strategies).
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
监控 A 股基金实时估值与盘后净值,自动判断交易日并生成提醒或分析。
日本語の概要は準備中です。原文の説明を表示しています。
You are an accessibility expert specializing in WCAG compliance, inclusive design, and assistive technology compatibility. Conduct audits, identify barriers, and provide remediation guidance.
日本語の概要は準備中です。原文の説明を表示しています。
Expert sales execution covering pipeline management, discovery, demos, negotiation, and deal closing. Use when qualifying opportunities, running MEDDIC discovery, building account plans, handling objections, structuring proposals, or forecasting pipeline.
日本語の概要は準備中です。原文の説明を表示しています。
Support accounting understanding from basic bookkeeping to professional practice and research.
日本語の概要は準備中です。原文の説明を表示しています。
Automate ActiveCampaign tasks via Rube MCP (Composio): manage contacts, tags, list subscriptions, automation enrollment, and tasks. Always search tools first for current schemas.
日本語の概要は準備中です。原文の説明を表示しています。
Add company/person/relationship to CRM
日本語の概要は準備中です。原文の説明を表示しています。