import { describe, expect, it } from 'vitest'; import type { AuditEvent } from './types.js'; import { renderPhase0BaselineMarkdown, summarizePhase0Baseline } from './phase0BaselineSummary.js'; function makeEvent( timestamp: number, event_type: AuditEvent['event_type'], event: Record, ): AuditEvent { return { timestamp, level: 'info', event_type, event, }; } describe('summarizePhase0Baseline', () => { it('summarizes run outcomes, cancel latency, and reaction decisions', () => { const events: AuditEvent[] = [ makeEvent(1000, 'run.state', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', state: 'start', }), makeEvent(1200, 'run.state', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', state: 'complete', }), makeEvent(2000, 'run.state', { session_id: 'discord:s2', channel: 'discord', sender: 'u2', source: 'gateway', state: 'start', }), makeEvent(2400, 'run.state', { session_id: 'discord:s2', channel: 'discord', sender: 'u2', source: 'gateway', state: 'error', }), makeEvent(3000, 'run.state', { session_id: 'telegram:s3', channel: 'telegram', sender: 'u3', source: 'channel', state: 'start', }), makeEvent(3200, 'run.state', { session_id: 'telegram:s3', channel: 'telegram', sender: 'u3', source: 'channel', state: 'cancelled', }), makeEvent(3300, 'run.state', { session_id: 'telegram:s3', channel: 'telegram', sender: 'u3', source: 'channel', state: 'cancel_requested', }), makeEvent(3500, 'run.cancel', { session_id: 'telegram:s3', channel: 'telegram', sender: 'u3', source: 'channel', requested: true, acknowledged: true, latency_ms: 120, }), makeEvent(3600, 'run.cancel', { session_id: 'discord:s2', channel: 'discord', sender: 'u2', source: 'gateway', requested: true, acknowledged: false, latency_ms: 300, }), makeEvent(3700, 'reaction.match', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', rule_name: 'boss-email', }), makeEvent(3800, 'reaction.skip', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', reason: 'no_match', candidate_count: 1, }), makeEvent(3900, 'reaction.skip', { session_id: 'discord:s2', channel: 'discord', sender: 'u2', source: 'gateway', reason: 'no_rules', candidate_count: 0, }), ]; const summary = summarizePhase0Baseline(events); expect(summary.event_counts.run_state).toBe(7); expect(summary.run_outcomes.overall.total_outcomes).toBe(3); expect(summary.run_outcomes.overall.complete).toBe(1); expect(summary.run_outcomes.overall.cancelled).toBe(1); expect(summary.run_outcomes.overall.error).toBe(1); expect(summary.run_outcomes.overall.cancel_requested).toBe(1); expect(summary.run_outcomes.overall.start).toBe(3); const telegram = summary.run_outcomes.by_channel.find((row) => row.key === 'telegram'); expect(telegram?.stats.total_outcomes).toBe(2); const discord = summary.run_outcomes.by_channel.find((row) => row.key === 'discord'); expect(discord?.stats.total_outcomes).toBe(1); const cancelStats = summary.cancel_latency_ms; expect(cancelStats?.count).toBe(2); expect(cancelStats?.p50_ms).toBe(210); expect(cancelStats?.p95_ms).toBe(291); expect(summary.reactions.matched).toBe(1); expect(summary.reactions.skipped).toBe(2); expect(summary.reactions.match_rate_pct).toBe(33.33); expect(summary.reactions.skip_reasons).toEqual([ { reason: 'no_match', count: 1, pct: 50 }, { reason: 'no_rules', count: 1, pct: 50 }, ]); }); it('filters by channel', () => { const events: AuditEvent[] = [ makeEvent(1000, 'run.state', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', state: 'complete', }), makeEvent(1100, 'run.state', { session_id: 'discord:s2', channel: 'discord', sender: 'u2', source: 'gateway', state: 'error', }), ]; const summary = summarizePhase0Baseline(events, { channels: ['telegram'] }); expect(summary.run_outcomes.overall.total_outcomes).toBe(1); expect(summary.run_outcomes.by_channel).toHaveLength(1); expect(summary.run_outcomes.by_channel[0]?.key).toBe('telegram'); }); it('applies max row limits including zero', () => { const events: AuditEvent[] = [ makeEvent(1000, 'run.state', { session_id: 's1', channel: 'telegram', sender: 'u1', source: 'channel', state: 'complete', }), makeEvent(1100, 'run.state', { session_id: 's2', channel: 'discord', sender: 'u2', source: 'gateway', state: 'error', }), makeEvent(1200, 'reaction.skip', { session_id: 's1', channel: 'telegram', sender: 'u1', source: 'channel', reason: 'no_match', }), makeEvent(1300, 'reaction.skip', { session_id: 's2', channel: 'discord', sender: 'u2', source: 'gateway', reason: 'no_rules', }), ]; const none = summarizePhase0Baseline(events, { maxChannels: 0, maxSessions: 0, maxSkipReasons: 0, }); expect(none.run_outcomes.by_channel).toHaveLength(0); expect(none.run_outcomes.by_session).toHaveLength(0); expect(none.reactions.skip_reasons).toHaveLength(0); const oneEach = summarizePhase0Baseline(events, { maxChannels: 1, maxSessions: 1, maxSkipReasons: 1, }); expect(oneEach.run_outcomes.by_channel).toHaveLength(1); expect(oneEach.run_outcomes.by_session).toHaveLength(1); expect(oneEach.reactions.skip_reasons).toHaveLength(1); }); it('rejects negative max limits', () => { const events: AuditEvent[] = []; expect(() => summarizePhase0Baseline(events, { maxSessions: -1 })).toThrow('maxSessions'); expect(() => summarizePhase0Baseline(events, { maxChannels: -1 })).toThrow('maxChannels'); expect(() => summarizePhase0Baseline(events, { maxSkipReasons: -1 })).toThrow('maxSkipReasons'); }); }); describe('renderPhase0BaselineMarkdown', () => { it('renders key sections', () => { const events: AuditEvent[] = [ makeEvent(1000, 'run.state', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', state: 'complete', }), makeEvent(1200, 'reaction.skip', { session_id: 'telegram:s1', channel: 'telegram', sender: 'u1', source: 'channel', reason: 'no_match', candidate_count: 1, }), ]; const summary = summarizePhase0Baseline(events); const markdown = renderPhase0BaselineMarkdown(summary); expect(markdown).toContain('Phase 0 Baseline Telemetry Summary'); expect(markdown).toContain('Run Outcomes (Overall)'); expect(markdown).toContain('Reaction Decisions'); expect(markdown).toContain('no_match'); }); });