Quick Start
This guide walks you through your first contract test in under 5 minutes.
Prerequisites
- CVT server running (see Installation)
- An OpenAPI schema to test against (we'll use the included Petstore schema)
You can either:
- Save the Petstore OpenAPI schema as
./openapi.jsonin your project directory, OR - Use the URL directly:
https://petstore3.swagger.io/api/v3/openapi.json
All registerSchema methods accept both file paths and URLs. The examples below use a local file path, but you can substitute a URL.
Step 1: Start the Server
# Using the published Docker image (recommended)
docker run -d -p 9550:9550 -p 9551:9551 ghcr.io/sahina/cvt:latest
# Or using Docker Compose (if you've cloned the repository)
make up
# Or build and run locally
make run-server
Verify it's running:
curl http://localhost:9551/metrics
# Should return Prometheus metrics
In a team environment, the producer (API owner) registers their OpenAPI schema with the shared CVT server, and consumers validate against it. In this Quick Start, we combine both steps for simplicity. See the CI/CD Integration Guide for the recommended production workflow.
Step 2: Create a Simple Test
- Node.js
- Python
- Go
- Java
Create a test file contract.test.ts:
import { ContractValidator } from "@sahina/cvt-sdk";
import * as path from "path";
describe("Petstore API Contract", () => {
let validator: ContractValidator;
beforeAll(async () => {
validator = new ContractValidator("localhost:9550");
// Register the Petstore schema from file
const schemaPath = path.resolve(__dirname, "./openapi.json");
await validator.registerSchema("petstore", schemaPath);
// Or register from URL:
// await validator.registerSchema('petstore', 'https://petstore3.swagger.io/api/v3/openapi.json');
});
afterAll(() => validator.close());
it("validates a correct pet response", async () => {
const result = await validator.validate(
{ method: "GET", path: "/pet/123" },
{
statusCode: 200,
body: {
id: 123,
name: "doggie",
photoUrls: ["https://example.com/photo.jpg"],
status: "available",
},
},
);
expect(result.valid).toBe(true);
});
it("catches invalid responses", async () => {
const result = await validator.validate(
{ method: "GET", path: "/pet/123" },
{
statusCode: 200,
body: { id: 123 }, // missing required 'name' and 'photoUrls' fields
},
);
expect(result.valid).toBe(false);
expect(result.errors.length).toBeGreaterThan(0);
});
});
Run the test:
npm test
Create a test file test_contract.py:
import pytest
from cvt_sdk import ContractValidator
@pytest.fixture(scope="module")
def validator():
v = ContractValidator('localhost:9550')
# Register the Petstore schema from file
v.register_schema('petstore', './openapi.json')
# Or register from URL:
# v.register_schema('petstore', 'https://petstore3.swagger.io/api/v3/openapi.json')
yield v
v.close()
def test_validates_correct_pet_response(validator):
result = validator.validate(
request={'method': 'GET', 'path': '/pet/123'},
response={
'status_code': 200,
'body': {
'id': 123,
'name': 'doggie',
'photoUrls': ['https://example.com/photo.jpg'],
'status': 'available'
}
}
)
assert result["valid"] is True
def test_catches_invalid_responses(validator):
result = validator.validate(
request={'method': 'GET', 'path': '/pet/123'},
response={
'status_code': 200,
'body': {'id': 123} # missing required 'name' and 'photoUrls' fields
}
)
assert result["valid"] is False
assert len(result["errors"]) > 0
Run the test:
pytest test_contract.py -v
Create a test file contract_test.go:
package main
import (
"context"
"testing"
"github.com/sahina/cvt/sdks/go/cvt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPetstoreAPIContract(t *testing.T) {
ctx := context.Background()
validator, err := cvt.NewValidator("localhost:9550")
require.NoError(t, err)
defer validator.Close()
// Register the Petstore schema from file
err = validator.RegisterSchema(ctx, "petstore", "./openapi.json")
// Or register from URL:
// err = validator.RegisterSchema(ctx, "petstore", "https://petstore3.swagger.io/api/v3/openapi.json")
require.NoError(t, err)
t.Run("validates correct pet response", func(t *testing.T) {
result, err := validator.Validate(ctx,
cvt.ValidationRequest{Method: "GET", Path: "/pet/123"},
cvt.ValidationResponse{
StatusCode: 200,
Body: map[string]any{
"id": 123,
"name": "doggie",
"photoUrls": []string{"https://example.com/photo.jpg"},
"status": "available",
},
},
)
require.NoError(t, err)
assert.True(t, result.Valid)
})
t.Run("catches invalid responses", func(t *testing.T) {
result, err := validator.Validate(ctx,
cvt.ValidationRequest{Method: "GET", Path: "/pet/123"},
cvt.ValidationResponse{
StatusCode: 200,
Body: map[string]any{"id": 123}, // missing required 'name' and 'photoUrls'
},
)
require.NoError(t, err)
assert.False(t, result.Valid)
assert.Greater(t, len(result.Errors), 0)
})
}
Run the test:
go test -v
Create a test file ContractTest.java:
package com.example;
import io.github.sahina.sdk.ContractValidator;
import io.github.sahina.sdk.ValidationRequest;
import io.github.sahina.sdk.ValidationResponse;
import io.github.sahina.sdk.ValidationResult;
import org.junit.jupiter.api.*;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
class PetstoreAPIContractTest {
private static ContractValidator validator;
@BeforeAll
static void setup() throws IOException {
validator = new ContractValidator("localhost:9550");
// Register the Petstore schema from file
validator.registerSchema("petstore", "./openapi.json");
// Or register from URL:
// validator.registerSchema("petstore", "https://petstore3.swagger.io/api/v3/openapi.json");
}
@AfterAll
static void teardown() {
validator.close();
}
@Test
void validatesCorrectPetResponse() {
ValidationRequest request = ValidationRequest.builder()
.method("GET")
.path("/pet/123")
.build();
ValidationResponse response = ValidationResponse.builder()
.statusCode(200)
.body("{\"id\": 123, \"name\": \"doggie\", \"photoUrls\": [\"https://example.com/photo.jpg\"], \"status\": \"available\"}")
.build();
ValidationResult result = validator.validate(request, response);
assertTrue(result.isValid());
}
@Test
void catchesInvalidResponses() {
ValidationRequest request = ValidationRequest.builder()
.method("GET")
.path("/pet/123")
.build();
ValidationResponse response = ValidationResponse.builder()
.statusCode(200)
.body("{\"id\": 123}") // missing required 'name' and 'photoUrls' fields
.build();
ValidationResult result = validator.validate(request, response);
assertFalse(result.isValid());
assertFalse(result.getErrors().isEmpty());
}
}
Run the test:
mvn test
Step 3: Use the CLI
For quick validations without code:
# Build the CLI
go build -o cvt ./cmd/cvt
# Create request and response files
cat > request.json << 'EOF'
{
"method": "GET",
"path": "/pet/123"
}
EOF
cat > response.json << 'EOF'
{
"status_code": 200,
"body": "{\"id\": 123, \"name\": \"doggie\", \"photoUrls\": [\"https://example.com/photo.jpg\"], \"status\": \"available\"}"
}
EOF
# Validate the interaction
cvt validate --schema openapi.json --request request.json --response response.json
What's Next?
Now that you've validated your first interaction, explore:
- Consumer Testing Guide - Test your API integrations
- Producer Testing Guide - Validate your API implementations
- Breaking Changes Guide - Detect schema incompatibilities
- SDK Reference - Language-specific features
Common Patterns
Loading Schema from URL
// Register schema directly from URL
await validator.registerSchema(
"petstore",
"https://petstore3.swagger.io/api/v3/openapi.json",
);
Using HTTP Adapters
Automatically validate all HTTP traffic:
import axios from "axios";
import { createAxiosAdapter } from "@sahina/cvt-sdk/adapters";
const api = axios.create({ baseURL: "http://api.example.com" });
const adapter = createAxiosAdapter({
axios: api,
validator,
autoValidate: true,
});
// All requests/responses are now validated automatically
const pet = await api.get("/pet/123");
// Check validation results
const interactions = adapter.getInteractions();
console.log(interactions[0].validationResult?.valid);
Registering as a Consumer
Enable deployment safety checks:
await validator.registerConsumer({
consumerId: "my-service",
consumerVersion: "1.0.0",
schemaId: "petstore",
schemaVersion: "1.0.0",
environment: "dev",
usedEndpoints: [
{
method: "GET",
path: "/pet/{petId}",
usedFields: ["id", "name", "status"],
},
],
});