mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-19 14:04:41 +08:00
354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import { t as translateCatalog } from '../../i18n/catalog';
|
|
import type { AIToolCall, SavedConnection } from '../../types';
|
|
import { executeLocalAIToolCall } from './aiLocalToolExecutor';
|
|
|
|
const buildConnection = (): SavedConnection => ({
|
|
id: 'conn-1',
|
|
name: '主库',
|
|
config: {
|
|
type: 'mysql',
|
|
host: '127.0.0.1',
|
|
port: 3306,
|
|
user: 'root',
|
|
},
|
|
});
|
|
|
|
const buildToolCall = (name: string, args: Record<string, unknown>): AIToolCall => ({
|
|
id: `call-${name}`,
|
|
type: 'function',
|
|
function: {
|
|
name,
|
|
arguments: JSON.stringify(args),
|
|
},
|
|
});
|
|
|
|
describe('aiLocalToolExecutor local asset inspection tools', () => {
|
|
it('returns local saved queries so the model can reuse historical sql scripts', async () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_saved_queries', {
|
|
keyword: '支付',
|
|
connectionId: 'conn-1',
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [],
|
|
toolContextMap: new Map(),
|
|
savedQueries: [
|
|
{
|
|
id: 'saved-1',
|
|
name: '支付订单核对',
|
|
sql: 'SELECT * FROM orders WHERE status = \'paid\'',
|
|
connectionId: 'conn-1',
|
|
dbName: 'crm',
|
|
createdAt: 2,
|
|
},
|
|
{
|
|
id: 'saved-2',
|
|
name: '用户列表',
|
|
sql: 'SELECT * FROM users',
|
|
connectionId: 'conn-1',
|
|
dbName: 'crm',
|
|
createdAt: 1,
|
|
},
|
|
],
|
|
runtime: {
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"totalMatched":1');
|
|
expect(result.content).toContain('支付订单核对');
|
|
expect(result.content).toContain('"connectionName":"主库"');
|
|
expect(result.content).toContain('status = \'paid\'');
|
|
});
|
|
|
|
it('returns local ai chat sessions so the model can locate previous conversations by title or preview', async () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_ai_sessions', {
|
|
keyword: '支付',
|
|
limit: 5,
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [],
|
|
toolContextMap: new Map(),
|
|
aiChatSessions: [
|
|
{ id: 'session-1', title: '支付异常排查', updatedAt: 200 },
|
|
{ id: 'session-2', title: '用户列表', updatedAt: 100 },
|
|
],
|
|
aiChatHistory: {
|
|
'session-1': [
|
|
{ id: 'msg-1', role: 'user', content: '帮我排查支付超时', timestamp: 101 },
|
|
{ id: 'msg-2', role: 'assistant', content: '先看最近错误日志', timestamp: 102 },
|
|
],
|
|
'session-2': [
|
|
{ id: 'msg-3', role: 'user', content: '列出最近注册用户', timestamp: 103 },
|
|
],
|
|
},
|
|
activeSessionId: 'session-2',
|
|
runtime: {
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"totalMatched":1');
|
|
expect(result.content).toContain('支付异常排查');
|
|
expect(result.content).toContain('帮我排查支付超时');
|
|
expect(result.content).toContain('先看最近错误日志');
|
|
expect(result.content).not.toContain('列出最近注册用户');
|
|
});
|
|
|
|
it('returns ai message flow diagnostics for the active session', async () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_ai_message_flow', {
|
|
limit: 8,
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [],
|
|
toolContextMap: new Map(),
|
|
aiChatSessions: [
|
|
{ id: 'session-1', title: '消息流异常', updatedAt: 200 },
|
|
],
|
|
aiChatHistory: {
|
|
'session-1': [
|
|
{ id: 'msg-1', role: 'user', content: 'AI 回复拆成多个气泡', timestamp: 101 },
|
|
{
|
|
id: 'msg-2',
|
|
role: 'assistant',
|
|
content: '先调用探针',
|
|
timestamp: 102,
|
|
tool_calls: [{ id: 'tool-1', type: 'function', function: { name: 'inspect_ai_runtime', arguments: '{}' } }],
|
|
},
|
|
{ id: 'msg-3', role: 'assistant', content: '继续回答', timestamp: 103 },
|
|
],
|
|
},
|
|
activeSessionId: 'session-1',
|
|
runtime: {
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"requestedSessionId":"session-1"');
|
|
expect(result.content).toContain('"unresolvedToolCallCount":1');
|
|
expect(result.content).toContain('"consecutiveAssistantPairCount":1');
|
|
expect(result.content).toContain('回复拆成多个气泡');
|
|
});
|
|
|
|
it('returns ai context budget diagnostics for the active session', async () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_ai_context_budget', {
|
|
messageLimit: 10,
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [{
|
|
alias: 'remote_probe',
|
|
originalName: 'remote_probe',
|
|
serverId: 'server-1',
|
|
serverName: '远程工具',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
keyword: { type: 'string' },
|
|
},
|
|
},
|
|
}],
|
|
toolContextMap: new Map(),
|
|
aiChatSessions: [
|
|
{ id: 'session-1', title: '上下文预算排查', updatedAt: 200 },
|
|
],
|
|
aiChatHistory: {
|
|
'session-1': [
|
|
{ id: 'msg-1', role: 'user', content: 'AI 变慢是不是上下文太大', timestamp: 101 },
|
|
{ id: 'msg-2', role: 'tool', content: 'x'.repeat(21000), timestamp: 102, tool_call_id: 'tool-1' },
|
|
],
|
|
},
|
|
activeSessionId: 'session-1',
|
|
aiContexts: {
|
|
'conn-1:crm': [
|
|
{ dbName: 'crm', tableName: 'orders', ddl: 'CREATE TABLE orders(id bigint, amount decimal(10,2));' },
|
|
],
|
|
},
|
|
skills: [{
|
|
id: 'skill-1',
|
|
name: 'SQL 审查',
|
|
systemPrompt: '先检查风险',
|
|
enabled: true,
|
|
scopes: ['database'],
|
|
}],
|
|
runtime: {
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"title":"上下文预算排查"');
|
|
expect(result.content).toContain('"toolResultChars":21000');
|
|
expect(result.content).toContain('"tableName":"orders"');
|
|
expect(result.content).toContain('"mcpToolCount":1');
|
|
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 () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_ai_support_bundle', {
|
|
keyword: 'mcp',
|
|
publicUrl: 'https://agent.example.com/mcp',
|
|
tokenConfigured: false,
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [{
|
|
alias: 'remote_probe',
|
|
originalName: 'remote_probe',
|
|
serverId: 'server-1',
|
|
serverName: '远程工具',
|
|
description: '读取远程信息',
|
|
inputSchema: {
|
|
type: 'object',
|
|
required: ['keyword'],
|
|
properties: {
|
|
keyword: { type: 'string', description: '关键词' },
|
|
},
|
|
},
|
|
}],
|
|
toolContextMap: new Map(),
|
|
aiChatSessions: [
|
|
{ id: 'session-1', title: 'AI 稳定性排障', updatedAt: 200 },
|
|
],
|
|
aiChatHistory: {
|
|
'session-1': [
|
|
{ id: 'msg-1', role: 'user', content: 'AI 最近不稳定', timestamp: 101 },
|
|
{
|
|
id: 'msg-2',
|
|
role: 'assistant',
|
|
content: '先看支持包',
|
|
timestamp: 102,
|
|
tool_calls: [{ id: 'tool-1', type: 'function', function: { name: 'inspect_app_health', arguments: '{}' } }],
|
|
},
|
|
{ id: 'msg-3', role: 'assistant', content: '继续分析', timestamp: 103 },
|
|
],
|
|
},
|
|
activeSessionId: 'session-1',
|
|
aiContexts: {
|
|
'conn-1:crm': [
|
|
{ dbName: 'crm', tableName: 'orders', ddl: 'CREATE TABLE orders(id bigint);' },
|
|
],
|
|
},
|
|
skills: [{
|
|
id: 'skill-1',
|
|
name: 'SQL 审查',
|
|
systemPrompt: '先检查风险',
|
|
enabled: true,
|
|
scopes: ['database'],
|
|
}],
|
|
runtime: {
|
|
getAIRuntimeState: vi.fn(async () => ({
|
|
providers: [{
|
|
id: 'provider-1',
|
|
name: 'OpenAI',
|
|
type: 'openai' as const,
|
|
apiKey: '',
|
|
baseUrl: 'https://api.example.com',
|
|
model: 'gpt-5',
|
|
hasSecret: true,
|
|
maxTokens: 4096,
|
|
temperature: 0.2,
|
|
}],
|
|
activeProviderId: 'provider-1',
|
|
safetyLevel: 'readonly',
|
|
contextLevel: 'schema_only',
|
|
})),
|
|
getMCPServers: vi.fn(async () => [{
|
|
id: 'server-1',
|
|
name: '远程工具',
|
|
transport: 'stdio' as const,
|
|
command: 'node',
|
|
args: ['server.js'],
|
|
env: { TOKEN: 'secret-value' },
|
|
enabled: true,
|
|
timeoutSeconds: 20,
|
|
}]),
|
|
getMCPClientInstallStatuses: vi.fn(async () => [{
|
|
client: 'openclaw',
|
|
displayName: 'OpenClaw',
|
|
installMode: 'remote' as const,
|
|
installed: false,
|
|
matchesCurrent: false,
|
|
clientDetected: false,
|
|
message: 'OpenClaw 走远程 MCP 桥接',
|
|
}]),
|
|
readAppLogTail: vi.fn(async () => ({
|
|
success: true,
|
|
logPath: 'C:/Users/mock/.GoNavi/Logs/gonavi.log',
|
|
lines: [
|
|
'2026/06/11 10:00:00 [WARN] MCP mock warning',
|
|
'2026/06/11 10:00:01 [ERROR] MCP mock error',
|
|
],
|
|
fileWindowTruncated: false,
|
|
matchedLinesTruncated: false,
|
|
})),
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
translate: (key, params) => translateCatalog('zh-CN', key, params),
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"kind":"ai_support_bundle"');
|
|
expect(result.content).toContain('"databasePasswordsIncluded":false');
|
|
expect(result.content).toContain('"providerSecretsIncluded":false');
|
|
expect(result.content).toContain('"mcpEnvValuesIncluded":false');
|
|
expect(result.content).toContain('"unresolvedToolCallCount":1');
|
|
expect(result.content).toContain('"consecutiveAssistantPairCount":1');
|
|
expect(result.content).toContain('"remoteMCPPublicUrl":"https://agent.example.com/mcp"');
|
|
expect(result.content).toContain('尚未确认 Bearer Token');
|
|
expect(result.content).not.toContain('secret-value');
|
|
});
|
|
|
|
it('returns sql snippets so the model can inspect local query templates', async () => {
|
|
const result = await executeLocalAIToolCall({
|
|
toolCall: buildToolCall('inspect_sql_snippets', {
|
|
keyword: '支付',
|
|
}),
|
|
connections: [buildConnection()],
|
|
mcpTools: [],
|
|
toolContextMap: new Map(),
|
|
sqlSnippets: [
|
|
{
|
|
id: 'snippet-1',
|
|
prefix: 'sel',
|
|
name: 'SELECT 模板',
|
|
body: 'SELECT * FROM ${1:table};',
|
|
isBuiltin: true,
|
|
createdAt: 1,
|
|
},
|
|
{
|
|
id: 'snippet-2',
|
|
prefix: 'pay',
|
|
name: '支付模板',
|
|
description: '支付对账',
|
|
body: 'SELECT * FROM pay_orders WHERE created_at >= ${1:start};',
|
|
isBuiltin: false,
|
|
createdAt: 2,
|
|
},
|
|
],
|
|
runtime: {
|
|
getDatabases: vi.fn(),
|
|
getTables: vi.fn(),
|
|
},
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.content).toContain('"totalMatched":1');
|
|
expect(result.content).toContain('"prefix":"pay"');
|
|
expect(result.content).toContain('"customCount":1');
|
|
expect(result.content).toContain('pay_orders');
|
|
});
|
|
});
|