mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 06:20:13 +00:00
feat(cliproxy): add proxy config resolver with CLI flag support
- parseProxyFlags() extracts --proxy-host, --proxy-port, --proxy-protocol - getProxyEnvVars() reads CCS_PROXY_* environment variables - resolveProxyConfig() merges CLI > ENV > config.yaml > defaults - hasProxyFlags() detects proxy-related CLI arguments - add 29 comprehensive unit tests
This commit is contained in:
@@ -0,0 +1,279 @@
|
|||||||
|
/**
|
||||||
|
* Proxy Config Resolver
|
||||||
|
*
|
||||||
|
* Resolves proxy configuration from multiple sources with priority:
|
||||||
|
* CLI flags > Environment variables > config.yaml > defaults
|
||||||
|
*
|
||||||
|
* Supports both local (spawn CLIProxyAPI) and remote (connect to external) modes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ResolvedProxyConfig } from './types';
|
||||||
|
import { CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||||
|
|
||||||
|
/** CLI flags for proxy configuration */
|
||||||
|
export const PROXY_CLI_FLAGS = [
|
||||||
|
'--proxy-host',
|
||||||
|
'--proxy-port',
|
||||||
|
'--proxy-protocol',
|
||||||
|
'--proxy-auth-token',
|
||||||
|
'--local-proxy',
|
||||||
|
'--remote-only',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/** Environment variable names for proxy configuration */
|
||||||
|
export const PROXY_ENV_VARS = {
|
||||||
|
host: 'CCS_PROXY_HOST',
|
||||||
|
port: 'CCS_PROXY_PORT',
|
||||||
|
protocol: 'CCS_PROXY_PROTOCOL',
|
||||||
|
authToken: 'CCS_PROXY_AUTH_TOKEN',
|
||||||
|
fallbackEnabled: 'CCS_PROXY_FALLBACK_ENABLED',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** Parsed CLI proxy flags */
|
||||||
|
interface ParsedProxyFlags {
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
protocol?: 'http' | 'https';
|
||||||
|
authToken?: string;
|
||||||
|
localProxy: boolean;
|
||||||
|
remoteOnly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Proxy config from environment variables */
|
||||||
|
interface EnvProxyConfig {
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
protocol?: 'http' | 'https';
|
||||||
|
authToken?: string;
|
||||||
|
fallbackEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse proxy-related CLI flags from argv.
|
||||||
|
* Returns parsed flags and remaining args (with proxy flags removed).
|
||||||
|
*/
|
||||||
|
export function parseProxyFlags(args: string[]): {
|
||||||
|
flags: ParsedProxyFlags;
|
||||||
|
remainingArgs: string[];
|
||||||
|
} {
|
||||||
|
const flags: ParsedProxyFlags = {
|
||||||
|
localProxy: false,
|
||||||
|
remoteOnly: false,
|
||||||
|
};
|
||||||
|
const remainingArgs: string[] = [];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (i < args.length) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (arg === '--proxy-host' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
||||||
|
flags.host = args[i + 1];
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--proxy-port' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
||||||
|
const port = parseInt(args[i + 1], 10);
|
||||||
|
if (!isNaN(port) && port > 0 && port <= 65535) {
|
||||||
|
flags.port = port;
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--proxy-protocol' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
||||||
|
const proto = args[i + 1].toLowerCase();
|
||||||
|
if (proto === 'http' || proto === 'https') {
|
||||||
|
flags.protocol = proto;
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--proxy-auth-token' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
||||||
|
flags.authToken = args[i + 1];
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--local-proxy') {
|
||||||
|
flags.localProxy = true;
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--remote-only') {
|
||||||
|
flags.remoteOnly = true;
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a proxy flag - keep in remaining args
|
||||||
|
remainingArgs.push(arg);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { flags, remainingArgs };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get proxy configuration from environment variables.
|
||||||
|
*/
|
||||||
|
export function getProxyEnvVars(): EnvProxyConfig {
|
||||||
|
const config: EnvProxyConfig = {};
|
||||||
|
|
||||||
|
const host = process.env[PROXY_ENV_VARS.host];
|
||||||
|
if (host) {
|
||||||
|
config.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = process.env[PROXY_ENV_VARS.port];
|
||||||
|
if (port) {
|
||||||
|
const portNum = parseInt(port, 10);
|
||||||
|
if (!isNaN(portNum) && portNum > 0 && portNum <= 65535) {
|
||||||
|
config.port = portNum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocol = process.env[PROXY_ENV_VARS.protocol];
|
||||||
|
if (protocol) {
|
||||||
|
const proto = protocol.toLowerCase();
|
||||||
|
if (proto === 'http' || proto === 'https') {
|
||||||
|
config.protocol = proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const authToken = process.env[PROXY_ENV_VARS.authToken];
|
||||||
|
if (authToken) {
|
||||||
|
config.authToken = authToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = process.env[PROXY_ENV_VARS.fallbackEnabled];
|
||||||
|
if (fallback !== undefined) {
|
||||||
|
// Accept: '1', 'true', 'yes' as enabled; '0', 'false', 'no' as disabled
|
||||||
|
const lower = fallback.toLowerCase();
|
||||||
|
if (lower === '1' || lower === 'true' || lower === 'yes') {
|
||||||
|
config.fallbackEnabled = true;
|
||||||
|
} else if (lower === '0' || lower === 'false' || lower === 'no') {
|
||||||
|
config.fallbackEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default proxy configuration values.
|
||||||
|
*/
|
||||||
|
const DEFAULT_PROXY_CONFIG: ResolvedProxyConfig = {
|
||||||
|
mode: 'local',
|
||||||
|
port: CLIPROXY_DEFAULT_PORT,
|
||||||
|
protocol: 'http',
|
||||||
|
fallbackEnabled: true,
|
||||||
|
autoStartLocal: true,
|
||||||
|
remoteOnly: false,
|
||||||
|
forceLocal: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve proxy configuration with priority: CLI > ENV > config.yaml > defaults.
|
||||||
|
*
|
||||||
|
* @param cliArgs - Raw CLI arguments
|
||||||
|
* @param configYamlProxy - Proxy section from config.yaml (optional, Phase 1)
|
||||||
|
* @returns Resolved configuration and remaining args (without proxy flags)
|
||||||
|
*/
|
||||||
|
export function resolveProxyConfig(
|
||||||
|
cliArgs: string[],
|
||||||
|
|
||||||
|
_configYamlProxy?: {
|
||||||
|
remote?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
protocol?: 'http' | 'https';
|
||||||
|
auth_token?: string;
|
||||||
|
fallback_enabled?: boolean;
|
||||||
|
};
|
||||||
|
local?: {
|
||||||
|
port?: number;
|
||||||
|
auto_start?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
): { config: ResolvedProxyConfig; remainingArgs: string[] } {
|
||||||
|
// 1. Parse CLI flags (highest priority)
|
||||||
|
const { flags: cliFlags, remainingArgs } = parseProxyFlags(cliArgs);
|
||||||
|
|
||||||
|
// 2. Get environment variables
|
||||||
|
const envConfig = getProxyEnvVars();
|
||||||
|
|
||||||
|
// 3. config.yaml proxy section (passed as parameter - Phase 1 provides this)
|
||||||
|
// For now, we use empty object if not provided; Phase 1 integrates unified config loading
|
||||||
|
const yamlConfig = _configYamlProxy || {};
|
||||||
|
|
||||||
|
// 4. Build resolved config with priority merge
|
||||||
|
const resolved: ResolvedProxyConfig = {
|
||||||
|
...DEFAULT_PROXY_CONFIG,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine mode: remote if host is specified anywhere (unless --local-proxy)
|
||||||
|
const hasRemoteHost =
|
||||||
|
cliFlags.host || envConfig.host || yamlConfig.remote?.host || yamlConfig.remote?.enabled;
|
||||||
|
|
||||||
|
// --local-proxy forces local mode regardless of remote config
|
||||||
|
if (cliFlags.localProxy) {
|
||||||
|
resolved.mode = 'local';
|
||||||
|
resolved.forceLocal = true;
|
||||||
|
} else if (hasRemoteHost) {
|
||||||
|
resolved.mode = 'remote';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge host: CLI > ENV > config.yaml
|
||||||
|
resolved.host = cliFlags.host ?? envConfig.host ?? yamlConfig.remote?.host;
|
||||||
|
|
||||||
|
// Merge port: CLI > ENV > config.yaml (remote or local) > default
|
||||||
|
resolved.port =
|
||||||
|
cliFlags.port ??
|
||||||
|
envConfig.port ??
|
||||||
|
(resolved.mode === 'remote' ? yamlConfig.remote?.port : yamlConfig.local?.port) ??
|
||||||
|
DEFAULT_PROXY_CONFIG.port;
|
||||||
|
|
||||||
|
// Merge protocol: CLI > ENV > config.yaml > default
|
||||||
|
resolved.protocol =
|
||||||
|
cliFlags.protocol ?? envConfig.protocol ?? yamlConfig.remote?.protocol ?? 'http';
|
||||||
|
|
||||||
|
// Merge auth token: CLI > ENV > config.yaml
|
||||||
|
resolved.authToken = cliFlags.authToken ?? envConfig.authToken ?? yamlConfig.remote?.auth_token;
|
||||||
|
|
||||||
|
// Merge fallback enabled: ENV > config.yaml > default
|
||||||
|
resolved.fallbackEnabled =
|
||||||
|
envConfig.fallbackEnabled ?? yamlConfig.remote?.fallback_enabled ?? true;
|
||||||
|
|
||||||
|
// --remote-only from CLI
|
||||||
|
resolved.remoteOnly = cliFlags.remoteOnly;
|
||||||
|
|
||||||
|
// If --remote-only, disable fallback
|
||||||
|
if (resolved.remoteOnly) {
|
||||||
|
resolved.fallbackEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-start local from config.yaml > default
|
||||||
|
resolved.autoStartLocal = yamlConfig.local?.auto_start ?? true;
|
||||||
|
|
||||||
|
return { config: resolved, remainingArgs };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if args contain any proxy flags.
|
||||||
|
* Used for quick filtering before full parse.
|
||||||
|
*/
|
||||||
|
export function hasProxyFlags(args: string[]): boolean {
|
||||||
|
return args.some(
|
||||||
|
(arg) =>
|
||||||
|
arg === '--proxy-host' ||
|
||||||
|
arg === '--proxy-port' ||
|
||||||
|
arg === '--proxy-protocol' ||
|
||||||
|
arg === '--proxy-auth-token' ||
|
||||||
|
arg === '--local-proxy' ||
|
||||||
|
arg === '--remote-only'
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* Unit tests for proxy-config-resolver module
|
||||||
|
*/
|
||||||
|
const { describe, it, expect, beforeEach, afterEach } = require('bun:test');
|
||||||
|
|
||||||
|
// Import from compiled dist
|
||||||
|
const {
|
||||||
|
parseProxyFlags,
|
||||||
|
getProxyEnvVars,
|
||||||
|
resolveProxyConfig,
|
||||||
|
hasProxyFlags,
|
||||||
|
PROXY_CLI_FLAGS,
|
||||||
|
PROXY_ENV_VARS,
|
||||||
|
} = require('../../../dist/cliproxy/proxy-config-resolver');
|
||||||
|
|
||||||
|
describe('proxy-config-resolver', () => {
|
||||||
|
describe('PROXY_CLI_FLAGS', () => {
|
||||||
|
it('should define all expected proxy flags', () => {
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--proxy-host');
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--proxy-port');
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--proxy-protocol');
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--proxy-auth-token');
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--local-proxy');
|
||||||
|
expect(PROXY_CLI_FLAGS).toContain('--remote-only');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PROXY_ENV_VARS', () => {
|
||||||
|
it('should define all expected environment variable names', () => {
|
||||||
|
expect(PROXY_ENV_VARS.host).toBe('CCS_PROXY_HOST');
|
||||||
|
expect(PROXY_ENV_VARS.port).toBe('CCS_PROXY_PORT');
|
||||||
|
expect(PROXY_ENV_VARS.protocol).toBe('CCS_PROXY_PROTOCOL');
|
||||||
|
expect(PROXY_ENV_VARS.authToken).toBe('CCS_PROXY_AUTH_TOKEN');
|
||||||
|
expect(PROXY_ENV_VARS.fallbackEnabled).toBe('CCS_PROXY_FALLBACK_ENABLED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseProxyFlags', () => {
|
||||||
|
it('should parse --proxy-host flag', () => {
|
||||||
|
const { flags, remainingArgs } = parseProxyFlags(['--proxy-host', '192.168.1.100']);
|
||||||
|
expect(flags.host).toBe('192.168.1.100');
|
||||||
|
expect(remainingArgs).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse --proxy-port flag', () => {
|
||||||
|
const { flags, remainingArgs } = parseProxyFlags(['--proxy-port', '9000']);
|
||||||
|
expect(flags.port).toBe(9000);
|
||||||
|
expect(remainingArgs).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse --proxy-protocol flag', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--proxy-protocol', 'https']);
|
||||||
|
expect(flags.protocol).toBe('https');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse --proxy-auth-token flag', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--proxy-auth-token', 'secret123']);
|
||||||
|
expect(flags.authToken).toBe('secret123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse --local-proxy boolean flag', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--local-proxy']);
|
||||||
|
expect(flags.localProxy).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse --remote-only boolean flag', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--remote-only']);
|
||||||
|
expect(flags.remoteOnly).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve non-proxy args in remainingArgs', () => {
|
||||||
|
const { flags, remainingArgs } = parseProxyFlags([
|
||||||
|
'--verbose',
|
||||||
|
'--proxy-host',
|
||||||
|
'localhost',
|
||||||
|
'--some-other-flag',
|
||||||
|
]);
|
||||||
|
expect(flags.host).toBe('localhost');
|
||||||
|
expect(remainingArgs).toEqual(['--verbose', '--some-other-flag']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed proxy and non-proxy args', () => {
|
||||||
|
const { flags, remainingArgs } = parseProxyFlags([
|
||||||
|
'arg1',
|
||||||
|
'--proxy-port',
|
||||||
|
'8080',
|
||||||
|
'arg2',
|
||||||
|
'--local-proxy',
|
||||||
|
'arg3',
|
||||||
|
]);
|
||||||
|
expect(flags.port).toBe(8080);
|
||||||
|
expect(flags.localProxy).toBe(true);
|
||||||
|
expect(remainingArgs).toEqual(['arg1', 'arg2', 'arg3']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore invalid port values', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--proxy-port', 'invalid']);
|
||||||
|
expect(flags.port).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore out-of-range port values', () => {
|
||||||
|
const { flags: flags1 } = parseProxyFlags(['--proxy-port', '0']);
|
||||||
|
expect(flags1.port).toBeUndefined();
|
||||||
|
|
||||||
|
const { flags: flags2 } = parseProxyFlags(['--proxy-port', '70000']);
|
||||||
|
expect(flags2.port).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should normalize protocol to lowercase', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--proxy-protocol', 'HTTPS']);
|
||||||
|
expect(flags.protocol).toBe('https');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore invalid protocol values', () => {
|
||||||
|
const { flags } = parseProxyFlags(['--proxy-protocol', 'ftp']);
|
||||||
|
expect(flags.protocol).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getProxyEnvVars', () => {
|
||||||
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clear proxy env vars
|
||||||
|
delete process.env.CCS_PROXY_HOST;
|
||||||
|
delete process.env.CCS_PROXY_PORT;
|
||||||
|
delete process.env.CCS_PROXY_PROTOCOL;
|
||||||
|
delete process.env.CCS_PROXY_AUTH_TOKEN;
|
||||||
|
delete process.env.CCS_PROXY_FALLBACK_ENABLED;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original env
|
||||||
|
Object.keys(process.env).forEach((key) => {
|
||||||
|
if (key.startsWith('CCS_PROXY_')) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.assign(process.env, originalEnv);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty config when no env vars set', () => {
|
||||||
|
const config = getProxyEnvVars();
|
||||||
|
expect(config.host).toBeUndefined();
|
||||||
|
expect(config.port).toBeUndefined();
|
||||||
|
expect(config.protocol).toBeUndefined();
|
||||||
|
expect(config.authToken).toBeUndefined();
|
||||||
|
expect(config.fallbackEnabled).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read CCS_PROXY_HOST', () => {
|
||||||
|
process.env.CCS_PROXY_HOST = 'remote.example.com';
|
||||||
|
const config = getProxyEnvVars();
|
||||||
|
expect(config.host).toBe('remote.example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read and parse CCS_PROXY_PORT', () => {
|
||||||
|
process.env.CCS_PROXY_PORT = '9000';
|
||||||
|
const config = getProxyEnvVars();
|
||||||
|
expect(config.port).toBe(9000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read CCS_PROXY_PROTOCOL', () => {
|
||||||
|
process.env.CCS_PROXY_PROTOCOL = 'https';
|
||||||
|
const config = getProxyEnvVars();
|
||||||
|
expect(config.protocol).toBe('https');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read CCS_PROXY_AUTH_TOKEN', () => {
|
||||||
|
process.env.CCS_PROXY_AUTH_TOKEN = 'my-secret-token';
|
||||||
|
const config = getProxyEnvVars();
|
||||||
|
expect(config.authToken).toBe('my-secret-token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse CCS_PROXY_FALLBACK_ENABLED as true', () => {
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = '1';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(true);
|
||||||
|
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = 'true';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(true);
|
||||||
|
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = 'yes';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse CCS_PROXY_FALLBACK_ENABLED as false', () => {
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = '0';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(false);
|
||||||
|
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = 'false';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(false);
|
||||||
|
|
||||||
|
process.env.CCS_PROXY_FALLBACK_ENABLED = 'no';
|
||||||
|
expect(getProxyEnvVars().fallbackEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolveProxyConfig', () => {
|
||||||
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.CCS_PROXY_HOST;
|
||||||
|
delete process.env.CCS_PROXY_PORT;
|
||||||
|
delete process.env.CCS_PROXY_PROTOCOL;
|
||||||
|
delete process.env.CCS_PROXY_AUTH_TOKEN;
|
||||||
|
delete process.env.CCS_PROXY_FALLBACK_ENABLED;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Object.keys(process.env).forEach((key) => {
|
||||||
|
if (key.startsWith('CCS_PROXY_')) {
|
||||||
|
delete process.env[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.assign(process.env, originalEnv);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return local mode by default', () => {
|
||||||
|
const { config } = resolveProxyConfig([]);
|
||||||
|
expect(config.mode).toBe('local');
|
||||||
|
expect(config.port).toBe(8317); // Default CLIProxy port
|
||||||
|
expect(config.fallbackEnabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable remote mode when --proxy-host is provided', () => {
|
||||||
|
const { config } = resolveProxyConfig(['--proxy-host', '192.168.1.100']);
|
||||||
|
expect(config.mode).toBe('remote');
|
||||||
|
expect(config.host).toBe('192.168.1.100');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable remote mode when CCS_PROXY_HOST env is set', () => {
|
||||||
|
process.env.CCS_PROXY_HOST = 'remote.example.com';
|
||||||
|
const { config } = resolveProxyConfig([]);
|
||||||
|
expect(config.mode).toBe('remote');
|
||||||
|
expect(config.host).toBe('remote.example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prioritize CLI flags over ENV vars', () => {
|
||||||
|
process.env.CCS_PROXY_HOST = 'env-host';
|
||||||
|
process.env.CCS_PROXY_PORT = '9000';
|
||||||
|
const { config } = resolveProxyConfig(['--proxy-host', 'cli-host', '--proxy-port', '8080']);
|
||||||
|
expect(config.host).toBe('cli-host');
|
||||||
|
expect(config.port).toBe(8080);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should force local mode with --local-proxy', () => {
|
||||||
|
process.env.CCS_PROXY_HOST = 'remote.example.com';
|
||||||
|
const { config } = resolveProxyConfig(['--local-proxy']);
|
||||||
|
expect(config.mode).toBe('local');
|
||||||
|
expect(config.forceLocal).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set remoteOnly and disable fallback with --remote-only', () => {
|
||||||
|
const { config } = resolveProxyConfig(['--proxy-host', 'remote', '--remote-only']);
|
||||||
|
expect(config.remoteOnly).toBe(true);
|
||||||
|
expect(config.fallbackEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasProxyFlags', () => {
|
||||||
|
it('should return true when proxy flags are present', () => {
|
||||||
|
expect(hasProxyFlags(['--proxy-host', 'localhost'])).toBe(true);
|
||||||
|
expect(hasProxyFlags(['--proxy-port', '8080'])).toBe(true);
|
||||||
|
expect(hasProxyFlags(['--local-proxy'])).toBe(true);
|
||||||
|
expect(hasProxyFlags(['--remote-only'])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when no proxy flags are present', () => {
|
||||||
|
expect(hasProxyFlags([])).toBe(false);
|
||||||
|
expect(hasProxyFlags(['--verbose', '--help'])).toBe(false);
|
||||||
|
expect(hasProxyFlags(['gemini', 'some-task'])).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user