feat: 添加完整的单元测试套件
- 新增 vitest 测试框架配置 - 添加 54 个测试文件,共 951 个测试用例 - 覆盖核心模块: - Agent: executor, registry, config-loader, permission-merger - Context: manager, compaction, prune, token-counter - Permission: manager, bash/file/git/web checkers, wildcard - Session: manager, storage - Tools: filesystem (12个), git (10个), web, shell, todo, task - LSP: client, server, language - Utils: config, diff - UI: terminal
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
|
||||
// Mock fs
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn(),
|
||||
writeFileSync: vi.fn(),
|
||||
mkdirSync: vi.fn(),
|
||||
}));
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { getConfig, loadConfig, saveConfig } from '../../../src/utils/config.js';
|
||||
|
||||
describe('Config - 配置管理', () => {
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// 清理环境变量
|
||||
delete process.env.AI_PROVIDER;
|
||||
delete process.env.ANTHROPIC_API_KEY;
|
||||
delete process.env.DEEPSEEK_API_KEY;
|
||||
delete process.env.AI_MODEL;
|
||||
delete process.env.AI_MAX_TOKENS;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 恢复环境变量
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
describe('getConfig - 获取原始配置', () => {
|
||||
it('配置文件存在时返回内容', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
||||
provider: 'anthropic',
|
||||
apiKey: 'test-key',
|
||||
model: 'claude-3-opus',
|
||||
}));
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
expect(config.provider).toBe('anthropic');
|
||||
expect(config.apiKey).toBe('test-key');
|
||||
expect(config.model).toBe('claude-3-opus');
|
||||
});
|
||||
|
||||
it('配置文件不存在时返回空对象', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
expect(config).toEqual({});
|
||||
});
|
||||
|
||||
it('配置文件解析错误时返回空对象', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('invalid json');
|
||||
|
||||
const config = getConfig();
|
||||
|
||||
expect(config).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadConfig - 加载完整配置', () => {
|
||||
it('从环境变量获取 Anthropic 配置', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'env-anthropic-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.provider).toBe('anthropic');
|
||||
expect(config.apiKey).toBe('env-anthropic-key');
|
||||
expect(config.model).toBe('claude-sonnet-4-20250514');
|
||||
});
|
||||
|
||||
it('从环境变量获取 DeepSeek 配置', () => {
|
||||
process.env.AI_PROVIDER = 'deepseek';
|
||||
process.env.DEEPSEEK_API_KEY = 'env-deepseek-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.provider).toBe('deepseek');
|
||||
expect(config.apiKey).toBe('env-deepseek-key');
|
||||
expect(config.model).toBe('deepseek-chat');
|
||||
});
|
||||
|
||||
it('配置文件优先级高于默认值', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'env-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
||||
model: 'custom-model',
|
||||
maxTokens: 8192,
|
||||
}));
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.model).toBe('custom-model');
|
||||
expect(config.maxTokens).toBe(8192);
|
||||
});
|
||||
|
||||
it('配置文件中的 provider 优先', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'anthropic-key';
|
||||
process.env.DEEPSEEK_API_KEY = 'deepseek-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
||||
provider: 'deepseek',
|
||||
deepseekApiKey: 'stored-deepseek-key',
|
||||
}));
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.provider).toBe('deepseek');
|
||||
// 使用环境变量中的 API Key(优先级更高)
|
||||
expect(config.apiKey).toBe('deepseek-key');
|
||||
});
|
||||
|
||||
it('包含系统提示词', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.systemPrompt).toBeDefined();
|
||||
expect(config.systemPrompt).toContain('终端');
|
||||
});
|
||||
|
||||
it('默认 maxTokens 为 4096', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-key';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.maxTokens).toBe(4096);
|
||||
});
|
||||
|
||||
it('环境变量设置的 maxTokens', () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-key';
|
||||
process.env.AI_MAX_TOKENS = '16384';
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
expect(config.maxTokens).toBe(16384);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveConfig - 保存配置', () => {
|
||||
it('创建目录并保存配置', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
saveConfig({ provider: 'anthropic', apiKey: 'new-key' });
|
||||
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.stringContaining('anthropic')
|
||||
);
|
||||
});
|
||||
|
||||
it('合并现有配置', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({
|
||||
provider: 'anthropic',
|
||||
apiKey: 'old-key',
|
||||
model: 'old-model',
|
||||
}));
|
||||
|
||||
saveConfig({ apiKey: 'new-key' });
|
||||
|
||||
const writeCall = vi.mocked(fs.writeFileSync).mock.calls[0];
|
||||
const savedConfig = JSON.parse(writeCall[1] as string);
|
||||
|
||||
expect(savedConfig.provider).toBe('anthropic'); // 保留
|
||||
expect(savedConfig.apiKey).toBe('new-key'); // 更新
|
||||
expect(savedConfig.model).toBe('old-model'); // 保留
|
||||
});
|
||||
|
||||
it('目录已存在时不重新创建', () => {
|
||||
vi.mocked(fs.existsSync)
|
||||
.mockReturnValueOnce(true) // 目录存在
|
||||
.mockReturnValueOnce(true); // 配置文件存在
|
||||
vi.mocked(fs.readFileSync).mockReturnValue('{}');
|
||||
|
||||
saveConfig({ apiKey: 'test' });
|
||||
|
||||
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user