Files
apophis-fastify/docs/chaos.md
T
John Dvorak dc7a4205ec fix: correct documented vs actual behavior discrepancies from subworker audit
- Fix verify.md --changed exit code (0 → 2)
- Add 'deep' as alias for 'thorough' in generation profile resolution
- Fix PLUGIN_CONTRACTS_SPEC status: Partially implemented (registry done, runner pending)
- Fix OUTBOUND_CONTRACT_MOCKING_SPEC status: Implemented (Phase 1)
- Fix cli.md environment matrix to match actual code granularity
- Fix chaos.md: document delay handler is currently a no-op
- Fix getting-started.md warning: note APOPHIS does not proactively detect nondeterminism
- Add variants section to getting-started.md
- Build: clean | Tests: 849 pass, 0 fail
2026-04-30 11:50:39 -07:00

3.6 KiB

Chaos Mode

Inject controlled failures into contract tests to validate resilience guarantees.

Chaos testing applies the invariant-driven verification approach from Invariant-Driven Automated Testing (Malhado Ribeiro, 2021) under adverse conditions: if a contract must hold, it should still hold when dependencies fail, responses are delayed, or payloads are corrupted.

Usage

const result = await fastify.apophis.contract({
  depth: 'standard',
  chaos: {
    probability: 0.1,  // 10% of requests get chaos
    delay: { probability: 1, minMs: 100, maxMs: 500 },
    error: { probability: 1, statusCode: 503 },
    dropout: { probability: 1 },
    corruption: { probability: 1 },
  },
});

Event Types

Delay

Adds artificial latency. Tests timeout contracts:

timeout_occurred(this) == false
response_time(this) < 1000

Note: Delay events are generated by the chaos arbitrary but the inbound delay handler is currently a no-op. Use this for timeout contract documentation; actual delay injection requires the outbound delay strategy or a custom handler.

Error

Forces HTTP status codes. Tests error-handling contracts:

if status:503 then response_body(this).retry_after != null

Dropout

Simulates network failure (status 0). Tests fallback contracts:

status:200 || status:0

Corruption

Mutates response bodies. Tests parsing robustness:

response_body(this).id != null

Corruption Strategies

Built-in strategies are content-type agnostic:

Strategy Effect
truncate Cuts response body short
malformed Invalidates structural boundaries (e.g., unclosed JSON, bad headers)
field-corrupt Replaces a random field value with corrupted data

Extension strategies can add content-type-specific behavior if needed.

Custom Corruption via Extensions

const myExtension = {
  name: 'custom-corrupt',
  corruptionStrategies: {
    'application/vnd.api+json': (data) => ({
      ...data,
      corrupted: true,
    }),
    'text/*': (data) => `CORRUPTED:${String(data)}`,
  },
};

await fastify.register(apophis, {
  extensions: [myExtension],
});

Extension strategies take precedence over built-ins. Wildcard patterns (text/*) match any subtype.

Environment Guard

Low-level contract chaos APIs require NODE_ENV=test. For CLI qualification, environment policy controls whether chaos gates may run.

Error: chaos is only available in test environment. Set NODE_ENV=test to enable quality features.

Interpreting Results

Failed tests include chaos events in diagnostics:

{
  "statusCode": 503,
  "error": "Contract violation: status:200",
  "chaosEvents": [
    {
      "type": "error",
      "injected": true,
      "details": {
        "statusCode": 503,
        "reason": "Chaos error: overridden 200 with 503"
      }
    }
  ]
}

Best Practices

  1. Start small: probability: 0.05 (5% of requests)
  2. Test one failure mode at a time: Comment out other chaos types
  3. Verify contracts handle chaos: if status:503 then response_body(this).error != null
  4. Use seeds for reproducibility: seed: 42 makes chaos deterministic

Example: Testing Retry Logic

fastify.get('/data', {
  schema: {
    'x-ensures': [
      'if status:503 then response_headers(this).retry-after != null',
      'redirect_count(this) <= 3',
    ],
  },
}, handler);

// Test
const result = await fastify.apophis.contract({
  chaos: {
    probability: 0.2,
    error: { probability: 1, statusCode: 503 },
  },
});