/** * Unit Tests — engine/run-registry.js */ import { describe, it, expect, beforeEach } from 'vitest'; const { RunRegistry, DEFAULT_MAX_EXITED } = require('../../src/engine/run-registry'); describe('RunRegistry', () => { let registry; beforeEach(() => { registry = new RunRegistry({ maxExitedRecords: 5 }); }); describe('add', () => { it('should add a run record', () => { const id = registry.add({ runId: 'r1', agentName: 'coder', backendId: 'claude' }); expect(id).toBe('r1'); expect(registry.get('r1')).toBeDefined(); expect(registry.get('r1').state).toBe('starting'); }); it('should set createdAtMs', () => { registry.add({ runId: 'r2', agentName: 'coder', backendId: 'claude' }); expect(registry.get('r2').createdAtMs).toBeGreaterThan(0); }); }); describe('get', () => { it('should return undefined for non-existent run', () => { expect(registry.get('no-such-run')).toBeUndefined(); }); }); describe('markState', () => { it('should transition to running', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.markState('r1', 'running'); expect(registry.get('r1').state).toBe('running'); expect(registry.get('r1').startedAtMs).toBeGreaterThan(0); }); it('should transition to exited with extra fields', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.markState('r1', 'exited', { exitCode: 0, terminationReason: 'exit' }); const r = registry.get('r1'); expect(r.state).toBe('exited'); expect(r.exitCode).toBe(0); expect(r.terminationReason).toBe('exit'); }); it('should be no-op for non-existent run', () => { expect(() => registry.markState('nope', 'running')).not.toThrow(); }); }); describe('touchOutput', () => { it('should update lastOutputAtMs', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.touchOutput('r1'); expect(registry.get('r1').lastOutputAtMs).toBeGreaterThan(0); }); }); describe('listActive', () => { it('should return only starting + running records', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.add({ runId: 'r2', agentName: 'a', backendId: 'b' }); registry.markState('r1', 'running'); registry.markState('r2', 'exited'); const active = registry.listActive(); expect(active).toHaveLength(1); expect(active[0].runId).toBe('r1'); }); }); describe('listByBackend', () => { it('should filter by backendId', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'claude' }); registry.add({ runId: 'r2', agentName: 'a', backendId: 'gemini' }); expect(registry.listByBackend('claude')).toHaveLength(1); expect(registry.listByBackend('gemini')).toHaveLength(1); }); }); describe('listByAgent', () => { it('should filter by agentName', () => { registry.add({ runId: 'r1', agentName: 'coder', backendId: 'b' }); registry.add({ runId: 'r2', agentName: 'verifier', backendId: 'b' }); expect(registry.listByAgent('coder')).toHaveLength(1); }); }); describe('remove', () => { it('should remove a record', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.remove('r1'); expect(registry.get('r1')).toBeUndefined(); }); }); describe('stats', () => { it('should return state counts', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.add({ runId: 'r2', agentName: 'a', backendId: 'b' }); registry.markState('r1', 'running'); const s = registry.stats(); expect(s.total).toBe(2); expect(s.running).toBe(1); expect(s.starting).toBe(1); }); }); describe('clear', () => { it('should remove all records', () => { registry.add({ runId: 'r1', agentName: 'a', backendId: 'b' }); registry.clear(); expect(registry.list()).toHaveLength(0); }); }); describe('_pruneExited', () => { it('should prune exited records beyond maxExited', () => { // Add 7 records, exit all, then add one more to trigger prune for (let i = 0; i < 7; i++) { registry.add({ runId: `r${i}`, agentName: 'a', backendId: 'b' }); registry.markState(`r${i}`, 'exited'); } // Adding a new one triggers prune registry.add({ runId: 'r7', agentName: 'a', backendId: 'b' }); // With maxExited=5, oldest exited should be pruned expect(registry.list().filter(r => r.state === 'exited').length).toBeLessThanOrEqual(5); }); }); describe('DEFAULT_MAX_EXITED', () => { it('should be 200', () => { expect(DEFAULT_MAX_EXITED).toBe(200); }); }); });