mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
test(delegation): add comprehensive CLI flag passthrough tests
Add 45 test cases covering all edge cases for CLI flag passthrough: - DelegationHandler: timeout/max-turns/fallback-model/agents/betas validation - HeadlessExecutor: duplicate flag filtering, undefined vs truthy checks
This commit is contained in:
@@ -5,7 +5,36 @@ import { SessionManager } from './session-manager';
|
|||||||
import { ResultFormatter } from './result-formatter';
|
import { ResultFormatter } from './result-formatter';
|
||||||
import { DelegationValidator } from '../utils/delegation-validator';
|
import { DelegationValidator } from '../utils/delegation-validator';
|
||||||
import { SettingsParser } from './settings-parser';
|
import { SettingsParser } from './settings-parser';
|
||||||
import { fail } from '../utils/ui';
|
import { fail, warn } from '../utils/ui';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate a string flag value
|
||||||
|
* @returns value if valid, undefined if invalid/missing
|
||||||
|
*/
|
||||||
|
function parseStringFlag(
|
||||||
|
args: string[],
|
||||||
|
flagName: string,
|
||||||
|
options?: { allowDashPrefix?: boolean }
|
||||||
|
): string | undefined {
|
||||||
|
const index = args.indexOf(flagName);
|
||||||
|
if (index === -1 || index >= args.length - 1) return undefined;
|
||||||
|
|
||||||
|
const value = args[index + 1];
|
||||||
|
|
||||||
|
// Reject dash-prefixed values (likely another flag)
|
||||||
|
if (!options?.allowDashPrefix && value.startsWith('-')) {
|
||||||
|
console.error(warn(`${flagName} value "${value}" looks like a flag. Ignoring.`));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject empty/whitespace-only
|
||||||
|
if (!value.trim()) {
|
||||||
|
console.error(warn(`${flagName} value is empty. Ignoring.`));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
interface ParsedArgs {
|
interface ParsedArgs {
|
||||||
profile: string;
|
profile: string;
|
||||||
@@ -186,38 +215,56 @@ export class DelegationHandler {
|
|||||||
options.permissionMode = args[permModeIndex + 1];
|
options.permissionMode = args[permModeIndex + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse timeout
|
// Parse timeout (validated: positive integer, max 10 minutes)
|
||||||
const timeoutIndex = args.indexOf('--timeout');
|
const timeoutIndex = args.indexOf('--timeout');
|
||||||
if (timeoutIndex !== -1 && timeoutIndex < args.length - 1) {
|
if (timeoutIndex !== -1 && timeoutIndex < args.length - 1) {
|
||||||
options.timeout = parseInt(args[timeoutIndex + 1], 10);
|
const rawVal = args[timeoutIndex + 1];
|
||||||
|
const val = parseInt(rawVal, 10);
|
||||||
|
if (!isNaN(val) && val > 0 && val <= 600000) {
|
||||||
|
options.timeout = val;
|
||||||
|
} else if (isNaN(val)) {
|
||||||
|
console.error(warn(`--timeout "${rawVal}" is not a number. Using default.`));
|
||||||
|
} else if (val <= 0) {
|
||||||
|
console.error(warn(`--timeout ${val} must be positive. Using default.`));
|
||||||
|
} else if (val > 600000) {
|
||||||
|
console.error(warn(`--timeout ${val} exceeds max (600000ms). Using default.`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse --max-turns (limit agentic turns)
|
// Parse --max-turns (limit agentic turns, max 100)
|
||||||
const maxTurnsIndex = args.indexOf('--max-turns');
|
const maxTurnsIndex = args.indexOf('--max-turns');
|
||||||
if (maxTurnsIndex !== -1 && maxTurnsIndex < args.length - 1) {
|
if (maxTurnsIndex !== -1 && maxTurnsIndex < args.length - 1) {
|
||||||
const val = parseInt(args[maxTurnsIndex + 1], 10);
|
const rawVal = args[maxTurnsIndex + 1];
|
||||||
if (!isNaN(val) && val > 0) {
|
const val = parseInt(rawVal, 10);
|
||||||
|
if (!isNaN(val) && val > 0 && val <= 100) {
|
||||||
options.maxTurns = val;
|
options.maxTurns = val;
|
||||||
|
} else if (isNaN(val)) {
|
||||||
|
console.error(warn(`--max-turns "${rawVal}" is not a number. Ignoring.`));
|
||||||
|
} else if (val <= 0) {
|
||||||
|
console.error(warn(`--max-turns ${val} must be positive. Ignoring.`));
|
||||||
|
} else if (val > 100) {
|
||||||
|
console.error(warn(`--max-turns ${val} exceeds max (100). Using 100.`));
|
||||||
|
options.maxTurns = 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse --fallback-model (auto-fallback on overload)
|
// Parse --fallback-model (auto-fallback on overload)
|
||||||
const fallbackIndex = args.indexOf('--fallback-model');
|
options.fallbackModel = parseStringFlag(args, '--fallback-model');
|
||||||
if (fallbackIndex !== -1 && fallbackIndex < args.length - 1) {
|
|
||||||
options.fallbackModel = args[fallbackIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse --agents (dynamic subagent JSON)
|
// Parse --agents (dynamic subagent JSON)
|
||||||
const agentsIndex = args.indexOf('--agents');
|
const agentsValue = parseStringFlag(args, '--agents');
|
||||||
if (agentsIndex !== -1 && agentsIndex < args.length - 1) {
|
if (agentsValue) {
|
||||||
options.agents = args[agentsIndex + 1];
|
// Validate JSON structure
|
||||||
|
try {
|
||||||
|
JSON.parse(agentsValue);
|
||||||
|
options.agents = agentsValue;
|
||||||
|
} catch {
|
||||||
|
console.error(warn('--agents must be valid JSON. Ignoring.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse --betas (experimental features)
|
// Parse --betas (experimental features)
|
||||||
const betasIndex = args.indexOf('--betas');
|
options.betas = parseStringFlag(args, '--betas');
|
||||||
if (betasIndex !== -1 && betasIndex < args.length - 1) {
|
|
||||||
options.betas = args[betasIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect extra args to pass through to Claude CLI
|
// Collect extra args to pass through to Claude CLI
|
||||||
// CCS-handled flags with values (skip these and their values):
|
// CCS-handled flags with values (skip these and their values):
|
||||||
|
|||||||
@@ -121,22 +121,38 @@ export class HeadlessExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Claude Code CLI passthrough flags (explicit, validated)
|
// Claude Code CLI passthrough flags (explicit, validated)
|
||||||
|
// Use undefined checks (not truthy) to allow empty strings if ever valid
|
||||||
if (maxTurns !== undefined && maxTurns > 0) {
|
if (maxTurns !== undefined && maxTurns > 0) {
|
||||||
args.push('--max-turns', String(maxTurns));
|
args.push('--max-turns', String(maxTurns));
|
||||||
}
|
}
|
||||||
if (fallbackModel) {
|
if (fallbackModel !== undefined && fallbackModel) {
|
||||||
args.push('--fallback-model', fallbackModel);
|
args.push('--fallback-model', fallbackModel);
|
||||||
}
|
}
|
||||||
if (agents) {
|
if (agents !== undefined && agents) {
|
||||||
args.push('--agents', agents);
|
args.push('--agents', agents);
|
||||||
}
|
}
|
||||||
if (betas) {
|
if (betas !== undefined && betas) {
|
||||||
args.push('--betas', betas);
|
args.push('--betas', betas);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passthrough extra args (catch-all for new/unknown flags)
|
// Passthrough extra args (catch-all for new/unknown flags)
|
||||||
|
// Filter out duplicates of explicitly handled flags
|
||||||
if (extraArgs.length > 0) {
|
if (extraArgs.length > 0) {
|
||||||
args.push(...extraArgs);
|
const explicitFlags = new Set(['--max-turns', '--fallback-model', '--agents', '--betas']);
|
||||||
|
const filteredExtras: string[] = [];
|
||||||
|
for (let i = 0; i < extraArgs.length; i++) {
|
||||||
|
if (explicitFlags.has(extraArgs[i])) {
|
||||||
|
// Skip this flag and its value (next element)
|
||||||
|
if (i + 1 < extraArgs.length && !extraArgs[i + 1].startsWith('-')) {
|
||||||
|
i++; // Skip value too
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filteredExtras.push(extraArgs[i]);
|
||||||
|
}
|
||||||
|
if (filteredExtras.length > 0) {
|
||||||
|
args.push(...filteredExtras);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CCS_DEBUG) {
|
if (process.env.CCS_DEBUG) {
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
/**
|
||||||
|
* 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<typeof spyOn>;
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* Tests for headless-executor CLI flag passthrough feature
|
||||||
|
* Covers: duplicate flag filtering, undefined checks, flag construction
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect } from 'bun:test';
|
||||||
|
|
||||||
|
describe('HeadlessExecutor flag construction', () => {
|
||||||
|
// Test the flag construction logic by simulating the filtering behavior
|
||||||
|
// Since HeadlessExecutor.execute() spawns a process, we test the logic directly
|
||||||
|
|
||||||
|
describe('Duplicate flag filtering', () => {
|
||||||
|
function filterExtraArgs(
|
||||||
|
extraArgs: string[],
|
||||||
|
explicitFlags: Set<string>
|
||||||
|
): string[] {
|
||||||
|
const filteredExtras: string[] = [];
|
||||||
|
for (let i = 0; i < extraArgs.length; i++) {
|
||||||
|
if (explicitFlags.has(extraArgs[i])) {
|
||||||
|
// Skip this flag and its value (next element)
|
||||||
|
if (i + 1 < extraArgs.length && !extraArgs[i + 1].startsWith('-')) {
|
||||||
|
i++; // Skip value too
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filteredExtras.push(extraArgs[i]);
|
||||||
|
}
|
||||||
|
return filteredExtras;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explicitFlags = new Set(['--max-turns', '--fallback-model', '--agents', '--betas']);
|
||||||
|
|
||||||
|
it('removes duplicate --max-turns from extraArgs', () => {
|
||||||
|
const result = filterExtraArgs(['--max-turns', '10', '--verbose'], explicitFlags);
|
||||||
|
expect(result).not.toContain('--max-turns');
|
||||||
|
expect(result).not.toContain('10');
|
||||||
|
expect(result).toContain('--verbose');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes duplicate --fallback-model from extraArgs', () => {
|
||||||
|
const result = filterExtraArgs(['--fallback-model', 'opus', '--debug'], explicitFlags);
|
||||||
|
expect(result).not.toContain('--fallback-model');
|
||||||
|
expect(result).not.toContain('opus');
|
||||||
|
expect(result).toContain('--debug');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes duplicate --agents from extraArgs', () => {
|
||||||
|
const result = filterExtraArgs(['--agents', '{"x":1}', '--json'], explicitFlags);
|
||||||
|
expect(result).not.toContain('--agents');
|
||||||
|
expect(result).not.toContain('{"x":1}');
|
||||||
|
expect(result).toContain('--json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes duplicate --betas from extraArgs', () => {
|
||||||
|
const result = filterExtraArgs(['--betas', 'feature1', '--output', 'json'], explicitFlags);
|
||||||
|
expect(result).not.toContain('--betas');
|
||||||
|
expect(result).not.toContain('feature1');
|
||||||
|
expect(result).toContain('--output');
|
||||||
|
expect(result).toContain('json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles flag at end of array without value', () => {
|
||||||
|
const result = filterExtraArgs(['--verbose', '--max-turns'], explicitFlags);
|
||||||
|
expect(result).toContain('--verbose');
|
||||||
|
expect(result).not.toContain('--max-turns');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles flag with dash-prefixed next arg (not a value)', () => {
|
||||||
|
const result = filterExtraArgs(['--max-turns', '--verbose'], explicitFlags);
|
||||||
|
// --max-turns is filtered, but --verbose is kept because it starts with -
|
||||||
|
expect(result).not.toContain('--max-turns');
|
||||||
|
expect(result).toContain('--verbose');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves unknown flags', () => {
|
||||||
|
const result = filterExtraArgs(['--custom-flag', 'value', '--another'], explicitFlags);
|
||||||
|
expect(result).toEqual(['--custom-flag', 'value', '--another']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty array', () => {
|
||||||
|
const result = filterExtraArgs([], explicitFlags);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes multiple duplicate flags', () => {
|
||||||
|
const result = filterExtraArgs(
|
||||||
|
['--max-turns', '5', '--agents', '{}', '--fallback-model', 'sonnet'],
|
||||||
|
explicitFlags
|
||||||
|
);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Flag undefined vs truthy checks', () => {
|
||||||
|
// Simulate the flag construction logic
|
||||||
|
function buildArgs(options: {
|
||||||
|
maxTurns?: number;
|
||||||
|
fallbackModel?: string;
|
||||||
|
agents?: string;
|
||||||
|
betas?: string;
|
||||||
|
}): string[] {
|
||||||
|
const args: string[] = [];
|
||||||
|
const { maxTurns, fallbackModel, agents, betas } = options;
|
||||||
|
|
||||||
|
if (maxTurns !== undefined && maxTurns > 0) {
|
||||||
|
args.push('--max-turns', String(maxTurns));
|
||||||
|
}
|
||||||
|
if (fallbackModel !== undefined && fallbackModel) {
|
||||||
|
args.push('--fallback-model', fallbackModel);
|
||||||
|
}
|
||||||
|
if (agents !== undefined && agents) {
|
||||||
|
args.push('--agents', agents);
|
||||||
|
}
|
||||||
|
if (betas !== undefined && betas) {
|
||||||
|
args.push('--betas', betas);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('handles all undefined options', () => {
|
||||||
|
const args = buildArgs({});
|
||||||
|
expect(args).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles maxTurns = 0 (should not add flag)', () => {
|
||||||
|
const args = buildArgs({ maxTurns: 0 });
|
||||||
|
expect(args).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles maxTurns > 0', () => {
|
||||||
|
const args = buildArgs({ maxTurns: 10 });
|
||||||
|
expect(args).toEqual(['--max-turns', '10']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty string fallbackModel (should not add flag)', () => {
|
||||||
|
const args = buildArgs({ fallbackModel: '' });
|
||||||
|
expect(args).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles valid fallbackModel', () => {
|
||||||
|
const args = buildArgs({ fallbackModel: 'sonnet' });
|
||||||
|
expect(args).toEqual(['--fallback-model', 'sonnet']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty string agents (should not add flag)', () => {
|
||||||
|
const args = buildArgs({ agents: '' });
|
||||||
|
expect(args).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles valid JSON agents', () => {
|
||||||
|
const args = buildArgs({ agents: '{"name":"test"}' });
|
||||||
|
expect(args).toEqual(['--agents', '{"name":"test"}']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles all valid options', () => {
|
||||||
|
const args = buildArgs({
|
||||||
|
maxTurns: 5,
|
||||||
|
fallbackModel: 'opus',
|
||||||
|
agents: '{}',
|
||||||
|
betas: 'feature1',
|
||||||
|
});
|
||||||
|
expect(args).toContain('--max-turns');
|
||||||
|
expect(args).toContain('--fallback-model');
|
||||||
|
expect(args).toContain('--agents');
|
||||||
|
expect(args).toContain('--betas');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user