# Quality Engines APOPHIS includes three quality engines for advanced testing: chaos injection, flake detection, and mutation testing. All require `NODE_ENV=test`. ## Chaos Injection Inject controlled failures into contract tests to validate resilience guarantees. Chaos events are generated by fast-check alongside test data, making them shrinkable — when a test fails, fast-check finds the minimal chaos event that causes the failure. ### Usage ```javascript const result = await fastify.apophis.contract({ runs: 50, chaos: { delay: { probability: 0.1, minMs: 100, maxMs: 500 }, error: { probability: 0.1, statusCode: 503 }, dropout: { probability: 0.05 }, corruption: { probability: 0.1 }, }, }) ``` ### Event Types | Type | Effect | Tests | |------|--------|-------| | `delay` | Artificial latency | `response_time(this) < 1000` | | `error` | Forces HTTP status code | Error-handling contracts | | `dropout` | Network failure (status 0 or 504) | Fallback contracts | | `corruption` | Mutates response bodies | Parsing robustness | ### Corruption Strategies | Strategy | Effect | |----------|--------| | `truncate` | Cuts response body in half | | `malformed` | Returns invalid JSON (`{"broken":`) | | `field-corrupt` | Sets a random field to `null` | ### Programmatic API ```javascript import { applyChaosToExecution, createChaosEventArbitrary, formatChaosEvents, } from 'apophis-fastify' // Apply pre-generated chaos events to a context const result = applyChaosToExecution(ctx, events) // Generate deterministic chaos events const arb = createChaosEventArbitrary(config, contractNames) const events = fc.sample(arb, { numRuns: 1, seed: 42 })[0] // Format for diagnostics console.log(formatChaosEvents(events)) ``` ### Best Practices 1. Start small: `probability: 0.05` (5% of requests) 2. Test one failure mode at a time 3. Verify contracts handle chaos: `if status:503 then response_code(GET /health) == 200` 4. Use seeds for reproducibility: `seed: 42` ## Flake Detection Automatically rerun failing tests with varied seeds to detect non-deterministic contracts. A "flake" is a test that fails on one run but passes on another with the same or different seed. ### Usage ```javascript import { FlakeDetector } from 'apophis-fastify' const detector = new FlakeDetector({ sameSeedReruns: 1, // Rerun with same seed seedVariations: 3, // Try 3 additional seeds }) const report = await detector.detectFlake( originalFailingResult, async (seed) => { const suite = await fastify.apophis.contract({ seed }) return { passed: suite.summary.failed === 0 } }, originalSeed ) if (report.isFlaky) { console.log(`Flaky with ${report.confidence} confidence`) console.log('Reruns:', report.reruns) } ``` ### Report Structure ```javascript { isFlaky: true, confidence: 'high', // 'high' | 'medium' | 'low' reruns: [ { seed: 42, passed: false }, { seed: 43, passed: true }, ] } ``` ### Confidence Scoring | Pass Rate | Confidence | |-----------|------------| | 0% pass | `high` (deterministic failure) | | < 50% pass | `medium` | | >= 50% pass | `low` (likely flaky) | ## Mutation Testing Measure contract strength by injecting synthetic bugs. A "mutation" is a small change to a contract (e.g., flip `==` to `!=`). If the test suite catches the mutation (fails), the mutation is "killed". If it passes, the mutation "survives" — indicating weak coverage. ### Usage ```javascript import { runMutationTesting } from 'apophis-fastify/quality/mutation' const report = await runMutationTesting(fastify, { runs: 10, seed: 42, maxMutationsPerContract: 5, routes: ['/items'], // Optional: only test these routes }) console.log(`Mutation score: ${report.score}%`) console.log(`Killed: ${report.killed}, Survived: ${report.survived}`) console.log('Weak contracts:', report.weakContracts) ``` ### Mutation Operators | Type | Example | |------|---------| | `flip-operator` | `== 201` → `!= 201` | | `change-number` | `== 200` → `== 201` | | `remove-clause` | `A && B` → `A` | | `negate-boolean` | `== true` → `== false` | | `swap-variable` | `response_body` → `request_body` | | `remove-ensures` | Remove one ensures clause entirely | ### Report Structure ```javascript { score: 85, // 0-100 killed: 17, survived: 3, durationMs: 4500, weakContracts: ['POST /items'], // Routes where no mutations were killed mutations: [ { mutation: { id: 'm0', route: 'POST /items', original: 'response_code(this) == 201', mutated: 'response_code(this) != 201', type: 'flip-operator', }, killed: true, durationMs: 120, } ] } ``` ### Single Mutation Test Test a specific mutation without running the full suite: ```javascript import { testMutation } from 'apophis-fastify/quality/mutation' const killed = await testMutation(fastify, contract, mutation, { runs: 10, seed: 42, }) ``` ## Environment Guard All quality engines require `NODE_ENV=test`: ``` Error: chaos is only available in test environment. Set NODE_ENV=test to enable quality features. ``` This prevents accidental execution in production or development. ## Integration Example Run all three engines in a CI pipeline: ```javascript // 1. Standard contract tests const suite = await fastify.apophis.contract({ runs: 50, seed: 42 }) // 2. Chaos tests const chaosSuite = await fastify.apophis.contract({ runs: 50, seed: 42, chaos: { error: { probability: 0.1, statusCode: 503 } }, }) // 3. Flake detection on failures for (const test of suite.tests.filter(t => !t.ok)) { const report = await detector.detectFlake(test, rerunFn, 42) if (report.isFlaky) { console.warn(`Flaky test detected: ${test.name}`) } } // 4. Mutation testing const mutationReport = await runMutationTesting(fastify, { runs: 10 }) if (mutationReport.score < 80) { console.warn(`Low mutation score: ${mutationReport.score}%`) } ```