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>
This commit is contained in:
150
tests/queue.test.ts
Normal file
150
tests/queue.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
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']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user