api-tests-coverage 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +703 -0
- package/config.yaml.example +227 -0
- package/dist/action/src/index.d.ts +2 -0
- package/dist/action/src/index.d.ts.map +1 -0
- package/dist/action/src/index.js +349 -0
- package/dist/action/src/prComment.d.ts +34 -0
- package/dist/action/src/prComment.d.ts.map +1 -0
- package/dist/action/src/prComment.js +146 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts +36 -0
- package/dist/src/ast/astAnalysisOrchestrator.d.ts.map +1 -0
- package/dist/src/ast/astAnalysisOrchestrator.js +123 -0
- package/dist/src/ast/astTypes.d.ts +105 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -0
- package/dist/src/ast/astTypes.js +9 -0
- package/dist/src/ast/languageAnalyzer.d.ts +46 -0
- package/dist/src/ast/languageAnalyzer.d.ts.map +1 -0
- package/dist/src/ast/languageAnalyzer.js +9 -0
- package/dist/src/ast/languageCapabilities.d.ts +24 -0
- package/dist/src/ast/languageCapabilities.d.ts.map +1 -0
- package/dist/src/ast/languageCapabilities.js +92 -0
- package/dist/src/ast/parseFile.d.ts +16 -0
- package/dist/src/ast/parseFile.d.ts.map +1 -0
- package/dist/src/ast/parseFile.js +65 -0
- package/dist/src/ast/parserRegistry.d.ts +39 -0
- package/dist/src/ast/parserRegistry.d.ts.map +1 -0
- package/dist/src/ast/parserRegistry.js +66 -0
- package/dist/src/buildSummary.d.ts +26 -0
- package/dist/src/buildSummary.d.ts.map +1 -0
- package/dist/src/buildSummary.js +193 -0
- package/dist/src/businessCoverage.d.ts +68 -0
- package/dist/src/businessCoverage.d.ts.map +1 -0
- package/dist/src/businessCoverage.js +290 -0
- package/dist/src/compatibilityCoverage.d.ts +83 -0
- package/dist/src/compatibilityCoverage.d.ts.map +1 -0
- package/dist/src/compatibilityCoverage.js +501 -0
- package/dist/src/config/defaultConfig.d.ts +9 -0
- package/dist/src/config/defaultConfig.d.ts.map +1 -0
- package/dist/src/config/defaultConfig.js +97 -0
- package/dist/src/config/index.d.ts +12 -0
- package/dist/src/config/index.d.ts.map +1 -0
- package/dist/src/config/index.js +37 -0
- package/dist/src/config/loadConfig.d.ts +29 -0
- package/dist/src/config/loadConfig.d.ts.map +1 -0
- package/dist/src/config/loadConfig.js +135 -0
- package/dist/src/config/mergeConfig.d.ts +15 -0
- package/dist/src/config/mergeConfig.d.ts.map +1 -0
- package/dist/src/config/mergeConfig.js +57 -0
- package/dist/src/config/schema.d.ts +15 -0
- package/dist/src/config/schema.d.ts.map +1 -0
- package/dist/src/config/schema.js +30 -0
- package/dist/src/config/types.d.ts +175 -0
- package/dist/src/config/types.d.ts.map +1 -0
- package/dist/src/config/types.js +9 -0
- package/dist/src/config/validateConfig.d.ts +22 -0
- package/dist/src/config/validateConfig.d.ts.map +1 -0
- package/dist/src/config/validateConfig.js +171 -0
- package/dist/src/config.d.ts +168 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +204 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts +67 -0
- package/dist/src/coverage/deep-analysis/callGraph.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/callGraph.js +275 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts +23 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/deepEndpointResolver.js +394 -0
- package/dist/src/coverage/deep-analysis/index.d.ts +17 -0
- package/dist/src/coverage/deep-analysis/index.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/index.js +63 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts +60 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveAssertions.js +121 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts +36 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveConstants.js +92 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts +55 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveEnums.js +152 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts +70 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveMethodChains.js +152 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts +80 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolvePaths.js +216 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts +71 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/resolveRequestWrappers.js +226 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts +58 -0
- package/dist/src/coverage/deep-analysis/symbolTable.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/symbolTable.js +230 -0
- package/dist/src/coverage/deep-analysis/types.d.ts +122 -0
- package/dist/src/coverage/deep-analysis/types.d.ts.map +1 -0
- package/dist/src/coverage/deep-analysis/types.js +21 -0
- package/dist/src/discovery/fileClassifier.d.ts +50 -0
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -0
- package/dist/src/discovery/fileClassifier.js +238 -0
- package/dist/src/discovery/projectDiscovery.d.ts +66 -0
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -0
- package/dist/src/discovery/projectDiscovery.js +287 -0
- package/dist/src/endpointCoverage.d.ts +70 -0
- package/dist/src/endpointCoverage.d.ts.map +1 -0
- package/dist/src/endpointCoverage.js +381 -0
- package/dist/src/errorCoverage.d.ts +93 -0
- package/dist/src/errorCoverage.d.ts.map +1 -0
- package/dist/src/errorCoverage.js +698 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +1441 -0
- package/dist/src/inference/businessRuleInference.d.ts +63 -0
- package/dist/src/inference/businessRuleInference.d.ts.map +1 -0
- package/dist/src/inference/businessRuleInference.js +268 -0
- package/dist/src/inference/integrationFlowInference.d.ts +56 -0
- package/dist/src/inference/integrationFlowInference.d.ts.map +1 -0
- package/dist/src/inference/integrationFlowInference.js +266 -0
- package/dist/src/integrationCoverage.d.ts +72 -0
- package/dist/src/integrationCoverage.d.ts.map +1 -0
- package/dist/src/integrationCoverage.js +317 -0
- package/dist/src/intelligence/index.d.ts +20 -0
- package/dist/src/intelligence/index.d.ts.map +1 -0
- package/dist/src/intelligence/index.js +105 -0
- package/dist/src/intelligence/linkageEngine.d.ts +20 -0
- package/dist/src/intelligence/linkageEngine.d.ts.map +1 -0
- package/dist/src/intelligence/linkageEngine.js +522 -0
- package/dist/src/intelligence/markdownReporter.d.ts +12 -0
- package/dist/src/intelligence/markdownReporter.d.ts.map +1 -0
- package/dist/src/intelligence/markdownReporter.js +265 -0
- package/dist/src/intelligence/riskScoring.d.ts +53 -0
- package/dist/src/intelligence/riskScoring.d.ts.map +1 -0
- package/dist/src/intelligence/riskScoring.js +181 -0
- package/dist/src/intelligence/types.d.ts +121 -0
- package/dist/src/intelligence/types.d.ts.map +1 -0
- package/dist/src/intelligence/types.js +8 -0
- package/dist/src/languageDetection.d.ts +100 -0
- package/dist/src/languageDetection.d.ts.map +1 -0
- package/dist/src/languageDetection.js +349 -0
- package/dist/src/languages/java/index.d.ts +16 -0
- package/dist/src/languages/java/index.d.ts.map +1 -0
- package/dist/src/languages/java/index.js +103 -0
- package/dist/src/languages/java/parser.d.ts +7 -0
- package/dist/src/languages/java/parser.d.ts.map +1 -0
- package/dist/src/languages/java/parser.js +50 -0
- package/dist/src/languages/java/semanticBuilder.d.ts +21 -0
- package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -0
- package/dist/src/languages/java/semanticBuilder.js +358 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts +20 -0
- package/dist/src/languages/javascript/annotationExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/annotationExtractor.js +94 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts +18 -0
- package/dist/src/languages/javascript/assertionResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/assertionResolver.js +150 -0
- package/dist/src/languages/javascript/callResolver.d.ts +23 -0
- package/dist/src/languages/javascript/callResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/callResolver.js +236 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts +23 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.d.ts.map +1 -0
- package/dist/src/languages/javascript/httpInteractionExtractor.js +205 -0
- package/dist/src/languages/javascript/index.d.ts +20 -0
- package/dist/src/languages/javascript/index.d.ts.map +1 -0
- package/dist/src/languages/javascript/index.js +136 -0
- package/dist/src/languages/javascript/parser.d.ts +14 -0
- package/dist/src/languages/javascript/parser.d.ts.map +1 -0
- package/dist/src/languages/javascript/parser.js +38 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts +31 -0
- package/dist/src/languages/javascript/symbolResolver.d.ts.map +1 -0
- package/dist/src/languages/javascript/symbolResolver.js +183 -0
- package/dist/src/languages/kotlin/index.d.ts +16 -0
- package/dist/src/languages/kotlin/index.d.ts.map +1 -0
- package/dist/src/languages/kotlin/index.js +151 -0
- package/dist/src/languages/kotlin/parser.d.ts +11 -0
- package/dist/src/languages/kotlin/parser.d.ts.map +1 -0
- package/dist/src/languages/kotlin/parser.js +74 -0
- package/dist/src/languages/python/index.d.ts +15 -0
- package/dist/src/languages/python/index.d.ts.map +1 -0
- package/dist/src/languages/python/index.js +293 -0
- package/dist/src/languages/ruby/index.d.ts +15 -0
- package/dist/src/languages/ruby/index.d.ts.map +1 -0
- package/dist/src/languages/ruby/index.js +274 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts +43 -0
- package/dist/src/languages/shared/treeSitterUtils.d.ts.map +1 -0
- package/dist/src/languages/shared/treeSitterUtils.js +100 -0
- package/dist/src/languages/typescript/index.d.ts +14 -0
- package/dist/src/languages/typescript/index.d.ts.map +1 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/lib/index.d.ts +228 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js +486 -0
- package/dist/src/mcp/client/index.d.ts +37 -0
- package/dist/src/mcp/client/index.d.ts.map +1 -0
- package/dist/src/mcp/client/index.js +235 -0
- package/dist/src/mcp/config.d.ts +50 -0
- package/dist/src/mcp/config.d.ts.map +1 -0
- package/dist/src/mcp/config.js +125 -0
- package/dist/src/mcp/events.d.ts +24 -0
- package/dist/src/mcp/events.d.ts.map +1 -0
- package/dist/src/mcp/events.js +48 -0
- package/dist/src/mcp/fallback/index.d.ts +50 -0
- package/dist/src/mcp/fallback/index.d.ts.map +1 -0
- package/dist/src/mcp/fallback/index.js +216 -0
- package/dist/src/mcp/index.d.ts +67 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +212 -0
- package/dist/src/mcp/normalizer.d.ts +21 -0
- package/dist/src/mcp/normalizer.d.ts.map +1 -0
- package/dist/src/mcp/normalizer.js +99 -0
- package/dist/src/mcp/prompts/index.d.ts +86 -0
- package/dist/src/mcp/prompts/index.d.ts.map +1 -0
- package/dist/src/mcp/prompts/index.js +304 -0
- package/dist/src/mcp/templates/index.d.ts +35 -0
- package/dist/src/mcp/templates/index.d.ts.map +1 -0
- package/dist/src/mcp/templates/index.js +143 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts +47 -0
- package/dist/src/mcp/testing/mock-server/index.d.ts.map +1 -0
- package/dist/src/mcp/testing/mock-server/index.js +157 -0
- package/dist/src/mcp/types.d.ts +127 -0
- package/dist/src/mcp/types.d.ts.map +1 -0
- package/dist/src/mcp/types.js +8 -0
- package/dist/src/observability.d.ts +138 -0
- package/dist/src/observability.d.ts.map +1 -0
- package/dist/src/observability.js +519 -0
- package/dist/src/parameterCoverage.d.ts +75 -0
- package/dist/src/parameterCoverage.d.ts.map +1 -0
- package/dist/src/parameterCoverage.js +629 -0
- package/dist/src/perfResilienceCoverage.d.ts +155 -0
- package/dist/src/perfResilienceCoverage.d.ts.map +1 -0
- package/dist/src/perfResilienceCoverage.js +670 -0
- package/dist/src/pluginLoader.d.ts +51 -0
- package/dist/src/pluginLoader.d.ts.map +1 -0
- package/dist/src/pluginLoader.js +72 -0
- package/dist/src/publishing.d.ts +63 -0
- package/dist/src/publishing.d.ts.map +1 -0
- package/dist/src/publishing.js +379 -0
- package/dist/src/qualityGate.d.ts +58 -0
- package/dist/src/qualityGate.d.ts.map +1 -0
- package/dist/src/qualityGate.js +118 -0
- package/dist/src/reporting.d.ts +41 -0
- package/dist/src/reporting.d.ts.map +1 -0
- package/dist/src/reporting.js +278 -0
- package/dist/src/screenshots.d.ts +71 -0
- package/dist/src/screenshots.d.ts.map +1 -0
- package/dist/src/screenshots.js +141 -0
- package/dist/src/security/gate/index.d.ts +11 -0
- package/dist/src/security/gate/index.d.ts.map +1 -0
- package/dist/src/security/gate/index.js +65 -0
- package/dist/src/security/index.d.ts +30 -0
- package/dist/src/security/index.d.ts.map +1 -0
- package/dist/src/security/index.js +342 -0
- package/dist/src/security/normalizers/semgrep.d.ts +10 -0
- package/dist/src/security/normalizers/semgrep.d.ts.map +1 -0
- package/dist/src/security/normalizers/semgrep.js +104 -0
- package/dist/src/security/normalizers/trivy.d.ts +10 -0
- package/dist/src/security/normalizers/trivy.d.ts.map +1 -0
- package/dist/src/security/normalizers/trivy.js +78 -0
- package/dist/src/security/normalizers/zap.d.ts +10 -0
- package/dist/src/security/normalizers/zap.d.ts.map +1 -0
- package/dist/src/security/normalizers/zap.js +104 -0
- package/dist/src/security/scanners/semgrep.d.ts +6 -0
- package/dist/src/security/scanners/semgrep.d.ts.map +1 -0
- package/dist/src/security/scanners/semgrep.js +125 -0
- package/dist/src/security/scanners/trivy.d.ts +6 -0
- package/dist/src/security/scanners/trivy.d.ts.map +1 -0
- package/dist/src/security/scanners/trivy.js +115 -0
- package/dist/src/security/scanners/zap.d.ts +6 -0
- package/dist/src/security/scanners/zap.d.ts.map +1 -0
- package/dist/src/security/scanners/zap.js +135 -0
- package/dist/src/security/types.d.ts +146 -0
- package/dist/src/security/types.d.ts.map +1 -0
- package/dist/src/security/types.js +6 -0
- package/dist/src/securityCoverage.d.ts +116 -0
- package/dist/src/securityCoverage.d.ts.map +1 -0
- package/dist/src/securityCoverage.js +725 -0
- package/dist/src/summary/buildSummary.d.ts +28 -0
- package/dist/src/summary/buildSummary.d.ts.map +1 -0
- package/dist/src/summary/buildSummary.js +257 -0
- package/dist/src/summary/evaluateMetrics.d.ts +31 -0
- package/dist/src/summary/evaluateMetrics.d.ts.map +1 -0
- package/dist/src/summary/evaluateMetrics.js +118 -0
- package/dist/src/summary/index.d.ts +10 -0
- package/dist/src/summary/index.d.ts.map +1 -0
- package/dist/src/summary/index.js +22 -0
- package/dist/src/summary/markdownRenderer.d.ts +139 -0
- package/dist/src/summary/markdownRenderer.d.ts.map +1 -0
- package/dist/src/summary/markdownRenderer.js +459 -0
- package/dist/src/summary/prSummary.d.ts +24 -0
- package/dist/src/summary/prSummary.d.ts.map +1 -0
- package/dist/src/summary/prSummary.js +233 -0
- package/dist/src/summary/summaryTypes.d.ts +35 -0
- package/dist/src/summary/summaryTypes.d.ts.map +1 -0
- package/dist/src/summary/summaryTypes.js +27 -0
- package/package.json +84 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ERROR_CATEGORIES = void 0;
|
|
40
|
+
exports.statusCodeToCategories = statusCodeToCategories;
|
|
41
|
+
exports.parseErrorScenarios = parseErrorScenarios;
|
|
42
|
+
exports.segmentMentionsEndpoint = segmentMentionsEndpoint;
|
|
43
|
+
exports.matchesCategoryHeuristic = matchesCategoryHeuristic;
|
|
44
|
+
exports.segmentCoversScenario = segmentCoversScenario;
|
|
45
|
+
exports.analyzeErrorCoverage = analyzeErrorCoverage;
|
|
46
|
+
exports.buildErrorCoverageReport = buildErrorCoverageReport;
|
|
47
|
+
exports.generateErrorReports = generateErrorReports;
|
|
48
|
+
const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
52
|
+
const astAnalysisOrchestrator_1 = require("./ast/astAnalysisOrchestrator");
|
|
53
|
+
exports.ERROR_CATEGORIES = [
|
|
54
|
+
'missing-parameter',
|
|
55
|
+
'invalid-value',
|
|
56
|
+
'unauthorized',
|
|
57
|
+
'forbidden',
|
|
58
|
+
'not-found',
|
|
59
|
+
'conflict',
|
|
60
|
+
'server-error',
|
|
61
|
+
];
|
|
62
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
63
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
64
|
+
function escapeRegex(s) {
|
|
65
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Map a HTTP status code (and optional description) to one or more error
|
|
69
|
+
* categories that are relevant for heuristic matching.
|
|
70
|
+
*/
|
|
71
|
+
function statusCodeToCategories(statusCode, description) {
|
|
72
|
+
const descLower = description.toLowerCase();
|
|
73
|
+
const categories = [];
|
|
74
|
+
switch (statusCode) {
|
|
75
|
+
case 400:
|
|
76
|
+
// Distinguish by description when possible; otherwise include both
|
|
77
|
+
if (descLower.includes('missing') || descLower.includes('required')) {
|
|
78
|
+
categories.push('missing-parameter');
|
|
79
|
+
}
|
|
80
|
+
else if (descLower.includes('invalid') ||
|
|
81
|
+
descLower.includes('format') ||
|
|
82
|
+
descLower.includes('type') ||
|
|
83
|
+
descLower.includes('value')) {
|
|
84
|
+
categories.push('invalid-value');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
categories.push('missing-parameter', 'invalid-value');
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case 401:
|
|
91
|
+
categories.push('unauthorized');
|
|
92
|
+
break;
|
|
93
|
+
case 403:
|
|
94
|
+
categories.push('forbidden');
|
|
95
|
+
break;
|
|
96
|
+
case 404:
|
|
97
|
+
categories.push('not-found');
|
|
98
|
+
break;
|
|
99
|
+
case 409:
|
|
100
|
+
categories.push('conflict');
|
|
101
|
+
break;
|
|
102
|
+
case 422:
|
|
103
|
+
categories.push('invalid-value');
|
|
104
|
+
break;
|
|
105
|
+
case 500:
|
|
106
|
+
case 502:
|
|
107
|
+
case 503:
|
|
108
|
+
case 504:
|
|
109
|
+
categories.push('server-error');
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
113
|
+
categories.push('invalid-value');
|
|
114
|
+
}
|
|
115
|
+
else if (statusCode >= 500) {
|
|
116
|
+
categories.push('server-error');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return categories;
|
|
120
|
+
}
|
|
121
|
+
// ─── Spec parsing ─────────────────────────────────────────────────────────────
|
|
122
|
+
/**
|
|
123
|
+
* Parse an OpenAPI 3 spec and return all non-2xx (error) response scenarios.
|
|
124
|
+
*/
|
|
125
|
+
async function parseErrorScenarios(specPath) {
|
|
126
|
+
var _a;
|
|
127
|
+
const api = (await swagger_parser_1.default.dereference(specPath));
|
|
128
|
+
const scenarios = [];
|
|
129
|
+
if (!api.paths)
|
|
130
|
+
return scenarios;
|
|
131
|
+
for (const [apiPath, pathItem] of Object.entries(api.paths)) {
|
|
132
|
+
if (!pathItem)
|
|
133
|
+
continue;
|
|
134
|
+
for (const method of HTTP_METHODS) {
|
|
135
|
+
const operation = pathItem[method];
|
|
136
|
+
if (!operation)
|
|
137
|
+
continue;
|
|
138
|
+
if (!operation.responses)
|
|
139
|
+
continue;
|
|
140
|
+
const endpoint = `${method.toUpperCase()} ${apiPath}`;
|
|
141
|
+
for (const [statusCodeStr, responseObj] of Object.entries(operation.responses)) {
|
|
142
|
+
const statusCode = parseInt(statusCodeStr, 10);
|
|
143
|
+
// Only 4xx and 5xx responses
|
|
144
|
+
if (isNaN(statusCode) || statusCode < 400)
|
|
145
|
+
continue;
|
|
146
|
+
const response = responseObj;
|
|
147
|
+
const description = (_a = response.description) !== null && _a !== void 0 ? _a : '';
|
|
148
|
+
const categories = statusCodeToCategories(statusCode, description);
|
|
149
|
+
scenarios.push({
|
|
150
|
+
id: `${endpoint}:${statusCode}`,
|
|
151
|
+
endpoint,
|
|
152
|
+
method: method.toUpperCase(),
|
|
153
|
+
path: apiPath,
|
|
154
|
+
statusCode,
|
|
155
|
+
description,
|
|
156
|
+
categories,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return scenarios;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Split a test file into individual test blocks (test / it declarations).
|
|
165
|
+
*/
|
|
166
|
+
function extractTestSegments(fileContents) {
|
|
167
|
+
const segments = [];
|
|
168
|
+
const declPattern = /\b(?:test|it)\s*\(\s*(['"`])([\s\S]*?)\1/g;
|
|
169
|
+
const positions = [];
|
|
170
|
+
let m;
|
|
171
|
+
while ((m = declPattern.exec(fileContents)) !== null) {
|
|
172
|
+
positions.push({ start: m.index, desc: m[2] });
|
|
173
|
+
}
|
|
174
|
+
for (let i = 0; i < positions.length; i++) {
|
|
175
|
+
const start = positions[i].start;
|
|
176
|
+
const end = i + 1 < positions.length ? positions[i + 1].start : fileContents.length;
|
|
177
|
+
segments.push({ description: positions[i].desc, content: fileContents.slice(start, end) });
|
|
178
|
+
}
|
|
179
|
+
return segments;
|
|
180
|
+
}
|
|
181
|
+
// ─── Endpoint matching ────────────────────────────────────────────────────────
|
|
182
|
+
/**
|
|
183
|
+
* Return true if the test segment appears to exercise the endpoint associated
|
|
184
|
+
* with the given error scenario.
|
|
185
|
+
*
|
|
186
|
+
* Detection strategy (in order):
|
|
187
|
+
* 1. Description contains "METHOD /pathBase" (e.g. "POST /users")
|
|
188
|
+
* 2. Description contains the path base
|
|
189
|
+
* 3. Content contains the path base AND the HTTP method as a string literal
|
|
190
|
+
*/
|
|
191
|
+
function segmentMentionsEndpoint(segment, scenario) {
|
|
192
|
+
const contentLower = segment.content.toLowerCase();
|
|
193
|
+
const descLower = segment.description.toLowerCase();
|
|
194
|
+
const pathBase = scenario.path.split('{')[0].toLowerCase(); // e.g. "/users/" or "/users"
|
|
195
|
+
const methodLower = scenario.method.toLowerCase();
|
|
196
|
+
// 1. Description contains "METHOD /pathBase" phrase
|
|
197
|
+
if (descLower.includes(`${methodLower} ${pathBase}`))
|
|
198
|
+
return true;
|
|
199
|
+
// 2. Description contains the pathBase AND method keyword
|
|
200
|
+
if (descLower.includes(pathBase) && descLower.includes(methodLower))
|
|
201
|
+
return true;
|
|
202
|
+
// 3. Content contains pathBase (as a URL string) AND method (as a string literal)
|
|
203
|
+
const pathInContent = contentLower.includes(pathBase);
|
|
204
|
+
const methodInContent = contentLower.includes(`'${methodLower}'`) ||
|
|
205
|
+
contentLower.includes(`"${methodLower}"`);
|
|
206
|
+
if (pathInContent && methodInContent)
|
|
207
|
+
return true;
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
// ─── Category heuristics ──────────────────────────────────────────────────────
|
|
211
|
+
/**
|
|
212
|
+
* Return true when a segment's description or content hints that it is
|
|
213
|
+
* exercising the given error category.
|
|
214
|
+
*/
|
|
215
|
+
function matchesCategoryHeuristic(segment, category) {
|
|
216
|
+
const descLower = segment.description.toLowerCase();
|
|
217
|
+
const contentLower = segment.content.toLowerCase();
|
|
218
|
+
switch (category) {
|
|
219
|
+
case 'missing-parameter':
|
|
220
|
+
// Description keywords
|
|
221
|
+
if (descLower.includes('missing') ||
|
|
222
|
+
descLower.includes('without') ||
|
|
223
|
+
descLower.includes('omit') ||
|
|
224
|
+
descLower.includes('no body') ||
|
|
225
|
+
descLower.includes('required field'))
|
|
226
|
+
return true;
|
|
227
|
+
// Code: delete obj.field or field = undefined
|
|
228
|
+
if (/delete\s+\w+\.\w+/.test(segment.content))
|
|
229
|
+
return true;
|
|
230
|
+
if (/\w+\s*=\s*undefined/.test(segment.content))
|
|
231
|
+
return true;
|
|
232
|
+
return false;
|
|
233
|
+
case 'invalid-value':
|
|
234
|
+
// Description keywords
|
|
235
|
+
if (descLower.includes('invalid') ||
|
|
236
|
+
descLower.includes('wrong type') ||
|
|
237
|
+
descLower.includes('bad value') ||
|
|
238
|
+
descLower.includes('malformed') ||
|
|
239
|
+
descLower.includes('incorrect format') ||
|
|
240
|
+
descLower.includes('out of range') ||
|
|
241
|
+
descLower.includes('exceeds'))
|
|
242
|
+
return true;
|
|
243
|
+
// Code: field: null or field: undefined
|
|
244
|
+
if (/['"]?\w+['"]?\s*:\s*(null|undefined)\b/.test(segment.content))
|
|
245
|
+
return true;
|
|
246
|
+
return false;
|
|
247
|
+
case 'unauthorized':
|
|
248
|
+
// Description keywords
|
|
249
|
+
if (descLower.includes('unauthorized') ||
|
|
250
|
+
descLower.includes('unauthenticated') ||
|
|
251
|
+
descLower.includes('no token') ||
|
|
252
|
+
descLower.includes('missing token') ||
|
|
253
|
+
descLower.includes('invalid token') ||
|
|
254
|
+
descLower.includes('no auth') ||
|
|
255
|
+
descLower.includes('missing auth') ||
|
|
256
|
+
descLower.includes('without auth') ||
|
|
257
|
+
descLower.includes('without token') ||
|
|
258
|
+
descLower.includes('missing api key') ||
|
|
259
|
+
descLower.includes('invalid api key') ||
|
|
260
|
+
descLower.includes('no api key'))
|
|
261
|
+
return true;
|
|
262
|
+
// Code: Authorization header with empty or bare "Bearer " token
|
|
263
|
+
if (/authorization\s*:\s*['"]\s*['"]/.test(contentLower))
|
|
264
|
+
return true;
|
|
265
|
+
if (/authorization\s*:\s*['"]bearer\s+['"]/.test(contentLower))
|
|
266
|
+
return true;
|
|
267
|
+
return false;
|
|
268
|
+
case 'forbidden':
|
|
269
|
+
// Description keywords
|
|
270
|
+
if (descLower.includes('forbidden') ||
|
|
271
|
+
descLower.includes('not allowed') ||
|
|
272
|
+
descLower.includes('insufficient permission') ||
|
|
273
|
+
descLower.includes('access denied') ||
|
|
274
|
+
descLower.includes('no permission'))
|
|
275
|
+
return true;
|
|
276
|
+
return false;
|
|
277
|
+
case 'not-found':
|
|
278
|
+
// Description keywords
|
|
279
|
+
if (descLower.includes('not found') ||
|
|
280
|
+
descLower.includes('nonexistent') ||
|
|
281
|
+
descLower.includes('non-existent') ||
|
|
282
|
+
descLower.includes('unknown id') ||
|
|
283
|
+
descLower.includes('does not exist') ||
|
|
284
|
+
descLower.includes('missing resource'))
|
|
285
|
+
return true;
|
|
286
|
+
// Code: very large / obviously fake numeric IDs (6+ digits)
|
|
287
|
+
if (/\/\d{6,}/.test(segment.content))
|
|
288
|
+
return true;
|
|
289
|
+
if (/['"`]\d{6,}['"`]/.test(segment.content))
|
|
290
|
+
return true;
|
|
291
|
+
// Nil UUID
|
|
292
|
+
if (/00000000-0000-0000-0000-000000000000/.test(segment.content))
|
|
293
|
+
return true;
|
|
294
|
+
return false;
|
|
295
|
+
case 'conflict':
|
|
296
|
+
if (descLower.includes('conflict') ||
|
|
297
|
+
descLower.includes('duplicate') ||
|
|
298
|
+
descLower.includes('already exists') ||
|
|
299
|
+
descLower.includes('already registered') ||
|
|
300
|
+
descLower.includes('already in use') ||
|
|
301
|
+
descLower.includes('taken'))
|
|
302
|
+
return true;
|
|
303
|
+
return false;
|
|
304
|
+
case 'server-error':
|
|
305
|
+
if (descLower.includes('server error') ||
|
|
306
|
+
descLower.includes('internal error') ||
|
|
307
|
+
descLower.includes('service unavailable') ||
|
|
308
|
+
descLower.includes('unexpected error'))
|
|
309
|
+
return true;
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// ─── Coverage determination ───────────────────────────────────────────────────
|
|
314
|
+
/**
|
|
315
|
+
* Return true if the segment contains a direct status-code assertion
|
|
316
|
+
* matching the scenario's status code.
|
|
317
|
+
*
|
|
318
|
+
* Recognised patterns:
|
|
319
|
+
* .toBe(400) .toEqual(400) status === 400 statusCode: 400
|
|
320
|
+
* expect(…).toBe(400)
|
|
321
|
+
*/
|
|
322
|
+
function hasStatusCodeAssertion(segment, statusCode) {
|
|
323
|
+
const patterns = [
|
|
324
|
+
new RegExp(`\\.toBe\\(\\s*${statusCode}\\s*\\)`),
|
|
325
|
+
new RegExp(`\\.toEqual\\(\\s*${statusCode}\\s*\\)`),
|
|
326
|
+
new RegExp(`status\\s*[=!]=+\\s*${statusCode}\\b`),
|
|
327
|
+
new RegExp(`statusCode\\s*[=!]=+\\s*${statusCode}\\b`),
|
|
328
|
+
new RegExp(`statusCode\\s*:\\s*${statusCode}\\b`),
|
|
329
|
+
];
|
|
330
|
+
return patterns.some((p) => p.test(segment.content));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Return true if the segment contains an assertion on an error message body
|
|
334
|
+
* that is consistent with the given category.
|
|
335
|
+
*
|
|
336
|
+
* Examples detected:
|
|
337
|
+
* expect(res.body).toHaveProperty('error')
|
|
338
|
+
* expect(res.body.message).toContain('not found')
|
|
339
|
+
* expect(thrown.message).toMatch(/unauthorized/i)
|
|
340
|
+
*/
|
|
341
|
+
function hasErrorMessageAssertion(segment, category) {
|
|
342
|
+
const contentLower = segment.content.toLowerCase();
|
|
343
|
+
// Generic: any error-body assertion
|
|
344
|
+
const hasBodyErrorProp = /tohaveproperty\s*\(\s*['"]error['"]/.test(contentLower) ||
|
|
345
|
+
/\.body\.error/.test(contentLower) ||
|
|
346
|
+
/\.body\.message/.test(contentLower);
|
|
347
|
+
if (!hasBodyErrorProp)
|
|
348
|
+
return false;
|
|
349
|
+
// Narrow by category keyword inside the assertion
|
|
350
|
+
const categoryKeywords = {
|
|
351
|
+
'missing-parameter': ['required', 'missing', 'must be provided'],
|
|
352
|
+
'invalid-value': ['invalid', 'format', 'type', 'must be', 'out of range'],
|
|
353
|
+
unauthorized: ['unauthorized', 'unauthenticated', 'token', 'api key'],
|
|
354
|
+
forbidden: ['forbidden', 'permission', 'access denied'],
|
|
355
|
+
'not-found': ['not found', 'does not exist'],
|
|
356
|
+
conflict: ['conflict', 'duplicate', 'already', 'in use'],
|
|
357
|
+
'server-error': ['internal', 'server error', 'unexpected'],
|
|
358
|
+
};
|
|
359
|
+
const keywords = categoryKeywords[category];
|
|
360
|
+
// If any keyword appears anywhere in the test content → match
|
|
361
|
+
return keywords.some((kw) => contentLower.includes(kw));
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Decide whether a single test segment covers the given error scenario.
|
|
365
|
+
*/
|
|
366
|
+
function segmentCoversScenario(segment, scenario) {
|
|
367
|
+
// 1. Direct status code assertion
|
|
368
|
+
if (hasStatusCodeAssertion(segment, scenario.statusCode))
|
|
369
|
+
return true;
|
|
370
|
+
// 2. Category heuristics from description
|
|
371
|
+
for (const cat of scenario.categories) {
|
|
372
|
+
if (matchesCategoryHeuristic(segment, cat))
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// 3. Indirect error-body assertion with category keywords
|
|
376
|
+
for (const cat of scenario.categories) {
|
|
377
|
+
if (hasErrorMessageAssertion(segment, cat))
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
// ─── AST augmentation ─────────────────────────────────────────────────────────
|
|
383
|
+
/** Detect language from file extension for AST analysis. */
|
|
384
|
+
function detectLanguageForAst(filePath) {
|
|
385
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
386
|
+
switch (ext) {
|
|
387
|
+
case '.ts':
|
|
388
|
+
case '.tsx': return 'typescript';
|
|
389
|
+
case '.js':
|
|
390
|
+
case '.jsx': return 'javascript';
|
|
391
|
+
case '.java': return 'java';
|
|
392
|
+
case '.kt':
|
|
393
|
+
case '.kts': return 'kotlin';
|
|
394
|
+
case '.py': return 'python';
|
|
395
|
+
case '.rb': return 'ruby';
|
|
396
|
+
case '.feature': return 'cucumber';
|
|
397
|
+
default: return 'auto';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/** Normalize a path for loose matching against OpenAPI templates. */
|
|
401
|
+
function normalizeErrPath(p) {
|
|
402
|
+
return p.split('?')[0].replace(/\/$/, '').toLowerCase();
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Return true when the interaction's path loosely matches the scenario path.
|
|
406
|
+
*/
|
|
407
|
+
function errorPathMatches(interaction, scenarioPath) {
|
|
408
|
+
var _a;
|
|
409
|
+
const iPath = normalizeErrPath((_a = interaction.normalizedPath) !== null && _a !== void 0 ? _a : interaction.path);
|
|
410
|
+
const aPath = normalizeErrPath(scenarioPath);
|
|
411
|
+
if (iPath === aPath)
|
|
412
|
+
return true;
|
|
413
|
+
const templateBase = aPath.split('{')[0].replace(/\/$/, '');
|
|
414
|
+
return !!(templateBase && iPath.startsWith(templateBase));
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Map parameterScenarios strings to error categories.
|
|
418
|
+
*/
|
|
419
|
+
function scenariosToErrorCategories(scenarios) {
|
|
420
|
+
const s = scenarios.map((x) => x.toLowerCase());
|
|
421
|
+
const cats = [];
|
|
422
|
+
if (s.some((x) => x.includes('missing') || x.includes('required')))
|
|
423
|
+
cats.push('missing-parameter');
|
|
424
|
+
if (s.some((x) => x.includes('invalid') || x.includes('bad') || x.includes('malformed')))
|
|
425
|
+
cats.push('invalid-value');
|
|
426
|
+
if (s.some((x) => x.includes('unauth') || x.includes('401')))
|
|
427
|
+
cats.push('unauthorized');
|
|
428
|
+
if (s.some((x) => x.includes('forbidden') || x.includes('403')))
|
|
429
|
+
cats.push('forbidden');
|
|
430
|
+
if (s.some((x) => x.includes('not-found') || x.includes('404')))
|
|
431
|
+
cats.push('not-found');
|
|
432
|
+
if (s.some((x) => x.includes('conflict') || x.includes('409')))
|
|
433
|
+
cats.push('conflict');
|
|
434
|
+
if (s.some((x) => x.includes('server') || x.includes('5xx') || x.includes('500')))
|
|
435
|
+
cats.push('server-error');
|
|
436
|
+
return cats;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Build AST interaction map from test files for error coverage augmentation.
|
|
440
|
+
*/
|
|
441
|
+
function buildErrorAstMap(testFiles, astOptions) {
|
|
442
|
+
var _a;
|
|
443
|
+
(0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
|
|
444
|
+
const context = (0, astAnalysisOrchestrator_1.buildAnalysisContext)(astOptions.astConfig, astOptions.deepConfig);
|
|
445
|
+
const map = new Map();
|
|
446
|
+
for (const filePath of testFiles) {
|
|
447
|
+
let content;
|
|
448
|
+
try {
|
|
449
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const lang = detectLanguageForAst(filePath);
|
|
455
|
+
const interactions = (0, astAnalysisOrchestrator_1.analyzeFile)(content, filePath, lang, context);
|
|
456
|
+
for (const interaction of interactions) {
|
|
457
|
+
const key = normalizeErrPath((_a = interaction.normalizedPath) !== null && _a !== void 0 ? _a : interaction.path);
|
|
458
|
+
if (!map.has(key))
|
|
459
|
+
map.set(key, []);
|
|
460
|
+
map.get(key).push(interaction);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return map;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Check if AST interactions at the scenario's endpoint cover this error scenario.
|
|
467
|
+
* Returns truthy with metadata when an AST-derived signal is found.
|
|
468
|
+
*/
|
|
469
|
+
function checkAstErrorCoverage(matchingInteractions, scenario) {
|
|
470
|
+
let bestInteraction;
|
|
471
|
+
for (const interaction of matchingInteractions) {
|
|
472
|
+
// A status-code assertion indicates the test checks an HTTP status at this
|
|
473
|
+
// endpoint — a strong signal that the scenario is exercised
|
|
474
|
+
if (interaction.assertionType === 'status-code' ||
|
|
475
|
+
interaction.assertionType === 'fluent-chain') {
|
|
476
|
+
bestInteraction = interaction;
|
|
477
|
+
}
|
|
478
|
+
// parameterScenarios → error category matching
|
|
479
|
+
if (interaction.parameterScenarios && interaction.parameterScenarios.length > 0) {
|
|
480
|
+
const cats = scenariosToErrorCategories(interaction.parameterScenarios);
|
|
481
|
+
if (cats.some((c) => scenario.categories.includes(c))) {
|
|
482
|
+
bestInteraction = interaction;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!bestInteraction)
|
|
487
|
+
return { covered: false };
|
|
488
|
+
return {
|
|
489
|
+
covered: true,
|
|
490
|
+
astMetadata: {
|
|
491
|
+
sourceLanguage: bestInteraction.sourceLanguage,
|
|
492
|
+
resolutionType: bestInteraction.resolutionType,
|
|
493
|
+
confidence: bestInteraction.confidence,
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
// ─── Analysis ─────────────────────────────────────────────────────────────────
|
|
498
|
+
/**
|
|
499
|
+
* Scan test files matching testGlob and determine which error scenarios are
|
|
500
|
+
* covered. Optionally augments text-scan results with AST-derived semantic
|
|
501
|
+
* signals when `astOptions` is provided.
|
|
502
|
+
*/
|
|
503
|
+
async function analyzeErrorCoverage(scenarios, testGlob, astOptions) {
|
|
504
|
+
const testFiles = await (0, fast_glob_1.default)(testGlob, { onlyFiles: true });
|
|
505
|
+
// Pre-read and segment all test files
|
|
506
|
+
const allSegments = [];
|
|
507
|
+
for (const filePath of testFiles) {
|
|
508
|
+
const contents = fs.readFileSync(filePath, 'utf-8');
|
|
509
|
+
allSegments.push(...extractTestSegments(contents));
|
|
510
|
+
}
|
|
511
|
+
// Build AST interaction map when AST options provided
|
|
512
|
+
const astMap = astOptions
|
|
513
|
+
? buildErrorAstMap(testFiles, astOptions)
|
|
514
|
+
: null;
|
|
515
|
+
return scenarios.map((scenario) => {
|
|
516
|
+
var _a;
|
|
517
|
+
// ── Text-scan pass ──────────────────────────────────────────────────────
|
|
518
|
+
const matchedTests = [];
|
|
519
|
+
for (const segment of allSegments) {
|
|
520
|
+
if (segmentMentionsEndpoint(segment, scenario) && segmentCoversScenario(segment, scenario)) {
|
|
521
|
+
matchedTests.push(segment.description);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
let covered = matchedTests.length > 0;
|
|
525
|
+
let astMetadata;
|
|
526
|
+
// ── AST augmentation pass ───────────────────────────────────────────────
|
|
527
|
+
if (astMap !== null && !covered) {
|
|
528
|
+
const matchingInteractions = [];
|
|
529
|
+
for (const [, interactions] of astMap) {
|
|
530
|
+
for (const interaction of interactions) {
|
|
531
|
+
if (errorPathMatches(interaction, scenario.path) &&
|
|
532
|
+
interaction.method.toUpperCase() === scenario.method.toUpperCase()) {
|
|
533
|
+
matchingInteractions.push(interaction);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (matchingInteractions.length > 0) {
|
|
538
|
+
const astResult = checkAstErrorCoverage(matchingInteractions, scenario);
|
|
539
|
+
if (astResult.covered) {
|
|
540
|
+
covered = true;
|
|
541
|
+
astMetadata = astResult.astMetadata;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else if (covered && astMap !== null) {
|
|
546
|
+
// Already covered by text-scan — still attach AST metadata if available
|
|
547
|
+
const matchingInteractions = [];
|
|
548
|
+
for (const [, interactions] of astMap) {
|
|
549
|
+
for (const interaction of interactions) {
|
|
550
|
+
if (errorPathMatches(interaction, scenario.path) &&
|
|
551
|
+
interaction.method.toUpperCase() === scenario.method.toUpperCase()) {
|
|
552
|
+
matchingInteractions.push(interaction);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (matchingInteractions.length > 0) {
|
|
557
|
+
const best = (_a = matchingInteractions.find((i) => i.confidence === 'high' || i.confidence === 'medium')) !== null && _a !== void 0 ? _a : matchingInteractions[0];
|
|
558
|
+
astMetadata = {
|
|
559
|
+
sourceLanguage: best.sourceLanguage,
|
|
560
|
+
resolutionType: best.resolutionType,
|
|
561
|
+
confidence: best.confidence,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return { scenario, covered, matchedTests, astMetadata };
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
// ─── Report building ──────────────────────────────────────────────────────────
|
|
569
|
+
/**
|
|
570
|
+
* Aggregate error coverage results into a summary report.
|
|
571
|
+
*/
|
|
572
|
+
function buildErrorCoverageReport(coverages) {
|
|
573
|
+
const total = coverages.length;
|
|
574
|
+
const covered = coverages.filter((c) => c.covered).length;
|
|
575
|
+
const percentage = total === 0 ? 0 : Math.round((covered / total) * 10000) / 100;
|
|
576
|
+
// Per-category tallies
|
|
577
|
+
const categorySummary = exports.ERROR_CATEGORIES.reduce((acc, cat) => {
|
|
578
|
+
acc[cat] = { total: 0, covered: 0 };
|
|
579
|
+
return acc;
|
|
580
|
+
}, {});
|
|
581
|
+
for (const c of coverages) {
|
|
582
|
+
for (const cat of c.scenario.categories) {
|
|
583
|
+
categorySummary[cat].total += 1;
|
|
584
|
+
if (c.covered)
|
|
585
|
+
categorySummary[cat].covered += 1;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return { total, covered, percentage, scenarios: coverages, categorySummary };
|
|
589
|
+
}
|
|
590
|
+
// ─── Report generation ────────────────────────────────────────────────────────
|
|
591
|
+
/**
|
|
592
|
+
* Write JSON and HTML error-coverage reports to reportsDir.
|
|
593
|
+
*/
|
|
594
|
+
function generateErrorReports(report, reportsDir) {
|
|
595
|
+
if (!fs.existsSync(reportsDir)) {
|
|
596
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
597
|
+
}
|
|
598
|
+
// ── JSON ─────────────────────────────────────────────────────────────────
|
|
599
|
+
const jsonPath = path.join(reportsDir, 'error-coverage.json');
|
|
600
|
+
const jsonReport = {
|
|
601
|
+
total: report.total,
|
|
602
|
+
covered: report.covered,
|
|
603
|
+
percentage: report.percentage,
|
|
604
|
+
categorySummary: report.categorySummary,
|
|
605
|
+
scenarios: report.scenarios.map(({ scenario, covered, matchedTests }) => ({
|
|
606
|
+
id: scenario.id,
|
|
607
|
+
endpoint: scenario.endpoint,
|
|
608
|
+
statusCode: scenario.statusCode,
|
|
609
|
+
description: scenario.description,
|
|
610
|
+
categories: scenario.categories,
|
|
611
|
+
covered,
|
|
612
|
+
matchedTests,
|
|
613
|
+
})),
|
|
614
|
+
};
|
|
615
|
+
fs.writeFileSync(jsonPath, JSON.stringify(jsonReport, null, 2), 'utf-8');
|
|
616
|
+
// ── HTML ─────────────────────────────────────────────────────────────────
|
|
617
|
+
const htmlPath = path.join(reportsDir, 'error-coverage.html');
|
|
618
|
+
const catHeaders = exports.ERROR_CATEGORIES.map((c) => `<th>${c}</th>`).join('\n ');
|
|
619
|
+
const rows = report.scenarios
|
|
620
|
+
.map(({ scenario, covered, matchedTests }) => {
|
|
621
|
+
const rowClass = covered ? 'covered' : 'uncovered';
|
|
622
|
+
const statusCell = covered ? '✅ Covered' : '❌ Not covered';
|
|
623
|
+
const catCells = exports.ERROR_CATEGORIES.map((cat) => {
|
|
624
|
+
if (!scenario.categories.includes(cat))
|
|
625
|
+
return '<td class="na">—</td>';
|
|
626
|
+
return covered ? '<td class="yes">✅</td>' : '<td class="no">❌</td>';
|
|
627
|
+
}).join('\n ');
|
|
628
|
+
const testsHint = matchedTests.length > 0 ? matchedTests.map((t) => `• ${t}`).join('<br>') : '—';
|
|
629
|
+
return ` <tr class="${rowClass}" title="${testsHint}">
|
|
630
|
+
<td>${scenario.endpoint}</td>
|
|
631
|
+
<td>${scenario.statusCode}</td>
|
|
632
|
+
<td>${scenario.description}</td>
|
|
633
|
+
<td>${statusCell}</td>
|
|
634
|
+
${catCells}
|
|
635
|
+
</tr>`;
|
|
636
|
+
})
|
|
637
|
+
.join('\n');
|
|
638
|
+
const catSummaryRows = exports.ERROR_CATEGORIES.map((cat) => {
|
|
639
|
+
const s = report.categorySummary[cat];
|
|
640
|
+
const pct = s.total === 0 ? '—' : `${Math.round((s.covered / s.total) * 100)}%`;
|
|
641
|
+
return ` <tr>
|
|
642
|
+
<td>${cat}</td>
|
|
643
|
+
<td>${s.covered}/${s.total}</td>
|
|
644
|
+
<td>${pct}</td>
|
|
645
|
+
</tr>`;
|
|
646
|
+
}).join('\n');
|
|
647
|
+
const html = `<!DOCTYPE html>
|
|
648
|
+
<html lang="en">
|
|
649
|
+
<head>
|
|
650
|
+
<meta charset="UTF-8">
|
|
651
|
+
<title>Error Coverage Report</title>
|
|
652
|
+
<style>
|
|
653
|
+
body { font-family: sans-serif; padding: 2rem; }
|
|
654
|
+
h1, h2 { margin-bottom: 0.5rem; }
|
|
655
|
+
.summary { margin-bottom: 1.5rem; font-size: 1.1rem; }
|
|
656
|
+
table { border-collapse: collapse; width: 100%; margin-bottom: 2rem; }
|
|
657
|
+
th, td { border: 1px solid #ccc; padding: 0.5rem 1rem; text-align: left; }
|
|
658
|
+
th { background: #f0f0f0; }
|
|
659
|
+
tr.covered { background: #e6ffe6; }
|
|
660
|
+
tr.uncovered { background: #ffe6e6; }
|
|
661
|
+
td.yes { color: #2a7a2a; font-weight: bold; }
|
|
662
|
+
td.no { color: #aa2222; font-weight: bold; }
|
|
663
|
+
td.na { color: #999; }
|
|
664
|
+
</style>
|
|
665
|
+
</head>
|
|
666
|
+
<body>
|
|
667
|
+
<h1>Error Coverage Report</h1>
|
|
668
|
+
<div class="summary">
|
|
669
|
+
Covered: <strong>${report.covered}/${report.total}</strong> error scenarios
|
|
670
|
+
(<strong>${report.percentage}%</strong>)
|
|
671
|
+
</div>
|
|
672
|
+
<table>
|
|
673
|
+
<thead>
|
|
674
|
+
<tr>
|
|
675
|
+
<th>Endpoint</th>
|
|
676
|
+
<th>Status</th>
|
|
677
|
+
<th>Description</th>
|
|
678
|
+
<th>Coverage</th>
|
|
679
|
+
${catHeaders}
|
|
680
|
+
</tr>
|
|
681
|
+
</thead>
|
|
682
|
+
<tbody>
|
|
683
|
+
${rows}
|
|
684
|
+
</tbody>
|
|
685
|
+
</table>
|
|
686
|
+
<h2>Category Summary</h2>
|
|
687
|
+
<table>
|
|
688
|
+
<thead>
|
|
689
|
+
<tr><th>Category</th><th>Covered / Total</th><th>%</th></tr>
|
|
690
|
+
</thead>
|
|
691
|
+
<tbody>
|
|
692
|
+
${catSummaryRows}
|
|
693
|
+
</tbody>
|
|
694
|
+
</table>
|
|
695
|
+
</body>
|
|
696
|
+
</html>`;
|
|
697
|
+
fs.writeFileSync(htmlPath, html, 'utf-8');
|
|
698
|
+
}
|