style: format source and test files

This commit is contained in:
Tam Nhu Tran
2026-02-17 17:03:11 +07:00
parent f451f4e421
commit 539afea737
34 changed files with 486 additions and 295 deletions
@@ -94,9 +94,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise<void>
* Fix image analysis configuration issues
*/
export async function fixImageAnalysisConfig(): Promise<boolean> {
const { updateUnifiedConfig, loadOrCreateUnifiedConfig } = await import(
'../../config/unified-config-loader'
);
const { updateUnifiedConfig, loadOrCreateUnifiedConfig } =
await import('../../config/unified-config-loader');
const config = loadOrCreateUnifiedConfig();
let fixed = false;
+74 -12
View File
@@ -32,7 +32,8 @@ const MOCK_PORT = 59876; // Use a unique port for mock server
const CLIPROXY_API_KEY = 'test-api-key-12345';
// Default provider models for testing (matches DEFAULT_IMAGE_ANALYSIS_CONFIG)
const DEFAULT_PROVIDER_MODELS = 'agy:gemini-2.5-flash,gemini:gemini-2.5-flash,codex:gpt-5.1-codex-mini,kiro:kiro-claude-haiku-4-5,ghcp:claude-haiku-4.5,claude:claude-haiku-4-5-20251001';
const DEFAULT_PROVIDER_MODELS =
'agy:gemini-2.5-flash,gemini:gemini-2.5-flash,codex:gpt-5.1-codex-mini,kiro:kiro-claude-haiku-4-5,ghcp:claude-haiku-4.5,claude:claude-haiku-4-5-20251001';
const DEFAULT_PROVIDER = 'agy'; // Default test provider
// ============================================================================
@@ -168,13 +169,75 @@ function invokeHook(
function createTestPng(filepath: string): void {
// 1x1 PNG with a red pixel (RGB: 255, 0, 0)
const png = Buffer.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, // PNG signature
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, // IHDR chunk
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, // IDAT chunk (red pixel)
0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf, 0xc0, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x18, 0xdd, 0x8d,
0xb4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, // IEND chunk
0x44, 0xae, 0x42, 0x60, 0x82,
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a, // PNG signature
0x00,
0x00,
0x00,
0x0d,
0x49,
0x48,
0x44,
0x52, // IHDR chunk
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x01,
0x08,
0x02,
0x00,
0x00,
0x00,
0x90,
0x77,
0x53,
0xde,
0x00,
0x00,
0x00,
0x0c,
0x49,
0x44,
0x41, // IDAT chunk (red pixel)
0x54,
0x08,
0xd7,
0x63,
0xf8,
0xcf,
0xc0,
0x00,
0x00,
0x01,
0x01,
0x01,
0x00,
0x18,
0xdd,
0x8d,
0xb4,
0x00,
0x00,
0x00,
0x00,
0x49,
0x45,
0x4e, // IEND chunk
0x44,
0xae,
0x42,
0x60,
0x82,
]);
fs.writeFileSync(filepath, png);
}
@@ -487,15 +550,14 @@ describe('Image Analyzer Hook', () => {
expect(result.code).toBe(2);
const output = JSON.parse(result.stdout);
expect(output.decision).toBe('block');
expect(output.hookSpecificOutput.permissionDecisionReason).toContain(
'CLIProxy unavailable'
);
expect(output.hookSpecificOutput.permissionDecisionReason).toContain('CLIProxy unavailable');
});
it('should analyze PNG via mock CLIProxy and return analysis', () => {
resetMockState();
mockResponse = {
content: 'This image shows a small red square, likely a single pixel or very minimal graphic.',
content:
'This image shows a small red square, likely a single pixel or very minimal graphic.',
statusCode: 200,
};
+10 -6
View File
@@ -100,12 +100,14 @@ describe('ProfileDetector', () => {
const mockUnifiedConfig = {
version: 2,
profiles: {
glm: { settings: settingsPath, type: 'api' }
}
glm: { settings: settingsPath, type: 'api' },
},
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(mockUnifiedConfig as any);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(
mockUnifiedConfig as any
);
try {
const result = detector.detectProfileType('glm');
@@ -122,12 +124,14 @@ describe('ProfileDetector', () => {
const mockUnifiedConfig = {
version: 2,
accounts: {
work: { created: '2025-01-01', last_used: '2025-01-02' }
}
work: { created: '2025-01-01', last_used: '2025-01-02' },
},
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(mockUnifiedConfig as any);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(
mockUnifiedConfig as any
);
try {
const result = detector.detectProfileType('work');
@@ -13,7 +13,11 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { handleQuotaExhaustion, writeQuotaWarning, maskEmail } from '../../../src/cliproxy/account-safety';
import {
handleQuotaExhaustion,
writeQuotaWarning,
maskEmail,
} from '../../../src/cliproxy/account-safety';
// Setup test isolation
let tmpDir: string;
@@ -128,8 +128,7 @@ describe('detectFailedTier', () => {
});
it('should match first tier when multiple models mentioned', () => {
const stderr =
'Tried claude-opus-4-6-thinking, then gemini-3-pro-preview, both failed';
const stderr = 'Tried claude-opus-4-6-thinking, then gemini-3-pro-preview, both failed';
const result = detectFailedTier(stderr, tiers);
expect(result).toBe('opus'); // First match
});
@@ -57,9 +57,7 @@ describe('shouldApplyExtendedContext', () => {
});
it('returns false for Claude models without explicit flag', () => {
expect(shouldApplyExtendedContext('agy', 'claude-opus-4-5-thinking', undefined)).toBe(
false
);
expect(shouldApplyExtendedContext('agy', 'claude-opus-4-5-thinking', undefined)).toBe(false);
});
it('returns false for Claude models without explicit flag', () => {
@@ -427,9 +427,7 @@ describe('management-api-client', () => {
describe('CRUD operations', () => {
it('should get claude keys', async () => {
const client = new ManagementApiClient(config);
const mockKeys: ClaudeKey[] = [
{ 'api-key': 'sk-test-123', prefix: 'glm-' },
];
const mockKeys: ClaudeKey[] = [{ 'api-key': 'sk-test-123', prefix: 'glm-' }];
const originalFetch = global.fetch;
global.fetch = mock(() =>
@@ -449,9 +447,7 @@ describe('management-api-client', () => {
it('should put claude keys', async () => {
const client = new ManagementApiClient(config);
const mockKeys: ClaudeKey[] = [
{ 'api-key': 'sk-test-456', prefix: 'kimi-' },
];
const mockKeys: ClaudeKey[] = [{ 'api-key': 'sk-test-456', prefix: 'kimi-' }];
const originalFetch = global.fetch;
let requestBody: string | undefined;
+1 -4
View File
@@ -12,10 +12,7 @@
*/
import { describe, it, expect } from 'bun:test';
import {
validatePort,
CLIPROXY_DEFAULT_PORT,
} from '../../../src/cliproxy/config-generator';
import { validatePort, CLIPROXY_DEFAULT_PORT } from '../../../src/cliproxy/config-generator';
import { resolveProxyConfig } from '../../../src/cliproxy/proxy-config-resolver';
describe('Port Validation', () => {
@@ -67,9 +67,7 @@ describe('Gemini CLI Quota Fetcher', () => {
});
it('should handle camelCase API response', () => {
const rawBuckets = [
{ modelId: 'gemini-3-flash-preview', remainingFraction: 0.75 },
];
const rawBuckets = [{ modelId: 'gemini-3-flash-preview', remainingFraction: 0.75 }];
const buckets = buildGeminiCliBuckets(rawBuckets);
@@ -11,7 +11,11 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { startQuotaMonitor, stopQuotaMonitor, clearQuotaCache } from '../../../src/cliproxy/quota-manager';
import {
startQuotaMonitor,
stopQuotaMonitor,
clearQuotaCache,
} from '../../../src/cliproxy/quota-manager';
// Setup test isolation
let tmpDir: string;
@@ -2,7 +2,10 @@
* Unit tests for remote-proxy-client module
*/
import { describe, it, expect } from 'bun:test';
import type { RemoteProxyClientConfig, RemoteProxyStatus } from '../../../src/cliproxy/remote-proxy-client';
import type {
RemoteProxyClientConfig,
RemoteProxyStatus,
} from '../../../src/cliproxy/remote-proxy-client';
// We test the module's type exports and error handling logic
// Actual HTTP calls are not mocked in this unit test - use integration tests for that
+1 -4
View File
@@ -535,10 +535,7 @@ describe('sanitizeToolSchemas', () => {
});
test('handles tools without input_schema', () => {
const tools = [
{ name: 'simple_tool', description: 'No schema' },
{ name: 'another_tool' },
];
const tools = [{ name: 'simple_tool', description: 'No schema' }, { name: 'another_tool' }];
const result = sanitizeToolSchemas(tools);
@@ -111,7 +111,10 @@ describe('ToolSanitizationProxy Integration', () => {
body: JSON.stringify({
model: 'test-model',
tools: [
{ name: 'gitmcp__plus-pro-components__plus-pro-components', description: 'Test tool' },
{
name: 'gitmcp__plus-pro-components__plus-pro-components',
description: 'Test tool',
},
{ name: 'valid_tool', description: 'Valid tool' },
],
messages: [{ role: 'user', content: 'test' }],
@@ -465,11 +468,7 @@ describe('ToolSanitizationProxy Integration', () => {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tools: [
{ name: 'tool_a__x__x' },
{ name: 'tool_b__y__y' },
{ name: 'tool_c_valid' },
],
tools: [{ name: 'tool_a__x__x' }, { name: 'tool_b__y__y' }, { name: 'tool_c_valid' }],
}),
});
+10 -14
View File
@@ -77,15 +77,11 @@ describe('env-command', () => {
});
it('formats powershell export', () => {
expect(formatExportLine('powershell', 'API_KEY', 'sk-123')).toBe(
"$env:API_KEY = 'sk-123'"
);
expect(formatExportLine('powershell', 'API_KEY', 'sk-123')).toBe("$env:API_KEY = 'sk-123'");
});
it('escapes single quotes in values', () => {
expect(formatExportLine('bash', 'VAL', "it's here")).toBe(
"export VAL='it'\\''s here'"
);
expect(formatExportLine('bash', 'VAL', "it's here")).toBe("export VAL='it'\\''s here'");
});
it('handles empty values', () => {
@@ -104,9 +100,7 @@ describe('env-command', () => {
});
it('prevents backtick injection in values', () => {
expect(formatExportLine('bash', 'TOKEN', 'safe`whoami`')).toBe(
"export TOKEN='safe`whoami`'"
);
expect(formatExportLine('bash', 'TOKEN', 'safe`whoami`')).toBe("export TOKEN='safe`whoami`'");
});
it('escapes single quotes in fish values', () => {
@@ -114,9 +108,7 @@ describe('env-command', () => {
});
it('escapes single quotes in powershell values', () => {
expect(formatExportLine('powershell', 'VAL', "it's here")).toBe(
"$env:VAL = 'it''s here'"
);
expect(formatExportLine('powershell', 'VAL', "it's here")).toBe("$env:VAL = 'it''s here'");
});
});
@@ -205,11 +197,15 @@ describe('env-command', () => {
});
it('returns undefined when no positional args', () => {
expect(findProfile(['--format', 'openai', '--shell', 'fish'], ['format', 'shell'])).toBeUndefined();
expect(
findProfile(['--format', 'openai', '--shell', 'fish'], ['format', 'shell'])
).toBeUndefined();
});
it('skips multiple flag-value pairs', () => {
expect(findProfile(['--format', 'openai', '--shell', 'fish', 'codex'], ['format', 'shell'])).toBe('codex');
expect(
findProfile(['--format', 'openai', '--shell', 'fish', 'codex'], ['format', 'shell'])
).toBe('codex');
});
});
});
+10 -4
View File
@@ -39,7 +39,8 @@ describe('isFirstTimeInstall logic', () => {
createConfigJson({ profiles: { glm: '~/.ccs/glm.settings.json' } });
const legacyConfig = JSON.parse(fs.readFileSync(path.join(testDir, 'config.json'), 'utf8'));
const hasLegacyProfiles = legacyConfig.profiles && Object.keys(legacyConfig.profiles).length > 0;
const hasLegacyProfiles =
legacyConfig.profiles && Object.keys(legacyConfig.profiles).length > 0;
expect(hasLegacyProfiles).toBe(true);
});
@@ -48,8 +49,11 @@ describe('isFirstTimeInstall logic', () => {
createConfigYaml('version: 2\nprofiles: {}\naccounts: {}');
createProfilesJson({ profiles: { work: { path: '/some/path' } } });
const legacyProfiles = JSON.parse(fs.readFileSync(path.join(testDir, 'profiles.json'), 'utf8'));
const hasLegacyAccounts = legacyProfiles.profiles && Object.keys(legacyProfiles.profiles).length > 0;
const legacyProfiles = JSON.parse(
fs.readFileSync(path.join(testDir, 'profiles.json'), 'utf8')
);
const hasLegacyAccounts =
legacyProfiles.profiles && Object.keys(legacyProfiles.profiles).length > 0;
expect(hasLegacyAccounts).toBe(true);
});
@@ -105,7 +109,9 @@ cliproxy_server:
createProfilesJson({ profiles: {} });
const legacyConfig = JSON.parse(fs.readFileSync(path.join(testDir, 'config.json'), 'utf8'));
const legacyProfiles = JSON.parse(fs.readFileSync(path.join(testDir, 'profiles.json'), 'utf8'));
const legacyProfiles = JSON.parse(
fs.readFileSync(path.join(testDir, 'profiles.json'), 'utf8')
);
const hasLegacyProfiles = Object.keys(legacyConfig.profiles || {}).length > 0;
const hasLegacyAccounts = Object.keys(legacyProfiles.profiles || {}).length > 0;
@@ -135,6 +135,8 @@ describe('shell-completion command', () => {
await expect(handleShellCompletionCommand([])).rejects.toThrow('process.exit(1)');
const plainErrorLines = errorLines.map(stripAnsi);
expect(plainErrorLines.some((line) => line.includes('Error: boom'))).toBe(true);
expect(plainErrorLines.some((line) => line.includes('ccs --shell-completion --zsh'))).toBe(true);
expect(plainErrorLines.some((line) => line.includes('ccs --shell-completion --zsh'))).toBe(
true
);
});
});
+25 -29
View File
@@ -141,39 +141,35 @@ describe('startDaemon', () => {
expect(result.error).toContain('Invalid port');
});
it(
'starts and stops daemon successfully',
async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
expect(result.pid).toBeDefined();
it('starts and stops daemon successfully', async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
expect(result.pid).toBeDefined();
// Verify health
const running = await isDaemonRunning(port);
expect(running).toBe(true);
// Verify health
const running = await isDaemonRunning(port);
expect(running).toBe(true);
// Verify chat endpoint exists (requires auth, should not be 404)
const chatResponse = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'hello' }],
}),
});
expect(chatResponse.status).toBe(401);
// Verify chat endpoint exists (requires auth, should not be 404)
const chatResponse = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'hello' }],
}),
});
expect(chatResponse.status).toBe(401);
// Stop
const stopResult = await stopDaemon();
expect(stopResult.success).toBe(true);
// Stop
const stopResult = await stopDaemon();
expect(stopResult.success).toBe(true);
// Verify stopped
const stillRunning = await isDaemonRunning(port);
expect(stillRunning).toBe(false);
},
35000
);
// Verify stopped
const stillRunning = await isDaemonRunning(port);
expect(stillRunning).toBe(false);
}, 35000);
});
describe('isDaemonRunning', () => {
+16 -4
View File
@@ -300,7 +300,9 @@ describe('Message Translation', () => {
expect(result.messages).toHaveLength(1);
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content).toBe('[System Instructions]\nSystem instruction part 1 part 2');
expect(result.messages[0].content).toBe(
'[System Instructions]\nSystem instruction part 1 part 2'
);
});
});
});
@@ -332,7 +334,12 @@ describe('Request Encoding', () => {
},
];
const result = generateCursorBody([{ role: 'user', content: 'What is the weather?' }], 'gpt-4', tools, null);
const result = generateCursorBody(
[{ role: 'user', content: 'What is the weather?' }],
'gpt-4',
tools,
null
);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBeGreaterThan(0);
@@ -358,7 +365,9 @@ describe('Request Encoding', () => {
const executor = new CursorExecutor();
// Frame header says payload is 100 bytes but only 5 bytes follow
const truncatedFrame = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x02, 0x03, 0x04, 0x05]);
const truncatedFrame = Buffer.from([
0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x02, 0x03, 0x04, 0x05,
]);
const result = executor.transformProtobufToJSON(truncatedFrame, 'gpt-4', {
messages: [],
@@ -644,7 +653,10 @@ describe('CursorExecutor', () => {
];
// buildCursorRequest expects (model, body, stream, credentials)
buildCursorRequest('test-model', { messages }, false, { machineId: '12345', accessToken: 'test' });
buildCursorRequest('test-model', { messages }, false, {
machineId: '12345',
accessToken: 'test',
});
// Should have logged warning
const hasWarning = consoleSpy.some((log) => log.includes('Unknown message role'));
+2 -6
View File
@@ -14,9 +14,7 @@ import { type RawUsageEntry } from '../../src/web-server/jsonl-parser';
// TEST FIXTURES
// ============================================================================
const createEntry = (
overrides: Partial<RawUsageEntry> = {}
): RawUsageEntry => ({
const createEntry = (overrides: Partial<RawUsageEntry> = {}): RawUsageEntry => ({
inputTokens: 1000,
outputTokens: 500,
cacheCreationTokens: 100,
@@ -66,9 +64,7 @@ describe('aggregateDailyUsage', () => {
expect(result[0].modelsUsed).toContain('claude-opus-4-5-20251101');
// Find sonnet breakdown
const sonnet = result[0].modelBreakdowns.find(
(b) => b.modelName === 'claude-sonnet-4-5'
);
const sonnet = result[0].modelBreakdowns.find((b) => b.modelName === 'claude-sonnet-4-5');
expect(sonnet!.inputTokens).toBe(1500); // 1000 + 500
});
@@ -124,13 +124,7 @@ describe('DelegationHandler', () => {
describe('_extractOptions - agents JSON validation', () => {
it('accepts valid JSON for agents', () => {
const options = handler._extractOptions([
'glm',
'-p',
'test',
'--agents',
'{"name":"test"}',
]);
const options = handler._extractOptions(['glm', '-p', 'test', '--agents', '{"name":"test"}']);
expect(options.agents).toBe('{"name":"test"}');
});
@@ -153,7 +147,13 @@ describe('DelegationHandler', () => {
describe('_extractOptions - betas validation', () => {
it('accepts valid betas value', () => {
const options = handler._extractOptions(['glm', '-p', 'test', '--betas', 'feature1,feature2']);
const options = handler._extractOptions([
'glm',
'-p',
'test',
'--betas',
'feature1,feature2',
]);
expect(options.betas).toBe('feature1,feature2');
});
@@ -165,13 +165,7 @@ describe('DelegationHandler', () => {
describe('_extractOptions - extraArgs passthrough', () => {
it('passes unknown flags through to extraArgs', () => {
const options = handler._extractOptions([
'glm',
'-p',
'test',
'--unknown-flag',
'value',
]);
const options = handler._extractOptions(['glm', '-p', 'test', '--unknown-flag', 'value']);
expect(options.extraArgs).toContain('--unknown-flag');
expect(options.extraArgs).toContain('value');
});
@@ -10,10 +10,7 @@ describe('HeadlessExecutor flag construction', () => {
// Since HeadlessExecutor.execute() spawns a process, we test the logic directly
describe('Duplicate flag filtering', () => {
function filterExtraArgs(
extraArgs: string[],
explicitFlags: Set<string>
): string[] {
function filterExtraArgs(extraArgs: string[], explicitFlags: Set<string>): string[] {
const filteredExtras: string[] = [];
for (let i = 0; i < extraArgs.length; i++) {
if (explicitFlags.has(extraArgs[i])) {
+116 -46
View File
@@ -30,11 +30,13 @@ afterEach(() => {
});
// Helper to create proxy instance with specific config
async function createTestableProxy(config: {
maxRetries?: number;
baseDelay?: number;
enabled?: boolean;
} = {}) {
async function createTestableProxy(
config: {
maxRetries?: number;
baseDelay?: number;
enabled?: boolean;
} = {}
) {
// Set env vars before import
if (config.maxRetries !== undefined) {
process.env.GLMT_MAX_RETRIES = String(config.maxRetries);
@@ -56,7 +58,11 @@ describe('GLMT Retry Logic', () => {
it('should use default values when env vars not set', async () => {
const proxy = await createTestableProxy();
// Access private via type assertion for testing
const config = (proxy as unknown as { retryConfig: { maxRetries: number; baseDelay: number; enabled: boolean } }).retryConfig;
const config = (
proxy as unknown as {
retryConfig: { maxRetries: number; baseDelay: number; enabled: boolean };
}
).retryConfig;
expect(config.maxRetries).toBe(3);
expect(config.baseDelay).toBe(1000);
expect(config.enabled).toBe(true);
@@ -84,7 +90,11 @@ describe('GLMT Retry Logic', () => {
describe('calculateRetryDelay', () => {
it('should calculate exponential delay with jitter', async () => {
const proxy = await createTestableProxy({ baseDelay: 1000 });
const calcDelay = (proxy as unknown as { calculateRetryDelay: (attempt: number, retryAfter?: string) => number }).calculateRetryDelay.bind(proxy);
const calcDelay = (
proxy as unknown as {
calculateRetryDelay: (attempt: number, retryAfter?: string) => number;
}
).calculateRetryDelay.bind(proxy);
// Attempt 0: 2^0 * 1000 = 1000 + jitter (0-500)
const delay0 = calcDelay(0);
@@ -104,7 +114,11 @@ describe('GLMT Retry Logic', () => {
it('should honor Retry-After header in seconds', async () => {
const proxy = await createTestableProxy();
const calcDelay = (proxy as unknown as { calculateRetryDelay: (attempt: number, retryAfter?: string) => number }).calculateRetryDelay.bind(proxy);
const calcDelay = (
proxy as unknown as {
calculateRetryDelay: (attempt: number, retryAfter?: string) => number;
}
).calculateRetryDelay.bind(proxy);
// Retry-After: 5 seconds → 5000ms
const delay = calcDelay(0, '5');
@@ -113,7 +127,11 @@ describe('GLMT Retry Logic', () => {
it('should ignore invalid Retry-After header and fallback to exponential', async () => {
const proxy = await createTestableProxy({ baseDelay: 1000 });
const calcDelay = (proxy as unknown as { calculateRetryDelay: (attempt: number, retryAfter?: string) => number }).calculateRetryDelay.bind(proxy);
const calcDelay = (
proxy as unknown as {
calculateRetryDelay: (attempt: number, retryAfter?: string) => number;
}
).calculateRetryDelay.bind(proxy);
// Invalid header falls back to exponential
const delay = calcDelay(0, 'invalid');
@@ -123,7 +141,11 @@ describe('GLMT Retry Logic', () => {
it('should ignore zero or negative Retry-After', async () => {
const proxy = await createTestableProxy({ baseDelay: 1000 });
const calcDelay = (proxy as unknown as { calculateRetryDelay: (attempt: number, retryAfter?: string) => number }).calculateRetryDelay.bind(proxy);
const calcDelay = (
proxy as unknown as {
calculateRetryDelay: (attempt: number, retryAfter?: string) => number;
}
).calculateRetryDelay.bind(proxy);
const delay = calcDelay(0, '0');
expect(delay).toBeGreaterThanOrEqual(1000);
@@ -134,7 +156,11 @@ describe('GLMT Retry Logic', () => {
describe('isRetryableError', () => {
it('should return true for 429 status code', async () => {
const proxy = await createTestableProxy();
const isRetryable = (proxy as unknown as { isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string } }).isRetryableError.bind(proxy);
const isRetryable = (
proxy as unknown as {
isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string };
}
).isRetryableError.bind(proxy);
const result = isRetryable(new Error('Upstream error: 429 Too Many Requests'));
expect(result.retryable).toBe(true);
@@ -142,7 +168,11 @@ describe('GLMT Retry Logic', () => {
it('should return true for rate limit message', async () => {
const proxy = await createTestableProxy();
const isRetryable = (proxy as unknown as { isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string } }).isRetryableError.bind(proxy);
const isRetryable = (
proxy as unknown as {
isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string };
}
).isRetryableError.bind(proxy);
const result = isRetryable(new Error('Rate limit exceeded'));
expect(result.retryable).toBe(true);
@@ -150,7 +180,11 @@ describe('GLMT Retry Logic', () => {
it('should return false for non-retryable errors', async () => {
const proxy = await createTestableProxy();
const isRetryable = (proxy as unknown as { isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string } }).isRetryableError.bind(proxy);
const isRetryable = (
proxy as unknown as {
isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string };
}
).isRetryableError.bind(proxy);
expect(isRetryable(new Error('Connection refused')).retryable).toBe(false);
expect(isRetryable(new Error('Timeout')).retryable).toBe(false);
@@ -160,7 +194,11 @@ describe('GLMT Retry Logic', () => {
it('should extract Retry-After from error message', async () => {
const proxy = await createTestableProxy();
const isRetryable = (proxy as unknown as { isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string } }).isRetryableError.bind(proxy);
const isRetryable = (
proxy as unknown as {
isRetryableError: (error: Error) => { retryable: boolean; retryAfter?: string };
}
).isRetryableError.bind(proxy);
const result = isRetryable(new Error('429 Too Many Requests, Retry-After: 10'));
expect(result.retryable).toBe(true);
@@ -174,47 +212,66 @@ describe('GLMT Retry Logic', () => {
let attempts = 0;
// Mock forwardToUpstream
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream = async () => {
attempts++;
return { choices: [{ message: { content: 'success' } }] };
};
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream =
async () => {
attempts++;
return { choices: [{ message: { content: 'success' } }] };
};
const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown> }).forwardWithRetry.bind(proxy);
const forwardWithRetry = (
proxy as unknown as {
forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown>;
}
).forwardWithRetry.bind(proxy);
const result = await forwardWithRetry({}, {});
expect(attempts).toBe(1);
expect((result as { choices: Array<{ message: { content: string } }> }).choices[0].message.content).toBe('success');
expect(
(result as { choices: Array<{ message: { content: string } }> }).choices[0].message.content
).toBe('success');
});
it('should retry on 429 and succeed eventually', async () => {
const proxy = await createTestableProxy({ baseDelay: 10 }); // Fast for tests
let attempts = 0;
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream = async () => {
attempts++;
if (attempts < 3) {
throw new Error('Upstream error: 429 Too Many Requests');
}
return { choices: [{ message: { content: 'success after retry' } }] };
};
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream =
async () => {
attempts++;
if (attempts < 3) {
throw new Error('Upstream error: 429 Too Many Requests');
}
return { choices: [{ message: { content: 'success after retry' } }] };
};
const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown> }).forwardWithRetry.bind(proxy);
const forwardWithRetry = (
proxy as unknown as {
forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown>;
}
).forwardWithRetry.bind(proxy);
const result = await forwardWithRetry({}, {});
expect(attempts).toBe(3);
expect((result as { choices: Array<{ message: { content: string } }> }).choices[0].message.content).toBe('success after retry');
expect(
(result as { choices: Array<{ message: { content: string } }> }).choices[0].message.content
).toBe('success after retry');
});
it('should fail after max retries exhausted', async () => {
const proxy = await createTestableProxy({ maxRetries: 2, baseDelay: 10 });
let attempts = 0;
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream = async () => {
attempts++;
throw new Error('Upstream error: 429 Too Many Requests');
};
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream =
async () => {
attempts++;
throw new Error('Upstream error: 429 Too Many Requests');
};
const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown> }).forwardWithRetry.bind(proxy);
const forwardWithRetry = (
proxy as unknown as {
forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown>;
}
).forwardWithRetry.bind(proxy);
await expect(forwardWithRetry({}, {})).rejects.toThrow('429');
expect(attempts).toBe(3); // Initial + 2 retries
@@ -224,12 +281,17 @@ describe('GLMT Retry Logic', () => {
const proxy = await createTestableProxy({ enabled: false });
let attempts = 0;
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream = async () => {
attempts++;
throw new Error('Upstream error: 429 Too Many Requests');
};
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream =
async () => {
attempts++;
throw new Error('Upstream error: 429 Too Many Requests');
};
const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown> }).forwardWithRetry.bind(proxy);
const forwardWithRetry = (
proxy as unknown as {
forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown>;
}
).forwardWithRetry.bind(proxy);
await expect(forwardWithRetry({}, {})).rejects.toThrow('429');
expect(attempts).toBe(1);
@@ -239,12 +301,17 @@ describe('GLMT Retry Logic', () => {
const proxy = await createTestableProxy({ baseDelay: 10 });
let attempts = 0;
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream = async () => {
attempts++;
throw new Error('Upstream error: 500 Internal Server Error');
};
(proxy as unknown as { forwardToUpstream: () => Promise<unknown> }).forwardToUpstream =
async () => {
attempts++;
throw new Error('Upstream error: 500 Internal Server Error');
};
const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown> }).forwardWithRetry.bind(proxy);
const forwardWithRetry = (
proxy as unknown as {
forwardWithRetry: (req: unknown, headers: unknown) => Promise<unknown>;
}
).forwardWithRetry.bind(proxy);
await expect(forwardWithRetry({}, {})).rejects.toThrow('500');
expect(attempts).toBe(1);
@@ -254,7 +321,8 @@ describe('GLMT Retry Logic', () => {
describe('connection pooling', () => {
it('should create https.Agent with keepAlive enabled', async () => {
const proxy = await createTestableProxy();
const agent = (proxy as unknown as { httpsAgent: { options?: { keepAlive?: boolean } } }).httpsAgent;
const agent = (proxy as unknown as { httpsAgent: { options?: { keepAlive?: boolean } } })
.httpsAgent;
expect(agent).toBeDefined();
// Agent should have keepAlive behavior (internal property)
@@ -263,7 +331,9 @@ describe('GLMT Retry Logic', () => {
it('should destroy agent on stop', async () => {
const proxy = await createTestableProxy();
const agent = (proxy as unknown as { httpsAgent: { destroy: () => void; destroyed?: boolean } }).httpsAgent;
const agent = (
proxy as unknown as { httpsAgent: { destroy: () => void; destroyed?: boolean } }
).httpsAgent;
let destroyed = false;
const originalDestroy = agent.destroy.bind(agent);
+1 -8
View File
@@ -219,14 +219,7 @@ describe('parseJsonlFile', () => {
test('handles file with blank lines', async () => {
const filePath = path.join(tempDir, 'blanks.jsonl');
const content = [
'',
VALID_ASSISTANT_ENTRY,
'',
' ',
ASSISTANT_ENTRY_NO_CACHE,
'',
].join('\n');
const content = ['', VALID_ASSISTANT_ENTRY, '', ' ', ASSISTANT_ENTRY_NO_CACHE, ''].join('\n');
fs.writeFileSync(filePath, content);
+1 -1
View File
@@ -92,7 +92,7 @@ describe('mcp-manager logic', () => {
it('should not detect unrelated MCPs', () => {
expect(detectWebSearchMcp({ 'my-custom-mcp': {} })).toBe(false);
expect(detectWebSearchMcp({ 'filesystem': {} })).toBe(false);
expect(detectWebSearchMcp({ filesystem: {} })).toBe(false);
expect(detectWebSearchMcp({ 'github-copilot': {} })).toBe(false);
});
+2 -1
View File
@@ -63,7 +63,8 @@ describe('SharedManager', () => {
'claude-hud@claude-hud': [
{
scope: 'user',
installPath: '/home/kai/.ccs/instances/ck/plugins/cache/claude-hud/claude-hud/0.0.2',
installPath:
'/home/kai/.ccs/instances/ck/plugins/cache/claude-hud/claude-hud/0.0.2',
version: '0.0.2',
},
],
+95 -43
View File
@@ -101,9 +101,7 @@ describe('droid-config-manager', () => {
provider: 'anthropic',
});
const settings = JSON.parse(
fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8')
);
const settings = JSON.parse(fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8'));
expect(settings.customModels).toHaveLength(2);
expect(settings.customModels[0].displayName).toBe('My GPT');
expect(settings.customModels[1].displayName).toBe('CCS gemini');
@@ -135,9 +133,7 @@ describe('droid-config-manager', () => {
provider: 'anthropic',
});
const settings = JSON.parse(
fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8')
);
const settings = JSON.parse(fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8'));
expect(settings.customModels).toHaveLength(2);
expect(settings.customModels[0].provider).toBe('custom-provider');
expect(settings.customModels[1].displayName).toBe('CCS gemini');
@@ -162,7 +158,10 @@ describe('droid-config-manager', () => {
it('should reject symlinked temp file path', async () => {
const factoryDir = path.join(tmpDir, '.factory');
fs.mkdirSync(factoryDir, { recursive: true });
fs.writeFileSync(path.join(factoryDir, 'settings.json'), JSON.stringify({ customModels: [] }));
fs.writeFileSync(
path.join(factoryDir, 'settings.json'),
JSON.stringify({ customModels: [] })
);
fs.symlinkSync('/tmp', path.join(factoryDir, 'settings.json.tmp'));
await expect(
@@ -202,9 +201,7 @@ describe('droid-config-manager', () => {
provider: 'anthropic',
});
const settings = JSON.parse(
fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8')
);
const settings = JSON.parse(fs.readFileSync(path.join(factoryDir, 'settings.json'), 'utf8'));
expect(settings.customModels).toHaveLength(1);
expect(settings.customModels[0].displayName).toBe('CCS gemini');
expect(settings.customModels[0].apiKey).toBe('new-key');
@@ -255,8 +252,20 @@ describe('droid-config-manager', () => {
path.join(factoryDir, 'settings.json'),
JSON.stringify({
customModels: [
{ model: 'gpt-4o', displayName: 'My GPT', baseUrl: 'x', apiKey: 'y', provider: 'openai' },
{ model: 'opus', displayName: 'CCS gemini', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{
model: 'gpt-4o',
displayName: 'My GPT',
baseUrl: 'x',
apiKey: 'y',
provider: 'openai',
},
{
model: 'opus',
displayName: 'CCS gemini',
baseUrl: 'x',
apiKey: 'y',
provider: 'anthropic',
},
],
})
);
@@ -275,8 +284,20 @@ describe('droid-config-manager', () => {
path.join(factoryDir, 'settings.json'),
JSON.stringify({
customModels: [
{ model: 'opus', displayName: 'ccs-gemini', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{ model: 'gpt-4o', displayName: 'My GPT', baseUrl: 'x', apiKey: 'y', provider: 'openai' },
{
model: 'opus',
displayName: 'ccs-gemini',
baseUrl: 'x',
apiKey: 'y',
provider: 'anthropic',
},
{
model: 'gpt-4o',
displayName: 'My GPT',
baseUrl: 'x',
apiKey: 'y',
provider: 'openai',
},
],
})
);
@@ -351,7 +372,12 @@ describe('droid-config-manager', () => {
fs.writeFileSync(
path.join(factoryDir, 'settings.json'),
JSON.stringify({
customModels: [null, 123, 'bad', { displayName: 'CCS ok', model: 'x', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' }],
customModels: [
null,
123,
'bad',
{ displayName: 'CCS ok', model: 'x', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
],
})
);
@@ -458,8 +484,20 @@ describe('droid-config-manager', () => {
path.join(factoryDir, 'settings.json'),
JSON.stringify({
customModels: [
{ model: 'gpt-4o', displayName: 'My GPT', baseUrl: 'x', apiKey: 'y', provider: 'openai' },
{ model: 'opus', displayName: 'CCS old-profile', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{
model: 'gpt-4o',
displayName: 'My GPT',
baseUrl: 'x',
apiKey: 'y',
provider: 'openai',
},
{
model: 'opus',
displayName: 'CCS old-profile',
baseUrl: 'x',
apiKey: 'y',
provider: 'anthropic',
},
],
})
);
@@ -479,8 +517,20 @@ describe('droid-config-manager', () => {
path.join(factoryDir, 'settings.json'),
JSON.stringify({
customModels: [
{ model: 'opus', displayName: 'ccs-gemini', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{ model: 'sonnet', displayName: 'ccs-codex', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{
model: 'opus',
displayName: 'ccs-gemini',
baseUrl: 'x',
apiKey: 'y',
provider: 'anthropic',
},
{
model: 'sonnet',
displayName: 'ccs-codex',
baseUrl: 'x',
apiKey: 'y',
provider: 'anthropic',
},
],
})
);
@@ -502,7 +552,13 @@ describe('droid-config-manager', () => {
customModels: [
{ model: 'x', displayName: 'CCS ', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{ model: 'x', displayName: 'ccs-', baseUrl: 'x', apiKey: 'y', provider: 'anthropic' },
{ model: 'gpt-4o', displayName: 'My GPT', baseUrl: 'x', apiKey: 'y', provider: 'openai' },
{
model: 'gpt-4o',
displayName: 'My GPT',
baseUrl: 'x',
apiKey: 'y',
provider: 'openai',
},
],
})
);
@@ -517,31 +573,27 @@ describe('droid-config-manager', () => {
});
describe('concurrent writes', () => {
it(
'should handle concurrent upserts without data loss',
async () => {
const profiles = Array.from({ length: 10 }, (_, i) => `profile-${i}`);
it('should handle concurrent upserts without data loss', async () => {
const profiles = Array.from({ length: 10 }, (_, i) => `profile-${i}`);
await Promise.all(
profiles.map((p) =>
upsertCcsModel(p, {
model: 'test-model',
displayName: `CCS ${p}`,
baseUrl: 'http://localhost:8317',
apiKey: 'key',
provider: 'anthropic',
})
)
);
await Promise.all(
profiles.map((p) =>
upsertCcsModel(p, {
model: 'test-model',
displayName: `CCS ${p}`,
baseUrl: 'http://localhost:8317',
apiKey: 'key',
provider: 'anthropic',
})
)
);
const models = await listCcsModels();
expect(models.size).toBe(10);
const models = await listCcsModels();
expect(models.size).toBe(10);
for (const p of profiles) {
expect(models.has(p)).toBe(true);
}
},
15000
);
for (const p of profiles) {
expect(models.has(p)).toBe(true);
}
}, 15000);
});
});
+3 -3
View File
@@ -129,9 +129,9 @@ describe('stripTargetFlag', () => {
});
it('should remove repeated --target flags', () => {
expect(stripTargetFlag(['--target', 'droid', 'gemini', '--target=claude', '--verbose'])).toEqual(
['gemini', '--verbose']
);
expect(
stripTargetFlag(['--target', 'droid', 'gemini', '--target=claude', '--verbose'])
).toEqual(['gemini', '--verbose']);
});
it('should throw when --target has no value', () => {
+10 -1
View File
@@ -19,7 +19,16 @@ import { isUnifiedConfigEnabled } from '../../src/config/feature-flags';
// Inline helper to test secret key detection (utility kept for potential reuse)
function isSecretKey(key: string): boolean {
const upper = key.toUpperCase();
const secretPatterns = ['TOKEN', 'SECRET', 'API_KEY', 'APIKEY', 'PASSWORD', 'CREDENTIAL', 'AUTH', 'PRIVATE'];
const secretPatterns = [
'TOKEN',
'SECRET',
'API_KEY',
'APIKEY',
'PASSWORD',
'CREDENTIAL',
'AUTH',
'PRIVATE',
];
return secretPatterns.some((pattern) => upper.includes(pattern));
}
+47 -45
View File
@@ -1,89 +1,91 @@
import { expect, test, describe, beforeEach, afterEach } from "bun:test";
import * as path from "path";
import * as os from "os";
import { expandPath } from "../../../src/utils/helpers";
import { expect, test, describe, beforeEach, afterEach } from 'bun:test';
import * as path from 'path';
import * as os from 'os';
import { expandPath } from '../../../src/utils/helpers';
describe("expandPath", () => {
describe('expandPath', () => {
const originalEnv = { ...process.env };
const HOME = os.homedir();
beforeEach(() => {
process.env.TEST_HOME = "/custom/home";
process.env.TEST_VAR = "foo";
process.env.TEST_HOME = '/custom/home';
process.env.TEST_VAR = 'foo';
});
afterEach(() => {
process.env = { ...originalEnv };
});
test("1. Tilde expansion: ~/path -> /home/user/path", () => {
expect(expandPath("~/test/file.txt")).toBe(path.join(HOME, "test/file.txt"));
test('1. Tilde expansion: ~/path -> /home/user/path', () => {
expect(expandPath('~/test/file.txt')).toBe(path.join(HOME, 'test/file.txt'));
});
test("2. Windows tilde with backslash: ~\\path", () => {
test('2. Windows tilde with backslash: ~\\path', () => {
// expandPath handles both ~/ and ~\ regardless of platform
expect(expandPath("~\\test\\file.txt")).toBe(path.join(HOME, "test/file.txt"));
expect(expandPath('~\\test\\file.txt')).toBe(path.join(HOME, 'test/file.txt'));
});
test("3. Environment variable expansion: ${VAR}/path", () => {
expect(expandPath("${TEST_HOME}/file.txt")).toBe(path.normalize("/custom/home/file.txt"));
test('3. Environment variable expansion: ${VAR}/path', () => {
expect(expandPath('${TEST_HOME}/file.txt')).toBe(path.normalize('/custom/home/file.txt'));
});
test("4. Dollar sign env vars: $VAR/path", () => {
expect(expandPath("$TEST_HOME/file.txt")).toBe(path.normalize("/custom/home/file.txt"));
test('4. Dollar sign env vars: $VAR/path', () => {
expect(expandPath('$TEST_HOME/file.txt')).toBe(path.normalize('/custom/home/file.txt'));
});
test("5. Windows %VAR% expansion: %VAR%\\path (simulated)", () => {
test('5. Windows %VAR% expansion: %VAR%\\path (simulated)', () => {
// We can't easily mock process.platform if it's not win32,
// but the function check process.platform === 'win32'
if (process.platform === 'win32') {
expect(expandPath("%TEST_HOME%\\file.txt")).toBe(path.normalize("/custom/home/file.txt"));
expect(expandPath('%TEST_HOME%\\file.txt')).toBe(path.normalize('/custom/home/file.txt'));
} else {
// Should remain unchanged on non-windows (but separators normalized)
expect(expandPath("%TEST_HOME%\\file.txt")).toBe(path.normalize("%TEST_HOME%/file.txt"));
expect(expandPath('%TEST_HOME%\\file.txt')).toBe(path.normalize('%TEST_HOME%/file.txt'));
}
});
test("6. Mixed path separators normalization", () => {
const result = expandPath("path/to\\some/file");
expect(result).toBe(path.normalize("path/to/some/file"));
test('6. Mixed path separators normalization', () => {
const result = expandPath('path/to\\some/file');
expect(result).toBe(path.normalize('path/to/some/file'));
});
test("7. Nested env vars: ${HOME}/${VAR}/path", () => {
expect(expandPath("${TEST_HOME}/${TEST_VAR}/file.txt")).toBe(path.normalize("/custom/home/foo/file.txt"));
test('7. Nested env vars: ${HOME}/${VAR}/path', () => {
expect(expandPath('${TEST_HOME}/${TEST_VAR}/file.txt')).toBe(
path.normalize('/custom/home/foo/file.txt')
);
});
test("8. Empty/null path handling", () => {
expect(expandPath("")).toBe(".");
test('8. Empty/null path handling', () => {
expect(expandPath('')).toBe('.');
});
test("9. Already absolute paths stay unchanged", () => {
const absPath = "/absolute/path";
test('9. Already absolute paths stay unchanged', () => {
const absPath = '/absolute/path';
expect(expandPath(absPath)).toBe(path.normalize(absPath));
});
test("10. Undefined env vars -> empty string", () => {
expect(expandPath("${UNDEFINED_VAR}/file.txt")).toBe(path.normalize("/file.txt"));
expect(expandPath("$UNDEFINED_VAR/file.txt")).toBe(path.normalize("/file.txt"));
test('10. Undefined env vars -> empty string', () => {
expect(expandPath('${UNDEFINED_VAR}/file.txt')).toBe(path.normalize('/file.txt'));
expect(expandPath('$UNDEFINED_VAR/file.txt')).toBe(path.normalize('/file.txt'));
});
test("11. Windows drive letters stay intact", () => {
test('11. Windows drive letters stay intact', () => {
// Windows drive letter paths should be preserved
const result = expandPath("C:\\Users\\test\\file.txt");
expect(result).toContain("Users");
expect(result).toContain("test");
const result = expandPath('C:\\Users\\test\\file.txt');
expect(result).toContain('Users');
expect(result).toContain('test');
});
test("12. Windows UNC paths handled", () => {
test('12. Windows UNC paths handled', () => {
// UNC paths start with \\
const uncPath = "\\\\server\\share\\folder";
const uncPath = '\\\\server\\share\\folder';
const result = expandPath(uncPath);
// Should normalize but preserve the structure
expect(result).toContain("server");
expect(result).toContain("share");
expect(result).toContain('server');
expect(result).toContain('share');
});
test("13. Null-like input throws TypeError", () => {
test('13. Null-like input throws TypeError', () => {
// Function requires string input - documents current behavior
// @ts-ignore - testing runtime edge case
expect(() => expandPath(undefined as unknown as string)).toThrow(TypeError);
@@ -91,14 +93,14 @@ describe("expandPath", () => {
expect(() => expandPath(null as unknown as string)).toThrow(TypeError);
});
test("14. Path with spaces preserved", () => {
const pathWithSpaces = "~/My Documents/file.txt";
test('14. Path with spaces preserved', () => {
const pathWithSpaces = '~/My Documents/file.txt';
const result = expandPath(pathWithSpaces);
expect(result).toContain("My Documents");
expect(result).toContain('My Documents');
});
test("15. Multiple consecutive slashes normalized", () => {
const result = expandPath("path//to///file.txt");
expect(result).toBe(path.normalize("path/to/file.txt"));
test('15. Multiple consecutive slashes normalized', () => {
const result = expandPath('path//to///file.txt');
expect(result).toBe(path.normalize('path/to/file.txt'));
});
});
+9 -3
View File
@@ -97,7 +97,9 @@ describe('signal-forwarder', () => {
const child = createMockChildProcess();
const before = getSignalListenerCounts();
const onError = jest.fn(async () => {});
const err = Object.assign(new Error('spawn failed'), { code: 'ENOENT' }) as NodeJS.ErrnoException;
const err = Object.assign(new Error('spawn failed'), {
code: 'ENOENT',
}) as NodeJS.ErrnoException;
wireChildProcessSignals(child, onError);
child.emit('error', err);
@@ -113,7 +115,9 @@ describe('signal-forwarder', () => {
const child = createMockChildProcess();
const onError = jest.fn(async () => {});
const onExit = jest.fn();
const err = Object.assign(new Error('spawn failed'), { code: 'ENOENT' }) as NodeJS.ErrnoException;
const err = Object.assign(new Error('spawn failed'), {
code: 'ENOENT',
}) as NodeJS.ErrnoException;
wireChildProcessSignals(child, onError, onExit);
child.emit('error', err);
@@ -134,7 +138,9 @@ describe('signal-forwarder', () => {
.mockImplementation((() => undefined as never) as typeof process.exit);
try {
const err = Object.assign(new Error('spawn failed'), { code: 'ENOENT' }) as NodeJS.ErrnoException;
const err = Object.assign(new Error('spawn failed'), {
code: 'ENOENT',
}) as NodeJS.ErrnoException;
wireChildProcessSignals(child, onError);
child.emit('error', err);
await Promise.resolve();
@@ -1,5 +1,8 @@
import { expect, test, describe } from 'bun:test';
import { isCcsWebSearchHook, deduplicateCcsHooks } from '../../../../src/utils/websearch/hook-utils';
import {
isCcsWebSearchHook,
deduplicateCcsHooks,
} from '../../../../src/utils/websearch/hook-utils';
describe('isCcsWebSearchHook', () => {
test('Returns true for CCS hook with forward slashes (Unix path)', () => {
+3 -12
View File
@@ -28,17 +28,13 @@ describe('Dashboard Auth', () => {
describe('getDashboardAuthConfig', () => {
it('returns disabled by default', async () => {
const { getDashboardAuthConfig } = await import(
'../../src/config/unified-config-loader'
);
const { getDashboardAuthConfig } = await import('../../src/config/unified-config-loader');
const config = getDashboardAuthConfig();
expect(config.enabled).toBe(false);
});
it('returns 24 hour default session timeout', async () => {
const { getDashboardAuthConfig } = await import(
'../../src/config/unified-config-loader'
);
const { getDashboardAuthConfig } = await import('../../src/config/unified-config-loader');
const config = getDashboardAuthConfig();
expect(config.session_timeout_hours).toBe(24);
});
@@ -89,12 +85,7 @@ describe('Dashboard Auth', () => {
});
describe('public paths', () => {
const PUBLIC_PATHS = [
'/api/auth/login',
'/api/auth/check',
'/api/auth/setup',
'/api/health',
];
const PUBLIC_PATHS = ['/api/auth/login', '/api/auth/check', '/api/auth/setup', '/api/health'];
it('identifies public paths correctly', () => {
const isPublicPath = (path: string) =>
@@ -3,7 +3,9 @@ import { getStartUrlUnsupportedReason } from '../../../src/web-server/routes/cli
describe('cliproxy-auth-routes start-url guard', () => {
it('rejects device code providers', () => {
expect(getStartUrlUnsupportedReason('kiro')).toContain("Kiro method 'aws' uses Device Code flow");
expect(getStartUrlUnsupportedReason('kiro')).toContain(
"Kiro method 'aws' uses Device Code flow"
);
expect(getStartUrlUnsupportedReason('ghcp')).toContain("Provider 'ghcp' uses Device Code flow");
expect(getStartUrlUnsupportedReason('qwen')).toContain("Provider 'qwen' uses Device Code flow");
});
@@ -14,7 +14,10 @@ process.env.CCS_HOME = TEST_CCS_DIR;
// Import after setting env var
import type { CursorConfig } from '../../../src/config/unified-config-types';
import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../../../src/config/unified-config-loader';
import {
loadOrCreateUnifiedConfig,
saveUnifiedConfig,
} from '../../../src/config/unified-config-loader';
import { getCcsDir } from '../../../src/utils/config-manager';
describe('Cursor Settings Routes Logic', () => {