Files
MyGoNavi/frontend/src/utils/mcpClientInstallStatus.test.ts
Syngnat 450d1d66b4 feat(ai): 完善远程 MCP 结构模式与面板稳定性
- MCP HTTP 支持 schema-only 模式,远程配置默认不暴露 execute_sql

- OpenClaw/Hermans 向导补充安全边界与结构模式命令

- 拆分 AI 面板错误边界和 Linux CJK 字体提示组件
2026-06-11 09:26:54 +08:00

164 lines
6.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { AIMCPClientInstallStatus } from '../types';
import {
buildRemoteMCPClientGuide,
buildRemoteMCPClientQuickStart,
EMPTY_MCP_CLIENT_STATUSES,
formatMCPLaunchCommand,
isRemoteMCPClientStatus,
normalizeMCPClientStatuses,
pickPreferredMCPClient,
} from './mcpClientInstallStatus';
describe('mcpClientInstallStatus helpers', () => {
it('fills missing clients with default placeholder statuses', () => {
const statuses = normalizeMCPClientStatuses([
{
client: 'codex',
displayName: 'Codex',
installed: true,
matchesCurrent: true,
message: '已检测到 Codex 用户级 GoNavi MCP 配置,且与当前 GoNavi 安装路径一致',
},
]);
expect(statuses).toEqual([
EMPTY_MCP_CLIENT_STATUSES[0],
{
client: 'codex',
displayName: 'Codex',
installMode: 'auto',
installed: true,
matchesCurrent: true,
clientDetected: false,
clientCommand: 'codex',
clientPath: '',
message: '已检测到 Codex 用户级 GoNavi MCP 配置,且与当前 GoNavi 安装路径一致',
args: [],
},
EMPTY_MCP_CLIENT_STATUSES[2],
EMPTY_MCP_CLIENT_STATUSES[3],
]);
});
it('prefers an already-installed but outdated client over a completely uninstalled one', () => {
const statuses: AIMCPClientInstallStatus[] = [
{
client: 'claude-code',
displayName: 'Claude Code',
installed: false,
matchesCurrent: false,
message: '未检测到 Claude Code 用户级 GoNavi MCP 配置',
},
{
client: 'codex',
displayName: 'Codex',
installed: true,
matchesCurrent: false,
message: '已检测到 Codex 中的 GoNavi MCP 记录,但与当前 GoNavi 安装路径不一致,建议更新',
},
];
expect(pickPreferredMCPClient(statuses)).toBe('codex');
});
it('prefers a locally detected client command when neither client has existing GoNavi MCP config', () => {
const statuses: AIMCPClientInstallStatus[] = [
{
client: 'claude-code',
displayName: 'Claude Code',
installed: false,
matchesCurrent: false,
clientDetected: false,
clientCommand: 'claude',
message: '未检测到 Claude Code 用户级 GoNavi MCP 配置',
},
{
client: 'codex',
displayName: 'Codex',
installed: false,
matchesCurrent: false,
clientDetected: true,
clientCommand: 'codex',
clientPath: 'C:/Users/mock/AppData/Roaming/npm/codex.cmd',
message: '未检测到 Codex 用户级 GoNavi MCP 配置',
},
];
expect(pickPreferredMCPClient(statuses)).toBe('codex');
});
it('prefers a client that already matches current GoNavi over another client with a stale config', () => {
const statuses: AIMCPClientInstallStatus[] = [
{
client: 'claude-code',
displayName: 'Claude Code',
installed: true,
matchesCurrent: true,
clientDetected: true,
clientCommand: 'claude',
message: '已检测到 Claude Code 用户级 GoNavi MCP 配置,且与当前 GoNavi 安装路径一致',
},
{
client: 'codex',
displayName: 'Codex',
installed: true,
matchesCurrent: false,
clientDetected: true,
clientCommand: 'codex',
message: '已检测到 Codex 中的 GoNavi MCP 记录,但与当前 GoNavi 安装路径不一致,建议更新',
},
];
expect(pickPreferredMCPClient(statuses)).toBe('claude-code');
});
it('keeps the user-selected client when it is still present in the latest status list', () => {
expect(pickPreferredMCPClient(EMPTY_MCP_CLIENT_STATUSES, 'codex')).toBe('codex');
expect(pickPreferredMCPClient(EMPTY_MCP_CLIENT_STATUSES, 'openclaw')).toBe('openclaw');
});
it('formats quoted launch commands for display and clipboard use', () => {
expect(formatMCPLaunchCommand({
command: 'C:/Program Files/GoNavi/GoNavi.exe',
args: ['mcp-server', '--stdio'],
})).toBe('"C:/Program Files/GoNavi/GoNavi.exe" mcp-server --stdio');
});
it('marks OpenClaw and Hermans as remote bridge clients and builds a safe guide', () => {
const openClaw = EMPTY_MCP_CLIENT_STATUSES.find((item) => item.client === 'openclaw');
expect(isRemoteMCPClientStatus(openClaw)).toBe(true);
const guide = buildRemoteMCPClientGuide(openClaw);
expect(guide).toContain('GoNavi MCP 远程接入说明 - OpenClaw');
expect(guide).toContain('云端 Agent 不需要保存数据库密码');
expect(guide).toContain('默认使用 schema-only 模式,不注册 execute_sql');
expect(guide).toContain('不能直接使用 Windows 本地 stdio 命令');
expect(guide).toContain('allowMutating=true');
expect(guide).toContain('"type": "streamable-http"');
expect(guide).toContain('"Authorization": "Bearer <随机token>"');
expect(guide).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
expect(guide).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
});
it('builds remote quick-start snippets for cloud agents without database secrets', () => {
const quickStart = buildRemoteMCPClientQuickStart({
client: 'hermans',
displayName: 'OpenClaw',
});
expect(quickStart.displayName).toBe('OpenClaw');
expect(quickStart.configJson).toContain('"type": "streamable-http"');
expect(quickStart.configJson).toContain('"url": "https://<你的域名或隧道地址>/mcp"');
expect(quickStart.configJson).toContain('"Authorization": "Bearer <随机token>"');
expect(quickStart.configJson).not.toContain('password');
expect(quickStart.configCommand).toBe('GoNavi.exe mcp-server remote-config --client hermans --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
expect(quickStart.launchCommand).toBe('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
expect(quickStart.standaloneCommand).toBe('gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
expect(quickStart.verificationSteps.join('\n')).toContain('get_connections');
expect(quickStart.securityNotes.join('\n')).toContain('默认 --schema-only 不注册 execute_sql');
expect(quickStart.securityNotes.join('\n')).toContain('allowMutating=true');
});
});