claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
864 lines (758 loc) • 30 kB
text/typescript
/**
* Test Intelligence MCP Tools
*
* 5 MCP tools for AI-powered test intelligence:
* 1. test/select-predictive - Predictive test selection using RL
* 2. test/flaky-detect - Flaky test detection and analysis
* 3. test/coverage-gaps - Test coverage gap identification
* 4. test/mutation-optimize - Mutation testing optimization
* 5. test/generate-suggest - Test case generation suggestions
*/
import type {
MCPTool,
MCPToolResult,
ToolContext,
SelectPredictiveOutput,
SelectedTest,
FlakyDetectOutput,
FlakyTest,
CoverageGapsOutput,
CoverageGap,
MutationOptimizeOutput,
OptimizedMutation,
GenerateSuggestOutput,
TestSuggestion,
CodeChange,
} from './types.js';
import {
SelectPredictiveInputSchema,
FlakyDetectInputSchema,
CoverageGapsInputSchema,
MutationOptimizeInputSchema,
GenerateSuggestInputSchema,
successResult,
errorResult,
} from './types.js';
// ============================================================================
// Default Logger
// ============================================================================
const defaultLogger = {
debug: (msg: string, meta?: Record<string, unknown>) => console.debug(`[test-intelligence] ${msg}`, meta),
info: (msg: string, meta?: Record<string, unknown>) => console.info(`[test-intelligence] ${msg}`, meta),
warn: (msg: string, meta?: Record<string, unknown>) => console.warn(`[test-intelligence] ${msg}`, meta),
error: (msg: string, meta?: Record<string, unknown>) => console.error(`[test-intelligence] ${msg}`, meta),
};
// ============================================================================
// Tool 1: test/select-predictive
// ============================================================================
async function selectPredictiveHandler(
input: Record<string, unknown>,
context?: ToolContext
): Promise<MCPToolResult> {
const logger = context?.logger ?? defaultLogger;
const startTime = performance.now();
try {
const validationResult = SelectPredictiveInputSchema.safeParse(input);
if (!validationResult.success) {
return errorResult(`Invalid input: ${validationResult.error.message}`);
}
const { changes, strategy, budget } = validationResult.data;
logger.debug('Selecting tests predictively', { strategy, fileCount: changes.files?.length });
// Parse code changes
const codeChanges: CodeChange[] = (changes.files ?? []).map(file => ({
file,
type: 'modified' as const,
linesAdded: 10,
linesRemoved: 5,
}));
// Use learning bridge if available
let predictions: SelectedTest[] = [];
if (context?.learningBridge?.isReady()) {
const predicted = await context.learningBridge.predictFailingTests(
codeChanges,
budget?.maxTests ?? 100
);
predictions = predicted.map((p, idx) => ({
testId: p.testId,
testName: p.testId.split('/').pop() ?? p.testId,
suite: p.testId.split('/').slice(0, -1).join('/') || 'default',
priority: predicted.length - idx,
reason: p.reason,
estimatedDuration: 1000 + Math.random() * 5000,
failureProbability: p.failureProbability,
}));
} else {
// Fallback: generate mock predictions based on strategy
predictions = generateMockPredictions(codeChanges, strategy, budget?.maxTests ?? 50);
}
// Apply budget constraints
if (budget?.maxTests && predictions.length > budget.maxTests) {
predictions = predictions.slice(0, budget.maxTests);
}
if (budget?.maxDuration) {
let totalDuration = 0;
predictions = predictions.filter(p => {
totalDuration += p.estimatedDuration / 1000;
return totalDuration <= budget.maxDuration!;
});
}
const output: SelectPredictiveOutput = {
selectedTests: predictions,
totalTests: predictions.length,
estimatedDuration: predictions.reduce((s, p) => s + p.estimatedDuration, 0) / 1000,
confidence: budget?.confidence ?? 0.95,
strategy,
details: {
filesAnalyzed: codeChanges.length,
testsSkipped: Math.max(0, (budget?.maxTests ?? 100) - predictions.length),
coverageEstimate: Math.min(95, 60 + predictions.length * 0.5),
riskScore: predictions.reduce((s, p) => s + p.failureProbability, 0) / Math.max(1, predictions.length),
interpretation: getSelectionInterpretation(predictions, strategy),
},
};
const duration = performance.now() - startTime;
logger.info('Predictive selection completed', {
selected: predictions.length,
durationMs: duration.toFixed(2),
});
return successResult(output);
} catch (error) {
logger.error('Predictive selection failed', { error: String(error) });
return errorResult(error instanceof Error ? error : new Error(String(error)));
}
}
export const selectPredictiveTool: MCPTool = {
name: 'test/select-predictive',
description: 'Predictively select tests based on code changes using reinforcement learning. Returns tests most likely to fail, optimizing CI time while maintaining confidence.',
category: 'test-intelligence',
version: '0.1.0',
tags: ['testing', 'ci-optimization', 'machine-learning', 'predictive'],
cacheable: false,
inputSchema: {
type: 'object',
properties: {
changes: {
type: 'object',
properties: {
files: { type: 'array', items: { type: 'string' } },
gitDiff: { type: 'string' },
gitRef: { type: 'string' },
},
},
strategy: {
type: 'string',
enum: ['fast_feedback', 'high_coverage', 'risk_based', 'balanced'],
},
budget: {
type: 'object',
properties: {
maxTests: { type: 'number' },
maxDuration: { type: 'number' },
confidence: { type: 'number' },
},
},
},
required: ['changes'],
},
handler: selectPredictiveHandler,
};
// ============================================================================
// Tool 2: test/flaky-detect
// ============================================================================
async function flakyDetectHandler(
input: Record<string, unknown>,
context?: ToolContext
): Promise<MCPToolResult> {
const logger = context?.logger ?? defaultLogger;
const startTime = performance.now();
try {
const validationResult = FlakyDetectInputSchema.safeParse(input);
if (!validationResult.success) {
return errorResult(`Invalid input: ${validationResult.error.message}`);
}
const { scope, analysis, threshold } = validationResult.data;
logger.debug('Detecting flaky tests', { historyDepth: scope?.historyDepth, threshold });
// Analyze for flaky tests (mock implementation)
const flakyTests = generateMockFlakyTests(
scope?.testSuite,
analysis ?? ['intermittent_failures', 'timing_sensitive'],
threshold
);
const output: FlakyDetectOutput = {
flakyTests,
totalAnalyzed: 150,
flakinessScore: flakyTests.length / 150,
details: {
intermittentCount: flakyTests.filter(t => t.flakinessType.includes('intermittent_failures')).length,
timingSensitiveCount: flakyTests.filter(t => t.flakinessType.includes('timing_sensitive')).length,
orderDependentCount: flakyTests.filter(t => t.flakinessType.includes('order_dependent')).length,
resourceContentionCount: flakyTests.filter(t => t.flakinessType.includes('resource_contention')).length,
environmentSensitiveCount: flakyTests.filter(t => t.flakinessType.includes('environment_sensitive')).length,
recommendations: generateFlakyRecommendations(flakyTests),
},
};
const duration = performance.now() - startTime;
logger.info('Flaky detection completed', {
flakyFound: flakyTests.length,
durationMs: duration.toFixed(2),
});
return successResult(output);
} catch (error) {
logger.error('Flaky detection failed', { error: String(error) });
return errorResult(error instanceof Error ? error : new Error(String(error)));
}
}
export const flakyDetectTool: MCPTool = {
name: 'test/flaky-detect',
description: 'Detect flaky tests using pattern analysis. Identifies intermittent failures, timing-sensitive tests, order-dependent tests, and resource contention issues.',
category: 'test-intelligence',
version: '0.1.0',
tags: ['testing', 'flaky', 'reliability', 'analysis'],
cacheable: true,
cacheTTL: 300000,
inputSchema: {
type: 'object',
properties: {
scope: {
type: 'object',
properties: {
testSuite: { type: 'string' },
historyDepth: { type: 'number' },
},
},
analysis: {
type: 'array',
items: { type: 'string' },
},
threshold: { type: 'number' },
},
},
handler: flakyDetectHandler,
};
// ============================================================================
// Tool 3: test/coverage-gaps
// ============================================================================
async function coverageGapsHandler(
input: Record<string, unknown>,
context?: ToolContext
): Promise<MCPToolResult> {
const logger = context?.logger ?? defaultLogger;
const startTime = performance.now();
try {
const validationResult = CoverageGapsInputSchema.safeParse(input);
if (!validationResult.success) {
return errorResult(`Invalid input: ${validationResult.error.message}`);
}
const { targetPaths, coverageType, prioritization, minCoverage } = validationResult.data;
logger.debug('Analyzing coverage gaps', { coverageType, prioritization });
// Analyze coverage gaps (mock implementation)
const gaps = generateMockCoverageGaps(
targetPaths ?? ['src/'],
prioritization,
minCoverage
);
const overallCoverage = gaps.reduce((s, g) => s + g.coverage, 0) / Math.max(1, gaps.length);
const output: CoverageGapsOutput = {
gaps,
overallCoverage,
targetCoverage: minCoverage,
details: {
filesAnalyzed: gaps.length + 20,
uncoveredLines: gaps.reduce((s, g) => s + g.uncoveredLines.length, 0),
uncoveredBranches: gaps.reduce((s, g) => s + g.uncoveredBranches.length, 0),
uncoveredFunctions: gaps.reduce((s, g) => s + g.uncoveredFunctions.length, 0),
priorityDistribution: {
critical: gaps.filter(g => g.priority === 'critical').length,
high: gaps.filter(g => g.priority === 'high').length,
medium: gaps.filter(g => g.priority === 'medium').length,
low: gaps.filter(g => g.priority === 'low').length,
},
interpretation: getCoverageInterpretation(overallCoverage, minCoverage, gaps),
},
};
const duration = performance.now() - startTime;
logger.info('Coverage analysis completed', {
gapsFound: gaps.length,
overallCoverage: overallCoverage.toFixed(1),
durationMs: duration.toFixed(2),
});
return successResult(output);
} catch (error) {
logger.error('Coverage analysis failed', { error: String(error) });
return errorResult(error instanceof Error ? error : new Error(String(error)));
}
}
export const coverageGapsTool: MCPTool = {
name: 'test/coverage-gaps',
description: 'Identify test coverage gaps using code-test graph analysis. Prioritizes gaps by risk, complexity, code churn, and recency.',
category: 'test-intelligence',
version: '0.1.0',
tags: ['testing', 'coverage', 'analysis', 'quality'],
cacheable: true,
cacheTTL: 600000,
inputSchema: {
type: 'object',
properties: {
targetPaths: { type: 'array', items: { type: 'string' } },
coverageType: { type: 'string', enum: ['line', 'branch', 'function', 'semantic'] },
prioritization: { type: 'string', enum: ['risk', 'complexity', 'churn', 'recency'] },
minCoverage: { type: 'number' },
},
},
handler: coverageGapsHandler,
};
// ============================================================================
// Tool 4: test/mutation-optimize
// ============================================================================
async function mutationOptimizeHandler(
input: Record<string, unknown>,
context?: ToolContext
): Promise<MCPToolResult> {
const logger = context?.logger ?? defaultLogger;
const startTime = performance.now();
try {
const validationResult = MutationOptimizeInputSchema.safeParse(input);
if (!validationResult.success) {
return errorResult(`Invalid input: ${validationResult.error.message}`);
}
const { targetPath, budget, strategy, mutationTypes } = validationResult.data;
logger.debug('Optimizing mutation testing', { targetPath, strategy, budget });
// Generate optimized mutations (mock implementation)
const mutations = generateMockMutations(
targetPath,
budget ?? 100,
strategy,
mutationTypes
);
const killedMutants = mutations.filter(m => m.status === 'killed').length;
const survivingMutants = mutations.filter(m => m.status === 'survived').length;
const output: MutationOptimizeOutput = {
mutations,
mutationScore: killedMutants / Math.max(1, killedMutants + survivingMutants),
survivingMutants,
killedMutants,
details: {
totalMutations: mutations.length,
budgetUsed: mutations.length,
timeEstimate: mutations.length * 0.5,
coverageImprovement: survivingMutants * 0.5,
weakTests: findWeakTests(mutations),
interpretation: getMutationInterpretation(killedMutants, survivingMutants),
},
};
const duration = performance.now() - startTime;
logger.info('Mutation optimization completed', {
score: (killedMutants / Math.max(1, killedMutants + survivingMutants)).toFixed(2),
durationMs: duration.toFixed(2),
});
return successResult(output);
} catch (error) {
logger.error('Mutation optimization failed', { error: String(error) });
return errorResult(error instanceof Error ? error : new Error(String(error)));
}
}
export const mutationOptimizeTool: MCPTool = {
name: 'test/mutation-optimize',
description: 'Optimize mutation testing using selective mutation. Uses ML to prioritize mutations most likely to reveal test weaknesses.',
category: 'test-intelligence',
version: '0.1.0',
tags: ['testing', 'mutation', 'optimization', 'quality'],
cacheable: false,
inputSchema: {
type: 'object',
properties: {
targetPath: { type: 'string' },
budget: { type: 'number' },
strategy: { type: 'string', enum: ['random', 'coverage_guided', 'ml_guided', 'historical'] },
mutationTypes: { type: 'array', items: { type: 'string' } },
},
required: ['targetPath'],
},
handler: mutationOptimizeHandler,
};
// ============================================================================
// Tool 5: test/generate-suggest
// ============================================================================
async function generateSuggestHandler(
input: Record<string, unknown>,
context?: ToolContext
): Promise<MCPToolResult> {
const logger = context?.logger ?? defaultLogger;
const startTime = performance.now();
try {
const validationResult = GenerateSuggestInputSchema.safeParse(input);
if (!validationResult.success) {
return errorResult(`Invalid input: ${validationResult.error.message}`);
}
const { targetFunction, testStyle, framework, edgeCases, mockStrategy } = validationResult.data;
logger.debug('Generating test suggestions', { targetFunction, testStyle, framework });
// Generate test suggestions (mock implementation)
const suggestions = generateMockTestSuggestions(
targetFunction,
testStyle,
framework,
edgeCases,
mockStrategy
);
const output: GenerateSuggestOutput = {
suggestions,
coverage: {
statements: 75 + Math.random() * 20,
branches: 60 + Math.random() * 30,
functions: 80 + Math.random() * 15,
},
details: {
functionComplexity: 5 + Math.floor(Math.random() * 10),
parametersAnalyzed: 3 + Math.floor(Math.random() * 5),
edgeCasesFound: edgeCases ? suggestions.filter(s => s.category === 'edge_case').length : 0,
mockObjectsNeeded: mockStrategy !== 'none' ? ['database', 'httpClient', 'cache'] : [],
interpretation: getGenerationInterpretation(suggestions, testStyle),
},
};
const duration = performance.now() - startTime;
logger.info('Test generation completed', {
suggestions: suggestions.length,
durationMs: duration.toFixed(2),
});
return successResult(output);
} catch (error) {
logger.error('Test generation failed', { error: String(error) });
return errorResult(error instanceof Error ? error : new Error(String(error)));
}
}
export const generateSuggestTool: MCPTool = {
name: 'test/generate-suggest',
description: 'Suggest test cases for uncovered code paths. Analyzes function signatures, complexity, and generates framework-specific test code.',
category: 'test-intelligence',
version: '0.1.0',
tags: ['testing', 'generation', 'coverage', 'automation'],
cacheable: true,
cacheTTL: 120000,
inputSchema: {
type: 'object',
properties: {
targetFunction: { type: 'string' },
testStyle: { type: 'string', enum: ['unit', 'integration', 'property_based', 'snapshot'] },
framework: { type: 'string', enum: ['jest', 'vitest', 'pytest', 'junit', 'mocha'] },
edgeCases: { type: 'boolean' },
mockStrategy: { type: 'string', enum: ['minimal', 'full', 'none'] },
},
required: ['targetFunction'],
},
handler: generateSuggestHandler,
};
// ============================================================================
// Export All Tools
// ============================================================================
export const testIntelligenceTools: MCPTool[] = [
selectPredictiveTool,
flakyDetectTool,
coverageGapsTool,
mutationOptimizeTool,
generateSuggestTool,
];
// ============================================================================
// Helper Functions
// ============================================================================
function generateMockPredictions(
changes: CodeChange[],
strategy: string,
maxTests: number
): SelectedTest[] {
const predictions: SelectedTest[] = [];
for (let i = 0; i < Math.min(maxTests, changes.length * 3 + 5); i++) {
const failureProbability = strategy === 'risk_based'
? 0.8 - i * 0.05
: 0.5 + Math.random() * 0.3 - i * 0.02;
predictions.push({
testId: `test-${i + 1}`,
testName: `test_${changes[i % changes.length]?.file.split('/').pop()?.replace('.', '_')}_${i}`,
suite: changes[i % changes.length]?.file.split('/').slice(0, -1).join('/') || 'unit',
priority: maxTests - i,
reason: `Correlated with changes in ${changes[i % changes.length]?.file || 'source files'}`,
estimatedDuration: 500 + Math.random() * 3000,
failureProbability: Math.max(0, Math.min(1, failureProbability)),
});
}
return predictions;
}
function getSelectionInterpretation(predictions: SelectedTest[], strategy: string): string {
const highRisk = predictions.filter(p => p.failureProbability > 0.7).length;
if (highRisk > predictions.length / 2) {
return `High-risk changes detected. ${highRisk} tests have >70% failure probability. Recommend running full suite.`;
}
if (strategy === 'fast_feedback') {
return `Fast feedback mode selected ${predictions.length} tests focused on critical paths.`;
}
return `Balanced selection of ${predictions.length} tests optimized for ${strategy} strategy.`;
}
function generateMockFlakyTests(
testSuite: string | undefined,
analysisTypes: string[],
threshold: number
): FlakyTest[] {
const flakyTests: FlakyTest[] = [];
const count = 3 + Math.floor(Math.random() * 5);
for (let i = 0; i < count; i++) {
const types = analysisTypes.filter(() => Math.random() > 0.5) as FlakyTest['flakinessType'];
if (types.length === 0) types.push(analysisTypes[0] as FlakyTest['flakinessType'][0]);
flakyTests.push({
testId: `flaky-${i + 1}`,
testName: `test_${testSuite || 'unit'}_flaky_${i}`,
suite: testSuite || 'unit',
flakinessScore: threshold + Math.random() * (0.5 - threshold),
flakinessType: types,
failurePattern: `Fails approximately ${Math.floor(types[0].includes('intermittent') ? 20 : 10)}% of runs`,
lastFlaky: Date.now() - Math.random() * 86400000 * 7,
suggestedFix: getFlakyFix(types[0]),
});
}
return flakyTests;
}
function getFlakyFix(type: string): string {
switch (type) {
case 'intermittent_failures':
return 'Add retry logic or investigate race conditions';
case 'timing_sensitive':
return 'Replace setTimeout with proper async waiting';
case 'order_dependent':
return 'Ensure test isolation - reset state in beforeEach';
case 'resource_contention':
return 'Use dedicated test database or mock external resources';
case 'environment_sensitive':
return 'Mock environment variables and external dependencies';
default:
return 'Review test for potential sources of non-determinism';
}
}
function generateFlakyRecommendations(flakyTests: FlakyTest[]): string[] {
const recommendations: string[] = [];
if (flakyTests.some(t => t.flakinessType.includes('timing_sensitive'))) {
recommendations.push('Consider using waitFor utilities instead of fixed timeouts');
}
if (flakyTests.some(t => t.flakinessType.includes('order_dependent'))) {
recommendations.push('Run tests in random order to detect order dependencies');
}
if (flakyTests.length > 5) {
recommendations.push('Consider quarantining flaky tests to maintain CI reliability');
}
recommendations.push('Set up flaky test monitoring dashboard');
return recommendations;
}
function generateMockCoverageGaps(
paths: string[],
prioritization: string,
minCoverage: number
): CoverageGap[] {
const gaps: CoverageGap[] = [];
for (let i = 0; i < 5 + Math.floor(Math.random() * 5); i++) {
const coverage = 40 + Math.random() * (minCoverage - 40);
const riskScore = prioritization === 'risk' ? 0.5 + Math.random() * 0.5 : 0.3 + Math.random() * 0.4;
gaps.push({
file: `${paths[i % paths.length]}module_${i}/handler.ts`,
uncoveredLines: Array.from({ length: 5 + Math.floor(Math.random() * 10) }, (_, j) => 10 + j * 5),
uncoveredBranches: Array.from({ length: 2 + Math.floor(Math.random() * 4) }, (_, j) => 15 + j * 10),
uncoveredFunctions: [`function_${i}_a`, `function_${i}_b`],
coverage,
priority: riskScore > 0.7 ? 'critical' : riskScore > 0.5 ? 'high' : riskScore > 0.3 ? 'medium' : 'low',
riskScore,
complexity: 5 + Math.floor(Math.random() * 15),
churnScore: Math.random(),
suggestedTests: [`test_${i}_happy_path`, `test_${i}_edge_case`, `test_${i}_error`],
});
}
return gaps.sort((a, b) => b.riskScore - a.riskScore);
}
function getCoverageInterpretation(overall: number, target: number, gaps: CoverageGap[]): string {
const critical = gaps.filter(g => g.priority === 'critical').length;
if (overall >= target) {
return `Coverage target of ${target}% met. ${critical} critical areas still need attention.`;
}
if (overall >= target - 10) {
return `Coverage at ${overall.toFixed(1)}%, close to ${target}% target. Focus on ${critical} critical gaps.`;
}
return `Coverage at ${overall.toFixed(1)}%, below ${target}% target. ${gaps.length} files need attention.`;
}
function generateMockMutations(
targetPath: string,
budget: number,
strategy: string,
mutationTypes?: string[]
): OptimizedMutation[] {
const mutations: OptimizedMutation[] = [];
const types = mutationTypes ?? ['arithmetic', 'logical', 'boundary'];
const count = Math.min(budget, 20 + Math.floor(Math.random() * 30));
for (let i = 0; i < count; i++) {
const type = types[i % types.length] as OptimizedMutation['type'];
const killed = strategy === 'ml_guided' ? Math.random() > 0.2 : Math.random() > 0.4;
mutations.push({
id: `mut-${i + 1}`,
file: targetPath,
line: 10 + i * 3,
type,
original: getMutationOriginal(type),
mutated: getMutationMutated(type),
status: killed ? 'killed' : 'survived',
killingTests: killed ? [`test_${i % 5}`, `test_${(i + 1) % 5}`] : [],
priority: budget - i,
});
}
return mutations;
}
function getMutationOriginal(type: string): string {
switch (type) {
case 'arithmetic':
return 'a + b';
case 'logical':
return 'a && b';
case 'boundary':
return 'i < n';
case 'null_check':
return 'if (x !== null)';
case 'return_value':
return 'return result';
default:
return 'expression';
}
}
function getMutationMutated(type: string): string {
switch (type) {
case 'arithmetic':
return 'a - b';
case 'logical':
return 'a || b';
case 'boundary':
return 'i <= n';
case 'null_check':
return 'if (x === null)';
case 'return_value':
return 'return null';
default:
return 'mutated';
}
}
function findWeakTests(mutations: OptimizedMutation[]): string[] {
const testKillCounts = new Map<string, number>();
// Count surviving mutants for reference (used in logging/metrics if needed)
const _survivedCount = mutations.filter(m => m.status === 'survived').length;
void _survivedCount; // Acknowledge unused variable
for (const mutation of mutations) {
for (const test of mutation.killingTests) {
testKillCounts.set(test, (testKillCounts.get(test) ?? 0) + 1);
}
}
// Tests that killed few mutants are weak
return Array.from(testKillCounts.entries())
.filter(([, count]) => count < 3)
.map(([test]) => test)
.slice(0, 5);
}
function getMutationInterpretation(killed: number, survived: number): string {
const score = killed / Math.max(1, killed + survived);
if (score >= 0.8) {
return `Excellent mutation score of ${(score * 100).toFixed(0)}%. Test suite is robust.`;
}
if (score >= 0.6) {
return `Good mutation score of ${(score * 100).toFixed(0)}%. ${survived} surviving mutants indicate potential test gaps.`;
}
return `Mutation score of ${(score * 100).toFixed(0)}% below recommended threshold. ${survived} mutants survived, indicating weak test coverage.`;
}
function generateMockTestSuggestions(
targetFunction: string,
testStyle: string,
framework: string,
edgeCases: boolean,
mockStrategy?: string
): TestSuggestion[] {
const suggestions: TestSuggestion[] = [];
const funcName = targetFunction.split('/').pop() ?? targetFunction;
// Happy path test
suggestions.push({
name: `should ${funcName} with valid input`,
description: `Basic happy path test for ${funcName}`,
category: 'happy_path',
code: generateTestCode(funcName, 'happy_path', framework, mockStrategy),
priority: 1,
coverageGain: 30,
dependencies: [],
});
// Error handling test
suggestions.push({
name: `should handle errors in ${funcName}`,
description: `Error handling test for ${funcName}`,
category: 'error_handling',
code: generateTestCode(funcName, 'error_handling', framework, mockStrategy),
priority: 2,
coverageGain: 20,
dependencies: [],
});
if (edgeCases) {
// Edge case tests
suggestions.push({
name: `should ${funcName} with empty input`,
description: `Edge case: empty input for ${funcName}`,
category: 'edge_case',
code: generateTestCode(funcName, 'edge_case', framework, mockStrategy),
priority: 3,
coverageGain: 15,
dependencies: [],
});
suggestions.push({
name: `should ${funcName} with boundary values`,
description: `Boundary value test for ${funcName}`,
category: 'boundary',
code: generateTestCode(funcName, 'boundary', framework, mockStrategy),
priority: 4,
coverageGain: 15,
dependencies: [],
});
}
if (testStyle === 'integration') {
suggestions.push({
name: `should integrate ${funcName} with dependencies`,
description: `Integration test for ${funcName}`,
category: 'integration',
code: generateTestCode(funcName, 'integration', framework, mockStrategy),
priority: 5,
coverageGain: 25,
dependencies: ['database', 'httpClient'],
});
}
return suggestions;
}
function generateTestCode(
funcName: string,
category: string,
framework: string,
mockStrategy?: string
): string {
const describe = framework === 'pytest' ? 'class Test' : 'describe';
const it = framework === 'pytest' ? 'def test_' : 'it';
const expect = framework === 'pytest' ? 'assert' : 'expect';
if (framework === 'pytest') {
return `class Test${funcName.charAt(0).toUpperCase() + funcName.slice(1)}:
def test_${category}(self):
# Arrange
input_data = get_test_data()
${mockStrategy !== 'none' ? '# mock = Mock()' : ''}
# Act
result = ${funcName}(input_data)
# Assert
assert result is not None`;
}
return `${describe}('${funcName}', () => {
${it}('should handle ${category}', async () => {
// Arrange
const input = getTestData();
${mockStrategy !== 'none' ? '// const mock = vi.fn();' : ''}
// Act
const result = await ${funcName}(input);
// Assert
${expect}(result).toBeDefined();
});
});`;
}
function getGenerationInterpretation(suggestions: TestSuggestion[], style: string): string {
const totalGain = suggestions.reduce((s, t) => s + t.coverageGain, 0);
return `Generated ${suggestions.length} ${style} test suggestions with estimated ${totalGain}% coverage gain. Prioritized by impact and complexity.`;
}