fix: harden engine, enrich failure diagnostics, close adoption gaps
- P0: CLI verify now honors test budget with seeded multi-sample - P0: Observe sampling enforced via Math.random() gate in hook-validator - P1: Remove misleading undici-mock-agent isolation option - P1: Qualify reuses shared discoverRouteDetails() with warnings - P1: Chaos/scenario config exposed via preset schema - P1: README/docs limitations updated to current state - P2: Nested response annotations prefer 2xx deterministically - P2: --changed documented as heuristic in verify.md - Add observe sink tests (sampling 0/1, sink failure non-interference) - Add verify runs regression tests (scale, determinism, variants) - Add configured-scenario qualify test (independent of OAuth fixture) - Add coverageBreakdown to qualify artifacts (per-gate route coverage) - Add production-style observe example with real sink in docs/observe.md - Add nightly/staging vs PR gating guidance to docs/qualify.md - Enrich VerifyFailure with formula-aware diagnostics: status:201 => 'HTTP 200', body field checks => actual values - Remove stale observe CLI activation message - Document outbound mocks as process-global in getting-started.md - Refresh APOPHIS_ADOPTION_AUDIT.md with current state 903 tests pass, build clean, typecheck clean.
This commit is contained in:
@@ -46,23 +46,26 @@ await fastify.apophis.contract({
|
||||
})
|
||||
```
|
||||
|
||||
### wrapFetch for Outbound Interception
|
||||
### Outbound Mocking
|
||||
|
||||
Use `fastify.apophis.test.enableOutboundMocks()` in test code to mock HTTP dependencies:
|
||||
|
||||
```typescript
|
||||
import { wrapFetch, createOutboundInterceptor } from 'apophis-fastify'
|
||||
|
||||
const interceptor = createOutboundInterceptor([
|
||||
{
|
||||
target: 'api.stripe.com',
|
||||
delay: { probability: 0.1, minMs: 1000, maxMs: 5000 },
|
||||
error: {
|
||||
probability: 0.05,
|
||||
responses: [{ statusCode: 429, headers: { 'retry-after': '60' } }]
|
||||
}
|
||||
fastify.apophis.test.registerOutboundContracts({
|
||||
'payment-api': {
|
||||
target: 'https://api.stripe.com/v1',
|
||||
method: 'POST',
|
||||
response: { 200: { type: 'object', properties: { id: { type: 'string' } } } }
|
||||
}
|
||||
], 42)
|
||||
})
|
||||
fastify.apophis.test.enableOutboundMocks({ mode: 'example' })
|
||||
const calls = fastify.apophis.test.getOutboundCalls('payment-api')
|
||||
```
|
||||
|
||||
const interceptedFetch = wrapFetch(globalThis.fetch, interceptor)
|
||||
Programmatic access via `createOutboundMockRuntime`:
|
||||
|
||||
```typescript
|
||||
import { createOutboundMockRuntime } from '@apophis/fastify'
|
||||
```
|
||||
|
||||
### Mutation Testing
|
||||
@@ -70,7 +73,7 @@ const interceptedFetch = wrapFetch(globalThis.fetch, interceptor)
|
||||
Measure contract strength by injecting synthetic bugs:
|
||||
|
||||
```typescript
|
||||
import { runMutationTesting } from 'apophis-fastify/quality/mutation'
|
||||
import { runMutationTesting } from '@apophis/fastify/quality/mutation'
|
||||
|
||||
const report = await runMutationTesting(fastify)
|
||||
console.log(`Score: ${report.score}%`) // 0-100
|
||||
@@ -190,7 +193,7 @@ Extensions register custom APOSTL predicates that can be used in `x-ensures` and
|
||||
**Register via `extensions: [sseExtension]`**
|
||||
|
||||
```typescript
|
||||
import { sseExtension } from 'apophis-fastify/extensions/sse'
|
||||
import { sseExtension } from '@apophis/fastify/extensions/sse'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [sseExtension]
|
||||
@@ -236,7 +239,7 @@ sse_events(this).0.retry // number (ms)
|
||||
**Register via `extensions: [createSerializerExtension(registry)]`**
|
||||
|
||||
```typescript
|
||||
import { createSerializerExtension, createSerializerRegistry } from 'apophis-fastify/extensions/serializers'
|
||||
import { createSerializerExtension, createSerializerRegistry } from '@apophis/fastify/extensions/serializers'
|
||||
|
||||
const registry = createSerializerRegistry()
|
||||
registry.register('protobuf', {
|
||||
@@ -273,7 +276,7 @@ fastify.post('/users', {
|
||||
**Register via `extensions: [websocketExtension]`**
|
||||
|
||||
```typescript
|
||||
import { websocketExtension } from 'apophis-fastify/extensions/websocket'
|
||||
import { websocketExtension } from '@apophis/fastify/extensions/websocket'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [websocketExtension]
|
||||
@@ -320,7 +323,7 @@ ws_state(this) // string
|
||||
**Register via `extensions: [jwtExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { jwtExtension } from 'apophis-fastify/extensions'
|
||||
import { jwtExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [
|
||||
@@ -348,7 +351,7 @@ jwt_format(this) == "compact"
|
||||
**Register via `extensions: [x509Extension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { x509Extension } from 'apophis-fastify/extensions'
|
||||
import { x509Extension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [x509Extension()]
|
||||
@@ -370,7 +373,7 @@ x509_self_signed(this) == false
|
||||
**Register via `extensions: [spiffeExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { spiffeExtension } from 'apophis-fastify/extensions'
|
||||
import { spiffeExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [spiffeExtension()]
|
||||
@@ -391,7 +394,7 @@ spiffe_validate(this) == true
|
||||
**Register via `extensions: [tokenHashExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { tokenHashExtension } from 'apophis-fastify/extensions'
|
||||
import { tokenHashExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [tokenHashExtension()]
|
||||
@@ -412,7 +415,7 @@ token_hash(this, "sha256") == jwt_claims(this).ath
|
||||
**Register via `extensions: [httpSignatureExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { httpSignatureExtension } from 'apophis-fastify/extensions'
|
||||
import { httpSignatureExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [httpSignatureExtension()]
|
||||
@@ -433,7 +436,7 @@ signature_valid(this) == true
|
||||
**Register via `extensions: [timeExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { timeExtension } from 'apophis-fastify/extensions'
|
||||
import { timeExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [timeExtension()]
|
||||
@@ -453,7 +456,7 @@ jwt_claims(this).exp <= now() + 30000
|
||||
**Register via `extensions: [statefulExtension()]`**
|
||||
|
||||
```typescript
|
||||
import { statefulExtension } from 'apophis-fastify/extensions'
|
||||
import { statefulExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [statefulExtension()]
|
||||
@@ -521,7 +524,7 @@ fastify.get('/tenants/:id', {
|
||||
**Register via `extensions: [requestContextExtension(config)]`**
|
||||
|
||||
```typescript
|
||||
import { requestContextExtension } from 'apophis-fastify/extensions'
|
||||
import { requestContextExtension } from '@apophis/fastify/extensions'
|
||||
|
||||
await fastify.register(apophis, {
|
||||
extensions: [requestContextExtension()]
|
||||
@@ -555,19 +558,7 @@ await fastify.apophis.contract({
|
||||
|
||||
### Outbound Interception
|
||||
|
||||
```typescript
|
||||
import { wrapFetch, createOutboundInterceptor } from 'apophis-fastify'
|
||||
|
||||
const interceptor = createOutboundInterceptor([{
|
||||
target: 'api.stripe.com',
|
||||
error: {
|
||||
probability: 0.05,
|
||||
responses: [{ statusCode: 429, headers: { 'retry-after': '60' } }]
|
||||
}
|
||||
}], 42)
|
||||
|
||||
const interceptedFetch = wrapFetch(globalThis.fetch, interceptor)
|
||||
```
|
||||
Outbound interception works through `fastify.apophis.test.enableOutboundMocks()` in test code. See the [Outbound Mocking](#outbound-mocking) section for the supported API.
|
||||
|
||||
### Per-Route Overrides
|
||||
|
||||
|
||||
Reference in New Issue
Block a user