@autotelic/openapi-test-coverage
v0.1.0
Published
OpenAPI test coverage analysis based on the A-TEST '19 coverage criteria (TCL 0-7). Records HTTP traffic, computes coverage against an OpenAPI spec, and reports results.
Maintainers
Readme
@autotelic/openapi-test-coverage
OpenAPI test coverage analysis based on the A-TEST '19 paper "Test Coverage Criteria for RESTful Web APIs" (Martin-Lopez, Segura, Ruiz-Cortes).
Records HTTP traffic during integration tests, computes coverage against an OpenAPI 3.x spec, and reports results across 10 criteria organised into Test Coverage Levels (TCL 0--7).
Coverage Criteria
| TCL | Criteria added | |-----|----------------| | 0 | (none) | | 1 | Path coverage | | 2 | + Operation coverage | | 3 | + Input and output content-type coverage | | 4 | + Parameter coverage, status code class coverage | | 5 | + Parameter value coverage, status code coverage | | 6 | + Response body properties coverage | | 7 | + Operation flow coverage (not yet implemented) |
Install
npm install @autotelic/openapi-test-coverageOr from git:
npm install https://github.com/autotelic/openapi-test-coverage.gitQuick Start
1. Record HTTP calls
Use the Fastify adapter to automatically record all requests and responses:
const { registerCoverageHooks } = require('@autotelic/openapi-test-coverage/adapters/fastify')
// After creating your Fastify app and registering routes:
const recorder = registerCoverageHooks(app, { autoAssertOnInject: true })
await app.ready()Or record manually with the core Recorder:
const { Recorder } = require('@autotelic/openapi-test-coverage')
const recorder = new Recorder()
recorder.record({
method: 'GET',
pathname: '/v1/items',
queryParams: { limit: '10' },
responseStatusCode: 200,
responseContentType: 'application/json',
responseBody: [{ id: '1', name: 'Item' }],
})2. Compute coverage
const { computeCoverage } = require('@autotelic/openapi-test-coverage')
const coverage = computeCoverage(recorder.getCalls(), openapiSpec, {
excludedPaths: ['/internal/health'],
excludedStatusCodes: ['/legacy\tGET\t404'],
})
console.log(`TCL: ${coverage.tcl}`)
console.log(`Paths: ${coverage.path.covered}/${coverage.path.total}`)
console.log(`Operations: ${coverage.operation.covered}/${coverage.operation.total}`)3. Report
const { printReport, writeJsonReport, writeHtmlReport } = require('@autotelic/openapi-test-coverage')
printReport(coverage, { verbose: true })
writeJsonReport(coverage, 'test/output/openapi-coverage.json')
writeHtmlReport(coverage, 'test/output/openapi-coverage.html')Adapters
Fastify
registerCoverageHooks(app, options?) registers onSend and onResponse hooks on a Fastify instance. Returns a Recorder.
const { registerCoverageHooks } = require('@autotelic/openapi-test-coverage/adapters/fastify')
const recorder = registerCoverageHooks(app, {
// Pass an existing recorder (optional, creates one if omitted)
recorder: myRecorder,
// Wrap app.inject() to auto-mark responses as asserted (default: true)
autoAssertOnInject: true,
})Mocha
createCoverageAfterAll(options) returns a function suitable for use in Mocha's afterAll hook. It computes coverage, prints reports, and optionally enforces a minimum TCL.
const { createCoverageAfterAll } = require('@autotelic/openapi-test-coverage/adapters/mocha')
module.exports.mochaHooks = {
async beforeAll() {
// ... set up app, spec, recorder ...
},
async afterAll() {
const reportCoverage = createCoverageAfterAll({
getRecorder: () => app.coverageRecorder,
getSpec: () => app.openapiSpec,
config: {
excludedPaths: [],
excludedStatusCodes: [],
excludedResponseBodyProperties: [],
},
console: { verbose: true },
jsonReportPath: process.env.OPENAPI_COVERAGE_JSON,
htmlReportPath: process.env.OPENAPI_COVERAGE_HTML,
minTcl: 6,
})
reportCoverage()
await app.close()
},
}API
Core
| Export | Description |
|--------|-------------|
| Recorder | Class that stores recorded HTTP calls. Methods: record(opts), markLastResponseAsserted(), getCalls(), clear(). |
| computeCoverage(calls, spec, config?) | Compute all coverage metrics and TCL from recorded calls against an OpenAPI spec. Returns a CoverageResult. |
| computeTcl(inputs) | Compute the TCL (0--6) from individual metric totals/covered counts. |
| walkSpec(spec, excludedPaths?) | Extract all coverage targets (paths, operations, parameters, etc.) from a resolved OpenAPI spec. |
| matchRequestToSpec(call, specPaths, basePath) | Match a recorded request to a spec path template. |
| getBasePath(spec) | Derive the base path from the spec's servers[0].url. |
| collectSchemaPaths(spec, schema) | Collect all property paths from an OpenAPI schema (for response body coverage). |
| collectPathsFromValue(value) | Collect property paths from an actual JSON response body. |
Reporters
| Export | Description |
|--------|-------------|
| printReport(coverage, opts?) | Print a coloured console report. Options: { verbose: boolean, maxMissingItems: number }. |
| writeJsonReport(coverage, filepath) | Write coverage as JSON. Creates parent directories if needed. |
| writeHtmlReport(coverage, filepath) | Write a standalone HTML report. |
Configuration
CoverageConfig supports three exclusion lists:
excludedPaths-- Path keys to omit entirely (e.g.['/admin/reports']).excludedStatusCodes-- Tab-separatedpath\tmethod\tstatusCodekeys to exclude from status code and response body coverage.excludedResponseBodyProperties-- Tab-separatedpath\tmethod\tstatusCode\tcontentType\tpropertyPathkeys to exclude from response body property coverage.
Environment Variables
These are conventions used by the Mocha adapter; the core library is env-agnostic.
| Variable | Description |
|----------|-------------|
| OPENAPI_COVERAGE_JSON | Path to write a JSON coverage report. |
| OPENAPI_COVERAGE_HTML | Path to write an HTML coverage report. |
| OPENAPI_COVERAGE_MIN_TCL | Minimum TCL required; test run fails if below this. |
Coverage Result Shape
interface CoverageResult {
path: { total: number; covered: number; missing: string[] }
operation: { total: number; covered: number; missing: string[] }
statusCode: { total: number; covered: number; missing: string[] }
parameter: { total: number; covered: number; missing: string[] }
parameterValue: { total: number; covered: number; missing: string[] }
inputContentType: { total: number; covered: number; missing: string[] }
outputContentType: { total: number; covered: number; missing: string[] }
statusCodeClass: { total: number; covered: number; missing: string[] }
responseBodyProperties: { total: number; covered: number; missing: string[] }
responseAsserted: { total: number; covered: number; missing: string[] }
tcl: number
}Development
pnpm install
pnpm build # Build CJS + ESM + .d.ts
pnpm test # Run vitest
pnpm typecheck # tsc --noEmitLicense
MIT
