Files
telemetry-client-js/tests/queue.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

151 lines
4.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { EventQueue } from '../src/queue.js';
import {
TaskType,
Complexity,
Harness,
Provider,
Outcome,
TaskCompletionEvent,
} from '../src/types/events.js';
function makeEvent(id: string): TaskCompletionEvent {
return {
instance_id: 'test-instance',
event_id: id,
schema_version: '1.0',
timestamp: new Date().toISOString(),
task_duration_ms: 1000,
task_type: TaskType.IMPLEMENTATION,
complexity: Complexity.MEDIUM,
harness: Harness.CLAUDE_CODE,
model: 'claude-3-opus',
provider: Provider.ANTHROPIC,
estimated_input_tokens: 1000,
estimated_output_tokens: 500,
actual_input_tokens: 1100,
actual_output_tokens: 550,
estimated_cost_usd_micros: 50000,
actual_cost_usd_micros: 55000,
quality_gate_passed: true,
quality_gates_run: [],
quality_gates_failed: [],
context_compactions: 0,
context_rotations: 0,
context_utilization_final: 0.5,
outcome: Outcome.SUCCESS,
retry_count: 0,
};
}
describe('EventQueue', () => {
it('should enqueue and drain events', () => {
const queue = new EventQueue(10);
const event = makeEvent('e1');
queue.enqueue(event);
expect(queue.size).toBe(1);
expect(queue.isEmpty).toBe(false);
const drained = queue.drain(10);
expect(drained).toHaveLength(1);
expect(drained[0].event_id).toBe('e1');
expect(queue.isEmpty).toBe(true);
});
it('should respect maxSize with FIFO eviction', () => {
const queue = new EventQueue(3);
queue.enqueue(makeEvent('e1'));
queue.enqueue(makeEvent('e2'));
queue.enqueue(makeEvent('e3'));
expect(queue.size).toBe(3);
// Adding a 4th should evict the oldest (e1)
queue.enqueue(makeEvent('e4'));
expect(queue.size).toBe(3);
const drained = queue.drain(10);
expect(drained.map((e) => e.event_id)).toEqual(['e2', 'e3', 'e4']);
});
it('should drain up to maxItems', () => {
const queue = new EventQueue(10);
queue.enqueue(makeEvent('e1'));
queue.enqueue(makeEvent('e2'));
queue.enqueue(makeEvent('e3'));
const drained = queue.drain(2);
expect(drained).toHaveLength(2);
expect(drained.map((e) => e.event_id)).toEqual(['e1', 'e2']);
expect(queue.size).toBe(1);
});
it('should remove drained items from the queue', () => {
const queue = new EventQueue(10);
queue.enqueue(makeEvent('e1'));
queue.enqueue(makeEvent('e2'));
queue.drain(1);
expect(queue.size).toBe(1);
const remaining = queue.drain(10);
expect(remaining[0].event_id).toBe('e2');
});
it('should report isEmpty correctly', () => {
const queue = new EventQueue(5);
expect(queue.isEmpty).toBe(true);
queue.enqueue(makeEvent('e1'));
expect(queue.isEmpty).toBe(false);
queue.drain(1);
expect(queue.isEmpty).toBe(true);
});
it('should report size correctly', () => {
const queue = new EventQueue(10);
expect(queue.size).toBe(0);
queue.enqueue(makeEvent('e1'));
expect(queue.size).toBe(1);
queue.enqueue(makeEvent('e2'));
expect(queue.size).toBe(2);
queue.drain(1);
expect(queue.size).toBe(1);
});
it('should return empty array when draining empty queue', () => {
const queue = new EventQueue(5);
const drained = queue.drain(10);
expect(drained).toEqual([]);
});
it('should prepend events to the front of the queue', () => {
const queue = new EventQueue(10);
queue.enqueue(makeEvent('e3'));
queue.prepend([makeEvent('e1'), makeEvent('e2')]);
expect(queue.size).toBe(3);
const drained = queue.drain(10);
expect(drained.map((e) => e.event_id)).toEqual(['e1', 'e2', 'e3']);
});
it('should respect maxSize when prepending', () => {
const queue = new EventQueue(3);
queue.enqueue(makeEvent('e3'));
queue.enqueue(makeEvent('e4'));
// Only 1 slot available, so only first event should be prepended
queue.prepend([makeEvent('e1'), makeEvent('e2')]);
expect(queue.size).toBe(3);
const drained = queue.drain(10);
expect(drained.map((e) => e.event_id)).toEqual(['e1', 'e3', 'e4']);
});
});