import { describe, it, expect, vi, afterEach } from 'vitest'; import { EventBuilder } from '../src/event-builder.js'; import { ResolvedConfig } from '../src/config.js'; import { TaskType, Complexity, Harness, Provider, Outcome, QualityGate, RepoSizeCategory, } from '../src/types/events.js'; function makeConfig(): ResolvedConfig { return { serverUrl: 'https://tel.example.com', apiKey: 'a'.repeat(64), instanceId: 'my-instance-uuid', enabled: true, submitIntervalMs: 300_000, maxQueueSize: 1000, batchSize: 100, requestTimeoutMs: 10_000, predictionCacheTtlMs: 21_600_000, dryRun: false, maxRetries: 3, onError: () => {}, }; } describe('EventBuilder', () => { afterEach(() => { vi.restoreAllMocks(); }); it('should build a complete TaskCompletionEvent', () => { const builder = new EventBuilder(makeConfig()); const event = builder.build({ task_duration_ms: 15000, task_type: TaskType.IMPLEMENTATION, complexity: Complexity.HIGH, harness: Harness.CLAUDE_CODE, model: 'claude-3-opus', provider: Provider.ANTHROPIC, estimated_input_tokens: 2000, estimated_output_tokens: 1000, actual_input_tokens: 2200, actual_output_tokens: 1100, estimated_cost_usd_micros: 100000, actual_cost_usd_micros: 110000, quality_gate_passed: true, quality_gates_run: [QualityGate.BUILD, QualityGate.TEST, QualityGate.LINT], quality_gates_failed: [], context_compactions: 2, context_rotations: 1, context_utilization_final: 0.75, outcome: Outcome.SUCCESS, retry_count: 0, language: 'typescript', repo_size_category: RepoSizeCategory.MEDIUM, }); expect(event.task_type).toBe(TaskType.IMPLEMENTATION); expect(event.complexity).toBe(Complexity.HIGH); expect(event.model).toBe('claude-3-opus'); expect(event.quality_gates_run).toEqual([ QualityGate.BUILD, QualityGate.TEST, QualityGate.LINT, ]); expect(event.language).toBe('typescript'); expect(event.repo_size_category).toBe(RepoSizeCategory.MEDIUM); }); it('should auto-generate event_id as UUID', () => { const builder = new EventBuilder(makeConfig()); const event = builder.build({ task_duration_ms: 1000, task_type: TaskType.TESTING, complexity: Complexity.LOW, harness: Harness.AIDER, model: 'gpt-4', provider: Provider.OPENAI, estimated_input_tokens: 100, estimated_output_tokens: 50, actual_input_tokens: 100, actual_output_tokens: 50, estimated_cost_usd_micros: 1000, actual_cost_usd_micros: 1000, quality_gate_passed: true, quality_gates_run: [], quality_gates_failed: [], context_compactions: 0, context_rotations: 0, context_utilization_final: 0.3, outcome: Outcome.SUCCESS, retry_count: 0, }); // UUID format: 8-4-4-4-12 hex chars expect(event.event_id).toMatch( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/, ); // Each event should get a unique ID const event2 = builder.build({ task_duration_ms: 1000, task_type: TaskType.TESTING, complexity: Complexity.LOW, harness: Harness.AIDER, model: 'gpt-4', provider: Provider.OPENAI, estimated_input_tokens: 100, estimated_output_tokens: 50, actual_input_tokens: 100, actual_output_tokens: 50, estimated_cost_usd_micros: 1000, actual_cost_usd_micros: 1000, quality_gate_passed: true, quality_gates_run: [], quality_gates_failed: [], context_compactions: 0, context_rotations: 0, context_utilization_final: 0.3, outcome: Outcome.SUCCESS, retry_count: 0, }); expect(event.event_id).not.toBe(event2.event_id); }); it('should auto-set timestamp to ISO 8601', () => { const now = new Date('2026-02-07T10:00:00.000Z'); vi.setSystemTime(now); const builder = new EventBuilder(makeConfig()); const event = builder.build({ task_duration_ms: 1000, task_type: TaskType.DEBUGGING, complexity: Complexity.MEDIUM, harness: Harness.OPENCODE, model: 'claude-3-sonnet', provider: Provider.ANTHROPIC, estimated_input_tokens: 500, estimated_output_tokens: 200, actual_input_tokens: 500, actual_output_tokens: 200, estimated_cost_usd_micros: 5000, actual_cost_usd_micros: 5000, quality_gate_passed: false, quality_gates_run: [QualityGate.TEST], quality_gates_failed: [QualityGate.TEST], context_compactions: 0, context_rotations: 0, context_utilization_final: 0.4, outcome: Outcome.FAILURE, retry_count: 1, }); expect(event.timestamp).toBe('2026-02-07T10:00:00.000Z'); }); it('should set instance_id from config', () => { const config = makeConfig(); const builder = new EventBuilder(config); const event = builder.build({ task_duration_ms: 1000, task_type: TaskType.PLANNING, complexity: Complexity.LOW, harness: Harness.UNKNOWN, model: 'test-model', provider: Provider.UNKNOWN, estimated_input_tokens: 0, estimated_output_tokens: 0, actual_input_tokens: 0, actual_output_tokens: 0, estimated_cost_usd_micros: 0, actual_cost_usd_micros: 0, quality_gate_passed: true, quality_gates_run: [], quality_gates_failed: [], context_compactions: 0, context_rotations: 0, context_utilization_final: 0, outcome: Outcome.SUCCESS, retry_count: 0, }); expect(event.instance_id).toBe('my-instance-uuid'); }); it('should set schema_version to 1.0', () => { const builder = new EventBuilder(makeConfig()); const event = builder.build({ task_duration_ms: 1000, task_type: TaskType.REFACTORING, complexity: Complexity.CRITICAL, harness: Harness.KILO_CODE, model: 'gemini-pro', provider: Provider.GOOGLE, estimated_input_tokens: 3000, estimated_output_tokens: 2000, actual_input_tokens: 3000, actual_output_tokens: 2000, estimated_cost_usd_micros: 80000, actual_cost_usd_micros: 80000, quality_gate_passed: true, quality_gates_run: [QualityGate.TYPECHECK], quality_gates_failed: [], context_compactions: 5, context_rotations: 2, context_utilization_final: 0.95, outcome: Outcome.SUCCESS, retry_count: 0, }); expect(event.schema_version).toBe('1.0'); }); });