/**
* MCP 服务器集成测试
* 测试完整的服务器-客户端交互
*/
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn, ChildProcess } from 'child_process';
import path from 'path';
describe('MCP Server Integration Tests', () => {
let serverProcess: ChildProcess | null = null;
let client: Client | null = null;
let transport: StdioClientTransport | null = null;
// 测试超时设置
const TEST_TIMEOUT = 30000; // 30秒
beforeAll(async () => {
// 构建服务器路径
const serverPath = path.join(process.cwd(), 'dist', 'index.js');
// 检查构建文件是否存在
try {
require.resolve(serverPath);
} catch (error) {
console.warn(`构建文件不存在: ${serverPath}`);
console.warn('请先运行 npm run build');
return;
}
// 设置环境变量
const env = {
...process.env,
ZENDTAO_BASE_URL: 'http://localhost:3000',
ZENDTAO_TOKEN: 'test-token-for-integration',
NODE_ENV: 'test'
};
// 启动 MCP 服务器
serverProcess = spawn('node', [serverPath], {
env,
stdio: ['pipe', 'pipe', 'inherit'], // stderr 继承到当前进程
detached: false
});
// 等待服务器启动
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('服务器启动超时'));
}, 10000);
serverProcess!.stdout?.on('data', (data) => {
const output = data.toString();
if (output.includes('MCP 服务器已启动') || output.includes('connected')) {
clearTimeout(timeout);
resolve(true);
}
});
serverProcess!.on('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
// 创建客户端传输
transport = new StdioClientTransport({
command: 'node',
args: [serverPath],
env
});
// 创建 MCP 客户端
client = new Client({
name: 'test-client',
version: '1.0.0'
});
// 连接到服务器
await transport!.start();
await client!.connect(transport!);
console.log('✅ MCP 服务器连接成功');
}, TEST_TIMEOUT);
afterAll(async () => {
try {
// 清理连接
if (client) {
await client.close();
}
if (transport) {
await transport.close();
}
if (serverProcess) {
serverProcess.kill('SIGTERM');
// 等待进程结束
await new Promise((resolve) => {
if (serverProcess) {
serverProcess.on('exit', resolve);
} else {
resolve(true);
}
});
}
} catch (error) {
console.warn('清理过程中出现错误:', error);
}
});
// 检查是否可以运行测试
const canRunTests = () => {
if (!client || !serverProcess) {
console.warn('跳过测试:服务器未能正常启动');
return false;
}
return true;
};
describe('服务器基础功能', () => {
it('应该能够列出所有可用工具', async () => {
if (!canRunTests()) return;
try {
const response = await client!.listTools();
expect(response).toBeDefined();
expect(response.tools).toBeDefined();
expect(Array.isArray(response.tools)).toBe(true);
expect(response.tools.length).toBeGreaterThan(0);
// 检查工具命名规范
const toolNames = response.tools.map(tool => tool.name);
console.log('可用工具:', toolNames);
// 验证工具名称符合新的命名约定
expect(toolNames).toContain('zendao_list_projects');
expect(toolNames).toContain('zendao_get_project');
expect(toolNames).toContain('zendao_create_project');
expect(toolNames).toContain('zendao_list_tasks');
expect(toolNames).toContain('zendao_create_task');
expect(toolNames).toContain('zendao_update_task_status');
expect(toolNames).toContain('zendao_batch_create_tasks');
// 验证工具描述
response.tools.forEach(tool => {
expect(tool.description).toBeDefined();
expect(typeof tool.description).toBe('string');
expect(tool.description.length).toBeGreaterThan(0);
});
console.log('✅ 工具列表测试通过');
} catch (error) {
console.error('❌ 工具列表测试失败:', error);
throw error;
}
}, TEST_TIMEOUT);
it('应该提供服务器信息', async () => {
if (!canRunTests()) return;
try {
// 这可以测试服务器的基础连接状态
const response = await client!.listTools();
expect(response.tools.length).toBeGreaterThan(0);
console.log('✅ 服务器信息测试通过');
} catch (error) {
console.error('❌ 服务器信息测试失败:', error);
throw error;
}
}, TEST_TIMEOUT);
});
describe('工具调用测试', () => {
it('应该能够调用 zendao_list_projects 工具', async () => {
if (!canRunTests()) return;
try {
const result = await client!.callTool({
name: 'zendao_list_projects',
arguments: {
page: 1,
limit: 10
}
});
expect(result).toBeDefined();
expect(result.content).toBeDefined();
expect(Array.isArray(result.content)).toBe(true);
expect(result.content.length).toBeGreaterThan(0);
// 验证内容结构
result.content.forEach(content => {
expect(content.type).toBe('text');
expect(content.text).toBeDefined();
expect(typeof content.text).toBe('string');
});
console.log('工具调用结果:', result.content[0].text?.substring(0, 100) + '...');
console.log('✅ zendao_list_projects 工具调用测试通过');
} catch (error) {
console.error('❌ zendao_list_projects 工具调用测试失败:', error);
// 在集成测试中,API 调用失败是预期的,所以我们只测试工具调用本身
expect(error).toBeDefined();
}
}, TEST_TIMEOUT);
it('应该处理无效的参数', async () => {
if (!canRunTests()) return;
try {
await expect(client!.callTool({
name: 'zendao_list_projects',
arguments: {
page: -1 // 无效参数
}
})).rejects.toThrow();
console.log('✅ 无效参数处理测试通过');
} catch (error) {
console.error('❌ 无效参数处理测试失败:', error);
throw error;
}
}, TEST_TIMEOUT);
it('应该处理不存在的工具', async () => {
if (!canRunTests()) return;
try {
await expect(client!.callTool({
name: 'non_existent_tool',
arguments: {}
})).rejects.toThrow();
console.log('✅ 不存在工具处理测试通过');
} catch (error) {
console.error('❌ 不存在工具处理测试失败:', error);
throw error;
}
}, TEST_TIMEOUT);
});
describe('错误处理和恢复', () => {
it('应该能够处理并发请求', async () => {
if (!canRunTests()) return;
try {
// 发起多个并发请求
const requests = Array(5).fill(null).map(() =>
client!.callTool({
name: 'zendao_list_projects',
arguments: { page: 1, limit: 5 }
})
);
const results = await Promise.allSettled(requests);
// 检查结果
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
expect(result.value).toBeDefined();
expect(result.value.content).toBeDefined();
} else {
console.warn(`并发请求 ${index} 失败:`, result.reason);
}
});
console.log('✅ 并发请求测试通过');
} catch (error) {
console.error('❌ 并发请求测试失败:', error);
throw error;
}
}, TEST_TIMEOUT);
});
});
// 运行集成测试的辅助函数
export function runIntegrationTests() {
describe('MCP 服务器完整性检查', () => {
it('应该具备所有必要的项目结构文件', () => {
const fs = require('fs');
const path = require('path');
const requiredFiles = [
'package.json',
'tsconfig.json',
'src/index.ts',
'src/client.ts',
'src/tools/index.ts',
'src/utils/logger.ts',
'src/utils/cache.ts'
];
requiredFiles.forEach(file => {
const filePath = path.join(process.cwd(), file);
expect(fs.existsSync(filePath)).toBe(true);
});
console.log('✅ 项目结构完整性检查通过');
});
it('应该能够成功构建项目', async () => {
const { execSync } = require('child_process');
try {
execSync('npm run build', { stdio: 'pipe' });
console.log('✅ 项目构建测试通过');
} catch (error) {
console.error('❌ 项目构建失败:', error.message);
throw error;
}
}, 60000); // 1分钟超时
});
}