/** * Tests for analytics CLI command logic — metrics review and purge. * * Tests the command action logic by calling the underlying functions * with appropriate parameters, verifying the integration between * config loading, eventsDir resolution, and the analytics functions. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { computeReviewMetrics, formatReviewMetrics, parseSinceDuration, purgeOldEvents, } from '../features/analytics/index.js'; import type { ReviewFindingEvent } from '../features/analytics/index.js'; describe('metrics review command logic', () => { let eventsDir: string; beforeEach(() => { eventsDir = join(tmpdir(), `takt-test-cli-metrics-${Date.now()}`); mkdirSync(eventsDir, { recursive: true }); }); afterEach(() => { rmSync(eventsDir, { recursive: true, force: true }); }); it('should compute and format metrics from resolved eventsDir', () => { const events: ReviewFindingEvent[] = [ { type: 'review_finding', findingId: 'f-001', status: 'new', ruleId: 'r-1', severity: 'error', decision: 'reject', file: 'a.ts', line: 1, iteration: 1, runId: 'r', timestamp: '2026-02-18T10:00:00.000Z', }, ]; writeFileSync( join(eventsDir, '2026-02-18.jsonl'), events.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8', ); const durationMs = parseSinceDuration('30d'); const sinceMs = new Date('2026-02-18T00:00:00Z').getTime(); const result = computeReviewMetrics(eventsDir, sinceMs); const output = formatReviewMetrics(result); expect(output).toContain('Review Metrics'); expect(result.rejectCountsByRule.get('r-1')).toBe(1); }); it('should parse since duration and compute correct time window', () => { const durationMs = parseSinceDuration('7d'); const now = new Date('2026-02-18T12:00:00Z').getTime(); const sinceMs = now - durationMs; expect(sinceMs).toBe(new Date('2026-02-11T12:00:00Z').getTime()); }); }); describe('purge command logic', () => { let eventsDir: string; beforeEach(() => { eventsDir = join(tmpdir(), `takt-test-cli-purge-${Date.now()}`); mkdirSync(eventsDir, { recursive: true }); }); afterEach(() => { rmSync(eventsDir, { recursive: true, force: true }); }); it('should purge files using eventsDir from config and retentionDays from config', () => { writeFileSync(join(eventsDir, '2025-12-01.jsonl'), '{}', 'utf-8'); writeFileSync(join(eventsDir, '2026-02-18.jsonl'), '{}', 'utf-8'); const retentionDays = 30; const deleted = purgeOldEvents(eventsDir, retentionDays, new Date('2026-02-18T12:00:00Z')); expect(deleted).toContain('2025-12-01.jsonl'); expect(deleted).not.toContain('2026-02-18.jsonl'); }); it('should fallback to CLI retentionDays when config has no retentionDays', () => { writeFileSync(join(eventsDir, '2025-01-01.jsonl'), '{}', 'utf-8'); const cliRetentionDays = parseInt('30', 10); const configRetentionDays = undefined; const retentionDays = configRetentionDays ?? cliRetentionDays; const deleted = purgeOldEvents(eventsDir, retentionDays, new Date('2026-02-18T12:00:00Z')); expect(deleted).toContain('2025-01-01.jsonl'); }); it('should use config retentionDays when specified', () => { writeFileSync(join(eventsDir, '2026-02-10.jsonl'), '{}', 'utf-8'); writeFileSync(join(eventsDir, '2026-02-18.jsonl'), '{}', 'utf-8'); const cliRetentionDays = parseInt('30', 10); const configRetentionDays = 5; const retentionDays = configRetentionDays ?? cliRetentionDays; const deleted = purgeOldEvents(eventsDir, retentionDays, new Date('2026-02-18T12:00:00Z')); expect(deleted).toContain('2026-02-10.jsonl'); expect(deleted).not.toContain('2026-02-18.jsonl'); }); });