import { test } from 'node:test' import assert from 'node:assert' import { applyChaosToExecution, applyChaosToDependencyResponse, applyChaosToAllResponses, createChaosEventArbitrary, extractDelays, sleep, hasAppliedChaos, formatChaosEvents, } from '../quality/chaos-v3.js' import * as fc from 'fast-check' test('applyChaosToExecution: no chaos when events are empty', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: 'ok', headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, []) assert.strictEqual(result.applied, false) assert.strictEqual(result.ctx.response.statusCode, 200) }) test('applyChaosToExecution: inbound error changes status code', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: 'ok', headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, [ { type: 'inbound-error', target: 'inbound', statusCode: 500, body: { error: 'fail' } }, ]) assert.strictEqual(result.applied, true) assert.strictEqual(result.ctx.response.statusCode, 500) assert.deepStrictEqual(result.ctx.response.body, { error: 'fail' }) }) test('applyChaosToExecution: inbound dropout simulates gateway timeout', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: 'ok', headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, [ { type: 'inbound-dropout', target: 'inbound', statusCode: 504 }, ]) assert.strictEqual(result.applied, true) assert.strictEqual(result.ctx.response.statusCode, 504) }) test('applyChaosToExecution: inbound corruption truncates response body', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: { a: 1, b: 2, c: 3 }, headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, [ { type: 'inbound-corruption', target: 'inbound', corruptionStrategy: 'truncate' }, ]) assert.strictEqual(result.applied, true) assert.ok(Object.keys(result.ctx.response.body as object).length < 3) }) test('applyChaosToExecution: inbound corruption with field-corrupt', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: { name: 'test', value: 42 }, headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, [ { type: 'inbound-corruption', target: 'inbound', corruptionStrategy: 'field-corrupt', corruptionField: 'value' }, ]) assert.strictEqual(result.applied, true) assert.strictEqual((result.ctx.response.body as Record).value, null) assert.strictEqual((result.ctx.response.body as Record).name, 'test') }) test('applyChaosToExecution: inbound corruption malformed', () => { const ctx = { request: { body: null, headers: {}, query: {}, params: {} }, response: { body: { ok: true }, headers: {}, statusCode: 200, responseTime: 0 }, timedOut: false, redirects: [], } const result = applyChaosToExecution(ctx, [ { type: 'inbound-corruption', target: 'inbound', corruptionStrategy: 'malformed' }, ]) assert.strictEqual(result.applied, true) assert.strictEqual(result.ctx.response.body, '{"broken":') }) test('applyChaosToDependencyResponse: outbound error changes status', () => { const response = { contractName: 'stripe', statusCode: 200, body: { ok: true } } const result = applyChaosToDependencyResponse(response, [ { type: 'outbound-error', target: 'outbound', contractName: 'stripe', statusCode: 429, body: { error: 'rate_limited' } }, ]) assert.strictEqual(result.statusCode, 429) assert.deepStrictEqual(result.body, { error: 'rate_limited' }) }) test('applyChaosToDependencyResponse: ignores events for other contracts', () => { const response = { contractName: 'stripe', statusCode: 200, body: { ok: true } } const result = applyChaosToDependencyResponse(response, [ { type: 'outbound-error', target: 'outbound', contractName: 'other', statusCode: 500 }, ]) assert.strictEqual(result.statusCode, 200) assert.deepStrictEqual(result.body, { ok: true }) }) test('applyChaosToAllResponses: applies chaos to multiple responses', () => { const responses = [ { contractName: 'stripe', statusCode: 200, body: { id: 'pi_123' } }, { contractName: 'paypal', statusCode: 200, body: { id: 'pp_456' } }, ] const result = applyChaosToAllResponses(responses, [ { type: 'outbound-error', target: 'outbound', contractName: 'stripe', statusCode: 429 }, ]) assert.strictEqual(result[0]!.statusCode, 429) assert.strictEqual(result[1]!.statusCode, 200) }) test('extractDelays: computes total delay', () => { const delays = extractDelays([ { type: 'inbound-delay', target: 'inbound', delayMs: 100 }, { type: 'outbound-delay', target: 'outbound', contractName: 'stripe', delayMs: 50 }, { type: 'inbound-error', target: 'inbound', statusCode: 500 }, ]) assert.strictEqual(delays.totalMs, 150) assert.strictEqual(delays.events.length, 2) }) test('sleep: resolves after specified ms', async () => { const start = Date.now() await sleep(10) const elapsed = Date.now() - start assert.ok(elapsed >= 9) // Allow small timing variance }) test('hasAppliedChaos: detects applied chaos', () => { assert.strictEqual(hasAppliedChaos([{ type: 'none', target: 'inbound' }]), false) assert.strictEqual(hasAppliedChaos([{ type: 'inbound-error', target: 'inbound', statusCode: 500 }]), true) }) test('formatChaosEvents: formats events for diagnostics', () => { const formatted = formatChaosEvents([ { type: 'inbound-error', target: 'inbound', statusCode: 500 }, { type: 'outbound-delay', target: 'outbound', contractName: 'stripe', delayMs: 100 }, ]) assert.ok(formatted.includes('inbound-error')) assert.ok(formatted.includes('outbound-delay')) assert.ok(formatted.includes('stripe')) assert.ok(formatted.includes('100ms')) }) test('createChaosEventArbitrary: generates deterministic events with seed', () => { const arb = createChaosEventArbitrary( { probability: 1, delay: { probability: 0.5, minMs: 10, maxMs: 100 }, error: { probability: 0.5, statusCode: 500 }, }, ['stripe'] ) const samples1 = fc.sample(arb, { numRuns: 5, seed: 42 }) const samples2 = fc.sample(arb, { numRuns: 5, seed: 42 }) assert.deepStrictEqual(samples1, samples2) }) test('createChaosEventArbitrary: returns empty array when no config', () => { const arb = createChaosEventArbitrary(undefined, ['stripe']) const samples = fc.sample(arb, { numRuns: 5, seed: 42 }) assert.ok(samples.every((events) => events.length === 0)) }) test('createChaosEventArbitrary: generates outbound events for contracts', () => { const arb = createChaosEventArbitrary( { probability: 1, outbound: [ { target: 'stripe', error: { probability: 1, responses: [{ statusCode: 429 }] }, }, ], }, ['stripe'] ) const samples = fc.sample(arb, { numRuns: 20, seed: 42 }) // Should generate some outbound-error events const hasOutboundError = samples.some((events) => events.some((e) => e.type === 'outbound-error' && e.contractName === 'stripe') ) assert.ok(hasOutboundError, 'Should generate outbound-error events for stripe') })