From 2ea421990fb60b9ddd0f247a0bdf494bf942afe0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:42:28 +0000 Subject: [PATCH 1/6] Initial plan From ea9bf4545a26917b67a6503b8ff995c585bbd9d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:44:56 +0000 Subject: [PATCH 2/6] Initial plan for adding --graph-report feature Co-authored-by: dreamquality <130073078+dreamquality@users.noreply.github.com> --- auto-detect-newman.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto-detect-newman.html b/auto-detect-newman.html index d4d7d0e..42c36e0 100644 --- a/auto-detect-newman.html +++ b/auto-detect-newman.html @@ -384,7 +384,7 @@
Timestamp: 9/18/2025, 2:32:58 PM
+Timestamp: 9/21/2025, 12:44:21 PM
API Spec: Test API
Postman Collection: Test Newman Collection
From 3f611e0da132a77179fbe18b030d81907a4ec526 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:50:03 +0000 Subject: [PATCH 3/6] Add --graph-report CLI flag with D3.js graph visualization Co-authored-by: dreamquality <130073078+dreamquality@users.noreply.github.com> --- auto-detect-newman.html | 2 +- cli.js | 25 ++- lib/report.js | 414 +++++++++++++++++++++++++++++++++++++- test/graph-report.test.js | 70 +++++++ test/report.test.js | 40 +++- 5 files changed, 545 insertions(+), 6 deletions(-) create mode 100644 test/graph-report.test.js diff --git a/auto-detect-newman.html b/auto-detect-newman.html index 42c36e0..dde1dd8 100644 --- a/auto-detect-newman.html +++ b/auto-detect-newman.html @@ -384,7 +384,7 @@Timestamp: 9/21/2025, 12:44:21 PM
+Timestamp: 9/21/2025, 12:48:06 PM
API Spec: Test API
Postman Collection: Test Newman Collection
diff --git a/cli.js b/cli.js index f877d4a..5300810 100755 --- a/cli.js +++ b/cli.js @@ -9,7 +9,7 @@ const { loadAndParseSpec, extractOperationsFromSpec } = require("./lib/swagger") const { loadPostmanCollection, extractRequestsFromPostman } = require("./lib/postman"); const { loadNewmanReport, extractRequestsFromNewman } = require("./lib/newman"); const { matchOperationsDetailed } = require("./lib/match"); -const { generateHtmlReport } = require("./lib/report"); +const { generateHtmlReport, generateGraphHtmlReport } = require("./lib/report"); const { loadExcelSpec } = require("./lib/excel"); const { loadAndParseProto, extractOperationsFromProto, isProtoFile } = require("./lib/grpc"); const { loadAndParseGraphQL, extractOperationsFromGraphQL, isGraphQLFile } = require("./lib/graphql"); @@ -29,9 +29,10 @@ program .option("--strict-body", "Enable strict validation of requestBody (JSON)") .option("--outputTimestamp: ${timestamp}
+API Spec${apiCount > 1 ? 's' : ''}: ${specName}
+ ${apiCount > 1 ? `Individual APIs: ${apiNames.join(', ')}
` : ''} +Postman Collection: ${postmanCollectionName}
+Overall Coverage: ${coverage.toFixed(2)}%
+Timestamp: 9/21/2025, 12:48:06 PM
+Timestamp: 9/21/2025, 12:50:58 PM
API Spec: Test API
Postman Collection: Test Newman Collection
From 2581ebfe8e72d50695c59e70daf55e5fa8c853ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Sep 2025 19:36:19 +0000 Subject: [PATCH 5/6] Improve graph schema to expandable tree structure with hierarchical organization Co-authored-by: dreamquality <130073078+dreamquality@users.noreply.github.com> --- auto-detect-newman.html | 2 +- lib/report.js | 547 +++++++++++++++++++++++++------------- test/graph-report.test.js | 6 +- test/report.test.js | 2 +- 4 files changed, 367 insertions(+), 190 deletions(-) diff --git a/auto-detect-newman.html b/auto-detect-newman.html index 925dcfc..6b3008c 100644 --- a/auto-detect-newman.html +++ b/auto-detect-newman.html @@ -384,7 +384,7 @@Timestamp: 9/21/2025, 12:50:58 PM
+Timestamp: 9/21/2025, 7:35:43 PM
API Spec: Test API
Postman Collection: Test Newman Collection
diff --git a/lib/report.js b/lib/report.js index 17b6344..335b8fb 100644 --- a/lib/report.js +++ b/lib/report.js @@ -1070,63 +1070,106 @@ function generateHtmlReport({ coverage, coverageItems, meta }) { } /** - * generateGraphHtmlReport - generates a graph visualization of API coverage - * using D3.js to show nodes (endpoints) and edges (dependencies) with color coding + * generateGraphHtmlReport - generates an expandable tree visualization of API coverage + * using D3.js to show hierarchical structure with expandable nodes and color coding */ function generateGraphHtmlReport({ coverage, coverageItems, meta }) { const { timestamp, specName, postmanCollectionName, apiCount = 1, apiNames = [] } = meta; - // Prepare graph data structure - const nodes = []; - const links = []; - const nodeMap = new Map(); - let nodeId = 0; - - // Create nodes for each operation - coverageItems.forEach((item, index) => { - const id = `node-${nodeId++}`; - const status = item.unmatched ? 'not-covered' : - (item.matchedRequests && item.matchedRequests.length > 0 ? 'covered' : 'partial'); - - const node = { - id, - name: item.name || `${item.method} ${item.path}`, - method: item.method, - path: item.path, - status, - apiName: item.apiName || '', - coverage: item.unmatched ? 0 : 100, - group: item.tags && item.tags.length > 0 ? item.tags[0] : 'default' + // Create hierarchical tree structure + function buildHierarchy(items) { + const root = { + name: 'API Coverage', + children: [], + type: 'root', + status: 'root' }; - - nodes.push(node); - nodeMap.set(`${item.method}-${item.path}`, node); - }); - - // Create edges based on path relationships (simple heuristic) - for (let i = 0; i < nodes.length; i++) { - for (let j = i + 1; j < nodes.length; j++) { - const nodeA = nodes[i]; - const nodeB = nodes[j]; - - // Create links between nodes that share similar paths or are in the same group - if (nodeA.group === nodeB.group && nodeA.group !== 'default') { - links.push({ - source: nodeA.id, - target: nodeB.id, - type: 'same-group' + + // Group by API name first (if multiple APIs) + const apiGroups = {}; + items.forEach(item => { + const apiName = item.apiName || 'Main API'; + if (!apiGroups[apiName]) { + apiGroups[apiName] = []; + } + apiGroups[apiName].push(item); + }); + + Object.keys(apiGroups).forEach(apiName => { + const apiNode = { + name: apiName, + children: [], + type: 'api', + status: 'group' + }; + + // Group by tags within each API + const tagGroups = {}; + apiGroups[apiName].forEach(item => { + const tag = (item.tags && item.tags.length > 0) ? item.tags[0] : 'General'; + if (!tagGroups[tag]) { + tagGroups[tag] = []; + } + tagGroups[tag].push(item); + }); + + Object.keys(tagGroups).forEach(tag => { + const tagNode = { + name: tag, + children: [], + type: 'tag', + status: 'group' + }; + + // Group by base path within each tag + const pathGroups = {}; + tagGroups[tag].forEach(item => { + const basePath = item.path.split('/').slice(0, 3).join('/') || '/'; + if (!pathGroups[basePath]) { + pathGroups[basePath] = []; + } + pathGroups[basePath].push(item); }); - } else if (nodeA.path.includes(nodeB.path) || nodeB.path.includes(nodeA.path)) { - links.push({ - source: nodeA.id, - target: nodeB.id, - type: 'path-related' + + Object.keys(pathGroups).forEach(basePath => { + const pathNode = { + name: basePath, + children: [], + type: 'path', + status: 'group' + }; + + // Add individual endpoints + pathGroups[basePath].forEach(item => { + const status = item.unmatched ? 'not-covered' : + (item.matchedRequests && item.matchedRequests.length > 0 ? 'covered' : 'partial'); + + pathNode.children.push({ + name: `${item.method} ${item.path}`, + method: item.method, + path: item.path, + status, + type: 'endpoint', + coverage: item.unmatched ? 0 : 100, + matchedRequests: item.matchedRequests || [], + operationName: item.name + }); + }); + + tagNode.children.push(pathNode); }); - } - } + + apiNode.children.push(tagNode); + }); + + root.children.push(apiNode); + }); + + return root; } - const graphDataJson = JSON.stringify({ nodes, links }); + const treeData = buildHierarchy(coverageItems); + const treeDataJson = JSON.stringify(treeData); const html = ` @@ -1134,7 +1177,7 @@ function generateGraphHtmlReport({ coverage, coverageItems, meta }) { -Timestamp: 9/21/2025, 7:43:34 PM
+API Spec: Sample API for Newman Demo
+ +Postman Collection: Comprehensive Test Collection
+Overall Coverage: 16.67%
+Timestamp: ${timestamp}
-API Spec${apiCount > 1 ? 's' : ''}: ${specName}
- ${apiCount > 1 ? `Individual APIs: ${apiNames.join(', ')}
` : ''} -Postman Collection: ${postmanCollectionName}
-Overall Coverage: ${coverage.toFixed(2)}%
+