@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,741 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ###############################################################################
4
+ # Staging Test Runner
5
+ #
6
+ # Runs API tests against staging environment with automatic JWT generation.
7
+ #
8
+ # Usage:
9
+ # ./run-tests-staging.sh # Run V3 + V2 tests (Legacy skipped)
10
+ # ./run-tests-staging.sh --v3 # Run only V3 tests
11
+ # ./run-tests-staging.sh --v2 # Run only V2 tests
12
+ # ./run-tests-staging.sh --legacy # Run only Legacy tests
13
+ # ./run-tests-staging.sh --multi-product # Run V3 tests across all products
14
+ # ./run-tests-staging.sh --help # Show this help
15
+ #
16
+ # Note: Legacy API tests are SKIPPED by default in staging.
17
+ # Multi-Product Testing:
18
+ # Tests V3 APIs (CRUD + Files) across Freshdesk, Freshservice, and Freshworks CRM
19
+ # Requires product-specific environment files in postman/environments/:
20
+ # - staging-freshdesk.postman_environment.json
21
+ # - staging-freshservice.postman_environment.json
22
+ # - staging-freshworks_crm.postman_environment.json
23
+ #
24
+ # Prerequisites:
25
+ # - AWS CLI configured (set AWS_PROFILE for local use)
26
+ # - jq installed
27
+ # - Node.js installed
28
+ #
29
+ # Examples:
30
+ # AWS_PROFILE=staging ./run-tests-staging.sh
31
+ # ./run-tests-staging.sh --v3 # Quick V3 test only
32
+ # ./run-tests-staging.sh --multi-product # Test all products
33
+ ###############################################################################
34
+
35
+ set -e
36
+
37
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
39
+ # Display name shown on the reports — the repo/service name, overridable.
40
+ REPORT_LOGO="${SHIFTLEFT_REPORT_LOGO:-$(basename "$PROJECT_ROOT")}"
41
+ POSTMAN_DIR="$PROJECT_ROOT/postman"
42
+
43
+ # Configuration
44
+ CONFIG_FILE="$POSTMAN_DIR/config/staging.json"
45
+ REPORT_DIR="$POSTMAN_DIR/reports"
46
+ TIMESTAMP=$(date +%Y%m%d-%H%M%S)
47
+
48
+ # Collections
49
+ # Collections (now split into multiple files by folder)
50
+ CRUD_COLLECTIONS_DIR="$POSTMAN_DIR/collections/crud"
51
+ FILES_COLLECTIONS_DIR="$POSTMAN_DIR/collections/files"
52
+ V2_COLLECTIONS_DIR="$POSTMAN_DIR/collections/v2"
53
+ LEGACY_COLLECTIONS_DIR="$POSTMAN_DIR/collections/legacy"
54
+ ENVIRONMENT="$POSTMAN_DIR/environments/staging.postman_environment.json"
55
+
56
+ # Parse arguments
57
+ RUN_V3=false
58
+ RUN_V2=false
59
+ RUN_LEGACY=false
60
+ SHOW_HELP=false
61
+ MULTI_PRODUCT=false
62
+ RUN_ALL=true
63
+
64
+ # Multi-product configuration
65
+ PRODUCTS="freshdesk freshservice freshworks-crm"
66
+ declare -A PRODUCT_DISPLAY_NAMES
67
+ PRODUCT_DISPLAY_NAMES["freshdesk"]="Freshdesk"
68
+ PRODUCT_DISPLAY_NAMES["freshservice"]="Freshservice"
69
+ PRODUCT_DISPLAY_NAMES["freshworks-crm"]="Freshworks CRM"
70
+ declare -A PRODUCT_IDS
71
+ PRODUCT_IDS["freshdesk"]="9"
72
+ PRODUCT_IDS["freshservice"]="3"
73
+ PRODUCT_IDS["freshworks-crm"]="39"
74
+ declare -A PRODUCT_NAMES
75
+ PRODUCT_NAMES["freshdesk"]="freshdesk"
76
+ PRODUCT_NAMES["freshservice"]="freshservice"
77
+ PRODUCT_NAMES["freshworks-crm"]="freshworks_crm"
78
+ declare -A PRODUCT_FILE_IDS
79
+ PRODUCT_FILE_IDS["freshdesk"]="staging_fd_external_file_123"
80
+ PRODUCT_FILE_IDS["freshservice"]="staging_fs_external_file_123"
81
+ PRODUCT_FILE_IDS["freshworks-crm"]="staging_crm_external_file_123"
82
+
83
+ # Per-collection status tracking
84
+ declare -A COLLECTION_STATUS
85
+ declare -A COLLECTION_PASSED
86
+ declare -A COLLECTION_FAILED
87
+
88
+ # For multi-product, we track per product-collection
89
+ # For single product, we track per collection
90
+ COLLECTION_STATUS["V3_CRUD"]="Not Run"
91
+ COLLECTION_STATUS["V3_FILES"]="Not Run"
92
+ COLLECTION_STATUS["V2"]="Not Run"
93
+ COLLECTION_STATUS["LEGACY"]="Not Run"
94
+ COLLECTION_PASSED["V3_CRUD"]=0
95
+ COLLECTION_PASSED["V3_FILES"]=0
96
+ COLLECTION_PASSED["V2"]=0
97
+ COLLECTION_PASSED["LEGACY"]=0
98
+ COLLECTION_FAILED["V3_CRUD"]=0
99
+ COLLECTION_FAILED["V3_FILES"]=0
100
+ COLLECTION_FAILED["V2"]=0
101
+ COLLECTION_FAILED["LEGACY"]=0
102
+
103
+ # Multi-product tracking
104
+ declare -A PRODUCT_STATUS
105
+ PRODUCT_STATUS["freshdesk"]="Not Run"
106
+ PRODUCT_STATUS["freshservice"]="Not Run"
107
+ PRODUCT_STATUS["freshworks-crm"]="Not Run"
108
+
109
+ for arg in "$@"; do
110
+ case $arg in
111
+ --v3)
112
+ RUN_V3=true
113
+ RUN_ALL=false
114
+ ;;
115
+ --v2)
116
+ RUN_V2=true
117
+ RUN_ALL=false
118
+ ;;
119
+ --legacy)
120
+ RUN_LEGACY=true
121
+ RUN_ALL=false
122
+ ;;
123
+ --multi-product)
124
+ MULTI_PRODUCT=true
125
+ RUN_V3=true
126
+ RUN_ALL=false
127
+ ;;
128
+ --help|-h)
129
+ SHOW_HELP=true
130
+ ;;
131
+ esac
132
+ done
133
+
134
+ if [ "$SHOW_HELP" = true ]; then
135
+ head -33 "$0" | tail -30
136
+ exit 0
137
+ fi
138
+
139
+ if [ "$RUN_ALL" = true ]; then
140
+ RUN_V3=true
141
+ RUN_V2=true
142
+ # Skip Legacy tests in staging by default - they run locally via WireMock.
143
+ RUN_LEGACY=false
144
+ fi
145
+
146
+ echo "========================================="
147
+ if [ "$MULTI_PRODUCT" = true ]; then
148
+ echo "${REPORT_LOGO} Staging Tests (Multi-Product)"
149
+ else
150
+ echo "${REPORT_LOGO} Staging Tests"
151
+ fi
152
+ echo "========================================="
153
+ echo ""
154
+ if [ "$MULTI_PRODUCT" = true ]; then
155
+ echo "Mode: Multi-Product Testing"
156
+ echo "Products:"
157
+ echo " - Freshdesk (product_id: 9)"
158
+ echo " - Freshservice (product_id: 3)"
159
+ echo " - Freshworks CRM (product_id: 39)"
160
+ echo ""
161
+ echo "Test Suites: V3 API (CRUD + Files) for each product"
162
+ else
163
+ echo "Test Suites:"
164
+ [ "$RUN_V3" = true ] && echo " ✓ V3 API (CRUD + Files)"
165
+ [ "$RUN_V2" = true ] && echo " ✓ V2 API (OAuth, Transfer)"
166
+ [ "$RUN_LEGACY" = true ] && echo " ✓ Legacy API"
167
+ [ "$RUN_LEGACY" = false ] && echo " ⊘ Legacy API (skipped)"
168
+ fi
169
+ echo ""
170
+
171
+ # Check prerequisites
172
+ echo "Checking prerequisites..."
173
+ command -v jq >/dev/null 2>&1 || { echo "Error: jq required" >&2; exit 1; }
174
+ command -v aws >/dev/null 2>&1 || { echo "Error: AWS CLI required" >&2; exit 1; }
175
+ command -v node >/dev/null 2>&1 || { echo "Error: Node.js required" >&2; exit 1; }
176
+
177
+ if [ ! -f "$CONFIG_FILE" ]; then
178
+ echo "Error: Config file not found: $CONFIG_FILE"
179
+ exit 1
180
+ fi
181
+
182
+
183
+ echo "✓ Prerequisites OK"
184
+ echo ""
185
+
186
+ # Load configuration
187
+ echo "========================================="
188
+ echo "Step 1: Loading Configuration"
189
+ echo "========================================="
190
+ AWS_SECRET_NAME=$(jq -r '.aws.secretName' "$CONFIG_FILE")
191
+ AWS_REGION=$(jq -r '.aws.region' "$CONFIG_FILE")
192
+ AWS_CLIENT=$(jq -r '.aws.client' "$CONFIG_FILE")
193
+ AWS_ISSUER=$(jq -r '.aws.issuer' "$CONFIG_FILE")
194
+ BASE_URL=$(jq -r '.base_url' "$CONFIG_FILE")
195
+ echo " Base URL: $BASE_URL"
196
+ echo ""
197
+
198
+ # Fetch JWT secret from AWS
199
+ echo "========================================="
200
+ echo "Step 2: Fetching JWT Secret from AWS"
201
+ echo "========================================="
202
+ JWT_SECRET=$("$SCRIPT_DIR/../auth/get-aws-secret.sh" "$AWS_SECRET_NAME" "$AWS_CLIENT" "$AWS_ISSUER" "$AWS_REGION") || {
203
+ echo "Error: Failed to fetch JWT secret"
204
+ exit 1
205
+ }
206
+ echo "✓ JWT secret fetched"
207
+ echo ""
208
+
209
+ # Generate JWT token
210
+ echo "========================================="
211
+ echo "Step 3: Generating JWT Token"
212
+ echo "========================================="
213
+ export JWT_SECRET="$JWT_SECRET"
214
+ export API_ISSUER=$(jq -r '.jwt.api_issuer' "$CONFIG_FILE")
215
+ export API_ACCOUNT_ID=$(jq -r '.jwt.api_account_id' "$CONFIG_FILE")
216
+ export API_ACCOUNT_DOMAIN=$(jq -r '.jwt.api_account_domain' "$CONFIG_FILE")
217
+ export API_USER_ID=$(jq -r '.jwt.api_user_id' "$CONFIG_FILE")
218
+ export API_PRODUCT=$(jq -r '.jwt.api_product' "$CONFIG_FILE")
219
+ export API_PRODUCT_ID=$(jq -r '.jwt.api_product_id' "$CONFIG_FILE")
220
+ export API_USER_ROLE=$(jq -r '.jwt.api_user_role' "$CONFIG_FILE")
221
+ export API_POD=$(jq -r '.jwt.api_pod' "$CONFIG_FILE")
222
+ export API_SKUS=$(jq -r '.jwt.api_skus' "$CONFIG_FILE")
223
+ export SKU=$(jq -r '.jwt.sku' "$CONFIG_FILE")
224
+ export IS_SOURCE_INTERNAL=$(jq -r '.jwt.is_source_internal' "$CONFIG_FILE")
225
+ export ORGANISATION_ID=$(jq -r '.jwt.organisation_id' "$CONFIG_FILE")
226
+ export FRESHID_ACCOUNT_ID=$(jq -r '.jwt.freshid_account_id' "$CONFIG_FILE")
227
+ export API_JWT_EXPIRY=$(jq -r '.jwt.api_jwt_expiry' "$CONFIG_FILE")
228
+
229
+ JWT_TOKEN=$("$SCRIPT_DIR/../auth/generate-jwt.sh") || {
230
+ echo "Error: Failed to generate JWT token"
231
+ exit 1
232
+ }
233
+ unset JWT_SECRET # Clear secret immediately
234
+ echo "✓ JWT token generated"
235
+ echo ""
236
+
237
+ # Install Newman
238
+ echo "========================================="
239
+ echo "Step 4: Installing Newman"
240
+ echo "========================================="
241
+ cd "$POSTMAN_DIR"
242
+ npm ci --ignore-scripts --silent 2>/dev/null || npm install --ignore-scripts --silent
243
+ echo "✓ Newman ready"
244
+ echo ""
245
+
246
+ mkdir -p "$REPORT_DIR"
247
+ mkdir -p "$REPORT_DIR/json"
248
+
249
+ # Test parameters from config
250
+ EXTENSION_ID=$(jq -r '.test_data.extension_id' "$CONFIG_FILE")
251
+ VERSION_ID=$(jq -r '.test_data.version_id' "$CONFIG_FILE")
252
+ EXTENSION_TYPE=$(jq -r '.test_data.extension_type' "$CONFIG_FILE")
253
+
254
+ # Initialize results
255
+ TOTAL_PASSED=0
256
+ TOTAL_FAILED=0
257
+ declare -a RESULTS
258
+
259
+ # Parse Newman output to extract assertion counts
260
+ # Newman outputs a table like: │ assertions │ 20 │ 0 │
261
+ parse_newman_results() {
262
+ local output_file="$1"
263
+ local assertions_line=$(grep "assertions" "$output_file" 2>/dev/null | head -1)
264
+
265
+ NEWMAN_EXECUTED=$(echo "$assertions_line" | awk -F'│' '{gsub(/[^0-9]/,"",$3); print $3}')
266
+ NEWMAN_FAILED=$(echo "$assertions_line" | awk -F'│' '{gsub(/[^0-9]/,"",$4); print $4}')
267
+
268
+ NEWMAN_EXECUTED=${NEWMAN_EXECUTED:-0}
269
+ NEWMAN_FAILED=${NEWMAN_FAILED:-0}
270
+ NEWMAN_PASSED=$((NEWMAN_EXECUTED > 0 ? NEWMAN_EXECUTED - NEWMAN_FAILED : 0))
271
+ }
272
+
273
+ # Helper function to run all collections in a directory
274
+ run_collection_dir() {
275
+ local dir_name="$1"
276
+ local collections_dir="$2"
277
+ local report_prefix="$3"
278
+ local collection_key="$4"
279
+
280
+ echo "========================================="
281
+ echo "Running: $dir_name"
282
+ echo "========================================="
283
+
284
+ local dir_passed=0
285
+ local dir_failed=0
286
+ local all_passed=true
287
+
288
+ # Create a working copy of the environment file to chain variables between collections
289
+ local working_env="/tmp/newman-staging-env-${collection_key}.json"
290
+ cp "$ENVIRONMENT" "$working_env"
291
+
292
+ # Run collections in numerical order
293
+ for collection_file in "$collections_dir"/*.json; do
294
+ if [ ! -f "$collection_file" ]; then
295
+ continue
296
+ fi
297
+
298
+ local basename=$(basename "$collection_file" .json)
299
+ local report_name="${report_prefix}-${basename}"
300
+
301
+ echo ""
302
+ echo " → Running: $basename"
303
+ echo ""
304
+
305
+ local html_report="$REPORT_DIR/$report_name-staging-$TIMESTAMP.html"
306
+ local json_report="$REPORT_DIR/json/$report_name-staging-$TIMESTAMP.json"
307
+ local env_export="/tmp/newman-env-export-staging-${basename}.json"
308
+
309
+ if "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
310
+ --environment "$working_env" \
311
+ --env-var "auth_token=$JWT_TOKEN" \
312
+ --env-var "base_url=$BASE_URL" \
313
+ --env-var "base_url_v2=${BASE_URL%/api/v3}/api/v2" \
314
+ --env-var "legacy_base_url=${BASE_URL%/api/v3}" \
315
+ --env-var "extension_id=$EXTENSION_ID" \
316
+ --env-var "version_id=$VERSION_ID" \
317
+ --env-var "extension_type=$EXTENSION_TYPE" \
318
+ --env-var "product_id=$API_PRODUCT_ID" \
319
+ --env-var "account_id=$API_ACCOUNT_ID" \
320
+ --env-var "organisation_id=$ORGANISATION_ID" \
321
+ --env-var "freshid_account_id=$FRESHID_ACCOUNT_ID" \
322
+ --env-var "user_role=$API_USER_ROLE" \
323
+ --reporters cli,htmlextra,json \
324
+ --reporter-htmlextra-export "$html_report" \
325
+ --reporter-htmlextra-title "${REPORT_LOGO} - $dir_name - $basename (Staging)" \
326
+ --reporter-htmlextra-darkTheme \
327
+ --reporter-json-export "$json_report" \
328
+ --export-environment "$env_export" \
329
+ --timeout-request 30000 \
330
+ --delay-request 200 2>&1 | tee /tmp/newman-output.txt; then
331
+
332
+ # Extract pass/fail counts using proper parser
333
+ parse_newman_results /tmp/newman-output.txt
334
+
335
+ dir_passed=$((dir_passed + NEWMAN_PASSED))
336
+ dir_failed=$((dir_failed + NEWMAN_FAILED))
337
+
338
+ # Update working environment for next collection (chain variables like installation_id)
339
+ if [ -f "$env_export" ]; then
340
+ cp "$env_export" "$working_env"
341
+ fi
342
+
343
+ echo " ✓ $basename: PASSED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)"
344
+ else
345
+ # Extract counts even on failure using proper parser
346
+ parse_newman_results /tmp/newman-output.txt
347
+
348
+ dir_passed=$((dir_passed + NEWMAN_PASSED))
349
+ dir_failed=$((dir_failed + NEWMAN_FAILED))
350
+ all_passed=false
351
+
352
+ # Update working environment even on failure (to preserve any variables set)
353
+ if [ -f "$env_export" ]; then
354
+ cp "$env_export" "$working_env"
355
+ fi
356
+
357
+ echo " ✗ $basename: FAILED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)"
358
+ fi
359
+ done
360
+
361
+ # Cleanup temporary environment files
362
+ rm -f "$working_env" /tmp/newman-env-export-staging-*.json
363
+
364
+ # Update collection status
365
+ if [ "$all_passed" = true ] && [ "$dir_failed" -eq 0 ]; then
366
+ COLLECTION_STATUS["$collection_key"]="✓ PASSED"
367
+ RESULTS+=("$dir_name: ✓ PASSED ($dir_passed passed, $dir_failed failed)")
368
+ echo ""
369
+ echo "✓ $dir_name: ALL PASSED"
370
+ else
371
+ COLLECTION_STATUS["$collection_key"]="✗ FAILED"
372
+ RESULTS+=("$dir_name: ✗ FAILED ($dir_passed passed, $dir_failed failed)")
373
+ echo ""
374
+ echo "✗ $dir_name: SOME FAILED"
375
+ fi
376
+
377
+ COLLECTION_PASSED["$collection_key"]=$dir_passed
378
+ COLLECTION_FAILED["$collection_key"]=$dir_failed
379
+ TOTAL_PASSED=$((TOTAL_PASSED + dir_passed))
380
+ TOTAL_FAILED=$((TOTAL_FAILED + dir_failed))
381
+
382
+ [ "$all_passed" = true ] && [ "$dir_failed" -eq 0 ]
383
+ }
384
+
385
+ # Run collection helper
386
+ run_collection() {
387
+ local name="$1"
388
+ local collection="$2"
389
+ local report_name="$3"
390
+ local collection_key="$4" # Key for status tracking
391
+
392
+ echo "========================================="
393
+ echo "Running: $name"
394
+ echo "========================================="
395
+
396
+ local html_report="$REPORT_DIR/$report_name-staging-$TIMESTAMP.html"
397
+ local json_report="$REPORT_DIR/json/$report_name-staging-$TIMESTAMP.json"
398
+
399
+ if "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection" \
400
+ --environment "$ENVIRONMENT" \
401
+ --env-var "auth_token=$JWT_TOKEN" \
402
+ --env-var "base_url=$BASE_URL" \
403
+ --env-var "base_url_v2=${BASE_URL%/api/v3}/api/v2" \
404
+ --env-var "legacy_base_url=${BASE_URL%/api/v3}" \
405
+ --env-var "extension_id=$EXTENSION_ID" \
406
+ --env-var "version_id=$VERSION_ID" \
407
+ --env-var "extension_type=$EXTENSION_TYPE" \
408
+ --env-var "product_id=$API_PRODUCT_ID" \
409
+ --env-var "account_id=$API_ACCOUNT_ID" \
410
+ --env-var "organisation_id=$ORGANISATION_ID" \
411
+ --env-var "freshid_account_id=$FRESHID_ACCOUNT_ID" \
412
+ --env-var "user_role=$API_USER_ROLE" \
413
+ --reporters cli,htmlextra,json \
414
+ --reporter-htmlextra-export "$html_report" \
415
+ --reporter-htmlextra-title "${REPORT_LOGO} - $name (Staging)" \
416
+ --reporter-htmlextra-darkTheme \
417
+ --reporter-json-export "$json_report" \
418
+ --timeout-request 30000 \
419
+ --delay-request 200 2>&1 | tee /tmp/newman-output.txt; then
420
+
421
+ # Extract pass/fail counts using proper parser
422
+ parse_newman_results /tmp/newman-output.txt
423
+
424
+ echo "✓ $name: PASSED"
425
+ RESULTS+=("$name: ✓ PASSED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)")
426
+ COLLECTION_STATUS["$collection_key"]="✓ PASSED"
427
+ COLLECTION_PASSED["$collection_key"]=$NEWMAN_PASSED
428
+ COLLECTION_FAILED["$collection_key"]=$NEWMAN_FAILED
429
+ TOTAL_PASSED=$((TOTAL_PASSED + NEWMAN_PASSED))
430
+ TOTAL_FAILED=$((TOTAL_FAILED + NEWMAN_FAILED))
431
+ return 0
432
+ else
433
+ # Extract counts even on failure using proper parser
434
+ parse_newman_results /tmp/newman-output.txt
435
+
436
+ echo "✗ $name: FAILED"
437
+ RESULTS+=("$name: ✗ FAILED ($NEWMAN_PASSED passed, $NEWMAN_FAILED failed)")
438
+ COLLECTION_STATUS["$collection_key"]="✗ FAILED"
439
+ COLLECTION_PASSED["$collection_key"]=$NEWMAN_PASSED
440
+ COLLECTION_FAILED["$collection_key"]=$NEWMAN_FAILED
441
+ TOTAL_PASSED=$((TOTAL_PASSED + NEWMAN_PASSED))
442
+ TOTAL_FAILED=$((TOTAL_FAILED + NEWMAN_FAILED))
443
+ return 1
444
+ fi
445
+ }
446
+
447
+ # Run tests
448
+ echo ""
449
+
450
+ if [ "$MULTI_PRODUCT" = true ]; then
451
+ # Multi-product mode: Run V3 tests for each product
452
+ for product in $PRODUCTS; do
453
+ echo ""
454
+ echo "========================================="
455
+ echo "Testing Product: $product"
456
+ echo "========================================="
457
+
458
+ # Use base staging environment, override product-specific vars
459
+ PRODUCT_JWT_TOKEN="$JWT_TOKEN"
460
+ PRODUCT_ID="${PRODUCT_IDS[$product]}"
461
+ PRODUCT_NAME="${PRODUCT_NAMES[$product]}"
462
+ PRODUCT_FILE_ID="${PRODUCT_FILE_IDS[$product]}"
463
+
464
+ PRODUCT_PASSED=true
465
+
466
+ # Each product gets its own report subdirectory
467
+ PRODUCT_REPORT_DIR="$REPORT_DIR/${product}"
468
+ PRODUCT_JSON_DIR="$REPORT_DIR/json/${product}"
469
+ mkdir -p "$PRODUCT_REPORT_DIR" "$PRODUCT_JSON_DIR"
470
+
471
+ # Working env copy for chaining variables between collections
472
+ PRODUCT_WORKING_ENV="/tmp/newman-staging-env-${product}-${TIMESTAMP}.json"
473
+ cp "$ENVIRONMENT" "$PRODUCT_WORKING_ENV"
474
+
475
+ # Run CRUD tests for this product
476
+ crud_key="${product}_CRUD"
477
+ COLLECTION_STATUS["$crud_key"]="Not Run"
478
+ COLLECTION_PASSED["$crud_key"]=0
479
+ COLLECTION_FAILED["$crud_key"]=0
480
+ CRUD_PASSED=0; CRUD_FAILED=0; CRUD_ALL_PASSED=true
481
+
482
+ echo ""
483
+ echo "Running CRUD API tests for $product..."
484
+ for collection_file in "$CRUD_COLLECTIONS_DIR"/*.json; do
485
+ [ ! -f "$collection_file" ] && continue
486
+ basename=$(basename "$collection_file" .json)
487
+ REPORT_FILE="$PRODUCT_REPORT_DIR/crud-${basename}-$TIMESTAMP.html"
488
+ JSON_REPORT="$PRODUCT_JSON_DIR/crud-${basename}-$TIMESTAMP.json"
489
+ ENV_EXPORT="/tmp/newman-env-export-staging-${product}-crud-${basename}.json"
490
+ echo " → $basename"
491
+ "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
492
+ --environment "$PRODUCT_WORKING_ENV" \
493
+ --env-var "auth_token=$PRODUCT_JWT_TOKEN" \
494
+ --env-var "base_url=$BASE_URL" \
495
+ --env-var "base_url_v2=${BASE_URL%/api/v3}/api/v2" \
496
+ --env-var "legacy_base_url=${BASE_URL%/api/v3}" \
497
+ --env-var "extension_id=$EXTENSION_ID" \
498
+ --env-var "version_id=$VERSION_ID" \
499
+ --env-var "extension_type=$EXTENSION_TYPE" \
500
+ --env-var "product_id=$PRODUCT_ID" \
501
+ --env-var "product_name=$PRODUCT_NAME" \
502
+ --env-var "file_external_id=$PRODUCT_FILE_ID" \
503
+ --env-var "account_id=$API_ACCOUNT_ID" \
504
+ --env-var "organisation_id=$ORGANISATION_ID" \
505
+ --env-var "freshid_account_id=$FRESHID_ACCOUNT_ID" \
506
+ --env-var "user_role=$API_USER_ROLE" \
507
+ --reporters cli,htmlextra,json \
508
+ --reporter-htmlextra-export "$REPORT_FILE" \
509
+ --reporter-htmlextra-title "${REPORT_LOGO} - $product - CRUD - $basename (Staging)" \
510
+ --reporter-htmlextra-darkTheme \
511
+ --reporter-json-export "$JSON_REPORT" \
512
+ --export-environment "$ENV_EXPORT" \
513
+ --timeout-request 30000 \
514
+ --delay-request 200 2>&1 | tee /tmp/newman-output-${product}.txt
515
+ parse_newman_results /tmp/newman-output-${product}.txt
516
+ CRUD_PASSED=$((CRUD_PASSED + NEWMAN_PASSED))
517
+ CRUD_FAILED=$((CRUD_FAILED + NEWMAN_FAILED))
518
+ [ -f "$ENV_EXPORT" ] && cp "$ENV_EXPORT" "$PRODUCT_WORKING_ENV"
519
+ [ "$NEWMAN_FAILED" -gt 0 ] && CRUD_ALL_PASSED=false
520
+ done
521
+ COLLECTION_PASSED["$crud_key"]=$CRUD_PASSED
522
+ COLLECTION_FAILED["$crud_key"]=$CRUD_FAILED
523
+ TOTAL_PASSED=$((TOTAL_PASSED + CRUD_PASSED))
524
+ TOTAL_FAILED=$((TOTAL_FAILED + CRUD_FAILED))
525
+ if [ "$CRUD_ALL_PASSED" = true ]; then
526
+ echo "✓ CRUD tests passed for $product ($CRUD_PASSED passed, $CRUD_FAILED failed)"
527
+ COLLECTION_STATUS["$crud_key"]="✓ PASSED"
528
+ else
529
+ echo "✗ CRUD tests failed for $product ($CRUD_PASSED passed, $CRUD_FAILED failed)"
530
+ COLLECTION_STATUS["$crud_key"]="✗ FAILED"
531
+ PRODUCT_PASSED=false
532
+ fi
533
+
534
+ # Run Files tests for this product
535
+ files_key="${product}_FILES"
536
+ COLLECTION_STATUS["$files_key"]="Not Run"
537
+ COLLECTION_PASSED["$files_key"]=0
538
+ COLLECTION_FAILED["$files_key"]=0
539
+ FILES_PASSED=0; FILES_FAILED=0; FILES_ALL_PASSED=true
540
+
541
+ echo ""
542
+ echo "Running Files API tests for $product..."
543
+ for collection_file in "$FILES_COLLECTIONS_DIR"/*.json; do
544
+ [ ! -f "$collection_file" ] && continue
545
+ basename=$(basename "$collection_file" .json)
546
+ REPORT_FILE="$PRODUCT_REPORT_DIR/files-${basename}-$TIMESTAMP.html"
547
+ JSON_REPORT="$PRODUCT_JSON_DIR/files-${basename}-$TIMESTAMP.json"
548
+ ENV_EXPORT="/tmp/newman-env-export-staging-${product}-files-${basename}.json"
549
+ echo " → $basename"
550
+ "$POSTMAN_DIR/node_modules/.bin/newman" run "$collection_file" \
551
+ --environment "$PRODUCT_WORKING_ENV" \
552
+ --env-var "auth_token=$PRODUCT_JWT_TOKEN" \
553
+ --env-var "base_url=$BASE_URL" \
554
+ --env-var "base_url_v2=${BASE_URL%/api/v3}/api/v2" \
555
+ --env-var "legacy_base_url=${BASE_URL%/api/v3}" \
556
+ --env-var "extension_id=$EXTENSION_ID" \
557
+ --env-var "version_id=$VERSION_ID" \
558
+ --env-var "extension_type=$EXTENSION_TYPE" \
559
+ --env-var "product_id=$PRODUCT_ID" \
560
+ --env-var "product_name=$PRODUCT_NAME" \
561
+ --env-var "file_external_id=$PRODUCT_FILE_ID" \
562
+ --env-var "account_id=$API_ACCOUNT_ID" \
563
+ --env-var "organisation_id=$ORGANISATION_ID" \
564
+ --env-var "freshid_account_id=$FRESHID_ACCOUNT_ID" \
565
+ --env-var "user_role=$API_USER_ROLE" \
566
+ --reporters cli,htmlextra,json \
567
+ --reporter-htmlextra-export "$REPORT_FILE" \
568
+ --reporter-htmlextra-title "${REPORT_LOGO} - $product - Files - $basename (Staging)" \
569
+ --reporter-htmlextra-darkTheme \
570
+ --reporter-json-export "$JSON_REPORT" \
571
+ --export-environment "$ENV_EXPORT" \
572
+ --timeout-request 30000 \
573
+ --delay-request 200 2>&1 | tee /tmp/newman-output-${product}.txt
574
+ parse_newman_results /tmp/newman-output-${product}.txt
575
+ FILES_PASSED=$((FILES_PASSED + NEWMAN_PASSED))
576
+ FILES_FAILED=$((FILES_FAILED + NEWMAN_FAILED))
577
+ [ -f "$ENV_EXPORT" ] && cp "$ENV_EXPORT" "$PRODUCT_WORKING_ENV"
578
+ [ "$NEWMAN_FAILED" -gt 0 ] && FILES_ALL_PASSED=false
579
+ done
580
+ rm -f "$PRODUCT_WORKING_ENV" /tmp/newman-env-export-staging-${product}-*.json
581
+ COLLECTION_PASSED["$files_key"]=$FILES_PASSED
582
+ COLLECTION_FAILED["$files_key"]=$FILES_FAILED
583
+ TOTAL_PASSED=$((TOTAL_PASSED + FILES_PASSED))
584
+ TOTAL_FAILED=$((TOTAL_FAILED + FILES_FAILED))
585
+ if [ "$FILES_ALL_PASSED" = true ]; then
586
+ echo "✓ Files tests passed for $product ($FILES_PASSED passed, $FILES_FAILED failed)"
587
+ COLLECTION_STATUS["$files_key"]="✓ PASSED"
588
+ else
589
+ echo "✗ Files tests failed for $product ($FILES_PASSED passed, $FILES_FAILED failed)"
590
+ COLLECTION_STATUS["$files_key"]="✗ FAILED"
591
+ PRODUCT_PASSED=false
592
+ fi
593
+
594
+ # Update product status
595
+ if [ "$PRODUCT_PASSED" = true ]; then
596
+ PRODUCT_STATUS["$product"]="✓ PASSED"
597
+ else
598
+ PRODUCT_STATUS["$product"]="✗ FAILED"
599
+ fi
600
+ done
601
+ else
602
+ # Single product mode: Run selected test suites
603
+ if [ "$RUN_V3" = true ]; then
604
+ run_collection_dir "V3 CRUD API" "$CRUD_COLLECTIONS_DIR" "crud" "V3_CRUD" || true
605
+ echo ""
606
+ run_collection_dir "V3 Files API" "$FILES_COLLECTIONS_DIR" "files" "V3_FILES" || true
607
+ echo ""
608
+ fi
609
+
610
+ if [ "$RUN_V2" = true ]; then
611
+ run_collection_dir "V2 API" "$V2_COLLECTIONS_DIR" "v2" "V2" || true
612
+ echo ""
613
+ fi
614
+
615
+ if [ "$RUN_LEGACY" = true ]; then
616
+ run_collection_dir "Legacy API" "$LEGACY_COLLECTIONS_DIR" "legacy" "LEGACY" || true
617
+ echo ""
618
+ fi
619
+ fi
620
+
621
+ # Summary
622
+ echo ""
623
+ echo "========================================="
624
+ if [ "$MULTI_PRODUCT" = true ]; then
625
+ echo "Multi-Product Test Results"
626
+ else
627
+ echo "Staging Test Results"
628
+ fi
629
+ echo "========================================="
630
+ echo ""
631
+
632
+ if [ "$MULTI_PRODUCT" = true ]; then
633
+ # Show per-product results
634
+ echo "Product Results:"
635
+ for product in $PRODUCTS; do
636
+ status="${PRODUCT_STATUS[$product]}"
637
+ crud_key="${product}_CRUD"
638
+ files_key="${product}_FILES"
639
+ crud_status="${COLLECTION_STATUS[$crud_key]}"
640
+ files_status="${COLLECTION_STATUS[$files_key]}"
641
+ crud_passed="${COLLECTION_PASSED[$crud_key]}"
642
+ crud_failed="${COLLECTION_FAILED[$crud_key]}"
643
+ files_passed="${COLLECTION_PASSED[$files_key]}"
644
+ files_failed="${COLLECTION_FAILED[$files_key]}"
645
+
646
+ # Use display name
647
+ product_display="${PRODUCT_DISPLAY_NAMES[$product]}"
648
+
649
+ if [ "$status" != "Not Run" ]; then
650
+ echo " $product_display: $status"
651
+ if [ "$crud_status" != "Not Run" ]; then
652
+ echo " - CRUD API: $crud_status ($crud_passed passed, $crud_failed failed)"
653
+ fi
654
+ if [ "$files_status" != "Not Run" ]; then
655
+ echo " - Files API: $files_status ($files_passed passed, $files_failed failed)"
656
+ fi
657
+ fi
658
+ done
659
+ else
660
+ # Show per-collection results (original behavior)
661
+ echo "Collection Results:"
662
+ for collection_key in "V3_CRUD" "V3_FILES" "V2" "LEGACY"; do
663
+ status="${COLLECTION_STATUS[$collection_key]}"
664
+ passed="${COLLECTION_PASSED[$collection_key]}"
665
+ failed="${COLLECTION_FAILED[$collection_key]}"
666
+
667
+ if [ "$status" != "Not Run" ]; then
668
+ case $collection_key in
669
+ V3_CRUD)
670
+ echo " V3 CRUD API: $status ($passed passed, $failed failed)"
671
+ ;;
672
+ V3_FILES)
673
+ echo " V3 Files API: $status ($passed passed, $failed failed)"
674
+ ;;
675
+ V2)
676
+ echo " V2 API: $status ($passed passed, $failed failed)"
677
+ ;;
678
+ LEGACY)
679
+ echo " Legacy API: $status ($passed passed, $failed failed)"
680
+ ;;
681
+ esac
682
+ fi
683
+ done
684
+ fi
685
+
686
+ echo ""
687
+ echo "Overall Total: $TOTAL_PASSED passed, $TOTAL_FAILED failed"
688
+ echo ""
689
+ echo "Reports: $REPORT_DIR/"
690
+ echo ""
691
+
692
+ # Calculate passed/failed collections
693
+ PASSED_COLLECTIONS=0
694
+ FAILED_COLLECTIONS=0
695
+ if [ "$MULTI_PRODUCT" = true ]; then
696
+ # Count products as collections for multi-product mode
697
+ for product in $PRODUCTS; do
698
+ if [ "${PRODUCT_STATUS[$product]}" = "PASSED" ]; then
699
+ PASSED_COLLECTIONS=$((PASSED_COLLECTIONS + 1))
700
+ else
701
+ FAILED_COLLECTIONS=$((FAILED_COLLECTIONS + 1))
702
+ fi
703
+ done
704
+ else
705
+ # Count individual collections
706
+ for collection_key in "V3_CRUD" "V3_FILES" "V2" "LEGACY"; do
707
+ status="${COLLECTION_STATUS[$collection_key]}"
708
+ if [ "$status" != "Not Run" ]; then
709
+ if [ "$status" = "✓ PASSED" ]; then
710
+ PASSED_COLLECTIONS=$((PASSED_COLLECTIONS + 1))
711
+ else
712
+ FAILED_COLLECTIONS=$((FAILED_COLLECTIONS + 1))
713
+ fi
714
+ fi
715
+ done
716
+ fi
717
+
718
+ # Generate consolidated report
719
+ if [ -f "$SCRIPT_DIR/../report-generators/generate-consolidated-report.sh" ]; then
720
+ echo "========================================="
721
+ echo "Generating Consolidated Report"
722
+ echo "========================================="
723
+ TEST_RESULT=0
724
+ if [ $TOTAL_FAILED -gt 0 ]; then
725
+ TEST_RESULT=1
726
+ fi
727
+ "$SCRIPT_DIR/../report-generators/generate-consolidated-report.sh" "staging" "$TIMESTAMP" "$REPORT_DIR" "$PASSED_COLLECTIONS" "$FAILED_COLLECTIONS" "$TEST_RESULT" || true
728
+ echo ""
729
+ fi
730
+
731
+ # Note: Reports are preserved across runs. Each script manages its own cleanup:
732
+ # - mutation-report.sh: keeps only latest mutation report
733
+ # - java-api-coverage-matrix.sh: keeps only latest coverage matrix
734
+ # - Test reports: kept indefinitely (user can manually clean postman/reports/)
735
+
736
+ if [ $TOTAL_FAILED -gt 0 ]; then
737
+ echo "❌ Some tests failed"
738
+ exit 1
739
+ else
740
+ echo "✅ All tests passed successfully!"
741
+ fi