mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 02:19:39 +00:00
refactor(cliproxy/executor): extract browser-setup and account-resolution
Phases 04+05 of #1162. Splits two more concerns out of the orchestrator: - src/cliproxy/executor/browser-launch-setup.ts (118 LOC): resolveBrowserLaunchFlags + resolveBrowserRuntime. Encapsulates browser flag resolution, attach config, blocked-override warning, and runtime setup including MCP sync. - src/cliproxy/executor/account-resolution.ts (197 LOC): resolveRuntimeQuotaMonitorProviders, resolveAccounts (--accounts / --use / --nickname / OAuth ban-risk warn / default touch), applyAccountSafetyGuards, touchDefaultAccount. - New tests: 184 + 430 LOC covering both modules. index.ts: 1045 -> 895 LOC (-150). resolveRuntimeQuotaMonitorProviders re-exported from index.ts for __testExports backwards compat. Behavior unchanged; full suite passes 1824/1824. Refs #1162
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Unit tests for browser-launch-setup.ts (Phase 04)
|
||||
*
|
||||
* Tests cover:
|
||||
* - resolveBrowserLaunchFlags: no flags (default), --browser-launch override,
|
||||
* blocked override warning emitted, process.exit on parse error
|
||||
* - resolveBrowserRuntime: no attach (disabled), active runtime env, MCP sync
|
||||
* error propagation
|
||||
*
|
||||
* Strategy: mock the utils/browser and unified-config-loader modules so that
|
||||
* no real browser detection or file I/O occurs.
|
||||
*/
|
||||
|
||||
import { describe, expect, it, jest, beforeEach, afterEach, mock } from 'bun:test';
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Minimal BrowserConfig stub */
|
||||
function makeBrowserConfig(enabled = false, policy: 'auto' | 'always' | 'never' = 'auto'): object {
|
||||
return {
|
||||
claude: { enabled, policy, user_data_dir: '', devtools_port: 9222 },
|
||||
codex: { enabled: false, policy: 'auto' },
|
||||
};
|
||||
}
|
||||
|
||||
// ── resolveBrowserLaunchFlags — no flags ──────────────────────────────────────
|
||||
|
||||
describe('resolveBrowserLaunchFlags — no browser flags', () => {
|
||||
it('returns undefined override and passes args through unchanged', async () => {
|
||||
mock.module('../../utils/browser', () => ({
|
||||
appendBrowserToolArgs: (a: string[]) => a,
|
||||
resolveBrowserLaunchFlagResolution: (_args: string[]) => ({
|
||||
override: undefined,
|
||||
argsWithoutFlags: _args,
|
||||
}),
|
||||
getBlockedBrowserOverrideWarning: () => null,
|
||||
getEffectiveClaudeBrowserAttachConfig: () => ({ enabled: false }),
|
||||
resolveBrowserExposure: () => ({ exposeForLaunch: false }),
|
||||
ensureBrowserMcpOrThrow: () => true,
|
||||
resolveOptionalBrowserAttachRuntime: async () => ({ runtimeEnv: undefined }),
|
||||
syncBrowserMcpToConfigDir: () => true,
|
||||
}));
|
||||
mock.module('../../../config/unified-config-loader', () => ({
|
||||
getBrowserConfig: () => makeBrowserConfig(false),
|
||||
loadOrCreateUnifiedConfig: () => ({}),
|
||||
getThinkingConfig: () => ({}),
|
||||
}));
|
||||
|
||||
const { resolveBrowserLaunchFlags } = await import('../browser-launch-setup');
|
||||
const args = ['--model', 'claude-opus-4-5'];
|
||||
const result = resolveBrowserLaunchFlags(args);
|
||||
expect(result.browserLaunchOverride).toBeUndefined();
|
||||
expect(result.argsWithoutBrowserFlags).toEqual(args);
|
||||
});
|
||||
});
|
||||
|
||||
// ── resolveBrowserLaunchFlags — --browser-launch override ─────────────────────
|
||||
|
||||
describe('resolveBrowserLaunchFlags — with browser-launch override', () => {
|
||||
it('returns override and strips the browser flag from args', async () => {
|
||||
mock.module('../../utils/browser', () => ({
|
||||
appendBrowserToolArgs: (a: string[]) => a,
|
||||
resolveBrowserLaunchFlagResolution: (_args: string[]) => ({
|
||||
override: 'force-enable' as const,
|
||||
argsWithoutFlags: ['--model', 'claude-opus-4-5'],
|
||||
}),
|
||||
getBlockedBrowserOverrideWarning: () => null,
|
||||
getEffectiveClaudeBrowserAttachConfig: () => ({ enabled: true }),
|
||||
resolveBrowserExposure: () => ({ exposeForLaunch: true }),
|
||||
ensureBrowserMcpOrThrow: () => true,
|
||||
resolveOptionalBrowserAttachRuntime: async () => ({ runtimeEnv: undefined }),
|
||||
syncBrowserMcpToConfigDir: () => true,
|
||||
}));
|
||||
mock.module('../../../config/unified-config-loader', () => ({
|
||||
getBrowserConfig: () => makeBrowserConfig(true, 'auto'),
|
||||
loadOrCreateUnifiedConfig: () => ({}),
|
||||
getThinkingConfig: () => ({}),
|
||||
}));
|
||||
|
||||
const { resolveBrowserLaunchFlags } = await import('../browser-launch-setup');
|
||||
const result = resolveBrowserLaunchFlags(['--browser-launch', '--model', 'claude-opus-4-5']);
|
||||
expect(result.browserLaunchOverride).toBe('force-enable');
|
||||
expect(result.argsWithoutBrowserFlags).toEqual(['--model', 'claude-opus-4-5']);
|
||||
});
|
||||
});
|
||||
|
||||
// ── resolveBrowserLaunchFlags — blocked override warning emitted ──────────────
|
||||
|
||||
describe('resolveBrowserLaunchFlags — blocked override warning', () => {
|
||||
let stderrSpy: ReturnType<typeof jest.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
stderrSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stderrSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('emits warn() when getBlockedBrowserOverrideWarning returns a message', async () => {
|
||||
mock.module('../../utils/browser', () => ({
|
||||
appendBrowserToolArgs: (a: string[]) => a,
|
||||
resolveBrowserLaunchFlagResolution: (args: string[]) => ({
|
||||
override: 'force-enable' as const,
|
||||
argsWithoutFlags: args,
|
||||
}),
|
||||
getBlockedBrowserOverrideWarning: () => 'Browser override is blocked by policy',
|
||||
getEffectiveClaudeBrowserAttachConfig: () => ({ enabled: false }),
|
||||
resolveBrowserExposure: () => ({ exposeForLaunch: false }),
|
||||
ensureBrowserMcpOrThrow: () => true,
|
||||
resolveOptionalBrowserAttachRuntime: async () => ({ runtimeEnv: undefined }),
|
||||
syncBrowserMcpToConfigDir: () => true,
|
||||
}));
|
||||
mock.module('../../../config/unified-config-loader', () => ({
|
||||
getBrowserConfig: () => makeBrowserConfig(false, 'never'),
|
||||
loadOrCreateUnifiedConfig: () => ({}),
|
||||
getThinkingConfig: () => ({}),
|
||||
}));
|
||||
|
||||
const { resolveBrowserLaunchFlags } = await import('../browser-launch-setup');
|
||||
resolveBrowserLaunchFlags(['--model', 'claude-opus-4-5']);
|
||||
expect(stderrSpy).toHaveBeenCalledTimes(1);
|
||||
expect(stderrSpy.mock.calls[0][0]).toContain('Browser override is blocked by policy');
|
||||
});
|
||||
});
|
||||
|
||||
// ── resolveBrowserRuntime — attach disabled ───────────────────────────────────
|
||||
|
||||
describe('resolveBrowserRuntime — attach disabled', () => {
|
||||
it('returns undefined browserRuntimeEnv when browser attach is disabled', async () => {
|
||||
mock.module('../../utils/browser', () => ({
|
||||
appendBrowserToolArgs: (a: string[]) => a,
|
||||
resolveBrowserLaunchFlagResolution: (a: string[]) => ({
|
||||
override: undefined,
|
||||
argsWithoutFlags: a,
|
||||
}),
|
||||
getBlockedBrowserOverrideWarning: () => null,
|
||||
getEffectiveClaudeBrowserAttachConfig: () => ({ enabled: false }),
|
||||
resolveBrowserExposure: () => ({ exposeForLaunch: false }),
|
||||
ensureBrowserMcpOrThrow: () => true,
|
||||
resolveOptionalBrowserAttachRuntime: async () => ({ runtimeEnv: undefined }),
|
||||
syncBrowserMcpToConfigDir: () => true,
|
||||
}));
|
||||
mock.module('../../../config/unified-config-loader', () => ({
|
||||
getBrowserConfig: () => makeBrowserConfig(false),
|
||||
loadOrCreateUnifiedConfig: () => ({}),
|
||||
getThinkingConfig: () => ({}),
|
||||
}));
|
||||
|
||||
const { resolveBrowserRuntime } = await import('../browser-launch-setup');
|
||||
const result = await resolveBrowserRuntime(undefined, undefined);
|
||||
expect(result.browserRuntimeEnv).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── resolveBrowserRuntime — active runtime env ────────────────────────────────
|
||||
|
||||
describe('resolveBrowserRuntime — active runtime env', () => {
|
||||
it('returns runtimeEnv when browser attach resolves successfully', async () => {
|
||||
const fakeRuntimeEnv = { CCS_BROWSER_DEVTOOLS_WS_URL: 'ws://127.0.0.1:9222/json' };
|
||||
mock.module('../../utils/browser', () => ({
|
||||
appendBrowserToolArgs: (a: string[]) => a,
|
||||
resolveBrowserLaunchFlagResolution: (a: string[]) => ({
|
||||
override: 'force-enable' as const,
|
||||
argsWithoutFlags: a,
|
||||
}),
|
||||
getBlockedBrowserOverrideWarning: () => null,
|
||||
getEffectiveClaudeBrowserAttachConfig: () => ({ enabled: true }),
|
||||
resolveBrowserExposure: () => ({ exposeForLaunch: true }),
|
||||
ensureBrowserMcpOrThrow: () => true,
|
||||
resolveOptionalBrowserAttachRuntime: async () => ({ runtimeEnv: fakeRuntimeEnv }),
|
||||
syncBrowserMcpToConfigDir: () => true,
|
||||
}));
|
||||
mock.module('../../../config/unified-config-loader', () => ({
|
||||
getBrowserConfig: () => makeBrowserConfig(true, 'always'),
|
||||
loadOrCreateUnifiedConfig: () => ({}),
|
||||
getThinkingConfig: () => ({}),
|
||||
}));
|
||||
|
||||
const { resolveBrowserRuntime } = await import('../browser-launch-setup');
|
||||
const result = await resolveBrowserRuntime('force-enable', undefined);
|
||||
expect(result.browserRuntimeEnv).toEqual(fakeRuntimeEnv);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user