Files
lingxi-ai/tests/server/profile-credentials.test.ts
T
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

206 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, writeFileSync, readFileSync, readdirSync, existsSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import {
isExclusivePlatformKey,
stripExclusivePlatformCredentials,
disableExclusivePlatformsInConfig,
EXCLUSIVE_PLATFORMS,
EXCLUSIVE_PLATFORM_ENV_PATTERNS,
} from '../../packages/server/src/services/hermes/profile-credentials'
let tmpDir: string
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'profile-cred-test-'))
})
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true })
})
describe('isExclusivePlatformKey', () => {
it('matches all known exclusive platform prefixes (aligned with hermes-agent gateway/platforms)', () => {
const samples = [
'TELEGRAM_BOT_TOKEN',
'DISCORD_BOT_TOKEN',
'SLACK_APP_TOKEN',
'WHATSAPP_PHONE_NUMBER_ID',
'SIGNAL_PHONE_NUMBER',
'WEIXIN_TOKEN', 'WEIXIN_ACCOUNT_ID',
'FEISHU_APP_ID',
]
for (const k of samples) {
expect(isExclusivePlatformKey(k)).toBe(true)
}
})
it('does not match removed aliases or non-lock platforms', () => {
// 这些前缀在 hermes-agent gateway/platforms/ 中没有 _acquire_platform_lock 调用
const nonLock = [
'WECHAT_APP_ID', // wechat 不是上游 platform key(实际是 weixin
'LARK_APP_SECRET', // lark 不是上游 platform key(实际是 feishu
'LINE_CHANNEL_SECRET', // line 在 hermes-agent 中没有 adapter
'MATTERMOST_TOKEN', 'MATRIX_TOKEN', 'DINGTALK_TOKEN',
'WECOM_TOKEN', 'QQBOT_TOKEN', 'BLUEBUBBLES_TOKEN',
]
for (const k of nonLock) {
expect(isExclusivePlatformKey(k)).toBe(false)
}
})
it('does not match model provider keys or generic config', () => {
const safe = [
'OPENAI_API_KEY',
'ANTHROPIC_API_KEY',
'GEMINI_API_KEY',
'DEEPSEEK_API_KEY',
'MINIMAX_API_KEY',
'DASHSCOPE_API_KEY',
'BROWSER_HEADLESS',
'TERMINAL_DEFAULT_SHELL',
'HERMES_MAX_ITERATIONS',
'PORT',
'NODE_ENV',
]
for (const k of safe) {
expect(isExclusivePlatformKey(k)).toBe(false)
}
})
})
describe('stripExclusivePlatformCredentials', () => {
it('returns empty when file does not exist', () => {
expect(stripExclusivePlatformCredentials(join(tmpDir, 'nope.env'))).toEqual([])
})
it('returns empty and does not write when no exclusive keys present', () => {
const p = join(tmpDir, '.env')
const content = 'OPENAI_API_KEY=sk-xxx\nPORT=8642\n'
writeFileSync(p, content)
expect(stripExclusivePlatformCredentials(p)).toEqual([])
expect(readFileSync(p, 'utf-8')).toBe(content)
// 无备份文件
expect(readdirSync(tmpDir).filter(f => f.startsWith('.env.bak'))).toHaveLength(0)
})
it('strips exclusive credentials, keeps safe ones, and creates a backup', () => {
const p = join(tmpDir, '.env')
writeFileSync(p, [
'# comment',
'OPENAI_API_KEY=sk-xxx',
'WEIXIN_TOKEN=secret-token',
'WEIXIN_ACCOUNT_ID=acct-1',
'TELEGRAM_BOT_TOKEN=tg-token',
'PORT=8642',
'',
].join('\n'))
const removed = stripExclusivePlatformCredentials(p)
expect(removed).toEqual(['WEIXIN_TOKEN', 'WEIXIN_ACCOUNT_ID', 'TELEGRAM_BOT_TOKEN'])
const after = readFileSync(p, 'utf-8')
expect(after).toContain('OPENAI_API_KEY=sk-xxx')
expect(after).toContain('PORT=8642')
expect(after).toContain('# comment')
expect(after).not.toContain('WEIXIN_')
expect(after).not.toContain('TELEGRAM_')
// 备份文件存在且与原始内容一致
const backups = readdirSync(tmpDir).filter(f => f.startsWith('.env.bak'))
expect(backups).toHaveLength(1)
const backupContent = readFileSync(join(tmpDir, backups[0]), 'utf-8')
expect(backupContent).toContain('WEIXIN_TOKEN=secret-token')
})
})
describe('disableExclusivePlatformsInConfig', () => {
it('returns empty when file does not exist', () => {
expect(disableExclusivePlatformsInConfig(join(tmpDir, 'nope.yaml')))
.toEqual({ disabled: [], strippedConfigCredentials: [] })
})
it('returns empty when no exclusive platforms enabled and no embedded credentials', () => {
const p = join(tmpDir, 'config.yaml')
writeFileSync(p, 'platforms:\n cli:\n enabled: true\n')
expect(disableExclusivePlatformsInConfig(p))
.toEqual({ disabled: [], strippedConfigCredentials: [] })
expect(readdirSync(tmpDir).filter(f => f.startsWith('config.yaml.bak'))).toHaveLength(0)
})
it('disables enabled exclusive platforms, strips embedded credentials, and backs up', () => {
const p = join(tmpDir, 'config.yaml')
writeFileSync(p, [
'platforms:',
' cli:',
' enabled: true',
' weixin:',
' enabled: true',
' token: secret',
' extra:',
' account_id: acct-1',
' app_id: app-1',
' telegram:',
' enabled: true',
' bot_token: tg-token',
' discord:',
' enabled: false',
'',
].join('\n'))
const result = disableExclusivePlatformsInConfig(p)
expect(result.disabled.sort()).toEqual(['telegram', 'weixin'])
// 节点直挂 + extra 子节点的凭据都应该被清掉
expect(result.strippedConfigCredentials.sort()).toEqual([
'telegram.bot_token',
'weixin.extra.account_id',
'weixin.extra.app_id',
'weixin.token',
])
const after = readFileSync(p, 'utf-8')
expect(after).toMatch(/weixin:[\s\S]*?enabled:\s*false/)
expect(after).toMatch(/telegram:[\s\S]*?enabled:\s*false/)
expect(after).toMatch(/cli:[\s\S]*?enabled:\s*true/)
// 凭据已被清除
expect(after).not.toContain('secret')
expect(after).not.toContain('tg-token')
expect(after).not.toContain('acct-1')
const backups = readdirSync(tmpDir).filter(f => f.startsWith('config.yaml.bak'))
expect(backups).toHaveLength(1)
})
it('strips embedded credentials even when platform is already disabled', () => {
const p = join(tmpDir, 'config.yaml')
writeFileSync(p, [
'platforms:',
' weixin:',
' enabled: false',
' token: leftover-secret',
'',
].join('\n'))
const result = disableExclusivePlatformsInConfig(p)
expect(result.disabled).toEqual([])
expect(result.strippedConfigCredentials).toEqual(['weixin.token'])
const after = readFileSync(p, 'utf-8')
expect(after).not.toContain('leftover-secret')
})
it('returns empty on malformed yaml without throwing', () => {
const p = join(tmpDir, 'config.yaml')
writeFileSync(p, 'platforms: [unclosed')
expect(disableExclusivePlatformsInConfig(p))
.toEqual({ disabled: [], strippedConfigCredentials: [] })
})
})
describe('EXCLUSIVE_PLATFORMS list', () => {
it('stays in sync with the env pattern list (same length)', () => {
expect(EXCLUSIVE_PLATFORMS.length).toBe(EXCLUSIVE_PLATFORM_ENV_PATTERNS.length)
})
})