@freshworks/shiftleft-tools 1.1.8

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 (106) hide show
  1. package/README.md +351 -0
  2. package/bin/shiftleft.js +95 -0
  3. package/package.json +57 -0
  4. package/src/commands/doctor.js +208 -0
  5. package/src/commands/init-postman.js +298 -0
  6. package/src/commands/init-rules.js +78 -0
  7. package/src/commands/link.js +172 -0
  8. package/src/commands/protect.js +61 -0
  9. package/src/commands/run-tests.js +182 -0
  10. package/src/commands/setup-pipeline.js +209 -0
  11. package/src/commands/update.js +203 -0
  12. package/src/index.js +4 -0
  13. package/src/utils/copy-tree.js +98 -0
  14. package/src/utils/gitignore.js +26 -0
  15. package/src/utils/logger.js +9 -0
  16. package/src/utils/manifest.js +145 -0
  17. package/src/utils/stack.js +80 -0
  18. package/src/utils/template.js +135 -0
  19. package/templates/AGENTS.md +109 -0
  20. package/templates/CLAUDE.md +3 -0
  21. package/templates/jenkins/Jenkinsfile-java.groovy +432 -0
  22. package/templates/jenkins/Jenkinsfile-node.groovy +450 -0
  23. package/templates/postman/.husky/pre-commit +19 -0
  24. package/templates/postman/.prettierrc.json +5 -0
  25. package/templates/postman/README.md.ejs +147 -0
  26. package/templates/postman/collections/01-core.json.ejs +91 -0
  27. package/templates/postman/config/local.json.ejs +12 -0
  28. package/templates/postman/config/staging.json.ejs +26 -0
  29. package/templates/postman/environments/local.postman_environment.json.ejs +31 -0
  30. package/templates/postman/environments/staging.postman_environment.json.ejs +31 -0
  31. package/templates/postman/gitignore +16 -0
  32. package/templates/postman/npmrc +31 -0
  33. package/templates/postman/package.json.ejs +66 -0
  34. package/templates/postman/run-all-shim.sh +16 -0
  35. package/templates/postman/scripts/auth/generate-jwt.sh +113 -0
  36. package/templates/postman/scripts/auth/get-issuer-secret.sh +140 -0
  37. package/templates/postman/scripts/infra/start-mocks.sh +138 -0
  38. package/templates/postman/scripts/infra/stop-mocks.sh +43 -0
  39. package/templates/postman/scripts/lib/api_coverage.py +1122 -0
  40. package/templates/postman/scripts/lib/cleanup-reports.sh +101 -0
  41. package/templates/postman/scripts/lib/cleanup-stryker.sh +44 -0
  42. package/templates/postman/scripts/lib/report_combined.py +527 -0
  43. package/templates/postman/scripts/lib/report_consolidated.py +363 -0
  44. package/templates/postman/scripts/lib/report_generator.py +121 -0
  45. package/templates/postman/scripts/lib/report_migration.py +156 -0
  46. package/templates/postman/scripts/lib/report_mutation.py +110 -0
  47. package/templates/postman/scripts/lib/report_unit.py +353 -0
  48. package/templates/postman/scripts/lib/report_utils.py +973 -0
  49. package/templates/postman/scripts/report-generators/generate-consolidated-report.sh +445 -0
  50. package/templates/postman/scripts/report-generators/java-api-coverage-matrix.sh +257 -0
  51. package/templates/postman/scripts/report-generators/mutation-report.sh +672 -0
  52. package/templates/postman/scripts/report-generators/node-api-coverage-matrix.sh +167 -0
  53. package/templates/postman/scripts/report-generators/stage-report-artifacts.sh +27 -0
  54. package/templates/postman/scripts/run-all.sh +452 -0
  55. package/templates/postman/scripts/runners/run-mutation-tests.sh +113 -0
  56. package/templates/postman/scripts/runners/run-tests-local.sh +936 -0
  57. package/templates/postman/scripts/runners/run-tests-staging.sh +741 -0
  58. package/templates/postman-node/README.md.ejs +26 -0
  59. package/templates/postman-node/collections/crud/01-bootstrap.json.ejs +34 -0
  60. package/templates/postman-node/config/local.json.ejs +46 -0
  61. package/templates/postman-node/config/staging.json.ejs +31 -0
  62. package/templates/postman-node/local.test.env.ejs +3 -0
  63. package/templates/postman-node/mocks/external.js +14 -0
  64. package/templates/postman-node/package.json.ejs +39 -0
  65. package/templates/postman-node/requirements.txt +1 -0
  66. package/templates/postman-node/scripts/database/cleanup-mysql.sh +12 -0
  67. package/templates/postman-node/scripts/database/run-migrations.js +29 -0
  68. package/templates/postman-node/scripts/database/start-mysql.sh +34 -0
  69. package/templates/postman-node/scripts/database/wait-for-mysql.sh +36 -0
  70. package/templates/postman-node/scripts/lib/api_coverage_node.py +1137 -0
  71. package/templates/postman-node/scripts/lib/fetch-jwt.sh +86 -0
  72. package/templates/postman-node/scripts/lib/run-newman.sh +104 -0
  73. package/templates/postman-node/scripts/lib/setup-database.sh +55 -0
  74. package/templates/postman-node/scripts/lib/start-app.sh +48 -0
  75. package/templates/postman-node/scripts/lib/utils.sh +114 -0
  76. package/templates/postman-node/scripts/report-generators/stage-report-artifacts.sh +26 -0
  77. package/templates/postman-node/scripts/run-all.sh +303 -0
  78. package/templates/postman-node/scripts/runners/run-tests.sh +123 -0
  79. package/templates/postman-node/scripts/setup-mocks.js.ejs +29 -0
  80. package/templates/postman-node/stryker.config.js.ejs +51 -0
  81. package/templates/rules/local-test-setup.mdc +420 -0
  82. package/templates/rules/testing-node.mdc +66 -0
  83. package/templates/rules/testing.mdc +248 -0
  84. package/templates/skills/_shared/postman-standards.md +380 -0
  85. package/templates/skills/enhance-test-pipeline/SKILL-java.md +483 -0
  86. package/templates/skills/enhance-test-pipeline/SKILL-node.md +431 -0
  87. package/templates/skills/enhance-test-pipeline/SKILL.md +9 -0
  88. package/templates/skills/review-test-suite/SKILL-java.md +137 -0
  89. package/templates/skills/review-test-suite/SKILL-node.md +78 -0
  90. package/templates/skills/review-test-suite/SKILL.md +9 -0
  91. package/templates/skills/run-test-suite/SKILL-java.md +186 -0
  92. package/templates/skills/run-test-suite/SKILL-node.md +191 -0
  93. package/templates/skills/run-test-suite/SKILL.md +9 -0
  94. package/templates/skills/setup-api-tests/SKILL-java.md +1094 -0
  95. package/templates/skills/setup-api-tests/SKILL-node.md +141 -0
  96. package/templates/skills/setup-api-tests/SKILL.md +9 -0
  97. package/templates/skills/setup-mutation-tests/SKILL-java.md +303 -0
  98. package/templates/skills/setup-mutation-tests/SKILL-node.md +408 -0
  99. package/templates/skills/setup-mutation-tests/SKILL.md +9 -0
  100. package/templates/skills/setup-test-pipeline/SKILL-java.md +454 -0
  101. package/templates/skills/setup-test-pipeline/SKILL-node.md +318 -0
  102. package/templates/skills/setup-test-pipeline/SKILL.md +9 -0
  103. package/templates/skills/write-api-tests/SKILL-java.md +115 -0
  104. package/templates/skills/write-api-tests/SKILL-node.md +83 -0
  105. package/templates/skills/write-api-tests/SKILL.md +9 -0
  106. package/templates/stryker.config.js +50 -0
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/env bash
2
+ # =============================================================================
3
+ # generate-consolidated-report.sh
4
+ # Generates consolidated JSON and HTML reports from individual Newman JSON outputs
5
+ #
6
+ # Usage: ./generate-consolidated-report.sh <ENV> <TIMESTAMP> <REPORTS_DIR> <PASSED> <FAILED> <RESULT>
7
+ #
8
+ # This script can be used by any test runner that generates multiple collections
9
+ # =============================================================================
10
+
11
+ set -euo pipefail
12
+
13
+ # Arguments
14
+ ENV="${1:-local}"
15
+ TIMESTAMP="${2:-$(date '+%Y%m%d-%H%M%S')}"
16
+ REPORTS_DIR="${3:-./reports}"
17
+ PASSED_COLLECTIONS="${4:-0}"
18
+ FAILED_COLLECTIONS="${5:-0}"
19
+ TEST_RESULT="${6:-0}"
20
+
21
+ # Display name for the report. Honor REPORT_LOGO from the caller (run-all.sh
22
+ # exports it); otherwise derive the repo/service name from this script's path so
23
+ # standalone runners (e.g. a protected run-tests-staging.sh) still get it right.
24
+ _GCR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+ REPORT_LOGO="${REPORT_LOGO:-$(basename "$(cd "$_GCR_DIR/../../.." && pwd)")}"
26
+
27
+ # Colors
28
+ GREEN='\033[0;32m'
29
+ RED='\033[0;31m'
30
+ BLUE='\033[0;34m'
31
+ YELLOW='\033[1;33m'
32
+ BOLD='\033[1m'
33
+ NC='\033[0m'
34
+
35
+ echo ""
36
+ echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
37
+ echo -e "${BLUE}║ Generating Consolidated Report ║${NC}"
38
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
39
+ echo ""
40
+
41
+ # Find JSON files for this timestamp
42
+ JSON_DIR="$REPORTS_DIR/json"
43
+
44
+ # Check if JSON directory exists and has files
45
+ if [ ! -d "$JSON_DIR" ]; then
46
+ echo -e "${YELLOW}⚠ No JSON reports directory found: ${JSON_DIR}${NC}"
47
+ exit 0
48
+ fi
49
+
50
+ # Find JSON files matching the timestamp (including product subdirectories)
51
+ mapfile -t JSON_FILES < <(find "$JSON_DIR" -name "*-${TIMESTAMP}.json" -type f 2>/dev/null | sort)
52
+
53
+ if [ ${#JSON_FILES[@]} -eq 0 ]; then
54
+ echo -e "${YELLOW}⚠ No JSON reports found for timestamp: ${TIMESTAMP}${NC}"
55
+ exit 0
56
+ fi
57
+
58
+ echo -e "${BLUE}Found ${#JSON_FILES[@]} JSON report(s)${NC}"
59
+
60
+ # Calculate consolidated stats using jq
61
+ TOTAL_ASSERTIONS=$(jq -s '[.[].run.stats.assertions.total // 0] | add' "${JSON_FILES[@]}" 2>/dev/null || echo "0")
62
+ FAILED_ASSERTIONS=$(jq -s '[.[].run.stats.assertions.failed // 0] | add' "${JSON_FILES[@]}" 2>/dev/null || echo "0")
63
+ TOTAL_REQUESTS=$(jq -s '[.[].run.stats.requests.total // 0] | add' "${JSON_FILES[@]}" 2>/dev/null || echo "0")
64
+ FAILED_REQUESTS=$(jq -s '[.[].run.stats.requests.failed // 0] | add' "${JSON_FILES[@]}" 2>/dev/null || echo "0")
65
+ # Calculate duration as (completed - started) for each run, then sum
66
+ TOTAL_DURATION=$(jq -s '[.[].run.timings | (.completed // 0) - (.started // 0)] | add' "${JSON_FILES[@]}" 2>/dev/null || echo "0")
67
+
68
+ PASSED_ASSERTIONS=$((TOTAL_ASSERTIONS - FAILED_ASSERTIONS))
69
+ DURATION_SEC=$(echo "scale=2; $TOTAL_DURATION / 1000" | bc 2>/dev/null || echo "0")
70
+ TOTAL_COLLECTIONS=$((PASSED_COLLECTIONS + FAILED_COLLECTIONS))
71
+
72
+ # Print summary to console
73
+ echo ""
74
+ echo -e " ${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
75
+ echo -e " ${BOLD}CONSOLIDATED RESULTS - ${ENV}${NC}"
76
+ echo -e " ${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
77
+ echo ""
78
+ echo -e " Collections: ${GREEN}${PASSED_COLLECTIONS} passed${NC}, ${RED}${FAILED_COLLECTIONS} failed${NC} (${TOTAL_COLLECTIONS} total)"
79
+ echo -e " Assertions: ${GREEN}${PASSED_ASSERTIONS} passed${NC}, ${RED}${FAILED_ASSERTIONS} failed${NC} (${TOTAL_ASSERTIONS} total)"
80
+ echo -e " Requests: ${TOTAL_REQUESTS} total, ${FAILED_REQUESTS} failed"
81
+ echo -e " Duration: ${DURATION_SEC}s"
82
+ echo ""
83
+
84
+ # Generate consolidated JSON
85
+ CONSOLIDATED_JSON="$REPORTS_DIR/consolidated-${ENV}-${TIMESTAMP}.json"
86
+ STATUS=$([ "$TEST_RESULT" -eq 0 ] && echo 'PASSED' || echo 'FAILED')
87
+
88
+ jq -s '{
89
+ info: {
90
+ name: "'"${REPORT_LOGO}"' Consolidated Test Report",
91
+ timestamp: "'"${TIMESTAMP}"'",
92
+ environment: "'"${ENV}"'"
93
+ },
94
+ summary: {
95
+ status: "'"${STATUS}"'",
96
+ collections: {
97
+ total: '"${TOTAL_COLLECTIONS}"',
98
+ passed: '"${PASSED_COLLECTIONS}"',
99
+ failed: '"${FAILED_COLLECTIONS}"'
100
+ },
101
+ assertions: {
102
+ total: '"${TOTAL_ASSERTIONS}"',
103
+ passed: '"${PASSED_ASSERTIONS}"',
104
+ failed: '"${FAILED_ASSERTIONS}"'
105
+ },
106
+ requests: {
107
+ total: '"${TOTAL_REQUESTS}"',
108
+ failed: '"${FAILED_REQUESTS}"'
109
+ },
110
+ duration_ms: '"${TOTAL_DURATION}"'
111
+ },
112
+ runs: [.[]]
113
+ }' "${JSON_FILES[@]}" > "$CONSOLIDATED_JSON"
114
+
115
+ echo -e "${GREEN}✓ JSON: ${CONSOLIDATED_JSON}${NC}"
116
+
117
+ # Build collection links as JSON for Python
118
+ COLLECTION_LINKS_JSON="["
119
+ first=true
120
+ for json_file in "${JSON_FILES[@]}"; do
121
+ col_name=$(basename "$json_file" "-${TIMESTAMP}.json")
122
+ # Extract collection name - handle different naming patterns
123
+ if [[ "$col_name" == staging-* ]]; then
124
+ html_file="${col_name}-${TIMESTAMP}.html"
125
+ display_name=$(echo "$col_name" | sed "s/^staging-//" | sed 's/-/ /g' | sed 's/^[0-9]* //')
126
+ elif [[ "$col_name" == test-* ]]; then
127
+ html_file="${col_name}-${TIMESTAMP}.html"
128
+ display_name=$(echo "$col_name" | sed "s/^test-${ENV}-//" | sed 's/-/ /g' | sed 's/^[0-9]* //')
129
+ else
130
+ html_file="${col_name}-${TIMESTAMP}.html"
131
+ display_name=$(echo "$col_name" | sed 's/-/ /g' | sed 's/^[0-9]* //')
132
+ fi
133
+ if [ "$first" = true ]; then
134
+ first=false
135
+ else
136
+ COLLECTION_LINKS_JSON="${COLLECTION_LINKS_JSON},"
137
+ fi
138
+ COLLECTION_LINKS_JSON="${COLLECTION_LINKS_JSON}{\"name\":\"${display_name}\",\"file\":\"${html_file}\"}"
139
+ done
140
+ COLLECTION_LINKS_JSON="${COLLECTION_LINKS_JSON}]"
141
+
142
+ # Generate consolidated HTML using Python library
143
+ CONSOLIDATED_HTML="$REPORTS_DIR/consolidated-${ENV}-${TIMESTAMP}.html"
144
+ STATUS=$([ "$TEST_RESULT" -eq 0 ] && echo 'PASSED' || echo 'FAILED')
145
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
146
+
147
+ python3 "$SCRIPT_DIR/../lib/report_generator.py" consolidated "$CONSOLIDATED_HTML" \
148
+ --env "$ENV" \
149
+ --timestamp "$TIMESTAMP" \
150
+ --total-assertions "$TOTAL_ASSERTIONS" \
151
+ --passed-assertions "$PASSED_ASSERTIONS" \
152
+ --failed-assertions "$FAILED_ASSERTIONS" \
153
+ --total-requests "$TOTAL_REQUESTS" \
154
+ --duration "$DURATION_SEC" \
155
+ --status "$STATUS" \
156
+ --collection-links "$COLLECTION_LINKS_JSON"
157
+
158
+ # Skip inline HTML generation - now handled by Python
159
+ : << 'SKIP_INLINE_HTML'
160
+ STATUS_CLASS=$([ "$TEST_RESULT" -eq 0 ] && echo 'passed' || echo 'failed')
161
+ STATUS_TEXT=$([ "$TEST_RESULT" -eq 0 ] && echo '✓ ALL PASSED' || echo '✗ FAILURES')
162
+ FAILED_CLASS=$([ "$FAILED_ASSERTIONS" -gt 0 ] && echo 'err' || echo '')
163
+
164
+ cat > "$CONSOLIDATED_HTML" << EOF
165
+ <!DOCTYPE html>
166
+ <html lang="en">
167
+ <head>
168
+ <meta charset="UTF-8">
169
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
170
+ <title>Test Results - ${ENV} | ${REPORT_LOGO}</title>
171
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
172
+ <style>
173
+ :root {
174
+ --color-primary: #6366f1;
175
+ --color-primary-light: #818cf8;
176
+ --color-success: #22c55e;
177
+ --color-success-light: #86efac;
178
+ --color-error: #ef4444;
179
+ --color-error-light: #fca5a5;
180
+ --color-warning: #f59e0b;
181
+ --color-bg: #0f172a;
182
+ --color-surface: #1e293b;
183
+ --color-surface-hover: #334155;
184
+ --color-border: #334155;
185
+ --color-text: #f1f5f9;
186
+ --color-text-muted: #94a3b8;
187
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
188
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
189
+ --shadow-lg: 0 8px 24px rgba(0,0,0,0.5);
190
+ --radius: 16px;
191
+ --radius-sm: 10px;
192
+ }
193
+
194
+ * { box-sizing: border-box; margin: 0; padding: 0; }
195
+
196
+ body {
197
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
198
+ background: var(--color-bg);
199
+ color: var(--color-text);
200
+ min-height: 100vh;
201
+ padding: 2.5rem;
202
+ line-height: 1.6;
203
+ }
204
+
205
+ .container { max-width: 1000px; margin: 0 auto; }
206
+
207
+ /* Header */
208
+ header {
209
+ text-align: center;
210
+ margin-bottom: 3rem;
211
+ }
212
+
213
+ .logo {
214
+ font-size: 0.85rem;
215
+ font-weight: 600;
216
+ color: var(--color-primary-light);
217
+ text-transform: uppercase;
218
+ letter-spacing: 0.1em;
219
+ margin-bottom: 0.75rem;
220
+ }
221
+
222
+ h1 {
223
+ font-size: 2.5rem;
224
+ font-weight: 700;
225
+ color: var(--color-text);
226
+ margin-bottom: 0.5rem;
227
+ letter-spacing: -0.02em;
228
+ }
229
+
230
+ .subtitle {
231
+ color: var(--color-text-muted);
232
+ font-size: 0.95rem;
233
+ }
234
+
235
+ .status-badge {
236
+ display: inline-flex;
237
+ align-items: center;
238
+ gap: 0.5rem;
239
+ padding: 0.75rem 2rem;
240
+ border-radius: 50px;
241
+ font-weight: 600;
242
+ font-size: 1rem;
243
+ margin-top: 1.5rem;
244
+ box-shadow: var(--shadow-md);
245
+ transition: transform 0.2s, box-shadow 0.2s;
246
+ }
247
+
248
+ .status-badge:hover {
249
+ transform: translateY(-2px);
250
+ box-shadow: var(--shadow-lg);
251
+ }
252
+
253
+ .status-badge.passed {
254
+ background: linear-gradient(135deg, var(--color-success), #16a34a);
255
+ color: white;
256
+ }
257
+
258
+ .status-badge.failed {
259
+ background: linear-gradient(135deg, var(--color-error), #dc2626);
260
+ color: white;
261
+ }
262
+
263
+ /* Stats Grid */
264
+ .stats-grid {
265
+ display: grid;
266
+ grid-template-columns: repeat(4, 1fr);
267
+ gap: 1rem;
268
+ margin: 2.5rem 0;
269
+ }
270
+
271
+ .stat-card {
272
+ background: var(--color-surface);
273
+ border: 1px solid var(--color-border);
274
+ border-radius: var(--radius);
275
+ padding: 1.5rem;
276
+ text-align: center;
277
+ transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
278
+ }
279
+
280
+ .stat-card:hover {
281
+ transform: translateY(-4px);
282
+ box-shadow: var(--shadow-md);
283
+ border-color: var(--color-primary);
284
+ }
285
+
286
+ .stat-value {
287
+ font-size: 2.5rem;
288
+ font-weight: 700;
289
+ color: var(--color-primary-light);
290
+ line-height: 1;
291
+ margin-bottom: 0.5rem;
292
+ }
293
+
294
+ .stat-value.success { color: var(--color-success); }
295
+ .stat-value.error { color: var(--color-error); }
296
+
297
+ .stat-label {
298
+ color: var(--color-text-muted);
299
+ font-size: 0.75rem;
300
+ font-weight: 500;
301
+ text-transform: uppercase;
302
+ letter-spacing: 0.08em;
303
+ }
304
+
305
+ /* Collections Section */
306
+ .section {
307
+ margin-top: 2.5rem;
308
+ }
309
+
310
+ .section-header {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 0.75rem;
314
+ margin-bottom: 1.25rem;
315
+ }
316
+
317
+ .section-header h2 {
318
+ font-size: 1.25rem;
319
+ font-weight: 600;
320
+ color: var(--color-text);
321
+ }
322
+
323
+ .section-header .count {
324
+ background: var(--color-surface);
325
+ border: 1px solid var(--color-border);
326
+ padding: 0.25rem 0.75rem;
327
+ border-radius: 20px;
328
+ font-size: 0.8rem;
329
+ color: var(--color-text-muted);
330
+ }
331
+
332
+ .collection-list { list-style: none; }
333
+
334
+ .collection-item {
335
+ background: var(--color-surface);
336
+ border: 1px solid var(--color-border);
337
+ border-radius: var(--radius-sm);
338
+ padding: 1rem 1.25rem;
339
+ margin-bottom: 0.625rem;
340
+ display: flex;
341
+ justify-content: space-between;
342
+ align-items: center;
343
+ transition: all 0.2s;
344
+ }
345
+
346
+ .collection-item:hover {
347
+ background: var(--color-surface-hover);
348
+ border-color: var(--color-primary);
349
+ transform: translateX(4px);
350
+ }
351
+
352
+ .collection-name {
353
+ font-weight: 500;
354
+ color: var(--color-text);
355
+ }
356
+
357
+ .collection-link {
358
+ color: var(--color-primary-light);
359
+ text-decoration: none;
360
+ font-size: 0.875rem;
361
+ font-weight: 500;
362
+ display: inline-flex;
363
+ align-items: center;
364
+ gap: 0.375rem;
365
+ transition: color 0.2s;
366
+ }
367
+
368
+ .collection-link:hover {
369
+ color: var(--color-text);
370
+ }
371
+
372
+ /* Footer */
373
+ footer {
374
+ text-align: center;
375
+ margin-top: 3rem;
376
+ padding-top: 1.5rem;
377
+ border-top: 1px solid var(--color-border);
378
+ color: var(--color-text-muted);
379
+ font-size: 0.85rem;
380
+ }
381
+
382
+ footer span {
383
+ opacity: 0.5;
384
+ margin: 0 0.5rem;
385
+ }
386
+
387
+ @media (max-width: 768px) {
388
+ body { padding: 1.5rem; }
389
+ h1 { font-size: 1.75rem; }
390
+ .stats-grid { grid-template-columns: repeat(2, 1fr); }
391
+ .stat-value { font-size: 2rem; }
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+ <div class="container">
397
+ <header>
398
+ <div class="logo">${REPORT_LOGO}</div>
399
+ <h1>API Test Results</h1>
400
+ <p class="subtitle">${ENV} environment • ${TIMESTAMP}</p>
401
+ <div class="status-badge ${STATUS_CLASS}">${STATUS_TEXT}</div>
402
+ </header>
403
+
404
+ <div class="stats-grid">
405
+ <div class="stat-card">
406
+ <div class="stat-value">${TOTAL_ASSERTIONS}</div>
407
+ <div class="stat-label">Total Tests</div>
408
+ </div>
409
+ <div class="stat-card">
410
+ <div class="stat-value success">${PASSED_ASSERTIONS}</div>
411
+ <div class="stat-label">Passed</div>
412
+ </div>
413
+ <div class="stat-card">
414
+ <div class="stat-value ${FAILED_CLASS}">${FAILED_ASSERTIONS}</div>
415
+ <div class="stat-label">Failed</div>
416
+ </div>
417
+ <div class="stat-card">
418
+ <div class="stat-value">${TOTAL_REQUESTS}</div>
419
+ <div class="stat-label">Requests</div>
420
+ </div>
421
+ </div>
422
+
423
+ <div class="section">
424
+ <div class="section-header">
425
+ <h2>Collection Reports</h2>
426
+ <span class="count">${TOTAL_COLLECTIONS} collections</span>
427
+ </div>
428
+ <ul class="collection-list">${COLLECTION_LINKS}</ul>
429
+ </div>
430
+
431
+ <footer>
432
+ Duration: ${DURATION_SEC}s <span>•</span> Generated by Newman
433
+ </footer>
434
+ </div>
435
+ </body>
436
+ </html>
437
+ EOF
438
+ SKIP_INLINE_HTML
439
+
440
+ echo -e "${GREEN}✓ HTML: ${CONSOLIDATED_HTML}${NC}"
441
+ echo ""
442
+ echo -e "${BLUE}Open consolidated report:${NC}"
443
+ echo -e " ${BOLD}open ${CONSOLIDATED_HTML}${NC}"
444
+ echo ""
445
+
@@ -0,0 +1,257 @@
1
+ #!/bin/bash
2
+
3
+ ###############################################################################
4
+ # API Coverage Matrix Generator
5
+ #
6
+ # Works with ANY Java Spring Boot service that has:
7
+ # - Java controllers with @RequestMapping, @GetMapping, @PostMapping, etc.
8
+ # - Postman collections with test assertions
9
+ #
10
+ # Tracks:
11
+ # - Status codes tested per endpoint
12
+ # - Test count per endpoint
13
+ # - Response body assertions count
14
+ # - Request body variations count
15
+ # - Query parameter coverage
16
+ # - Request body field coverage (for POST/PUT/PATCH)
17
+ #
18
+ # Usage:
19
+ # ./java-api-coverage-matrix.sh [CONTROLLER_DIR] [POSTMAN_DIR]
20
+ #
21
+ # Arguments:
22
+ # CONTROLLER_DIR - Path to Java controller directory (auto-detected if not provided)
23
+ # POSTMAN_DIR - Path to Postman collections directory (default: ./postman)
24
+ # Can point to root postman/ or directly to collections subfolder
25
+ #
26
+ # Examples:
27
+ # # Auto-detect everything
28
+ # ./java-api-coverage-matrix.sh
29
+ #
30
+ # # Specify controller dir only (postman/ auto-detected)
31
+ # ./java-api-coverage-matrix.sh ./src/main/java/com/company/controller
32
+ #
33
+ # # Specify both paths explicitly
34
+ # ./java-api-coverage-matrix.sh \
35
+ # ./src/main/java/com/company/controller \
36
+ # ./postman/collections
37
+ #
38
+ # Exit Codes:
39
+ # 0 - Success
40
+ # 1 - Configuration error (missing directories)
41
+ # 2 - Python not available
42
+ # 3 - No endpoints found
43
+ # 4 - No Postman collections found
44
+ ###############################################################################
45
+
46
+ set -e
47
+
48
+ # Colors for output (disabled if not a terminal)
49
+ if [ -t 1 ]; then
50
+ RED='\033[0;31m'
51
+ GREEN='\033[0;32m'
52
+ YELLOW='\033[1;33m'
53
+ BLUE='\033[0;34m'
54
+ NC='\033[0m' # No Color
55
+ else
56
+ RED=''
57
+ GREEN=''
58
+ YELLOW=''
59
+ BLUE=''
60
+ NC=''
61
+ fi
62
+
63
+ log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
64
+ log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
65
+ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
66
+ log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
67
+
68
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
69
+
70
+ #------------------------------------------------------------------------------
71
+ # Check Python availability
72
+ #------------------------------------------------------------------------------
73
+ check_python() {
74
+ if ! command -v python3 &> /dev/null; then
75
+ log_error "Python 3 is required but not installed."
76
+ exit 2
77
+ fi
78
+
79
+ PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
80
+ PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1)
81
+ PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2)
82
+
83
+ if [ "$PYTHON_MAJOR" -lt 3 ] || ([ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 6 ]); then
84
+ log_error "Python 3.6+ is required. Found: Python $PYTHON_VERSION"
85
+ exit 2
86
+ fi
87
+
88
+ log_info "Using Python $PYTHON_VERSION"
89
+ }
90
+
91
+ #------------------------------------------------------------------------------
92
+ # Find controller directory
93
+ #------------------------------------------------------------------------------
94
+ find_controller_dir() {
95
+ local base_dir="$1"
96
+ local max_depth="${2:-10}"
97
+
98
+ found=$(find "$base_dir" -maxdepth "$max_depth" -type d -name "controller" \
99
+ ! -path "*/target/*" \
100
+ ! -path "*/build/*" \
101
+ ! -path "*/.git/*" \
102
+ ! -path "*/node_modules/*" \
103
+ 2>/dev/null | while read -r dir; do
104
+ if ls "$dir"/*.java >/dev/null 2>&1; then
105
+ if echo "$dir" | grep -q "src/main/java"; then
106
+ echo "$dir"
107
+ break
108
+ fi
109
+ fi
110
+ done | head -1)
111
+
112
+ if [ -n "$found" ]; then
113
+ echo "$found"
114
+ return 0
115
+ fi
116
+
117
+ found=$(find "$base_dir" -maxdepth "$max_depth" -type d -name "controller" \
118
+ ! -path "*/target/*" \
119
+ ! -path "*/build/*" \
120
+ ! -path "*/.git/*" \
121
+ ! -path "*/node_modules/*" \
122
+ 2>/dev/null | while read -r dir; do
123
+ if ls "$dir"/*.java >/dev/null 2>&1; then
124
+ echo "$dir"
125
+ break
126
+ fi
127
+ done | head -1)
128
+
129
+ echo "$found"
130
+ }
131
+
132
+ find_postman_dir() {
133
+ local base_dir="$1"
134
+ local patterns=("postman" "tests/postman" "api-tests" "integration-tests/postman")
135
+
136
+ for pattern in "${patterns[@]}"; do
137
+ if [ -d "$base_dir/$pattern" ]; then
138
+ echo "$base_dir/$pattern"
139
+ return 0
140
+ fi
141
+ done
142
+ echo ""
143
+ }
144
+
145
+ #------------------------------------------------------------------------------
146
+ # Validate inputs
147
+ #------------------------------------------------------------------------------
148
+ validate_inputs() {
149
+ local controller_dir="$1"
150
+ local postman_dir="$2"
151
+
152
+ if [ -z "$controller_dir" ] || [ ! -d "$controller_dir" ]; then
153
+ log_error "Controller directory not found: $controller_dir"
154
+ echo ""
155
+ echo "Usage: $0 [CONTROLLER_DIR] [POSTMAN_DIR]"
156
+ exit 1
157
+ fi
158
+
159
+ JAVA_COUNT=$(find "$controller_dir" -name "*.java" 2>/dev/null | wc -l | tr -d ' ')
160
+ if [ "$JAVA_COUNT" -eq 0 ]; then
161
+ log_error "No Java files found in: $controller_dir"
162
+ exit 3
163
+ fi
164
+ log_info "Found $JAVA_COUNT Java controller files"
165
+
166
+ if [ -z "$postman_dir" ] || [ ! -d "$postman_dir" ]; then
167
+ log_error "Postman directory not found: $postman_dir"
168
+ exit 1
169
+ fi
170
+
171
+ # Check for collections in top level or in collections/ subdirectory
172
+ COLLECTION_COUNT=$(find "$postman_dir" -maxdepth 1 -name "*.json" ! -name "*environment*" ! -name "package*.json" 2>/dev/null | wc -l | tr -d ' ')
173
+ if [ "$COLLECTION_COUNT" -eq 0 ] && [ -d "$postman_dir/collections" ]; then
174
+ COLLECTION_COUNT=$(find "$postman_dir/collections" -name "*.json" 2>/dev/null | wc -l | tr -d ' ')
175
+ fi
176
+ if [ "$COLLECTION_COUNT" -eq 0 ]; then
177
+ log_error "No Postman collection files found in: $postman_dir or $postman_dir/collections"
178
+ exit 4
179
+ fi
180
+ log_info "Found $COLLECTION_COUNT Postman collection files"
181
+ }
182
+
183
+ #------------------------------------------------------------------------------
184
+ # Clean up old reports (keep only last one)
185
+ #------------------------------------------------------------------------------
186
+ source "$SCRIPT_DIR/../lib/cleanup-reports.sh"
187
+
188
+ cleanup_old_reports() {
189
+ cleanup_reports_by_pattern "$1" "api-coverage-matrix-*.html" "${2:-1}"
190
+ }
191
+
192
+ #------------------------------------------------------------------------------
193
+ # Main script
194
+ #------------------------------------------------------------------------------
195
+
196
+ echo "========================================"
197
+ echo "API Coverage Matrix Generator"
198
+ echo "========================================"
199
+ echo ""
200
+
201
+ check_python
202
+
203
+ # Determine directories
204
+ if [ -n "$1" ] && [ -d "$1" ]; then
205
+ CONTROLLER_DIR="$1"
206
+ else
207
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)" || PROJECT_ROOT="$(pwd)"
208
+ CONTROLLER_DIR=$(find_controller_dir "$PROJECT_ROOT")
209
+ fi
210
+
211
+ if [ -n "$2" ] && [ -d "$2" ]; then
212
+ POSTMAN_DIR="$2"
213
+ else
214
+ PROJECT_ROOT="${PROJECT_ROOT:-$(pwd)}"
215
+ POSTMAN_DIR=$(find_postman_dir "$PROJECT_ROOT")
216
+ fi
217
+
218
+ echo "Configuration:"
219
+ echo " CONTROLLER_DIR: ${CONTROLLER_DIR:-<not found>}"
220
+ echo " POSTMAN_DIR: ${POSTMAN_DIR:-<not found>}"
221
+ echo ""
222
+
223
+ validate_inputs "$CONTROLLER_DIR" "$POSTMAN_DIR"
224
+
225
+ # Create reports directory
226
+ REPORTS_DIR="$POSTMAN_DIR/reports"
227
+ mkdir -p "$REPORTS_DIR"
228
+
229
+ # Cleanup old reports
230
+ cleanup_old_reports "$REPORTS_DIR" 1
231
+
232
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
233
+ HTML_OUTPUT_FILE="$REPORTS_DIR/api-coverage-matrix-$TIMESTAMP.html"
234
+
235
+ log_info "Generating coverage matrix..."
236
+
237
+ # Run the external Python module (much cleaner than embedded code)
238
+ cd "$SCRIPT_DIR/../lib"
239
+ python3 api_coverage.py \
240
+ --controller-dir "$CONTROLLER_DIR" \
241
+ --postman-dir "$POSTMAN_DIR" \
242
+ --html-output "$HTML_OUTPUT_FILE"
243
+
244
+ # Capture exit code
245
+ PYTHON_EXIT=$?
246
+
247
+ if [ $PYTHON_EXIT -ne 0 ]; then
248
+ echo ""
249
+ log_error "Matrix generation failed (exit code: $PYTHON_EXIT)"
250
+ exit $PYTHON_EXIT
251
+ fi
252
+
253
+ echo ""
254
+ echo "========================================"
255
+ log_success "Matrix Generation Complete"
256
+ echo "HTML: $HTML_OUTPUT_FILE"
257
+ echo "========================================"