♻️ refactor(ai-tests): 拆分 AI 配置探针测试

- 将 AI runtime、provider、safety、MCP 与 guidance 探针测试移入独立文件

- 缩减 aiLocalToolExecutor.test.ts 体积,保持执行器行为不变

- 通过定向测试和生产构建验证
This commit is contained in:
Syngnat
2026-06-10 10:00:37 +08:00
parent 0834d8cb3d
commit b4affbc1d5
2 changed files with 358 additions and 332 deletions

View File

@@ -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<string, unknown>): 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":"结构审查"');
});
});

View File

@@ -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', {}),