From b4affbc1d52f1d147609ba4304034f1ef18dd277 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Wed, 10 Jun 2026 10:00:37 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(ai-tests):=20?= =?UTF-8?q?=E6=8B=86=E5=88=86=20AI=20=E9=85=8D=E7=BD=AE=E6=8E=A2=E9=92=88?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 AI runtime、provider、safety、MCP 与 guidance 探针测试移入独立文件 - 缩减 aiLocalToolExecutor.test.ts 体积,保持执行器行为不变 - 通过定向测试和生产构建验证 --- ...calToolExecutor.aiConfigInspection.test.ts | 358 ++++++++++++++++++ .../components/ai/aiLocalToolExecutor.test.ts | 332 ---------------- 2 files changed, 358 insertions(+), 332 deletions(-) create mode 100644 frontend/src/components/ai/aiLocalToolExecutor.aiConfigInspection.test.ts diff --git a/frontend/src/components/ai/aiLocalToolExecutor.aiConfigInspection.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.aiConfigInspection.test.ts new file mode 100644 index 0000000..73aa4fd --- /dev/null +++ b/frontend/src/components/ai/aiLocalToolExecutor.aiConfigInspection.test.ts @@ -0,0 +1,358 @@ +import { describe, expect, it, vi } from 'vitest'; + +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): AIToolCall => ({ + id: `call-${name}`, + type: 'function', + function: { + name, + arguments: JSON.stringify(args), + }, +}); + +describe('aiLocalToolExecutor AI config inspection tools', () => { + it('returns the current ai runtime snapshot so the model can inspect provider, safety, skills, and tools', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_runtime', {}), + connections: [buildConnection()], + mcpTools: [{ + alias: 'browser_open', + originalName: 'browser_open', + serverId: 'server-1', + serverName: 'browser', + title: '打开浏览器', + description: '打开页面', + }], + skills: [{ + id: 'skill-1', + name: '结构审查', + systemPrompt: '先核对字段', + enabled: true, + scopes: ['database'], + requiredTools: ['get_columns'], + }], + dynamicModels: ['gpt-5.4', 'gpt-4.1'], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + getAIRuntimeState: vi.fn().mockResolvedValue({ + activeProviderId: 'provider-1', + providers: [{ + id: 'provider-1', + type: 'openai', + name: 'OpenAI 主账号', + apiKey: '', + hasSecret: true, + baseUrl: 'https://api.openai.com/v1', + model: 'gpt-5.4', + models: ['gpt-5.4', 'gpt-4.1'], + maxTokens: 32000, + temperature: 0.2, + }], + safetyLevel: 'readonly', + contextLevel: 'with_samples', + }), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"hasActiveProvider":true'); + expect(result.content).toContain('"name":"OpenAI 主账号"'); + expect(result.content).toContain('"safetyLevel":"readonly"'); + expect(result.content).toContain('"contextLevel":"with_samples"'); + expect(result.content).toContain('"enabledSkillCount":1'); + expect(result.content).toContain('"alias":"browser_open"'); + expect(result.content).toContain('"builtinToolCount":'); + }); + + it('returns the current ai safety snapshot so the model can inspect write boundaries and readonly guards', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_safety', {}), + connections: [{ + id: 'jvm-1', + name: 'JVM 诊断环境', + config: { + type: 'jvm', + host: '10.0.0.8', + port: 0, + user: '', + jvm: { + environment: 'uat', + readOnly: true, + diagnostic: { + transport: 'agent-bridge', + allowObserveCommands: true, + allowTraceCommands: true, + allowMutatingCommands: false, + }, + }, + }, + }], + tabs: [{ + id: 'diag-tab-1', + title: 'JVM 诊断', + type: 'jvm-diagnostic', + connectionId: 'jvm-1', + readOnly: true, + }], + activeTabId: 'diag-tab-1', + mcpTools: [], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + getAIRuntimeState: vi.fn().mockResolvedValue({ + safetyLevel: 'readwrite', + }), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"safetyLevel":"readwrite"'); + expect(result.content).toContain('"allowDML":true'); + expect(result.content).toContain('"allowDDL":false'); + expect(result.content).toContain('"readOnly":true'); + expect(result.content).toContain('"allowMutatingCommands":false'); + expect(result.content).toContain('allowMutating=true'); + }); + + it('returns the current ai provider snapshot so the model can inspect provider readiness and model selection', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_providers', {}), + connections: [buildConnection()], + mcpTools: [], + dynamicModels: ['gpt-5.4', 'gpt-4.1-mini'], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + getAIRuntimeState: vi.fn().mockResolvedValue({ + activeProviderId: 'provider-1', + providers: [ + { + id: 'provider-1', + type: 'openai', + name: 'OpenAI 主账号', + apiKey: '', + hasSecret: true, + baseUrl: 'https://api.openai.com/v1', + model: 'gpt-5.4', + models: ['gpt-5.4', 'gpt-4.1'], + maxTokens: 32000, + temperature: 0.2, + }, + { + id: 'provider-2', + type: 'custom', + name: '自建代理', + apiKey: '', + hasSecret: false, + baseUrl: '', + model: '', + models: [], + headers: { + Authorization: 'Bearer secret-token', + }, + maxTokens: 16000, + temperature: 0.7, + }, + ], + }), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"providerCount":2'); + expect(result.content).toContain('"missingSecretCount":1'); + expect(result.content).toContain('"name":"OpenAI 主账号"'); + expect(result.content).toContain('"name":"自建代理"'); + expect(result.content).toContain('"issues":["missing_secret","missing_base_url","missing_selected_model","missing_declared_models"]'); + expect(result.content).not.toContain('secret-token'); + }); + + it('returns the current chat readiness snapshot so the model can inspect why ai input cannot send yet', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_chat_readiness', {}), + connections: [buildConnection()], + mcpTools: [], + dynamicModels: ['gpt-5.5', 'gpt-4.1-mini'], + activeContext: { + connectionId: 'conn-1', + dbName: 'demo', + }, + aiContexts: { + 'conn-1:demo': [{ + dbName: 'demo', + tableName: 'orders', + ddl: 'CREATE TABLE orders (...)', + }], + }, + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + getAIRuntimeState: vi.fn().mockResolvedValue({ + activeProviderId: 'provider-1', + providers: [{ + id: 'provider-1', + type: 'openai', + name: 'OpenAI 主账号', + apiKey: '', + hasSecret: true, + baseUrl: 'https://api.openai.com/v1', + model: '', + models: ['gpt-5.5', 'gpt-4.1-mini'], + maxTokens: 32000, + temperature: 0.2, + }], + }), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"status":"missing_model"'); + expect(result.content).toContain('"contextAttachedCount":1'); + expect(result.content).toContain('"selectableModelCount":2'); + expect(result.content).toContain('OpenAI 主账号'); + }); + + it('returns the current mcp setup snapshot so the model can inspect configured servers and client install state', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_mcp_setup', {}), + connections: [buildConnection()], + mcpTools: [{ + alias: 'browser_open', + originalName: 'browser_open', + serverId: 'server-1', + serverName: 'Browser', + title: '打开页面', + description: '打开页面', + }], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + getMCPServers: vi.fn().mockResolvedValue([ + { + id: 'server-1', + name: 'Browser', + transport: 'stdio', + command: 'uvx', + args: ['mcp-server-browser'], + env: { + OPENAI_API_KEY: '***', + }, + enabled: true, + timeoutSeconds: 20, + }, + { + id: 'server-2', + name: 'Broken', + transport: 'stdio', + command: '', + args: [], + env: {}, + enabled: true, + timeoutSeconds: 1, + }, + ]), + getMCPClientInstallStatuses: vi.fn().mockResolvedValue([ + { + client: 'codex', + displayName: 'Codex', + installed: true, + matchesCurrent: false, + clientDetected: true, + clientCommand: 'codex', + clientPath: 'C:/Tools/codex.exe', + configPath: 'C:/Users/demo/.codex/config.toml', + command: 'gonavi-mcp-server', + args: ['stdio'], + message: '检测到旧的 GoNavi 路径', + }, + ]), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"serverCount":2'); + expect(result.content).toContain('"name":"Browser"'); + expect(result.content).toContain('"launchCommandPreview":"uvx mcp-server-browser"'); + expect(result.content).toContain('"serverConfigurationIssueCount":2'); + expect(result.content).toContain('"serversWithConfigurationErrors":1'); + expect(result.content).toContain('"key":"command-missing"'); + expect(result.content).toContain('"displayName":"Codex"'); + expect(result.content).toContain('"launchCommandPreview":"gonavi-mcp-server stdio"'); + }); + + it('returns the builtin mcp authoring guide so the model can explain how to fill command, args, env, and templates', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_mcp_authoring_guide', {}), + connections: [buildConnection()], + mcpTools: [], + toolContextMap: new Map(), + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"supportsWholeCommandAutoSplit":true'); + expect(result.content).toContain('"fullCommandPasteExample":"$env:GITHUB_TOKEN=...; uvx mcp-server-github --stdio"'); + expect(result.content).toContain('"title":"启动命令"'); + expect(result.content).toContain('"example":"node / uvx / python"'); + expect(result.content).toContain('PowerShell $env:KEY=VALUE;'); + expect(result.content).toContain('"title":"uvx 工具"'); + expect(result.content).toContain('"exampleLaunchPreview":"uvx some-mcp-server"'); + }); + + it('returns the current ai guidance snapshot so the model can inspect active prompts and enabled skills', async () => { + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_ai_guidance', {}), + connections: [buildConnection()], + mcpTools: [], + toolContextMap: new Map(), + userPromptSettings: { + global: '回答前先核对上下文。', + database: '生成 SQL 时只读优先。', + jvm: '', + jvmDiagnostic: '', + }, + skills: [{ + id: 'skill-1', + name: '结构审查', + description: '优先核对字段', + systemPrompt: '先看字段和索引,再给结论。', + enabled: true, + scopes: ['database'], + requiredTools: ['get_columns'], + }], + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"customPromptCount":2'); + expect(result.content).toContain('"scope":"global"'); + expect(result.content).toContain('回答前先核对上下文'); + expect(result.content).toContain('"enabledSkillCount":1'); + expect(result.content).toContain('"name":"结构审查"'); + }); +}); diff --git a/frontend/src/components/ai/aiLocalToolExecutor.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.test.ts index 18b407f..3f557ba 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.test.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.test.ts @@ -169,338 +169,6 @@ describe('aiLocalToolExecutor', () => { expect(result.content).toContain('CREATE TABLE orders'); }); - it('returns the current ai runtime snapshot so the model can inspect provider, safety, skills, and tools', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_ai_runtime', {}), - connections: [buildConnection()], - mcpTools: [{ - alias: 'browser_open', - originalName: 'browser_open', - serverId: 'server-1', - serverName: 'browser', - title: '打开浏览器', - description: '打开页面', - }], - skills: [{ - id: 'skill-1', - name: '结构审查', - systemPrompt: '先核对字段', - enabled: true, - scopes: ['database'], - requiredTools: ['get_columns'], - }], - dynamicModels: ['gpt-5.4', 'gpt-4.1'], - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - getAIRuntimeState: vi.fn().mockResolvedValue({ - activeProviderId: 'provider-1', - providers: [{ - id: 'provider-1', - type: 'openai', - name: 'OpenAI 主账号', - apiKey: '', - hasSecret: true, - baseUrl: 'https://api.openai.com/v1', - model: 'gpt-5.4', - models: ['gpt-5.4', 'gpt-4.1'], - maxTokens: 32000, - temperature: 0.2, - }], - safetyLevel: 'readonly', - contextLevel: 'with_samples', - }), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"hasActiveProvider":true'); - expect(result.content).toContain('"name":"OpenAI 主账号"'); - expect(result.content).toContain('"safetyLevel":"readonly"'); - expect(result.content).toContain('"contextLevel":"with_samples"'); - expect(result.content).toContain('"enabledSkillCount":1'); - expect(result.content).toContain('"alias":"browser_open"'); - expect(result.content).toContain('"builtinToolCount":'); - }); - - it('returns the current ai safety snapshot so the model can inspect write boundaries and readonly guards', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_ai_safety', {}), - connections: [{ - id: 'jvm-1', - name: 'JVM 诊断环境', - config: { - type: 'jvm', - host: '10.0.0.8', - port: 0, - user: '', - jvm: { - environment: 'uat', - readOnly: true, - diagnostic: { - transport: 'agent-bridge', - allowObserveCommands: true, - allowTraceCommands: true, - allowMutatingCommands: false, - }, - }, - }, - }], - tabs: [{ - id: 'diag-tab-1', - title: 'JVM 诊断', - type: 'jvm-diagnostic', - connectionId: 'jvm-1', - readOnly: true, - }], - activeTabId: 'diag-tab-1', - mcpTools: [], - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - getAIRuntimeState: vi.fn().mockResolvedValue({ - safetyLevel: 'readwrite', - }), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"safetyLevel":"readwrite"'); - expect(result.content).toContain('"allowDML":true'); - expect(result.content).toContain('"allowDDL":false'); - expect(result.content).toContain('"readOnly":true'); - expect(result.content).toContain('"allowMutatingCommands":false'); - expect(result.content).toContain('allowMutating=true'); - }); - - it('returns the current ai provider snapshot so the model can inspect provider readiness and model selection', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_ai_providers', {}), - connections: [buildConnection()], - mcpTools: [], - dynamicModels: ['gpt-5.4', 'gpt-4.1-mini'], - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - getAIRuntimeState: vi.fn().mockResolvedValue({ - activeProviderId: 'provider-1', - providers: [ - { - id: 'provider-1', - type: 'openai', - name: 'OpenAI 主账号', - apiKey: '', - hasSecret: true, - baseUrl: 'https://api.openai.com/v1', - model: 'gpt-5.4', - models: ['gpt-5.4', 'gpt-4.1'], - maxTokens: 32000, - temperature: 0.2, - }, - { - id: 'provider-2', - type: 'custom', - name: '自建代理', - apiKey: '', - hasSecret: false, - baseUrl: '', - model: '', - models: [], - headers: { - Authorization: 'Bearer secret-token', - }, - maxTokens: 16000, - temperature: 0.7, - }, - ], - }), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"providerCount":2'); - expect(result.content).toContain('"missingSecretCount":1'); - expect(result.content).toContain('"name":"OpenAI 主账号"'); - expect(result.content).toContain('"name":"自建代理"'); - expect(result.content).toContain('"issues":["missing_secret","missing_base_url","missing_selected_model","missing_declared_models"]'); - expect(result.content).not.toContain('secret-token'); - }); - - it('returns the current chat readiness snapshot so the model can inspect why ai input cannot send yet', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_ai_chat_readiness', {}), - connections: [buildConnection()], - mcpTools: [], - dynamicModels: ['gpt-5.5', 'gpt-4.1-mini'], - activeContext: { - connectionId: 'conn-1', - dbName: 'demo', - }, - aiContexts: { - 'conn-1:demo': [{ - dbName: 'demo', - tableName: 'orders', - ddl: 'CREATE TABLE orders (...)', - }], - }, - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - getAIRuntimeState: vi.fn().mockResolvedValue({ - activeProviderId: 'provider-1', - providers: [{ - id: 'provider-1', - type: 'openai', - name: 'OpenAI 主账号', - apiKey: '', - hasSecret: true, - baseUrl: 'https://api.openai.com/v1', - model: '', - models: ['gpt-5.5', 'gpt-4.1-mini'], - maxTokens: 32000, - temperature: 0.2, - }], - }), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"status":"missing_model"'); - expect(result.content).toContain('"contextAttachedCount":1'); - expect(result.content).toContain('"selectableModelCount":2'); - expect(result.content).toContain('OpenAI 主账号'); - }); - - it('returns the current mcp setup snapshot so the model can inspect configured servers and client install state', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_mcp_setup', {}), - connections: [buildConnection()], - mcpTools: [{ - alias: 'browser_open', - originalName: 'browser_open', - serverId: 'server-1', - serverName: 'Browser', - title: '打开页面', - description: '打开页面', - }], - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - getMCPServers: vi.fn().mockResolvedValue([ - { - id: 'server-1', - name: 'Browser', - transport: 'stdio', - command: 'uvx', - args: ['mcp-server-browser'], - env: { - OPENAI_API_KEY: '***', - }, - enabled: true, - timeoutSeconds: 20, - }, - { - id: 'server-2', - name: 'Broken', - transport: 'stdio', - command: '', - args: [], - env: {}, - enabled: true, - timeoutSeconds: 1, - }, - ]), - getMCPClientInstallStatuses: vi.fn().mockResolvedValue([ - { - client: 'codex', - displayName: 'Codex', - installed: true, - matchesCurrent: false, - clientDetected: true, - clientCommand: 'codex', - clientPath: 'C:/Tools/codex.exe', - configPath: 'C:/Users/demo/.codex/config.toml', - command: 'gonavi-mcp-server', - args: ['stdio'], - message: '检测到旧的 GoNavi 路径', - }, - ]), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"serverCount":2'); - expect(result.content).toContain('"name":"Browser"'); - expect(result.content).toContain('"launchCommandPreview":"uvx mcp-server-browser"'); - expect(result.content).toContain('"serverConfigurationIssueCount":2'); - expect(result.content).toContain('"serversWithConfigurationErrors":1'); - expect(result.content).toContain('"key":"command-missing"'); - expect(result.content).toContain('"displayName":"Codex"'); - expect(result.content).toContain('"launchCommandPreview":"gonavi-mcp-server stdio"'); - }); - - it('returns the builtin mcp authoring guide so the model can explain how to fill command, args, env, and templates', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_mcp_authoring_guide', {}), - connections: [buildConnection()], - mcpTools: [], - toolContextMap: new Map(), - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"supportsWholeCommandAutoSplit":true'); - expect(result.content).toContain('"fullCommandPasteExample":"$env:GITHUB_TOKEN=...; uvx mcp-server-github --stdio"'); - expect(result.content).toContain('"title":"启动命令"'); - expect(result.content).toContain('"example":"node / uvx / python"'); - expect(result.content).toContain('PowerShell $env:KEY=VALUE;'); - expect(result.content).toContain('"title":"uvx 工具"'); - expect(result.content).toContain('"exampleLaunchPreview":"uvx some-mcp-server"'); - }); - - it('returns the current ai guidance snapshot so the model can inspect active prompts and enabled skills', async () => { - const result = await executeLocalAIToolCall({ - toolCall: buildToolCall('inspect_ai_guidance', {}), - connections: [buildConnection()], - mcpTools: [], - toolContextMap: new Map(), - userPromptSettings: { - global: '回答前先核对上下文。', - database: '生成 SQL 时只读优先。', - jvm: '', - jvmDiagnostic: '', - }, - skills: [{ - id: 'skill-1', - name: '结构审查', - description: '优先核对字段', - systemPrompt: '先看字段和索引,再给结论。', - enabled: true, - scopes: ['database'], - requiredTools: ['get_columns'], - }], - runtime: { - getDatabases: vi.fn(), - getTables: vi.fn(), - }, - }); - - expect(result.success).toBe(true); - expect(result.content).toContain('"customPromptCount":2'); - expect(result.content).toContain('"scope":"global"'); - expect(result.content).toContain('回答前先核对上下文'); - expect(result.content).toContain('"enabledSkillCount":1'); - expect(result.content).toContain('"name":"结构审查"'); - }); - it('returns the current connection snapshot so the model can inspect host, db, and ssh state', async () => { const result = await executeLocalAIToolCall({ toolCall: buildToolCall('inspect_current_connection', {}),