/** * SignalManager Tests — Verify atomic signal.json operations. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { FileSystemSignalManager } from './signal-manager.js'; describe('FileSystemSignalManager', () => { let tempDir: string; let agentWorkdir: string; let signalManager: FileSystemSignalManager; beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), 'signal-manager-test-')); agentWorkdir = join(tempDir, 'agent-workdir'); await mkdir(join(agentWorkdir, '.cw', 'output'), { recursive: true }); signalManager = new FileSystemSignalManager(); }); afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); }); describe('clearSignal', () => { it('should remove existing signal.json file', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, JSON.stringify({ status: 'done' })); await signalManager.clearSignal(agentWorkdir); const exists = await signalManager.checkSignalExists(agentWorkdir); expect(exists).toBe(false); }); it('should not throw if signal.json does not exist', async () => { await expect(signalManager.clearSignal(agentWorkdir)).resolves.not.toThrow(); }); }); describe('checkSignalExists', () => { it('should return true when signal.json exists', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, JSON.stringify({ status: 'done' })); const exists = await signalManager.checkSignalExists(agentWorkdir); expect(exists).toBe(true); }); it('should return false when signal.json does not exist', async () => { const exists = await signalManager.checkSignalExists(agentWorkdir); expect(exists).toBe(false); }); }); describe('readSignal', () => { it('should read valid done signal', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const expectedSignal = { status: 'done' }; await writeFile(signalPath, JSON.stringify(expectedSignal)); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toEqual(expectedSignal); }); it('should read valid questions signal', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const expectedSignal = { status: 'questions', questions: [{ id: '1', question: 'What to do?' }] }; await writeFile(signalPath, JSON.stringify(expectedSignal)); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toEqual(expectedSignal); }); it('should read valid error signal', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const expectedSignal = { status: 'error', error: 'Something went wrong' }; await writeFile(signalPath, JSON.stringify(expectedSignal)); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toEqual(expectedSignal); }); it('should return null for invalid JSON', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, '{ invalid json'); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toBeNull(); }); it('should return null for invalid status', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, JSON.stringify({ status: 'invalid' })); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toBeNull(); }); it('should return null for empty file', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, ''); const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toBeNull(); }); it('should return null when file does not exist', async () => { const signal = await signalManager.readSignal(agentWorkdir); expect(signal).toBeNull(); }); }); describe('waitForSignal', () => { it('should return signal when file already exists', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const expectedSignal = { status: 'done' }; await writeFile(signalPath, JSON.stringify(expectedSignal)); const signal = await signalManager.waitForSignal(agentWorkdir, 1000); expect(signal).toEqual(expectedSignal); }); it('should wait for signal to appear', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const expectedSignal = { status: 'done' }; // Write signal after a delay setTimeout(async () => { await writeFile(signalPath, JSON.stringify(expectedSignal)); }, 30); const signal = await signalManager.waitForSignal(agentWorkdir, 1000); expect(signal).toEqual(expectedSignal); }); it('should timeout if signal never appears', async () => { const signal = await signalManager.waitForSignal(agentWorkdir, 100); expect(signal).toBeNull(); }); }); describe('validateSignalFile', () => { it('should return true for complete valid JSON file', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, JSON.stringify({ status: 'done' })); const isValid = await signalManager.validateSignalFile(signalPath); expect(isValid).toBe(true); }); it('should return false for incomplete JSON', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, '{ "status": "don'); const isValid = await signalManager.validateSignalFile(signalPath); expect(isValid).toBe(false); }); it('should return false for empty file', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); await writeFile(signalPath, ''); const isValid = await signalManager.validateSignalFile(signalPath); expect(isValid).toBe(false); }); it('should return false when file does not exist', async () => { const signalPath = join(agentWorkdir, '.cw/output/signal.json'); const isValid = await signalManager.validateSignalFile(signalPath); expect(isValid).toBe(false); }); }); });