mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 16:17:02 +00:00
feat(browser): add explicit runtime policy controls
This commit is contained in:
1 parent
d6c45fef04
commit
7d02f55f9f
25 files changed
+795
-50
No files matched your search
@@ -1,7 +1,11 @@
|
||||
import { afterEach, describe, expect, test, spyOn } from 'bun:test';
|
||||
import { afterEach, beforeEach, describe, expect, test, spyOn } from 'bun:test';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import * as browserUtils from '../../../src/utils/browser';
|
||||
import { handleBrowserCommand } from '../../../src/commands/browser-command';
|
||||
import { getBrowserConfig } from '../../../src/config/unified-config-loader';
|
||||
|
||||
function stripAnsi(input: string): string {
|
||||
return input.replace(/\u001b\[[0-9;]*m/g, '');
|
||||
@@ -20,7 +24,22 @@ function currentPlatform(): 'darwin' | 'linux' | 'win32' {
|
||||
}
|
||||
|
||||
describe('browser command', () => {
|
||||
let tempHome = '';
|
||||
let originalCcsHome: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = mkdtempSync(join(tmpdir(), 'ccs-browser-command-'));
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
rmSync(tempHome, { recursive: true, force: true });
|
||||
process.exitCode = 0;
|
||||
});
|
||||
|
||||
@@ -28,6 +47,7 @@ describe('browser command', () => {
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
source: 'config',
|
||||
overrideActive: false,
|
||||
state: 'ready',
|
||||
@@ -54,6 +74,7 @@ describe('browser command', () => {
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
state: 'enabled',
|
||||
title: 'Codex Browser Tools are enabled.',
|
||||
detail: 'CCS can inject the managed Playwright MCP overrides.',
|
||||
@@ -75,6 +96,7 @@ describe('browser command', () => {
|
||||
);
|
||||
expect(rendered.includes('Managed MCP: ccs-browser')).toBe(true);
|
||||
expect(rendered.includes('Managed server: ccs_browser')).toBe(true);
|
||||
expect(rendered.includes('Policy: auto')).toBe(true);
|
||||
expect(rendered.includes('DevTools endpoint: http://127.0.0.1:9222')).toBe(true);
|
||||
} finally {
|
||||
statusSpy.mockRestore();
|
||||
@@ -91,6 +113,7 @@ describe('browser command', () => {
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
source: 'CCS_BROWSER_PROFILE_DIR',
|
||||
overrideActive: true,
|
||||
state: 'browser_not_running',
|
||||
@@ -106,6 +129,7 @@ describe('browser command', () => {
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
state: 'unsupported_build',
|
||||
title: 'Codex Browser Tools need a Codex build with --config override support.',
|
||||
detail: 'Detected Codex at /usr/local/bin/codex, but it does not advertise --config overrides.',
|
||||
@@ -121,6 +145,7 @@ describe('browser command', () => {
|
||||
const rendered = await renderLines(['doctor']);
|
||||
|
||||
expect(rendered.includes('Result: action required')).toBe(true);
|
||||
expect(rendered.includes('Default launch behavior: hidden until `--browser`')).toBe(true);
|
||||
expect(rendered.includes('Source: CCS_BROWSER_PROFILE_DIR (env override active)')).toBe(
|
||||
true
|
||||
);
|
||||
@@ -140,6 +165,7 @@ describe('browser command', () => {
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
source: 'config',
|
||||
overrideActive: false,
|
||||
state: 'disabled',
|
||||
@@ -162,6 +188,7 @@ describe('browser command', () => {
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
state: 'unsupported_build',
|
||||
title: 'Codex Browser Tools need a Codex build with --config override support.',
|
||||
detail: 'No Codex binary was detected, so CCS cannot confirm managed browser override support.',
|
||||
@@ -194,6 +221,7 @@ describe('browser command', () => {
|
||||
status: {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
source: 'config',
|
||||
overrideActive: false,
|
||||
state: 'ready',
|
||||
@@ -220,6 +248,7 @@ describe('browser command', () => {
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
state: 'enabled',
|
||||
title: 'Codex Browser Tools are enabled.',
|
||||
detail: 'CCS can inject the managed Playwright MCP overrides.',
|
||||
@@ -255,10 +284,30 @@ describe('browser command', () => {
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
test('policy shows and updates the saved browser exposure mode', async () => {
|
||||
const rendered = await renderLines(['policy', '--all', 'manual']);
|
||||
|
||||
expect(rendered.includes('ccs browser policy')).toBe(true);
|
||||
expect(rendered.includes('Default launch behavior: hidden until `--browser`')).toBe(true);
|
||||
expect(getBrowserConfig().claude.policy).toBe('manual');
|
||||
expect(getBrowserConfig().codex.policy).toBe('manual');
|
||||
});
|
||||
|
||||
test('enable updates a single browser lane', async () => {
|
||||
await renderLines(['disable', 'codex']);
|
||||
expect(getBrowserConfig().codex.enabled).toBe(false);
|
||||
|
||||
const rendered = await renderLines(['enable', 'codex']);
|
||||
expect(rendered.includes('Updated codex browser lane.')).toBe(true);
|
||||
expect(getBrowserConfig().codex.enabled).toBe(true);
|
||||
});
|
||||
|
||||
test('literal browser help still renders the help page', async () => {
|
||||
const rendered = await renderLines(['help']);
|
||||
|
||||
expect(rendered.includes('CCS Browser Help')).toBe(true);
|
||||
expect(rendered.includes('ccs browser setup')).toBe(true);
|
||||
expect(rendered.includes('ccs browser policy')).toBe(true);
|
||||
expect(rendered.includes('--browser')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -142,7 +142,7 @@ describe('completion backend', () => {
|
||||
|
||||
test('suggests browser subcommands and fix/setup flags', () => {
|
||||
expect(suggestionValues(['browser'])).toEqual(
|
||||
expect.arrayContaining(['setup', 'status', 'doctor'])
|
||||
expect.arrayContaining(['setup', 'status', 'doctor', 'policy', 'enable', 'disable'])
|
||||
);
|
||||
expect(suggestionValues(['browser', 'setup'])).toEqual(expect.arrayContaining(['--help', '-h']));
|
||||
expect(suggestionValues(['browser', 'doctor'])).toEqual(
|
||||
|
||||
@@ -81,6 +81,8 @@ describe('help command parity', () => {
|
||||
expect(rendered.includes('ccs browser setup')).toBe(true);
|
||||
expect(rendered.includes('ccs browser status')).toBe(true);
|
||||
expect(rendered.includes('ccs browser doctor')).toBe(true);
|
||||
expect(rendered.includes('ccs browser policy')).toBe(true);
|
||||
expect(rendered.includes('--browser')).toBe(true);
|
||||
});
|
||||
|
||||
test('completion topic documents install and verification paths', async () => {
|
||||
|
||||
@@ -205,11 +205,13 @@ process.exit(0);
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -235,6 +237,114 @@ process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps Codex browser MCP overrides off by default when policy is manual', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tmpHome;
|
||||
|
||||
try {
|
||||
mutateUnifiedConfig((config) => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const result = runCcs(['default', '--target', 'codex', 'fix failing tests'], {
|
||||
...process.env,
|
||||
CI: '1',
|
||||
NO_COLOR: '1',
|
||||
CCS_HOME: tmpHome,
|
||||
CCS_CODEX_PATH: fakeCodexPath,
|
||||
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const calls = readLoggedCodexCalls(codexArgsLogPath);
|
||||
expect(calls).toEqual([['fix failing tests']]);
|
||||
} finally {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('forces Codex browser MCP overrides on for one launch when --browser is passed', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tmpHome;
|
||||
|
||||
try {
|
||||
mutateUnifiedConfig((config) => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const result = runCcs(['default', '--target', 'codex', '--browser', 'fix failing tests'], {
|
||||
...process.env,
|
||||
CI: '1',
|
||||
NO_COLOR: '1',
|
||||
CCS_HOME: tmpHome,
|
||||
CCS_CODEX_PATH: fakeCodexPath,
|
||||
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const calls = readLoggedCodexCalls(codexArgsLogPath);
|
||||
expect(calls[1]).toEqual(
|
||||
expect.arrayContaining([
|
||||
'mcp_servers.ccs_browser.enabled=true',
|
||||
'fix failing tests',
|
||||
])
|
||||
);
|
||||
} finally {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('suppresses Codex browser MCP overrides for one launch when --no-browser is passed', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
const result = runCcs(['default', '--target', 'codex', '--no-browser', 'fix failing tests'], {
|
||||
...process.env,
|
||||
CI: '1',
|
||||
NO_COLOR: '1',
|
||||
CCS_HOME: tmpHome,
|
||||
CCS_CODEX_PATH: fakeCodexPath,
|
||||
CCS_TEST_CODEX_ARGS_OUT: codexArgsLogPath,
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const calls = readLoggedCodexCalls(codexArgsLogPath);
|
||||
expect(calls).toEqual([['fix failing tests']]);
|
||||
});
|
||||
|
||||
it('keeps browser MCP runtime overrides when CCS_THINKING is ignored for native Codex default mode', () => {
|
||||
if (process.platform === 'win32') return;
|
||||
|
||||
|
||||
@@ -270,11 +270,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 43123,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -321,11 +323,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: unreachablePort,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -396,11 +400,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: browserProfileDir,
|
||||
devtools_port: Number.parseInt(port, 10),
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -221,11 +221,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 43123,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -273,11 +275,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: unreachablePort,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -354,11 +358,13 @@ server.listen(0, '127.0.0.1', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: browserProfileDir,
|
||||
devtools_port: Number.parseInt(port, 10),
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -158,16 +158,22 @@ describe('resolveTargetType', () => {
|
||||
|
||||
it('should detect internal entry target for dedicated package bin entrypoints', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
process.argv = ['node', 'ccs'];
|
||||
process.argv = ['node', '/usr/local/lib/ccs/dist/bin/droid-runtime.js'];
|
||||
expect(resolveTargetType([])).toBe('droid');
|
||||
});
|
||||
|
||||
it('should detect internal entry target for codex runtime bins', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'codex';
|
||||
process.argv = ['node', 'ccs'];
|
||||
process.argv = ['node', '/usr/local/lib/ccs/dist/bin/codex-runtime.js'];
|
||||
expect(resolveTargetType([])).toBe('codex');
|
||||
});
|
||||
|
||||
it('should ignore leaked internal entry target env on the main ccs entrypoint', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'codex';
|
||||
process.argv = ['node', 'ccs.js'];
|
||||
expect(resolveTargetType([])).toBe('claude');
|
||||
});
|
||||
|
||||
it('should normalize argv[0] and custom aliases case-insensitively', () => {
|
||||
process.env.CCS_DROID_ALIASES = 'DroidCaps';
|
||||
process.argv = ['node', 'DROIDCAPS'];
|
||||
@@ -242,7 +248,7 @@ describe('resolveTargetType', () => {
|
||||
|
||||
it('should prioritize internal entry target over profile config', () => {
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'droid';
|
||||
process.argv = ['node', 'ccs'];
|
||||
process.argv = ['node', '/usr/local/lib/ccs/dist/bin/droid-runtime.js'];
|
||||
expect(resolveTargetType([], { target: 'claude' })).toBe('droid');
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import {
|
||||
resolveBrowserExposure,
|
||||
resolveBrowserLaunchFlagResolution,
|
||||
} from '../../../../src/utils/browser/browser-policy';
|
||||
|
||||
describe('browser policy', () => {
|
||||
it('strips browser launch flags and records the override', () => {
|
||||
expect(resolveBrowserLaunchFlagResolution(['glm', '--browser', 'check app'])).toEqual({
|
||||
override: 'force-enable',
|
||||
argsWithoutFlags: ['glm', 'check app'],
|
||||
});
|
||||
expect(resolveBrowserLaunchFlagResolution(['glm', '--no-browser', 'check app'])).toEqual({
|
||||
override: 'force-disable',
|
||||
argsWithoutFlags: ['glm', 'check app'],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects conflicting browser launch flags', () => {
|
||||
expect(() => resolveBrowserLaunchFlagResolution(['--browser', '--no-browser'])).toThrow(
|
||||
'Use either `--browser` or `--no-browser`, not both.'
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves browser exposure from saved policy and one-run overrides', () => {
|
||||
expect(resolveBrowserExposure({ enabled: true, policy: 'auto' })).toMatchObject({
|
||||
exposeByDefault: true,
|
||||
exposeForLaunch: true,
|
||||
});
|
||||
expect(resolveBrowserExposure({ enabled: true, policy: 'manual' })).toMatchObject({
|
||||
exposeByDefault: false,
|
||||
exposeForLaunch: false,
|
||||
});
|
||||
expect(
|
||||
resolveBrowserExposure({ enabled: true, policy: 'manual' }, 'force-enable')
|
||||
).toMatchObject({
|
||||
exposeByDefault: false,
|
||||
exposeForLaunch: true,
|
||||
});
|
||||
expect(
|
||||
resolveBrowserExposure({ enabled: true, policy: 'auto' }, 'force-disable')
|
||||
).toMatchObject({
|
||||
exposeByDefault: true,
|
||||
exposeForLaunch: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -98,11 +98,13 @@ describe('browser status', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -138,11 +140,13 @@ describe('browser status', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '/config-browser',
|
||||
devtools_port: 9333,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -185,11 +189,13 @@ describe('browser status', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -227,11 +233,13 @@ describe('browser status', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '/tmp/browser-profile',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -294,11 +302,13 @@ describe('browser status', () => {
|
||||
config.browser = {
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
user_data_dir: '/tmp/config-browser',
|
||||
devtools_port: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -93,11 +93,13 @@ describe('browser routes', () => {
|
||||
expect(payload.config).toMatchObject({
|
||||
claude: {
|
||||
enabled: false,
|
||||
policy: 'auto',
|
||||
userDataDir: join(tempHome, '.ccs', 'browser', 'chrome-user-data'),
|
||||
devtoolsPort: 9222,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
policy: 'auto',
|
||||
},
|
||||
});
|
||||
expect(payload.status.claude).toMatchObject({
|
||||
@@ -117,11 +119,13 @@ describe('browser routes', () => {
|
||||
body: JSON.stringify({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
userDataDir: '/tmp/ccs-browser',
|
||||
devtoolsPort: 9333,
|
||||
},
|
||||
codex: {
|
||||
enabled: false,
|
||||
policy: 'manual',
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -131,11 +135,13 @@ describe('browser routes', () => {
|
||||
expect(payload.browser.config).toMatchObject({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
userDataDir: '/tmp/ccs-browser',
|
||||
devtoolsPort: 9333,
|
||||
},
|
||||
codex: {
|
||||
enabled: false,
|
||||
policy: 'manual',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -143,11 +149,13 @@ describe('browser routes', () => {
|
||||
expect(config.browser).toMatchObject({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
user_data_dir: '/tmp/ccs-browser',
|
||||
devtools_port: 9333,
|
||||
},
|
||||
codex: {
|
||||
enabled: false,
|
||||
policy: 'manual',
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -159,6 +167,7 @@ describe('browser routes', () => {
|
||||
body: JSON.stringify({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
userDataDir: '/tmp/ccs-browser-custom',
|
||||
devtoolsPort: 9333,
|
||||
},
|
||||
@@ -181,6 +190,7 @@ describe('browser routes', () => {
|
||||
const payload = await resetResponse.json();
|
||||
expect(payload.browser.config.claude).toMatchObject({
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
userDataDir: join(tempHome, '.ccs', 'browser', 'chrome-user-data'),
|
||||
devtoolsPort: 9333,
|
||||
});
|
||||
@@ -192,6 +202,7 @@ describe('browser routes', () => {
|
||||
expect(config.browser).toMatchObject({
|
||||
claude: {
|
||||
enabled: true,
|
||||
policy: 'manual',
|
||||
user_data_dir: join(tempHome, '.ccs', 'browser', 'chrome-user-data'),
|
||||
devtools_port: 9333,
|
||||
},
|
||||
@@ -214,4 +225,21 @@ describe('browser routes', () => {
|
||||
error: 'Invalid value for claude.devtoolsPort. Must be an integer between 1 and 65535.',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid browser policy values at the route boundary', async () => {
|
||||
const response = await fetch(`${baseUrl}/api/browser`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
codex: {
|
||||
policy: 'always',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(await response.json()).toEqual({
|
||||
error: 'Invalid value for codex.policy. Must be auto or manual.',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user