import { test } from 'node:test' import assert from 'node:assert' import Fastify from 'fastify' import * as fc from 'fast-check' import apophisPlugin from '../index.js' import { createChaosEventArbitrary } from '../quality/chaos-v3.js' import { FlakeDetector } from '../quality/flake.js' process.env.NODE_ENV = 'test' test('chaos: no events means no disruption', async (t) => { const fastify = Fastify() await fastify.register(apophisPlugin, { runtime: 'off' }) fastify.get('/ping', { schema: { 'x-category': 'utility', 'x-ensures': ['status:200', 'response_body(this).pong == true'], response: { 200: { type: 'object', properties: { pong: { type: 'boolean' } } } } } }, async () => { return { pong: true } }) await fastify.ready() t.after(() => fastify.close()) const result = await (fastify as any).apophis.contract({ runs: 5, chaos: { probability: 0 } }) assert.strictEqual(result.summary.failed, 0) }) test('chaos: error injection breaks contracts', async (t) => { const fastify = Fastify() await fastify.register(apophisPlugin, { runtime: 'off' }) fastify.post('/critical', { schema: { 'x-category': 'constructor', 'x-ensures': ['status:201', 'response_body(this).id != null'], body: { type: 'object', properties: { x: { type: 'number' } } }, response: { 201: { type: 'object', properties: { id: { type: 'string' } } } } } }, async (req, reply) => { reply.status(201) return { id: 'c-1' } }) await fastify.ready() t.after(() => fastify.close()) const result = await (fastify as any).apophis.contract({ runs: 10, chaos: { probability: 1, error: { probability: 1, statusCode: 503 } } }) assert.ok(result.summary.failed > 0, 'chaos error injection should cause contract failures') }) test('chaos: createChaosEventArbitrary determinism with fixed seed', async () => { const arb = createChaosEventArbitrary( { probability: 0.5, error: { probability: 0.5, statusCode: 503 }, delay: { probability: 0.5, minMs: 0, maxMs: 100 }, }, ['test.contract'] ) const events1 = fc.sample(arb, { numRuns: 1, seed: 42 })[0]! const events2 = fc.sample(arb, { numRuns: 1, seed: 42 })[0]! assert.deepStrictEqual(events1, events2, 'same seed produces identical chaos events') const events3 = fc.sample(arb, { numRuns: 1, seed: 43 })[0]! assert.deepStrictEqual(events1, events2) }) test('chaos: FlakeDetector classifies deterministic failures as not flaky', async () => { let callCount = 0 const rerunFn = async (seed?: number) => { callCount++ return { passed: false } } const detector = new FlakeDetector({ sameSeedReruns: 1, seedVariations: 2 }) const report = await detector.detectFlake( { ok: false, name: 'test', id: 1 }, rerunFn, 42 ) assert.strictEqual(report.isFlaky, false) assert.strictEqual(report.confidence, 'high') }) test('chaos: FlakeDetector with varying seeds detects intermittent failures', async () => { let flip = false const rerunFn = async (seed?: number) => { flip = !flip return { passed: flip } } const detector = new FlakeDetector({ sameSeedReruns: 1, seedVariations: 3 }) const report = await detector.detectFlake( { ok: false, name: 'test', id: 1 }, rerunFn, 42 ) assert.strictEqual(report.isFlaky, true) }) test('chaos: chaos events with no strategies produce only none events', async () => { const arb = createChaosEventArbitrary( { probability: 0.5 }, ['svc.users'] ) const events = fc.sample(arb, { numRuns: 1, seed: 1 })[0]! assert.strictEqual(events.filter((e: any) => e.type !== 'none').length, 0) }) test('chaos: chaos events with error strategy generates non-none events', async () => { const arb = createChaosEventArbitrary( { probability: 1, error: { probability: 1, statusCode: 503 } }, ['svc.orders'] ) const samples = fc.sample(arb, { numRuns: 10, seed: 123 }) const activeEvents = samples.flat().filter((e: any) => e.type !== 'none') assert.ok(activeEvents.length > 0, 'error strategy should produce chaos events') }) test('chaos: contract runner with chaos no-ops ignores none events', async (t) => { const fastify = Fastify() await fastify.register(apophisPlugin, { runtime: 'off' }) fastify.get('/stable', { schema: { 'x-category': 'observer', 'x-ensures': ['status:200', 'response_body(this).stable == true'], response: { 200: { type: 'object', properties: { stable: { type: 'boolean' } } } } } }, async () => { return { stable: true } }) await fastify.ready() t.after(() => fastify.close()) const result = await (fastify as any).apophis.contract({ runs: 10, chaos: { probability: 0 } }) assert.strictEqual(result.summary.failed, 0) })