api-tests-coverage 1.0.6 → 1.0.7

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.
Files changed (2) hide show
  1. package/dist/src/index.js +49 -24
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -1427,10 +1427,34 @@ program
1427
1427
  // ── 4. Full coverage analysis → coverage-summary.json ─────────────────
1428
1428
  const allCoverageResults = [];
1429
1429
  const fsMod = require('fs');
1430
+ // When test files are explicitly discovered, use them directly.
1431
+ // Otherwise fall back to a test-file-pattern glob (avoids scanning node_modules
1432
+ // or the entire project tree, which can hang on large repositories).
1430
1433
  const testsGlob = artifacts.testFiles.length > 0
1431
1434
  ? '{' + artifacts.testFiles.join(',') + '}'
1432
- : path.join(rootDir, '**', '*');
1435
+ : path.join(rootDir, '**', '*.{test,spec}.{js,ts,jsx,tsx,mjs,cjs,py,rb}');
1433
1436
  const detectedLanguages = artifacts.languages;
1437
+ // Pre-compute test entries once from the discovered test files.
1438
+ // These are reused for endpoint, error, and business-rule coverage matching
1439
+ // (avoids multiple glob expansions which can hang on large projects).
1440
+ const TEST_DECL_RE = /\b(?:test|it)\s*\(\s*(['"`])([\s\S]*?)\1/g;
1441
+ const testEntries = artifacts.testFiles.flatMap((tf) => {
1442
+ let content = '';
1443
+ try {
1444
+ content = fsMod.readFileSync(tf, 'utf-8');
1445
+ }
1446
+ catch {
1447
+ return [];
1448
+ }
1449
+ const descriptions = [];
1450
+ const contentLower = content.toLowerCase();
1451
+ let m;
1452
+ TEST_DECL_RE.lastIndex = 0;
1453
+ while ((m = TEST_DECL_RE.exec(content)) !== null) {
1454
+ descriptions.push(m[2].toLowerCase());
1455
+ }
1456
+ return [{ file: tf, contentLower, descriptions }];
1457
+ });
1434
1458
  if (artifacts.specs.length > 0) {
1435
1459
  for (const specPath of artifacts.specs) {
1436
1460
  // ── 4a. Endpoint coverage ───────────────────────────────────────────
@@ -1514,27 +1538,6 @@ program
1514
1538
  console.log(` Routes detected in service code: ${routeResult.routes.length}`);
1515
1539
  console.log(` Written to: ${routesPath}`);
1516
1540
  console.log(`\nAnalyzing endpoint coverage (from inferred routes)...`);
1517
- // Read test file contents for matching
1518
- const testContents = artifacts.testFiles.map((tf) => {
1519
- try {
1520
- return { file: tf, content: fsMod.readFileSync(tf, 'utf-8') };
1521
- }
1522
- catch {
1523
- return { file: tf, content: '' };
1524
- }
1525
- });
1526
- // Extract test descriptions for fine-grained matching
1527
- const TEST_DECL_PATTERN = /\b(?:test|it)\s*\(\s*(['"`])([\s\S]*?)\1/g;
1528
- const testEntries = testContents.map(({ file, content }) => {
1529
- const descriptions = [];
1530
- const contentLower = content.toLowerCase();
1531
- let m;
1532
- TEST_DECL_PATTERN.lastIndex = 0;
1533
- while ((m = TEST_DECL_PATTERN.exec(content)) !== null) {
1534
- descriptions.push(m[2].toLowerCase());
1535
- }
1536
- return { file, contentLower, descriptions };
1537
- });
1538
1541
  const endpointItems = routeResult.routes.map((route) => {
1539
1542
  var _a;
1540
1543
  const pathSegments = route.path.split('/').filter((s) => s.length > 1 && !s.startsWith(':'));
@@ -1684,7 +1687,9 @@ program
1684
1687
  }
1685
1688
  }
1686
1689
  else if (inferredRulesResult && inferredRulesResult.rules.length > 0) {
1687
- // Auto-inferred: use specificKeywords from rule for accurate test matching
1690
+ // Auto-inferred: use specificKeywords from rule for accurate test matching.
1691
+ // Reuse the testEntries already computed for endpoint coverage — avoids
1692
+ // a second glob expansion (which can be slow/hang on large projects).
1688
1693
  try {
1689
1694
  console.log(`\nAnalyzing business rules coverage (from inferred rules)...`);
1690
1695
  const syntheticRules = inferredRulesResult.rules.map((r) => {
@@ -1715,7 +1720,27 @@ program
1715
1720
  scenarios: [],
1716
1721
  };
1717
1722
  });
1718
- const bizCoverages = await (0, businessCoverage_1.analyzeBusinessCoverage)(syntheticRules, testsGlob);
1723
+ // Match rules against the testEntries already built for endpoint coverage.
1724
+ // This avoids a second glob expansion and is safe when there are no test files.
1725
+ const bizCoverages = syntheticRules.map((rule) => {
1726
+ const kwsLower = rule.keywords.map((k) => k.toLowerCase());
1727
+ const matchedDescs = [];
1728
+ const matchedFileSet = new Set();
1729
+ for (const { file, descriptions } of testEntries) {
1730
+ const hitting = descriptions.filter((desc) => kwsLower.length > 0 && kwsLower.some((kw) => desc.includes(kw)));
1731
+ if (hitting.length > 0) {
1732
+ matchedDescs.push(...hitting);
1733
+ matchedFileSet.add(file);
1734
+ }
1735
+ }
1736
+ return {
1737
+ rule,
1738
+ covered: matchedDescs.length > 0,
1739
+ testFiles: [...matchedFileSet],
1740
+ matchedTests: matchedDescs,
1741
+ scenarios: [],
1742
+ };
1743
+ });
1719
1744
  const bizReport = (0, businessCoverage_1.buildBusinessCoverageReport)(bizCoverages);
1720
1745
  const bizResult = {
1721
1746
  type: 'business',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api-tests-coverage",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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",