Skip to main content
Glama
NorthSeacoder

Frontend Test Generation & Code Review MCP Server

analyze-raw-diff-test-matrix.ts8.59 kB
/** * AnalyzeRawDiffTestMatrixTool - 从外部 raw diff 分析测试矩阵(n8n/GitLab 集成) * * 职责: * 1. 接受外部传入的 raw diff 文本 * 2. 解析 diff 并分析测试矩阵 * 3. 返回功能清单和测试场景 */ import { z } from 'zod'; import { BaseTool, ToolMetadata } from '../core/base-tool.js'; import { BaseAnalyzeTestMatrix } from './base-analyze-test-matrix.js'; import { ResolvePathTool } from './resolve-path.js'; import { TestMatrixAnalyzer } from '../agents/test-matrix-analyzer.js'; import { OpenAIClient } from '../clients/openai.js'; import { StateManager } from '../state/manager.js'; import { parseDiff, generateNumberedDiff } from '../utils/diff-parser.js'; import { isFrontendFile } from '../schemas/diff.js'; import { logger } from '../utils/logger.js'; import type { FeatureItem, TestScenarioItem } from '../schemas/test-matrix.js'; // Zod schema for AnalyzeRawDiffTestMatrixInput export const AnalyzeRawDiffTestMatrixInputSchema = z.object({ rawDiff: z.string().describe('Unified diff 格式的原始文本(必需)'), identifier: z.string().describe('唯一标识符,如 MR ID、PR ID、commit hash(必需)'), projectRoot: z.string().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('可选的元数据'), forceRefresh: z.boolean().optional().describe('强制刷新缓存(默认 false)'), }); export interface AnalyzeRawDiffTestMatrixInput { rawDiff: string; // Unified diff 格式的原始文本 identifier: string; // 唯一标识符(如 MR ID、PR ID) projectRoot: string; // 项目根目录绝对路径 metadata?: { title?: string; author?: string; mergeRequestId?: string; commitHash?: string; branch?: string; }; forceRefresh?: boolean; } export interface AnalyzeRawDiffTestMatrixOutput { identifier: 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 AnalyzeRawDiffTestMatrixTool extends BaseTool< AnalyzeRawDiffTestMatrixInput, AnalyzeRawDiffTestMatrixOutput > { 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 AnalyzeRawDiffTestMatrixInputSchema; } getMetadata(): ToolMetadata { return { name: 'analyze-raw-diff-test-matrix', description: '从外部传入的 raw diff 内容分析测试矩阵(专为 n8n/GitLab 工作流设计)。\n\n' + '💡 特性:\n' + '• 接受标准 unified diff 格式\n' + '• 支持 GitLab MR、GitHub PR 等平台\n' + '• 分步式工作流,先分析后决策\n' + '• 自动检测测试框架\n\n' + '📋 使用场景:\n' + '• n8n 工作流中,GitLab 节点已获取 diff\n' + '• CI/CD 流程中的增量测试分析\n' + '• 分步式测试生成(先分析矩阵,再决定是否生成)\n\n' + '⚠️ 注意:\n' + '• rawDiff 必须是标准 unified diff 格式\n' + '• projectRoot 必须是有效的项目根目录\n' + '• 返回的矩阵可用于 generate-tests-from-raw-diff', inputSchema: { type: 'object', properties: { rawDiff: { type: 'string', description: 'Unified diff 格式的原始文本(必需)', }, identifier: { type: 'string', description: '唯一标识符,如 MR ID、PR ID、commit hash(必需)', }, projectRoot: { type: 'string', description: '项目根目录绝对路径(必需)', }, metadata: { type: 'object', properties: { title: { type: 'string' }, author: { type: 'string' }, mergeRequestId: { type: 'string' }, commitHash: { type: 'string' }, branch: { type: 'string' }, }, description: '可选的元数据', }, forceRefresh: { type: 'boolean', description: '强制刷新缓存(默认 false)', }, }, required: ['rawDiff', 'identifier', 'projectRoot'], }, category: 'test-generation', version: '3.0.0', }; } protected async executeImpl( input: AnalyzeRawDiffTestMatrixInput ): Promise<AnalyzeRawDiffTestMatrixOutput> { const { rawDiff, identifier, projectRoot, metadata } = input; logger.info('[AnalyzeRawDiffTestMatrixTool] Parsing raw diff...', { identifier, diffLength: rawDiff.length, }); // 1. 解析 raw diff const diff = parseDiff(rawDiff, identifier, { diffId: metadata?.commitHash || identifier, title: metadata?.title, summary: metadata?.mergeRequestId || metadata?.commitHash, author: metadata?.author, }); // 生成带行号的 diff diff.numberedRaw = generateNumberedDiff(diff); // 过滤前端文件 const frontendFiles = diff.files.filter((f) => isFrontendFile(f.path)); diff.files = frontendFiles; if (diff.files.length === 0) { throw new Error(`No frontend files found in diff (identifier: ${identifier})`); } logger.info('[AnalyzeRawDiffTestMatrixTool] Frontend files found', { count: diff.files.length, files: diff.files.map((f) => f.path), }); // 2. 使用 BaseAnalyzeTestMatrix 执行分析 const analysisResult = await this.baseAnalyzer.analyze({ diff, revisionId: identifier, projectRoot, metadata: { commitInfo: metadata?.commitHash ? { hash: metadata.commitHash, author: metadata.author || 'unknown', date: new Date().toISOString(), message: metadata.title || '', } : undefined, }, messages: { emptyDiff: () => `Raw diff is empty (identifier: ${identifier})`, noFrontendFiles: () => `No frontend files found in raw diff (identifier: ${identifier}). Total files: ${diff.files.length}`, noFeatures: () => `No features detected in raw diff (identifier: ${identifier}).\n` + `Files analyzed: ${diff.files.map((f) => f.path).join(', ')}`, }, }); // 3. 转换为工具输出格式 const statistics = this.generateStatistics( analysisResult.matrix.features, analysisResult.matrix.scenarios ); logger.info('[AnalyzeRawDiffTestMatrixTool] Analysis completed', { identifier, totalFeatures: analysisResult.matrix.features.length, totalScenarios: analysisResult.matrix.scenarios.length, estimatedTests: statistics.estimatedTests, }); return { identifier, features: analysisResult.matrix.features, scenarios: analysisResult.matrix.scenarios, framework: analysisResult.metadata.framework || 'vitest', projectRoot, 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; } 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