mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
This merge carries the worktree fix into the local main branch so long multi-session desktop usage keeps rendering cost bounded while DeepSeek follows the shared General Settings Thinking toggle. It preserves running-session behavior and keeps legacy DeepSeek managed env from pinning thinking off after upgrade. Constraint: User requested the worktree result merged into local main without pushing. Confidence: high Scope-risk: moderate Directive: Keep DeepSeek controlled by the global Thinking setting; do not restore provider-specific disabled-thinking defaults without both toggle-state tests. Tested: bun test src/server/__tests__/conversations.test.ts -t "global Thinking setting control DeepSeek" Tested: bun test src/server/__tests__/persistence-upgrade.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/title-service.test.ts src/utils/__tests__/thinking.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts Tested: cd desktop && bun run test -- MessageList.test.tsx ContextUsageIndicator.test.tsx Tested: bun run check:server
281 lines
10 KiB
TypeScript
281 lines
10 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
|
import * as fs from 'fs/promises'
|
|
import * as os from 'os'
|
|
import * as path from 'path'
|
|
import { ProviderService } from '../services/providerService.js'
|
|
import {
|
|
CURRENT_PROVIDER_INDEX_SCHEMA_VERSION,
|
|
ensurePersistentStorageUpgraded,
|
|
resetPersistentStorageMigrationsForTests,
|
|
} from '../services/persistentStorageMigrations.js'
|
|
|
|
let tempDir: string
|
|
|
|
async function listFiles(dir: string) {
|
|
try {
|
|
return await fs.readdir(dir)
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
describe('persistent storage upgrade migrations', () => {
|
|
beforeEach(async () => {
|
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cc-haha-persistence-'))
|
|
process.env.CLAUDE_CONFIG_DIR = tempDir
|
|
resetPersistentStorageMigrationsForTests()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
resetPersistentStorageMigrationsForTests()
|
|
delete process.env.CLAUDE_CONFIG_DIR
|
|
await fs.rm(tempDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('migrates legacy providers index and writes a backup before changing it', async () => {
|
|
const ccHahaDir = path.join(tempDir, 'cc-haha')
|
|
await fs.mkdir(ccHahaDir, { recursive: true })
|
|
await fs.writeFile(
|
|
path.join(ccHahaDir, 'providers.json'),
|
|
JSON.stringify({
|
|
activeProviderId: 'provider-1',
|
|
rootFutureField: { keep: true },
|
|
providers: [{
|
|
id: 'provider-1',
|
|
presetId: 'custom',
|
|
name: 'Legacy Provider',
|
|
apiKey: 'token',
|
|
baseUrl: 'https://example.test',
|
|
models: { main: 'model-main', haiku: '', sonnet: '', opus: '' },
|
|
extraFutureField: 'keep-me',
|
|
}],
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
expect(report.migratedEntries).toContain('cc-haha/providers.json')
|
|
|
|
const migrated = JSON.parse(await fs.readFile(path.join(ccHahaDir, 'providers.json'), 'utf-8')) as {
|
|
schemaVersion?: number
|
|
activeId?: string | null
|
|
activeProviderId?: string
|
|
rootFutureField?: unknown
|
|
providers?: Array<Record<string, unknown>>
|
|
}
|
|
expect(migrated.schemaVersion).toBe(CURRENT_PROVIDER_INDEX_SCHEMA_VERSION)
|
|
expect(migrated.activeId).toBe('provider-1')
|
|
expect(migrated.activeProviderId).toBeUndefined()
|
|
expect(migrated.rootFutureField).toEqual({ keep: true })
|
|
expect(migrated.providers?.[0]?.extraFutureField).toBe('keep-me')
|
|
|
|
const backups = (await listFiles(ccHahaDir)).filter((file) => file.startsWith('providers.json.bak-before-migration-'))
|
|
expect(backups.length).toBe(1)
|
|
|
|
const service = new ProviderService()
|
|
const { providers, activeId } = await service.listProviders()
|
|
expect(providers).toHaveLength(1)
|
|
expect(activeId).toBe('provider-1')
|
|
|
|
await service.updateProvider('provider-1', { name: 'Renamed Provider' })
|
|
const rewritten = JSON.parse(await fs.readFile(path.join(ccHahaDir, 'providers.json'), 'utf-8')) as {
|
|
rootFutureField?: unknown
|
|
providers?: Array<Record<string, unknown>>
|
|
}
|
|
expect(rewritten.rootFutureField).toEqual({ keep: true })
|
|
expect(rewritten.providers?.[0]?.extraFutureField).toBe('keep-me')
|
|
})
|
|
|
|
test('imports legacy root providers config into cc-haha storage without deleting the source', async () => {
|
|
await fs.writeFile(
|
|
path.join(tempDir, 'providers.json'),
|
|
JSON.stringify({
|
|
version: 1,
|
|
activeModel: 'legacy-sonnet',
|
|
providers: [{
|
|
id: 'legacy-provider',
|
|
name: 'Legacy Root Provider',
|
|
baseUrl: 'https://legacy.example.test',
|
|
apiKey: 'legacy-token',
|
|
models: [
|
|
{ id: 'legacy-haiku', name: 'Legacy Haiku' },
|
|
{ id: 'legacy-sonnet', name: 'Legacy Sonnet' },
|
|
],
|
|
isActive: true,
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
notes: 'keep note',
|
|
}],
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
expect(report.migratedEntries).toContain('providers.json -> cc-haha/providers.json')
|
|
expect(report.migratedEntries).toContain('providers.json -> cc-haha/settings.json')
|
|
expect(JSON.parse(await fs.readFile(path.join(tempDir, 'providers.json'), 'utf-8'))).toMatchObject({
|
|
version: 1,
|
|
activeModel: 'legacy-sonnet',
|
|
})
|
|
|
|
const migrated = JSON.parse(await fs.readFile(path.join(tempDir, 'cc-haha', 'providers.json'), 'utf-8')) as {
|
|
activeId?: string | null
|
|
providers?: Array<{
|
|
id?: string
|
|
presetId?: string
|
|
apiFormat?: string
|
|
models?: Record<string, string>
|
|
notes?: string
|
|
}>
|
|
}
|
|
expect(migrated.activeId).toBe('legacy-provider')
|
|
expect(migrated.providers?.[0]).toMatchObject({
|
|
id: 'legacy-provider',
|
|
presetId: 'custom',
|
|
apiFormat: 'anthropic',
|
|
notes: 'keep note',
|
|
models: {
|
|
main: 'legacy-sonnet',
|
|
haiku: 'legacy-sonnet',
|
|
sonnet: 'legacy-sonnet',
|
|
opus: 'legacy-sonnet',
|
|
},
|
|
})
|
|
|
|
const managedSettings = JSON.parse(await fs.readFile(path.join(tempDir, 'cc-haha', 'settings.json'), 'utf-8')) as {
|
|
env?: Record<string, string>
|
|
}
|
|
expect(managedSettings.env).toMatchObject({
|
|
ANTHROPIC_BASE_URL: 'https://legacy.example.test',
|
|
ANTHROPIC_AUTH_TOKEN: 'legacy-token',
|
|
ANTHROPIC_MODEL: 'legacy-sonnet',
|
|
})
|
|
|
|
const service = new ProviderService()
|
|
const { providers, activeId } = await service.listProviders()
|
|
expect(activeId).toBe('legacy-provider')
|
|
expect(providers[0]?.models.main).toBe('legacy-sonnet')
|
|
})
|
|
|
|
test('does not overwrite current cc-haha provider storage with a legacy root config', async () => {
|
|
const ccHahaDir = path.join(tempDir, 'cc-haha')
|
|
await fs.mkdir(ccHahaDir, { recursive: true })
|
|
await fs.writeFile(
|
|
path.join(tempDir, 'providers.json'),
|
|
JSON.stringify({
|
|
version: 1,
|
|
activeModel: 'legacy-model',
|
|
providers: [{
|
|
id: 'legacy-provider',
|
|
name: 'Legacy Root Provider',
|
|
baseUrl: 'https://legacy.example.test',
|
|
apiKey: 'legacy-token',
|
|
models: [{ id: 'legacy-model' }],
|
|
isActive: true,
|
|
}],
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
await fs.writeFile(
|
|
path.join(ccHahaDir, 'providers.json'),
|
|
JSON.stringify({
|
|
schemaVersion: CURRENT_PROVIDER_INDEX_SCHEMA_VERSION,
|
|
activeId: null,
|
|
providers: [],
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
expect(report.migratedEntries).not.toContain('providers.json -> cc-haha/providers.json')
|
|
const current = JSON.parse(await fs.readFile(path.join(ccHahaDir, 'providers.json'), 'utf-8')) as {
|
|
activeId?: string | null
|
|
providers?: unknown[]
|
|
}
|
|
expect(current.activeId).toBeNull()
|
|
expect(current.providers).toEqual([])
|
|
})
|
|
|
|
test('does not write repo-owned schema metadata into shared user settings', async () => {
|
|
await fs.writeFile(
|
|
path.join(tempDir, 'settings.json'),
|
|
JSON.stringify({
|
|
defaultMode: 'acceptEdits',
|
|
userOwnedFutureField: { nested: true },
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
const settings = JSON.parse(await fs.readFile(path.join(tempDir, 'settings.json'), 'utf-8')) as Record<string, unknown>
|
|
expect(settings.schemaVersion).toBeUndefined()
|
|
expect(settings.userOwnedFutureField).toEqual({ nested: true })
|
|
})
|
|
|
|
test('quarantines malformed managed settings instead of blocking startup', async () => {
|
|
const ccHahaDir = path.join(tempDir, 'cc-haha')
|
|
await fs.mkdir(ccHahaDir, { recursive: true })
|
|
await fs.writeFile(path.join(ccHahaDir, 'settings.json'), '{"env":', 'utf-8')
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
expect(report.migratedEntries).toContain('cc-haha/settings.json')
|
|
expect(JSON.parse(await fs.readFile(path.join(ccHahaDir, 'settings.json'), 'utf-8'))).toEqual({})
|
|
const quarantined = (await listFiles(ccHahaDir)).filter((file) => file.startsWith('settings.json.invalid-'))
|
|
expect(quarantined.length).toBe(1)
|
|
})
|
|
|
|
test('upgrades existing DeepSeek managed env to follow global thinking settings', async () => {
|
|
const ccHahaDir = path.join(tempDir, 'cc-haha')
|
|
await fs.mkdir(ccHahaDir, { recursive: true })
|
|
await fs.writeFile(
|
|
path.join(ccHahaDir, 'settings.json'),
|
|
JSON.stringify({
|
|
env: {
|
|
ANTHROPIC_BASE_URL: 'https://api.deepseek.com/anthropic',
|
|
ANTHROPIC_AUTH_TOKEN: 'test-token',
|
|
ANTHROPIC_MODEL: 'deepseek-v4-pro',
|
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'deepseek-v4-flash',
|
|
ANTHROPIC_DEFAULT_SONNET_MODEL: 'deepseek-v4-pro',
|
|
ANTHROPIC_DEFAULT_OPUS_MODEL: 'deepseek-v4-pro',
|
|
CC_HAHA_SEND_DISABLED_THINKING: '1',
|
|
USER_CUSTOM_ENV: 'keep-me',
|
|
},
|
|
}, null, 2),
|
|
'utf-8',
|
|
)
|
|
|
|
const report = await ensurePersistentStorageUpgraded()
|
|
|
|
expect(report.failures).toEqual([])
|
|
expect(report.migratedEntries).toContain('cc-haha/settings.json')
|
|
|
|
const migrated = JSON.parse(await fs.readFile(path.join(ccHahaDir, 'settings.json'), 'utf-8')) as {
|
|
env?: Record<string, string>
|
|
}
|
|
expect(migrated.env?.CC_HAHA_SEND_DISABLED_THINKING).toBeUndefined()
|
|
expect(migrated.env?.ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES).toBe(
|
|
'thinking,effort,adaptive_thinking,max_effort',
|
|
)
|
|
expect(migrated.env?.ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES).toBe(
|
|
'thinking,effort,adaptive_thinking,max_effort',
|
|
)
|
|
expect(migrated.env?.ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES).toBe(
|
|
'thinking,effort,adaptive_thinking,max_effort',
|
|
)
|
|
expect(migrated.env?.USER_CUSTOM_ENV).toBe('keep-me')
|
|
|
|
const backups = (await listFiles(ccHahaDir)).filter((file) => file.startsWith('settings.json.bak-before-migration-'))
|
|
expect(backups.length).toBe(1)
|
|
})
|
|
})
|