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.
@@ -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,CAuBpE;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"}
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
- console.log('\nNo business-rules.yaml file detected.');
1383
- console.log('Business rules will be inferred automatically.');
1384
- const rulesResult = (0, businessRuleInference_1.inferBusinessRules)(artifacts.serviceFiles, warnings);
1385
- const rulesPath = (0, businessRuleInference_1.writeInferredBusinessRules)(rulesResult, reportsDir);
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 = rulesResult.rules.map((r) => [
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
- console.log('\nNo integration-flows.yaml file detected.');
1407
- console.log('Integration flows will be inferred automatically.');
1408
- const flowsResult = (0, integrationFlowInference_1.inferIntegrationFlows)(artifacts.testFiles, warnings);
1409
- const flowsPath = (0, integrationFlowInference_1.writeInferredIntegrationFlows)(flowsResult, reportsDir);
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 = flowsResult.flows.map((f) => [
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. Endpoint coverage analysis → coverage-summary.json ─────────────
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
- if (allCoverageResults.length > 0) {
1455
- const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
1456
- (0, reporting_1.generateMultiFormatReports)(allCoverageResults, ['json'], reportsDir, {}, observabilityInfo);
1457
- console.log(`\nReports written to: ${reportsDir}`);
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: (_l = options['port']) !== null && _l !== void 0 ? _l : 4000,
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,CAsB1E;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"}
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,CAkF/E"}
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",
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",