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,629 @@
|
|
|
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.parseParameters = parseParameters;
|
|
40
|
+
exports.analyzeParameterCoverage = analyzeParameterCoverage;
|
|
41
|
+
exports.buildParameterCoverageReport = buildParameterCoverageReport;
|
|
42
|
+
exports.generateParameterReports = generateParameterReports;
|
|
43
|
+
const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
47
|
+
const astAnalysisOrchestrator_1 = require("./ast/astAnalysisOrchestrator");
|
|
48
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
49
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
50
|
+
function escapeRegex(s) {
|
|
51
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
52
|
+
}
|
|
53
|
+
function extractSchemaConstraints(schema) {
|
|
54
|
+
if (!schema)
|
|
55
|
+
return {};
|
|
56
|
+
return {
|
|
57
|
+
type: schema.type,
|
|
58
|
+
format: schema.format,
|
|
59
|
+
minimum: schema.minimum,
|
|
60
|
+
maximum: schema.maximum,
|
|
61
|
+
minLength: schema.minLength,
|
|
62
|
+
maxLength: schema.maxLength,
|
|
63
|
+
pattern: schema.pattern,
|
|
64
|
+
enum: schema.enum,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// ─── Parameter parsing ───────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Parse an OpenAPI 3 spec file and return a flat list of all parameters,
|
|
70
|
+
* including request-body properties treated as body parameters.
|
|
71
|
+
*/
|
|
72
|
+
async function parseParameters(specPath) {
|
|
73
|
+
var _a, _b, _c, _d;
|
|
74
|
+
const api = (await swagger_parser_1.default.dereference(specPath));
|
|
75
|
+
const params = [];
|
|
76
|
+
if (!api.paths)
|
|
77
|
+
return params;
|
|
78
|
+
for (const [apiPath, pathItem] of Object.entries(api.paths)) {
|
|
79
|
+
if (!pathItem)
|
|
80
|
+
continue;
|
|
81
|
+
// Path-level parameters (shared across all operations in this path)
|
|
82
|
+
const pathLevelParams = ((_a = pathItem.parameters) !== null && _a !== void 0 ? _a : []);
|
|
83
|
+
for (const method of HTTP_METHODS) {
|
|
84
|
+
const operation = pathItem[method];
|
|
85
|
+
if (!operation)
|
|
86
|
+
continue;
|
|
87
|
+
const endpoint = `${method.toUpperCase()} ${apiPath}`;
|
|
88
|
+
// Merge path-level and operation-level parameters; operation-level wins
|
|
89
|
+
const mergedParams = new Map();
|
|
90
|
+
for (const p of pathLevelParams) {
|
|
91
|
+
mergedParams.set(`${p.in}:${p.name}`, p);
|
|
92
|
+
}
|
|
93
|
+
const operationParams = ((_b = operation.parameters) !== null && _b !== void 0 ? _b : []);
|
|
94
|
+
for (const p of operationParams) {
|
|
95
|
+
mergedParams.set(`${p.in}:${p.name}`, p);
|
|
96
|
+
}
|
|
97
|
+
for (const p of mergedParams.values()) {
|
|
98
|
+
params.push({
|
|
99
|
+
endpoint,
|
|
100
|
+
method: method.toUpperCase(),
|
|
101
|
+
path: apiPath,
|
|
102
|
+
name: p.name,
|
|
103
|
+
location: p.in,
|
|
104
|
+
required: p.required === true || p.in === 'path',
|
|
105
|
+
schema: extractSchemaConstraints(p.schema),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Request body – treat each top-level JSON schema property as a parameter
|
|
109
|
+
if (operation.requestBody) {
|
|
110
|
+
const rb = operation.requestBody;
|
|
111
|
+
const jsonContent = (_c = rb.content) === null || _c === void 0 ? void 0 : _c['application/json'];
|
|
112
|
+
if (jsonContent === null || jsonContent === void 0 ? void 0 : jsonContent.schema) {
|
|
113
|
+
const schema = jsonContent.schema;
|
|
114
|
+
if (schema.type === 'object' && schema.properties) {
|
|
115
|
+
const required = (_d = schema.required) !== null && _d !== void 0 ? _d : [];
|
|
116
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
117
|
+
params.push({
|
|
118
|
+
endpoint,
|
|
119
|
+
method: method.toUpperCase(),
|
|
120
|
+
path: apiPath,
|
|
121
|
+
name: propName,
|
|
122
|
+
location: 'body',
|
|
123
|
+
required: required.includes(propName),
|
|
124
|
+
schema: extractSchemaConstraints(propSchema),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return params;
|
|
133
|
+
}
|
|
134
|
+
// ─── AST augmentation ─────────────────────────────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* Infer coverage category flags from a `parameterScenarios` string array (as
|
|
137
|
+
* populated by language analyzers) or from the interaction's semantic context.
|
|
138
|
+
*/
|
|
139
|
+
function scenariosToFlags(scenarios) {
|
|
140
|
+
const s = scenarios.map((x) => x.toLowerCase());
|
|
141
|
+
return {
|
|
142
|
+
validValue: s.includes('valid') ||
|
|
143
|
+
s.includes('happy-path') ||
|
|
144
|
+
s.includes('success') ||
|
|
145
|
+
s.includes('positive'),
|
|
146
|
+
boundaryValue: s.includes('boundary') ||
|
|
147
|
+
s.includes('min') ||
|
|
148
|
+
s.includes('max') ||
|
|
149
|
+
s.includes('edge') ||
|
|
150
|
+
s.includes('zero') ||
|
|
151
|
+
s.includes('empty') ||
|
|
152
|
+
s.includes('oversized'),
|
|
153
|
+
missing: s.includes('missing') ||
|
|
154
|
+
s.includes('missing-required') ||
|
|
155
|
+
s.includes('absent') ||
|
|
156
|
+
s.includes('omitted'),
|
|
157
|
+
invalidValue: s.includes('invalid') ||
|
|
158
|
+
s.includes('invalid-value') ||
|
|
159
|
+
s.includes('bad-value') ||
|
|
160
|
+
s.includes('wrong-type') ||
|
|
161
|
+
s.includes('malformed') ||
|
|
162
|
+
s.includes('null') ||
|
|
163
|
+
s.includes('invalid-enum'),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Detect the language of a file from its extension for AST analysis.
|
|
168
|
+
*/
|
|
169
|
+
function detectLanguage(filePath) {
|
|
170
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
171
|
+
switch (ext) {
|
|
172
|
+
case '.ts':
|
|
173
|
+
case '.tsx':
|
|
174
|
+
return 'typescript';
|
|
175
|
+
case '.js':
|
|
176
|
+
case '.jsx':
|
|
177
|
+
return 'javascript';
|
|
178
|
+
case '.java':
|
|
179
|
+
return 'java';
|
|
180
|
+
case '.kt':
|
|
181
|
+
case '.kts':
|
|
182
|
+
return 'kotlin';
|
|
183
|
+
case '.py':
|
|
184
|
+
return 'python';
|
|
185
|
+
case '.rb':
|
|
186
|
+
return 'ruby';
|
|
187
|
+
case '.feature':
|
|
188
|
+
return 'cucumber';
|
|
189
|
+
default:
|
|
190
|
+
return 'auto';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Normalize an endpoint path (possibly with {param} placeholders) so that it
|
|
195
|
+
* can be compared against an OpenAPI path template.
|
|
196
|
+
* e.g. '/users/123' → '/users/{id}' won't match — but '/users/{id}' → '/users/{id}' will.
|
|
197
|
+
* We strip the query string and trailing slash for a loose match.
|
|
198
|
+
*/
|
|
199
|
+
function normalizePath(p) {
|
|
200
|
+
return p.split('?')[0].replace(/\/$/, '').toLowerCase();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Return true when the interaction's resolved path loosely matches the
|
|
204
|
+
* OpenAPI path template (e.g. /users/{id} ≈ /users/).
|
|
205
|
+
*/
|
|
206
|
+
function pathMatches(interaction, apiPath) {
|
|
207
|
+
var _a;
|
|
208
|
+
const iPath = normalizePath((_a = interaction.normalizedPath) !== null && _a !== void 0 ? _a : interaction.path);
|
|
209
|
+
const aPath = normalizePath(apiPath);
|
|
210
|
+
// Exact match
|
|
211
|
+
if (iPath === aPath)
|
|
212
|
+
return true;
|
|
213
|
+
// OpenAPI template prefix match: /users/{id} starts with /users
|
|
214
|
+
const templateBase = aPath.split('{')[0].replace(/\/$/, '');
|
|
215
|
+
if (templateBase && iPath.startsWith(templateBase))
|
|
216
|
+
return true;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Run AST analysis over all files matching `testGlob` and build a map of
|
|
221
|
+
* endpoint path → interactions for quick lookup during parameter coverage.
|
|
222
|
+
*/
|
|
223
|
+
function buildAstInteractionMap(testFiles, astOptions) {
|
|
224
|
+
var _a;
|
|
225
|
+
(0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
|
|
226
|
+
const context = (0, astAnalysisOrchestrator_1.buildAnalysisContext)(astOptions.astConfig, astOptions.deepConfig);
|
|
227
|
+
const map = new Map();
|
|
228
|
+
for (const filePath of testFiles) {
|
|
229
|
+
let content;
|
|
230
|
+
try {
|
|
231
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const lang = detectLanguage(filePath);
|
|
237
|
+
const interactions = (0, astAnalysisOrchestrator_1.analyzeFile)(content, filePath, lang, context);
|
|
238
|
+
for (const interaction of interactions) {
|
|
239
|
+
const key = normalizePath((_a = interaction.normalizedPath) !== null && _a !== void 0 ? _a : interaction.path);
|
|
240
|
+
if (!map.has(key))
|
|
241
|
+
map.set(key, []);
|
|
242
|
+
map.get(key).push(interaction);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return map;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Given all AST interactions that match a parameter's endpoint path, compute
|
|
249
|
+
* AST-derived coverage flags and metadata.
|
|
250
|
+
*/
|
|
251
|
+
function deriveAstCoverage(matchingInteractions) {
|
|
252
|
+
let validValue = false;
|
|
253
|
+
let boundaryValue = false;
|
|
254
|
+
let missing = false;
|
|
255
|
+
let invalidValue = false;
|
|
256
|
+
let bestInteraction;
|
|
257
|
+
for (const interaction of matchingInteractions) {
|
|
258
|
+
// Use parameterScenarios if populated
|
|
259
|
+
if (interaction.parameterScenarios && interaction.parameterScenarios.length > 0) {
|
|
260
|
+
const flags = scenariosToFlags(interaction.parameterScenarios);
|
|
261
|
+
validValue = validValue || flags.validValue;
|
|
262
|
+
boundaryValue = boundaryValue || flags.boundaryValue;
|
|
263
|
+
missing = missing || flags.missing;
|
|
264
|
+
invalidValue = invalidValue || flags.invalidValue;
|
|
265
|
+
}
|
|
266
|
+
// Pick the highest-confidence interaction for metadata
|
|
267
|
+
if (!bestInteraction ||
|
|
268
|
+
(interaction.confidence === 'high' && bestInteraction.confidence !== 'high') ||
|
|
269
|
+
(interaction.confidence === 'medium' && bestInteraction.confidence === 'low')) {
|
|
270
|
+
bestInteraction = interaction;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const astMetadata = bestInteraction
|
|
274
|
+
? {
|
|
275
|
+
sourceLanguage: bestInteraction.sourceLanguage,
|
|
276
|
+
resolutionType: bestInteraction.resolutionType,
|
|
277
|
+
confidence: bestInteraction.confidence,
|
|
278
|
+
}
|
|
279
|
+
: undefined;
|
|
280
|
+
return { validValue, boundaryValue, missing, invalidValue, astMetadata };
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Split a test file into individual test blocks (test / it declarations).
|
|
284
|
+
* Each block includes its description and the code up to the next test.
|
|
285
|
+
*/
|
|
286
|
+
function extractTestSegments(fileContents) {
|
|
287
|
+
const segments = [];
|
|
288
|
+
// Match `test('...', ` or `it('...', ` with any quote style
|
|
289
|
+
const declPattern = /\b(?:test|it)\s*\(\s*(['"`])([\s\S]*?)\1/g;
|
|
290
|
+
const positions = [];
|
|
291
|
+
let m;
|
|
292
|
+
while ((m = declPattern.exec(fileContents)) !== null) {
|
|
293
|
+
positions.push({ start: m.index, desc: m[2] });
|
|
294
|
+
}
|
|
295
|
+
for (let i = 0; i < positions.length; i++) {
|
|
296
|
+
const start = positions[i].start;
|
|
297
|
+
const end = i + 1 < positions.length ? positions[i + 1].start : fileContents.length;
|
|
298
|
+
segments.push({ description: positions[i].desc, content: fileContents.slice(start, end) });
|
|
299
|
+
}
|
|
300
|
+
return segments;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Determine which coverage categories a single test segment addresses
|
|
304
|
+
* for the given parameter.
|
|
305
|
+
*/
|
|
306
|
+
function classifySegment(segment, param) {
|
|
307
|
+
const descLower = segment.description.toLowerCase();
|
|
308
|
+
const contentLower = segment.content.toLowerCase();
|
|
309
|
+
const paramNameLower = param.name.toLowerCase();
|
|
310
|
+
// A segment is relevant if it mentions the base path (before any '{') OR the parameter name
|
|
311
|
+
const pathBase = param.path.split('{')[0].toLowerCase();
|
|
312
|
+
const mentionsPath = contentLower.includes(pathBase);
|
|
313
|
+
const mentionsParam = contentLower.includes(paramNameLower);
|
|
314
|
+
if (!mentionsPath && !mentionsParam) {
|
|
315
|
+
return { validValue: false, boundaryValue: false, missing: false, invalidValue: false };
|
|
316
|
+
}
|
|
317
|
+
// ── Invalid ──────────────────────────────────────────────────────────────
|
|
318
|
+
let invalidValue = false;
|
|
319
|
+
if (descLower.includes('invalid') || descLower.includes('wrong type') || descLower.includes('bad type')) {
|
|
320
|
+
invalidValue = true;
|
|
321
|
+
}
|
|
322
|
+
// null/undefined assigned to the param in object literal
|
|
323
|
+
const nullPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*(null|undefined)`, 'i');
|
|
324
|
+
if (nullPattern.test(segment.content)) {
|
|
325
|
+
invalidValue = true;
|
|
326
|
+
}
|
|
327
|
+
// Wrong type: string value for an integer param
|
|
328
|
+
if (param.schema.type === 'integer' || param.schema.type === 'number') {
|
|
329
|
+
const wrongTypePattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*['"][^'"]+['"]`, 'i');
|
|
330
|
+
if (wrongTypePattern.test(segment.content)) {
|
|
331
|
+
invalidValue = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// ── Missing ───────────────────────────────────────────────────────────────
|
|
335
|
+
let missing = false;
|
|
336
|
+
if (descLower.includes('missing') || descLower.includes('without') || descLower.includes('omit')) {
|
|
337
|
+
missing = true;
|
|
338
|
+
}
|
|
339
|
+
// Required body param absent from an object literal in the test
|
|
340
|
+
if (param.required && param.location === 'body') {
|
|
341
|
+
const bodyLiteralPattern = /\{[^{}]*\}/g;
|
|
342
|
+
let bm;
|
|
343
|
+
while ((bm = bodyLiteralPattern.exec(segment.content)) !== null) {
|
|
344
|
+
const obj = bm[0];
|
|
345
|
+
// Only consider non-empty object literals that have at least one key
|
|
346
|
+
if (/['"]?\w+['"]?\s*:/.test(obj) && !obj.toLowerCase().includes(paramNameLower)) {
|
|
347
|
+
missing = true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// ── Boundary ──────────────────────────────────────────────────────────────
|
|
352
|
+
let boundaryValue = false;
|
|
353
|
+
if (descLower.includes('boundary') ||
|
|
354
|
+
descLower.includes('edge') ||
|
|
355
|
+
descLower.includes('min ') ||
|
|
356
|
+
descLower.includes('max ') ||
|
|
357
|
+
descLower.includes('minimum') ||
|
|
358
|
+
descLower.includes('maximum') ||
|
|
359
|
+
descLower.includes('zero') ||
|
|
360
|
+
descLower.includes('empty') ||
|
|
361
|
+
descLower.includes('limit')) {
|
|
362
|
+
boundaryValue = true;
|
|
363
|
+
}
|
|
364
|
+
if (param.schema.type === 'integer' || param.schema.type === 'number') {
|
|
365
|
+
// 0 or 1 are common boundary values; also check schema min/max explicitly
|
|
366
|
+
const zeroOrOnePattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*(?:0|1)\\b`, 'i');
|
|
367
|
+
if (zeroOrOnePattern.test(segment.content)) {
|
|
368
|
+
boundaryValue = true;
|
|
369
|
+
}
|
|
370
|
+
if (param.schema.minimum !== undefined) {
|
|
371
|
+
const minValPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*${param.schema.minimum}\\b`, 'i');
|
|
372
|
+
if (minValPattern.test(segment.content)) {
|
|
373
|
+
boundaryValue = true;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (param.schema.maximum !== undefined) {
|
|
377
|
+
const maxValPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*${param.schema.maximum}\\b`, 'i');
|
|
378
|
+
if (maxValPattern.test(segment.content)) {
|
|
379
|
+
boundaryValue = true;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Boundary integer in URL (e.g. /users/1 or /users/0)
|
|
383
|
+
if (param.location === 'path') {
|
|
384
|
+
const urlBoundaryPattern = new RegExp(`${escapeRegex(pathBase)}(?:0|1)(?:[/?#]|$)`, 'i');
|
|
385
|
+
if (urlBoundaryPattern.test(segment.content)) {
|
|
386
|
+
boundaryValue = true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Boundary query param value (e.g. limit=1, limit=0)
|
|
390
|
+
if (param.location === 'query') {
|
|
391
|
+
const queryBoundaryPattern = new RegExp(`[?&]${escapeRegex(param.name)}=(?:0|1)(?:[&# ]|$)`, 'i');
|
|
392
|
+
if (queryBoundaryPattern.test(segment.content)) {
|
|
393
|
+
boundaryValue = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else if (param.schema.type === 'string') {
|
|
398
|
+
// Empty string in object literal
|
|
399
|
+
const emptyPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*['"]\\s*['"]`, 'i');
|
|
400
|
+
if (emptyPattern.test(segment.content)) {
|
|
401
|
+
boundaryValue = true;
|
|
402
|
+
}
|
|
403
|
+
// Single-character string value (covers minLength: 1 boundary)
|
|
404
|
+
const singleCharPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*['"][^'"]{1}['"]`, 'i');
|
|
405
|
+
if (singleCharPattern.test(segment.content)) {
|
|
406
|
+
boundaryValue = true;
|
|
407
|
+
}
|
|
408
|
+
// Query / header single char (e.g. ?q=a or X-Api-Key: k)
|
|
409
|
+
if (param.location === 'query') {
|
|
410
|
+
const querySingleCharPattern = new RegExp(`[?&]${escapeRegex(param.name)}=[^&# ]{1}(?:[&# ]|$)`, 'i');
|
|
411
|
+
if (querySingleCharPattern.test(segment.content)) {
|
|
412
|
+
boundaryValue = true;
|
|
413
|
+
}
|
|
414
|
+
// Empty query value
|
|
415
|
+
const queryEmptyPattern = new RegExp(`[?&]${escapeRegex(param.name)}=(?:[&# ]|$)`, 'i');
|
|
416
|
+
if (queryEmptyPattern.test(segment.content)) {
|
|
417
|
+
boundaryValue = true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (param.location === 'header') {
|
|
421
|
+
// Header with single-char value
|
|
422
|
+
const headerSingleCharPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*['"][^'"]{1}['"]`, 'i');
|
|
423
|
+
if (headerSingleCharPattern.test(segment.content)) {
|
|
424
|
+
boundaryValue = true;
|
|
425
|
+
}
|
|
426
|
+
// Header with empty value
|
|
427
|
+
const headerEmptyPattern = new RegExp(`['"]?${escapeRegex(param.name)}['"]?\\s*:\\s*['"]\\s*['"]`, 'i');
|
|
428
|
+
if (headerEmptyPattern.test(segment.content)) {
|
|
429
|
+
boundaryValue = true;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// ── Valid ─────────────────────────────────────────────────────────────────
|
|
434
|
+
let validValue = false;
|
|
435
|
+
if (descLower.includes('valid') && !descLower.includes('invalid')) {
|
|
436
|
+
validValue = true;
|
|
437
|
+
}
|
|
438
|
+
if (descLower.includes('success') || descLower.includes('creates') || /returns? 2\d{2}/.test(descLower)) {
|
|
439
|
+
validValue = true;
|
|
440
|
+
}
|
|
441
|
+
// If the segment mentions the param and is not categorized as boundary/invalid/missing, treat as valid
|
|
442
|
+
if (!validValue && !boundaryValue && !invalidValue && !missing && mentionsParam && mentionsPath) {
|
|
443
|
+
validValue = true;
|
|
444
|
+
}
|
|
445
|
+
return { validValue, boundaryValue, missing, invalidValue };
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* For each parameter, scan test files and determine which coverage
|
|
449
|
+
* categories are satisfied. Optionally augments text-scan results with
|
|
450
|
+
* AST-derived semantic signals when `astOptions` is provided.
|
|
451
|
+
*/
|
|
452
|
+
async function analyzeParameterCoverage(params, testGlob, astOptions) {
|
|
453
|
+
const testFiles = await (0, fast_glob_1.default)(testGlob, { onlyFiles: true });
|
|
454
|
+
// Pre-read all test files and extract test segments
|
|
455
|
+
const allSegments = [];
|
|
456
|
+
for (const filePath of testFiles) {
|
|
457
|
+
const contents = fs.readFileSync(filePath, 'utf-8');
|
|
458
|
+
allSegments.push(...extractTestSegments(contents));
|
|
459
|
+
}
|
|
460
|
+
// Build AST interaction map if AST options provided
|
|
461
|
+
const astMap = astOptions
|
|
462
|
+
? buildAstInteractionMap(testFiles, astOptions)
|
|
463
|
+
: null;
|
|
464
|
+
return params.map((param) => {
|
|
465
|
+
// ── Text-scan pass ──────────────────────────────────────────────────────
|
|
466
|
+
let validValue = false;
|
|
467
|
+
let boundaryValue = false;
|
|
468
|
+
let missing = false;
|
|
469
|
+
let invalidValue = false;
|
|
470
|
+
for (const segment of allSegments) {
|
|
471
|
+
const result = classifySegment(segment, param);
|
|
472
|
+
validValue = validValue || result.validValue;
|
|
473
|
+
boundaryValue = boundaryValue || result.boundaryValue;
|
|
474
|
+
missing = missing || result.missing;
|
|
475
|
+
invalidValue = invalidValue || result.invalidValue;
|
|
476
|
+
}
|
|
477
|
+
// ── AST augmentation pass ───────────────────────────────────────────────
|
|
478
|
+
let astMetadata;
|
|
479
|
+
if (astMap !== null) {
|
|
480
|
+
// Collect all interactions that match this parameter's endpoint path
|
|
481
|
+
const matchingInteractions = [];
|
|
482
|
+
for (const [, interactions] of astMap) {
|
|
483
|
+
for (const interaction of interactions) {
|
|
484
|
+
if (pathMatches(interaction, param.path) &&
|
|
485
|
+
interaction.method.toUpperCase() === param.method.toUpperCase()) {
|
|
486
|
+
matchingInteractions.push(interaction);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (matchingInteractions.length > 0) {
|
|
491
|
+
const astResult = deriveAstCoverage(matchingInteractions);
|
|
492
|
+
// Merge: OR semantics — AST supplements but never removes text-scan coverage
|
|
493
|
+
validValue = validValue || astResult.validValue;
|
|
494
|
+
boundaryValue = boundaryValue || astResult.boundaryValue;
|
|
495
|
+
missing = missing || astResult.missing;
|
|
496
|
+
invalidValue = invalidValue || astResult.invalidValue;
|
|
497
|
+
astMetadata = astResult.astMetadata;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const categoriesCovered = [validValue, boundaryValue, missing, invalidValue].filter(Boolean).length;
|
|
501
|
+
const ratio = categoriesCovered / 4;
|
|
502
|
+
return { parameter: param, validValue, boundaryValue, missing, invalidValue, ratio, astMetadata };
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
// ─── Report building ──────────────────────────────────────────────────────────
|
|
506
|
+
/**
|
|
507
|
+
* Aggregate individual parameter coverages into a summary report.
|
|
508
|
+
*/
|
|
509
|
+
function buildParameterCoverageReport(coverages) {
|
|
510
|
+
const total = coverages.length;
|
|
511
|
+
if (total === 0) {
|
|
512
|
+
return {
|
|
513
|
+
totalParameters: 0,
|
|
514
|
+
averageCoverage: 0,
|
|
515
|
+
fullyCoveredPercentage: 0,
|
|
516
|
+
uncoveredParameters: [],
|
|
517
|
+
parameters: [],
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
const totalRatio = coverages.reduce((sum, c) => sum + c.ratio, 0);
|
|
521
|
+
const averageCoverage = Math.round((totalRatio / total) * 10000) / 100;
|
|
522
|
+
const fullyCovered = coverages.filter((c) => c.ratio === 1).length;
|
|
523
|
+
const fullyCoveredPercentage = Math.round((fullyCovered / total) * 10000) / 100;
|
|
524
|
+
const uncoveredParameters = coverages
|
|
525
|
+
.filter((c) => c.ratio === 0)
|
|
526
|
+
.map((c) => c.parameter);
|
|
527
|
+
return { totalParameters: total, averageCoverage, fullyCoveredPercentage, uncoveredParameters, parameters: coverages };
|
|
528
|
+
}
|
|
529
|
+
// ─── Report generation ────────────────────────────────────────────────────────
|
|
530
|
+
/**
|
|
531
|
+
* Write JSON and HTML parameter-coverage reports to the given directory.
|
|
532
|
+
*/
|
|
533
|
+
function generateParameterReports(report, reportsDir) {
|
|
534
|
+
if (!fs.existsSync(reportsDir)) {
|
|
535
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
536
|
+
}
|
|
537
|
+
// ── JSON ─────────────────────────────────────────────────────────────────
|
|
538
|
+
const jsonPath = path.join(reportsDir, 'parameter-coverage.json');
|
|
539
|
+
const jsonReport = {
|
|
540
|
+
totalParameters: report.totalParameters,
|
|
541
|
+
averageCoverage: report.averageCoverage,
|
|
542
|
+
fullyCoveredPercentage: report.fullyCoveredPercentage,
|
|
543
|
+
uncoveredParameters: report.uncoveredParameters.map(({ endpoint, name, location, required }) => ({
|
|
544
|
+
endpoint,
|
|
545
|
+
name,
|
|
546
|
+
location,
|
|
547
|
+
required,
|
|
548
|
+
})),
|
|
549
|
+
parameters: report.parameters.map(({ parameter: p, validValue, boundaryValue, missing, invalidValue, ratio }) => ({
|
|
550
|
+
endpoint: p.endpoint,
|
|
551
|
+
name: p.name,
|
|
552
|
+
location: p.location,
|
|
553
|
+
required: p.required,
|
|
554
|
+
validValue,
|
|
555
|
+
boundaryValue,
|
|
556
|
+
missing,
|
|
557
|
+
invalidValue,
|
|
558
|
+
coveragePercent: Math.round(ratio * 100),
|
|
559
|
+
})),
|
|
560
|
+
};
|
|
561
|
+
fs.writeFileSync(jsonPath, JSON.stringify(jsonReport, null, 2), 'utf-8');
|
|
562
|
+
// ── HTML ─────────────────────────────────────────────────────────────────
|
|
563
|
+
const htmlPath = path.join(reportsDir, 'parameter-coverage.html');
|
|
564
|
+
const rows = report.parameters
|
|
565
|
+
.map(({ parameter: p, validValue, boundaryValue, missing, invalidValue, ratio }) => {
|
|
566
|
+
const pct = Math.round(ratio * 100);
|
|
567
|
+
const rowClass = ratio === 1 ? 'full' : ratio === 0 ? 'none' : 'partial';
|
|
568
|
+
const cell = (covered) => covered ? '<td class="yes">✅</td>' : '<td class="no">❌</td>';
|
|
569
|
+
return ` <tr class="${rowClass}">
|
|
570
|
+
<td>${p.endpoint}</td>
|
|
571
|
+
<td>${p.name}</td>
|
|
572
|
+
<td>${p.location}</td>
|
|
573
|
+
<td>${p.required ? 'Yes' : 'No'}</td>
|
|
574
|
+
${cell(validValue)}
|
|
575
|
+
${cell(boundaryValue)}
|
|
576
|
+
${cell(missing)}
|
|
577
|
+
${cell(invalidValue)}
|
|
578
|
+
<td>${pct}%</td>
|
|
579
|
+
</tr>`;
|
|
580
|
+
})
|
|
581
|
+
.join('\n');
|
|
582
|
+
const html = `<!DOCTYPE html>
|
|
583
|
+
<html lang="en">
|
|
584
|
+
<head>
|
|
585
|
+
<meta charset="UTF-8">
|
|
586
|
+
<title>Parameter Coverage Report</title>
|
|
587
|
+
<style>
|
|
588
|
+
body { font-family: sans-serif; padding: 2rem; }
|
|
589
|
+
h1 { margin-bottom: 0.5rem; }
|
|
590
|
+
.summary { margin-bottom: 1.5rem; font-size: 1.1rem; }
|
|
591
|
+
table { border-collapse: collapse; width: 100%; }
|
|
592
|
+
th, td { border: 1px solid #ccc; padding: 0.5rem 1rem; text-align: left; }
|
|
593
|
+
th { background: #f0f0f0; }
|
|
594
|
+
tr.full { background: #e6ffe6; }
|
|
595
|
+
tr.partial { background: #fffde6; }
|
|
596
|
+
tr.none { background: #ffe6e6; }
|
|
597
|
+
td.yes { color: #2a7a2a; font-weight: bold; }
|
|
598
|
+
td.no { color: #aa2222; font-weight: bold; }
|
|
599
|
+
</style>
|
|
600
|
+
</head>
|
|
601
|
+
<body>
|
|
602
|
+
<h1>Parameter Coverage Report</h1>
|
|
603
|
+
<div class="summary">
|
|
604
|
+
<strong>${report.totalParameters}</strong> parameters analyzed —
|
|
605
|
+
average coverage <strong>${report.averageCoverage}%</strong> —
|
|
606
|
+
fully covered <strong>${report.fullyCoveredPercentage}%</strong>
|
|
607
|
+
</div>
|
|
608
|
+
<table>
|
|
609
|
+
<thead>
|
|
610
|
+
<tr>
|
|
611
|
+
<th>Endpoint</th>
|
|
612
|
+
<th>Parameter</th>
|
|
613
|
+
<th>Location</th>
|
|
614
|
+
<th>Required</th>
|
|
615
|
+
<th>Valid</th>
|
|
616
|
+
<th>Boundary</th>
|
|
617
|
+
<th>Missing</th>
|
|
618
|
+
<th>Invalid</th>
|
|
619
|
+
<th>Coverage %</th>
|
|
620
|
+
</tr>
|
|
621
|
+
</thead>
|
|
622
|
+
<tbody>
|
|
623
|
+
${rows}
|
|
624
|
+
</tbody>
|
|
625
|
+
</table>
|
|
626
|
+
</body>
|
|
627
|
+
</html>`;
|
|
628
|
+
fs.writeFileSync(htmlPath, html, 'utf-8');
|
|
629
|
+
}
|