mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-26 16:31:42 +08:00
test(ai): 对齐多语言 fallback 测试基线
This commit is contained in:
@@ -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')");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,25 +62,25 @@ describe('AIMCPClientInstallPanel', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(markup).toContain('这里是在把 GoNavi MCP 接入 Claude Code / Codex / OpenClaw / Hermans');
|
||||
expect(markup).toContain('给外部工具调用');
|
||||
expect(markup).toContain('OpenClaw、Hermans 这类云端 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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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('填 npx、node、uvx、python、docker,或某个 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('不要写 export、set 或 $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('"connectionId":"<connectionId>"');
|
||||
expect(markup).toContain('"sql":"<sql>"');
|
||||
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' });
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user