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,670 @@
|
|
|
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.RESILIENCE_KEYWORDS = exports.RESILIENCE_CATEGORIES = void 0;
|
|
40
|
+
exports.parseEndpointsFromSpec = parseEndpointsFromSpec;
|
|
41
|
+
exports.parseLoadTestResults = parseLoadTestResults;
|
|
42
|
+
exports.parseJMeterCsv = parseJMeterCsv;
|
|
43
|
+
exports.parseK6Json = parseK6Json;
|
|
44
|
+
exports.buildResilienceScenarios = buildResilienceScenarios;
|
|
45
|
+
exports.collectTestEntries = collectTestEntries;
|
|
46
|
+
exports.testCoversScenario = testCoversScenario;
|
|
47
|
+
exports.analyzeResilienceCoverage = analyzeResilienceCoverage;
|
|
48
|
+
exports.analyzePerformanceCoverage = analyzePerformanceCoverage;
|
|
49
|
+
exports.buildPerfResilienceReport = buildPerfResilienceReport;
|
|
50
|
+
exports.generatePerfResilienceReports = generatePerfResilienceReports;
|
|
51
|
+
const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
55
|
+
exports.RESILIENCE_CATEGORIES = [
|
|
56
|
+
'timeout',
|
|
57
|
+
'retry',
|
|
58
|
+
'circuit-breaker',
|
|
59
|
+
'fallback',
|
|
60
|
+
'rate-limiting',
|
|
61
|
+
'bulkhead',
|
|
62
|
+
];
|
|
63
|
+
// ─── Keyword maps ─────────────────────────────────────────────────────────────
|
|
64
|
+
/** Keywords used to detect resilience tests in test descriptions */
|
|
65
|
+
exports.RESILIENCE_KEYWORDS = {
|
|
66
|
+
timeout: [
|
|
67
|
+
'timeout', 'timed out', 'time out', 'deadline exceeded', 'request timeout',
|
|
68
|
+
'connection timeout', 'slow response', 'latency', 'response time',
|
|
69
|
+
],
|
|
70
|
+
retry: [
|
|
71
|
+
'retry', 'retries', 'retrying', 'back-off', 'backoff', 'exponential backoff',
|
|
72
|
+
'retry logic', 'retry attempt', 'max retries',
|
|
73
|
+
],
|
|
74
|
+
'circuit-breaker': [
|
|
75
|
+
'circuit breaker', 'circuit-breaker', 'circuit open', 'circuit closed',
|
|
76
|
+
'circuit half-open', 'short circuit', 'tripped', 'open state',
|
|
77
|
+
],
|
|
78
|
+
fallback: [
|
|
79
|
+
'fallback', 'fall back', 'graceful degradation', 'degraded', 'default value',
|
|
80
|
+
'cached response', 'fail gracefully', 'partial failure', 'unavailable',
|
|
81
|
+
'service unavailable', '503', '5xx',
|
|
82
|
+
],
|
|
83
|
+
'rate-limiting': [
|
|
84
|
+
'rate limit', 'rate-limit', 'rate limiting', '429', 'too many requests',
|
|
85
|
+
'throttle', 'throttling', 'quota', 'request quota', 'slow down',
|
|
86
|
+
],
|
|
87
|
+
bulkhead: [
|
|
88
|
+
'bulkhead', 'isolation', 'resource isolation', 'thread pool', 'semaphore',
|
|
89
|
+
'concurrency limit', 'max concurrent', 'overload',
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
// ─── OpenAPI spec parsing ─────────────────────────────────────────────────────
|
|
93
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
94
|
+
/**
|
|
95
|
+
* Parse an OpenAPI/Swagger spec and return a flat list of endpoints.
|
|
96
|
+
*/
|
|
97
|
+
async function parseEndpointsFromSpec(specPath) {
|
|
98
|
+
var _a;
|
|
99
|
+
const api = (await swagger_parser_1.default.validate(specPath));
|
|
100
|
+
const endpoints = [];
|
|
101
|
+
for (const [apiPath, pathItem] of Object.entries((_a = api.paths) !== null && _a !== void 0 ? _a : {})) {
|
|
102
|
+
if (!pathItem)
|
|
103
|
+
continue;
|
|
104
|
+
for (const method of HTTP_METHODS) {
|
|
105
|
+
if (pathItem[method]) {
|
|
106
|
+
const id = `${method.toUpperCase()} ${apiPath}`;
|
|
107
|
+
endpoints.push({ id, method: method.toUpperCase(), path: apiPath });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return endpoints;
|
|
112
|
+
}
|
|
113
|
+
// ─── Load-test result parsing ─────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Parse one or more load-test result files.
|
|
116
|
+
* Supported formats:
|
|
117
|
+
* - JMeter .jtl / .csv (comma-separated with standard JMeter headers)
|
|
118
|
+
* - k6 JSON summary (`{ "metrics": { "http_req_duration": { "values": { ... } } } }`)
|
|
119
|
+
*
|
|
120
|
+
* Returns a map from endpoint label → LoadTestMetrics.
|
|
121
|
+
*/
|
|
122
|
+
function parseLoadTestResults(filePaths) {
|
|
123
|
+
const results = new Map();
|
|
124
|
+
for (const filePath of filePaths) {
|
|
125
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
126
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
127
|
+
if (ext === '.json') {
|
|
128
|
+
const parsed = parseK6Json(raw);
|
|
129
|
+
for (const [label, metrics] of parsed) {
|
|
130
|
+
results.set(label, metrics);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// .jtl, .csv, or any other extension → try JMeter CSV
|
|
135
|
+
const parsed = parseJMeterCsv(raw);
|
|
136
|
+
for (const [label, metrics] of parsed) {
|
|
137
|
+
results.set(label, metrics);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
// ── JMeter CSV ────────────────────────────────────────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Parse a JMeter .jtl / .csv file.
|
|
146
|
+
*
|
|
147
|
+
* Expected header (standard JMeter format):
|
|
148
|
+
* timeStamp,elapsed,label,responseCode,success,bytes,sentBytes,grpThreads,allThreads,Latency,Connect
|
|
149
|
+
*
|
|
150
|
+
* Returns a map of label → aggregated LoadTestMetrics.
|
|
151
|
+
*/
|
|
152
|
+
function parseJMeterCsv(csvContent) {
|
|
153
|
+
var _a, _b, _c;
|
|
154
|
+
const lines = csvContent.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
155
|
+
if (lines.length < 2)
|
|
156
|
+
return new Map();
|
|
157
|
+
const headerLine = lines[0].toLowerCase();
|
|
158
|
+
const headers = headerLine.split(',').map((h) => h.trim());
|
|
159
|
+
const elapsedIdx = headers.indexOf('elapsed');
|
|
160
|
+
const labelIdx = headers.indexOf('label');
|
|
161
|
+
const successIdx = headers.indexOf('success');
|
|
162
|
+
if (elapsedIdx === -1 || labelIdx === -1)
|
|
163
|
+
return new Map();
|
|
164
|
+
// Accumulate raw samples per label
|
|
165
|
+
const raw = new Map();
|
|
166
|
+
for (let i = 1; i < lines.length; i++) {
|
|
167
|
+
const cols = lines[i].split(',');
|
|
168
|
+
const label = ((_a = cols[labelIdx]) !== null && _a !== void 0 ? _a : '').trim();
|
|
169
|
+
const elapsed = parseFloat((_b = cols[elapsedIdx]) !== null && _b !== void 0 ? _b : '0');
|
|
170
|
+
const success = successIdx === -1 ? true : ((_c = cols[successIdx]) !== null && _c !== void 0 ? _c : 'true').trim().toLowerCase() === 'true';
|
|
171
|
+
if (!label || isNaN(elapsed))
|
|
172
|
+
continue;
|
|
173
|
+
if (!raw.has(label))
|
|
174
|
+
raw.set(label, { elapsed: [], errors: 0 });
|
|
175
|
+
const entry = raw.get(label);
|
|
176
|
+
entry.elapsed.push(elapsed);
|
|
177
|
+
if (!success)
|
|
178
|
+
entry.errors++;
|
|
179
|
+
}
|
|
180
|
+
const result = new Map();
|
|
181
|
+
for (const [label, data] of raw) {
|
|
182
|
+
if (data.elapsed.length === 0)
|
|
183
|
+
continue;
|
|
184
|
+
const metrics = computeMetrics(label, data.elapsed, data.errors);
|
|
185
|
+
result.set(label, metrics);
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
// ── k6 JSON ───────────────────────────────────────────────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Parse a k6 JSON summary file.
|
|
192
|
+
*
|
|
193
|
+
* Supports both the top-level summary format:
|
|
194
|
+
* `{ "metrics": { "http_req_duration": { "values": { "avg", "med", "p(95)", "p(99)" } } } }`
|
|
195
|
+
* and a per-URL breakdown when present in `scenarios` or `groups` keys.
|
|
196
|
+
*/
|
|
197
|
+
function parseK6Json(jsonContent) {
|
|
198
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
|
|
199
|
+
const result = new Map();
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(jsonContent);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
if (!parsed || typeof parsed !== 'object')
|
|
208
|
+
return result;
|
|
209
|
+
const doc = parsed;
|
|
210
|
+
// Top-level metrics (entire run summary)
|
|
211
|
+
const metrics = doc.metrics;
|
|
212
|
+
if (!metrics)
|
|
213
|
+
return result;
|
|
214
|
+
const durationMetric = metrics['http_req_duration'];
|
|
215
|
+
const failedMetric = metrics['http_req_failed'];
|
|
216
|
+
const reqs = metrics['http_reqs'];
|
|
217
|
+
if (!durationMetric)
|
|
218
|
+
return result;
|
|
219
|
+
const dVals = ((_a = durationMetric['values']) !== null && _a !== void 0 ? _a : durationMetric);
|
|
220
|
+
const fVals = failedMetric
|
|
221
|
+
? ((_b = failedMetric['values']) !== null && _b !== void 0 ? _b : failedMetric)
|
|
222
|
+
: {};
|
|
223
|
+
const rVals = reqs ? ((_c = reqs['values']) !== null && _c !== void 0 ? _c : reqs) : {};
|
|
224
|
+
const avg = (_d = toNum(dVals['avg'])) !== null && _d !== void 0 ? _d : 0;
|
|
225
|
+
const med = (_f = (_e = toNum(dVals['med'])) !== null && _e !== void 0 ? _e : toNum(dVals['median'])) !== null && _f !== void 0 ? _f : avg;
|
|
226
|
+
const p95 = (_h = (_g = toNum(dVals['p(95)'])) !== null && _g !== void 0 ? _g : toNum(dVals['p95'])) !== null && _h !== void 0 ? _h : avg;
|
|
227
|
+
const p99 = (_k = (_j = toNum(dVals['p(99)'])) !== null && _j !== void 0 ? _j : toNum(dVals['p99'])) !== null && _k !== void 0 ? _k : avg;
|
|
228
|
+
const errorRate = (_l = toNum(fVals['rate'])) !== null && _l !== void 0 ? _l : 0;
|
|
229
|
+
const count = (_m = toNum(rVals['count'])) !== null && _m !== void 0 ? _m : 0;
|
|
230
|
+
const throughput = (_o = toNum(rVals['rate'])) !== null && _o !== void 0 ? _o : 0;
|
|
231
|
+
result.set('overall', {
|
|
232
|
+
endpoint: 'overall',
|
|
233
|
+
sampleCount: count,
|
|
234
|
+
avg,
|
|
235
|
+
median: med,
|
|
236
|
+
p95,
|
|
237
|
+
p99,
|
|
238
|
+
errorRate,
|
|
239
|
+
throughput,
|
|
240
|
+
});
|
|
241
|
+
// Per-URL metrics: check for a "scenarios" or "url" breakdown
|
|
242
|
+
const scenarios = doc.scenarios;
|
|
243
|
+
if (scenarios) {
|
|
244
|
+
for (const [scenarioName, scenarioData] of Object.entries(scenarios)) {
|
|
245
|
+
if (!scenarioData || typeof scenarioData !== 'object')
|
|
246
|
+
continue;
|
|
247
|
+
const sd = scenarioData;
|
|
248
|
+
const sdMetrics = sd['metrics'];
|
|
249
|
+
if (!sdMetrics)
|
|
250
|
+
continue;
|
|
251
|
+
const sdDur = sdMetrics['http_req_duration'];
|
|
252
|
+
if (!sdDur)
|
|
253
|
+
continue;
|
|
254
|
+
const sdVals = ((_p = sdDur['values']) !== null && _p !== void 0 ? _p : sdDur);
|
|
255
|
+
const sdFailed = sdMetrics['http_req_failed'];
|
|
256
|
+
const sdFVals = sdFailed
|
|
257
|
+
? ((_q = sdFailed['values']) !== null && _q !== void 0 ? _q : sdFailed)
|
|
258
|
+
: {};
|
|
259
|
+
result.set(scenarioName, {
|
|
260
|
+
endpoint: scenarioName,
|
|
261
|
+
sampleCount: (_s = toNum((_r = sdMetrics['http_reqs']) === null || _r === void 0 ? void 0 : _r['count'])) !== null && _s !== void 0 ? _s : 0,
|
|
262
|
+
avg: (_t = toNum(sdVals['avg'])) !== null && _t !== void 0 ? _t : 0,
|
|
263
|
+
median: (_v = (_u = toNum(sdVals['med'])) !== null && _u !== void 0 ? _u : toNum(sdVals['median'])) !== null && _v !== void 0 ? _v : 0,
|
|
264
|
+
p95: (_x = (_w = toNum(sdVals['p(95)'])) !== null && _w !== void 0 ? _w : toNum(sdVals['p95'])) !== null && _x !== void 0 ? _x : 0,
|
|
265
|
+
p99: (_z = (_y = toNum(sdVals['p(99)'])) !== null && _y !== void 0 ? _y : toNum(sdVals['p99'])) !== null && _z !== void 0 ? _z : 0,
|
|
266
|
+
errorRate: (_0 = toNum(sdFVals['rate'])) !== null && _0 !== void 0 ? _0 : 0,
|
|
267
|
+
throughput: 0,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
function toNum(v) {
|
|
274
|
+
if (typeof v === 'number')
|
|
275
|
+
return v;
|
|
276
|
+
if (typeof v === 'string') {
|
|
277
|
+
const n = parseFloat(v);
|
|
278
|
+
return isNaN(n) ? undefined : n;
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
/** Compute aggregated metrics from raw elapsed time samples */
|
|
283
|
+
function computeMetrics(label, elapsed, errors) {
|
|
284
|
+
const sorted = [...elapsed].sort((a, b) => a - b);
|
|
285
|
+
const n = sorted.length;
|
|
286
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
287
|
+
const avg = sum / n;
|
|
288
|
+
const median = percentile(sorted, 50);
|
|
289
|
+
const p95 = percentile(sorted, 95);
|
|
290
|
+
const p99 = percentile(sorted, 99);
|
|
291
|
+
const errorRate = errors / n;
|
|
292
|
+
// Throughput is approximate (samples per "second" is not derivable from elapsed alone;
|
|
293
|
+
// we return samples/total-elapsed-seconds as an approximation when total > 0)
|
|
294
|
+
const totalElapsed = sum / 1000; // convert ms to s
|
|
295
|
+
const throughput = totalElapsed > 0 ? n / totalElapsed : 0;
|
|
296
|
+
return {
|
|
297
|
+
endpoint: label,
|
|
298
|
+
sampleCount: n,
|
|
299
|
+
avg: round2(avg),
|
|
300
|
+
median: round2(median),
|
|
301
|
+
p95: round2(p95),
|
|
302
|
+
p99: round2(p99),
|
|
303
|
+
errorRate: round4(errorRate),
|
|
304
|
+
throughput: round2(throughput),
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function percentile(sorted, p) {
|
|
308
|
+
if (sorted.length === 0)
|
|
309
|
+
return 0;
|
|
310
|
+
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
311
|
+
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
|
|
312
|
+
}
|
|
313
|
+
function round2(n) {
|
|
314
|
+
return Math.round(n * 100) / 100;
|
|
315
|
+
}
|
|
316
|
+
function round4(n) {
|
|
317
|
+
return Math.round(n * 10000) / 10000;
|
|
318
|
+
}
|
|
319
|
+
// ─── Resilience scenario generation ──────────────────────────────────────────
|
|
320
|
+
/**
|
|
321
|
+
* Build the list of resilience scenarios to check.
|
|
322
|
+
*
|
|
323
|
+
* For each endpoint we create one scenario per resilience category.
|
|
324
|
+
* This gives a measurable and exhaustive checklist.
|
|
325
|
+
*/
|
|
326
|
+
function buildResilienceScenarios(endpoints) {
|
|
327
|
+
const scenarios = [];
|
|
328
|
+
for (const endpoint of endpoints) {
|
|
329
|
+
for (const cat of exports.RESILIENCE_CATEGORIES) {
|
|
330
|
+
scenarios.push({
|
|
331
|
+
id: `${cat}:${endpoint.id}`,
|
|
332
|
+
category: cat,
|
|
333
|
+
description: resilienceDescription(cat, endpoint.id),
|
|
334
|
+
endpoint,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return scenarios;
|
|
339
|
+
}
|
|
340
|
+
function resilienceDescription(cat, endpointId) {
|
|
341
|
+
switch (cat) {
|
|
342
|
+
case 'timeout':
|
|
343
|
+
return `Timeout handling for ${endpointId}: verify that slow/unresponsive upstream causes proper timeout error`;
|
|
344
|
+
case 'retry':
|
|
345
|
+
return `Retry logic for ${endpointId}: verify that transient failures trigger retries with back-off`;
|
|
346
|
+
case 'circuit-breaker':
|
|
347
|
+
return `Circuit-breaker for ${endpointId}: verify that repeated failures open the circuit and stop cascading`;
|
|
348
|
+
case 'fallback':
|
|
349
|
+
return `Fallback/degradation for ${endpointId}: verify that unavailable dependency returns a graceful response`;
|
|
350
|
+
case 'rate-limiting':
|
|
351
|
+
return `Rate-limiting for ${endpointId}: verify that excessive requests return HTTP 429 with Retry-After`;
|
|
352
|
+
case 'bulkhead':
|
|
353
|
+
return `Bulkhead for ${endpointId}: verify that resource isolation prevents one endpoint from starving others`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Read test files matching a glob and extract test descriptions.
|
|
358
|
+
* Recognises Jest/Mocha-style `test(`, `it(`, and `describe(` calls.
|
|
359
|
+
*/
|
|
360
|
+
async function collectTestEntries(testsGlob) {
|
|
361
|
+
const files = await (0, fast_glob_1.default)(testsGlob, { absolute: true });
|
|
362
|
+
const entries = [];
|
|
363
|
+
for (const file of files) {
|
|
364
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
365
|
+
const pattern = /(?:test|it|describe)\s*\(\s*(['"`])([\s\S]*?)\1/g;
|
|
366
|
+
let m;
|
|
367
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
368
|
+
entries.push({ description: m[2], filePath: file });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return entries;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Determine whether a test entry covers a given resilience scenario.
|
|
375
|
+
*
|
|
376
|
+
* Matching rules (applied to test description only):
|
|
377
|
+
* 1. Explicit `@resilience <scenarioId>` annotation
|
|
378
|
+
* 2. Category keywords AND (endpoint path or method in description)
|
|
379
|
+
* 3. Category keywords alone (for global/category-level coverage)
|
|
380
|
+
*/
|
|
381
|
+
function testCoversScenario(entry, scenario) {
|
|
382
|
+
const desc = entry.description.toLowerCase();
|
|
383
|
+
// Rule 1: explicit annotation
|
|
384
|
+
if (desc.includes(`@resilience ${scenario.id.toLowerCase()}`))
|
|
385
|
+
return true;
|
|
386
|
+
const keywords = exports.RESILIENCE_KEYWORDS[scenario.category];
|
|
387
|
+
const hasKeyword = keywords.some((kw) => desc.includes(kw.toLowerCase()));
|
|
388
|
+
if (!hasKeyword)
|
|
389
|
+
return false;
|
|
390
|
+
// Rule 2: keyword + endpoint reference
|
|
391
|
+
if (scenario.endpoint) {
|
|
392
|
+
const pathLower = scenario.endpoint.path.toLowerCase();
|
|
393
|
+
const methodLower = scenario.endpoint.method.toLowerCase();
|
|
394
|
+
if (desc.includes(pathLower) || desc.includes(methodLower))
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
// Rule 3: keyword match alone (category-wide coverage)
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Analyse test files and return coverage for each resilience scenario.
|
|
402
|
+
*/
|
|
403
|
+
async function analyzeResilienceCoverage(scenarios, testsGlob) {
|
|
404
|
+
const entries = await collectTestEntries(testsGlob);
|
|
405
|
+
return scenarios.map((scenario) => {
|
|
406
|
+
const matched = [];
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
if (testCoversScenario(entry, scenario)) {
|
|
409
|
+
matched.push(entry.description);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
scenario,
|
|
414
|
+
covered: matched.length > 0,
|
|
415
|
+
matchedTests: matched,
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// ─── Performance coverage analysis ───────────────────────────────────────────
|
|
420
|
+
/**
|
|
421
|
+
* Match load-test metrics to spec endpoints.
|
|
422
|
+
*
|
|
423
|
+
* A metrics label is matched to an endpoint when:
|
|
424
|
+
* - exact string match (e.g. "GET /users")
|
|
425
|
+
* - case-insensitive substring match of the endpoint path within the label
|
|
426
|
+
* - the label contains both the HTTP method and path fragments
|
|
427
|
+
*/
|
|
428
|
+
function matchMetricsToEndpoint(endpoint, metricsMap) {
|
|
429
|
+
const endpointLower = endpoint.id.toLowerCase();
|
|
430
|
+
const pathLower = endpoint.path.toLowerCase();
|
|
431
|
+
const methodLower = endpoint.method.toLowerCase();
|
|
432
|
+
// Try exact match first
|
|
433
|
+
for (const [label, metrics] of metricsMap) {
|
|
434
|
+
if (label.toLowerCase() === endpointLower)
|
|
435
|
+
return metrics;
|
|
436
|
+
}
|
|
437
|
+
// Try path substring match
|
|
438
|
+
for (const [label, metrics] of metricsMap) {
|
|
439
|
+
const lbl = label.toLowerCase();
|
|
440
|
+
if (lbl.includes(pathLower) && lbl.includes(methodLower))
|
|
441
|
+
return metrics;
|
|
442
|
+
}
|
|
443
|
+
// Try path only (covers cases where label is just the path)
|
|
444
|
+
for (const [label, metrics] of metricsMap) {
|
|
445
|
+
if (label.toLowerCase().includes(pathLower))
|
|
446
|
+
return metrics;
|
|
447
|
+
}
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Compute performance coverage for each endpoint.
|
|
452
|
+
*/
|
|
453
|
+
function analyzePerformanceCoverage(endpoints, metricsMap, thresholds) {
|
|
454
|
+
return endpoints.map((endpoint) => {
|
|
455
|
+
const metrics = matchMetricsToEndpoint(endpoint, metricsMap);
|
|
456
|
+
if (!metrics) {
|
|
457
|
+
return {
|
|
458
|
+
endpoint,
|
|
459
|
+
hasLoadTestData: false,
|
|
460
|
+
meetsResponseTime: false,
|
|
461
|
+
meetsErrorRate: false,
|
|
462
|
+
status: 'missing-data',
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const meetsResponseTime = metrics.median <= thresholds.responseMs;
|
|
466
|
+
const meetsErrorRate = metrics.errorRate <= thresholds.errorRate;
|
|
467
|
+
const status = meetsResponseTime && meetsErrorRate ? 'good' : 'needs-improvement';
|
|
468
|
+
return {
|
|
469
|
+
endpoint,
|
|
470
|
+
hasLoadTestData: true,
|
|
471
|
+
metrics,
|
|
472
|
+
meetsResponseTime,
|
|
473
|
+
meetsErrorRate,
|
|
474
|
+
status,
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
// ─── Report building ──────────────────────────────────────────────────────────
|
|
479
|
+
/**
|
|
480
|
+
* Assemble the full PerfResilienceReport from individual coverage lists.
|
|
481
|
+
*/
|
|
482
|
+
function buildPerfResilienceReport(performanceCoverages, resilienceCoverages) {
|
|
483
|
+
const totalEndpoints = performanceCoverages.length;
|
|
484
|
+
const endpointsWithLoadData = performanceCoverages.filter((c) => c.hasLoadTestData).length;
|
|
485
|
+
const performanceCoveragePercent = totalEndpoints > 0 ? round2((endpointsWithLoadData / totalEndpoints) * 100) : 0;
|
|
486
|
+
const totalResilienceScenarios = resilienceCoverages.length;
|
|
487
|
+
const coveredResilienceScenarios = resilienceCoverages.filter((c) => c.covered).length;
|
|
488
|
+
const resilienceCoveragePercent = totalResilienceScenarios > 0
|
|
489
|
+
? round2((coveredResilienceScenarios / totalResilienceScenarios) * 100)
|
|
490
|
+
: 0;
|
|
491
|
+
const resilienceCategorySummary = {};
|
|
492
|
+
for (const cat of exports.RESILIENCE_CATEGORIES) {
|
|
493
|
+
const catCoverages = resilienceCoverages.filter((c) => c.scenario.category === cat);
|
|
494
|
+
resilienceCategorySummary[cat] = {
|
|
495
|
+
total: catCoverages.length,
|
|
496
|
+
covered: catCoverages.filter((c) => c.covered).length,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
totalEndpoints,
|
|
501
|
+
endpointsWithLoadData,
|
|
502
|
+
performanceCoveragePercent,
|
|
503
|
+
performanceCoverages,
|
|
504
|
+
totalResilienceScenarios,
|
|
505
|
+
coveredResilienceScenarios,
|
|
506
|
+
resilienceCoveragePercent,
|
|
507
|
+
resilienceCoverages,
|
|
508
|
+
resilienceCategorySummary,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
// ─── Report generation ────────────────────────────────────────────────────────
|
|
512
|
+
/**
|
|
513
|
+
* Write `reports/perf-resilience-coverage.json` and
|
|
514
|
+
* `reports/perf-resilience-coverage.html` to `reportsDir`.
|
|
515
|
+
*/
|
|
516
|
+
function generatePerfResilienceReports(report, reportsDir) {
|
|
517
|
+
if (!fs.existsSync(reportsDir)) {
|
|
518
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
519
|
+
}
|
|
520
|
+
writePerfJson(report, reportsDir);
|
|
521
|
+
writePerfHtml(report, reportsDir);
|
|
522
|
+
}
|
|
523
|
+
function writePerfJson(report, reportsDir) {
|
|
524
|
+
const payload = {
|
|
525
|
+
generatedAt: new Date().toISOString(),
|
|
526
|
+
summary: {
|
|
527
|
+
totalEndpoints: report.totalEndpoints,
|
|
528
|
+
endpointsWithLoadData: report.endpointsWithLoadData,
|
|
529
|
+
performanceCoveragePercent: report.performanceCoveragePercent,
|
|
530
|
+
totalResilienceScenarios: report.totalResilienceScenarios,
|
|
531
|
+
coveredResilienceScenarios: report.coveredResilienceScenarios,
|
|
532
|
+
resilienceCoveragePercent: report.resilienceCoveragePercent,
|
|
533
|
+
},
|
|
534
|
+
resilienceCategorySummary: report.resilienceCategorySummary,
|
|
535
|
+
performanceDetails: report.performanceCoverages.map((c) => ({
|
|
536
|
+
endpoint: c.endpoint.id,
|
|
537
|
+
hasLoadTestData: c.hasLoadTestData,
|
|
538
|
+
status: c.status,
|
|
539
|
+
meetsResponseTime: c.meetsResponseTime,
|
|
540
|
+
meetsErrorRate: c.meetsErrorRate,
|
|
541
|
+
...(c.metrics ? { metrics: c.metrics } : {}),
|
|
542
|
+
})),
|
|
543
|
+
resilienceDetails: report.resilienceCoverages.map((c) => ({
|
|
544
|
+
scenario: c.scenario.id,
|
|
545
|
+
category: c.scenario.category,
|
|
546
|
+
description: c.scenario.description,
|
|
547
|
+
covered: c.covered,
|
|
548
|
+
matchedTests: c.matchedTests,
|
|
549
|
+
})),
|
|
550
|
+
};
|
|
551
|
+
const outPath = path.join(reportsDir, 'perf-resilience-coverage.json');
|
|
552
|
+
fs.writeFileSync(outPath, JSON.stringify(payload, null, 2), 'utf-8');
|
|
553
|
+
}
|
|
554
|
+
function writePerfHtml(report, reportsDir) {
|
|
555
|
+
const perfRows = report.performanceCoverages
|
|
556
|
+
.map((c) => {
|
|
557
|
+
const rowClass = c.status === 'good' ? 'good' : c.status === 'needs-improvement' ? 'warn' : 'below';
|
|
558
|
+
const metricsCell = c.metrics
|
|
559
|
+
? `<td>${c.metrics.median} ms</td><td>${c.metrics.p95} ms</td><td>${(c.metrics.errorRate * 100).toFixed(2)}%</td><td>${c.metrics.throughput} req/s</td>`
|
|
560
|
+
: '<td colspan="4">—</td>';
|
|
561
|
+
const statusCell = c.status === 'good' ? '✅ Good' : c.status === 'needs-improvement' ? '⚠️ Needs Improvement' : '❌ Missing Data';
|
|
562
|
+
return ` <tr class="${rowClass}">
|
|
563
|
+
<td>${c.endpoint.id}</td>
|
|
564
|
+
${metricsCell}
|
|
565
|
+
<td>${statusCell}</td>
|
|
566
|
+
</tr>`;
|
|
567
|
+
})
|
|
568
|
+
.join('\n');
|
|
569
|
+
const resilienceRows = report.resilienceCoverages
|
|
570
|
+
.map((c) => {
|
|
571
|
+
var _a, _b;
|
|
572
|
+
const rowClass = c.covered ? 'good' : 'below';
|
|
573
|
+
const coveredCell = c.covered ? '✅ Covered' : '❌ Not covered';
|
|
574
|
+
return ` <tr class="${rowClass}">
|
|
575
|
+
<td>${c.scenario.category}</td>
|
|
576
|
+
<td>${(_b = (_a = c.scenario.endpoint) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '—'}</td>
|
|
577
|
+
<td>${c.scenario.description}</td>
|
|
578
|
+
<td>${coveredCell}</td>
|
|
579
|
+
</tr>`;
|
|
580
|
+
})
|
|
581
|
+
.join('\n');
|
|
582
|
+
const catRows = exports.RESILIENCE_CATEGORIES.map((cat) => {
|
|
583
|
+
const s = report.resilienceCategorySummary[cat];
|
|
584
|
+
const pct = s.total > 0 ? Math.round((s.covered / s.total) * 100) : 0;
|
|
585
|
+
const cls = pct >= 80 ? 'good' : pct > 0 ? 'warn' : 'below';
|
|
586
|
+
return ` <tr class="${cls}"><td>${cat}</td><td>${s.covered}/${s.total}</td><td>${pct}%</td></tr>`;
|
|
587
|
+
}).join('\n');
|
|
588
|
+
const html = `<!DOCTYPE html>
|
|
589
|
+
<html lang="en">
|
|
590
|
+
<head>
|
|
591
|
+
<meta charset="UTF-8">
|
|
592
|
+
<title>Performance & Resilience Coverage Report</title>
|
|
593
|
+
<style>
|
|
594
|
+
body { font-family: sans-serif; padding: 2rem; background: #fafafa; }
|
|
595
|
+
h1, h2 { margin-bottom: 0.25rem; }
|
|
596
|
+
.meta { color: #666; font-size: 0.9rem; margin-bottom: 1.5rem; }
|
|
597
|
+
.summary { display: flex; gap: 2rem; margin-bottom: 2rem; flex-wrap: wrap; }
|
|
598
|
+
.card { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 1rem 1.5rem; min-width: 180px; }
|
|
599
|
+
.card .value { font-size: 2rem; font-weight: bold; }
|
|
600
|
+
table { border-collapse: collapse; width: 100%; max-width: 1000px; margin-bottom: 2rem; }
|
|
601
|
+
th, td { border: 1px solid #ccc; padding: 0.5rem 0.75rem; text-align: left; }
|
|
602
|
+
th { background: #f0f0f0; }
|
|
603
|
+
tr.good { background: #e6ffe6; }
|
|
604
|
+
tr.warn { background: #fff9e6; }
|
|
605
|
+
tr.below { background: #ffe6e6; }
|
|
606
|
+
</style>
|
|
607
|
+
</head>
|
|
608
|
+
<body>
|
|
609
|
+
<h1>Performance & Resilience Coverage Report</h1>
|
|
610
|
+
<p class="meta">Generated: ${new Date().toISOString()}</p>
|
|
611
|
+
|
|
612
|
+
<div class="summary">
|
|
613
|
+
<div class="card">
|
|
614
|
+
<div class="label">Performance Coverage</div>
|
|
615
|
+
<div class="value">${report.performanceCoveragePercent}%</div>
|
|
616
|
+
<div class="sub">${report.endpointsWithLoadData}/${report.totalEndpoints} endpoints with load data</div>
|
|
617
|
+
</div>
|
|
618
|
+
<div class="card">
|
|
619
|
+
<div class="label">Resilience Coverage</div>
|
|
620
|
+
<div class="value">${report.resilienceCoveragePercent}%</div>
|
|
621
|
+
<div class="sub">${report.coveredResilienceScenarios}/${report.totalResilienceScenarios} scenarios covered</div>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
|
|
625
|
+
<h2>Performance Coverage by Endpoint</h2>
|
|
626
|
+
<table>
|
|
627
|
+
<thead>
|
|
628
|
+
<tr>
|
|
629
|
+
<th>Endpoint</th>
|
|
630
|
+
<th>Median</th>
|
|
631
|
+
<th>P95</th>
|
|
632
|
+
<th>Error Rate</th>
|
|
633
|
+
<th>Throughput</th>
|
|
634
|
+
<th>Status</th>
|
|
635
|
+
</tr>
|
|
636
|
+
</thead>
|
|
637
|
+
<tbody>
|
|
638
|
+
${perfRows}
|
|
639
|
+
</tbody>
|
|
640
|
+
</table>
|
|
641
|
+
|
|
642
|
+
<h2>Resilience Coverage by Category</h2>
|
|
643
|
+
<table>
|
|
644
|
+
<thead>
|
|
645
|
+
<tr><th>Category</th><th>Covered / Total</th><th>%</th></tr>
|
|
646
|
+
</thead>
|
|
647
|
+
<tbody>
|
|
648
|
+
${catRows}
|
|
649
|
+
</tbody>
|
|
650
|
+
</table>
|
|
651
|
+
|
|
652
|
+
<h2>Resilience Coverage by Scenario</h2>
|
|
653
|
+
<table>
|
|
654
|
+
<thead>
|
|
655
|
+
<tr>
|
|
656
|
+
<th>Category</th>
|
|
657
|
+
<th>Endpoint</th>
|
|
658
|
+
<th>Description</th>
|
|
659
|
+
<th>Covered</th>
|
|
660
|
+
</tr>
|
|
661
|
+
</thead>
|
|
662
|
+
<tbody>
|
|
663
|
+
${resilienceRows}
|
|
664
|
+
</tbody>
|
|
665
|
+
</table>
|
|
666
|
+
</body>
|
|
667
|
+
</html>`;
|
|
668
|
+
const outPath = path.join(reportsDir, 'perf-resilience-coverage.html');
|
|
669
|
+
fs.writeFileSync(outPath, html, 'utf-8');
|
|
670
|
+
}
|