Files
lingxi-ai/tests/server/config-helpers-file-lock.test.ts
yi 7d10320a82
Build / build (push) Has been cancelled
NPM Lockfile Check / npm ci --ignore-scripts (push) Has been cancelled
Playwright / e2e (push) Has been cancelled
feat: 灵犀 Studio Web UI 定制版
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 11:29:11 +08:00

147 lines
4.9 KiB
TypeScript

import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import YAML from 'js-yaml'
const originalHermesHome = process.env.HERMES_HOME
const tempHomes: string[] = []
let hermesHome = ''
async function loadHelpers() {
vi.resetModules()
process.env.HERMES_HOME = hermesHome
return import('../../packages/server/src/services/config-helpers')
}
beforeEach(async () => {
hermesHome = await mkdtemp(join(tmpdir(), 'hermes-config-helpers-'))
tempHomes.push(hermesHome)
await mkdir(hermesHome, { recursive: true })
})
afterEach(async () => {
vi.resetModules()
if (originalHermesHome === undefined) delete process.env.HERMES_HOME
else process.env.HERMES_HOME = originalHermesHome
await Promise.all(tempHomes.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
hermesHome = ''
})
describe('config-helpers locked file updates', () => {
it('merges concurrent config.yaml updates by re-reading under the file lock', async () => {
await writeFile(join(hermesHome, 'config.yaml'), 'model:\n default: old\n', 'utf-8')
const { updateConfigYaml } = await loadHelpers()
await Promise.all([
updateConfigYaml(async (cfg) => {
await new Promise(resolve => setTimeout(resolve, 25))
cfg.model.default = 'glm-5.1'
return cfg
}),
updateConfigYaml((cfg) => {
cfg.platforms = cfg.platforms || {}
cfg.platforms.api_server = { extra: { port: 8648 } }
return cfg
}),
])
const config = YAML.load(await readFile(join(hermesHome, 'config.yaml'), 'utf-8')) as any
expect(config.model.default).toBe('glm-5.1')
expect(config.platforms.api_server.extra.port).toBe(8648)
await expect(readFile(join(hermesHome, 'config.yaml.bak'), 'utf-8')).resolves.toContain('model:')
})
it('serializes concurrent .env updates without losing keys', async () => {
await writeFile(join(hermesHome, '.env'), 'OPENROUTER_API_KEY=keep\n', 'utf-8')
const { saveEnvValue } = await loadHelpers()
await Promise.all([
saveEnvValue('DEEPSEEK_API_KEY', 'deepseek'),
saveEnvValue('MOONSHOT_API_KEY', 'moonshot'),
])
const env = await readFile(join(hermesHome, '.env'), 'utf-8')
expect(env).toContain('OPENROUTER_API_KEY=keep')
expect(env).toContain('DEEPSEEK_API_KEY=deepseek')
expect(env).toContain('MOONSHOT_API_KEY=moonshot')
})
it('rejects invalid .env keys instead of writing keyless lines', async () => {
const envPath = join(hermesHome, '.env')
await writeFile(envPath, 'OPENROUTER_API_KEY=keep\n', 'utf-8')
const { saveEnvValue } = await loadHelpers()
await expect(saveEnvValue('', 'leaked-value')).rejects.toThrow('Invalid .env key')
await expect(saveEnvValue('=BROKEN', 'leaked-value')).rejects.toThrow('Invalid .env key')
const env = await readFile(envPath, 'utf-8')
expect(env).toBe('OPENROUTER_API_KEY=keep\n')
expect(env).not.toContain('=leaked-value')
})
it('skips writing config.yaml when an updater returns write false', async () => {
const configPath = join(hermesHome, 'config.yaml')
await writeFile(configPath, 'model:\n default: old\n', 'utf-8')
const before = await readFile(configPath, 'utf-8')
const { updateConfigYaml } = await loadHelpers()
const result = await updateConfigYaml((cfg) => ({ data: cfg, result: 'unchanged', write: false }))
expect(result).toBe('unchanged')
await expect(readFile(configPath, 'utf-8')).resolves.toBe(before)
await expect(readFile(`${configPath}.bak`, 'utf-8')).rejects.toMatchObject({ code: 'ENOENT' })
})
it('strips api_server config before gateway restart', async () => {
const { stripLegacyApiServerGatewayConfig } = await loadHelpers()
const result = stripLegacyApiServerGatewayConfig({
model: { default: 'glm-5.1' },
platforms: {
api_server: {
enabled: true,
key: '',
cors_origins: '*',
extra: {
port: 8642,
host: '127.0.0.1',
},
},
feishu: {
enabled: true,
},
},
})
expect(result.changed).toBe(true)
expect(result.config).toEqual({
model: { default: 'glm-5.1' },
platforms: {
feishu: {
enabled: true,
},
},
})
})
it('removes custom api_server fields as well', async () => {
const { stripLegacyApiServerGatewayConfig } = await loadHelpers()
const result = stripLegacyApiServerGatewayConfig({
platforms: {
api_server: {
key: 'custom-key',
cors_origins: 'https://example.com',
extra: {
port: 8642,
host: '127.0.0.1',
mode: 'custom',
},
},
},
})
expect(result.changed).toBe(true)
expect(result.config).toEqual({})
})
})