Files
Claude-Code-Workflow/ccw/tests/settings-persistence.test.ts
catlog22 985085c624 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.
2026-01-25 17:27:58 +08:00

376 lines
14 KiB
TypeScript

/**
* 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);
});
});
});