api-tests-coverage 1.0.24 → 1.0.26
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/dist/dashboard/dist/assets/_basePickBy-BHjg34fk.js +1 -0
- package/dist/dashboard/dist/assets/_basePickBy-DIphIltc.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-D57u2_9m.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-DxJYHd7T.js +1 -0
- package/dist/dashboard/dist/assets/arc-DQosMxPM.js +1 -0
- package/dist/dashboard/dist/assets/arc-DcXkmNi0.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-CNbtIqHR.js +36 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-ChMY32ql.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-DVhWtRxG.js +122 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-NgdJaQvK.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-B6esYq70.js +10 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-ChTe70Dn.js +10 -0
- package/dist/dashboard/dist/assets/channel-B3Mj1BTw.js +1 -0
- package/dist/dashboard/dist/assets/channel-Df6s6dhy.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-B7Pkx3C3.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BS3-4dfL.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-8ClDkPsD.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-BCczdImM.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW--cjprmFF.js +165 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-D6Mi4ccz.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-B0tOisd5.js +220 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-D9bxNSnS.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-CSek7h3u.js +15 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-RSShKwSG.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-BRCzcTtl.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-DFyjAoyD.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-ARq4habW.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-TFdw1-iS.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-CWotsEVz.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-DrmzpdLp.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-Dr8j2BkV.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-cvlgQ4cC.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-Dr8j2BkV.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-cvlgQ4cC.js +1 -0
- package/dist/dashboard/dist/assets/clone-D-A0zWrx.js +1 -0
- package/dist/dashboard/dist/assets/clone-DRiM0_7k.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-L06bC_vI.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-_dXvVagP.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-BfhkcdcZ.js +4 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-LQJxsDjp.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-Bguvtjhb.js +24 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-C8bgfsC2.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CDM-bAUc.js +43 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-SPnyk4NX.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-Cv8CAseP.js +24 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DNQuKOCA.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-CgAEujxC.js +60 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DHMIYnca.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-B-9A_TD6.js +162 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-C8juupCT.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-BzXOAiOm.js +267 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-DDmcIEO0.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-6Rn0oWgA.js +65 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-D4YFQ0Qf.js +65 -0
- package/dist/dashboard/dist/assets/graph-DI2MOSai.js +1 -0
- package/dist/dashboard/dist/assets/graph-VO6A5Zyb.js +1 -0
- package/dist/dashboard/dist/assets/index-BD_Ue7zI.js +777 -0
- package/dist/dashboard/dist/assets/index-BQfUzgMV.js +778 -0
- package/dist/dashboard/dist/assets/index-Bpho1Ov5.css +1 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BEOgUULT.js +2 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BULYGXV8.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-CBFUW_L2.js +139 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-CpGd67rs.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-BXpodEnf.js +89 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-CXjvcKGc.js +89 -0
- package/dist/dashboard/dist/assets/layout-Cpj8l95P.js +1 -0
- package/dist/dashboard/dist/assets/layout-D5qgY_UX.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-CKZrc1IF.js +68 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-_3DZbNEl.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-B--OM1Gs.js +30 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-uIOJq-u0.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-CDx0v76p.js +7 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-D5g_wTRC.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-BLpGY-Om.js +64 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-CbvZ1a-7.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-D-fji9s3.js +10 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-E0klRQfk.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-Byy1IdkL.js +145 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-CWB1Ub2x.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-J-c1KNJ7.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-x3lHxmNY.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-D0gUM6SR.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-DRL2jF9p.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-D490JqJU.js +61 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-LOxOovzx.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-C5Nk6dQh.js +162 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-C6DntuKu.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-BKisDUaz.js +7 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-CYsIKi3H.js +7 -0
- package/dist/dashboard/dist/index.html +2 -2
- package/dist/src/analyzeHelpers.d.ts +60 -0
- package/dist/src/analyzeHelpers.d.ts.map +1 -0
- package/dist/src/analyzeHelpers.js +670 -0
- package/dist/src/config/defaultConfig.js +1 -1
- package/dist/src/index.js +553 -615
- package/dist/src/reporting.d.ts +10 -0
- package/dist/src/reporting.d.ts.map +1 -1
- package/dist/src/reporting.js +3 -0
- package/dist/src/summary/evaluateMetrics.js +3 -2
- package/dist/src/unitAnalysis.d.ts +132 -1
- package/dist/src/unitAnalysis.d.ts.map +1 -1
- package/dist/src/unitAnalysis.js +1139 -185
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const endpointCoverage_1 = require("./endpointCoverage");
|
|
40
41
|
const parameterCoverage_1 = require("./parameterCoverage");
|
|
@@ -44,6 +45,7 @@ const errorCoverage_1 = require("./errorCoverage");
|
|
|
44
45
|
const securityCoverage_1 = require("./securityCoverage");
|
|
45
46
|
const perfResilienceCoverage_1 = require("./perfResilienceCoverage");
|
|
46
47
|
const compatibilityCoverage_1 = require("./compatibilityCoverage");
|
|
48
|
+
const qualityGate_1 = require("./qualityGate");
|
|
47
49
|
const springCloudContractParser_1 = require("./contracts/springCloudContractParser");
|
|
48
50
|
const pactBrokerClient_1 = require("./contracts/pactBrokerClient");
|
|
49
51
|
const schema_1 = require("./streaming/schema");
|
|
@@ -70,11 +72,10 @@ const routeInference_1 = require("./inference/routeInference");
|
|
|
70
72
|
const scanManifest_1 = require("./inference/scanManifest");
|
|
71
73
|
const serveDashboard_1 = require("./serveDashboard");
|
|
72
74
|
const unitAnalysis_1 = require("./unitAnalysis");
|
|
75
|
+
const analyzeHelpers_1 = require("./analyzeHelpers");
|
|
73
76
|
// Register all language AST analyzers at startup.
|
|
74
77
|
// This side-effect import ensures each language module's registerAnalyzer() call runs.
|
|
75
78
|
(0, astAnalysisOrchestrator_1.registerAllAnalyzers)();
|
|
76
|
-
/** Keywords that indicate a test is exercising an error/failure path. */
|
|
77
|
-
const ERROR_TEST_KEYWORDS = ['error', 'fail', 'throw', 'exception', 'reject', 'invalid', 'blank', 'missing'];
|
|
78
79
|
const program = new commander_1.Command();
|
|
79
80
|
program
|
|
80
81
|
.name('api-tests-coverage-analyzer')
|
|
@@ -181,7 +182,7 @@ async function finaliseObservability(allResults, thresholds, metricsPort, servic
|
|
|
181
182
|
// Log threshold breaches
|
|
182
183
|
for (const r of allResults) {
|
|
183
184
|
const t = thresholds[r.type];
|
|
184
|
-
if (t !== undefined && r.coveragePercent < t) {
|
|
185
|
+
if (t !== undefined && r.totalItems > 0 && r.coveragePercent < t) {
|
|
185
186
|
(0, observability_1.logThresholdBreach)(logger, r.type, r.coveragePercent, t);
|
|
186
187
|
}
|
|
187
188
|
}
|
|
@@ -1562,102 +1563,64 @@ program
|
|
|
1562
1563
|
.command('analyze')
|
|
1563
1564
|
.description('Zero-config full analysis: discover project artifacts, infer missing rules/flows, compute coverage.')
|
|
1564
1565
|
.option('--root <dir>', 'Project root to analyze (default: current working directory)')
|
|
1566
|
+
.option('--spec <path>', 'Path to the current OpenAPI/Swagger spec file (auto-detected when omitted)')
|
|
1567
|
+
.option('--spec-old <path>', 'Path to the previous OpenAPI/Swagger spec file for compatibility analysis')
|
|
1565
1568
|
.option('--reports-dir <dir>', 'Directory to write reports to (default: reports/)')
|
|
1566
1569
|
.option('--export-inferred-rules', 'Export inferred rules/flows as editable YAML files')
|
|
1567
|
-
.option('--no-infer-business-rules', 'Disable business rule inference')
|
|
1568
|
-
.option('--no-infer-integration-flows', 'Disable integration flow inference')
|
|
1570
|
+
.option('--no-infer-business-rules', 'Disable business rule inference for this run when config would otherwise allow it')
|
|
1571
|
+
.option('--no-infer-integration-flows', 'Disable integration flow inference for this run when config would otherwise allow it')
|
|
1569
1572
|
.option('--dashboard', 'Start the coverage dashboard after analysis')
|
|
1570
1573
|
.option('--port <port>', 'Port for the dashboard server (requires --dashboard)', parseInt)
|
|
1571
1574
|
.option('--open', 'Open the dashboard in your browser automatically (requires --dashboard)')
|
|
1572
1575
|
.action(async (options) => {
|
|
1573
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
1576
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1574
1577
|
const { metricsPort, serviceName } = setupObservability();
|
|
1575
1578
|
const logger = (0, observability_1.getLogger)();
|
|
1576
1579
|
const configPath = program.opts()['config'];
|
|
1577
1580
|
const analyzerCfg = (0, config_1.loadCentralConfig)(configPath);
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
const doInferFlows = options['inferIntegrationFlows'] !== false
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
logger.info({ event: 'analyze_start', rootDir }, 'Starting
|
|
1586
|
-
// ── 1. Discover project artifacts ──────────────────────────────────────
|
|
1581
|
+
const startedAt = Date.now();
|
|
1582
|
+
const rootDir = path.resolve((_a = options['root']) !== null && _a !== void 0 ? _a : process.cwd());
|
|
1583
|
+
const reportsDir = path.resolve((_d = (_b = options['reportsDir']) !== null && _b !== void 0 ? _b : (_c = analyzerCfg.reports) === null || _c === void 0 ? void 0 : _c.outputDir) !== null && _d !== void 0 ? _d : 'reports');
|
|
1584
|
+
const doInferRules = ((_e = analyzerCfg.analysis) === null || _e === void 0 ? void 0 : _e.inferBusinessRules) !== false && options['inferBusinessRules'] !== false;
|
|
1585
|
+
const doInferFlows = ((_f = analyzerCfg.analysis) === null || _f === void 0 ? void 0 : _f.inferIntegrationFlows) !== false && options['inferIntegrationFlows'] !== false;
|
|
1586
|
+
const fsMod = fs;
|
|
1587
|
+
fsMod.mkdirSync(reportsDir, { recursive: true });
|
|
1588
|
+
logger.info({ event: 'analyze_start', rootDir }, 'Starting full analyze command');
|
|
1587
1589
|
const artifacts = (0, projectDiscovery_1.discoverProject)({
|
|
1588
1590
|
rootDir,
|
|
1589
1591
|
generatedSourceDirs: analyzerCfg.project.generatedSourceDirs,
|
|
1590
1592
|
});
|
|
1593
|
+
const specPath = await (0, analyzeHelpers_1.detectOpenApiSpec)(rootDir, reportsDir, options['spec']);
|
|
1594
|
+
if (specPath) {
|
|
1595
|
+
console.log(`Auto-detected OpenAPI spec at: ${specPath}`);
|
|
1596
|
+
}
|
|
1591
1597
|
console.log('\n=== API Test Coverage Analyzer ===');
|
|
1592
1598
|
console.log(`Project root: ${rootDir}`);
|
|
1593
1599
|
console.log(`Languages detected: ${artifacts.languages.join(', ') || 'none'}`);
|
|
1594
1600
|
console.log(`Frameworks detected: ${artifacts.frameworks.join(', ') || 'none'}`);
|
|
1595
|
-
console.log(`API specs found: ${artifacts.specs.length}`);
|
|
1596
1601
|
console.log(`Test files found: ${artifacts.testFiles.length}`);
|
|
1597
1602
|
console.log(`Service files found: ${artifacts.serviceFiles.length}`);
|
|
1598
|
-
if (!agnosticDisc && artifacts.specs.length === 0) {
|
|
1599
|
-
console.error('\n[ERROR] No API spec found and agnosticDiscovery is disabled. Aborting.');
|
|
1600
|
-
process.exitCode = 1;
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
1603
|
const warnings = [];
|
|
1604
1604
|
let inferredRulesResult = null;
|
|
1605
1605
|
let inferredFlowsResult = null;
|
|
1606
|
-
// ── 2. Business rule inference ─────────────────────────────────────────
|
|
1607
1606
|
if (doInferRules) {
|
|
1608
1607
|
inferredRulesResult = (0, businessRuleInference_1.inferBusinessRules)(artifacts.serviceFiles, warnings);
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
console.log(` Rules detected in service code: ${inferredRulesResult.rules.length}`);
|
|
1612
|
-
console.log(` Written to: ${rulesPath}`);
|
|
1613
|
-
if (options['exportInferredRules']) {
|
|
1614
|
-
const fsMod = require('fs');
|
|
1615
|
-
const yaml = inferredRulesResult.rules.map((r) => [
|
|
1616
|
-
`- id: ${r.id}`,
|
|
1617
|
-
` name: ${r.name}`,
|
|
1618
|
-
` type: ${r.type}`,
|
|
1619
|
-
r.endpoint ? ` endpoint: "${r.endpoint}"` : null,
|
|
1620
|
-
` condition: "${r.condition.replace(/"/g, "'")}"`,
|
|
1621
|
-
` source: ${r.source_location}`,
|
|
1622
|
-
].filter(Boolean).join('\n')).join('\n');
|
|
1623
|
-
fsMod.writeFileSync(path.join(rootDir, 'generated-business-rules.yaml'), yaml, 'utf-8');
|
|
1624
|
-
console.log(' Exported: generated-business-rules.yaml');
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
// ── 3. Integration flow inference ──────────────────────────────────────
|
|
1608
|
+
(0, businessRuleInference_1.writeInferredBusinessRules)(inferredRulesResult, reportsDir);
|
|
1609
|
+
}
|
|
1628
1610
|
if (doInferFlows) {
|
|
1629
1611
|
inferredFlowsResult = (0, integrationFlowInference_1.inferIntegrationFlows)(artifacts.testFiles, warnings);
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
...f.steps.map((s) => ` - { method: ${s.method}, path: "${s.path}" }`),
|
|
1641
|
-
].join('\n')).join('\n');
|
|
1642
|
-
fs.writeFileSync(path.join(rootDir, 'generated-integration-flows.yaml'), yaml, 'utf-8');
|
|
1643
|
-
console.log(' Exported: generated-integration-flows.yaml');
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
// ── 4. Full coverage analysis → coverage-summary.json ─────────────────
|
|
1647
|
-
const allCoverageResults = [];
|
|
1648
|
-
const fsMod = require('fs');
|
|
1649
|
-
// When test files are explicitly discovered, use them directly.
|
|
1650
|
-
// Otherwise fall back to a test-file-pattern glob (avoids scanning node_modules
|
|
1651
|
-
// or the entire project tree, which can hang on large repositories).
|
|
1652
|
-
const testsGlob = artifacts.testFiles.length > 0
|
|
1653
|
-
? '{' + artifacts.testFiles.join(',') + '}'
|
|
1654
|
-
: path.join(rootDir, '**', '*.{test,spec}.{js,ts,jsx,tsx,mjs,cjs,py,rb}');
|
|
1612
|
+
(0, integrationFlowInference_1.writeInferredIntegrationFlows)(inferredFlowsResult, reportsDir);
|
|
1613
|
+
}
|
|
1614
|
+
const discoveredTestsPattern = `{${artifacts.testFiles.join(',')}}`;
|
|
1615
|
+
const discoveredSourcePattern = `{${artifacts.serviceFiles.join(',')}}`;
|
|
1616
|
+
const testsGlob = artifacts.testFiles.length > 0 && discoveredTestsPattern.length <= 8000
|
|
1617
|
+
? discoveredTestsPattern
|
|
1618
|
+
: path.join(rootDir, '**', '*.{test,spec}.{js,ts,jsx,tsx,mjs,cjs,py,rb,java,kt}');
|
|
1619
|
+
const sourceGlob = artifacts.serviceFiles.length > 0 && discoveredSourcePattern.length <= 8000
|
|
1620
|
+
? discoveredSourcePattern
|
|
1621
|
+
: path.join(rootDir, '**/*.{js,jsx,ts,tsx,java,kt,py,rb}');
|
|
1655
1622
|
const detectedLanguages = artifacts.languages;
|
|
1656
|
-
// Pre-compute test entries once from the discovered test files.
|
|
1657
|
-
// These are reused for endpoint, error, and business-rule coverage matching
|
|
1658
|
-
// (avoids multiple glob expansions which can hang on large projects).
|
|
1659
1623
|
const TEST_DECL_RE = /\b(?:test|it)\s*\(\s*(['"`])([\s\S]*?)\1/g;
|
|
1660
|
-
// Java/Kotlin JUnit: @Test followed by a method declaration
|
|
1661
1624
|
const JAVA_TEST_RE = /@Test\b[^{]*?(?:public|protected|private|default)?\s+(?:\w+\s+)?(\w+)\s*\(\s*\)/g;
|
|
1662
1625
|
const testEntries = artifacts.testFiles.flatMap((tf) => {
|
|
1663
1626
|
let content = '';
|
|
@@ -1671,590 +1634,552 @@ program
|
|
|
1671
1634
|
const contentLower = content.toLowerCase();
|
|
1672
1635
|
const ext = path.extname(tf).toLowerCase();
|
|
1673
1636
|
const isJavaLike = ext === '.java' || ext === '.kt' || ext === '.kts';
|
|
1674
|
-
let
|
|
1637
|
+
let javaMatch;
|
|
1675
1638
|
if (isJavaLike) {
|
|
1676
|
-
// Extract @Test-annotated method names; convert snake_case to spaces for keyword matching
|
|
1677
1639
|
JAVA_TEST_RE.lastIndex = 0;
|
|
1678
|
-
while ((
|
|
1679
|
-
descriptions.push(
|
|
1640
|
+
while ((javaMatch = JAVA_TEST_RE.exec(content)) !== null) {
|
|
1641
|
+
descriptions.push(javaMatch[1].replace(/_/g, ' ').toLowerCase());
|
|
1680
1642
|
}
|
|
1681
1643
|
}
|
|
1682
1644
|
else {
|
|
1645
|
+
let jsMatch;
|
|
1683
1646
|
TEST_DECL_RE.lastIndex = 0;
|
|
1684
|
-
while ((
|
|
1685
|
-
descriptions.push(
|
|
1647
|
+
while ((jsMatch = TEST_DECL_RE.exec(content)) !== null) {
|
|
1648
|
+
descriptions.push(jsMatch[2].toLowerCase());
|
|
1686
1649
|
}
|
|
1687
1650
|
}
|
|
1688
1651
|
return [{ file: tf, contentLower, descriptions, isJavaLike }];
|
|
1689
1652
|
});
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
astConfig: (_m = (_l = analyzerCfg.analysis) === null || _l === void 0 ? void 0 : _l.ast) !== null && _m !== void 0 ? _m : {},
|
|
1717
|
-
deepConfig: undefined,
|
|
1718
|
-
};
|
|
1719
|
-
const paramCoverages = await (0, parameterCoverage_1.analyzeParameterCoverage)(parameters, testsGlob, astParamOptions);
|
|
1720
|
-
const paramReport = (0, parameterCoverage_1.buildParameterCoverageReport)(paramCoverages);
|
|
1721
|
-
const paramResult = {
|
|
1722
|
-
type: 'parameter',
|
|
1723
|
-
totalItems: paramReport.totalParameters,
|
|
1724
|
-
coveredItems: paramCoverages.filter((c) => c.ratio > 0).length,
|
|
1725
|
-
coveragePercent: paramReport.averageCoverage,
|
|
1726
|
-
details: paramReport,
|
|
1727
|
-
};
|
|
1728
|
-
allCoverageResults.push(paramResult);
|
|
1729
|
-
console.log(` ${paramResult.coveredItems}/${paramResult.totalItems} parameters covered (${paramReport.averageCoverage}%)`);
|
|
1730
|
-
}
|
|
1731
|
-
catch (err) {
|
|
1732
|
-
warnings.push(`Parameter coverage failed for ${path.basename(specPath)}: ` +
|
|
1733
|
-
`${err instanceof Error ? err.message : String(err)}`);
|
|
1734
|
-
}
|
|
1735
|
-
// ── 4c. Error scenario coverage ─────────────────────────────────────
|
|
1736
|
-
try {
|
|
1737
|
-
const scenarios = await (0, errorCoverage_1.parseErrorScenarios)(specPath);
|
|
1738
|
-
if (scenarios.length > 0) {
|
|
1739
|
-
const astErrorOptions = {
|
|
1740
|
-
astConfig: (_p = (_o = analyzerCfg.analysis) === null || _o === void 0 ? void 0 : _o.ast) !== null && _p !== void 0 ? _p : {},
|
|
1741
|
-
deepConfig: undefined,
|
|
1742
|
-
};
|
|
1743
|
-
const errorCoverages = await (0, errorCoverage_1.analyzeErrorCoverage)(scenarios, testsGlob, astErrorOptions);
|
|
1744
|
-
const errorReport = (0, errorCoverage_1.buildErrorCoverageReport)(errorCoverages);
|
|
1745
|
-
const errorResult = {
|
|
1746
|
-
type: 'error',
|
|
1747
|
-
totalItems: errorReport.total,
|
|
1748
|
-
coveredItems: errorReport.covered,
|
|
1749
|
-
coveragePercent: errorReport.percentage,
|
|
1750
|
-
details: errorReport,
|
|
1751
|
-
};
|
|
1752
|
-
allCoverageResults.push(errorResult);
|
|
1753
|
-
console.log(` ${errorReport.covered}/${errorReport.total} error scenarios covered (${errorReport.percentage}%)`);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
catch (err) {
|
|
1757
|
-
warnings.push(`Error coverage failed for ${path.basename(specPath)}: ` +
|
|
1758
|
-
`${err instanceof Error ? err.message : String(err)}`);
|
|
1653
|
+
const loadResultCandidates = [
|
|
1654
|
+
path.join(rootDir, 'load-results.json'),
|
|
1655
|
+
path.join(rootDir, 'load-results.self-analysis.json'),
|
|
1656
|
+
path.join(rootDir, 'reports', 'load-results.json'),
|
|
1657
|
+
path.join(rootDir, 'reports', 'load-results.self-analysis.json'),
|
|
1658
|
+
].filter((candidate) => fsMod.existsSync(candidate));
|
|
1659
|
+
const resilienceKeywordPattern = /\b(circuit.?breaker|retry|timeout|fallback|bulkhead)\b/i;
|
|
1660
|
+
const resiliencePatternFiles = artifacts.serviceFiles.filter((filePath) => {
|
|
1661
|
+
try {
|
|
1662
|
+
return resilienceKeywordPattern.test(fsMod.readFileSync(filePath, 'utf-8'));
|
|
1663
|
+
}
|
|
1664
|
+
catch {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
const performanceFilesScanned = artifacts.serviceFiles.length + loadResultCandidates.length;
|
|
1669
|
+
const allCoverageResults = [];
|
|
1670
|
+
const endpointResult = await (0, analyzeHelpers_1.runMetricSafe)('endpoint', { filesScanned: artifacts.serviceFiles.length, scanDurationMs: 0, detectionMethod: specPath ? 'spec-driven' : 'inferred' }, async () => {
|
|
1671
|
+
const metricStartedAt = Date.now();
|
|
1672
|
+
if (specPath) {
|
|
1673
|
+
const endpoints = await (0, endpointCoverage_1.parseOpenApiSpec)(specPath);
|
|
1674
|
+
const coverageMap = await (0, endpointCoverage_1.analyzeTestCoverage)(endpoints, testsGlob, detectedLanguages);
|
|
1675
|
+
const report = (0, endpointCoverage_1.buildCoverageReport)(coverageMap);
|
|
1676
|
+
(0, endpointCoverage_1.generateReports)(report, reportsDir);
|
|
1677
|
+
if (report.total === 0) {
|
|
1678
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('endpoint', (0, analyzeHelpers_1.requiredNaReason)('endpoint', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, report);
|
|
1759
1679
|
}
|
|
1680
|
+
return {
|
|
1681
|
+
type: 'endpoint',
|
|
1682
|
+
totalItems: report.total,
|
|
1683
|
+
coveredItems: report.covered,
|
|
1684
|
+
coveragePercent: report.percentage,
|
|
1685
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1686
|
+
details: report,
|
|
1687
|
+
};
|
|
1760
1688
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const pathSegments = route.path.split('/').filter((s) => s.length > 1 && !s.startsWith(':'));
|
|
1779
|
-
const method = route.method.toLowerCase();
|
|
1780
|
-
// Leaf path segment is the most specific identifier (e.g., "comments", "favorite", "feed")
|
|
1781
|
-
const leafSegment = (_a = pathSegments[pathSegments.length - 1]) !== null && _a !== void 0 ? _a : '';
|
|
1782
|
-
const matchedTests = [];
|
|
1783
|
-
for (const { file, contentLower, descriptions } of testEntries) {
|
|
1784
|
-
let matched = false;
|
|
1785
|
-
// Priority 1: handler function name appears in test file imports/calls
|
|
1786
|
-
if (route.handlerFunction) {
|
|
1787
|
-
const fnLower = route.handlerFunction.toLowerCase();
|
|
1788
|
-
if (contentLower.includes(fnLower)) {
|
|
1789
|
-
matched = true;
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
// Priority 2: test description mentions method + leaf path segment
|
|
1793
|
-
if (!matched && leafSegment.length > 2) {
|
|
1794
|
-
matched = descriptions.some((desc) => desc.includes(method) && desc.includes(leafSegment));
|
|
1795
|
-
}
|
|
1796
|
-
// Priority 3: test description mentions the exact path
|
|
1797
|
-
if (!matched && route.path.length >= 1) {
|
|
1798
|
-
matched = descriptions.some((desc) => desc.includes(route.path.toLowerCase()));
|
|
1799
|
-
}
|
|
1800
|
-
// Priority 4: test file directly calls the URL path (e.g., axios.get('/articles'))
|
|
1801
|
-
if (!matched && pathSegments.length > 0) {
|
|
1802
|
-
// Check for full path string in file (e.g., axios.get('/articles/feed'))
|
|
1803
|
-
const quotedPathPattern = new RegExp(`['"\`]${route.path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"\`]`);
|
|
1804
|
-
if (quotedPathPattern.test(contentLower)) {
|
|
1805
|
-
matched = true;
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
if (matched) {
|
|
1809
|
-
matchedTests.push(path.basename(file));
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
const covered = matchedTests.length > 0;
|
|
1813
|
-
return {
|
|
1814
|
-
id: `${route.method.toUpperCase()} ${route.path}`,
|
|
1815
|
-
covered,
|
|
1816
|
-
matchedTests,
|
|
1817
|
-
handler_function: route.handlerFunction,
|
|
1818
|
-
source_file: route.sourceFile,
|
|
1819
|
-
line_number: route.lineNumber,
|
|
1820
|
-
};
|
|
1821
|
-
});
|
|
1822
|
-
const coveredCount = endpointItems.filter((i) => i.covered).length;
|
|
1823
|
-
const pct = endpointItems.length > 0 ? Math.round((coveredCount / endpointItems.length) * 100) : 0;
|
|
1824
|
-
const endpointResult = {
|
|
1825
|
-
type: 'endpoint',
|
|
1826
|
-
totalItems: endpointItems.length,
|
|
1827
|
-
coveredItems: coveredCount,
|
|
1828
|
-
coveragePercent: pct,
|
|
1829
|
-
details: {
|
|
1830
|
-
total: endpointItems.length,
|
|
1831
|
-
covered: coveredCount,
|
|
1832
|
-
percentage: pct,
|
|
1833
|
-
items: endpointItems,
|
|
1834
|
-
source: 'inferred',
|
|
1835
|
-
},
|
|
1836
|
-
};
|
|
1837
|
-
allCoverageResults.push(endpointResult);
|
|
1838
|
-
console.log(` ${coveredCount}/${endpointItems.length} inferred routes have test coverage (${pct}%)`);
|
|
1839
|
-
// ── 4c-alt. Error coverage from inferred routes + rules ──────────
|
|
1840
|
-
if (inferredRulesResult && inferredRulesResult.rules.length > 0) {
|
|
1841
|
-
console.log(`\nAnalyzing error coverage (from inferred business rules)...`);
|
|
1842
|
-
const errorCandidateRules = inferredRulesResult.rules.filter((r) => r.type === 'validation' || r.type === 'business_logic');
|
|
1843
|
-
const errorItems = errorCandidateRules.map((rule) => {
|
|
1844
|
-
var _a;
|
|
1845
|
-
const matchedTestDescriptions = [];
|
|
1846
|
-
for (const { file, descriptions, isJavaLike, contentLower } of testEntries) {
|
|
1847
|
-
// Match at TEST DESCRIPTION level, not file level
|
|
1848
|
-
// Require: description contains an error indicator + at least one specific keyword
|
|
1849
|
-
const specificKws = (_a = rule.specificKeywords) !== null && _a !== void 0 ? _a : [];
|
|
1850
|
-
const matchingDescs = descriptions.filter((desc) => {
|
|
1851
|
-
const hasErrorKeyword = ERROR_TEST_KEYWORDS.some((kw) => desc.includes(kw));
|
|
1852
|
-
if (!hasErrorKeyword)
|
|
1853
|
-
return false;
|
|
1854
|
-
// If we have specific keywords, at least one must match in the description
|
|
1855
|
-
if (specificKws.length > 0) {
|
|
1856
|
-
return specificKws.some((kw) => desc.includes(kw.toLowerCase()));
|
|
1857
|
-
}
|
|
1858
|
-
// No specific keywords — use handler function name as fallback
|
|
1859
|
-
return true;
|
|
1860
|
-
});
|
|
1861
|
-
if (matchingDescs.length > 0) {
|
|
1862
|
-
matchedTestDescriptions.push(...matchingDescs.map((d) => `[${path.basename(file)}] ${d}`));
|
|
1863
|
-
}
|
|
1864
|
-
else if (isJavaLike) {
|
|
1865
|
-
// Java/Kotlin: test method names rarely contain exception class names.
|
|
1866
|
-
// Check file body for specific long keywords (≥8 chars, e.g. "authorization")
|
|
1867
|
-
// combined with HTTP 4xx status checks or exception throws.
|
|
1868
|
-
const specificLongKws = specificKws
|
|
1869
|
-
.filter((k) => k.length >= 8)
|
|
1870
|
-
.map((k) => k.toLowerCase());
|
|
1871
|
-
if (specificLongKws.length > 0 && specificLongKws.some((kw) => contentLower.includes(kw))) {
|
|
1872
|
-
const hasErrorInContent = /\.statuscode\s*\(\s*[45]\d{2}|throw\s+new\s+\w*exception/i.test(contentLower);
|
|
1873
|
-
if (hasErrorInContent) {
|
|
1874
|
-
// Prefer test methods that look like error/boundary tests
|
|
1875
|
-
const errorDescs = descriptions.filter((d) => /\b(4\d\d|error|fail|forbidden|unauthorized|invalid|exception|not.?found)\b/.test(d));
|
|
1876
|
-
const descsToReport = errorDescs.length > 0 ? errorDescs : descriptions.slice(0, 1);
|
|
1877
|
-
matchedTestDescriptions.push(...descsToReport.map((d) => `[${path.basename(file)}] ${d}`));
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
return {
|
|
1883
|
-
id: rule.id,
|
|
1884
|
-
description: rule.condition,
|
|
1885
|
-
covered: matchedTestDescriptions.length > 0,
|
|
1886
|
-
matchedTests: matchedTestDescriptions,
|
|
1887
|
-
source_location: rule.source_location,
|
|
1888
|
-
code_snippet: rule.code_snippet,
|
|
1889
|
-
};
|
|
1890
|
-
});
|
|
1891
|
-
const errorCovered = errorItems.filter((i) => i.covered).length;
|
|
1892
|
-
const errorPct = errorItems.length > 0 ? Math.round((errorCovered / errorItems.length) * 100) : 0;
|
|
1893
|
-
const errorResult = {
|
|
1894
|
-
type: 'error',
|
|
1895
|
-
totalItems: errorItems.length,
|
|
1896
|
-
coveredItems: errorCovered,
|
|
1897
|
-
coveragePercent: errorPct,
|
|
1898
|
-
details: {
|
|
1899
|
-
total: errorItems.length,
|
|
1900
|
-
covered: errorCovered,
|
|
1901
|
-
percentage: errorPct,
|
|
1902
|
-
items: errorItems,
|
|
1903
|
-
source: 'inferred',
|
|
1904
|
-
},
|
|
1905
|
-
};
|
|
1906
|
-
allCoverageResults.push(errorResult);
|
|
1907
|
-
console.log(` ${errorCovered}/${errorItems.length} inferred error scenarios have test coverage (${errorPct}%)`);
|
|
1908
|
-
}
|
|
1909
|
-
// ── 4b-alt. Parameter coverage from inferred routes + body params ──
|
|
1910
|
-
// Extract path parameters (e.g. ':article', ':id') and body params
|
|
1911
|
-
// (e.g. 'req.body.email') from inferred routes and business rules.
|
|
1912
|
-
// This populates the Parameters tab even without an OpenAPI spec.
|
|
1913
|
-
console.log(`\nAnalyzing parameter coverage (from inferred routes)...`);
|
|
1914
|
-
try {
|
|
1915
|
-
const paramItems = [];
|
|
1916
|
-
// Path parameters from routes
|
|
1917
|
-
const seenParamKeys = new Set();
|
|
1918
|
-
for (const epItem of endpointItems) {
|
|
1919
|
-
const route = routeResult.routes.find((r) => `${r.method.toUpperCase()} ${r.path}` === epItem.id);
|
|
1920
|
-
if (!route)
|
|
1921
|
-
continue;
|
|
1922
|
-
const pathParams = route.path.split('/').filter((s) => s.startsWith(':'));
|
|
1923
|
-
for (const param of pathParams) {
|
|
1924
|
-
const paramName = param.slice(1); // strip ':'
|
|
1925
|
-
const key = `${paramName}@${route.method.toUpperCase()}`;
|
|
1926
|
-
if (seenParamKeys.has(key))
|
|
1927
|
-
continue;
|
|
1928
|
-
seenParamKeys.add(key);
|
|
1929
|
-
paramItems.push({
|
|
1930
|
-
id: `PATH :${paramName} @ ${epItem.id}`,
|
|
1931
|
-
covered: epItem.covered, // param is covered if the route is covered
|
|
1932
|
-
matchedTests: epItem.matchedTests,
|
|
1933
|
-
param_type: 'path',
|
|
1934
|
-
source_file: route.sourceFile,
|
|
1935
|
-
line_number: route.lineNumber,
|
|
1936
|
-
});
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
// Body parameters from inferred business rules (req.body.<field>)
|
|
1940
|
-
if (inferredRulesResult) {
|
|
1941
|
-
const BODY_PARAM_RE = /req\.body\.(\w+)/g;
|
|
1942
|
-
const seenBodyParams = new Set();
|
|
1943
|
-
for (const rule of inferredRulesResult.rules) {
|
|
1944
|
-
let bm;
|
|
1945
|
-
BODY_PARAM_RE.lastIndex = 0;
|
|
1946
|
-
const combinedText = `${rule.condition} ${rule.code_snippet}`;
|
|
1947
|
-
while ((bm = BODY_PARAM_RE.exec(combinedText)) !== null) {
|
|
1948
|
-
const fieldName = bm[1];
|
|
1949
|
-
const paramKey = `body_${fieldName}_${(_q = rule.endpoint) !== null && _q !== void 0 ? _q : ''}`;
|
|
1950
|
-
if (seenBodyParams.has(paramKey))
|
|
1951
|
-
continue;
|
|
1952
|
-
seenBodyParams.add(paramKey);
|
|
1953
|
-
const endpointLabel = (_r = rule.endpoint) !== null && _r !== void 0 ? _r : 'unknown endpoint';
|
|
1954
|
-
// Match: any test description mentioning the field name
|
|
1955
|
-
const fieldNameLower = fieldName.toLowerCase();
|
|
1956
|
-
const matchedTestDescs = [];
|
|
1957
|
-
for (const { file, descriptions } of testEntries) {
|
|
1958
|
-
const hitting = descriptions.filter((desc) => desc.includes(fieldNameLower));
|
|
1959
|
-
if (hitting.length > 0) {
|
|
1960
|
-
matchedTestDescs.push(...hitting.map((d) => `[${path.basename(file)}] ${d}`));
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
const srcParts = (_t = (_s = rule.source_location) === null || _s === void 0 ? void 0 : _s.split(':')) !== null && _t !== void 0 ? _t : [];
|
|
1964
|
-
const srcFile = srcParts[0];
|
|
1965
|
-
const srcLine = srcParts[1] !== undefined ? parseInt(srcParts[1], 10) : undefined;
|
|
1966
|
-
paramItems.push({
|
|
1967
|
-
id: `BODY ${fieldName} @ ${endpointLabel}`,
|
|
1968
|
-
covered: matchedTestDescs.length > 0,
|
|
1969
|
-
matchedTests: matchedTestDescs,
|
|
1970
|
-
param_type: 'body',
|
|
1971
|
-
source_file: srcFile,
|
|
1972
|
-
line_number: srcLine && srcLine > 0 ? srcLine : undefined,
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
if (paramItems.length > 0) {
|
|
1978
|
-
const paramCovered = paramItems.filter((i) => i.covered).length;
|
|
1979
|
-
const paramPct = Math.round((paramCovered / paramItems.length) * 100);
|
|
1980
|
-
allCoverageResults.push({
|
|
1981
|
-
type: 'parameter',
|
|
1982
|
-
totalItems: paramItems.length,
|
|
1983
|
-
coveredItems: paramCovered,
|
|
1984
|
-
coveragePercent: paramPct,
|
|
1985
|
-
details: {
|
|
1986
|
-
total: paramItems.length,
|
|
1987
|
-
covered: paramCovered,
|
|
1988
|
-
percentage: paramPct,
|
|
1989
|
-
items: paramItems,
|
|
1990
|
-
source: 'inferred',
|
|
1991
|
-
},
|
|
1992
|
-
});
|
|
1993
|
-
console.log(` ${paramCovered}/${paramItems.length} inferred parameters have test coverage (${paramPct}%)`);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
catch (paramErr) {
|
|
1997
|
-
warnings.push(`Parameter inference failed: ${paramErr instanceof Error ? paramErr.message : String(paramErr)}`);
|
|
1689
|
+
const routeResult = (0, routeInference_1.inferRoutes)(artifacts.serviceFiles, {
|
|
1690
|
+
generatedSourceDirs: analyzerCfg.project.generatedSourceDirs,
|
|
1691
|
+
openApiFirst: analyzerCfg.project.openApiFirst,
|
|
1692
|
+
});
|
|
1693
|
+
(0, routeInference_1.writeInferredRoutes)(routeResult, reportsDir);
|
|
1694
|
+
const endpointItems = routeResult.routes.map((route) => {
|
|
1695
|
+
var _a;
|
|
1696
|
+
const pathSegments = route.path.split('/').filter((segment) => segment.length > 1 && !segment.startsWith(':'));
|
|
1697
|
+
const leafSegment = (_a = pathSegments[pathSegments.length - 1]) !== null && _a !== void 0 ? _a : '';
|
|
1698
|
+
const method = route.method.toLowerCase();
|
|
1699
|
+
const matchedTests = [];
|
|
1700
|
+
for (const { file, contentLower, descriptions } of testEntries) {
|
|
1701
|
+
const matched = (route.handlerFunction ? contentLower.includes(route.handlerFunction.toLowerCase()) : false) ||
|
|
1702
|
+
(leafSegment.length > 2 && descriptions.some((desc) => desc.includes(method) && desc.includes(leafSegment))) ||
|
|
1703
|
+
descriptions.some((desc) => desc.includes(route.path.toLowerCase()));
|
|
1704
|
+
if (matched) {
|
|
1705
|
+
matchedTests.push(path.basename(file));
|
|
1998
1706
|
}
|
|
1999
1707
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1708
|
+
return {
|
|
1709
|
+
id: `${route.method.toUpperCase()} ${route.path}`,
|
|
1710
|
+
covered: matchedTests.length > 0,
|
|
1711
|
+
matchedTests,
|
|
1712
|
+
source_file: route.sourceFile,
|
|
1713
|
+
line_number: route.lineNumber,
|
|
1714
|
+
};
|
|
1715
|
+
});
|
|
1716
|
+
if (endpointItems.length === 0) {
|
|
1717
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('endpoint', (0, analyzeHelpers_1.requiredNaReason)('endpoint', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' }, { items: [] });
|
|
1718
|
+
}
|
|
1719
|
+
const covered = endpointItems.filter((item) => item.covered).length;
|
|
1720
|
+
const percentage = Math.round((covered / endpointItems.length) * 100);
|
|
1721
|
+
return {
|
|
1722
|
+
type: 'endpoint',
|
|
1723
|
+
totalItems: endpointItems.length,
|
|
1724
|
+
coveredItems: covered,
|
|
1725
|
+
coveragePercent: percentage,
|
|
1726
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' },
|
|
1727
|
+
details: {
|
|
1728
|
+
total: endpointItems.length,
|
|
1729
|
+
covered,
|
|
1730
|
+
percentage,
|
|
1731
|
+
items: endpointItems,
|
|
1732
|
+
source: 'inferred',
|
|
1733
|
+
},
|
|
1734
|
+
};
|
|
1735
|
+
});
|
|
1736
|
+
allCoverageResults.push(endpointResult);
|
|
1737
|
+
const parameterResult = await (0, analyzeHelpers_1.runMetricSafe)('parameter', { filesScanned: artifacts.serviceFiles.length, scanDurationMs: 0, detectionMethod: specPath ? 'spec-driven' : 'inferred' }, async () => {
|
|
1738
|
+
var _a, _b;
|
|
1739
|
+
const metricStartedAt = Date.now();
|
|
1740
|
+
if (!specPath) {
|
|
1741
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('parameter', (0, analyzeHelpers_1.requiredNaReason)('parameter', { routesScanned: endpointResult.totalItems }), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' });
|
|
2003
1742
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
1743
|
+
const parameters = await (0, parameterCoverage_1.parseParameters)(specPath);
|
|
1744
|
+
const astParamOptions = {
|
|
1745
|
+
astConfig: (_b = (_a = analyzerCfg.analysis) === null || _a === void 0 ? void 0 : _a.ast) !== null && _b !== void 0 ? _b : {},
|
|
1746
|
+
deepConfig: undefined,
|
|
1747
|
+
};
|
|
1748
|
+
const coverages = await (0, parameterCoverage_1.analyzeParameterCoverage)(parameters, testsGlob, astParamOptions);
|
|
1749
|
+
const report = (0, parameterCoverage_1.buildParameterCoverageReport)(coverages);
|
|
1750
|
+
(0, parameterCoverage_1.generateParameterReports)(report, reportsDir);
|
|
1751
|
+
if (report.totalParameters === 0) {
|
|
1752
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('parameter', (0, analyzeHelpers_1.requiredNaReason)('parameter', { routesScanned: endpointResult.totalItems }), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, report);
|
|
2006
1753
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
1754
|
+
return {
|
|
1755
|
+
type: 'parameter',
|
|
1756
|
+
totalItems: report.totalParameters,
|
|
1757
|
+
coveredItems: coverages.filter((coverage) => coverage.ratio > 0).length,
|
|
1758
|
+
coveragePercent: report.averageCoverage,
|
|
1759
|
+
scanMeta: { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1760
|
+
details: report,
|
|
1761
|
+
};
|
|
1762
|
+
});
|
|
1763
|
+
allCoverageResults.push(parameterResult);
|
|
2009
1764
|
const businessRulesYaml = path.join(rootDir, 'business-rules.yaml');
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1765
|
+
const businessResult = await (0, analyzeHelpers_1.runMetricSafe)('business', { filesScanned: artifacts.serviceFiles.length, scanDurationMs: 0, detectionMethod: fsMod.existsSync(businessRulesYaml) ? 'spec-driven' : 'inferred' }, async () => {
|
|
1766
|
+
var _a;
|
|
1767
|
+
const metricStartedAt = Date.now();
|
|
1768
|
+
if (fsMod.existsSync(businessRulesYaml)) {
|
|
2014
1769
|
const rules = (0, businessCoverage_1.parseBusinessRules)(businessRulesYaml);
|
|
2015
|
-
const
|
|
2016
|
-
const
|
|
2017
|
-
|
|
1770
|
+
const coverages = await (0, businessCoverage_1.analyzeBusinessCoverage)(rules, testsGlob);
|
|
1771
|
+
const report = (0, businessCoverage_1.buildBusinessCoverageReport)(coverages);
|
|
1772
|
+
(0, businessCoverage_1.generateBusinessReports)(report, reportsDir);
|
|
1773
|
+
if (report.total === 0) {
|
|
1774
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('business', (0, analyzeHelpers_1.requiredNaReason)('business', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, report);
|
|
1775
|
+
}
|
|
1776
|
+
return {
|
|
2018
1777
|
type: 'business',
|
|
2019
|
-
totalItems:
|
|
2020
|
-
coveredItems:
|
|
2021
|
-
coveragePercent:
|
|
2022
|
-
|
|
1778
|
+
totalItems: report.total,
|
|
1779
|
+
coveredItems: report.covered,
|
|
1780
|
+
coveragePercent: report.percentage,
|
|
1781
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1782
|
+
details: report,
|
|
2023
1783
|
};
|
|
2024
|
-
allCoverageResults.push(bizResult);
|
|
2025
|
-
console.log(` ${bizReport.covered}/${bizReport.total} business rules covered (${bizReport.percentage}%)`);
|
|
2026
1784
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
1785
|
+
const inferredRules = (_a = inferredRulesResult === null || inferredRulesResult === void 0 ? void 0 : inferredRulesResult.rules) !== null && _a !== void 0 ? _a : [];
|
|
1786
|
+
if (inferredRules.length === 0) {
|
|
1787
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('business', (0, analyzeHelpers_1.requiredNaReason)('business', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' }, { items: [] });
|
|
2029
1788
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
console.log(`\nAnalyzing business rules coverage (from inferred rules)...`);
|
|
2037
|
-
const syntheticRules = inferredRulesResult.rules.map((r) => {
|
|
2038
|
-
const kwSet = new Set();
|
|
2039
|
-
// Use specificKeywords extracted from the condition (most accurate)
|
|
2040
|
-
if (r.specificKeywords && r.specificKeywords.length > 0) {
|
|
2041
|
-
r.specificKeywords.forEach((kw) => { if (!businessRuleInference_1.KEYWORD_STOP_WORDS.has(kw))
|
|
2042
|
-
kwSet.add(kw); });
|
|
2043
|
-
}
|
|
2044
|
-
else {
|
|
2045
|
-
// Fallback: words from rule name only (filter stop words)
|
|
2046
|
-
r.name.toLowerCase().split(/[-_\s]+/).forEach((w) => {
|
|
2047
|
-
if (w.length > 2 && !businessRuleInference_1.KEYWORD_STOP_WORDS.has(w))
|
|
2048
|
-
kwSet.add(w);
|
|
2049
|
-
});
|
|
2050
|
-
}
|
|
2051
|
-
// Non-trivial path segments from endpoint
|
|
2052
|
-
if (r.endpoint) {
|
|
2053
|
-
r.endpoint.toLowerCase().split(/[/.\s:]+/)
|
|
2054
|
-
.forEach((w) => { if (w.length > 2 && !/^(api|v\d)$/.test(w) && !businessRuleInference_1.KEYWORD_STOP_WORDS.has(w))
|
|
2055
|
-
kwSet.add(w); });
|
|
2056
|
-
}
|
|
2057
|
-
return {
|
|
2058
|
-
id: r.id,
|
|
2059
|
-
description: r.name,
|
|
2060
|
-
endpoints: r.endpoint ? [r.endpoint] : [],
|
|
2061
|
-
keywords: [...kwSet],
|
|
2062
|
-
scenarios: [],
|
|
2063
|
-
};
|
|
1789
|
+
const rules = inferredRules.map((rule) => {
|
|
1790
|
+
var _a;
|
|
1791
|
+
const kwSet = new Set();
|
|
1792
|
+
((_a = rule.specificKeywords) !== null && _a !== void 0 ? _a : []).forEach((keyword) => {
|
|
1793
|
+
if (!businessRuleInference_1.KEYWORD_STOP_WORDS.has(keyword))
|
|
1794
|
+
kwSet.add(keyword.toLowerCase());
|
|
2064
1795
|
});
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
}
|
|
1796
|
+
if (kwSet.size === 0) {
|
|
1797
|
+
rule.name.toLowerCase().split(/[-_\s]+/).forEach((word) => {
|
|
1798
|
+
if (word.length > 2 && !businessRuleInference_1.KEYWORD_STOP_WORDS.has(word))
|
|
1799
|
+
kwSet.add(word);
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
return {
|
|
1803
|
+
id: rule.id,
|
|
1804
|
+
description: rule.name,
|
|
1805
|
+
endpoints: rule.endpoint ? [rule.endpoint] : [],
|
|
1806
|
+
keywords: [...kwSet],
|
|
1807
|
+
scenarios: [],
|
|
1808
|
+
};
|
|
1809
|
+
});
|
|
1810
|
+
const coverages = rules.map((rule) => {
|
|
1811
|
+
const keywords = rule.keywords.map((keyword) => keyword.toLowerCase());
|
|
1812
|
+
const matchedDescriptions = [];
|
|
1813
|
+
const matchedFiles = new Set();
|
|
1814
|
+
for (const { file, descriptions } of testEntries) {
|
|
1815
|
+
const hits = descriptions.filter((description) => keywords.length > 0 && keywords.some((keyword) => description.includes(keyword)));
|
|
1816
|
+
if (hits.length > 0) {
|
|
1817
|
+
hits.forEach((hit) => matchedDescriptions.push(hit));
|
|
1818
|
+
matchedFiles.add(file);
|
|
2089
1819
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
rule,
|
|
1823
|
+
covered: matchedDescriptions.length > 0,
|
|
1824
|
+
testFiles: [...matchedFiles],
|
|
1825
|
+
matchedTests: matchedDescriptions,
|
|
1826
|
+
scenarios: [],
|
|
1827
|
+
};
|
|
1828
|
+
});
|
|
1829
|
+
const report = (0, businessCoverage_1.buildBusinessCoverageReport)(coverages);
|
|
1830
|
+
(0, businessCoverage_1.generateBusinessReports)(report, reportsDir);
|
|
1831
|
+
return {
|
|
1832
|
+
type: 'business',
|
|
1833
|
+
totalItems: report.total,
|
|
1834
|
+
coveredItems: report.covered,
|
|
1835
|
+
coveragePercent: report.percentage,
|
|
1836
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' },
|
|
1837
|
+
details: report.total === 0
|
|
1838
|
+
? { items: [] }
|
|
1839
|
+
: {
|
|
1840
|
+
...report,
|
|
1841
|
+
inferred_details: inferredRules.reduce((acc, rule) => {
|
|
1842
|
+
acc[rule.id] = {
|
|
1843
|
+
source_location: rule.source_location,
|
|
1844
|
+
condition: rule.condition,
|
|
1845
|
+
code_snippet: rule.code_snippet,
|
|
1846
|
+
type: rule.type,
|
|
1847
|
+
specificKeywords: rule.specificKeywords,
|
|
2113
1848
|
};
|
|
2114
1849
|
return acc;
|
|
2115
1850
|
}, {}),
|
|
2116
1851
|
},
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
warnings.push(`Business rules coverage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
// ── 4e. Integration flows coverage ──────────────────────────────────────
|
|
1852
|
+
reason: report.total === 0 ? (0, analyzeHelpers_1.requiredNaReason)('business', { filesScanned: artifacts.serviceFiles.length }) : undefined,
|
|
1853
|
+
status: report.total === 0 ? 'N/A' : undefined,
|
|
1854
|
+
};
|
|
1855
|
+
});
|
|
1856
|
+
allCoverageResults.push(businessResult);
|
|
2126
1857
|
const integrationFlowsYaml = path.join(rootDir, 'integration-flows.yaml');
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
1858
|
+
const integrationResult = await (0, analyzeHelpers_1.runMetricSafe)('integration', { filesScanned: artifacts.testFiles.length, scanDurationMs: 0, detectionMethod: fsMod.existsSync(integrationFlowsYaml) ? 'spec-driven' : 'inferred' }, async () => {
|
|
1859
|
+
var _a;
|
|
1860
|
+
const metricStartedAt = Date.now();
|
|
1861
|
+
if (fsMod.existsSync(integrationFlowsYaml)) {
|
|
2131
1862
|
const flows = (0, integrationCoverage_1.parseIntegrationFlows)(integrationFlowsYaml);
|
|
2132
|
-
const
|
|
2133
|
-
const
|
|
2134
|
-
|
|
1863
|
+
const coverages = await (0, integrationCoverage_1.analyzeIntegrationCoverage)(flows, testsGlob);
|
|
1864
|
+
const report = (0, integrationCoverage_1.buildIntegrationCoverageReport)(coverages);
|
|
1865
|
+
(0, integrationCoverage_1.generateIntegrationReports)(report, reportsDir);
|
|
1866
|
+
if (report.total === 0) {
|
|
1867
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('integration', (0, analyzeHelpers_1.requiredNaReason)('integration', { filesScanned: artifacts.testFiles.length }), { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, report);
|
|
1868
|
+
}
|
|
1869
|
+
return {
|
|
2135
1870
|
type: 'integration',
|
|
2136
|
-
totalItems:
|
|
2137
|
-
coveredItems:
|
|
2138
|
-
coveragePercent:
|
|
2139
|
-
|
|
1871
|
+
totalItems: report.total,
|
|
1872
|
+
coveredItems: report.complete,
|
|
1873
|
+
coveragePercent: report.percentage,
|
|
1874
|
+
scanMeta: { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1875
|
+
details: report,
|
|
2140
1876
|
};
|
|
2141
|
-
allCoverageResults.push(flowResult);
|
|
2142
|
-
console.log(` ${flowReport.complete}/${flowReport.total} integration flows covered (${flowReport.percentage}%)`);
|
|
2143
1877
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
1878
|
+
const inferredFlows = (_a = inferredFlowsResult === null || inferredFlowsResult === void 0 ? void 0 : inferredFlowsResult.flows) !== null && _a !== void 0 ? _a : [];
|
|
1879
|
+
if (inferredFlows.length === 0) {
|
|
1880
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('integration', (0, analyzeHelpers_1.requiredNaReason)('integration', { filesScanned: artifacts.testFiles.length }), { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' }, { items: [] });
|
|
2146
1881
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
id: f.id,
|
|
2153
|
-
name: f.name,
|
|
2154
|
-
total: f.steps.length,
|
|
2155
|
-
covered: f.steps.length,
|
|
1882
|
+
const items = inferredFlows.map((flow) => ({
|
|
1883
|
+
id: flow.id,
|
|
1884
|
+
name: flow.name,
|
|
1885
|
+
total: flow.steps.length,
|
|
1886
|
+
covered: flow.steps.length,
|
|
2156
1887
|
complete: true,
|
|
2157
1888
|
percentage: 100,
|
|
2158
1889
|
uncoveredSteps: [],
|
|
2159
1890
|
}));
|
|
2160
|
-
const
|
|
2161
|
-
total:
|
|
2162
|
-
complete:
|
|
2163
|
-
percentage: 100,
|
|
2164
|
-
items
|
|
1891
|
+
const report = {
|
|
1892
|
+
total: items.length,
|
|
1893
|
+
complete: items.length,
|
|
1894
|
+
percentage: items.length > 0 ? 100 : 0,
|
|
1895
|
+
items,
|
|
2165
1896
|
};
|
|
2166
|
-
|
|
1897
|
+
return {
|
|
2167
1898
|
type: 'integration',
|
|
2168
|
-
totalItems:
|
|
2169
|
-
coveredItems:
|
|
2170
|
-
coveragePercent:
|
|
2171
|
-
|
|
1899
|
+
totalItems: report.total,
|
|
1900
|
+
coveredItems: report.complete,
|
|
1901
|
+
coveragePercent: report.percentage,
|
|
1902
|
+
scanMeta: { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' },
|
|
1903
|
+
details: report,
|
|
2172
1904
|
};
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
const
|
|
2178
|
-
(
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
1905
|
+
});
|
|
1906
|
+
allCoverageResults.push(integrationResult);
|
|
1907
|
+
const errorResult = await (0, analyzeHelpers_1.runMetricSafe)('error', { filesScanned: endpointResult.totalItems, scanDurationMs: 0, detectionMethod: specPath ? 'spec-driven' : 'inferred' }, async () => {
|
|
1908
|
+
var _a, _b;
|
|
1909
|
+
const metricStartedAt = Date.now();
|
|
1910
|
+
if (!specPath) {
|
|
1911
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('error', (0, analyzeHelpers_1.requiredNaReason)('error'), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'inferred' });
|
|
1912
|
+
}
|
|
1913
|
+
const scenarios = await (0, errorCoverage_1.parseErrorScenarios)(specPath);
|
|
1914
|
+
if (scenarios.length === 0) {
|
|
1915
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('error', (0, analyzeHelpers_1.requiredNaReason)('error'), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, { items: [] });
|
|
1916
|
+
}
|
|
1917
|
+
const astErrorOptions = {
|
|
1918
|
+
astConfig: (_b = (_a = analyzerCfg.analysis) === null || _a === void 0 ? void 0 : _a.ast) !== null && _b !== void 0 ? _b : {},
|
|
1919
|
+
deepConfig: undefined,
|
|
1920
|
+
};
|
|
1921
|
+
const coverages = await (0, errorCoverage_1.analyzeErrorCoverage)(scenarios, testsGlob, astErrorOptions);
|
|
1922
|
+
const report = (0, errorCoverage_1.buildErrorCoverageReport)(coverages);
|
|
1923
|
+
(0, errorCoverage_1.generateErrorReports)(report, reportsDir);
|
|
1924
|
+
return {
|
|
1925
|
+
type: 'error',
|
|
1926
|
+
totalItems: report.total,
|
|
1927
|
+
coveredItems: report.covered,
|
|
1928
|
+
coveragePercent: report.percentage,
|
|
1929
|
+
scanMeta: { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1930
|
+
details: report,
|
|
1931
|
+
};
|
|
1932
|
+
});
|
|
1933
|
+
allCoverageResults.push(errorResult);
|
|
1934
|
+
const securityResult = await (0, analyzeHelpers_1.runMetricSafe)('security', { filesScanned: endpointResult.totalItems, scanDurationMs: 0, detectionMethod: specPath ? 'spec-driven' : 'ast' }, async () => {
|
|
1935
|
+
var _a, _b, _c;
|
|
1936
|
+
const metricStartedAt = Date.now();
|
|
1937
|
+
if (!specPath) {
|
|
1938
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('security', (0, analyzeHelpers_1.requiredNaReason)('security'), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'ast' });
|
|
1939
|
+
}
|
|
1940
|
+
const controls = await (0, securityCoverage_1.parseSecurityControls)(specPath);
|
|
1941
|
+
if (controls.length === 0) {
|
|
1942
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('security', (0, analyzeHelpers_1.requiredNaReason)('security', { specDetected: true }), { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, { items: [] });
|
|
1943
|
+
}
|
|
1944
|
+
const astSecurityOptions = {
|
|
1945
|
+
astConfig: (_b = (_a = analyzerCfg.analysis) === null || _a === void 0 ? void 0 : _a.ast) !== null && _b !== void 0 ? _b : {},
|
|
1946
|
+
deepConfig: (_c = analyzerCfg.scans.coverage) === null || _c === void 0 ? void 0 : _c.deepAnalysis,
|
|
1947
|
+
};
|
|
1948
|
+
const coverages = await (0, securityCoverage_1.analyzeSecurityCoverage)(controls, testsGlob, undefined, astSecurityOptions);
|
|
1949
|
+
const report = (0, securityCoverage_1.buildSecurityCoverageReport)(coverages, 0);
|
|
1950
|
+
(0, securityCoverage_1.generateSecurityReports)(report, reportsDir);
|
|
1951
|
+
return {
|
|
1952
|
+
type: 'security',
|
|
1953
|
+
totalItems: report.total,
|
|
1954
|
+
coveredItems: report.covered,
|
|
1955
|
+
coveragePercent: report.percentage,
|
|
1956
|
+
scanMeta: { filesScanned: endpointResult.totalItems, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
1957
|
+
details: report,
|
|
1958
|
+
};
|
|
1959
|
+
});
|
|
1960
|
+
allCoverageResults.push(securityResult);
|
|
1961
|
+
const performanceResult = await (0, analyzeHelpers_1.runMetricSafe)('performance', { filesScanned: performanceFilesScanned, scanDurationMs: 0, detectionMethod: 'cascaded' }, async () => {
|
|
1962
|
+
const metricStartedAt = Date.now();
|
|
1963
|
+
if (!specPath || loadResultCandidates.length === 0) {
|
|
1964
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('performance', (0, analyzeHelpers_1.requiredNaReason)('performance', { filesScanned: performanceFilesScanned }), { filesScanned: performanceFilesScanned, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' });
|
|
1965
|
+
}
|
|
1966
|
+
const endpoints = await (0, perfResilienceCoverage_1.parseEndpointsFromSpec)(specPath);
|
|
1967
|
+
const thresholds = { responseMs: 500, errorRate: 0.05 };
|
|
1968
|
+
const metricsMap = (0, perfResilienceCoverage_1.parseLoadTestResults)(loadResultCandidates);
|
|
1969
|
+
const perfCoverages = (0, perfResilienceCoverage_1.analyzePerformanceCoverage)(endpoints, metricsMap, thresholds);
|
|
1970
|
+
const scenarios = (0, perfResilienceCoverage_1.buildResilienceScenarios)(endpoints);
|
|
1971
|
+
const resilienceCoverages = await (0, perfResilienceCoverage_1.analyzeResilienceCoverage)(scenarios, testsGlob);
|
|
1972
|
+
const report = (0, perfResilienceCoverage_1.buildPerfResilienceReport)(perfCoverages, resilienceCoverages);
|
|
1973
|
+
(0, perfResilienceCoverage_1.generatePerfResilienceReports)(report, reportsDir);
|
|
1974
|
+
if (report.totalEndpoints === 0) {
|
|
1975
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('performance', (0, analyzeHelpers_1.requiredNaReason)('performance', { filesScanned: performanceFilesScanned }), { filesScanned: performanceFilesScanned, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' }, report);
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
type: 'performance',
|
|
1979
|
+
totalItems: report.totalEndpoints,
|
|
1980
|
+
coveredItems: report.endpointsWithLoadData,
|
|
1981
|
+
coveragePercent: report.performanceCoveragePercent,
|
|
1982
|
+
scanMeta: { filesScanned: performanceFilesScanned, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' },
|
|
1983
|
+
details: report,
|
|
1984
|
+
};
|
|
1985
|
+
});
|
|
1986
|
+
allCoverageResults.push(performanceResult);
|
|
1987
|
+
const resilienceResult = await (0, analyzeHelpers_1.runMetricSafe)('resilience', { filesScanned: artifacts.serviceFiles.length, scanDurationMs: 0, detectionMethod: 'cascaded' }, async () => {
|
|
1988
|
+
const metricStartedAt = Date.now();
|
|
1989
|
+
if (!specPath || resiliencePatternFiles.length === 0) {
|
|
1990
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('resilience', (0, analyzeHelpers_1.requiredNaReason)('resilience', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' });
|
|
1991
|
+
}
|
|
1992
|
+
const endpoints = await (0, perfResilienceCoverage_1.parseEndpointsFromSpec)(specPath);
|
|
1993
|
+
const metricsMap = loadResultCandidates.length > 0 ? (0, perfResilienceCoverage_1.parseLoadTestResults)(loadResultCandidates) : new Map();
|
|
1994
|
+
const perfCoverages = (0, perfResilienceCoverage_1.analyzePerformanceCoverage)(endpoints, metricsMap, { responseMs: 500, errorRate: 0.05 });
|
|
1995
|
+
const scenarios = (0, perfResilienceCoverage_1.buildResilienceScenarios)(endpoints);
|
|
1996
|
+
const resilienceCoverages = await (0, perfResilienceCoverage_1.analyzeResilienceCoverage)(scenarios, testsGlob);
|
|
1997
|
+
const report = (0, perfResilienceCoverage_1.buildPerfResilienceReport)(perfCoverages, resilienceCoverages);
|
|
1998
|
+
(0, perfResilienceCoverage_1.generatePerfResilienceReports)(report, reportsDir);
|
|
1999
|
+
if (report.totalResilienceScenarios === 0) {
|
|
2000
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('resilience', (0, analyzeHelpers_1.requiredNaReason)('resilience', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' }, report);
|
|
2001
|
+
}
|
|
2002
|
+
return {
|
|
2003
|
+
type: 'resilience',
|
|
2004
|
+
totalItems: report.totalResilienceScenarios,
|
|
2005
|
+
coveredItems: report.coveredResilienceScenarios,
|
|
2006
|
+
coveragePercent: report.resilienceCoveragePercent,
|
|
2007
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' },
|
|
2008
|
+
details: report,
|
|
2009
|
+
};
|
|
2010
|
+
});
|
|
2011
|
+
allCoverageResults.push(resilienceResult);
|
|
2012
|
+
const unitResult = await (0, analyzeHelpers_1.runMetricSafe)('unit', { filesScanned: artifacts.testFiles.length, scanDurationMs: 0, detectionMethod: 'cascaded' }, async () => {
|
|
2013
|
+
const metricStartedAt = Date.now();
|
|
2014
|
+
const execution = await (0, unitAnalysis_1.analyzeUnitTests)({
|
|
2015
|
+
rootDir,
|
|
2016
|
+
testFiles: artifacts.testFiles,
|
|
2017
|
+
serviceFiles: artifacts.serviceFiles,
|
|
2018
|
+
languages: artifacts.languages,
|
|
2019
|
+
config: analyzerCfg.unitAnalysis,
|
|
2020
|
+
});
|
|
2021
|
+
if (execution.result.totalItems === 0) {
|
|
2022
|
+
return {
|
|
2023
|
+
...execution.result,
|
|
2024
|
+
status: 'N/A',
|
|
2025
|
+
reason: (0, analyzeHelpers_1.requiredNaReason)('unit', { filesScanned: artifacts.testFiles.length }),
|
|
2026
|
+
scanMeta: { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' },
|
|
2192
2027
|
};
|
|
2193
|
-
fsMod.writeFileSync(summaryPath, JSON.stringify(summaryJson, null, 2), 'utf-8');
|
|
2194
2028
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2029
|
+
return {
|
|
2030
|
+
...execution.result,
|
|
2031
|
+
scanMeta: { filesScanned: artifacts.testFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'cascaded' },
|
|
2032
|
+
};
|
|
2033
|
+
});
|
|
2034
|
+
allCoverageResults.push(unitResult);
|
|
2035
|
+
const eventResult = await (0, analyzeHelpers_1.runMetricSafe)('event', { filesScanned: artifacts.serviceFiles.length, scanDurationMs: 0, detectionMethod: 'ast' }, async () => {
|
|
2036
|
+
const metricStartedAt = Date.now();
|
|
2037
|
+
const { analyzeEventCoverage, writeEventReport } = await Promise.resolve().then(() => __importStar(require('./streaming/eventCoverage')));
|
|
2038
|
+
const result = analyzeEventCoverage(sourceGlob, testsGlob);
|
|
2039
|
+
if (result.totalItems === 0) {
|
|
2040
|
+
const emptyReport = {
|
|
2041
|
+
generatedAt: new Date().toISOString(),
|
|
2042
|
+
...(0, analyzeHelpers_1.buildNaCoverageResult)('event', (0, analyzeHelpers_1.requiredNaReason)('event', { filesScanned: artifacts.serviceFiles.length }), { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'ast' }, result.details),
|
|
2043
|
+
topics: [],
|
|
2044
|
+
};
|
|
2045
|
+
fsMod.writeFileSync(path.join(reportsDir, 'event-coverage.json'), JSON.stringify(emptyReport, null, 2), 'utf-8');
|
|
2046
|
+
return emptyReport;
|
|
2197
2047
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
});
|
|
2215
|
-
console.log(`\nCoverage Intelligence: ${intelReport.summary.totalFindings} findings, ` +
|
|
2216
|
-
`${intelReport.summary.totalRecommendations} recommendations ` +
|
|
2217
|
-
`(${intelReport.summary.criticalUncoveredItems} critical uncovered)`);
|
|
2218
|
-
if (intelReport.summary.recommendationsByPriority.P0 > 0) {
|
|
2219
|
-
console.log(`⚠️ P0 Recommendations: ${intelReport.summary.recommendationsByPriority.P0} — immediate action required`);
|
|
2220
|
-
}
|
|
2048
|
+
writeEventReport(result, reportsDir);
|
|
2049
|
+
return {
|
|
2050
|
+
type: 'event',
|
|
2051
|
+
totalItems: result.totalItems,
|
|
2052
|
+
coveredItems: result.coveredItems,
|
|
2053
|
+
coveragePercent: result.coveragePercent,
|
|
2054
|
+
scanMeta: { filesScanned: artifacts.serviceFiles.length, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'ast' },
|
|
2055
|
+
details: result.details,
|
|
2056
|
+
};
|
|
2057
|
+
});
|
|
2058
|
+
allCoverageResults.push(eventResult);
|
|
2059
|
+
const compatibilityResult = await (0, analyzeHelpers_1.runMetricSafe)('compatibility', { filesScanned: specPath ? 2 : 0, scanDurationMs: 0, detectionMethod: 'spec-driven' }, async () => {
|
|
2060
|
+
const metricStartedAt = Date.now();
|
|
2061
|
+
const oldSpecPath = options['specOld'] ? path.resolve(options['specOld']) : undefined;
|
|
2062
|
+
if (!specPath || !oldSpecPath || !fsMod.existsSync(oldSpecPath)) {
|
|
2063
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('compatibility', (0, analyzeHelpers_1.requiredNaReason)('compatibility'), { filesScanned: specPath ? 1 : 0, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' });
|
|
2221
2064
|
}
|
|
2222
|
-
|
|
2223
|
-
|
|
2065
|
+
const oldApi = await (0, compatibilityCoverage_1.loadSpec)(oldSpecPath);
|
|
2066
|
+
const newApi = await (0, compatibilityCoverage_1.loadSpec)(specPath);
|
|
2067
|
+
const changes = (0, compatibilityCoverage_1.compareSpecs)(oldApi, newApi);
|
|
2068
|
+
const verificationResults = (0, compatibilityCoverage_1.verifyContracts)([], newApi);
|
|
2069
|
+
const report = (0, compatibilityCoverage_1.buildCompatibilityReport)(oldApi, newApi, changes, verificationResults, oldSpecPath, specPath);
|
|
2070
|
+
(0, compatibilityCoverage_1.generateCompatibilityReports)(report, reportsDir);
|
|
2071
|
+
const uniqueAffectedEndpoints = new Set(report.breakingChanges.filter((change) => change.changeType !== 'added').map((change) => `${change.method}:${change.path}`)).size;
|
|
2072
|
+
const coveredItems = report.totalOldEndpoints - uniqueAffectedEndpoints;
|
|
2073
|
+
if (report.totalOldEndpoints === 0) {
|
|
2074
|
+
return (0, analyzeHelpers_1.buildNaCoverageResult)('compatibility', (0, analyzeHelpers_1.requiredNaReason)('compatibility'), { filesScanned: 2, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' }, report);
|
|
2075
|
+
}
|
|
2076
|
+
return {
|
|
2077
|
+
type: 'compatibility',
|
|
2078
|
+
totalItems: report.totalOldEndpoints,
|
|
2079
|
+
coveredItems,
|
|
2080
|
+
coveragePercent: report.compatibilityPercent,
|
|
2081
|
+
scanMeta: { filesScanned: 2, scanDurationMs: Date.now() - metricStartedAt, detectionMethod: 'spec-driven' },
|
|
2082
|
+
details: report,
|
|
2083
|
+
};
|
|
2084
|
+
});
|
|
2085
|
+
allCoverageResults.push(compatibilityResult);
|
|
2086
|
+
const orderedResults = (0, analyzeHelpers_1.sortResultsInKnownOrder)(allCoverageResults);
|
|
2087
|
+
const effectiveThresholds = Object.fromEntries(summaryTypes_1.KNOWN_METRIC_TYPES
|
|
2088
|
+
.map((type) => [type, (0, qualityGate_1.effectiveThresholdForCategory)(analyzerCfg.thresholds, type)])
|
|
2089
|
+
.filter((entry) => entry[1] !== undefined));
|
|
2090
|
+
const qualityGateResult = (0, qualityGate_1.evaluateQualityGate)(orderedResults, { thresholds: effectiveThresholds, qualityGate: analyzerCfg.qualityGate });
|
|
2091
|
+
const enrichedResults = (0, analyzeHelpers_1.enrichResultsWithStatuses)(orderedResults, effectiveThresholds, qualityGateResult);
|
|
2092
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
2093
|
+
(0, reporting_1.generateMultiFormatReports)(enrichedResults, ['json'], reportsDir, Object.fromEntries(Object.entries(effectiveThresholds).filter((entry) => entry[1] !== undefined)), observabilityInfo);
|
|
2094
|
+
for (const result of enrichedResults) {
|
|
2095
|
+
if (result.type !== 'event') {
|
|
2096
|
+
(0, analyzeHelpers_1.writeMetricReport)(result, reportsDir);
|
|
2224
2097
|
}
|
|
2225
2098
|
}
|
|
2226
|
-
|
|
2099
|
+
const coverageResultsForIntel = enrichedResults.map((result) => ({
|
|
2100
|
+
type: result.type,
|
|
2101
|
+
totalItems: result.totalItems,
|
|
2102
|
+
coveredItems: result.coveredItems,
|
|
2103
|
+
coveragePercent: result.coveragePercent,
|
|
2104
|
+
details: normalizeDetailsForIntelligence(result.type, result.details),
|
|
2105
|
+
}));
|
|
2106
|
+
const intelligenceReport = (0, index_3.runIntelligenceEngine)({
|
|
2107
|
+
coverageResults: coverageResultsForIntel,
|
|
2108
|
+
languages: artifacts.languages,
|
|
2109
|
+
frameworks: artifacts.frameworks,
|
|
2110
|
+
projectName: path.basename(rootDir),
|
|
2111
|
+
outDir: reportsDir,
|
|
2112
|
+
});
|
|
2113
|
+
(0, observability_2.recordIntelligenceMetrics)({
|
|
2114
|
+
projectName: path.basename(rootDir),
|
|
2115
|
+
totalFindings: intelligenceReport.summary.totalFindings,
|
|
2116
|
+
totalRecommendations: intelligenceReport.summary.totalRecommendations,
|
|
2117
|
+
recommendationsByPriority: intelligenceReport.summary.recommendationsByPriority,
|
|
2118
|
+
maxRiskScore: intelligenceReport.summary.maxRiskScore,
|
|
2119
|
+
avgRiskScore: intelligenceReport.summary.avgRiskScore,
|
|
2120
|
+
criticalUncoveredItems: intelligenceReport.summary.criticalUncoveredItems,
|
|
2121
|
+
unprotectedSecurityFindings: intelligenceReport.summary.unprotectedSecurityFindings,
|
|
2122
|
+
languages: artifacts.languages,
|
|
2123
|
+
frameworks: artifacts.frameworks,
|
|
2124
|
+
}, path.basename(rootDir));
|
|
2125
|
+
const summaryInput = {
|
|
2126
|
+
results: enrichedResults,
|
|
2127
|
+
qualityGate: qualityGateResult,
|
|
2128
|
+
thresholds: effectiveThresholds,
|
|
2129
|
+
projectName: path.basename(rootDir),
|
|
2130
|
+
intelligenceSummary: intelligenceReport.summary,
|
|
2131
|
+
};
|
|
2132
|
+
await (0, buildSummary_1.generateBuildSummary)(summaryInput, reportsDir);
|
|
2133
|
+
await (0, prSummary_1.generatePrSummary)(summaryInput, reportsDir);
|
|
2134
|
+
(0, analyzeHelpers_1.writeArchitectureArtifacts)({
|
|
2135
|
+
rootDir,
|
|
2136
|
+
reportsDir,
|
|
2137
|
+
languages: artifacts.languages,
|
|
2138
|
+
frameworks: artifacts.frameworks,
|
|
2139
|
+
serviceFiles: artifacts.serviceFiles.length,
|
|
2140
|
+
testFiles: artifacts.testFiles.length,
|
|
2141
|
+
specPath,
|
|
2142
|
+
results: enrichedResults,
|
|
2143
|
+
});
|
|
2144
|
+
const summaryPath = path.join(reportsDir, 'coverage-summary.json');
|
|
2145
|
+
try {
|
|
2146
|
+
const summaryJson = JSON.parse(fsMod.readFileSync(summaryPath, 'utf-8'));
|
|
2147
|
+
summaryJson.discoveryInfo = {
|
|
2148
|
+
projectRoot: rootDir,
|
|
2149
|
+
analyzedAt: new Date().toISOString(),
|
|
2150
|
+
languages: artifacts.languages,
|
|
2151
|
+
frameworks: artifacts.frameworks,
|
|
2152
|
+
serviceFilesCount: artifacts.serviceFiles.length,
|
|
2153
|
+
testFilesCount: artifacts.testFiles.length,
|
|
2154
|
+
specFilesCount: specPath ? 1 : 0,
|
|
2155
|
+
analysisMode: specPath ? 'explicit-spec' : 'inferred',
|
|
2156
|
+
};
|
|
2157
|
+
summaryJson.qualityGate = qualityGateResult;
|
|
2158
|
+
fsMod.writeFileSync(summaryPath, JSON.stringify(summaryJson, null, 2), 'utf-8');
|
|
2159
|
+
}
|
|
2160
|
+
catch (err) {
|
|
2161
|
+
warnings.push(`Coverage summary update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2162
|
+
}
|
|
2227
2163
|
try {
|
|
2228
|
-
const scanTypes =
|
|
2229
|
-
type:
|
|
2230
|
-
source:
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2164
|
+
const scanTypes = enrichedResults.map((result) => ({
|
|
2165
|
+
type: result.type,
|
|
2166
|
+
source: result.totalItems === 0
|
|
2167
|
+
? 'skipped'
|
|
2168
|
+
: specPath
|
|
2169
|
+
? 'explicit'
|
|
2170
|
+
: 'inferred',
|
|
2171
|
+
reason: result.reason,
|
|
2172
|
+
itemsFound: result.totalItems,
|
|
2173
|
+
itemsCovered: result.coveredItems,
|
|
2174
|
+
coveragePercent: result.coveragePercent,
|
|
2234
2175
|
}));
|
|
2235
|
-
// Add skipped types (exclude 'business' and 'integration' since these are always attempted
|
|
2236
|
-
// via rule/flow inference regardless of whether a spec file is present; their absence from
|
|
2237
|
-
// allCoverageResults means no rules or flows were discovered, which is informative on its own.)
|
|
2238
|
-
const coveredTypes = new Set(allCoverageResults.map((r) => r.type));
|
|
2239
|
-
for (const skippedType of summaryTypes_1.KNOWN_METRIC_TYPES.filter((t) => t !== 'business' && t !== 'integration')) {
|
|
2240
|
-
if (!coveredTypes.has(skippedType)) {
|
|
2241
|
-
scanTypes.push({
|
|
2242
|
-
type: skippedType,
|
|
2243
|
-
source: 'skipped',
|
|
2244
|
-
reason: artifacts.specs.length === 0 ? 'No API spec and no routes detected' : 'No data available',
|
|
2245
|
-
itemsFound: 0,
|
|
2246
|
-
itemsCovered: 0,
|
|
2247
|
-
coveragePercent: 0,
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
2176
|
const manifestPath = (0, scanManifest_1.writeScanManifest)({
|
|
2252
2177
|
projectRoot: rootDir,
|
|
2253
2178
|
analyzedAt: new Date().toISOString(),
|
|
2254
2179
|
discoveredFiles: {
|
|
2255
2180
|
serviceFiles: artifacts.serviceFiles,
|
|
2256
2181
|
testFiles: artifacts.testFiles,
|
|
2257
|
-
specFiles:
|
|
2182
|
+
specFiles: specPath ? [specPath] : [],
|
|
2258
2183
|
},
|
|
2259
2184
|
languages: artifacts.languages,
|
|
2260
2185
|
frameworks: artifacts.frameworks,
|
|
@@ -2265,27 +2190,37 @@ program
|
|
|
2265
2190
|
catch (err) {
|
|
2266
2191
|
warnings.push(`Scan manifest write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2267
2192
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
console.warn(`[WARN] ${w}`);
|
|
2271
|
-
}
|
|
2272
|
-
// ── 6. Configuration override log ──────────────────────────────────────
|
|
2273
|
-
if (options['inferBusinessRules'] === false) {
|
|
2274
|
-
console.log('\nConfiguration override detected: business rule inference disabled via CLI');
|
|
2193
|
+
for (const warning of warnings) {
|
|
2194
|
+
console.warn(`[WARN] ${warning}`);
|
|
2275
2195
|
}
|
|
2276
|
-
|
|
2277
|
-
|
|
2196
|
+
console.log('');
|
|
2197
|
+
console.log((0, analyzeHelpers_1.renderAnalyzeSummaryTable)({
|
|
2198
|
+
rootDir,
|
|
2199
|
+
serviceFiles: artifacts.serviceFiles.length,
|
|
2200
|
+
testFiles: artifacts.testFiles.length,
|
|
2201
|
+
durationMs: Date.now() - startedAt,
|
|
2202
|
+
results: enrichedResults,
|
|
2203
|
+
thresholds: effectiveThresholds,
|
|
2204
|
+
qualityGate: qualityGateResult,
|
|
2205
|
+
}));
|
|
2206
|
+
console.log(`\nReports written to: ${reportsDir}`);
|
|
2207
|
+
logger.info({ event: 'analyze_complete', warnings: warnings.length, metrics: enrichedResults.length }, 'Full analyze command complete');
|
|
2208
|
+
await finaliseObservability(enrichedResults, Object.fromEntries(Object.entries(effectiveThresholds).filter((entry) => entry[1] !== undefined)), metricsPort, serviceName);
|
|
2209
|
+
const thresholdFailures = (0, reporting_1.checkThresholds)(enrichedResults, Object.fromEntries(Object.entries(effectiveThresholds).filter((entry) => entry[1] !== undefined)));
|
|
2210
|
+
if (thresholdFailures.length > 0) {
|
|
2211
|
+
for (const failure of thresholdFailures) {
|
|
2212
|
+
console.error(`THRESHOLD FAILURE: ${failure}`);
|
|
2213
|
+
}
|
|
2214
|
+
process.exitCode = 1;
|
|
2278
2215
|
}
|
|
2279
|
-
logger.info({ event: 'analyze_complete', warnings: warnings.length }, 'Agnostic project analysis complete');
|
|
2280
|
-
await finaliseObservability(allCoverageResults, {}, metricsPort, serviceName);
|
|
2281
|
-
// ── 7. Optionally launch the dashboard ─────────────────────────────────
|
|
2282
2216
|
if (options['dashboard']) {
|
|
2217
|
+
const port = (_g = options['port']) !== null && _g !== void 0 ? _g : 4000;
|
|
2218
|
+
console.log(`Dashboard: http://localhost:${port}`);
|
|
2283
2219
|
(0, serveDashboard_1.serveDashboard)({
|
|
2284
|
-
reportsDir
|
|
2285
|
-
port
|
|
2220
|
+
reportsDir,
|
|
2221
|
+
port,
|
|
2286
2222
|
open: Boolean(options['open']),
|
|
2287
2223
|
});
|
|
2288
|
-
// Keep the process alive — the HTTP server holds the event loop open
|
|
2289
2224
|
}
|
|
2290
2225
|
});
|
|
2291
2226
|
// ─── unit-analysis command ────────────────────────────────────────────────────
|
|
@@ -2327,7 +2262,10 @@ program
|
|
|
2327
2262
|
if (execution.failures.length > 0) {
|
|
2328
2263
|
process.exitCode = 1;
|
|
2329
2264
|
}
|
|
2330
|
-
|
|
2265
|
+
for (const line of execution.consoleLines) {
|
|
2266
|
+
console.log(line);
|
|
2267
|
+
}
|
|
2268
|
+
console.log(`Unit analysis complete: ${execution.result.coveredItems}/${execution.result.totalItems} unit items analysed`);
|
|
2331
2269
|
console.log(`Reports written to: ${reportsDir}`);
|
|
2332
2270
|
await finaliseObservability([execution.result], {}, metricsPort, serviceName);
|
|
2333
2271
|
});
|