api-tests-coverage 1.0.3 → 1.0.5
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/src/businessCoverage.d.ts.map +1 -1
- package/dist/src/businessCoverage.js +14 -1
- package/dist/src/index.js +192 -29
- package/dist/src/integrationCoverage.d.ts.map +1 -1
- package/dist/src/integrationCoverage.js +13 -1
- package/dist/src/serveDashboard.d.ts.map +1 -1
- package/dist/src/serveDashboard.js +11 -0
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"businessCoverage.d.ts","sourceRoot":"","sources":["../../src/businessCoverage.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qDAAqD;IACrD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,yCAAyC;IACzC,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"businessCoverage.d.ts","sourceRoot":"","sources":["../../src/businessCoverage.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qDAAqD;IACrD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,yCAAyC;IACzC,SAAS,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CA8BpE;AAmED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,YAAY,EAAE,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAyDjC;AAID;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,oBAAoB,EAAE,GAChC,sBAAsB,CAOxB;AAID;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,MAAM,GACjB,IAAI,CAgHN"}
|
|
@@ -65,7 +65,20 @@ function parseBusinessRules(rulesPath) {
|
|
|
65
65
|
!Array.isArray(parsed.rules)) {
|
|
66
66
|
throw new Error(`Invalid business rules file: expected a top-level "rules" array in ${rulesPath}`);
|
|
67
67
|
}
|
|
68
|
-
return parsed.rules
|
|
68
|
+
return parsed.rules.map((rule) => {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
return ({
|
|
71
|
+
...rule,
|
|
72
|
+
keywords: (_a = rule.keywords) !== null && _a !== void 0 ? _a : [],
|
|
73
|
+
scenarios: ((_b = rule.scenarios) !== null && _b !== void 0 ? _b : []).map((s) => {
|
|
74
|
+
var _a;
|
|
75
|
+
return ({
|
|
76
|
+
...s,
|
|
77
|
+
keywords: (_a = s.keywords) !== null && _a !== void 0 ? _a : [],
|
|
78
|
+
});
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
});
|
|
69
82
|
}
|
|
70
83
|
/**
|
|
71
84
|
* Extract all test / it declarations from a file, together with their
|
package/dist/src/index.js
CHANGED
|
@@ -1349,7 +1349,7 @@ program
|
|
|
1349
1349
|
.option('--port <port>', 'Port for the dashboard server (requires --dashboard)', parseInt)
|
|
1350
1350
|
.option('--open', 'Open the dashboard in your browser automatically (requires --dashboard)')
|
|
1351
1351
|
.action(async (options) => {
|
|
1352
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
1352
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
1353
1353
|
const { metricsPort, serviceName } = setupObservability();
|
|
1354
1354
|
const logger = (0, observability_1.getLogger)();
|
|
1355
1355
|
const configPath = program.opts()['config'];
|
|
@@ -1377,19 +1377,18 @@ program
|
|
|
1377
1377
|
return;
|
|
1378
1378
|
}
|
|
1379
1379
|
const warnings = [];
|
|
1380
|
+
let inferredRulesResult = null;
|
|
1381
|
+
let inferredFlowsResult = null;
|
|
1380
1382
|
// ── 2. Business rule inference ─────────────────────────────────────────
|
|
1381
1383
|
if (doInferRules) {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
console.log(`\nBusiness Rule Coverage`);
|
|
1387
|
-
console.log(` Source: inferred`);
|
|
1388
|
-
console.log(` Rules detected: ${rulesResult.rules.length}`);
|
|
1384
|
+
inferredRulesResult = (0, businessRuleInference_1.inferBusinessRules)(artifacts.serviceFiles, warnings);
|
|
1385
|
+
const rulesPath = (0, businessRuleInference_1.writeInferredBusinessRules)(inferredRulesResult, reportsDir);
|
|
1386
|
+
console.log(`\nBusiness Rule Inference`);
|
|
1387
|
+
console.log(` Rules detected in service code: ${inferredRulesResult.rules.length}`);
|
|
1389
1388
|
console.log(` Written to: ${rulesPath}`);
|
|
1390
1389
|
if (options['exportInferredRules']) {
|
|
1391
1390
|
const fsMod = require('fs');
|
|
1392
|
-
const yaml =
|
|
1391
|
+
const yaml = inferredRulesResult.rules.map((r) => [
|
|
1393
1392
|
`- id: ${r.id}`,
|
|
1394
1393
|
` name: ${r.name}`,
|
|
1395
1394
|
` type: ${r.type}`,
|
|
@@ -1403,17 +1402,14 @@ program
|
|
|
1403
1402
|
}
|
|
1404
1403
|
// ── 3. Integration flow inference ──────────────────────────────────────
|
|
1405
1404
|
if (doInferFlows) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
console.log(`\nIntegration Flow Coverage`);
|
|
1411
|
-
console.log(` Source: inferred`);
|
|
1412
|
-
console.log(` Flows detected: ${flowsResult.flows.length}`);
|
|
1405
|
+
inferredFlowsResult = (0, integrationFlowInference_1.inferIntegrationFlows)(artifacts.testFiles, warnings);
|
|
1406
|
+
const flowsPath = (0, integrationFlowInference_1.writeInferredIntegrationFlows)(inferredFlowsResult, reportsDir);
|
|
1407
|
+
console.log(`\nIntegration Flow Inference`);
|
|
1408
|
+
console.log(` Multi-step flows detected in tests: ${inferredFlowsResult.flows.length}`);
|
|
1413
1409
|
console.log(` Written to: ${flowsPath}`);
|
|
1414
1410
|
if (options['exportInferredRules']) {
|
|
1415
1411
|
const fs = require('fs');
|
|
1416
|
-
const yaml =
|
|
1412
|
+
const yaml = inferredFlowsResult.flows.map((f) => [
|
|
1417
1413
|
`- id: ${f.id}`,
|
|
1418
1414
|
` name: "${f.name}"`,
|
|
1419
1415
|
` steps:`,
|
|
@@ -1423,14 +1419,16 @@ program
|
|
|
1423
1419
|
console.log(' Exported: generated-integration-flows.yaml');
|
|
1424
1420
|
}
|
|
1425
1421
|
}
|
|
1426
|
-
// ── 4.
|
|
1422
|
+
// ── 4. Full coverage analysis → coverage-summary.json ─────────────────
|
|
1427
1423
|
const allCoverageResults = [];
|
|
1424
|
+
const fsMod = require('fs');
|
|
1425
|
+
const testsGlob = artifacts.testFiles.length > 0
|
|
1426
|
+
? '{' + artifacts.testFiles.join(',') + '}'
|
|
1427
|
+
: path.join(rootDir, '**', '*');
|
|
1428
|
+
const detectedLanguages = artifacts.languages;
|
|
1428
1429
|
if (artifacts.specs.length > 0) {
|
|
1429
|
-
const testsGlob = artifacts.testFiles.length > 0
|
|
1430
|
-
? '{' + artifacts.testFiles.join(',') + '}'
|
|
1431
|
-
: path.join(rootDir, '**', '*');
|
|
1432
|
-
const detectedLanguages = artifacts.languages;
|
|
1433
1430
|
for (const specPath of artifacts.specs) {
|
|
1431
|
+
// ── 4a. Endpoint coverage ───────────────────────────────────────────
|
|
1434
1432
|
try {
|
|
1435
1433
|
console.log(`\nAnalyzing endpoint coverage for: ${path.basename(specPath)}`);
|
|
1436
1434
|
const endpoints = await (0, endpointCoverage_1.parseOpenApiSpec)(specPath);
|
|
@@ -1450,15 +1448,180 @@ program
|
|
|
1450
1448
|
warnings.push(`Endpoint coverage failed for ${path.basename(specPath)}: ` +
|
|
1451
1449
|
`${err instanceof Error ? err.message : String(err)}`);
|
|
1452
1450
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1451
|
+
// ── 4b. Parameter coverage ──────────────────────────────────────────
|
|
1452
|
+
try {
|
|
1453
|
+
const parameters = await (0, parameterCoverage_1.parseParameters)(specPath);
|
|
1454
|
+
const astParamOptions = {
|
|
1455
|
+
astConfig: (_m = (_l = analyzerCfg.analysis) === null || _l === void 0 ? void 0 : _l.ast) !== null && _m !== void 0 ? _m : {},
|
|
1456
|
+
deepConfig: undefined,
|
|
1457
|
+
};
|
|
1458
|
+
const paramCoverages = await (0, parameterCoverage_1.analyzeParameterCoverage)(parameters, testsGlob, astParamOptions);
|
|
1459
|
+
const paramReport = (0, parameterCoverage_1.buildParameterCoverageReport)(paramCoverages);
|
|
1460
|
+
const paramResult = {
|
|
1461
|
+
type: 'parameter',
|
|
1462
|
+
totalItems: paramReport.totalParameters,
|
|
1463
|
+
coveredItems: paramCoverages.filter((c) => c.ratio > 0).length,
|
|
1464
|
+
coveragePercent: paramReport.averageCoverage,
|
|
1465
|
+
details: paramReport,
|
|
1466
|
+
};
|
|
1467
|
+
allCoverageResults.push(paramResult);
|
|
1468
|
+
console.log(` ${paramResult.coveredItems}/${paramResult.totalItems} parameters covered (${paramReport.averageCoverage}%)`);
|
|
1469
|
+
}
|
|
1470
|
+
catch (err) {
|
|
1471
|
+
warnings.push(`Parameter coverage failed for ${path.basename(specPath)}: ` +
|
|
1472
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
1473
|
+
}
|
|
1474
|
+
// ── 4c. Error scenario coverage ─────────────────────────────────────
|
|
1475
|
+
try {
|
|
1476
|
+
const scenarios = await (0, errorCoverage_1.parseErrorScenarios)(specPath);
|
|
1477
|
+
if (scenarios.length > 0) {
|
|
1478
|
+
const astErrorOptions = {
|
|
1479
|
+
astConfig: (_p = (_o = analyzerCfg.analysis) === null || _o === void 0 ? void 0 : _o.ast) !== null && _p !== void 0 ? _p : {},
|
|
1480
|
+
deepConfig: undefined,
|
|
1481
|
+
};
|
|
1482
|
+
const errorCoverages = await (0, errorCoverage_1.analyzeErrorCoverage)(scenarios, testsGlob, astErrorOptions);
|
|
1483
|
+
const errorReport = (0, errorCoverage_1.buildErrorCoverageReport)(errorCoverages);
|
|
1484
|
+
const errorResult = {
|
|
1485
|
+
type: 'error',
|
|
1486
|
+
totalItems: errorReport.total,
|
|
1487
|
+
coveredItems: errorReport.covered,
|
|
1488
|
+
coveragePercent: errorReport.percentage,
|
|
1489
|
+
details: errorReport,
|
|
1490
|
+
};
|
|
1491
|
+
allCoverageResults.push(errorResult);
|
|
1492
|
+
console.log(` ${errorReport.covered}/${errorReport.total} error scenarios covered (${errorReport.percentage}%)`);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
catch (err) {
|
|
1496
|
+
warnings.push(`Error coverage failed for ${path.basename(specPath)}: ` +
|
|
1497
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
1498
|
+
}
|
|
1458
1499
|
}
|
|
1459
1500
|
}
|
|
1460
1501
|
else {
|
|
1461
|
-
warnings.push('No API spec files found; endpoint coverage analysis skipped.');
|
|
1502
|
+
warnings.push('No API spec files found; endpoint/parameter/error coverage analysis skipped.');
|
|
1503
|
+
}
|
|
1504
|
+
// ── 4d. Business rules coverage ─────────────────────────────────────────
|
|
1505
|
+
const businessRulesYaml = path.join(rootDir, 'business-rules.yaml');
|
|
1506
|
+
if (fsMod.existsSync(businessRulesYaml)) {
|
|
1507
|
+
// Explicit YAML takes precedence
|
|
1508
|
+
try {
|
|
1509
|
+
console.log(`\nAnalyzing business rules coverage...`);
|
|
1510
|
+
const rules = (0, businessCoverage_1.parseBusinessRules)(businessRulesYaml);
|
|
1511
|
+
const bizCoverages = await (0, businessCoverage_1.analyzeBusinessCoverage)(rules, testsGlob);
|
|
1512
|
+
const bizReport = (0, businessCoverage_1.buildBusinessCoverageReport)(bizCoverages);
|
|
1513
|
+
const bizResult = {
|
|
1514
|
+
type: 'business',
|
|
1515
|
+
totalItems: bizReport.total,
|
|
1516
|
+
coveredItems: bizReport.covered,
|
|
1517
|
+
coveragePercent: bizReport.percentage,
|
|
1518
|
+
details: bizReport,
|
|
1519
|
+
};
|
|
1520
|
+
allCoverageResults.push(bizResult);
|
|
1521
|
+
console.log(` ${bizReport.covered}/${bizReport.total} business rules covered (${bizReport.percentage}%)`);
|
|
1522
|
+
}
|
|
1523
|
+
catch (err) {
|
|
1524
|
+
warnings.push(`Business rules coverage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
else if (inferredRulesResult && inferredRulesResult.rules.length > 0) {
|
|
1528
|
+
// Auto-inferred: derive keywords from rule name, condition, and endpoint
|
|
1529
|
+
try {
|
|
1530
|
+
console.log(`\nAnalyzing business rules coverage (from inferred rules)...`);
|
|
1531
|
+
const syntheticRules = inferredRulesResult.rules.map((r) => {
|
|
1532
|
+
var _a;
|
|
1533
|
+
const kwSet = new Set();
|
|
1534
|
+
// Words from the rule name (e.g. "amount-exceeds-balance" → ["amount", "exceeds", "balance"])
|
|
1535
|
+
r.name.toLowerCase().split(/[-_\s]+/).forEach((w) => { if (w.length > 2)
|
|
1536
|
+
kwSet.add(w); });
|
|
1537
|
+
// Identifiers from the condition expression
|
|
1538
|
+
((_a = r.condition.match(/\b[a-zA-Z][a-zA-Z0-9]{3,}\b/g)) !== null && _a !== void 0 ? _a : [])
|
|
1539
|
+
.forEach((w) => kwSet.add(w.toLowerCase()));
|
|
1540
|
+
// Non-trivial path segments from endpoint
|
|
1541
|
+
if (r.endpoint) {
|
|
1542
|
+
r.endpoint.toLowerCase().split(/[/.\s:]+/)
|
|
1543
|
+
.forEach((w) => { if (w.length > 2 && !/^(api|v\d)$/.test(w))
|
|
1544
|
+
kwSet.add(w); });
|
|
1545
|
+
}
|
|
1546
|
+
return {
|
|
1547
|
+
id: r.id,
|
|
1548
|
+
description: r.name,
|
|
1549
|
+
endpoints: r.endpoint ? [r.endpoint] : [],
|
|
1550
|
+
keywords: [...kwSet],
|
|
1551
|
+
scenarios: [],
|
|
1552
|
+
};
|
|
1553
|
+
});
|
|
1554
|
+
const bizCoverages = await (0, businessCoverage_1.analyzeBusinessCoverage)(syntheticRules, testsGlob);
|
|
1555
|
+
const bizReport = (0, businessCoverage_1.buildBusinessCoverageReport)(bizCoverages);
|
|
1556
|
+
const bizResult = {
|
|
1557
|
+
type: 'business',
|
|
1558
|
+
totalItems: bizReport.total,
|
|
1559
|
+
coveredItems: bizReport.covered,
|
|
1560
|
+
coveragePercent: bizReport.percentage,
|
|
1561
|
+
details: bizReport,
|
|
1562
|
+
};
|
|
1563
|
+
allCoverageResults.push(bizResult);
|
|
1564
|
+
console.log(` ${bizReport.covered}/${bizReport.total} inferred business rules have test coverage (${bizReport.percentage}%)`);
|
|
1565
|
+
}
|
|
1566
|
+
catch (err) {
|
|
1567
|
+
warnings.push(`Business rules coverage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
// ── 4e. Integration flows coverage ──────────────────────────────────────
|
|
1571
|
+
const integrationFlowsYaml = path.join(rootDir, 'integration-flows.yaml');
|
|
1572
|
+
if (fsMod.existsSync(integrationFlowsYaml)) {
|
|
1573
|
+
// Explicit YAML takes precedence
|
|
1574
|
+
try {
|
|
1575
|
+
console.log(`\nAnalyzing integration flows coverage...`);
|
|
1576
|
+
const flows = (0, integrationCoverage_1.parseIntegrationFlows)(integrationFlowsYaml);
|
|
1577
|
+
const flowCoverages = await (0, integrationCoverage_1.analyzeIntegrationCoverage)(flows, testsGlob);
|
|
1578
|
+
const flowReport = (0, integrationCoverage_1.buildIntegrationCoverageReport)(flowCoverages);
|
|
1579
|
+
const flowResult = {
|
|
1580
|
+
type: 'integration',
|
|
1581
|
+
totalItems: flowReport.total,
|
|
1582
|
+
coveredItems: flowReport.complete,
|
|
1583
|
+
coveragePercent: flowReport.percentage,
|
|
1584
|
+
details: flowReport,
|
|
1585
|
+
};
|
|
1586
|
+
allCoverageResults.push(flowResult);
|
|
1587
|
+
console.log(` ${flowReport.complete}/${flowReport.total} integration flows covered (${flowReport.percentage}%)`);
|
|
1588
|
+
}
|
|
1589
|
+
catch (err) {
|
|
1590
|
+
warnings.push(`Integration flows coverage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
else if (inferredFlowsResult && inferredFlowsResult.flows.length > 0) {
|
|
1594
|
+
// Auto-inferred: flows are extracted FROM tests, so by definition they're all covered
|
|
1595
|
+
console.log(`\nIntegration flows coverage (from inferred flows)...`);
|
|
1596
|
+
const syntheticItems = inferredFlowsResult.flows.map((f) => ({
|
|
1597
|
+
id: f.id,
|
|
1598
|
+
name: f.name,
|
|
1599
|
+
total: f.steps.length,
|
|
1600
|
+
covered: f.steps.length,
|
|
1601
|
+
complete: true,
|
|
1602
|
+
percentage: 100,
|
|
1603
|
+
uncoveredSteps: [],
|
|
1604
|
+
}));
|
|
1605
|
+
const syntheticReport = {
|
|
1606
|
+
total: syntheticItems.length,
|
|
1607
|
+
complete: syntheticItems.length,
|
|
1608
|
+
percentage: 100,
|
|
1609
|
+
items: syntheticItems,
|
|
1610
|
+
};
|
|
1611
|
+
const flowResult = {
|
|
1612
|
+
type: 'integration',
|
|
1613
|
+
totalItems: syntheticReport.total,
|
|
1614
|
+
coveredItems: syntheticReport.complete,
|
|
1615
|
+
coveragePercent: syntheticReport.percentage,
|
|
1616
|
+
details: syntheticReport,
|
|
1617
|
+
};
|
|
1618
|
+
allCoverageResults.push(flowResult);
|
|
1619
|
+
console.log(` ${syntheticReport.complete}/${syntheticReport.total} multi-step flows detected and covered (100%)`);
|
|
1620
|
+
}
|
|
1621
|
+
if (allCoverageResults.length > 0) {
|
|
1622
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
1623
|
+
(0, reporting_1.generateMultiFormatReports)(allCoverageResults, ['json'], reportsDir, {}, observabilityInfo);
|
|
1624
|
+
console.log(`\nReports written to: ${reportsDir}`);
|
|
1462
1625
|
}
|
|
1463
1626
|
// ── 5. Emit warnings ───────────────────────────────────────────────────
|
|
1464
1627
|
for (const w of warnings) {
|
|
@@ -1477,7 +1640,7 @@ program
|
|
|
1477
1640
|
if (options['dashboard']) {
|
|
1478
1641
|
(0, serveDashboard_1.serveDashboard)({
|
|
1479
1642
|
reportsDir: reportsDir,
|
|
1480
|
-
port: (
|
|
1643
|
+
port: (_q = options['port']) !== null && _q !== void 0 ? _q : 4000,
|
|
1481
1644
|
open: Boolean(options['open']),
|
|
1482
1645
|
});
|
|
1483
1646
|
// Keep the process alive — the HTTP server holds the event loop open
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integrationCoverage.d.ts","sourceRoot":"","sources":["../../src/integrationCoverage.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,CAAC;IACtB,kGAAkG;IAClG,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,wEAAwE;IACxE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"integrationCoverage.d.ts","sourceRoot":"","sources":["../../src/integrationCoverage.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,CAAC;IACtB,kGAAkG;IAClG,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,wEAAwE;IACxE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAID;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE,CA4B1E;AAkFD;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,eAAe,EAAE,EACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,EAAE,CAAC,CAqDzB;AAID;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,YAAY,EAAE,GACxB,yBAAyB,CAQ3B;AAID;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,yBAAyB,EACjC,UAAU,EAAE,MAAM,GACjB,IAAI,CAgIN"}
|
|
@@ -64,7 +64,19 @@ function parseIntegrationFlows(flowsPath) {
|
|
|
64
64
|
!Array.isArray(parsed.flows)) {
|
|
65
65
|
throw new Error(`Invalid integration flows file: expected a top-level "flows" array in ${flowsPath}`);
|
|
66
66
|
}
|
|
67
|
-
return parsed.flows
|
|
67
|
+
return parsed.flows.map((flow) => {
|
|
68
|
+
var _a;
|
|
69
|
+
return ({
|
|
70
|
+
...flow,
|
|
71
|
+
steps: ((_a = flow.steps) !== null && _a !== void 0 ? _a : []).map((step) => {
|
|
72
|
+
var _a;
|
|
73
|
+
return ({
|
|
74
|
+
...step,
|
|
75
|
+
keywords: (_a = step.keywords) !== null && _a !== void 0 ? _a : [],
|
|
76
|
+
});
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
68
80
|
}
|
|
69
81
|
/**
|
|
70
82
|
* Extract all test/it declarations from a file, together with their
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serveDashboard.d.ts","sourceRoot":"","sources":["../../src/serveDashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAwD7B,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"serveDashboard.d.ts","sourceRoot":"","sources":["../../src/serveDashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAwD7B,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAAC,MAAM,CA6F/E"}
|
|
@@ -165,6 +165,17 @@ function serveDashboard(options = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
});
|
|
168
|
+
server.on('error', (err) => {
|
|
169
|
+
if (err.code === 'EADDRINUSE') {
|
|
170
|
+
console.error(`\n[ERROR] Port ${port} is already in use.`);
|
|
171
|
+
console.error(` Kill the existing process or use --port <number> to choose a different port.`);
|
|
172
|
+
console.error(` Example: api-tests-coverage analyze --dashboard --port 4001 --open`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.error(`\n[ERROR] Dashboard server failed to start: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
|
168
179
|
server.listen(port, () => {
|
|
169
180
|
const serverUrl = `http://localhost:${port}`;
|
|
170
181
|
console.log(`\n=== API Coverage Dashboard ===`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api-tests-coverage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "CLI and library to measure how thoroughly your test suite exercises your API surface area",
|
|
5
5
|
"main": "dist/src/lib/index.js",
|
|
6
6
|
"types": "dist/src/lib/index.d.ts",
|
|
@@ -64,7 +64,9 @@
|
|
|
64
64
|
"js-yaml": "^4.1.1",
|
|
65
65
|
"openapi-types": "^12.1.3",
|
|
66
66
|
"pino": "^10.3.1",
|
|
67
|
-
"prom-client": "^15.1.3"
|
|
67
|
+
"prom-client": "^15.1.3"
|
|
68
|
+
},
|
|
69
|
+
"optionalDependencies": {
|
|
68
70
|
"tree-sitter": "^0.25.0",
|
|
69
71
|
"tree-sitter-java": "^0.23.5",
|
|
70
72
|
"tree-sitter-python": "^0.25.0",
|