Files
telemetry-client-js/tests/event-builder.test.ts
Jason Woltje 177720e523 feat: TypeScript telemetry client SDK v0.1.0
Standalone npm package (@mosaicstack/telemetry-client) for reporting
task-completion telemetry and querying predictions from the Mosaic
Stack Telemetry server.

- TelemetryClient with setInterval-based background flush
- EventQueue (bounded FIFO array)
- BatchSubmitter with native fetch, exponential backoff, Retry-After
- PredictionCache (Map + TTL)
- EventBuilder with auto-generated event_id/timestamp
- Zero runtime dependencies (Node 18+ native APIs)
- 43 tests, 86% branch coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 23:25:31 -06:00

220 lines
6.5 KiB
TypeScript

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');
});
});