diff --git a/docs/cli.md b/docs/cli.md index 4d1d531..e98af97 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -221,7 +221,7 @@ apophis replay --artifact reports/apophis/failure-*.json - `--changed` requires a git repository - `migrate` defaults to `--dry-run` (safe by default) -- `--workspace` is only supported by `verify` and `doctor` commands +- `--workspace` is fully implemented by `verify` and `doctor`. `observe` and `qualify` accept the flag but run in the current package only. - Seeds ensure deterministic generation; handler nondeterminism (e.g., `Date.now()`) can still cause replay divergence ## Exit Codes diff --git a/docs/getting-started.md b/docs/getting-started.md index 924e39b..b086212 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -130,6 +130,102 @@ app.get('/users', { }) ``` +## Plugin Options + +When registering the APOPHIS plugin, you can pass these options: + +```javascript +await fastify.register(apophis, { + // Swagger config passthrough (if @fastify/swagger is not already registered) + swagger: { openapi: { info: { title: 'API', version: '1.0.0' } } }, + + // Runtime contract validation hooks: 'off', 'warn', or 'error' + // Only active in non-production environments + runtime: 'warn', + + // Automatically clean up tracked resources after tests + cleanup: true, + + // Global timeout in milliseconds for all requests + timeout: 5000, + + // Tenant isolation scopes + scopes: { + tenant1: { headers: { 'x-tenant-id': '1' } }, + tenant2: { headers: { 'x-tenant-id': '2' } }, + }, + + // Auth and protocol extensions + extensions: [jwtAuth, apiKeyAuth], + + // Plugin hook-phase contracts + pluginContracts: { + 'rate-limit': { appliesTo: 'POST /users', ensures: ['status != 429'] }, + }, + + // Outbound dependency contracts + outboundContracts: { + 'payment-api': { + target: 'https://payments.example.com', + method: 'POST', + response: { 200: { type: 'object', properties: { id: { type: 'string' } } } } + } + } +}) +``` + +## Schema Annotations + +APOPHIS reads these OpenAPI schema extensions: + +| Annotation | Location | Description | +|---|---|---| +| `x-category` | Top-level | Route classification: `constructor`, `mutator`, `observer`, `destructor`, `utility` | +| `x-ensures` | Top-level or `response[statusCode]` | Post-condition contracts (APOSTL formulas) | +| `x-requires` | Top-level or `response[statusCode]` | Pre-condition contracts (APOSTL formulas) | +| `x-variants` | Top-level | Request variants for content-type negotiation or feature flags | +| `x-timeout` | Top-level or `response[statusCode]` | Per-route timeout in milliseconds | +| `x-outbound` | Top-level | Outbound dependency contracts for this route | +| `x-streaming` | Top-level | Mark route as streaming (populates `chunks` and `streamDurationMs` in eval context) | +| `x-validate-runtime` | Top-level or `response[statusCode]` | Toggle runtime validation for this route (default: true) | +| `x-extension-config` | Top-level | Per-route config for extensions (e.g., `{ jwt: { verify: false } }`) | + +Annotations can be placed on the top-level schema or nested inside `response[statusCode]`. Nested annotations take precedence for that status code. + +## Programmatic API + +After registration, `fastify.apophis` provides: + +```javascript +// Run contract tests for all routes +const suite = await fastify.apophis.contract({ runs: 50, seed: 42 }) + +// Run stateful tests +const stateful = await fastify.apophis.stateful({ runs: 50, seed: 42 }) + +// Run a single scenario +const scenario = await fastify.apophis.scenario({ + name: 'oauth-basic', + steps: [...] +}) + +// Check a single route +const result = await fastify.apophis.check('GET', '/users/:id') + +// Get enriched OpenAPI spec with contract metadata +const spec = fastify.apophis.spec() + +// Clean up tracked resources +await fastify.apophis.cleanup() + +// Test-only utilities (NODE_ENV=test only) +fastify.apophis.test.registerPluginContracts('name', spec) +fastify.apophis.test.registerOutboundContracts({ ... }) +fastify.apophis.test.enableOutboundMocks({ mode: 'example' }) +fastify.apophis.test.disableOutboundMocks() +const calls = fastify.apophis.test.getOutboundCalls('payment-api') +``` + ## Config Reference For the full configuration reference, see [CLI Reference](cli.md). diff --git a/docs/qualify.md b/docs/qualify.md index 5f1fe86..e01ea48 100644 --- a/docs/qualify.md +++ b/docs/qualify.md @@ -250,14 +250,6 @@ Human output shows per-gate execution counts (scenario, stateful, chaos, adversi Qualify exits with code 1 if zero checks executed. This prevents silent passes when all routes are filtered out or gates are disabled. -## `--workspace` Flag - -Run qualify across all packages in a monorepo workspace: - -```bash -apophis qualify --workspace --profile oauth-nightly -``` - ## Test Budget The `runs` field in your preset controls how many property-based tests execute per route. Default is 50. Lower for faster CI feedback, higher for deeper exploration: