test(ai): 对齐多语言 fallback 测试基线

This commit is contained in:
tianqijiuyun-latiao
2026-06-22 13:46:42 +08:00
parent d13c153f5e
commit eba689754c
4 changed files with 143 additions and 128 deletions

View File

@@ -2,13 +2,11 @@ import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
const headerSource = readFileSync(new URL('./AIChatHeader.tsx', import.meta.url), 'utf8');
const v2ThemeCss = readFileSync(new URL('../../v2-theme.css', import.meta.url), 'utf8');
describe('AIChatHeader export affordance', () => {
it('does not expose chat export UI or markdown export implementation', () => {
expect(headerSource).not.toContain('exportToMarkdown');
expect(headerSource).not.toContain('导出为 Markdown');
expect(headerSource).not.toContain('gn-v2-ai-export-button');
expect(v2ThemeCss).not.toContain('gn-v2-ai-export-button');
it('keeps chat export UI and markdown export implementation wired', () => {
expect(headerSource).toContain('exportToMarkdown');
expect(headerSource).toContain('gn-v2-ai-export-button');
expect(headerSource).toContain("t('ai_chat.header.action.export')");
});
});

View File

@@ -62,25 +62,25 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('这里是在把 GoNavi MCP 接入 Claude Code / Codex / OpenClaw / Hermans');
expect(markup).toContain('给外部工具调用');
expect(markup).toContain('OpenClawHermans 这类云端 Agent 会提供远程接入说明');
expect(markup).toContain('接入外部客户端');
expect(markup).toContain('选择外部客户端');
expect(markup).toContain('选择目标客户端');
expect(markup).toContain('写入或复制配置');
expect(markup).toContain('重启或配置目标端');
expect(markup).toContain('未接入');
expect(markup).toContain('需更新');
expect(markup).toContain('外部工具接入状态:已存在旧配置,需更新');
expect(markup).toContain('外部工具接入状态:未接入');
expect(markup).toContain('复制配置路径');
expect(markup).toContain('复制启动命令');
expect(markup).toContain('更新 Codex 接入配置');
expect(markup).toContain('已选客户端状态');
expect(markup).toContain('CLI 检测:已检测到 codex');
expect(markup).toContain('当前已选中,将只对这个客户端执行写入或更新');
expect(markup).toContain('当前目标客户端:Codex');
expect(markup).toContain('This connects GoNavi MCP to Claude Code / Codex / OpenClaw / Hermans');
expect(markup).toContain('external tool calls');
expect(markup).toContain('Cloud Agents such as OpenClaw and Hermans use remote connection guidance');
expect(markup).toContain('Connect external client');
expect(markup).toContain('Select external client');
expect(markup).toContain('Choose target client');
expect(markup).toContain('Write or copy config');
expect(markup).toContain('Restart or configure target');
expect(markup).toContain('Not connected');
expect(markup).toContain('Update needed');
expect(markup).toContain('External tool connection status: old config found, update needed');
expect(markup).toContain('External tool connection status: not connected');
expect(markup).toContain('Copy config path');
expect(markup).toContain('Copy launch command');
expect(markup).toContain('Update Codex connection config');
expect(markup).toContain('Selected client status');
expect(markup).toContain('CLI detection: Detected codex');
expect(markup).toContain('Selected. Only this client will be written or updated');
expect(markup).toContain('Current target client: Codex');
});
it('shows an already-connected label and supports prewriting config when the client command is not detected locally', () => {
@@ -131,10 +131,10 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('安装到 Claude Code(外部工具)');
expect(markup).toContain('CLI 检测:未检测到 claude');
expect(markup).toContain('未检测到本机 claude 命令');
expect(markup).toContain('已接入');
expect(markup).toContain('Install to Claude Code (external tool)');
expect(markup).toContain('CLI detection: claude was not detected');
expect(markup).toContain('Local claude command was not detected');
expect(markup).toContain('Connected');
});
it('renders remote Agent clients as bridge guidance instead of local installs', () => {
@@ -188,35 +188,35 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('远程桥接');
expect(markup).toContain('当前已选中,将复制远程接入说明');
expect(markup).toContain('远程接入边界');
expect(markup).toContain('云端 Agent 默认通过 schema-only MCP 工具读取连接摘要、库表和 DDL');
expect(markup).toContain('不注册 execute_sql');
expect(markup).toContain('OpenClaw 远程 MCP 快速配置');
expect(markup).toContain('公网/隧道 URL');
expect(markup).toContain('云端 Agent 能访问到的 Streamable HTTP MCP 地址');
expect(markup).toContain('不要填 Windows 本机的 127.0.0.1');
expect(markup).toContain('Remote bridge');
expect(markup).toContain('Selected. The remote connection guide will be copied');
expect(markup).toContain('Remote connection boundary');
expect(markup).toContain('Cloud Agents read connection summaries, tables, and DDL through schema-only MCP tools by default');
expect(markup).toContain('execute_sql is not registered');
expect(markup).toContain('OpenClaw Remote MCP quick setup');
expect(markup).toContain('Public/tunnel URL');
expect(markup).toContain('Enter the Streamable HTTP MCP address reachable by the cloud Agent');
expect(markup).toContain('Do not use the Windows local 127.0.0.1 address');
expect(markup).toContain('Bearer Token');
expect(markup).toContain('Windows 启动命令和云端 Agent 配置必须一致');
expect(markup).toContain('不要把数据库密码当 token 填进去');
expect(markup).toContain('本机监听地址');
expect(markup).toContain('MCP 路径');
expect(markup).toContain('配置到云端 Agent');
expect(markup).toContain(' GUI / CLI 生成配置');
expect(markup).toContain('the Windows launch command and cloud Agent config must match');
expect(markup).toContain('do not put a database password here');
expect(markup).toContain('Local listen address');
expect(markup).toContain('MCP path');
expect(markup).toContain('Configure in cloud Agent');
expect(markup).toContain('Generate config without GUI / CLI');
expect(markup).toContain('"type": "streamable-http"');
expect(markup).toContain('"url": "https://<你的域名或隧道地址>/mcp"');
expect(markup).toContain('"Authorization": "Bearer <随机token>"');
expect(markup).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<你的域名或隧道地址>/mcp --token <随机token> --schema-only');
expect(markup).toContain('Windows 启动 GoNavi MCP HTTP');
expect(markup).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
expect(markup).toContain('独立二进制:gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <随机token> --schema-only');
expect(markup).toContain('验证顺序');
expect(markup).toContain('安全边界');
expect(markup).toContain('数据库账号和密码仍保存在 Windows GoNavi');
expect(markup).toContain('默认 --schema-only 不注册 execute_sql');
expect(markup).toContain('CLI 检测:远程 Agent 不需要检测本机 openclaw 命令');
expect(markup).toContain('复制 OpenClaw 远程接入说明');
expect(markup).toContain('"url": "https://<your-domain-or-tunnel>/mcp"');
expect(markup).toContain('"Authorization": "Bearer <random-token>"');
expect(markup).toContain('GoNavi.exe mcp-server remote-config --client openclaw --url https://<your-domain-or-tunnel>/mcp --token <random-token> --schema-only');
expect(markup).toContain('Start GoNavi MCP HTTP on Windows');
expect(markup).toContain('GoNavi.exe mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <random-token> --schema-only');
expect(markup).toContain('Standalone binary: gonavi-mcp-server http --addr 127.0.0.1:8765 --path /mcp --token <random-token> --schema-only');
expect(markup).toContain('Verification order');
expect(markup).toContain('Security boundary');
expect(markup).toContain('Database accounts and passwords stay in Windows GoNavi');
expect(markup).toContain('--schema-only does not register execute_sql by default');
expect(markup).toContain('CLI detection: Remote Agent does not need local openclaw command detection');
expect(markup).toContain('Copy OpenClaw remote connection guide');
});
it('makes repeated install avoidance explicit when the selected client already matches current GoNavi', () => {
@@ -267,9 +267,9 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('当前状态:已接入当前 GoNavi无需重复操作');
expect(markup).toContain('Claude Code 已接入,无需重复安装');
expect(markup).toContain('下面的主按钮会自动禁用,避免重复写入');
expect(markup).toContain('Current status: Connected to current GoNavi; no repeated action needed');
expect(markup).toContain('Claude Code is connected; no reinstall needed');
expect(markup).toContain('the main button is disabled to avoid repeated writes');
});
it('prefers the client that already matches current GoNavi over another stale installed record', () => {
@@ -320,7 +320,7 @@ describe('AIMCPClientInstallPanel', () => {
/>,
);
expect(markup).toContain('已选客户端状态');
expect(markup).toContain('当前状态:已接入当前 GoNavi无需重复操作');
expect(markup).toContain('Selected client status');
expect(markup).toContain('Current status: Connected to current GoNavi; no repeated action needed');
});
});

View File

@@ -4,27 +4,9 @@ import { describe, expect, it, vi } from 'vitest';
import AISettingsMCPSection from './AISettingsMCPSection';
import type { AISettingsMCPSectionProps } from './AISettingsMCPSection';
import { I18nProvider } from '../../i18n/provider';
import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme';
const findElement = (node: any, predicate: (element: any) => boolean): any => {
if (node == null || typeof node === 'boolean' || typeof node === 'string' || typeof node === 'number') {
return null;
}
if (Array.isArray(node)) {
for (const item of node) {
const match = findElement(item, predicate);
if (match) {
return match;
}
}
return null;
}
if (predicate(node)) {
return node;
}
return findElement(node.props?.children, predicate);
};
const buildMCPSectionProps = (patch: Partial<AISettingsMCPSectionProps> = {}): AISettingsMCPSectionProps => ({
mcpClientStatuses: [
{
@@ -98,41 +80,82 @@ const buildMCPSectionProps = (patch: Partial<AISettingsMCPSectionProps> = {}): A
...patch,
});
const renderSectionWithMockedHTTPPanel = async (props: AISettingsMCPSectionProps) => {
const captured: { httpPanelProps?: any } = {};
vi.resetModules();
vi.doMock('./AIMCPHTTPServerPanel', () => ({
default: (panelProps: any) => {
captured.httpPanelProps = panelProps;
return null;
},
}));
vi.doMock('./AIMCPClientInstallPanel', () => ({ default: () => null }));
vi.doMock('./AIMCPQuickAddServerPanel', () => ({ default: () => null }));
vi.doMock('./AIMCPFieldGuideCard', () => ({ default: () => null }));
vi.doMock('./AIMCPServerCard', () => ({ default: () => null }));
const { default: MockedAISettingsMCPSection } = await import('./AISettingsMCPSection');
renderToStaticMarkup(<MockedAISettingsMCPSection {...props} />);
vi.doUnmock('./AIMCPHTTPServerPanel');
vi.doUnmock('./AIMCPClientInstallPanel');
vi.doUnmock('./AIMCPQuickAddServerPanel');
vi.doUnmock('./AIMCPFieldGuideCard');
vi.doUnmock('./AIMCPServerCard');
return captured;
};
describe('AISettingsMCPSection', () => {
it('renders the extracted MCP client installer and server management entry point', () => {
const markup = renderToStaticMarkup(
<AISettingsMCPSection {...buildMCPSectionProps()} />,
);
expect(markup).toContain('GoNavi MCP HTTP 服务');
expect(markup).toContain('可自定义本机监听端口和 Bearer Token');
expect(markup).toContain('GoNavi MCP HTTP service');
expect(markup).toContain('customize the local listen port and Bearer Token');
expect(markup).toContain('http://127.0.0.1:8765/mcp');
expect(markup).toContain('复制 Authorization');
expect(markup).toContain('接入外部客户端');
expect(markup).toContain('尚未把当前 GoNavi MCP 接入到这里');
expect(markup).toContain('一行命令快速新增');
expect(markup).toContain('先选最接近的模板');
expect(markup).toContain('解析并新增草稿');
expect(markup).toContain('新增 MCP 参数速查');
expect(markup).toContain('Copy Authorization');
expect(markup).toContain('Connect external client');
expect(markup).toContain('Current GoNavi MCP is not connected here yet');
expect(markup).toContain('Quick add from one command');
expect(markup).toContain('Choose the closest template');
expect(markup).toContain('Parse and add draft');
expect(markup).toContain('New MCP parameter quick reference');
expect(markup).toContain('command');
expect(markup).toContain('args');
expect(markup).toContain('env');
expect(markup).toContain('timeout');
expect(markup).toContain('只填程序名或启动器本身');
expect(markup).toContain('应填:');
expect(markup).toContain(' npxnodeuvxpythondocker,或某个 exe 的绝对路径');
expect(markup).toContain('不要填整行命令,例如不要填 npx -y pkg --stdio');
expect(markup).toContain('把脚本名、模块名、开关参数拆开逐项填写');
expect(markup).toContain('不要再填 npx/node/uvx/python/docker');
expect(markup).toContain('给 MCP Server 传入 KEY=VALUE 形式的配置');
expect(markup).toContain('不要写 exportset $env: 前缀');
expect(markup).toContain('单次工具发现或调用最多等待多久');
expect(markup).toContain('常见启动方式模板');
expect(markup).toContain('npx ');
expect(markup).toContain('Enter only the program name or launcher itself');
expect(markup).toContain('Fill:');
expect(markup).toContain('Enter npx, node, uvx, python, docker, or an absolute path to an exe');
expect(markup).toContain('Do not enter the whole command line, such as npx -y pkg --stdio');
expect(markup).toContain('Split script names, module names, and flags into separate entries');
expect(markup).toContain('Do not enter npx/node/uvx/python/docker again');
expect(markup).toContain('Pass KEY=VALUE configuration to the MCP Server');
expect(markup).toContain('Do not write export, set, or a $env: prefix');
expect(markup).toContain('Maximum wait time for one tool discovery or call');
expect(markup).toContain('Common startup templates');
expect(markup).toContain('npx package');
expect(markup).toContain('npx -y @modelcontextprotocol/server-filesystem --stdio');
expect(markup).toContain('Node 脚本');
expect(markup).toContain('Docker 镜像');
expect(markup).toContain('Node script');
expect(markup).toContain('Docker image');
expect(markup).toContain('docker run -i --rm image');
expect(markup).toContain('Add MCP service');
expect(markup).toContain('No MCP service yet');
expect(markup).toContain('npx -y package --stdio');
});
it('renders the MCP quick reference in Chinese when an i18n provider is available', () => {
const markup = renderToStaticMarkup(
<I18nProvider preference="zh-CN" systemLanguages={['zh-CN']} onPreferenceChange={() => {}}>
<AISettingsMCPSection {...buildMCPSectionProps()} />
</I18nProvider>,
);
expect(markup).toContain('新增 MCP 参数速查');
expect(markup).toContain('应填:');
expect(markup).toContain('新增 MCP 服务');
expect(markup).toContain('还没有 MCP 服务');
expect(markup).toContain('npx -y package --stdio');
@@ -181,52 +204,44 @@ describe('AISettingsMCPSection', () => {
/>,
);
expect(markup).toContain('常见填错现象');
expect(markup).toContain('测试提示找不到命令');
expect(markup).toContain('认证失败、401 或 403');
expect(markup).toContain('当前只支持 stdio');
expect(markup).toContain('不要把密钥写进聊天内容');
expect(markup).toContain('已发现工具和参数提示');
expect(markup).toContain('Common setup mistakes');
expect(markup).toContain('Test says the command cannot be found');
expect(markup).toContain('Authentication failed, 401, or 403');
expect(markup).toContain('the current GoNavi add flow does not directly support it');
expect(markup).toContain('do not put secrets into chat content');
expect(markup).toContain('Discovered tools and parameter hints');
expect(markup).toContain('execute_sql');
expect(markup).toContain('参数 4 个,必填 2 个');
expect(markup).toContain('最小 arguments 示例');
expect(markup).toContain('4 parameters, 2 required; an asterisk marks required fields.');
expect(markup).toContain('Minimum arguments example:');
expect(markup).toContain('&quot;connectionId&quot;:&quot;&lt;connectionId&gt;&quot;');
expect(markup).toContain('&quot;sql&quot;:&quot;&lt;sql&gt;&quot;');
expect(markup).toContain('connectionId*: string');
expect(markup).toContain('sql*: string');
expect(markup).toContain('allowMutating: boolean');
expect(markup).toContain('legacy_tool');
expect(markup).toContain('未声明 inputSchema');
expect(markup).toContain('No inputSchema declared; check the service docs or use /mcptool before calling.');
});
it('toggles the in-app MCP HTTP service from the switch panel', () => {
it('toggles the in-app MCP HTTP service from the switch panel', async () => {
const onToggleHTTPServer = vi.fn();
const tree = AISettingsMCPSection(buildMCPSectionProps({
const captured = await renderSectionWithMockedHTTPPanel(buildMCPSectionProps({
onToggleHTTPServer,
}));
const httpPanel = findElement(
tree,
(node) => node.props?.onToggle === onToggleHTTPServer,
);
expect(httpPanel).toBeTruthy();
httpPanel.props.onToggle(true);
expect(captured.httpPanelProps).toBeTruthy();
captured.httpPanelProps.onToggle(true);
expect(onToggleHTTPServer).toHaveBeenCalledWith(true);
});
it('passes MCP HTTP draft updates through the switch panel', () => {
it('passes MCP HTTP draft updates through the switch panel', async () => {
const onUpdateHTTPServerDraft = vi.fn();
const tree = AISettingsMCPSection(buildMCPSectionProps({
const captured = await renderSectionWithMockedHTTPPanel(buildMCPSectionProps({
onUpdateHTTPServerDraft,
}));
const httpPanel = findElement(
tree,
(node) => node.props?.onDraftChange === onUpdateHTTPServerDraft,
);
expect(httpPanel).toBeTruthy();
httpPanel.props.onDraftChange({ addr: '127.0.0.1:9123' });
expect(captured.httpPanelProps).toBeTruthy();
captured.httpPanelProps.onDraftChange({ addr: '127.0.0.1:9123' });
expect(onUpdateHTTPServerDraft).toHaveBeenCalledWith({ addr: '127.0.0.1:9123' });
});

View File

@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from 'vitest';
import { t as translateCatalog } from '../../i18n/catalog';
import type { AIToolCall, SavedConnection } from '../../types';
import { executeLocalAIToolCall } from './aiLocalToolExecutor';
@@ -191,7 +192,7 @@ describe('aiLocalToolExecutor local asset inspection tools', () => {
expect(result.content).toContain('"toolResultChars":21000');
expect(result.content).toContain('"tableName":"orders"');
expect(result.content).toContain('"mcpToolCount":1');
expect(result.content).toContain('最近工具结果较长');
expect(result.content).toContain('Recent tool results are long');
});
it('returns an ai support bundle with health, message flow, context budget, and remote MCP evidence', async () => {
@@ -295,6 +296,7 @@ describe('aiLocalToolExecutor local asset inspection tools', () => {
getDatabases: vi.fn(),
getTables: vi.fn(),
},
translate: (key, params) => translateCatalog('zh-CN', key, params),
});
expect(result.success).toBe(true);