/** * Tests for delegation-handler CLI flag passthrough feature * Covers: parseStringFlag, timeout validation, max-turns validation, agents JSON validation */ import { describe, it, expect, beforeEach, afterEach, spyOn } from 'bun:test'; // Import the DelegationHandler class import { DelegationHandler } from '../../../src/delegation/delegation-handler'; describe('DelegationHandler', () => { let handler: DelegationHandler; let consoleErrorSpy: ReturnType; beforeEach(() => { handler = new DelegationHandler(); consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { consoleErrorSpy.mockRestore(); }); describe('_extractOptions - timeout validation', () => { it('accepts valid positive timeout', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout', '30000']); expect(options.timeout).toBe(30000); }); it('rejects NaN timeout with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout', 'abc']); expect(options.timeout).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects negative timeout with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout', '-5000']); expect(options.timeout).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects zero timeout with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout', '0']); expect(options.timeout).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects timeout exceeding max (600000ms) with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout', '700000']); expect(options.timeout).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('ignores missing timeout value at end of args', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--timeout']); expect(options.timeout).toBeUndefined(); }); }); describe('_extractOptions - max-turns validation', () => { it('accepts valid positive max-turns', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', '10']); expect(options.maxTurns).toBe(10); }); it('rejects NaN max-turns with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', 'abc']); expect(options.maxTurns).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects negative max-turns with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', '-5']); expect(options.maxTurns).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects zero max-turns with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', '0']); expect(options.maxTurns).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('caps max-turns at 100 when exceeding limit', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', '500']); expect(options.maxTurns).toBe(100); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('accepts max-turns at exactly 100', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--max-turns', '100']); expect(options.maxTurns).toBe(100); }); }); describe('_extractOptions - fallback-model validation', () => { it('accepts valid fallback-model', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--fallback-model', 'sonnet']); expect(options.fallbackModel).toBe('sonnet'); }); it('rejects dash-prefixed value with warning', () => { const options = handler._extractOptions([ 'glm', '-p', 'test', '--fallback-model', '--other-flag', ]); expect(options.fallbackModel).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects empty string value', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--fallback-model', '']); expect(options.fallbackModel).toBeUndefined(); }); it('rejects whitespace-only value', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--fallback-model', ' ']); expect(options.fallbackModel).toBeUndefined(); }); }); describe('_extractOptions - agents JSON validation', () => { it('accepts valid JSON for agents', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--agents', '{"name":"test"}']); expect(options.agents).toBe('{"name":"test"}'); }); it('rejects invalid JSON with warning', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--agents', '{invalid json}']); expect(options.agents).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalled(); }); it('rejects dash-prefixed value', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--agents', '--other']); expect(options.agents).toBeUndefined(); }); it('accepts JSON array for agents', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--agents', '[{"a":1}]']); expect(options.agents).toBe('[{"a":1}]'); }); }); describe('_extractOptions - betas validation', () => { it('accepts valid betas value', () => { const options = handler._extractOptions([ 'glm', '-p', 'test', '--betas', 'feature1,feature2', ]); expect(options.betas).toBe('feature1,feature2'); }); it('rejects dash-prefixed value', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--betas', '--feature']); expect(options.betas).toBeUndefined(); }); }); describe('_extractOptions - extraArgs passthrough', () => { it('passes unknown flags through to extraArgs', () => { const options = handler._extractOptions(['glm', '-p', 'test', '--unknown-flag', 'value']); expect(options.extraArgs).toContain('--unknown-flag'); expect(options.extraArgs).toContain('value'); }); it('excludes CCS-handled flags from extraArgs', () => { const options = handler._extractOptions([ 'glm', '-p', 'test', '--max-turns', '10', '--unknown', 'val', ]); expect(options.extraArgs).not.toContain('--max-turns'); expect(options.extraArgs).not.toContain('10'); expect(options.extraArgs).toContain('--unknown'); }); }); describe('_extractProfile', () => { it('extracts profile name from first non-flag arg', () => { const profile = handler._extractProfile(['glm', '-p', 'test']); expect(profile).toBe('glm'); }); it('returns empty string when no profile found', () => { const profile = handler._extractProfile(['-p', 'test']); expect(profile).toBe(''); }); it('skips flag values correctly', () => { const profile = handler._extractProfile(['-p', 'test', 'kimi']); expect(profile).toBe('kimi'); }); }); });