import { describe, it, expect } from 'vitest'; import { FilterMetrics } from '../src/llm/metrics.js'; describe('FilterMetrics', () => { it('starts with zeroed stats', () => { const m = new FilterMetrics(); const stats = m.getStats(); expect(stats.totalTokensProcessed).toBe(0); expect(stats.tokensSaved).toBe(0); expect(stats.cacheHits).toBe(0); expect(stats.cacheMisses).toBe(0); expect(stats.filterCount).toBe(0); expect(stats.averageFilterLatencyMs).toBe(0); }); it('records filter operations and accumulates tokens', () => { const m = new FilterMetrics(); m.recordFilter(500, 200, 50); m.recordFilter(300, 100, 30); const stats = m.getStats(); expect(stats.totalTokensProcessed).toBe(800); expect(stats.tokensSaved).toBe(500); // (500-200) + (300-100) expect(stats.filterCount).toBe(2); expect(stats.averageFilterLatencyMs).toBe(40); // (50+30)/2 }); it('does not allow negative token savings', () => { const m = new FilterMetrics(); // Filtered output is larger than original (edge case) m.recordFilter(100, 200, 10); const stats = m.getStats(); expect(stats.totalTokensProcessed).toBe(100); expect(stats.tokensSaved).toBe(0); // clamped to 0 }); it('records cache hits and misses independently', () => { const m = new FilterMetrics(); m.recordCacheHit(); m.recordCacheHit(); m.recordCacheMiss(); const stats = m.getStats(); expect(stats.cacheHits).toBe(2); expect(stats.cacheMisses).toBe(1); }); it('computes average latency correctly', () => { const m = new FilterMetrics(); m.recordFilter(100, 50, 10); m.recordFilter(100, 50, 20); m.recordFilter(100, 50, 30); expect(m.getStats().averageFilterLatencyMs).toBe(20); }); it('returns 0 average latency when no filter operations', () => { const m = new FilterMetrics(); // Only cache operations, no filter calls m.recordCacheHit(); expect(m.getStats().averageFilterLatencyMs).toBe(0); }); it('resets all metrics to zero', () => { const m = new FilterMetrics(); m.recordFilter(500, 200, 50); m.recordCacheHit(); m.recordCacheMiss(); m.reset(); const stats = m.getStats(); expect(stats.totalTokensProcessed).toBe(0); expect(stats.tokensSaved).toBe(0); expect(stats.cacheHits).toBe(0); expect(stats.cacheMisses).toBe(0); expect(stats.filterCount).toBe(0); expect(stats.averageFilterLatencyMs).toBe(0); }); it('returns independent snapshots', () => { const m = new FilterMetrics(); m.recordFilter(100, 50, 10); const snap1 = m.getStats(); m.recordFilter(200, 100, 20); const snap2 = m.getStats(); // snap1 should not have been mutated expect(snap1.totalTokensProcessed).toBe(100); expect(snap2.totalTokensProcessed).toBe(300); }); });