Files
MyGoNavi/frontend/src/components/ai/aiLocalToolExecutor.localAssetsInspection.test.ts
2026-06-22 13:46:42 +08:00

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');
});
});