UNPKG

@skyramp/mcp

Version:

Skyramp MCP (Model Context Protocol) Server - AI-powered test generation and execution

263 lines (262 loc) 10.9 kB
import { BASE_SCORES, CONTEXT_RULES, } from "../types/TestMapping.js"; import { TestType } from "../types/TestTypes.js"; /** * Scoring Engine * Calculates priority scores for test types based on repository analysis */ export class ScoringEngine { /** * Calculate priority score for a specific test type */ static calculateTestScore(testType, analysis) { const _baseScore = BASE_SCORES[testType]; const contextMultiplier = this.calculateContextMultiplier(testType, analysis); const _finalScore = _baseScore * contextMultiplier; const feasibility = this.assessFeasibility(testType, analysis); const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis); const reasoning = this.generateReasoning(testType, _baseScore, contextMultiplier, _finalScore, analysis); return { testType, _baseScore, contextMultiplier, _finalScore: Math.round(_finalScore * 10) / 10, feasibility, requiredArtifacts, reasoning, }; } /** * Calculate context multiplier based on repository characteristics */ static calculateContextMultiplier(testType, analysis) { const rules = CONTEXT_RULES[testType]; let multiplier = 1.0; for (const rule of rules) { if (this.evaluateCondition(rule.condition, analysis)) { multiplier = rule.multiplier; break; // Use first matching rule } } return multiplier; } /** * Evaluate a context condition against repository analysis */ static evaluateCondition(condition, analysis) { const { projectClassification, infrastructure, existingTests, artifacts } = analysis; switch (condition) { // Project type conditions case "full-stack": return projectClassification.projectType === "full-stack"; case "backend-only": return projectClassification.projectType === "rest-api"; case "frontend-spa": return projectClassification.projectType === "frontend"; case "library": return projectClassification.projectType === "library"; case "cli-or-library": return ["library", "cli"].includes(projectClassification.projectType); case "microservices": return (projectClassification.projectType === "microservices" || projectClassification.deploymentPattern === "microservices"); case "monolith": return (projectClassification.deploymentPattern === "containerized-monolith"); // Infrastructure conditions case "hasKubernetes": return infrastructure.hasKubernetes; case "hasDockerCompose": return infrastructure.hasDockerCompose; case "has-cicd": return infrastructure.hasCiCd; // Testing conditions case "no-existing-tests": return Object.values(existingTests.coverage).every((v) => v === 0); case "has-unit-missing-integration": return (existingTests.coverage.unit > 0 && existingTests.coverage.integration === 0); case "has-integration-tests": return existingTests.coverage.integration > 0; case "comprehensive-tests": return (Object.values(existingTests.coverage).reduce((a, b) => a + b, 0) > 20); // Artifact conditions case "frontend-spa-or-fullstack": return ["frontend", "full-stack"].includes(projectClassification.projectType); case "multiple-services": return projectClassification.deploymentPattern === "microservices"; // Security conditions (inferred) case "handles-payments": return (analysis.businessContext.mainPurpose .toLowerCase() .includes("payment") || analysis.businessContext.mainPurpose .toLowerCase() .includes("commerce")); case "handles-pii": return (analysis.businessContext.mainPurpose.toLowerCase().includes("user") || analysis.businessContext.mainPurpose.toLowerCase().includes("profile")); case "oauth2": return analysis.authentication.method === "oauth2"; case "internal-service": case "internal-tool": return analysis.businessContext.mainPurpose .toLowerCase() .includes("internal"); default: return false; } } /** * Assess feasibility of generating a test type */ static assessFeasibility(testType, analysis) { const { artifacts, projectClassification } = analysis; // Check for N/A cases first if (testType === TestType.UI && projectClassification.projectType === "rest-api") { return "not-applicable"; } if (testType === TestType.E2E && projectClassification.projectType === "library") { return "not-applicable"; } // Check artifact requirements const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis); const missingCount = requiredArtifacts.missing.length; if (missingCount === 0) { return "high"; } else if (missingCount === 1) { return "medium"; } else { return "low"; } } /** * Identify required artifacts and their availability */ static identifyRequiredArtifacts(testType, analysis) { const available = []; const missing = []; const { artifacts } = analysis; // Check OpenAPI spec if (artifacts.openApiSpecs.length > 0) { available.push("openApiSpec"); } else { if ([ TestType.SMOKE, TestType.CONTRACT, TestType.FUZZ, TestType.INTEGRATION, TestType.LOAD, ].includes(testType)) { missing.push("openApiSpec"); } } // Check Playwright recordings if (artifacts.playwrightRecordings.length > 0) { available.push("playwrightRecording"); } else { if ([TestType.UI, TestType.E2E].includes(testType)) { missing.push("playwrightRecording"); } } // Check trace files if (artifacts.traceFiles.length > 0) { available.push("traceFile"); } else { if ([TestType.E2E, TestType.INTEGRATION].includes(testType)) { // Trace files are helpful but not required if OpenAPI is available if (artifacts.openApiSpecs.length === 0) { missing.push("traceFile"); } } } return { available, missing }; } /** * Generate reasoning explanation for the score */ static generateReasoning(testType, _baseScore, multiplier, _finalScore, analysis) { const { projectClassification, existingTests, artifacts, infrastructure } = analysis; let reasoning = ""; // Base reasoning by test type switch (testType) { case TestType.INTEGRATION: if (existingTests.coverage.integration === 0) { reasoning = `No integration tests exist for ${analysis.apiEndpoints.totalCount} endpoints. `; } else { reasoning = `${existingTests.coverage.integration} integration tests exist. `; } if (projectClassification.deploymentPattern === "microservices") { reasoning += "Microservices architecture makes integration testing critical. "; } break; case TestType.E2E: if (projectClassification.projectType === "full-stack") { reasoning = "Full-stack application - E2E tests validate complete user journeys. "; } else { reasoning = "E2E tests have limited applicability for this project type. "; } break; case TestType.UI: if (["frontend", "full-stack"].includes(projectClassification.projectType)) { reasoning = "UI testing essential for frontend validation. "; } else { reasoning = "No UI components detected. "; } break; case TestType.SMOKE: if (Object.values(existingTests.coverage).every((v) => v === 0)) { reasoning = "No existing tests - smoke tests provide quick wins. "; } if (infrastructure.hasCiCd) { reasoning += "CI/CD pipeline benefits from smoke test validation. "; } break; case TestType.LOAD: if (infrastructure.hasKubernetes) { reasoning = "Kubernetes deployment suggests high-traffic expectations. "; } else { reasoning = "Load testing validates performance and scalability. "; } break; case TestType.FUZZ: if (analysis.authentication.method !== "none") { reasoning = "API handles authentication - security testing important. "; } reasoning += "Fuzz testing discovers input validation issues. "; break; case TestType.CONTRACT: if (artifacts.openApiSpecs.length > 0) { reasoning = "OpenAPI spec available - contract validation straightforward. "; } else { reasoning = "No OpenAPI spec - contract tests harder to generate. "; } break; } // Add artifact status const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis); if (requiredArtifacts.missing.length > 0) { reasoning += `Missing: ${requiredArtifacts.missing.join(", ")}. `; } else if (requiredArtifacts.available.length > 0) { reasoning += `All required artifacts available. `; } return reasoning.trim(); } }