Skip to main content
Glama
NorthSeacoder

Frontend Test Generation & Code Review MCP Server

analyze-test-matrix.ts8.1 kB
/** * AnalyzeTestMatrixTool - 封装 TestMatrixAnalyzer 为 MCP 工具 * * 职责: * 1. 解析传入的 diff(git、raw diff 等) * 2. 分析代码变更的功能清单和测试矩阵 * 3. 检测测试框架 * 4. 返回测试矩阵结果供后续生成使用 */ import { z } from 'zod'; import { BaseTool, ToolMetadata } from '../core/base-tool.js'; import { TestMatrixAnalyzer } from '../agents/test-matrix-analyzer.js'; import { BaseAnalyzeTestMatrix } from './base-analyze-test-matrix.js'; import { ResolvePathTool } from './resolve-path.js'; import { OpenAIClient } from '../clients/openai.js'; import { StateManager } from '../state/manager.js'; import { logger } from '../utils/logger.js'; import { parseDiff, generateNumberedDiff } from '../utils/diff-parser.js'; import { isFrontendFile } from '../schemas/diff.js'; import type { FeatureItem, TestScenarioItem } from '../schemas/test-matrix.js'; // Zod schema for AnalyzeTestMatrixInput export const AnalyzeTestMatrixInputSchema = z.object({ rawDiff: z.string().describe('REQUIRED. Unified diff 格式的原始文本(git diff 或其他工具生成的 diff)'), identifier: z.string().optional().describe('唯一标识符(如 MR ID、PR ID、commit hash)'), projectRoot: z.string().optional().describe('项目根目录绝对路径(强烈推荐提供,用于检测测试框架和解析文件路径)'), metadata: z.object({ title: z.string().optional(), author: z.string().optional(), mergeRequestId: z.string().optional(), commitHash: z.string().optional(), branch: z.string().optional(), }).optional().describe('可选的元数据'), }); export interface AnalyzeTestMatrixInput { rawDiff: string; identifier?: string; projectRoot?: string; metadata?: { title?: string; author?: string; mergeRequestId?: string; commitHash?: string; branch?: string; }; } export interface AnalyzeTestMatrixOutput { revisionId: string; features: FeatureItem[]; scenarios: TestScenarioItem[]; framework: string; projectRoot: string; statistics: { totalFeatures: number; totalScenarios: number; estimatedTests: number; featuresByType: Record<string, number>; scenariosByType: Record<string, number>; }; } export class AnalyzeTestMatrixTool extends BaseTool<AnalyzeTestMatrixInput, AnalyzeTestMatrixOutput> { private baseAnalyzer: BaseAnalyzeTestMatrix; constructor( openai: OpenAIClient, state: StateManager ) { super(); const resolvePathTool = new ResolvePathTool(); const analyzer = new TestMatrixAnalyzer(openai); this.baseAnalyzer = new BaseAnalyzeTestMatrix(resolvePathTool, state, analyzer); } // Expose Zod schema for FastMCP getZodSchema() { return AnalyzeTestMatrixInputSchema; } getMetadata(): ToolMetadata { return { name: 'analyze-test-matrix', description: '分析代码变更的功能清单和测试矩阵,这是测试生成的第一步。\n\n' + '🔍 分析内容:\n' + '• 功能清单(变更涉及的功能点)\n' + '• 测试矩阵(每个功能需要的测试场景)\n' + '• 测试框架检测(Vitest/Jest)\n' + '• 项目根目录检测\n\n' + '📊 输出信息:\n' + '• features: 功能清单数组\n' + '• scenarios: 测试场景数组\n' + '• framework: 检测到的测试框架\n' + '• projectRoot: 项目根目录路径\n' + '• statistics: 统计信息\n\n' + '💡 推荐工作流:\n' + '1. 在客户端或工作流中获取 unified diff(git diff 输出)\n' + '2. 执行 pwd 命令获取当前工作目录\n' + '3. 调用此工具,传入 rawDiff 以及 projectRoot 参数\n' + '4. 保存返回的 projectRoot 值,供 generate-tests 使用\n\n' + '⚠️ 注意:projectRoot 参数虽然可选,但强烈建议提供,否则可能导致路径解析失败。', inputSchema: { type: 'object', properties: { rawDiff: { type: 'string', description: 'Unified diff 格式的原始文本(git diff 或其他工具生成的 diff)', }, identifier: { type: 'string', description: '唯一标识符(如 MR ID、PR ID、commit hash)', }, projectRoot: { type: 'string', description: '项目根目录绝对路径(强烈推荐提供,用于检测测试框架和解析文件路径)', }, metadata: { type: 'object', description: '可选的元数据', }, }, required: ['rawDiff'], }, category: 'test-generation', version: '3.0.0', }; } protected async executeImpl(input: AnalyzeTestMatrixInput): Promise<AnalyzeTestMatrixOutput> { const { rawDiff, identifier, projectRoot, metadata } = input; // Determine the identifier to use const effectiveId = identifier || metadata?.commitHash || 'unknown'; // 1. 解析 diff logger.info(`[AnalyzeTestMatrixTool] Parsing raw diff for ${effectiveId}...`); const parsedDiff = parseDiff(rawDiff, effectiveId, { diffId: metadata?.commitHash || identifier, title: metadata?.title, summary: metadata?.mergeRequestId || metadata?.commitHash, author: metadata?.author, }); parsedDiff.numberedRaw = generateNumberedDiff(parsedDiff); parsedDiff.metadata = metadata ? { ...metadata } : {}; // Filter frontend files const frontendFiles = parsedDiff.files.filter((f) => isFrontendFile(f.path)); parsedDiff.files = frontendFiles; const diff = parsedDiff; if (diff.files.length === 0) { throw new Error(`No frontend files found in ${effectiveId}`); } // 2. 使用 BaseAnalyzeTestMatrix 执行分析 logger.info(`[AnalyzeTestMatrixTool] Analyzing test matrix...`); const analysisResult = await this.baseAnalyzer.analyze({ diff, revisionId: effectiveId, projectRoot, metadata: metadata ? { commitInfo: metadata.commitHash ? { hash: metadata.commitHash, author: metadata.author || 'unknown', date: new Date().toISOString(), message: metadata.title || '', } : undefined, } : undefined, }); // 3. 转换为工具输出格式 const statistics = this.generateStatistics( analysisResult.matrix.features, analysisResult.matrix.scenarios ); logger.info(`[AnalyzeTestMatrixTool] Analysis completed`, { identifier: effectiveId, totalFeatures: analysisResult.matrix.features.length, totalScenarios: analysisResult.matrix.scenarios.length, estimatedTests: statistics.estimatedTests, }); return { revisionId: effectiveId, features: analysisResult.matrix.features, scenarios: analysisResult.matrix.scenarios, framework: analysisResult.metadata.framework || 'vitest', projectRoot: projectRoot || process.cwd(), statistics, }; } private generateStatistics( features: FeatureItem[], scenarios: TestScenarioItem[] ): { totalFeatures: number; totalScenarios: number; estimatedTests: number; featuresByType: Record<string, number>; scenariosByType: Record<string, number>; } { const featuresByType: Record<string, number> = {}; const scenariosByType: Record<string, number> = {}; for (const feature of features) { featuresByType[feature.type] = (featuresByType[feature.type] || 0) + 1; } for (const scenario of scenarios) { scenariosByType[scenario.scenario] = (scenariosByType[scenario.scenario] || 0) + 1; } // 估算测试数量:每个场景可能生成 1-3 个测试用例 const estimatedTests = scenarios.reduce((sum, s) => sum + (s.testCases?.length || 2), 0); return { totalFeatures: features.length, totalScenarios: scenarios.length, estimatedTests, featuresByType, scenariosByType, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/NorthSeacoder/fe-testgen-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server