run-registry.test.js 4.73 KB
/**
 * 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);
    });
  });
});