test: 更新测试用例适配 provider 隔离与新 API

- providers-real.test.ts 重写适配 ModelMapping 对象格式
- e2e 测试适配 cc-haha/settings.json 路径
- settings.test.ts 验证写入隔离
- models.ts 小调整
This commit is contained in:
程序员阿江(Relakkes) 2026-04-15 14:19:04 +08:00
parent 0e60cadb13
commit 5fdf50436c
8 changed files with 196 additions and 185 deletions

View File

@ -57,8 +57,8 @@ const MOCK_AGENTS = [
{
agentType: 'doc-writer',
description: 'Writes technical documentation',
model: 'claude-haiku-4-5-20251001',
modelDisplay: 'claude-haiku-4-5-20251001',
model: 'claude-haiku-4-5',
modelDisplay: 'claude-haiku-4-5',
tools: ['Read'],
systemPrompt: 'You write clear and concise docs.',
color: 'green',

View File

@ -121,7 +121,7 @@ const FRONTIER_MODEL_NAME = 'Claude Opus 4.6'
const CLAUDE_4_5_OR_4_6_MODEL_IDS = {
opus: 'claude-opus-4-6',
sonnet: 'claude-sonnet-4-6',
haiku: 'claude-haiku-4-5-20251001',
haiku: 'claude-haiku-4-5',
}
function getHooksSection(): string {

View File

@ -1605,7 +1605,7 @@ async function run(): Promise<CommanderCommand> {
// `type: 'stdio'`. An enterprise-config ant with the GB gate on would
// otherwise process.exit(1). Chrome has the same latent issue but has
// shipped without incident; chicago places itself correctly.
if (getPlatform() === 'macos' && !getIsNonInteractiveSession()) {
if (getPlatform() === 'macos') {
try {
const {
getChicagoEnabled

View File

@ -63,7 +63,7 @@ describe('Business Flow: Scheduled Tasks', () => {
prompt: 'Look at git log from yesterday, summarize changes, list blockers',
recurring: true,
permissionMode: 'default',
model: 'claude-sonnet-4-6-20250514',
model: 'claude-sonnet-4-6',
folderPath: '/Users/dev/project',
useWorktree: true,
})
@ -75,7 +75,7 @@ describe('Business Flow: Scheduled Tasks', () => {
expect(data.task.prompt).toContain('git log')
expect(data.task.recurring).toBe(true)
expect(data.task.permissionMode).toBe('default')
expect(data.task.model).toBe('claude-sonnet-4-6-20250514')
expect(data.task.model).toBe('claude-sonnet-4-6')
expect(data.task.createdAt).toBeGreaterThan(0)
})
@ -223,7 +223,7 @@ describe('Business Flow: Agent Management', () => {
const { status, data } = await api('POST', '/api/agents', {
name: 'security-auditor',
description: 'Audits code for security vulnerabilities',
model: 'claude-opus-4-6-20250610',
model: 'claude-opus-4-6',
tools: ['Read', 'Grep', 'Glob', 'Bash'],
systemPrompt: 'You are a security expert. Focus on OWASP top 10.',
color: 'red',
@ -235,7 +235,7 @@ describe('Business Flow: Agent Management', () => {
const { status } = await api('POST', '/api/agents', {
name: 'test-writer',
description: 'Writes unit tests',
model: 'claude-sonnet-4-6-20250514',
model: 'claude-sonnet-4-6',
tools: ['Read', 'Write', 'Bash'],
})
expect(status).toBe(201)
@ -258,7 +258,7 @@ describe('Business Flow: Agent Management', () => {
const { data } = await api('GET', '/api/agents/security-auditor')
expect(data.agent.name).toBe('security-auditor')
expect(data.agent.description).toContain('security')
expect(data.agent.model).toBe('claude-opus-4-6-20250610')
expect(data.agent.model).toBe('claude-opus-4-6')
expect(data.agent.systemPrompt).toContain('OWASP')
})
@ -331,22 +331,22 @@ describe('Business Flow: Models & Effort', () => {
it('should default to Sonnet model', async () => {
const { data } = await api('GET', '/api/models/current')
expect(data.model.id).toBe('claude-sonnet-4-6-20250514')
expect(data.model.id).toBe('claude-sonnet-4-6')
})
it('should switch to Opus 4.6', async () => {
const { status } = await api('PUT', '/api/models/current', {
modelId: 'claude-opus-4-6-20250610',
modelId: 'claude-opus-4-6',
})
expect(status).toBe(200)
const { data } = await api('GET', '/api/models/current')
expect(data.model.id).toBe('claude-opus-4-6-20250610')
expect(data.model.id).toBe('claude-opus-4-6')
expect(data.model.name).toBe('Opus 4.6')
})
it('should switch to Haiku 4.5', async () => {
await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5-20251001' })
await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5' })
const { data } = await api('GET', '/api/models/current')
expect(data.model.name).toBe('Haiku 4.5')
})
@ -389,13 +389,13 @@ describe('Business Flow: Models & Effort', () => {
})
it('should persist model and effort to settings file', async () => {
await api('PUT', '/api/models/current', { modelId: 'claude-opus-4-6-20250610' })
await api('PUT', '/api/models/current', { modelId: 'claude-opus-4-6' })
await api('PUT', '/api/effort', { level: 'high' })
const settingsPath = path.join(tmpDir, 'settings.json')
const raw = await fs.readFile(settingsPath, 'utf-8')
const settings = JSON.parse(raw)
expect(settings.model).toBe('claude-opus-4-6-20250610')
expect(settings.model).toBe('claude-opus-4-6')
expect(settings.effort).toBe('high')
})
})
@ -716,7 +716,7 @@ describe('Business Flow: Settings Persistence', () => {
it('should write and read complex settings', async () => {
const settings = {
theme: 'dark',
model: 'claude-opus-4-6-20250610',
model: 'claude-opus-4-6',
effort: 'high',
outputStyle: 'verbose',
permissions: {
@ -729,7 +729,7 @@ describe('Business Flow: Settings Persistence', () => {
const { data } = await api('GET', '/api/settings/user')
expect(data.theme).toBe('dark')
expect(data.model).toBe('claude-opus-4-6-20250610')
expect(data.model).toBe('claude-opus-4-6')
expect(data.permissions.allow).toContain('Read')
expect(data.permissions.deny).toContain('Bash(rm -rf /)')
})
@ -758,7 +758,7 @@ describe('Business Flow: Settings Persistence', () => {
})
it('should merge user and project settings', async () => {
await api('PUT', '/api/settings/user', { theme: 'light', model: 'claude-sonnet-4-6-20250514' })
await api('PUT', '/api/settings/user', { theme: 'light', model: 'claude-sonnet-4-6' })
const { data } = await api('GET', '/api/settings')
expect(data.theme).toBeDefined()

View File

@ -133,11 +133,11 @@ describe('E2E: Full Flow', () => {
})
it('should update and read user settings', async () => {
await api('PUT', '/api/settings/user', { theme: 'dark', model: 'claude-sonnet-4-6-20250514' })
await api('PUT', '/api/settings/user', { theme: 'dark', model: 'claude-sonnet-4-6' })
const { data } = await api('GET', '/api/settings/user')
expect(data.theme).toBe('dark')
expect(data.model).toBe('claude-sonnet-4-6-20250514')
expect(data.model).toBe('claude-sonnet-4-6')
})
it('should get and set permission mode', async () => {
@ -163,10 +163,10 @@ describe('E2E: Full Flow', () => {
})
it('should switch model', async () => {
await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5-20251001' })
await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5' })
const { data } = await api('GET', '/api/models/current')
expect(data.model.id).toBe('claude-haiku-4-5-20251001')
expect(data.model.id).toBe('claude-haiku-4-5')
})
it('should get and set effort level', async () => {
@ -256,7 +256,7 @@ describe('E2E: Full Flow', () => {
const { status } = await api('POST', '/api/agents', {
name: 'test-agent',
description: 'A test agent',
model: 'claude-sonnet-4-6-20250514',
model: 'claude-sonnet-4-6',
})
expect(status).toBe(201)
})

View File

@ -1,6 +1,7 @@
/**
* Provider ProviderService
* settings.json
* cc-haha/settings.json
* (provider env ~/.claude/cc-haha/settings.json settings.json)
*/
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
@ -9,6 +10,13 @@ import * as path from 'path'
import * as os from 'os'
import { ProviderService } from '../services/providerService.js'
const MODEL_MAPPING = {
main: 'MiniMax-M2.7-highspeed',
haiku: 'MiniMax-M2.7-highspeed',
sonnet: 'MiniMax-M2.7-highspeed',
opus: 'MiniMax-M2.7-highspeed',
}
describe('Real Provider Configs', () => {
let tmpDir: string
let service: ProviderService
@ -24,204 +32,213 @@ describe('Real Provider Configs', () => {
await fs.rm(tmpDir, { recursive: true, force: true })
})
test('添加 MiniMax Provider 并激活', async () => {
// Helper: read the Haha-specific settings file
async function readCcHahaSettings(): Promise<Record<string, unknown>> {
const raw = await fs.readFile(path.join(tmpDir, 'cc-haha', 'settings.json'), 'utf-8')
return JSON.parse(raw)
}
// Helper: check original settings.json is NOT modified
async function originalSettingsExists(): Promise<boolean> {
try {
await fs.access(path.join(tmpDir, 'settings.json'))
return true
} catch {
return false
}
}
test('添加 MiniMax Provider 并激活 — 写入 cc-haha/settings.json', async () => {
const minimax = await service.addProvider({
presetId: 'minimax',
name: 'MiniMax',
baseUrl: 'https://api.minimaxi.com/anthropic',
apiKey: 'sk-fake-test-key-for-testing-only',
models: [
{ id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed', description: 'MiniMax 高速模型' },
],
models: MODEL_MAPPING,
notes: 'MiniMax 官方 Anthropic 兼容接口',
})
// 第一个 provider 应该自动激活
expect(minimax.isActive).toBe(true)
expect(minimax.name).toBe('MiniMax')
// 验证 settings.json 写入
const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic')
expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe(minimax.apiKey)
expect(settings.model).toBe('MiniMax-M2.7-highspeed')
// 激活 provider
await service.activateProvider(minimax.id)
console.log('✅ MiniMax Provider 添加并自动激活成功')
console.log(' settings.json env:', JSON.stringify(settings.env, null, 2))
console.log(' settings.json model:', settings.model)
// 验证写入 cc-haha/settings.json
const settings = await readCcHahaSettings()
expect((settings.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic')
expect((settings.env as Record<string, string>).ANTHROPIC_AUTH_TOKEN).toBe('sk-fake-test-key-for-testing-only')
expect((settings.env as Record<string, string>).ANTHROPIC_MODEL).toBe('MiniMax-M2.7-highspeed')
// 验证原版 settings.json 没有被创建
expect(await originalSettingsExists()).toBe(false)
console.log('✅ Provider 写入 cc-haha/settings.json原版 settings.json 未被污染')
})
test('添加接口AI中转站 Provider切换激活', async () => {
// 先添加 MiniMax自动激活
test('切换 Provider — 更新 cc-haha/settings.json', async () => {
const minimax = await service.addProvider({
presetId: 'minimax',
name: 'MiniMax',
baseUrl: 'https://api.minimaxi.com/anthropic',
apiKey: 'sk-api-test-minimax',
models: [
{ id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed' },
],
models: MODEL_MAPPING,
})
expect(minimax.isActive).toBe(true)
// 添加接口AI中转站不应自动激活
const jiekou = await service.addProvider({
presetId: 'custom',
name: '接口AI中转站',
baseUrl: 'https://api.jiekou.ai/anthropic',
apiKey: 'sk-fake-test-key-for-testing-only',
models: [
{ id: 'claude-opus-4-6', name: 'Opus 4.6', description: 'Most capable', context: '200k' },
{ id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', description: 'Most efficient', context: '200k' },
{ id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5', description: 'Fastest', context: '200k' },
],
notes: '接口AI中转站 — 支持多个 Claude 模型',
})
expect(jiekou.isActive).toBe(false)
// 激活接口AI中转站选择 Opus 4.6
await service.activateProvider(jiekou.id, 'claude-opus-4-6')
// 验证 providers.json
const providers = await service.listProviders()
const activeMinimax = providers.find(p => p.id === minimax.id)!
const activeJiekou = providers.find(p => p.id === jiekou.id)!
expect(activeMinimax.isActive).toBe(false)
expect(activeJiekou.isActive).toBe(true)
// 验证 settings.json
const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic')
expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe(jiekou.apiKey)
expect(settings.model).toBe('claude-opus-4-6')
console.log('✅ 接口AI中转站激活成功')
console.log(' settings.json env:', JSON.stringify(settings.env, null, 2))
console.log(' settings.json model:', settings.model)
})
test('切换模型 — 从 Opus 切到 Sonnet', async () => {
const jiekou = await service.addProvider({
name: '接口AI中转站',
baseUrl: 'https://api.jiekou.ai/anthropic',
apiKey: 'sk_test_jiekou',
models: [
{ id: 'claude-opus-4-6', name: 'Opus 4.6' },
{ id: 'claude-sonnet-4-6', name: 'Sonnet 4.6' },
{ id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5' },
],
models: {
main: 'claude-opus-4-6',
haiku: 'claude-haiku-4-5',
sonnet: 'claude-sonnet-4-6',
opus: 'claude-opus-4-6',
},
})
// 自动激活了第一个模型 opus
let settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
expect(settings.model).toBe('claude-opus-4-6')
// 先激活 MiniMax
await service.activateProvider(minimax.id)
let settings = await readCcHahaSettings()
expect((settings.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic')
// 切换到 Sonnet
await service.activateProvider(jiekou.id, 'claude-sonnet-4-6')
settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
expect(settings.model).toBe('claude-sonnet-4-6')
// 切换到接口AI中转站
await service.activateProvider(jiekou.id)
settings = await readCcHahaSettings()
expect((settings.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic')
expect((settings.env as Record<string, string>).ANTHROPIC_AUTH_TOKEN).toBe('sk-fake-test-key-for-testing-only')
expect((settings.env as Record<string, string>).ANTHROPIC_MODEL).toBe('claude-opus-4-6')
console.log('✅ 模型切换成功: opus → sonnet')
// 验证 activeId 正确
const list = await service.listProviders()
expect(list.activeId).toBe(jiekou.id)
// 原版 settings.json 依然不存在
expect(await originalSettingsExists()).toBe(false)
console.log('✅ 切换 Provider 成功cc-haha/settings.json 更新正确')
})
test('settings.json 保留已有字段', async () => {
// 预写一个有内容的 settings.json模拟用户已有配置
test('cc-haha/settings.json 保留已有字段', async () => {
// 预写一个有内容的 cc-haha/settings.json模拟用户已有配置
await fs.mkdir(path.join(tmpDir, 'cc-haha'), { recursive: true })
await fs.writeFile(
path.join(tmpDir, 'settings.json'),
path.join(tmpDir, 'cc-haha', 'settings.json'),
JSON.stringify({
effortLevel: 'high',
language: '中文',
skipDangerousModePermissionPrompt: true,
customField: 'should_be_preserved',
env: {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
EXISTING_VAR: 'should_be_preserved',
},
hooks: {
Stop: [{ hooks: [{ command: 'echo done', type: 'command' }], matcher: '' }],
},
}, null, 2),
)
// 添加并激活 provider
await service.addProvider({
const provider = await service.addProvider({
presetId: 'custom',
name: '接口AI中转站',
baseUrl: 'https://api.jiekou.ai/anthropic',
apiKey: 'sk_test',
models: [{ id: 'claude-opus-4-6', name: 'Opus 4.6' }],
models: {
main: 'claude-opus-4-6',
haiku: 'claude-haiku-4-5',
sonnet: 'claude-sonnet-4-6',
opus: 'claude-opus-4-6',
},
})
await service.activateProvider(provider.id)
const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
const settings = await readCcHahaSettings()
// 验证新字段写入
expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic')
expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe('sk_test')
expect(settings.model).toBe('claude-opus-4-6')
expect((settings.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic')
expect((settings.env as Record<string, string>).ANTHROPIC_AUTH_TOKEN).toBe('sk_test')
// 验证已有字段保留
expect(settings.effortLevel).toBe('high')
expect(settings.language).toBe('中文')
expect(settings.skipDangerousModePermissionPrompt).toBe(true)
expect(settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).toBe('1')
expect(settings.env.EXISTING_VAR).toBe('should_be_preserved')
expect(settings.hooks).toBeDefined()
expect(settings.hooks.Stop).toHaveLength(1)
expect(settings.customField).toBe('should_be_preserved')
expect((settings.env as Record<string, string>).EXISTING_VAR).toBe('should_be_preserved')
console.log('✅ settings.json 已有字段全部保留')
console.log(' 完整 settings:', JSON.stringify(settings, null, 2))
console.log('✅ cc-haha/settings.json 已有字段全部保留')
})
test('连通性测试 — MiniMax预期能连但可能 401', async () => {
// 使用假 key 测试连通性机制本身
test('activateOfficial 清除 provider env', async () => {
const provider = await service.addProvider({
presetId: 'minimax',
name: 'MiniMax',
baseUrl: 'https://api.minimaxi.com/anthropic',
apiKey: 'sk-test',
models: MODEL_MAPPING,
})
await service.activateProvider(provider.id)
// 确认写入了
let settings = await readCcHahaSettings()
expect((settings.env as Record<string, string>).ANTHROPIC_BASE_URL).toBeDefined()
// 切换到 official
await service.activateOfficial()
settings = await readCcHahaSettings()
const env = settings.env as Record<string, string> | undefined
expect(env?.ANTHROPIC_BASE_URL).toBeUndefined()
expect(env?.ANTHROPIC_AUTH_TOKEN).toBeUndefined()
expect(env?.ANTHROPIC_MODEL).toBeUndefined()
console.log('✅ activateOfficial 正确清除了 provider env')
})
test('连通性测试 — 返回结构正确', async () => {
const result = await service.testProviderConfig({
baseUrl: 'https://api.minimaxi.com/anthropic',
apiKey: 'sk-fake-test-key',
modelId: 'MiniMax-M2.7-highspeed',
})
console.log('🔌 MiniMax 连通性测试结果:')
console.log(' success:', result.success)
console.log(' latencyMs:', result.latencyMs)
console.log(' httpStatus:', result.httpStatus)
console.log(' error:', result.error)
console.log(' modelUsed:', result.modelUsed)
// testProviderConfig 返回 { connectivity: { ... }, proxy?: { ... } }
expect(result.connectivity).toBeDefined()
expect(result.connectivity.latencyMs).toBeGreaterThanOrEqual(0)
expect(result.connectivity.modelUsed).toBe('MiniMax-M2.7-highspeed')
// 不断言成功/失败,因为假 key 肯定失败,但验证机制正常工作
expect(result.latencyMs).toBeGreaterThanOrEqual(0)
expect(result.modelUsed).toBe('MiniMax-M2.7-highspeed')
console.log('🔌 MiniMax 连通性测试结果:')
console.log(' success:', result.connectivity.success)
console.log(' latencyMs:', result.connectivity.latencyMs)
console.log(' error:', result.connectivity.error)
})
test('GAP 分析 — 用户配置中我们未覆盖的字段', () => {
// 用户的 MiniMax 配置包含我们未处理的字段:
const minimaxConfig = {
env: {
ANTHROPIC_AUTH_TOKEN: 'sk-...',
ANTHROPIC_BASE_URL: 'https://api.minimaxi.com/anthropic',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持
ANTHROPIC_DEFAULT_OPUS_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持
ANTHROPIC_DEFAULT_SONNET_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持
ANTHROPIC_MODEL: 'MiniMax-M2.7-highspeed',
API_TIMEOUT_MS: '3000000', // ❌ 未支持
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1', // ❌ 未支持
},
skipDangerousModePermissionPrompt: true, // ❌ 未支持
}
test('providers.json 和 cc-haha/settings.json 独立于 settings.json', async () => {
// 模拟原版 Claude Code 的 settings.json 已存在
await fs.writeFile(
path.join(tmpDir, 'settings.json'),
JSON.stringify({
effortLevel: 'high',
env: {
ANTHROPIC_BASE_URL: 'https://original-claude-code.api.com',
ANTHROPIC_AUTH_TOKEN: 'original-key',
},
}, null, 2),
)
// 用户的接口AI配置包含更多未覆盖字段:
const jiekouConfig = {
effortLevel: 'high', // ❌ 未同步
enabledPlugins: { /* ... */ }, // ❌ 未同步
env: {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1', // ❌ 未支持
},
hooks: { /* ... */ }, // ❌ 未同步
}
// Haha 添加并激活自己的 provider
const provider = await service.addProvider({
presetId: 'minimax',
name: 'MiniMax',
baseUrl: 'https://api.minimaxi.com/anthropic',
apiKey: 'sk-haha-key',
models: MODEL_MAPPING,
})
await service.activateProvider(provider.id)
// 打印 GAP 分析
console.log('\n📋 GAP 分析 — 需要在 syncToSettings 中额外支持的字段:')
console.log(' 1. ANTHROPIC_DEFAULT_HAIKU_MODEL — 各模型的 tier 默认值')
console.log(' 2. ANTHROPIC_DEFAULT_SONNET_MODEL')
console.log(' 3. ANTHROPIC_DEFAULT_OPUS_MODEL')
console.log(' 4. API_TIMEOUT_MS — 超时配置')
console.log(' 5. 其他自定义 env vars应支持 extraEnv 字段)')
console.log(' 6. skipDangerousModePermissionPrompt — 非 env 的额外 settings 字段')
// 验证原版 settings.json 没被修改
const original = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8'))
expect((original.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://original-claude-code.api.com')
expect((original.env as Record<string, string>).ANTHROPIC_AUTH_TOKEN).toBe('original-key')
expect(original.effortLevel).toBe('high')
expect(true).toBe(true) // GAP 分析通过
// 验证 cc-haha/settings.json 是 Haha 自己的
const haha = await readCcHahaSettings()
expect((haha.env as Record<string, string>).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic')
expect((haha.env as Record<string, string>).ANTHROPIC_AUTH_TOKEN).toBe('sk-haha-key')
console.log('✅ 原版 settings.json 完好无损Haha 配置独立存储')
})
})

View File

@ -64,21 +64,21 @@ describe('SettingsService', () => {
it('should write and read user settings', async () => {
const svc = new SettingsService()
await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6-20250610' })
await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6' })
const settings = await svc.getUserSettings()
expect(settings.theme).toBe('dark')
expect(settings.model).toBe('claude-opus-4-6-20250610')
expect(settings.model).toBe('claude-opus-4-6')
})
it('should merge settings on update (shallow merge)', async () => {
const svc = new SettingsService()
await svc.updateUserSettings({ theme: 'dark' })
await svc.updateUserSettings({ model: 'claude-haiku-4-5-20251001' })
await svc.updateUserSettings({ model: 'claude-haiku-4-5' })
const settings = await svc.getUserSettings()
expect(settings.theme).toBe('dark')
expect(settings.model).toBe('claude-haiku-4-5-20251001')
expect(settings.model).toBe('claude-haiku-4-5')
})
it('should read and write project settings', async () => {
@ -97,14 +97,14 @@ describe('SettingsService', () => {
await fs.mkdir(path.join(projectRoot, '.claude'), { recursive: true })
const svc = new SettingsService(projectRoot)
await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6-20250610' })
await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6' })
await svc.updateProjectSettings({ theme: 'light' })
const merged = await svc.getSettings()
// project overrides user
expect(merged.theme).toBe('light')
// user value preserved when not overridden
expect(merged.model).toBe('claude-opus-4-6-20250610')
expect(merged.model).toBe('claude-opus-4-6')
})
it('should get default permission mode', async () => {
@ -168,7 +168,7 @@ describe('Settings API', () => {
it('PUT /api/settings/user should update user settings', async () => {
const { req, url, segments } = makeRequest('PUT', '/api/settings/user', {
model: 'claude-opus-4-6-20250610',
model: 'claude-opus-4-6',
})
const res = await handleSettingsApi(req, url, segments)
@ -180,7 +180,7 @@ describe('Settings API', () => {
const { req: r2, url: u2, segments: s2 } = makeRequest('GET', '/api/settings/user')
const res2 = await handleSettingsApi(r2, u2, s2)
const body2 = await res2.json()
expect(body2.model).toBe('claude-opus-4-6-20250610')
expect(body2.model).toBe('claude-opus-4-6')
})
it('GET /api/permissions/mode should return default mode', async () => {
@ -245,25 +245,25 @@ describe('Models API', () => {
expect(res.status).toBe(200)
const body = await res.json()
expect(body.model.id).toBe('claude-sonnet-4-6-20250514')
expect(body.model.id).toBe('claude-sonnet-4-6')
})
it('PUT /api/models/current should switch model', async () => {
const { req, url, segments } = makeRequest('PUT', '/api/models/current', {
modelId: 'claude-opus-4-6-20250610',
modelId: 'claude-opus-4-6',
})
const res = await handleModelsApi(req, url, segments)
expect(res.status).toBe(200)
const body = await res.json()
expect(body.ok).toBe(true)
expect(body.model).toBe('claude-opus-4-6-20250610')
expect(body.model).toBe('claude-opus-4-6')
// Verify persisted
const { req: r2, url: u2, segments: s2 } = makeRequest('GET', '/api/models/current')
const res2 = await handleModelsApi(r2, u2, s2)
const body2 = await res2.json()
expect(body2.model.id).toBe('claude-opus-4-6-20250610')
expect(body2.model.id).toBe('claude-opus-4-6')
})
it('PUT /api/models/current should reject missing modelId', async () => {

View File

@ -16,25 +16,19 @@ import { ApiError, errorResponse } from '../middleware/errorHandler.js'
const DEFAULT_MODELS = [
{
id: 'claude-opus-4-6-20250610',
id: 'claude-opus-4-6',
name: 'Opus 4.6',
description: 'Most capable for ambitious work',
context: '200k',
},
{
id: 'claude-opus-4-6-20250610:1m',
name: 'Opus 4.6 1M',
description: 'Most capable for ambitious work',
context: '1m',
},
{
id: 'claude-sonnet-4-6-20250514',
id: 'claude-sonnet-4-6',
name: 'Sonnet 4.6',
description: 'Most efficient for everyday tasks',
context: '200k',
},
{
id: 'claude-haiku-4-5-20251001',
id: 'claude-haiku-4-5',
name: 'Haiku 4.5',
description: 'Fastest for quick answers',
context: '200k',
@ -43,8 +37,8 @@ const DEFAULT_MODELS = [
const EFFORT_LEVELS = ['low', 'medium', 'high', 'max'] as const
const DEFAULT_MODEL = 'claude-sonnet-4-6-20250514'
const DEFAULT_EFFORT = 'medium'
const DEFAULT_MODEL = 'claude-opus-4-6'
const DEFAULT_EFFORT = 'max'
const settingsService = new SettingsService()
const providerService = new ProviderService()