mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
Refactor CLI Config Manager and Add Provider Model Routes
- Removed deprecated constants and functions from cli-config-manager.ts. - Introduced new provider model presets in litellm-provider-models.ts for better organization and management of model information. - Created provider-routes.ts to handle API endpoints for retrieving provider information and models. - Added integration tests for provider routes to ensure correct functionality and response structure. - Implemented unit tests for settings persistence functions, covering various scenarios and edge cases. - Enhanced error handling and validation in the new routes and settings functions.
This commit is contained in:
205
ccw/tests/integration/provider-routes.test.ts
Normal file
205
ccw/tests/integration/provider-routes.test.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Integration tests for provider routes.
|
||||
*
|
||||
* Notes:
|
||||
* - Targets runtime implementation shipped in `ccw/dist`.
|
||||
* - Exercises real HTTP request/response flow via a minimal test server.
|
||||
*/
|
||||
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import http from 'node:http';
|
||||
|
||||
const providerRoutesUrl = new URL('../../dist/core/routes/provider-routes.js', import.meta.url);
|
||||
providerRoutesUrl.searchParams.set('t', String(Date.now()));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let mod: any;
|
||||
|
||||
before(async () => {
|
||||
mod = await import(providerRoutesUrl.href);
|
||||
});
|
||||
|
||||
describe('Provider Routes Integration', () => {
|
||||
let server: http.Server;
|
||||
const PORT = 19998;
|
||||
|
||||
function startServer() {
|
||||
server = http.createServer((req, res) => {
|
||||
const routeContext = {
|
||||
pathname: new URL(req.url!, `http://localhost:${PORT}`).pathname,
|
||||
url: new URL(req.url!, `http://localhost:${PORT}`),
|
||||
req,
|
||||
res,
|
||||
initialPath: process.cwd(),
|
||||
handlePostRequest: () => {},
|
||||
broadcastToClients: () => {},
|
||||
extractSessionIdFromPath: () => null,
|
||||
server
|
||||
};
|
||||
|
||||
mod.handleProviderRoutes(routeContext).then((handled: boolean) => {
|
||||
if (!handled) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Not Found' }));
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
});
|
||||
});
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
server.listen(PORT, () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
function stopServer() {
|
||||
return new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
describe('GET /api/providers', () => {
|
||||
it('should return list of all providers', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
assert(Array.isArray(data.providers));
|
||||
assert(data.providers.length > 0);
|
||||
assert(data.providers.some((p: any) => p.id === 'google'));
|
||||
assert(data.providers.some((p: any) => p.id === 'qwen'));
|
||||
assert(data.providers.some((p: any) => p.id === 'openai'));
|
||||
assert(data.providers.some((p: any) => p.id === 'anthropic'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should include provider name and model count', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers`);
|
||||
const data: any = await response.json();
|
||||
|
||||
const googleProvider = data.providers.find((p: any) => p.id === 'google');
|
||||
assert(googleProvider);
|
||||
assert.strictEqual(googleProvider.name, 'Google AI');
|
||||
assert(typeof googleProvider.modelCount === 'number');
|
||||
assert(googleProvider.modelCount > 0);
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/providers/:provider/models', () => {
|
||||
it('should return models for google provider', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/google/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
assert.strictEqual(data.provider, 'google');
|
||||
assert.strictEqual(data.providerName, 'Google AI');
|
||||
assert(Array.isArray(data.models));
|
||||
assert(data.models.some((m: any) => m.id === 'gemini-2.5-pro'));
|
||||
assert(data.models.some((m: any) => m.id === 'gemini-2.5-flash'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should return models with capabilities and context window', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/google/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
const geminiPro = data.models.find((m: any) => m.id === 'gemini-2.5-pro');
|
||||
assert(geminiPro);
|
||||
assert.strictEqual(geminiPro.name, 'Gemini 2.5 Pro');
|
||||
assert(Array.isArray(geminiPro.capabilities));
|
||||
assert(geminiPro.capabilities.includes('text'));
|
||||
assert(geminiPro.capabilities.includes('vision'));
|
||||
assert(geminiPro.capabilities.includes('code'));
|
||||
assert(typeof geminiPro.contextWindow === 'number');
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should return models for qwen provider', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/qwen/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
assert.strictEqual(data.provider, 'qwen');
|
||||
assert(Array.isArray(data.models));
|
||||
assert(data.models.some((m: any) => m.id === 'coder-model'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should return models for openai provider', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/openai/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
assert.strictEqual(data.provider, 'openai');
|
||||
assert(Array.isArray(data.models));
|
||||
assert(data.models.some((m: any) => m.id === 'gpt-5.2'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should return models for anthropic provider', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/anthropic/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
assert.strictEqual(data.provider, 'anthropic');
|
||||
assert(Array.isArray(data.models));
|
||||
assert(data.models.some((m: any) => m.id === 'sonnet'));
|
||||
assert(data.models.some((m: any) => m.id === 'opus'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should return 404 for unknown provider', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/unknown/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 404);
|
||||
assert.strictEqual(data.success, false);
|
||||
assert(data.error.includes('Provider not found'));
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
|
||||
it('should handle URL encoding in provider name', async () => {
|
||||
await startServer();
|
||||
|
||||
const response = await fetch(`http://localhost:${PORT}/api/providers/${encodeURIComponent('google')}/models`);
|
||||
const data: any = await response.json();
|
||||
|
||||
assert.strictEqual(response.status, 200);
|
||||
assert.strictEqual(data.success, true);
|
||||
|
||||
await stopServer();
|
||||
});
|
||||
});
|
||||
});
|
||||
375
ccw/tests/settings-persistence.test.ts
Normal file
375
ccw/tests/settings-persistence.test.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* Unit tests for Settings Persistence Functions
|
||||
*
|
||||
* Tests the new setter/getter functions for ClaudeCliSettingsConfig:
|
||||
* - setPromptFormat / getPromptFormat
|
||||
* - setDefaultModel / getDefaultModel
|
||||
* - setAutoSyncEnabled / getAutoSyncEnabled
|
||||
* - setSmartContextEnabled / getSmartContextEnabled
|
||||
* - setNativeResume / getNativeResume
|
||||
*/
|
||||
|
||||
import { after, afterEach, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { existsSync, mkdtempSync, rmSync, writeFileSync, readFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
// Set up isolated test environment
|
||||
const TEST_CCW_HOME = mkdtempSync(join(tmpdir(), 'ccw-settings-test-'));
|
||||
process.env.CCW_DATA_DIR = TEST_CCW_HOME;
|
||||
|
||||
const claudeCliToolsPath = new URL('../dist/tools/claude-cli-tools.js', import.meta.url).href;
|
||||
|
||||
describe('Settings Persistence Functions', async () => {
|
||||
let claudeCliTools: any;
|
||||
const testProjectDir = join(TEST_CCW_HOME, 'test-project');
|
||||
|
||||
before(async () => {
|
||||
claudeCliTools = await import(claudeCliToolsPath);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
rmSync(TEST_CCW_HOME, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up settings file after each test
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
if (existsSync(settingsPath)) {
|
||||
rmSync(settingsPath);
|
||||
}
|
||||
});
|
||||
|
||||
describe('setPromptFormat / getPromptFormat', () => {
|
||||
it('should set and get prompt format', () => {
|
||||
const result = claudeCliTools.setPromptFormat(testProjectDir, 'yaml');
|
||||
assert.equal(result.promptFormat, 'yaml');
|
||||
|
||||
const retrieved = claudeCliTools.getPromptFormat(testProjectDir);
|
||||
assert.equal(retrieved, 'yaml');
|
||||
});
|
||||
|
||||
it('should persist prompt format to file', () => {
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'json');
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
assert.ok(existsSync(settingsPath), 'Settings file should exist');
|
||||
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.promptFormat, 'json');
|
||||
});
|
||||
|
||||
it('should update existing prompt format', () => {
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'plain');
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'yaml');
|
||||
|
||||
const retrieved = claudeCliTools.getPromptFormat(testProjectDir);
|
||||
assert.equal(retrieved, 'yaml');
|
||||
});
|
||||
|
||||
it('should return default when file does not exist', () => {
|
||||
const retrieved = claudeCliTools.getPromptFormat(testProjectDir);
|
||||
assert.equal(retrieved, 'plain');
|
||||
});
|
||||
|
||||
it('should accept all valid format values', () => {
|
||||
const formats: Array<'plain' | 'yaml' | 'json'> = ['plain', 'yaml', 'json'];
|
||||
|
||||
for (const format of formats) {
|
||||
claudeCliTools.setPromptFormat(testProjectDir, format);
|
||||
const retrieved = claudeCliTools.getPromptFormat(testProjectDir);
|
||||
assert.equal(retrieved, format, `Format ${format} should be set correctly`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultModel / getDefaultModel', () => {
|
||||
it('should set and get default model', () => {
|
||||
const result = claudeCliTools.setDefaultModel(testProjectDir, 'gemini-2.5-pro');
|
||||
assert.equal(result.defaultModel, 'gemini-2.5-pro');
|
||||
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, 'gemini-2.5-pro');
|
||||
});
|
||||
|
||||
it('should persist default model to file', () => {
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'claude-opus-4');
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
assert.ok(existsSync(settingsPath), 'Settings file should exist');
|
||||
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.defaultModel, 'claude-opus-4');
|
||||
});
|
||||
|
||||
it('should update existing default model', () => {
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'gpt-4.1');
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'gpt-5.2');
|
||||
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, 'gpt-5.2');
|
||||
});
|
||||
|
||||
it('should return undefined when not set', () => {
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, undefined);
|
||||
});
|
||||
|
||||
it('should handle arbitrary model names', () => {
|
||||
const models = ['custom-model-1', 'test-model', 'my-fine-tuned-model'];
|
||||
|
||||
for (const model of models) {
|
||||
claudeCliTools.setDefaultModel(testProjectDir, model);
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, model, `Model ${model} should be set correctly`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAutoSyncEnabled / getAutoSyncEnabled', () => {
|
||||
it('should set and get auto-sync enabled status', () => {
|
||||
const result = claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
assert.equal(result.autoSyncEnabled, true);
|
||||
|
||||
const retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, true);
|
||||
});
|
||||
|
||||
it('should persist auto-sync status to file', () => {
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, false);
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
assert.ok(existsSync(settingsPath), 'Settings file should exist');
|
||||
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.autoSyncEnabled, false);
|
||||
});
|
||||
|
||||
it('should update existing auto-sync status', () => {
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, false);
|
||||
|
||||
const retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, false);
|
||||
});
|
||||
|
||||
it('should return undefined when not set', () => {
|
||||
const retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, undefined);
|
||||
});
|
||||
|
||||
it('should toggle between true and false', () => {
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
let retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, true);
|
||||
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, false);
|
||||
retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, false);
|
||||
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
retrieved = claudeCliTools.getAutoSyncEnabled(testProjectDir);
|
||||
assert.equal(retrieved, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSmartContextEnabled / getSmartContextEnabled', () => {
|
||||
it('should set and get smart context enabled status', () => {
|
||||
const result = claudeCliTools.setSmartContextEnabled(testProjectDir, true);
|
||||
assert.equal(result.smartContext.enabled, true);
|
||||
|
||||
const retrieved = claudeCliTools.getSmartContextEnabled(testProjectDir);
|
||||
assert.equal(retrieved, true);
|
||||
});
|
||||
|
||||
it('should persist smart context status to file', () => {
|
||||
claudeCliTools.setSmartContextEnabled(testProjectDir, true);
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
assert.ok(existsSync(settingsPath), 'Settings file should exist');
|
||||
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.smartContext.enabled, true);
|
||||
});
|
||||
|
||||
it('should preserve other smartContext properties', () => {
|
||||
// First, load settings to check default maxFiles
|
||||
const settings = claudeCliTools.loadClaudeCliSettings(testProjectDir);
|
||||
const defaultMaxFiles = settings.smartContext.maxFiles;
|
||||
|
||||
claudeCliTools.setSmartContextEnabled(testProjectDir, true);
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
|
||||
assert.equal(content.smartContext.enabled, true);
|
||||
assert.equal(content.smartContext.maxFiles, defaultMaxFiles, 'maxFiles should be preserved');
|
||||
});
|
||||
|
||||
it('should return false when not set', () => {
|
||||
const retrieved = claudeCliTools.getSmartContextEnabled(testProjectDir);
|
||||
assert.equal(retrieved, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setNativeResume / getNativeResume', () => {
|
||||
it('should set and get native resume status', () => {
|
||||
const result = claudeCliTools.setNativeResume(testProjectDir, false);
|
||||
assert.equal(result.nativeResume, false);
|
||||
|
||||
const retrieved = claudeCliTools.getNativeResume(testProjectDir);
|
||||
assert.equal(retrieved, false);
|
||||
});
|
||||
|
||||
it('should persist native resume status to file', () => {
|
||||
claudeCliTools.setNativeResume(testProjectDir, false);
|
||||
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
assert.ok(existsSync(settingsPath), 'Settings file should exist');
|
||||
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.nativeResume, false);
|
||||
});
|
||||
|
||||
it('should return true when not set (default)', () => {
|
||||
const retrieved = claudeCliTools.getNativeResume(testProjectDir);
|
||||
assert.equal(retrieved, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple settings updates', () => {
|
||||
it('should handle multiple settings updates in sequence', () => {
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'yaml');
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'gemini-2.5-pro');
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
claudeCliTools.setSmartContextEnabled(testProjectDir, true);
|
||||
claudeCliTools.setNativeResume(testProjectDir, false);
|
||||
|
||||
assert.equal(claudeCliTools.getPromptFormat(testProjectDir), 'yaml');
|
||||
assert.equal(claudeCliTools.getDefaultModel(testProjectDir), 'gemini-2.5-pro');
|
||||
assert.equal(claudeCliTools.getAutoSyncEnabled(testProjectDir), true);
|
||||
assert.equal(claudeCliTools.getSmartContextEnabled(testProjectDir), true);
|
||||
assert.equal(claudeCliTools.getNativeResume(testProjectDir), false);
|
||||
});
|
||||
|
||||
it('should preserve existing settings when updating one', () => {
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'json');
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'test-model');
|
||||
|
||||
// Update only auto-sync
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
|
||||
// Verify previous settings are preserved
|
||||
assert.equal(claudeCliTools.getPromptFormat(testProjectDir), 'json');
|
||||
assert.equal(claudeCliTools.getDefaultModel(testProjectDir), 'test-model');
|
||||
assert.equal(claudeCliTools.getAutoSyncEnabled(testProjectDir), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance', () => {
|
||||
it('should complete setter operations in under 10ms', () => {
|
||||
const operations = [
|
||||
() => claudeCliTools.setPromptFormat(testProjectDir, 'yaml'),
|
||||
() => claudeCliTools.setDefaultModel(testProjectDir, 'test-model'),
|
||||
() => claudeCliTools.setAutoSyncEnabled(testProjectDir, true),
|
||||
() => claudeCliTools.setSmartContextEnabled(testProjectDir, true),
|
||||
() => claudeCliTools.setNativeResume(testProjectDir, false),
|
||||
];
|
||||
|
||||
for (const operation of operations) {
|
||||
const start = Date.now();
|
||||
operation();
|
||||
const duration = Date.now() - start;
|
||||
assert.ok(duration < 10, `Operation should complete in under 10ms (took ${duration}ms)`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should complete getter operations in under 10ms', () => {
|
||||
// Set up some data first
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'yaml');
|
||||
claudeCliTools.setDefaultModel(testProjectDir, 'test-model');
|
||||
claudeCliTools.setAutoSyncEnabled(testProjectDir, true);
|
||||
|
||||
const operations = [
|
||||
() => claudeCliTools.getPromptFormat(testProjectDir),
|
||||
() => claudeCliTools.getDefaultModel(testProjectDir),
|
||||
() => claudeCliTools.getAutoSyncEnabled(testProjectDir),
|
||||
() => claudeCliTools.getSmartContextEnabled(testProjectDir),
|
||||
() => claudeCliTools.getNativeResume(testProjectDir),
|
||||
];
|
||||
|
||||
for (const operation of operations) {
|
||||
const start = Date.now();
|
||||
operation();
|
||||
const duration = Date.now() - start;
|
||||
assert.ok(duration < 10, `Operation should complete in under 10ms (took ${duration}ms)`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('File corruption handling', () => {
|
||||
it('should handle invalid JSON gracefully', () => {
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
const claudeDir = join(TEST_CCW_HOME, '.claude');
|
||||
|
||||
// Create .claude directory if it doesn't exist
|
||||
if (!existsSync(claudeDir)) {
|
||||
mkdtempSync(claudeDir);
|
||||
}
|
||||
|
||||
// Write invalid JSON
|
||||
writeFileSync(settingsPath, '{invalid json}', 'utf-8');
|
||||
|
||||
// Getters should return defaults
|
||||
assert.equal(claudeCliTools.getPromptFormat(testProjectDir), 'plain');
|
||||
assert.equal(claudeCliTools.getDefaultModel(testProjectDir), undefined);
|
||||
assert.equal(claudeCliTools.getAutoSyncEnabled(testProjectDir), undefined);
|
||||
});
|
||||
|
||||
it('should recover from corrupted file by overwriting', () => {
|
||||
const settingsPath = join(TEST_CCW_HOME, '.claude', 'cli-settings.json');
|
||||
const claudeDir = join(TEST_CCW_HOME, '.claude');
|
||||
|
||||
// Create .claude directory if it doesn't exist
|
||||
if (!existsSync(claudeDir)) {
|
||||
mkdtempSync(claudeDir);
|
||||
}
|
||||
|
||||
// Write invalid JSON
|
||||
writeFileSync(settingsPath, '{invalid json}', 'utf-8');
|
||||
|
||||
// Setter should fix the file
|
||||
claudeCliTools.setPromptFormat(testProjectDir, 'yaml');
|
||||
|
||||
// Should now read correctly
|
||||
assert.equal(claudeCliTools.getPromptFormat(testProjectDir), 'yaml');
|
||||
|
||||
// File should be valid JSON
|
||||
const content = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
assert.equal(content.promptFormat, 'yaml');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle empty string for defaultModel', () => {
|
||||
claudeCliTools.setDefaultModel(testProjectDir, '');
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, '');
|
||||
});
|
||||
|
||||
it('should handle very long model names', () => {
|
||||
const longModelName = 'a'.repeat(1000);
|
||||
claudeCliTools.setDefaultModel(testProjectDir, longModelName);
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, longModelName);
|
||||
});
|
||||
|
||||
it('should handle special characters in model names', () => {
|
||||
const specialName = 'model-@#$%^&*()_+{}[]|:;<>?,./~`';
|
||||
claudeCliTools.setDefaultModel(testProjectDir, specialName);
|
||||
const retrieved = claudeCliTools.getDefaultModel(testProjectDir);
|
||||
assert.equal(retrieved, specialName);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user