Skip to main content
Glama

Frontend Test Generation & Code Review MCP Server

analyze-test-matrix.ts6.01 kB
import { FetchDiffTool } from './fetch-diff.js'; import { ResolvePathTool } from './resolve-path.js'; import { detectProjectTestStack } from './detect-stack.js'; import { TestMatrixAnalyzer } from '../agents/test-matrix-analyzer.js'; import { StateManager } from '../state/manager.js'; import { getTestStackDetectionPath } from '../utils/project-root.js'; import type { TestMatrixAnalysis } from '../schemas/test-matrix.js'; import { logger } from '../utils/logger.js'; export interface AnalyzeTestMatrixInput { revisionId: string; projectRoot?: string; forceRefresh?: boolean; } export class AnalyzeTestMatrixTool { constructor( private fetchDiffTool: FetchDiffTool, private resolvePathTool: ResolvePathTool, private stateManager: StateManager, private analyzer: TestMatrixAnalyzer ) {} async analyze(input: AnalyzeTestMatrixInput): Promise<TestMatrixAnalysis> { const startTime = Date.now(); // 1. 获取 diff const diff = await this.fetchDiffTool.fetch({ revisionId: input.revisionId, forceRefresh: input.forceRefresh || false, }); // 过滤前端文件 const frontendDiff = this.fetchDiffTool.filterFrontendFiles(diff); // 2. 通过 resolve-path 工具解析项目根目录 const filePaths = frontendDiff.files.map(f => f.path); const resolveResult = await this.resolvePathTool.resolve({ paths: filePaths, projectRoot: input.projectRoot, // 传递 projectRoot 参数 }); logger.info('Project root resolved', { root: resolveResult.root, isMonorepo: resolveResult.isMonorepo, workspaceType: resolveResult.workspaceType, }); // 构造 ProjectRoot 对象(兼容原有逻辑) const projectRoot = { root: resolveResult.root, isMonorepo: resolveResult.isMonorepo, workspaceType: resolveResult.workspaceType, }; // 3. 检测测试栈 const testDetectionPath = getTestStackDetectionPath( projectRoot, filePaths[0] ); const stack = await detectProjectTestStack(testDetectionPath); const framework = stack.unit || 'vitest'; logger.info('Test stack detected', { framework, detectionPath: testDetectionPath, }); // 4. 构建上下文 const context = { diff: frontendDiff.numberedRaw || frontendDiff.raw, files: frontendDiff.files.map(f => ({ path: f.path, content: f.hunks.map(h => h.lines.join('\n')).join('\n'), })), framework, }; // 验证 diff 内容 if (!context.diff || context.diff.trim().length === 0) { throw new Error(`Diff 内容为空。文件数量: ${frontendDiff.files.length}`); } if (context.files.length === 0) { throw new Error(`没有前端文件变更。总文件数: ${diff.files.length}`); } logger.info('Context prepared for analysis', { diffLength: context.diff.length, filesCount: context.files.length, filePaths: context.files.map(f => f.path), }); // 5. 执行矩阵分析 logger.info('Analyzing test matrix...', { diffFilesCount: frontendDiff.files.length, diffLength: frontendDiff.raw?.length || 0, }); const analysisResult = await this.analyzer.execute(context); if (analysisResult.items.length === 0) { throw new Error('Test matrix analysis failed: no items returned'); } const matrixData = analysisResult.items[0]; // 检查是否有功能变更 if (!matrixData.features || matrixData.features.length === 0) { logger.warn('No features detected in test matrix analysis', { itemsLength: analysisResult.items.length, matrixDataKeys: Object.keys(matrixData), rawResponse: JSON.stringify(matrixData, null, 2).substring(0, 500), }); throw new Error( `未检测到功能变更。\n` + `可能原因:\n` + `1. diff 中没有前端代码变更\n` + `2. 变更都是格式调整或注释修改\n` + `3. AI 分析失败(请检查日志)\n\n` + `diff 文件数量: ${frontendDiff.files.length}\n` + `文件路径: ${frontendDiff.files.map(f => f.path).join(', ')}` ); } logger.info('Features detected', { featuresCount: matrixData.features.length, scenariosCount: matrixData.scenarios?.length || 0, featureNames: matrixData.features.map(f => f.name).join(', '), }); // 6. 计算统计信息 const coverageStats = { 'happy-path': 0, 'edge-case': 0, 'error-path': 0, 'state-change': 0, }; for (const scenario of matrixData.scenarios) { const scenarioType = scenario.scenario; // 只统计 schema 中定义的 4 种类型 if (scenarioType === 'happy-path' || scenarioType === 'edge-case' || scenarioType === 'error-path' || scenarioType === 'state-change') { coverageStats[scenarioType]++; } } const estimatedTests = matrixData.scenarios.reduce( (sum, s) => sum + s.testCases.length, 0 ); // 7. 构建结果 const result: TestMatrixAnalysis = { matrix: { features: matrixData.features, scenarios: matrixData.scenarios, summary: { totalFeatures: matrixData.features.length, totalScenarios: matrixData.scenarios.length, estimatedTests, coverage: coverageStats, }, }, metadata: { diffId: diff.diffId || '', revisionId: input.revisionId, framework, duration: Date.now() - startTime, }, }; // 8. 保存矩阵到状态(供后续生成测试用例使用) await this.stateManager.saveTestMatrix(input.revisionId, result.matrix); logger.info('Test matrix analysis completed', { features: result.matrix.summary.totalFeatures, scenarios: result.matrix.summary.totalScenarios, estimatedTests: result.matrix.summary.estimatedTests, }); return result; } }

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