freature: 支持mcp 协议
This commit is contained in:
172
src/tests/adapters/mcp-output-adapter.unit.test.js
Normal file
172
src/tests/adapters/mcp-output-adapter.unit.test.js
Normal file
@ -0,0 +1,172 @@
|
||||
const { MCPOutputAdapter } = require('../../lib/adapters/MCPOutputAdapter');
|
||||
|
||||
describe('MCPOutputAdapter 单元测试', () => {
|
||||
let adapter;
|
||||
|
||||
beforeEach(() => {
|
||||
adapter = new MCPOutputAdapter();
|
||||
});
|
||||
|
||||
describe('基础功能测试', () => {
|
||||
test('MCPOutputAdapter类应该能创建', () => {
|
||||
expect(adapter).toBeDefined();
|
||||
expect(adapter).toBeInstanceOf(MCPOutputAdapter);
|
||||
});
|
||||
|
||||
test('应该有convertToMCPFormat方法', () => {
|
||||
expect(typeof adapter.convertToMCPFormat).toBe('function');
|
||||
});
|
||||
|
||||
test('应该有sanitizeText方法', () => {
|
||||
expect(typeof adapter.sanitizeText).toBe('function');
|
||||
});
|
||||
|
||||
test('应该有handleError方法', () => {
|
||||
expect(typeof adapter.handleError).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('文本转换测试', () => {
|
||||
test('应该保留emoji和中文字符', () => {
|
||||
const input = '🎯 PromptX 系统初始化完成!';
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
expect(result.content).toBeDefined();
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(result.content[0].text).toContain('🎯');
|
||||
expect(result.content[0].text).toContain('PromptX');
|
||||
});
|
||||
|
||||
test('应该保留markdown格式', () => {
|
||||
const input = '## 🎯 角色激活总结\n✅ **assistant 角色已完全激活!**';
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
expect(result.content[0].text).toContain('##');
|
||||
expect(result.content[0].text).toContain('**');
|
||||
expect(result.content[0].text).toContain('✅');
|
||||
});
|
||||
|
||||
test('应该处理复杂的PromptX输出格式', () => {
|
||||
const input = `============================================================
|
||||
🎯 锦囊目的:激活特定AI角色,分析并生成具体的思维模式、行为模式和知识学习计划
|
||||
============================================================
|
||||
|
||||
📜 锦囊内容:
|
||||
🎭 **角色激活完成:assistant** - 所有技能已自动加载`;
|
||||
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
expect(result.content[0].text).toContain('🎯');
|
||||
expect(result.content[0].text).toContain('📜');
|
||||
expect(result.content[0].text).toContain('🎭');
|
||||
expect(result.content[0].text).toContain('====');
|
||||
});
|
||||
|
||||
test('应该处理多行内容', () => {
|
||||
const input = `行1\n行2\n行3`;
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
expect(result.content[0].text).toContain('行1');
|
||||
expect(result.content[0].text).toContain('行2');
|
||||
expect(result.content[0].text).toContain('行3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('对象输入处理测试', () => {
|
||||
test('应该处理PouchOutput对象', () => {
|
||||
const mockPouchOutput = {
|
||||
toString: () => '🎯 模拟的PouchOutput输出'
|
||||
};
|
||||
|
||||
const result = adapter.convertToMCPFormat(mockPouchOutput);
|
||||
expect(result.content[0].text).toBe('🎯 模拟的PouchOutput输出');
|
||||
});
|
||||
|
||||
test('应该处理普通对象', () => {
|
||||
const input = { message: '测试消息', status: 'success' };
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
expect(result.content[0].text).toContain('message');
|
||||
expect(result.content[0].text).toContain('测试消息');
|
||||
});
|
||||
|
||||
test('应该处理null和undefined', () => {
|
||||
const nullResult = adapter.convertToMCPFormat(null);
|
||||
const undefinedResult = adapter.convertToMCPFormat(undefined);
|
||||
|
||||
expect(nullResult.content[0].text).toBe('null');
|
||||
expect(undefinedResult.content[0].text).toBe('undefined');
|
||||
});
|
||||
});
|
||||
|
||||
describe('错误处理测试', () => {
|
||||
test('应该处理转换错误', () => {
|
||||
const result = adapter.handleError(new Error('测试错误'));
|
||||
|
||||
expect(result.content[0].text).toContain('❌');
|
||||
expect(result.content[0].text).toContain('测试错误');
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
|
||||
test('应该处理未知错误', () => {
|
||||
const result = adapter.handleError('字符串错误');
|
||||
|
||||
expect(result.content[0].text).toContain('❌');
|
||||
expect(result.content[0].text).toContain('字符串错误');
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
|
||||
test('错误输出应该符合MCP格式', () => {
|
||||
const result = adapter.handleError(new Error('测试'));
|
||||
|
||||
expect(result.content).toBeDefined();
|
||||
expect(Array.isArray(result.content)).toBe(true);
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(typeof result.content[0].text).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('边界情况测试', () => {
|
||||
test('应该处理空字符串', () => {
|
||||
const result = adapter.convertToMCPFormat('');
|
||||
expect(result.content[0].text).toBe('');
|
||||
});
|
||||
|
||||
test('应该处理非常长的文本', () => {
|
||||
const longText = 'a'.repeat(10000);
|
||||
const result = adapter.convertToMCPFormat(longText);
|
||||
expect(result.content[0].text).toBe(longText);
|
||||
});
|
||||
|
||||
test('应该处理特殊字符', () => {
|
||||
const specialChars = '\\n\\r\\t"\'{|}[]()';
|
||||
const result = adapter.convertToMCPFormat(specialChars);
|
||||
expect(result.content[0].text).toContain(specialChars);
|
||||
});
|
||||
});
|
||||
|
||||
describe('输出格式验证测试', () => {
|
||||
test('输出应该始终符合MCP content格式', () => {
|
||||
const inputs = [
|
||||
'simple text',
|
||||
'🎯 emoji text',
|
||||
{ object: 'data' },
|
||||
['array', 'data'],
|
||||
null,
|
||||
undefined
|
||||
];
|
||||
|
||||
inputs.forEach(input => {
|
||||
const result = adapter.convertToMCPFormat(input);
|
||||
|
||||
// 验证MCP标准格式
|
||||
expect(result).toHaveProperty('content');
|
||||
expect(Array.isArray(result.content)).toBe(true);
|
||||
expect(result.content).toHaveLength(1);
|
||||
expect(result.content[0]).toHaveProperty('type', 'text');
|
||||
expect(result.content[0]).toHaveProperty('text');
|
||||
expect(typeof result.content[0].text).toBe('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
308
src/tests/commands/mcp-server.unit.test.js
Normal file
308
src/tests/commands/mcp-server.unit.test.js
Normal file
@ -0,0 +1,308 @@
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// 测试辅助函数
|
||||
function normalizeOutput(output) {
|
||||
return output
|
||||
.replace(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/g, 'TIMESTAMP')
|
||||
.replace(/\[\d+ms\]/g, '[TIME]')
|
||||
.replace(/PS [^>]+>/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
describe('MCP Server 项目结构验证', () => {
|
||||
test('现有CLI入口文件存在', () => {
|
||||
expect(fs.existsSync('src/bin/promptx.js')).toBe(true);
|
||||
});
|
||||
|
||||
test('commands目录已创建', () => {
|
||||
expect(fs.existsSync('src/lib/commands')).toBe(true);
|
||||
});
|
||||
|
||||
test('MCP SDK依赖已安装', () => {
|
||||
const pkg = require('../../../package.json');
|
||||
expect(pkg.dependencies['@modelcontextprotocol/sdk']).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLI函数调用基线测试', () => {
|
||||
let cli;
|
||||
|
||||
beforeEach(() => {
|
||||
// 重新导入以确保清洁状态
|
||||
delete require.cache[require.resolve('../../lib/core/pouch')];
|
||||
cli = require('../../lib/core/pouch').cli;
|
||||
});
|
||||
|
||||
test('cli.execute函数可用性', () => {
|
||||
expect(typeof cli.execute).toBe('function');
|
||||
});
|
||||
|
||||
test('init命令函数调用', async () => {
|
||||
const result = await cli.execute('init', []);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.toString()).toContain('🎯');
|
||||
}, 10000);
|
||||
|
||||
test('hello命令函数调用', async () => {
|
||||
const result = await cli.execute('hello', []);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.toString()).toContain('🎯');
|
||||
}, 10000);
|
||||
|
||||
test('action命令函数调用', async () => {
|
||||
const result = await cli.execute('action', ['assistant']);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.toString()).toContain('⚡');
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
describe('MCP适配器单元测试', () => {
|
||||
let mcpServer;
|
||||
|
||||
beforeEach(() => {
|
||||
try {
|
||||
const { MCPServerCommand } = require('../../lib/commands/MCPServerCommand');
|
||||
mcpServer = new MCPServerCommand();
|
||||
} catch (error) {
|
||||
mcpServer = null;
|
||||
}
|
||||
});
|
||||
|
||||
describe('基础结构测试', () => {
|
||||
test('MCPServerCommand类应该能导入', () => {
|
||||
expect(() => {
|
||||
require('../../lib/commands/MCPServerCommand');
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('MCPServerCommand应该有必要方法', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true); // 跳过测试如果类还没实现
|
||||
return;
|
||||
}
|
||||
|
||||
expect(typeof mcpServer.execute).toBe('function');
|
||||
expect(typeof mcpServer.getToolDefinitions).toBe('function');
|
||||
expect(typeof mcpServer.convertMCPToCliParams).toBe('function');
|
||||
expect(typeof mcpServer.callTool).toBe('function');
|
||||
expect(typeof mcpServer.log).toBe('function');
|
||||
});
|
||||
|
||||
test('调试模式应该可配置', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
expect(typeof mcpServer.debug).toBe('boolean');
|
||||
expect(typeof mcpServer.log).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('参数转换测试', () => {
|
||||
test('promptx_init参数转换', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = mcpServer.convertMCPToCliParams('promptx_init', {});
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
test('promptx_action参数转换', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = mcpServer.convertMCPToCliParams('promptx_action', {
|
||||
role: 'product-manager'
|
||||
});
|
||||
expect(result).toEqual(['product-manager']);
|
||||
});
|
||||
|
||||
test('promptx_learn参数转换', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = mcpServer.convertMCPToCliParams('promptx_learn', {
|
||||
resource: 'thought://creativity'
|
||||
});
|
||||
expect(result).toEqual(['thought://creativity']);
|
||||
});
|
||||
|
||||
test('promptx_remember参数转换', () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = mcpServer.convertMCPToCliParams('promptx_remember', {
|
||||
content: '测试内容',
|
||||
tags: '测试 标签'
|
||||
});
|
||||
expect(result).toEqual(['测试内容', '--tags', '测试 标签']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('工具调用测试', () => {
|
||||
test('init工具调用', async () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mcpServer.callTool('promptx_init', {});
|
||||
expect(result.content).toBeDefined();
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(result.content[0].text).toContain('🎯');
|
||||
}, 15000);
|
||||
|
||||
test('hello工具调用', async () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mcpServer.callTool('promptx_hello', {});
|
||||
expect(result.content).toBeDefined();
|
||||
expect(result.content[0].text).toContain('🎯');
|
||||
}, 15000);
|
||||
|
||||
test('action工具调用', async () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mcpServer.callTool('promptx_action', {
|
||||
role: 'assistant'
|
||||
});
|
||||
expect(result.content).toBeDefined();
|
||||
expect(result.content[0].text).toContain('⚡');
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
describe('错误处理测试', () => {
|
||||
test('无效工具名处理', async () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mcpServer.callTool('invalid_tool', {});
|
||||
expect(result.content[0].text).toContain('❌');
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
|
||||
test('缺少必需参数处理', async () => {
|
||||
if (!mcpServer) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mcpServer.callTool('promptx_action', {});
|
||||
expect(result.content[0].text).toContain('❌');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MCP vs CLI 一致性测试', () => {
|
||||
let mcpServer;
|
||||
let cli;
|
||||
|
||||
beforeEach(() => {
|
||||
try {
|
||||
const { MCPServerCommand } = require('../../lib/commands/MCPServerCommand');
|
||||
mcpServer = new MCPServerCommand();
|
||||
cli = require('../../lib/core/pouch').cli;
|
||||
} catch (error) {
|
||||
mcpServer = null;
|
||||
cli = null;
|
||||
}
|
||||
});
|
||||
|
||||
test('init: MCP vs CLI 输出一致性', async () => {
|
||||
if (!mcpServer || !cli) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过MCP调用
|
||||
const mcpResult = await mcpServer.callTool('promptx_init', {});
|
||||
const mcpOutput = normalizeOutput(mcpResult.content[0].text);
|
||||
|
||||
// 直接CLI函数调用
|
||||
const cliResult = await cli.execute('init', []);
|
||||
const cliOutput = normalizeOutput(cliResult.toString());
|
||||
|
||||
// 验证输出一致性
|
||||
expect(mcpOutput).toBe(cliOutput);
|
||||
}, 15000);
|
||||
|
||||
test('action: MCP vs CLI 输出一致性', async () => {
|
||||
if (!mcpServer || !cli) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const role = 'assistant';
|
||||
|
||||
const mcpResult = await mcpServer.callTool('promptx_action', { role });
|
||||
const mcpOutput = normalizeOutput(mcpResult.content[0].text);
|
||||
|
||||
const cliResult = await cli.execute('action', [role]);
|
||||
const cliOutput = normalizeOutput(cliResult.toString());
|
||||
|
||||
expect(mcpOutput).toBe(cliOutput);
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
describe('MCP协议通信测试', () => {
|
||||
test('工具定义获取', () => {
|
||||
let mcpServer;
|
||||
try {
|
||||
const { MCPServerCommand } = require('../../lib/commands/MCPServerCommand');
|
||||
mcpServer = new MCPServerCommand();
|
||||
} catch (error) {
|
||||
expect(true).toBe(true); // 跳过如果还没实现
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = mcpServer.getToolDefinitions();
|
||||
expect(tools).toHaveLength(6);
|
||||
|
||||
const toolNames = tools.map(t => t.name);
|
||||
expect(toolNames).toContain('promptx_init');
|
||||
expect(toolNames).toContain('promptx_hello');
|
||||
expect(toolNames).toContain('promptx_action');
|
||||
expect(toolNames).toContain('promptx_learn');
|
||||
expect(toolNames).toContain('promptx_recall');
|
||||
expect(toolNames).toContain('promptx_remember');
|
||||
});
|
||||
|
||||
test('工具Schema验证', () => {
|
||||
let mcpServer;
|
||||
try {
|
||||
const { MCPServerCommand } = require('../../lib/commands/MCPServerCommand');
|
||||
mcpServer = new MCPServerCommand();
|
||||
} catch (error) {
|
||||
expect(true).toBe(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = mcpServer.getToolDefinitions();
|
||||
const actionTool = tools.find(t => t.name === 'promptx_action');
|
||||
|
||||
expect(actionTool.inputSchema.properties.role).toBeDefined();
|
||||
expect(actionTool.inputSchema.required).toContain('role');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user