Files
apophis-fastify/src/test/chaos-final.test.ts
T

157 lines
4.7 KiB
TypeScript

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)
})