@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.
- package/README.md +351 -0
- package/bin/shiftleft.js +95 -0
- package/package.json +57 -0
- package/src/commands/doctor.js +208 -0
- package/src/commands/init-postman.js +298 -0
- package/src/commands/init-rules.js +78 -0
- package/src/commands/link.js +172 -0
- package/src/commands/protect.js +61 -0
- package/src/commands/run-tests.js +182 -0
- package/src/commands/setup-pipeline.js +209 -0
- package/src/commands/update.js +203 -0
- package/src/index.js +4 -0
- package/src/utils/copy-tree.js +98 -0
- package/src/utils/gitignore.js +26 -0
- package/src/utils/logger.js +9 -0
- package/src/utils/manifest.js +145 -0
- package/src/utils/stack.js +80 -0
- package/src/utils/template.js +135 -0
- package/templates/AGENTS.md +109 -0
- package/templates/CLAUDE.md +3 -0
- package/templates/jenkins/Jenkinsfile-java.groovy +432 -0
- package/templates/jenkins/Jenkinsfile-node.groovy +450 -0
- package/templates/postman/.husky/pre-commit +19 -0
- package/templates/postman/.prettierrc.json +5 -0
- package/templates/postman/README.md.ejs +147 -0
- package/templates/postman/collections/01-core.json.ejs +91 -0
- package/templates/postman/config/local.json.ejs +12 -0
- package/templates/postman/config/staging.json.ejs +26 -0
- package/templates/postman/environments/local.postman_environment.json.ejs +31 -0
- package/templates/postman/environments/staging.postman_environment.json.ejs +31 -0
- package/templates/postman/gitignore +16 -0
- package/templates/postman/npmrc +31 -0
- package/templates/postman/package.json.ejs +66 -0
- package/templates/postman/run-all-shim.sh +16 -0
- package/templates/postman/scripts/auth/generate-jwt.sh +113 -0
- package/templates/postman/scripts/auth/get-issuer-secret.sh +140 -0
- package/templates/postman/scripts/infra/start-mocks.sh +138 -0
- package/templates/postman/scripts/infra/stop-mocks.sh +43 -0
- package/templates/postman/scripts/lib/api_coverage.py +1122 -0
- package/templates/postman/scripts/lib/cleanup-reports.sh +101 -0
- package/templates/postman/scripts/lib/cleanup-stryker.sh +44 -0
- package/templates/postman/scripts/lib/report_combined.py +527 -0
- package/templates/postman/scripts/lib/report_consolidated.py +363 -0
- package/templates/postman/scripts/lib/report_generator.py +121 -0
- package/templates/postman/scripts/lib/report_migration.py +156 -0
- package/templates/postman/scripts/lib/report_mutation.py +110 -0
- package/templates/postman/scripts/lib/report_unit.py +353 -0
- package/templates/postman/scripts/lib/report_utils.py +973 -0
- package/templates/postman/scripts/report-generators/generate-consolidated-report.sh +445 -0
- package/templates/postman/scripts/report-generators/java-api-coverage-matrix.sh +257 -0
- package/templates/postman/scripts/report-generators/mutation-report.sh +672 -0
- package/templates/postman/scripts/report-generators/node-api-coverage-matrix.sh +167 -0
- package/templates/postman/scripts/report-generators/stage-report-artifacts.sh +27 -0
- package/templates/postman/scripts/run-all.sh +452 -0
- package/templates/postman/scripts/runners/run-mutation-tests.sh +113 -0
- package/templates/postman/scripts/runners/run-tests-local.sh +936 -0
- package/templates/postman/scripts/runners/run-tests-staging.sh +741 -0
- package/templates/postman-node/README.md.ejs +26 -0
- package/templates/postman-node/collections/crud/01-bootstrap.json.ejs +34 -0
- package/templates/postman-node/config/local.json.ejs +46 -0
- package/templates/postman-node/config/staging.json.ejs +31 -0
- package/templates/postman-node/local.test.env.ejs +3 -0
- package/templates/postman-node/mocks/external.js +14 -0
- package/templates/postman-node/package.json.ejs +39 -0
- package/templates/postman-node/requirements.txt +1 -0
- package/templates/postman-node/scripts/database/cleanup-mysql.sh +12 -0
- package/templates/postman-node/scripts/database/run-migrations.js +29 -0
- package/templates/postman-node/scripts/database/start-mysql.sh +34 -0
- package/templates/postman-node/scripts/database/wait-for-mysql.sh +36 -0
- package/templates/postman-node/scripts/lib/api_coverage_node.py +1137 -0
- package/templates/postman-node/scripts/lib/fetch-jwt.sh +86 -0
- package/templates/postman-node/scripts/lib/run-newman.sh +104 -0
- package/templates/postman-node/scripts/lib/setup-database.sh +55 -0
- package/templates/postman-node/scripts/lib/start-app.sh +48 -0
- package/templates/postman-node/scripts/lib/utils.sh +114 -0
- package/templates/postman-node/scripts/report-generators/stage-report-artifacts.sh +26 -0
- package/templates/postman-node/scripts/run-all.sh +303 -0
- package/templates/postman-node/scripts/runners/run-tests.sh +123 -0
- package/templates/postman-node/scripts/setup-mocks.js.ejs +29 -0
- package/templates/postman-node/stryker.config.js.ejs +51 -0
- package/templates/rules/local-test-setup.mdc +420 -0
- package/templates/rules/testing-node.mdc +66 -0
- package/templates/rules/testing.mdc +248 -0
- package/templates/skills/_shared/postman-standards.md +380 -0
- package/templates/skills/enhance-test-pipeline/SKILL-java.md +483 -0
- package/templates/skills/enhance-test-pipeline/SKILL-node.md +431 -0
- package/templates/skills/enhance-test-pipeline/SKILL.md +9 -0
- package/templates/skills/review-test-suite/SKILL-java.md +137 -0
- package/templates/skills/review-test-suite/SKILL-node.md +78 -0
- package/templates/skills/review-test-suite/SKILL.md +9 -0
- package/templates/skills/run-test-suite/SKILL-java.md +186 -0
- package/templates/skills/run-test-suite/SKILL-node.md +191 -0
- package/templates/skills/run-test-suite/SKILL.md +9 -0
- package/templates/skills/setup-api-tests/SKILL-java.md +1094 -0
- package/templates/skills/setup-api-tests/SKILL-node.md +141 -0
- package/templates/skills/setup-api-tests/SKILL.md +9 -0
- package/templates/skills/setup-mutation-tests/SKILL-java.md +303 -0
- package/templates/skills/setup-mutation-tests/SKILL-node.md +408 -0
- package/templates/skills/setup-mutation-tests/SKILL.md +9 -0
- package/templates/skills/setup-test-pipeline/SKILL-java.md +454 -0
- package/templates/skills/setup-test-pipeline/SKILL-node.md +318 -0
- package/templates/skills/setup-test-pipeline/SKILL.md +9 -0
- package/templates/skills/write-api-tests/SKILL-java.md +115 -0
- package/templates/skills/write-api-tests/SKILL-node.md +83 -0
- package/templates/skills/write-api-tests/SKILL.md +9 -0
- 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 "========================================"
|