From 539afea7374f2c931ab5cbb1e04e0845c57729b5 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 17 Feb 2026 17:03:11 +0700 Subject: [PATCH] style: format source and test files --- src/management/checks/image-analysis-check.ts | 5 +- tests/e2e/image-analyzer-hook.e2e.test.ts | 86 ++++++++-- tests/unit/auth/profile-detector.test.ts | 16 +- .../account-safety-quota-exhaustion.test.ts | 6 +- .../unit/cliproxy/composite-fallback.test.ts | 3 +- .../cliproxy/extended-context-config.test.ts | 4 +- .../cliproxy/management-api-client.test.ts | 8 +- tests/unit/cliproxy/port-validation.test.ts | 5 +- .../cliproxy/quota-fetcher-gemini-cli.test.ts | 4 +- .../cliproxy/quota-monitor-runtime.test.ts | 6 +- .../unit/cliproxy/remote-proxy-client.test.ts | 5 +- tests/unit/cliproxy/schema-sanitizer.test.ts | 5 +- ...ool-sanitization-proxy-integration.test.ts | 11 +- tests/unit/commands/env-command.test.ts | 24 ++- tests/unit/commands/setup-command.test.ts | 14 +- .../commands/shell-completion-command.test.ts | 4 +- tests/unit/cursor/cursor-daemon.test.ts | 54 +++--- tests/unit/cursor/cursor-protobuf.test.ts | 20 ++- tests/unit/data-aggregator.test.ts | 8 +- .../delegation/delegation-handler.test.ts | 24 +-- .../unit/delegation/headless-executor.test.ts | 5 +- tests/unit/glmt/retry-logic.test.ts | 162 +++++++++++++----- tests/unit/jsonl-parser.test.ts | 9 +- tests/unit/mcp-manager.test.ts | 2 +- tests/unit/shared-manager.test.ts | 3 +- .../unit/targets/droid-config-manager.test.ts | 138 ++++++++++----- tests/unit/targets/target-resolver.test.ts | 6 +- tests/unit/unified-config.test.ts | 11 +- tests/unit/utils/expand-path.test.ts | 92 +++++----- tests/unit/utils/signal-forwarder.test.ts | 12 +- tests/unit/utils/websearch/hook-utils.test.ts | 5 +- tests/unit/web-server/auth-middleware.test.ts | 15 +- .../web-server/cliproxy-auth-routes.test.ts | 4 +- .../web-server/cursor-settings-routes.test.ts | 5 +- 34 files changed, 486 insertions(+), 295 deletions(-) diff --git a/src/management/checks/image-analysis-check.ts b/src/management/checks/image-analysis-check.ts index 39a0f7c5..e539bff4 100644 --- a/src/management/checks/image-analysis-check.ts +++ b/src/management/checks/image-analysis-check.ts @@ -94,9 +94,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise * Fix image analysis configuration issues */ export async function fixImageAnalysisConfig(): Promise { - const { updateUnifiedConfig, loadOrCreateUnifiedConfig } = await import( - '../../config/unified-config-loader' - ); + const { updateUnifiedConfig, loadOrCreateUnifiedConfig } = + await import('../../config/unified-config-loader'); const config = loadOrCreateUnifiedConfig(); let fixed = false; diff --git a/tests/e2e/image-analyzer-hook.e2e.test.ts b/tests/e2e/image-analyzer-hook.e2e.test.ts index 8df3adfa..86c5c95e 100644 --- a/tests/e2e/image-analyzer-hook.e2e.test.ts +++ b/tests/e2e/image-analyzer-hook.e2e.test.ts @@ -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, }; diff --git a/tests/unit/auth/profile-detector.test.ts b/tests/unit/auth/profile-detector.test.ts index 52f2ae68..91cefcd0 100644 --- a/tests/unit/auth/profile-detector.test.ts +++ b/tests/unit/auth/profile-detector.test.ts @@ -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'); diff --git a/tests/unit/cliproxy/account-safety-quota-exhaustion.test.ts b/tests/unit/cliproxy/account-safety-quota-exhaustion.test.ts index 855832e7..a24db225 100644 --- a/tests/unit/cliproxy/account-safety-quota-exhaustion.test.ts +++ b/tests/unit/cliproxy/account-safety-quota-exhaustion.test.ts @@ -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; diff --git a/tests/unit/cliproxy/composite-fallback.test.ts b/tests/unit/cliproxy/composite-fallback.test.ts index 338f6a02..33a5f5d3 100644 --- a/tests/unit/cliproxy/composite-fallback.test.ts +++ b/tests/unit/cliproxy/composite-fallback.test.ts @@ -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 }); diff --git a/tests/unit/cliproxy/extended-context-config.test.ts b/tests/unit/cliproxy/extended-context-config.test.ts index 5e3a12af..697ba35e 100644 --- a/tests/unit/cliproxy/extended-context-config.test.ts +++ b/tests/unit/cliproxy/extended-context-config.test.ts @@ -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', () => { diff --git a/tests/unit/cliproxy/management-api-client.test.ts b/tests/unit/cliproxy/management-api-client.test.ts index 1816f8ef..e3b6b7b4 100644 --- a/tests/unit/cliproxy/management-api-client.test.ts +++ b/tests/unit/cliproxy/management-api-client.test.ts @@ -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; diff --git a/tests/unit/cliproxy/port-validation.test.ts b/tests/unit/cliproxy/port-validation.test.ts index 71d7301a..9fb39153 100644 --- a/tests/unit/cliproxy/port-validation.test.ts +++ b/tests/unit/cliproxy/port-validation.test.ts @@ -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', () => { diff --git a/tests/unit/cliproxy/quota-fetcher-gemini-cli.test.ts b/tests/unit/cliproxy/quota-fetcher-gemini-cli.test.ts index 2a9e6b6f..c879d84b 100644 --- a/tests/unit/cliproxy/quota-fetcher-gemini-cli.test.ts +++ b/tests/unit/cliproxy/quota-fetcher-gemini-cli.test.ts @@ -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); diff --git a/tests/unit/cliproxy/quota-monitor-runtime.test.ts b/tests/unit/cliproxy/quota-monitor-runtime.test.ts index 9bb57284..7afbcc45 100644 --- a/tests/unit/cliproxy/quota-monitor-runtime.test.ts +++ b/tests/unit/cliproxy/quota-monitor-runtime.test.ts @@ -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; diff --git a/tests/unit/cliproxy/remote-proxy-client.test.ts b/tests/unit/cliproxy/remote-proxy-client.test.ts index 58defd72..e15b8d3a 100644 --- a/tests/unit/cliproxy/remote-proxy-client.test.ts +++ b/tests/unit/cliproxy/remote-proxy-client.test.ts @@ -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 diff --git a/tests/unit/cliproxy/schema-sanitizer.test.ts b/tests/unit/cliproxy/schema-sanitizer.test.ts index b5e64f38..9d555269 100644 --- a/tests/unit/cliproxy/schema-sanitizer.test.ts +++ b/tests/unit/cliproxy/schema-sanitizer.test.ts @@ -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); diff --git a/tests/unit/cliproxy/tool-sanitization-proxy-integration.test.ts b/tests/unit/cliproxy/tool-sanitization-proxy-integration.test.ts index 8bbfc5d0..03a9bc5f 100644 --- a/tests/unit/cliproxy/tool-sanitization-proxy-integration.test.ts +++ b/tests/unit/cliproxy/tool-sanitization-proxy-integration.test.ts @@ -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' }], }), }); diff --git a/tests/unit/commands/env-command.test.ts b/tests/unit/commands/env-command.test.ts index aef8327d..9c2fcfd7 100644 --- a/tests/unit/commands/env-command.test.ts +++ b/tests/unit/commands/env-command.test.ts @@ -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'); }); }); }); diff --git a/tests/unit/commands/setup-command.test.ts b/tests/unit/commands/setup-command.test.ts index 8277aefb..f5e47edb 100644 --- a/tests/unit/commands/setup-command.test.ts +++ b/tests/unit/commands/setup-command.test.ts @@ -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; diff --git a/tests/unit/commands/shell-completion-command.test.ts b/tests/unit/commands/shell-completion-command.test.ts index f9c93a53..4c2d71ab 100644 --- a/tests/unit/commands/shell-completion-command.test.ts +++ b/tests/unit/commands/shell-completion-command.test.ts @@ -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 + ); }); }); diff --git a/tests/unit/cursor/cursor-daemon.test.ts b/tests/unit/cursor/cursor-daemon.test.ts index b46fa317..7990985a 100644 --- a/tests/unit/cursor/cursor-daemon.test.ts +++ b/tests/unit/cursor/cursor-daemon.test.ts @@ -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', () => { diff --git a/tests/unit/cursor/cursor-protobuf.test.ts b/tests/unit/cursor/cursor-protobuf.test.ts index 0ea16a7c..86c2f27d 100644 --- a/tests/unit/cursor/cursor-protobuf.test.ts +++ b/tests/unit/cursor/cursor-protobuf.test.ts @@ -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')); diff --git a/tests/unit/data-aggregator.test.ts b/tests/unit/data-aggregator.test.ts index 50268007..4984cc15 100644 --- a/tests/unit/data-aggregator.test.ts +++ b/tests/unit/data-aggregator.test.ts @@ -14,9 +14,7 @@ import { type RawUsageEntry } from '../../src/web-server/jsonl-parser'; // TEST FIXTURES // ============================================================================ -const createEntry = ( - overrides: Partial = {} -): RawUsageEntry => ({ +const createEntry = (overrides: Partial = {}): 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 }); diff --git a/tests/unit/delegation/delegation-handler.test.ts b/tests/unit/delegation/delegation-handler.test.ts index 793e09e0..c5d4e044 100644 --- a/tests/unit/delegation/delegation-handler.test.ts +++ b/tests/unit/delegation/delegation-handler.test.ts @@ -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'); }); diff --git a/tests/unit/delegation/headless-executor.test.ts b/tests/unit/delegation/headless-executor.test.ts index cb78d954..14479d27 100644 --- a/tests/unit/delegation/headless-executor.test.ts +++ b/tests/unit/delegation/headless-executor.test.ts @@ -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[] { + function filterExtraArgs(extraArgs: string[], explicitFlags: Set): string[] { const filteredExtras: string[] = []; for (let i = 0; i < extraArgs.length; i++) { if (explicitFlags.has(extraArgs[i])) { diff --git a/tests/unit/glmt/retry-logic.test.ts b/tests/unit/glmt/retry-logic.test.ts index 6523cbfa..683d2154 100644 --- a/tests/unit/glmt/retry-logic.test.ts +++ b/tests/unit/glmt/retry-logic.test.ts @@ -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 }).forwardToUpstream = async () => { - attempts++; - return { choices: [{ message: { content: 'success' } }] }; - }; + (proxy as unknown as { forwardToUpstream: () => Promise }).forwardToUpstream = + async () => { + attempts++; + return { choices: [{ message: { content: 'success' } }] }; + }; - const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise }).forwardWithRetry.bind(proxy); + const forwardWithRetry = ( + proxy as unknown as { + forwardWithRetry: (req: unknown, headers: unknown) => Promise; + } + ).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 }).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 }).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 }).forwardWithRetry.bind(proxy); + const forwardWithRetry = ( + proxy as unknown as { + forwardWithRetry: (req: unknown, headers: unknown) => Promise; + } + ).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 }).forwardToUpstream = async () => { - attempts++; - throw new Error('Upstream error: 429 Too Many Requests'); - }; + (proxy as unknown as { forwardToUpstream: () => Promise }).forwardToUpstream = + async () => { + attempts++; + throw new Error('Upstream error: 429 Too Many Requests'); + }; - const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise }).forwardWithRetry.bind(proxy); + const forwardWithRetry = ( + proxy as unknown as { + forwardWithRetry: (req: unknown, headers: unknown) => Promise; + } + ).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 }).forwardToUpstream = async () => { - attempts++; - throw new Error('Upstream error: 429 Too Many Requests'); - }; + (proxy as unknown as { forwardToUpstream: () => Promise }).forwardToUpstream = + async () => { + attempts++; + throw new Error('Upstream error: 429 Too Many Requests'); + }; - const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise }).forwardWithRetry.bind(proxy); + const forwardWithRetry = ( + proxy as unknown as { + forwardWithRetry: (req: unknown, headers: unknown) => Promise; + } + ).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 }).forwardToUpstream = async () => { - attempts++; - throw new Error('Upstream error: 500 Internal Server Error'); - }; + (proxy as unknown as { forwardToUpstream: () => Promise }).forwardToUpstream = + async () => { + attempts++; + throw new Error('Upstream error: 500 Internal Server Error'); + }; - const forwardWithRetry = (proxy as unknown as { forwardWithRetry: (req: unknown, headers: unknown) => Promise }).forwardWithRetry.bind(proxy); + const forwardWithRetry = ( + proxy as unknown as { + forwardWithRetry: (req: unknown, headers: unknown) => Promise; + } + ).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); diff --git a/tests/unit/jsonl-parser.test.ts b/tests/unit/jsonl-parser.test.ts index a6a19b1d..b3c00e71 100644 --- a/tests/unit/jsonl-parser.test.ts +++ b/tests/unit/jsonl-parser.test.ts @@ -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); diff --git a/tests/unit/mcp-manager.test.ts b/tests/unit/mcp-manager.test.ts index e82362e8..bc77b9f5 100644 --- a/tests/unit/mcp-manager.test.ts +++ b/tests/unit/mcp-manager.test.ts @@ -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); }); diff --git a/tests/unit/shared-manager.test.ts b/tests/unit/shared-manager.test.ts index f9124a58..c06e2b48 100644 --- a/tests/unit/shared-manager.test.ts +++ b/tests/unit/shared-manager.test.ts @@ -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', }, ], diff --git a/tests/unit/targets/droid-config-manager.test.ts b/tests/unit/targets/droid-config-manager.test.ts index c1743d67..3df8cb5d 100644 --- a/tests/unit/targets/droid-config-manager.test.ts +++ b/tests/unit/targets/droid-config-manager.test.ts @@ -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); }); }); diff --git a/tests/unit/targets/target-resolver.test.ts b/tests/unit/targets/target-resolver.test.ts index 03ca380a..cfed158d 100644 --- a/tests/unit/targets/target-resolver.test.ts +++ b/tests/unit/targets/target-resolver.test.ts @@ -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', () => { diff --git a/tests/unit/unified-config.test.ts b/tests/unit/unified-config.test.ts index eaeb30dc..b4094ea4 100644 --- a/tests/unit/unified-config.test.ts +++ b/tests/unit/unified-config.test.ts @@ -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)); } diff --git a/tests/unit/utils/expand-path.test.ts b/tests/unit/utils/expand-path.test.ts index 42dab0f5..f8d759ef 100644 --- a/tests/unit/utils/expand-path.test.ts +++ b/tests/unit/utils/expand-path.test.ts @@ -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')); }); }); diff --git a/tests/unit/utils/signal-forwarder.test.ts b/tests/unit/utils/signal-forwarder.test.ts index 463453f8..451987f7 100644 --- a/tests/unit/utils/signal-forwarder.test.ts +++ b/tests/unit/utils/signal-forwarder.test.ts @@ -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(); diff --git a/tests/unit/utils/websearch/hook-utils.test.ts b/tests/unit/utils/websearch/hook-utils.test.ts index 64373365..56c5aa5f 100644 --- a/tests/unit/utils/websearch/hook-utils.test.ts +++ b/tests/unit/utils/websearch/hook-utils.test.ts @@ -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)', () => { diff --git a/tests/unit/web-server/auth-middleware.test.ts b/tests/unit/web-server/auth-middleware.test.ts index 9c0f9191..e441b310 100644 --- a/tests/unit/web-server/auth-middleware.test.ts +++ b/tests/unit/web-server/auth-middleware.test.ts @@ -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) => diff --git a/tests/unit/web-server/cliproxy-auth-routes.test.ts b/tests/unit/web-server/cliproxy-auth-routes.test.ts index 1ad73d09..9982c133 100644 --- a/tests/unit/web-server/cliproxy-auth-routes.test.ts +++ b/tests/unit/web-server/cliproxy-auth-routes.test.ts @@ -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"); }); diff --git a/tests/unit/web-server/cursor-settings-routes.test.ts b/tests/unit/web-server/cursor-settings-routes.test.ts index fb163a7a..6f74edcc 100644 --- a/tests/unit/web-server/cursor-settings-routes.test.ts +++ b/tests/unit/web-server/cursor-settings-routes.test.ts @@ -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', () => {