@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,936 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ###############################################################################
4
+ # Local Test Runner
5
+ #
6
+ # Runs API integration tests locally with optional JaCoCo coverage.
7
+ #
8
+ # Usage:
9
+ # ./run-tests-local.sh # Run all tests (V3 + V2 + Legacy)
10
+ # ./run-tests-local.sh --v3 # Run only V3 tests (CRUD + Files)
11
+ # ./run-tests-local.sh --v2 # Run only V2 tests (OAuth, Transfer)
12
+ # ./run-tests-local.sh --legacy # Run only Legacy tests
13
+ # ./run-tests-local.sh --all # Run all test suites
14
+ # ./run-tests-local.sh --coverage # Run tests with JaCoCo coverage
15
+ # ./run-tests-local.sh --multi-product # Run V3 tests across all products
16
+ # ./run-tests-local.sh --skip-services # Skip starting LocalStack/WireMock/Spring
17
+ # ./run-tests-local.sh --help # Show this help
18
+ #
19
+ # Multi-Product Testing:
20
+ # Tests V3 APIs (CRUD + Files) across Freshdesk, Freshservice, and Freshworks CRM
21
+ # Requires product-specific environment files in postman/environments/:
22
+ # - local.postman_environment.json (Freshdesk)
23
+ # - local-freshservice.postman_environment.json
24
+ # - local-freshworks-crm.postman_environment.json
25
+ #
26
+ # Examples:
27
+ # ./run-tests-local.sh # Quick: run all API tests
28
+ # ./run-tests-local.sh --coverage # Full: all tests + coverage
29
+ # ./run-tests-local.sh --v3 # Dev: just test V3 changes
30
+ # ./run-tests-local.sh --multi-product # Test all products locally
31
+ # ./run-tests-local.sh --all --multi-product # All suites, all products
32
+ # ./run-tests-local.sh --skip-services # Tests only (services running)
33
+ ###############################################################################
34
+
35
+ set -e
36
+
37
+ #==============================================================================
38
+ # CONFIGURATION
39
+ #==============================================================================
40
+
41
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
42
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
43
+ # Display name shown on the reports — the repo/service name, overridable.
44
+ REPORT_LOGO="${SHIFTLEFT_REPORT_LOGO:-$(basename "$PROJECT_ROOT")}"
45
+ POSTMAN_DIR="$PROJECT_ROOT/postman"
46
+ REPORT_DIR="$POSTMAN_DIR/reports"
47
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
48
+
49
+ # Application settings
50
+ APP_PORT=8080
51
+ APP_PID_FILE="$SCRIPT_DIR/spring-boot-app.pid"
52
+ APP_LOG_FILE="$SCRIPT_DIR/spring-boot-app.log"
53
+
54
+ # Collection directories
55
+ CRUD_COLLECTIONS_DIR="$POSTMAN_DIR/collections/crud"
56
+ FILES_COLLECTIONS_DIR="$POSTMAN_DIR/collections/files"
57
+ V2_COLLECTIONS_DIR="$POSTMAN_DIR/collections/v2"
58
+ LEGACY_COLLECTIONS_DIR="$POSTMAN_DIR/collections/legacy"
59
+ ENVIRONMENT="$POSTMAN_DIR/environments/local.postman_environment.json"
60
+
61
+ # Coverage settings
62
+ COVERAGE_DIR="$PROJECT_ROOT/coverage"
63
+ COVERAGE_EXEC_FILE="$COVERAGE_DIR/jacoco-api-$TIMESTAMP.exec"
64
+
65
+ # Multi-product configuration
66
+ PRODUCTS="freshdesk freshservice freshworks-crm"
67
+ declare -A PRODUCT_CONFIG=(
68
+ ["freshdesk_id"]="1"
69
+ ["freshdesk_env"]="local.postman_environment.json"
70
+ ["freshdesk_display"]="Freshdesk"
71
+ ["freshservice_id"]="3"
72
+ ["freshservice_env"]="local-freshservice.postman_environment.json"
73
+ ["freshservice_display"]="Freshservice"
74
+ ["freshworks-crm_id"]="39"
75
+ ["freshworks-crm_env"]="local-freshworks-crm.postman_environment.json"
76
+ ["freshworks-crm_display"]="Freshworks CRM"
77
+ )
78
+
79
+ #==============================================================================
80
+ # STATE TRACKING
81
+ #==============================================================================
82
+
83
+ TOTAL_PASSED=0
84
+ TOTAL_FAILED=0
85
+ declare -a RESULTS
86
+ declare -A COLLECTION_STATUS
87
+ declare -A COLLECTION_PASSED
88
+ declare -A COLLECTION_FAILED
89
+ declare -A PRODUCT_STATUS
90
+
91
+ # Initialize collection status
92
+ for key in V3_CRUD V3_FILES V2 LEGACY; do
93
+ COLLECTION_STATUS["$key"]="Not Run"
94
+ COLLECTION_PASSED["$key"]=0
95
+ COLLECTION_FAILED["$key"]=0
96
+ done
97
+
98
+ # Initialize product status
99
+ for product in $PRODUCTS; do
100
+ PRODUCT_STATUS["$product"]="Not Run"
101
+ done
102
+
103
+ #==============================================================================
104
+ # ARGUMENT PARSING
105
+ #==============================================================================
106
+
107
+ RUN_V3=false
108
+ RUN_V2=false
109
+ RUN_LEGACY=false
110
+ RUN_COVERAGE=false
111
+ MULTI_PRODUCT=false
112
+ SKIP_SERVICES=false
113
+ SHOW_HELP=false
114
+ RUN_ALL=true
115
+
116
+ parse_arguments() {
117
+ for arg in "$@"; do
118
+ case $arg in
119
+ --v3)
120
+ RUN_V3=true
121
+ RUN_ALL=false
122
+ ;;
123
+ --v2)
124
+ RUN_V2=true
125
+ RUN_ALL=false
126
+ ;;
127
+ --legacy)
128
+ RUN_LEGACY=true
129
+ RUN_ALL=false
130
+ ;;
131
+ --all)
132
+ RUN_ALL=true
133
+ ;;
134
+ --coverage)
135
+ RUN_COVERAGE=true
136
+ ;;
137
+ --multi-product)
138
+ MULTI_PRODUCT=true
139
+ RUN_V3=true
140
+ ;;
141
+ --skip-services)
142
+ SKIP_SERVICES=true
143
+ ;;
144
+ --help|-h)
145
+ SHOW_HELP=true
146
+ ;;
147
+ esac
148
+ done
149
+
150
+ # If running all, enable all test suites
151
+ if [ "$RUN_ALL" = true ]; then
152
+ RUN_V3=true
153
+ RUN_V2=true
154
+ RUN_LEGACY=true
155
+ fi
156
+ }
157
+
158
+ show_help() {
159
+ head -32 "$0" | tail -29
160
+ exit 0
161
+ }
162
+
163
+ #==============================================================================
164
+ # UTILITY FUNCTIONS
165
+ #==============================================================================
166
+
167
+ # Extract value from Postman environment JSON
168
+ get_env_value() {
169
+ local env_file="$1"
170
+ local key="$2"
171
+ python3 -c "import json; d=json.load(open('$env_file')); print(next((v['value'] for v in d['values'] if v['key']=='$key'), ''))" 2>/dev/null
172
+ }
173
+
174
+ # Parse Newman output to extract assertion counts
175
+ parse_newman_results() {
176
+ local output_file="$1"
177
+ local assertions_line=$(grep "assertions" "$output_file" 2>/dev/null | head -1)
178
+
179
+ NEWMAN_EXECUTED=$(echo "$assertions_line" | awk -F'│' '{gsub(/[^0-9]/,"",$3); print $3}')
180
+ NEWMAN_FAILED=$(echo "$assertions_line" | awk -F'│' '{gsub(/[^0-9]/,"",$4); print $4}')
181
+
182
+ NEWMAN_EXECUTED=${NEWMAN_EXECUTED:-0}
183
+ NEWMAN_FAILED=${NEWMAN_FAILED:-0}
184
+ NEWMAN_PASSED=$((NEWMAN_EXECUTED > 0 ? NEWMAN_EXECUTED - NEWMAN_FAILED : 0))
185
+ }
186
+
187
+ # Print colored status
188
+ print_status() {
189
+ local status="$1"
190
+ local message="$2"
191
+ if [ "$status" = "success" ]; then
192
+ echo "✓ $message"
193
+ elif [ "$status" = "error" ]; then
194
+ echo "✗ $message"
195
+ elif [ "$status" = "warning" ]; then
196
+ echo "⚠ $message"
197
+ else
198
+ echo " $message"
199
+ fi
200
+ }
201
+
202
+ #==============================================================================
203
+ # JAVA SETUP
204
+ #==============================================================================
205
+
206
+ setup_java() {
207
+ echo "Detecting Java 11..."
208
+
209
+ # Try SDKMAN first
210
+ export SDKMAN_DIR="$HOME/.sdkman"
211
+ if [ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]; then
212
+ source "$SDKMAN_DIR/bin/sdkman-init.sh"
213
+ # Try to use any Java 11 version available
214
+ local java11=$(sdk list java 2>/dev/null | grep -o '11\.[0-9.]*-[a-z]*' | head -1)
215
+ if [ -n "$java11" ]; then
216
+ sdk use java "$java11" 2>/dev/null || true
217
+ fi
218
+ fi
219
+
220
+ # Try java_home (macOS)
221
+ if command -v /usr/libexec/java_home >/dev/null 2>&1; then
222
+ local java_11_home=$(/usr/libexec/java_home -v 11 2>/dev/null)
223
+ if [ -n "$java_11_home" ]; then
224
+ export JAVA_HOME="$java_11_home"
225
+ export PATH="$JAVA_HOME/bin:$PATH"
226
+ fi
227
+ fi
228
+
229
+ # Fallback to common installation paths
230
+ if [ -z "$JAVA_HOME" ] || [ ! -d "$JAVA_HOME" ]; then
231
+ for java_path in \
232
+ "/Library/Java/JavaVirtualMachines/amazon-corretto-11.jdk/Contents/Home" \
233
+ "$HOME/.sdkman/candidates/java/current" \
234
+ "/usr/lib/jvm/java-11-openjdk" \
235
+ "/usr/lib/jvm/java-11"; do
236
+ if [ -d "$java_path" ]; then
237
+ export JAVA_HOME="$java_path"
238
+ export PATH="$JAVA_HOME/bin:$PATH"
239
+ break
240
+ fi
241
+ done
242
+ fi
243
+
244
+ # Verify Java version
245
+ local java_version=$(java -version 2>&1 | head -1 | grep -oE '[0-9]+' | head -1)
246
+ if [ "$java_version" != "11" ]; then
247
+ echo "Warning: Java 11 not detected. Current version: $(java -version 2>&1 | head -1)"
248
+ echo "Please set JAVA_HOME to Java 11 or run: export JAVA_HOME=\$(/usr/libexec/java_home -v 11)"
249
+ else
250
+ print_status "success" "Java 11 detected: $(java -version 2>&1 | head -1)"
251
+ fi
252
+ }
253
+
254
+ #==============================================================================
255
+ # SERVICE MANAGEMENT
256
+ #==============================================================================
257
+
258
+ # Source container runtime detection
259
+ source "$SCRIPT_DIR/../infra/container-runtime.sh"
260
+
261
+ start_services() {
262
+ if [ "$SKIP_SERVICES" = true ]; then
263
+ echo "Skipping service startup (--skip-services)"
264
+ return 0
265
+ fi
266
+
267
+ # Step 1: Start LocalStack
268
+ echo ""
269
+ echo "========================================="
270
+ echo "Step 1: Starting LocalStack"
271
+ echo "========================================="
272
+ "$SCRIPT_DIR/../infra/setup-localstack.sh" || echo "Warning: LocalStack setup issues"
273
+
274
+ # Step 2: Start WireMock
275
+ echo ""
276
+ echo "========================================="
277
+ echo "Step 2: Starting WireMock"
278
+ echo "========================================="
279
+ "$SCRIPT_DIR/../infra/start-mocks.sh"
280
+
281
+ # Step 3: Start Spring Boot
282
+ echo ""
283
+ echo "========================================="
284
+ echo "Step 3: Starting Spring Boot"
285
+ echo "========================================="
286
+ start_spring_boot
287
+ }
288
+
289
+ start_spring_boot() {
290
+ if curl -s "http://localhost:$APP_PORT/actuator/health" > /dev/null 2>&1; then
291
+ print_status "success" "Spring Boot already running on port $APP_PORT"
292
+ return 0
293
+ fi
294
+
295
+ cd "$PROJECT_ROOT"
296
+ echo "Starting Spring Boot with integration profile..."
297
+
298
+ local jvm_args="-Dserver.port=$APP_PORT"
299
+
300
+ if [ "$RUN_COVERAGE" = true ]; then
301
+ local jacoco_jar="$HOME/.m2/repository/org/jacoco/org.jacoco.agent/0.8.11/org.jacoco.agent-0.8.11-runtime.jar"
302
+ if [ -f "$jacoco_jar" ]; then
303
+ mkdir -p "$COVERAGE_DIR"
304
+ jvm_args="-javaagent:$jacoco_jar=destfile=$COVERAGE_EXEC_FILE,includes=com.freshworks.marketplace.installation.* $jvm_args"
305
+ echo "JaCoCo coverage enabled"
306
+ else
307
+ echo "Warning: JaCoCo agent not found. Run 'mvn dependency:resolve' first."
308
+ fi
309
+ fi
310
+
311
+ nohup mvn -pl installation spring-boot:run \
312
+ -Dspring-boot.run.profiles=integration \
313
+ -Dspring-boot.run.jvmArguments="$jvm_args" \
314
+ -Dmaven.test.skip=true -Dcheckstyle.skip=true \
315
+ > "$APP_LOG_FILE" 2>&1 &
316
+
317
+ local app_pid=$!
318
+ echo "$app_pid" > "$APP_PID_FILE"
319
+
320
+ # Wait for startup
321
+ local max_wait=120
322
+ local waited=0
323
+ while ! curl -s "http://localhost:$APP_PORT/actuator/health" > /dev/null 2>&1; do
324
+ if [ $waited -ge $max_wait ]; then
325
+ echo "Error: Application failed to start"
326
+ tail -30 "$APP_LOG_FILE"
327
+ exit 1
328
+ fi
329
+ sleep 2
330
+ waited=$((waited + 2))
331
+ printf "\r Waiting... %d/%ds" $waited $max_wait
332
+ done
333
+ echo ""
334
+ print_status "success" "Spring Boot started (PID: $app_pid)"
335
+ }
336
+
337
+ stop_services() {
338
+ echo ""
339
+ echo "========================================="
340
+ echo "Cleanup"
341
+ echo "========================================="
342
+
343
+ # Stop Spring Boot
344
+ if [ -f "$APP_PID_FILE" ]; then
345
+ local app_pid=$(cat "$APP_PID_FILE")
346
+ if ps -p "$app_pid" > /dev/null 2>&1; then
347
+ echo "Stopping Spring Boot application (PID: $app_pid)..."
348
+ kill "$app_pid" 2>/dev/null || true
349
+ sleep 2
350
+ fi
351
+ rm -f "$APP_PID_FILE"
352
+ fi
353
+
354
+ # Stop WireMock
355
+ "$SCRIPT_DIR/../infra/stop-mocks.sh" 2>/dev/null || true
356
+
357
+ # Stop LocalStack
358
+ if $CONTAINER_CMD ps 2>/dev/null | grep -q mp-installation-localstack; then
359
+ echo "Stopping LocalStack..."
360
+ $CONTAINER_COMPOSE_CMD -f "$PROJECT_ROOT/docker-compose-localstack.yml" down > /dev/null 2>&1 || true
361
+ print_status "success" "LocalStack stopped"
362
+ fi
363
+
364
+ # Generate coverage report if enabled
365
+ generate_coverage_report
366
+
367
+ print_status "success" "Cleanup completed"
368
+ }
369
+
370
+ generate_coverage_report() {
371
+ if [ "$RUN_COVERAGE" != true ] || [ ! -f "$COVERAGE_EXEC_FILE" ]; then
372
+ return 0
373
+ fi
374
+
375
+ echo ""
376
+ echo "========================================="
377
+ echo "Generating Coverage Report"
378
+ echo "========================================="
379
+
380
+ local coverage_html_dir="$COVERAGE_DIR/html-report-$TIMESTAMP"
381
+ mkdir -p "$coverage_html_dir"
382
+
383
+ cd "$PROJECT_ROOT/installation"
384
+
385
+ echo "Generating JaCoCo report..."
386
+ mvn jacoco:report -q 2>/dev/null || true
387
+
388
+ if [ -d "target/site/jacoco" ]; then
389
+ cp -r target/site/jacoco/* "$coverage_html_dir/" 2>/dev/null || true
390
+
391
+ echo ""
392
+ echo "========================================="
393
+ echo "COVERAGE REPORT GENERATED"
394
+ echo "========================================="
395
+ echo "Timestamped Report: $coverage_html_dir/index.html"
396
+ echo "Latest Report: installation/target/site/jacoco/index.html"
397
+ echo "Raw Data: $COVERAGE_EXEC_FILE"
398
+ echo ""
399
+ echo "To view: open $coverage_html_dir/index.html"
400
+ else
401
+ echo "Warning: Coverage report generation failed"
402
+ fi
403
+ }
404
+
405
+ #==============================================================================
406
+ # CLEANUP FUNCTIONS
407
+ #==============================================================================
408
+
409
+ # Cleanup all installations across ALL products before running tests
410
+ cleanup_all_installations() {
411
+ echo ""
412
+ echo " → Cleaning up existing installations..."
413
+
414
+ local base_url="http://localhost:8080/api/v3"
415
+ local total_count=0
416
+ local product
417
+
418
+ for product in $PRODUCTS; do
419
+ local env_key="${product}_env"
420
+ local env_file="$POSTMAN_DIR/environments/${PRODUCT_CONFIG[$env_key]}"
421
+
422
+ if [ ! -f "$env_file" ]; then
423
+ continue
424
+ fi
425
+
426
+ local auth_token=$(get_env_value "$env_file" "auth_token")
427
+ local product_id=$(get_env_value "$env_file" "product_id")
428
+ local account_id=$(get_env_value "$env_file" "account_id")
429
+
430
+ if [ -z "$auth_token" ]; then
431
+ continue
432
+ fi
433
+
434
+ # List all installations (types: 1=extension, 4=bundle, 6=custom)
435
+ local list_response=$(curl -s -X GET "${base_url}/installations?type=1,4,6" \
436
+ -H "Authorization: Bearer ${auth_token}" \
437
+ -H "x-mp-product-id: ${product_id}" \
438
+ -H "x-mp-account-id: ${account_id}" \
439
+ -H "X-MP-USER-ROLE: admin" \
440
+ -H "Content-Type: application/json" 2>/dev/null)
441
+
442
+ # Extract and delete installation IDs
443
+ local installation_ids=$(echo "$list_response" | python3 -c "
444
+ import json, sys
445
+ try:
446
+ data = json.load(sys.stdin)
447
+ if isinstance(data, list):
448
+ ids = [str(item.get('installed_extension_id', '')) for item in data if item.get('installed_extension_id')]
449
+ else:
450
+ ids = []
451
+ print(' '.join(ids))
452
+ except:
453
+ print('')
454
+ " 2>/dev/null)
455
+
456
+ if [ -n "$installation_ids" ] && [ "$installation_ids" != "" ]; then
457
+ for inst_id in $installation_ids; do
458
+ curl -s -X DELETE "${base_url}/installations/${inst_id}" \
459
+ -H "Authorization: Bearer ${auth_token}" \
460
+ -H "x-mp-product-id: ${product_id}" \
461
+ -H "x-mp-account-id: ${account_id}" \
462
+ -H "X-MP-USER-ROLE: admin" \
463
+ -H "Content-Type: application/json" >/dev/null 2>&1
464
+ total_count=$((total_count + 1))
465
+ done
466
+ fi
467
+ done
468
+
469
+ if [ $total_count -gt 0 ]; then
470
+ print_status "success" "Cleaned up $total_count installation(s)"
471
+ else
472
+ print_status "success" "No existing installations to clean up"
473
+ fi
474
+
475
+ sleep 1
476
+ }
477
+
478
+ #==============================================================================
479
+ # TEST EXECUTION
480
+ #==============================================================================
481
+
482
+ # Run all collections in a directory
483
+ run_collection_dir() {
484
+ local dir_name="$1"
485
+ local collections_dir="$2"
486
+ local report_prefix="$3"
487
+ local collection_key="$4"
488
+
489
+ echo "========================================="
490
+ echo "Running: $dir_name"
491
+ echo "========================================="
492
+
493
+ local dir_passed=0
494
+ local dir_failed=0
495
+ local all_passed=true
496
+ local newman_tmp="/tmp/newman-output-${TIMESTAMP}-$$.txt"
497
+
498
+ # Create working environment copy
499
+ local working_env="/tmp/newman-env-${collection_key}-${TIMESTAMP}.json"
500
+ cp "$ENVIRONMENT" "$working_env"
501
+
502
+ # Run collections in order
503
+ for collection_file in "$collections_dir"/*.json; do
504
+ [ ! -f "$collection_file" ] && continue
505
+
506
+ local basename=$(basename "$collection_file" .json)
507
+ local html_report="$REPORT_DIR/${report_prefix}-${basename}-$TIMESTAMP.html"
508
+ local json_report="$REPORT_DIR/json/${report_prefix}-${basename}-$TIMESTAMP.json"
509
+ local env_export="/tmp/newman-env-export-${collection_key}-${basename}.json"
510
+
511
+ echo ""
512
+ echo " → Running: $basename"
513
+ echo ""
514
+
515
+ "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
516
+ --environment "$working_env" \
517
+ --reporters cli,htmlextra,json \
518
+ --reporter-htmlextra-export "$html_report" \
519
+ --reporter-json-export "$json_report" \
520
+ --export-environment "$env_export" \
521
+ --timeout-request 30000 \
522
+ --delay-request 100 2>&1 | tee "$newman_tmp"
523
+
524
+ parse_newman_results "$newman_tmp"
525
+
526
+ dir_passed=$((dir_passed + NEWMAN_PASSED))
527
+ dir_failed=$((dir_failed + NEWMAN_FAILED))
528
+
529
+ # Chain environment for next collection
530
+ [ -f "$env_export" ] && cp "$env_export" "$working_env"
531
+
532
+ if [ "$NEWMAN_FAILED" -gt 0 ]; then
533
+ all_passed=false
534
+ print_status "error" "$basename: FAILED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)"
535
+ else
536
+ print_status "success" "$basename: PASSED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)"
537
+ fi
538
+ done
539
+
540
+ # Cleanup temp files
541
+ rm -f "$working_env" /tmp/newman-env-export-${collection_key}-*.json "$newman_tmp"
542
+
543
+ # Update status
544
+ if [ "$all_passed" = true ] && [ "$dir_failed" -eq 0 ]; then
545
+ COLLECTION_STATUS["$collection_key"]="✓ PASSED"
546
+ RESULTS+=("$dir_name: ✓ PASSED ($dir_passed passed, $dir_failed failed)")
547
+ echo ""
548
+ print_status "success" "$dir_name: ALL PASSED"
549
+ else
550
+ COLLECTION_STATUS["$collection_key"]="✗ FAILED"
551
+ RESULTS+=("$dir_name: ✗ FAILED ($dir_passed passed, $dir_failed failed)")
552
+ echo ""
553
+ print_status "error" "$dir_name: SOME FAILED"
554
+ fi
555
+
556
+ COLLECTION_PASSED["$collection_key"]=$dir_passed
557
+ COLLECTION_FAILED["$collection_key"]=$dir_failed
558
+ TOTAL_PASSED=$((TOTAL_PASSED + dir_passed))
559
+ TOTAL_FAILED=$((TOTAL_FAILED + dir_failed))
560
+
561
+ [ "$all_passed" = true ] && [ "$dir_failed" -eq 0 ]
562
+ }
563
+
564
+ # Run tests for a single product (multi-product mode)
565
+ run_product_tests() {
566
+ local product="$1"
567
+ local display_name="${PRODUCT_CONFIG[${product}_display]}"
568
+ local env_file="$POSTMAN_DIR/environments/${PRODUCT_CONFIG[${product}_env]}"
569
+
570
+ echo ""
571
+ echo "========================================="
572
+ echo "Testing Product: $display_name"
573
+ echo "========================================="
574
+
575
+ if [ ! -f "$env_file" ]; then
576
+ echo "Warning: Environment file not found: $env_file"
577
+ echo "Skipping $product"
578
+ PRODUCT_STATUS["$product"]="⚠ SKIPPED"
579
+ return 1
580
+ fi
581
+
582
+ # Clean up installations before testing this product
583
+ cleanup_all_installations
584
+
585
+ local product_passed=true
586
+ local newman_tmp="/tmp/newman-output-${product}-${TIMESTAMP}-$$.txt"
587
+
588
+ # Each product gets its own report subdirectory to avoid cross-product overwrites
589
+ local product_report_dir="$REPORT_DIR/${product}"
590
+ local product_json_dir="$REPORT_DIR/json/${product}"
591
+ mkdir -p "$product_report_dir" "$product_json_dir"
592
+
593
+ # Create working environment copy
594
+ local working_env="/tmp/newman-env-${product}-${TIMESTAMP}.json"
595
+ cp "$env_file" "$working_env"
596
+
597
+ # Run CRUD tests
598
+ local crud_key="${product}_CRUD"
599
+ COLLECTION_STATUS["$crud_key"]="Not Run"
600
+ COLLECTION_PASSED["$crud_key"]=0
601
+ COLLECTION_FAILED["$crud_key"]=0
602
+
603
+ echo ""
604
+ echo "Running CRUD API tests for $display_name..."
605
+
606
+ local crud_passed=0 crud_failed=0 crud_all_passed=true
607
+
608
+ for collection_file in "$CRUD_COLLECTIONS_DIR"/*.json; do
609
+ [ ! -f "$collection_file" ] && continue
610
+
611
+ local basename=$(basename "$collection_file" .json)
612
+ local html_report="$product_report_dir/crud-${basename}-$TIMESTAMP.html"
613
+ local json_report="$product_json_dir/crud-${basename}-$TIMESTAMP.json"
614
+ local env_export="/tmp/newman-env-export-${product}-crud-${basename}.json"
615
+
616
+ "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
617
+ --environment "$working_env" \
618
+ --reporters cli,htmlextra,json \
619
+ --reporter-htmlextra-export "$html_report" \
620
+ --reporter-htmlextra-title "${REPORT_LOGO} - $display_name - CRUD - $basename (Local)" \
621
+ --reporter-htmlextra-darkTheme \
622
+ --reporter-json-export "$json_report" \
623
+ --export-environment "$env_export" \
624
+ --timeout-request 30000 \
625
+ --delay-request 100 2>&1 | tee "$newman_tmp"
626
+
627
+ parse_newman_results "$newman_tmp"
628
+
629
+ crud_passed=$((crud_passed + NEWMAN_PASSED))
630
+ crud_failed=$((crud_failed + NEWMAN_FAILED))
631
+
632
+ [ -f "$env_export" ] && cp "$env_export" "$working_env"
633
+ [ "$NEWMAN_FAILED" -gt 0 ] && crud_all_passed=false
634
+ done
635
+
636
+ if [ "$crud_all_passed" = true ] && [ "$crud_failed" -eq 0 ]; then
637
+ print_status "success" "CRUD tests passed for $display_name"
638
+ COLLECTION_STATUS["$crud_key"]="✓ PASSED"
639
+ else
640
+ print_status "error" "CRUD tests failed for $display_name"
641
+ COLLECTION_STATUS["$crud_key"]="✗ FAILED"
642
+ product_passed=false
643
+ fi
644
+
645
+ COLLECTION_PASSED["$crud_key"]=$crud_passed
646
+ COLLECTION_FAILED["$crud_key"]=$crud_failed
647
+ TOTAL_PASSED=$((TOTAL_PASSED + crud_passed))
648
+ TOTAL_FAILED=$((TOTAL_FAILED + crud_failed))
649
+
650
+ # Run Files tests (using environment from CRUD tests)
651
+ local files_key="${product}_FILES"
652
+ COLLECTION_STATUS["$files_key"]="Not Run"
653
+ COLLECTION_PASSED["$files_key"]=0
654
+ COLLECTION_FAILED["$files_key"]=0
655
+
656
+ echo ""
657
+ echo "Running Files API tests for $display_name..."
658
+
659
+ local files_passed=0 files_failed=0 files_all_passed=true
660
+
661
+ for collection_file in "$FILES_COLLECTIONS_DIR"/*.json; do
662
+ [ ! -f "$collection_file" ] && continue
663
+
664
+ local basename=$(basename "$collection_file" .json)
665
+ local html_report="$product_report_dir/files-${basename}-$TIMESTAMP.html"
666
+ local json_report="$product_json_dir/files-${basename}-$TIMESTAMP.json"
667
+ local env_export="/tmp/newman-env-export-${product}-files-${basename}.json"
668
+
669
+ "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
670
+ --environment "$working_env" \
671
+ --reporters cli,htmlextra,json \
672
+ --reporter-htmlextra-export "$html_report" \
673
+ --reporter-htmlextra-title "${REPORT_LOGO} - $display_name - Files - $basename (Local)" \
674
+ --reporter-htmlextra-darkTheme \
675
+ --reporter-json-export "$json_report" \
676
+ --export-environment "$env_export" \
677
+ --timeout-request 30000 \
678
+ --delay-request 100 2>&1 | tee "$newman_tmp"
679
+
680
+ parse_newman_results "$newman_tmp"
681
+
682
+ files_passed=$((files_passed + NEWMAN_PASSED))
683
+ files_failed=$((files_failed + NEWMAN_FAILED))
684
+
685
+ [ -f "$env_export" ] && cp "$env_export" "$working_env"
686
+ [ "$NEWMAN_FAILED" -gt 0 ] && files_all_passed=false
687
+ done
688
+
689
+ # Cleanup temp files for this product
690
+ rm -f "$working_env" /tmp/newman-env-export-${product}-*.json "$newman_tmp"
691
+
692
+ if [ "$files_all_passed" = true ] && [ "$files_failed" -eq 0 ]; then
693
+ print_status "success" "Files tests passed for $display_name"
694
+ COLLECTION_STATUS["$files_key"]="✓ PASSED"
695
+ else
696
+ print_status "error" "Files tests failed for $display_name"
697
+ COLLECTION_STATUS["$files_key"]="✗ FAILED"
698
+ product_passed=false
699
+ fi
700
+
701
+ COLLECTION_PASSED["$files_key"]=$files_passed
702
+ COLLECTION_FAILED["$files_key"]=$files_failed
703
+ TOTAL_PASSED=$((TOTAL_PASSED + files_passed))
704
+ TOTAL_FAILED=$((TOTAL_FAILED + files_failed))
705
+
706
+ # Update product status
707
+ if [ "$product_passed" = true ]; then
708
+ PRODUCT_STATUS["$product"]="✓ PASSED"
709
+ else
710
+ PRODUCT_STATUS["$product"]="✗ FAILED"
711
+ fi
712
+ }
713
+
714
+ run_tests() {
715
+ echo ""
716
+
717
+ if [ "$MULTI_PRODUCT" = true ]; then
718
+ # Multi-product mode: run V3 (CRUD + Files) across all products
719
+ for product in $PRODUCTS; do
720
+ run_product_tests "$product"
721
+ done
722
+ # Also run V2 and Legacy once (single-product) if --all was specified
723
+ if [ "$RUN_V2" = true ]; then
724
+ run_collection_dir "V2 API" "$V2_COLLECTIONS_DIR" "v2" "V2" || true
725
+ echo ""
726
+ fi
727
+ if [ "$RUN_LEGACY" = true ]; then
728
+ run_collection_dir "Legacy API" "$LEGACY_COLLECTIONS_DIR" "legacy" "LEGACY" || true
729
+ echo ""
730
+ fi
731
+ else
732
+ # Single product mode
733
+ if [ "$RUN_V3" = true ]; then
734
+ run_collection_dir "V3 CRUD API" "$CRUD_COLLECTIONS_DIR" "crud" "V3_CRUD" || true
735
+ echo ""
736
+ run_collection_dir "V3 Files API" "$FILES_COLLECTIONS_DIR" "files" "V3_FILES" || true
737
+ echo ""
738
+ fi
739
+
740
+ if [ "$RUN_V2" = true ]; then
741
+ run_collection_dir "V2 API" "$V2_COLLECTIONS_DIR" "v2" "V2" || true
742
+ echo ""
743
+ fi
744
+
745
+ if [ "$RUN_LEGACY" = true ]; then
746
+ run_collection_dir "Legacy API" "$LEGACY_COLLECTIONS_DIR" "legacy" "LEGACY" || true
747
+ echo ""
748
+ fi
749
+ fi
750
+ }
751
+
752
+ #==============================================================================
753
+ # REPORTING
754
+ #==============================================================================
755
+
756
+ print_summary() {
757
+ echo ""
758
+ echo "========================================="
759
+ if [ "$MULTI_PRODUCT" = true ]; then
760
+ echo "Multi-Product Test Results"
761
+ else
762
+ echo "Test Results Summary"
763
+ fi
764
+ echo "========================================="
765
+ echo ""
766
+
767
+ if [ "$MULTI_PRODUCT" = true ]; then
768
+ echo "Product Results:"
769
+ for product in $PRODUCTS; do
770
+ local status="${PRODUCT_STATUS[$product]}"
771
+ local display_name="${PRODUCT_CONFIG[${product}_display]}"
772
+ local crud_key="${product}_CRUD"
773
+ local files_key="${product}_FILES"
774
+
775
+ if [ "$status" != "Not Run" ]; then
776
+ echo " $display_name: $status"
777
+ if [ "${COLLECTION_STATUS[$crud_key]}" != "Not Run" ]; then
778
+ echo " - CRUD API: ${COLLECTION_STATUS[$crud_key]} (${COLLECTION_PASSED[$crud_key]} passed, ${COLLECTION_FAILED[$crud_key]} failed)"
779
+ fi
780
+ if [ "${COLLECTION_STATUS[$files_key]}" != "Not Run" ]; then
781
+ echo " - Files API: ${COLLECTION_STATUS[$files_key]} (${COLLECTION_PASSED[$files_key]} passed, ${COLLECTION_FAILED[$files_key]} failed)"
782
+ fi
783
+ fi
784
+ done
785
+ else
786
+ echo "Collection Results:"
787
+ for collection_key in V3_CRUD V3_FILES V2 LEGACY; do
788
+ local status="${COLLECTION_STATUS[$collection_key]}"
789
+ local passed="${COLLECTION_PASSED[$collection_key]}"
790
+ local failed="${COLLECTION_FAILED[$collection_key]}"
791
+
792
+ if [ "$status" != "Not Run" ]; then
793
+ case $collection_key in
794
+ V3_CRUD) echo " V3 CRUD API: $status ($passed passed, $failed failed)" ;;
795
+ V3_FILES) echo " V3 Files API: $status ($passed passed, $failed failed)" ;;
796
+ V2) echo " V2 API: $status ($passed passed, $failed failed)" ;;
797
+ LEGACY) echo " Legacy API: $status ($passed passed, $failed failed)" ;;
798
+ esac
799
+ fi
800
+ done
801
+ fi
802
+
803
+ echo ""
804
+ echo "Overall Total: $TOTAL_PASSED passed, $TOTAL_FAILED failed"
805
+ echo ""
806
+ echo "Reports: $REPORT_DIR/"
807
+ }
808
+
809
+ generate_consolidated_report() {
810
+ # Calculate passed/failed collections
811
+ local passed_collections=0
812
+ local failed_collections=0
813
+
814
+ if [ "$MULTI_PRODUCT" = true ]; then
815
+ for product in $PRODUCTS; do
816
+ if [ "${PRODUCT_STATUS[$product]}" = "✓ PASSED" ]; then
817
+ passed_collections=$((passed_collections + 1))
818
+ elif [ "${PRODUCT_STATUS[$product]}" != "Not Run" ]; then
819
+ failed_collections=$((failed_collections + 1))
820
+ fi
821
+ done
822
+ else
823
+ for collection_key in V3_CRUD V3_FILES V2 LEGACY; do
824
+ if [ "${COLLECTION_STATUS[$collection_key]}" = "✓ PASSED" ]; then
825
+ passed_collections=$((passed_collections + 1))
826
+ elif [ "${COLLECTION_STATUS[$collection_key]}" != "Not Run" ]; then
827
+ failed_collections=$((failed_collections + 1))
828
+ fi
829
+ done
830
+ fi
831
+
832
+ local test_result=0
833
+ [ $TOTAL_FAILED -gt 0 ] && test_result=1
834
+
835
+ if [ -f "$SCRIPT_DIR/../report-generators/generate-consolidated-report.sh" ]; then
836
+ "$SCRIPT_DIR/../report-generators/generate-consolidated-report.sh" "local" "$TIMESTAMP" "$REPORT_DIR" \
837
+ "$passed_collections" "$failed_collections" "$test_result" || true
838
+ fi
839
+ }
840
+
841
+ #==============================================================================
842
+ # MAIN
843
+ #==============================================================================
844
+
845
+ main() {
846
+ parse_arguments "$@"
847
+
848
+ [ "$SHOW_HELP" = true ] && show_help
849
+
850
+ # Display banner
851
+ echo "========================================="
852
+ if [ "$MULTI_PRODUCT" = true ]; then
853
+ echo "${REPORT_LOGO} API Integration Tests (Multi-Product)"
854
+ else
855
+ echo "${REPORT_LOGO} API Integration Tests"
856
+ fi
857
+ echo "========================================="
858
+ echo ""
859
+
860
+ if [ "$MULTI_PRODUCT" = true ]; then
861
+ echo "Mode: Multi-Product Testing (Local)"
862
+ echo "Products:"
863
+ for product in $PRODUCTS; do
864
+ local display="${PRODUCT_CONFIG[${product}_display]}"
865
+ local id="${PRODUCT_CONFIG[${product}_id]}"
866
+ echo " - $display (product_id: $id)"
867
+ done
868
+ echo ""
869
+ echo "Test Suites: V3 API (CRUD + Files) for each product"
870
+ else
871
+ echo "Test Suites:"
872
+ [ "$RUN_V3" = true ] && echo " ✓ V3 API (CRUD + Files)"
873
+ [ "$RUN_V2" = true ] && echo " ✓ V2 API (OAuth, Transfer)"
874
+ [ "$RUN_LEGACY" = true ] && echo " ✓ Legacy API"
875
+ fi
876
+ [ "$RUN_COVERAGE" = true ] && echo " ✓ Coverage: Enabled"
877
+ [ "$SKIP_SERVICES" = true ] && echo " ⚠ Services: Skipped (--skip-services)"
878
+ echo ""
879
+
880
+ # Setup
881
+ setup_java
882
+
883
+ echo ""
884
+ echo "Checking prerequisites..."
885
+ command -v java >/dev/null 2>&1 || { echo "Error: Java required" >&2; exit 1; }
886
+ command -v mvn >/dev/null 2>&1 || { echo "Error: Maven required" >&2; exit 1; }
887
+ command -v node >/dev/null 2>&1 || { echo "Error: Node.js required" >&2; exit 1; }
888
+ check_container_runtime || exit 1
889
+ print_status "success" "Prerequisites OK"
890
+
891
+ # Setup cleanup trap
892
+ trap stop_services EXIT INT TERM
893
+
894
+ # Start services
895
+ start_services
896
+
897
+ # Install Newman and setup git hooks
898
+ echo ""
899
+ echo "========================================="
900
+ echo "Step 4: Installing Newman"
901
+ echo "========================================="
902
+ cd "$POSTMAN_DIR"
903
+ npm ci --ignore-scripts --silent 2>/dev/null || npm install --ignore-scripts --silent
904
+ print_status "success" "Newman ready"
905
+
906
+ # Setup git hooks for auto-formatting (runs once, idempotent)
907
+ if [ -d "$POSTMAN_DIR/.husky" ]; then
908
+ cd "$PROJECT_ROOT"
909
+ git config core.hooksPath postman/.husky/_ 2>/dev/null || true
910
+ fi
911
+
912
+ mkdir -p "$REPORT_DIR" "$REPORT_DIR/json"
913
+
914
+ # Run tests
915
+ run_tests
916
+
917
+ # Print summary
918
+ print_summary
919
+
920
+ # Generate consolidated report
921
+ generate_consolidated_report
922
+
923
+ # Exit with appropriate code
924
+ if [ $TOTAL_FAILED -gt 0 ]; then
925
+ echo ""
926
+ echo "❌ Some tests failed"
927
+ exit 1
928
+ else
929
+ echo ""
930
+ echo "✅ All tests passed successfully!"
931
+ exit 0
932
+ fi
933
+ }
934
+
935
+ # Run main
936
+ main "$@"