@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,248 @@
1
+ ---
2
+ description: Test writing rules - MANDATORY when editing test files
3
+ globs:
4
+ - "**/*Test.java"
5
+ - "**/*IntegrationTest.java"
6
+ - "**/src/test/**/*.java"
7
+ - "**/src/integration-test/**/*.java"
8
+ ---
9
+
10
+ # Test Writing Rules - MANDATORY
11
+
12
+ All tests MUST survive PIT mutation testing. Weak assertions WILL fail.
13
+
14
+ ## CRITICAL: Mutation Testing (PIT)
15
+
16
+ - Target mutation score: **80%+**
17
+ - Run mutation tests: `./postman/scripts/report-generators/mutation-report.sh`
18
+ - Weak assertions allow mutants to survive = test is worthless
19
+
20
+ ## Assertion Strength - CRITICAL
21
+
22
+ NEVER write weak assertions. They prove nothing and mutants will survive.
23
+
24
+ ### WRONG - Weak Assertions (DO NOT USE):
25
+
26
+ ```java
27
+ assertNotNull(result); // WRONG - proves nothing
28
+ assertTrue(result.isPresent()); // WRONG - no value check
29
+ assertThat(list).isNotEmpty(); // WRONG - no content check
30
+ assertThat(count).isGreaterThan(0); // WRONG - boundary not tested
31
+ assertThat(result).isNotNull(); // WRONG - still proves nothing
32
+ verify(repository).save(any()); // WRONG - content not verified
33
+ ```
34
+
35
+ ### CORRECT - Strong Assertions (USE THESE):
36
+
37
+ ```java
38
+ assertThat(result.getId()).isEqualTo(expectedId); // Specific value
39
+ assertThat(result.getName()).isEqualTo("Expected Name"); // Exact match
40
+ assertThat(result.getStatus()).isEqualTo(Status.ACTIVE); // Enum value
41
+ assertThat(list).containsExactly(item1, item2); // Content verified
42
+ assertThat(list).hasSize(3); // Combined with content check
43
+ ```
44
+
45
+ ## Test All Output Fields
46
+
47
+ If a method returns an object with 5 fields, you MUST assert on ALL 5 fields.
48
+
49
+ ### WRONG:
50
+
51
+ ```java
52
+ AppDTO result = service.getApp("123");
53
+ assertThat(result.getId()).isEqualTo("123"); // WRONG - only checking 1 field
54
+ ```
55
+
56
+ ### CORRECT:
57
+
58
+ ```java
59
+ AppDTO result = service.getApp("123");
60
+ assertThat(result.getId()).isEqualTo("123");
61
+ assertThat(result.getName()).isEqualTo("Test App");
62
+ assertThat(result.getType()).isEqualTo(AppType.PLATFORM);
63
+ assertThat(result.getStatus()).isEqualTo(Status.ACTIVE);
64
+ assertThat(result.getCreatedAt()).isNotNull(); // OK for timestamps
65
+ ```
66
+
67
+ ## Verify Saved Arguments with ArgumentCaptor
68
+
69
+ ### WRONG:
70
+
71
+ ```java
72
+ verify(repository).save(any()); // Mutant survives - content not verified
73
+ ```
74
+
75
+ ### CORRECT:
76
+
77
+ ```java
78
+ ArgumentCaptor<App> captor = ArgumentCaptor.forClass(App.class);
79
+ verify(repository).save(captor.capture());
80
+ App saved = captor.getValue();
81
+ assertThat(saved.getName()).isEqualTo("Expected Name");
82
+ assertThat(saved.getType()).isEqualTo(AppType.CUSTOM);
83
+ assertThat(saved.getStatus()).isEqualTo(Status.PENDING);
84
+ ```
85
+
86
+ ## Exception Messages MUST Be Verified
87
+
88
+ ### WRONG:
89
+
90
+ ```java
91
+ assertThrows(ResourceNotFoundException.class, () -> service.getApp("invalid"));
92
+ ```
93
+
94
+ ### CORRECT:
95
+
96
+ ```java
97
+ var ex = assertThrows(ResourceNotFoundException.class, () -> service.getApp("invalid"));
98
+ assertThat(ex.getMessage()).isEqualTo("App not found with id: invalid");
99
+ ```
100
+
101
+ ## Use Parameterized Tests for Combinations
102
+
103
+ For methods with multiple inputs, you MUST use `@ParameterizedTest`:
104
+
105
+ ```java
106
+ @ParameterizedTest
107
+ @EnumSource(AppType.class)
108
+ void getApps_shouldFilterByAllAppTypes(AppType type) {
109
+ // Test each enum value
110
+ }
111
+
112
+ @ParameterizedTest
113
+ @MethodSource("invalidInputs")
114
+ void create_shouldRejectInvalidInputs(String name, String expectedError) {
115
+ var ex = assertThrows(BadRequestException.class, () -> service.create(name));
116
+ assertThat(ex.getMessage()).contains(expectedError);
117
+ }
118
+
119
+ static Stream<Arguments> invalidInputs() {
120
+ return Stream.of(
121
+ Arguments.of(null, "Name is required"),
122
+ Arguments.of("", "Name is required"),
123
+ Arguments.of("a".repeat(256), "Name must not exceed 255")
124
+ );
125
+ }
126
+ ```
127
+
128
+ ## Test Naming Convention
129
+
130
+ Use: `methodName_whenCondition_shouldExpectedResult`
131
+
132
+ ```java
133
+ @Test
134
+ void getAppById_whenAppExists_shouldReturnAppDetails() { }
135
+
136
+ @Test
137
+ void getAppById_whenAppNotFound_shouldThrowResourceNotFoundException() { }
138
+
139
+ @Test
140
+ void createApp_whenValidRequest_shouldReturnCreatedApp() { }
141
+
142
+ @Test
143
+ void createApp_whenDuplicateName_shouldThrowConflictException() { }
144
+ ```
145
+
146
+ ## Unit Test Structure
147
+
148
+ ```java
149
+ @ExtendWith(MockitoExtension.class)
150
+ class AppServiceTest {
151
+ @Mock
152
+ private AppRepository appRepository;
153
+
154
+ @Mock
155
+ private AppMapper appMapper;
156
+
157
+ @InjectMocks
158
+ private AppService appService;
159
+
160
+ @Nested
161
+ class GetAppById {
162
+ @Test
163
+ void whenAppExists_shouldReturnAppDetails() {
164
+ // Given
165
+ String appId = "app-123";
166
+ App app = App.builder().externalId(appId).name("Test").build();
167
+ AppDetailsDTO expected = AppDetailsDTO.builder().id(appId).name("Test").build();
168
+
169
+ when(appRepository.findByExternalId(appId)).thenReturn(Optional.of(app));
170
+ when(appMapper.toDetailsDTO(app)).thenReturn(expected);
171
+
172
+ // When
173
+ AppDetailsDTO result = appService.getAppById(appId);
174
+
175
+ // Then
176
+ assertThat(result.getId()).isEqualTo(appId);
177
+ assertThat(result.getName()).isEqualTo("Test");
178
+ verify(appRepository).findByExternalId(appId);
179
+ }
180
+
181
+ @Test
182
+ void whenAppNotFound_shouldThrowException() {
183
+ // Given
184
+ when(appRepository.findByExternalId("invalid")).thenReturn(Optional.empty());
185
+
186
+ // When/Then
187
+ var ex = assertThrows(ResourceNotFoundException.class,
188
+ () -> appService.getAppById("invalid"));
189
+ assertThat(ex.getMessage()).isEqualTo("App not found with id: invalid");
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## Integration Test Structure
196
+
197
+ ```java
198
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
199
+ @AutoConfigureMockMvc
200
+ @ActiveProfiles("integration")
201
+ @Sql(scripts = "/data-sql/apps.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
202
+ @Sql(scripts = "/data-sql/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
203
+ class AppControllerIntegrationTest {
204
+ @Autowired
205
+ private MockMvc mockMvc;
206
+
207
+ @Autowired
208
+ private JdbcTemplate jdbcTemplate;
209
+
210
+ @MockBean
211
+ private FreshIDApiClient freshIDApiClient; // Mock external services only
212
+
213
+ @Test
214
+ void getApp_whenExists_shouldReturn200WithDetails() throws Exception {
215
+ mockMvc.perform(get("/api/v1/apps/{id}", "app-1")
216
+ .header("Authorization", "Bearer test-token"))
217
+ .andExpect(status().isOk())
218
+ .andExpect(jsonPath("$.id").value("app-1"))
219
+ .andExpect(jsonPath("$.name").value("Test App"))
220
+ .andExpect(jsonPath("$.type").value("PLATFORM"));
221
+ }
222
+
223
+ @Test
224
+ void getApp_whenNotFound_shouldReturn404() throws Exception {
225
+ mockMvc.perform(get("/api/v1/apps/{id}", "nonexistent")
226
+ .header("Authorization", "Bearer test-token"))
227
+ .andExpect(status().isNotFound())
228
+ .andExpect(jsonPath("$.detail").value("App not found with id: nonexistent"));
229
+ }
230
+ }
231
+ ```
232
+
233
+ ## Running Tests
234
+
235
+ | Command | What it runs |
236
+ |---------|--------------|
237
+ | `mvn test` | Unit tests only |
238
+ | `./postman/scripts/report-generators/mutation-report.sh` | PIT mutation testing |
239
+ | `cd postman/scripts && ./runners/run-tests-local.sh` | Postman API tests (local) |
240
+ | `cd postman/scripts && ./run-all.sh` | Full test suite |
241
+ | `./postman/scripts/report-generators/java-api-coverage-matrix.sh` | API coverage report |
242
+
243
+ ## Before Merge Checklist
244
+
245
+ 1. `mvn test` - all unit tests pass
246
+ 2. `./postman/scripts/runners/run-tests-local.sh` - all Postman tests pass
247
+ 3. `./postman/scripts/report-generators/java-api-coverage-matrix.sh` - 100% 2xx coverage
248
+ 4. `./postman/scripts/report-generators/mutation-report.sh` - 80%+ mutation score
@@ -0,0 +1,380 @@
1
+ # Postman/Newman Standards
2
+
3
+ Language-agnostic standards for writing and organizing Postman integration tests. Referenced by all language-specific skill files.
4
+
5
+ ---
6
+
7
+ ## Library scripts are staged from the package — never copied or committed
8
+
9
+ The pipeline's library scripts (`postman/scripts/lib/`, `runners/`,
10
+ `report-generators/`, `auth/`, `infra/`, `database/`) are **not** vendored into
11
+ the repo. They live in the installed `shiftleft-tools` package and are pulled in
12
+ on demand:
13
+
14
+ - `shiftleft test` (and the committed `postman/scripts/run-all.sh` shim) stage the
15
+ current scripts automatically before every run.
16
+ - `shiftleft stage-scripts` stages them on their own — run this first if you need
17
+ to invoke an individual staged script directly (e.g.
18
+ `./postman/scripts/report-generators/...`).
19
+
20
+ Staged scripts are gitignored and refreshed each run, so they always match the
21
+ installed package. **Do not copy scripts from a templates directory, write them
22
+ from scratch, or commit them.** Only `run-all.sh` (a thin shim) and repo-owned
23
+ files (collections, config, environments, Node's `setup-mocks.js`, Java's
24
+ `wiremock/mappings/`) are committed.
25
+
26
+ **Exception — a customized library script.** If a repo must customize a library
27
+ script (e.g. a service-specific `runners/run-tests-local.sh`), mark it protected
28
+ so staging won't overwrite it: `shiftleft protect runners/run-tests-local.sh`.
29
+ Protected paths are recorded in `.shiftleft.json` and must stay committed.
30
+
31
+ ---
32
+
33
+ ## Collection Organization
34
+
35
+ Collections live in `postman/collections/` with numbered naming for execution order.
36
+
37
+ **Naming convention**: `XX-description.json`
38
+
39
+ **Example structure** (explore actual folder for this project's collections):
40
+ ```
41
+ postman/collections/
42
+ ├── 01-bootstrap.json # Creates prerequisite test data, sets env vars
43
+ ├── 02-core-crud.json # Core CRUD operations
44
+ ├── 03-authorization.json # Auth tests (may be STAGING ONLY)
45
+ ├── 04-query-params.json # Sorting, filtering, pagination
46
+ ├── 05-validation.json # Invalid input, 400 errors
47
+ └── 99-cleanup.json # Deletes all test data, always runs last
48
+ ```
49
+
50
+ **Common patterns**:
51
+ - Authorization tests often skip in local (no API gateway)
52
+ - Internal/cache endpoints often skip in staging (not exposed)
53
+
54
+ ---
55
+
56
+ ## Writing Test Assertions
57
+
58
+ ### MANDATORY: One Status Code Per Test
59
+
60
+ Each test request MUST assert exactly ONE HTTP status code:
61
+
62
+ ```javascript
63
+ // CORRECT - Single status code
64
+ pm.test("Status code is 200", function () {
65
+ pm.response.to.have.status(200);
66
+ });
67
+
68
+ // WRONG - Multiple status codes
69
+ pm.test("Status is valid", function () {
70
+ pm.expect(pm.response.code).to.be.oneOf([200, 201]); // NEVER do this
71
+ });
72
+ ```
73
+
74
+ ### MANDATORY: Strong Value Assertions
75
+
76
+ ```javascript
77
+ // WRONG - Weak assertions
78
+ pm.test("Response has data", function () {
79
+ pm.expect(pm.response.json()).to.not.be.null; // Proves nothing
80
+ });
81
+
82
+ // CORRECT - Strong assertions
83
+ pm.test("Response has correct data", function () {
84
+ const json = pm.response.json();
85
+ pm.expect(json.id).to.eql("app-123");
86
+ pm.expect(json.name).to.eql("Expected Name");
87
+ pm.expect(json.type).to.eql("PLATFORM");
88
+ });
89
+ ```
90
+
91
+ ### FORBIDDEN: 5xx Status Code Assertions
92
+
93
+ NEVER assert on 5xx status codes - they indicate bugs, not expected behavior:
94
+
95
+ ```javascript
96
+ // FORBIDDEN - Never do this
97
+ pm.test("Status code is 500", function () {
98
+ pm.response.to.have.status(500);
99
+ });
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Environment-Specific Skip Pattern
105
+
106
+ Some tests behave differently between local and staging. Use this pattern:
107
+
108
+ ### Skip in Local (Staging-Only Test)
109
+
110
+ ```javascript
111
+ // Pre-request Script
112
+ if (pm.environment.get('environment') === 'local') {
113
+ console.log('[SKIP] Reason: JWT validation happens at gateway, local has no gateway | Env: local');
114
+ pm.execution.skipRequest();
115
+ }
116
+ ```
117
+
118
+ ### Skip in Staging (Local-Only Test)
119
+
120
+ ```javascript
121
+ // Pre-request Script
122
+ if (pm.environment.get('environment') !== 'local') {
123
+ console.log('[SKIP] Reason: Internal endpoint not exposed in staging | Env: staging');
124
+ pm.execution.skipRequest();
125
+ }
126
+ ```
127
+
128
+ ### Skip Log Format (MANDATORY)
129
+
130
+ ```
131
+ [SKIP] Reason: <why this test is skipped> | Env: <local|staging>
132
+ ```
133
+
134
+ ### Naming Convention for Environment-Specific Tests
135
+
136
+ Append `(local)` or `(staging)` to test names:
137
+
138
+ ```
139
+ "Create - Invalid Token (staging)" // Expects 401, skipped in local
140
+ "Create - Invalid Token (local)" // Expects 400, skipped in staging
141
+ "PATCH - 404 non-existent (staging)" // Expects 404, skipped in local
142
+ "PATCH - 403 non-existent (local)" // Expects 403, skipped in staging
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Why Tests Differ by Environment
148
+
149
+ | Scenario | Local Behavior | Staging Behavior |
150
+ |----------|---------------|------------------|
151
+ | Invalid JWT token | 400 Bad Request | 401 Unauthorized (API Gateway) |
152
+ | Non-existent resource with bad auth | 403 Forbidden | 404 Not Found |
153
+ | Internal endpoints | Works | Not exposed |
154
+ | Authorization checks | @PreAuthorize first | Resource lookup first |
155
+
156
+ ---
157
+
158
+ ## HTTP Status Coverage Requirements
159
+
160
+ Every endpoint MUST have tests for:
161
+
162
+ | Status | When | Priority |
163
+ |--------|------|----------|
164
+ | 200 OK | GET/PUT/PATCH success | High |
165
+ | 201 Created | POST success | High |
166
+ | 400 Bad Request | Invalid input/validation | High |
167
+ | 401 Unauthorized | Missing/invalid auth | High |
168
+ | 404 Not Found | Resource not found | High |
169
+ | 204 No Content | DELETE success | Medium |
170
+ | 409 Conflict | Duplicate/concurrent update | Medium |
171
+
172
+ ---
173
+
174
+ ## Query Parameter Coverage - MANDATORY
175
+
176
+ For endpoints with query parameters, you MUST test each parameter with multiple values.
177
+
178
+ ### What to Test
179
+
180
+ | Parameter Type | Required Tests |
181
+ |---------------|----------------|
182
+ | **Filter params** (type, status) | Each valid enum/value |
183
+ | **Sort params** (sort, orderBy) | Ascending AND descending |
184
+ | **Pagination** (page, size) | First page, middle page, edge cases |
185
+ | **Search** (query, q) | Valid search, empty results |
186
+
187
+ ### Example: GET /apps with Query Params
188
+
189
+ ```javascript
190
+ // Test each filter value
191
+ GET /apps?type=PLATFORM
192
+ GET /apps?type=CUSTOM
193
+ GET /apps?type=NATIVE
194
+
195
+ // Test sorting - BOTH directions
196
+ GET /apps?sort=name,asc
197
+ GET /apps?sort=name,desc
198
+ GET /apps?sort=createdAt,desc
199
+
200
+ // Test pagination
201
+ GET /apps?page=0&size=10
202
+ GET /apps?page=1&size=5
203
+ GET /apps?size=100 // Max size
204
+
205
+ // Test combinations
206
+ GET /apps?type=PLATFORM&sort=name,asc&page=0&size=20
207
+ ```
208
+
209
+ ### Assertions for Query Params
210
+
211
+ ```javascript
212
+ // For filter tests - verify filtered results
213
+ pm.test("All results match filter", function () {
214
+ const json = pm.response.json();
215
+ json.content.forEach(item => {
216
+ pm.expect(item.type).to.eql("PLATFORM");
217
+ });
218
+ });
219
+
220
+ // For sort tests - verify order
221
+ pm.test("Results sorted by name ascending", function () {
222
+ const json = pm.response.json();
223
+ const names = json.content.map(item => item.name);
224
+ const sorted = [...names].sort();
225
+ pm.expect(names).to.eql(sorted);
226
+ });
227
+
228
+ // For pagination - verify page metadata
229
+ pm.test("Pagination metadata correct", function () {
230
+ const json = pm.response.json();
231
+ pm.expect(json.page).to.eql(0);
232
+ pm.expect(json.size).to.eql(10);
233
+ pm.expect(json.totalElements).to.be.a('number');
234
+ });
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Include Parameter Coverage - MANDATORY
240
+
241
+ For endpoints with `include` parameter, test ALL expected values individually AND in combination.
242
+
243
+ ### Expected Include Values
244
+
245
+ | Endpoint Type | Expected Values |
246
+ |--------------|-----------------|
247
+ | List endpoints (`/apps`, `/apps/{id}`) | `configs`, `oauth_configs_list`, `secure_iparams` |
248
+ | Configuration endpoints | `secure_iparams`, `oauth_iparams`, `extension_type` |
249
+
250
+ ### Example: Testing Include Parameter
251
+
252
+ ```javascript
253
+ // Test each include value INDIVIDUALLY
254
+ GET /apps/{id}?include=configs
255
+ GET /apps/{id}?include=oauth_configs_list
256
+ GET /apps/{id}?include=secure_iparams
257
+
258
+ // Test combinations
259
+ GET /apps/{id}?include=configs,oauth_configs_list
260
+ GET /apps/{id}?include=configs,oauth_configs_list,secure_iparams
261
+ ```
262
+
263
+ ### Assertions for Include Tests
264
+
265
+ ```javascript
266
+ // Verify included data is present
267
+ pm.test("Response includes configs", function () {
268
+ const json = pm.response.json();
269
+ pm.expect(json).to.have.property('configs');
270
+ pm.expect(json.configs).to.be.an('array');
271
+ });
272
+
273
+ // Verify data is NOT included when not requested
274
+ pm.test("Response excludes oauth_configs when not requested", function () {
275
+ const json = pm.response.json();
276
+ pm.expect(json.oauthConfigs).to.be.undefined;
277
+ });
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Request Body Variations - For POST/PUT/PATCH
283
+
284
+ For write operations, test different body scenarios to ensure validation works correctly.
285
+
286
+ ### Required Body Test Cases
287
+
288
+ | Test Case | Purpose | Expected Status |
289
+ |-----------|---------|-----------------|
290
+ | All required fields | Happy path | 201/200 |
291
+ | Required + optional fields | Full payload | 201/200 |
292
+ | Minimal valid body | Edge case | 201/200 |
293
+ | Missing required field | Validation | 400 |
294
+ | Invalid field value | Validation | 400 |
295
+ | Empty body | Validation | 400 |
296
+ | Extra unknown fields | Ignored/rejected | 201 or 400 |
297
+
298
+ ### Example: POST /apps Body Variations
299
+
300
+ ```javascript
301
+ // Test 1: All required fields
302
+ {
303
+ "name": "Test App",
304
+ "type": "PLATFORM"
305
+ }
306
+
307
+ // Test 2: With optional fields
308
+ {
309
+ "name": "Test App",
310
+ "type": "PLATFORM",
311
+ "description": "Optional description",
312
+ "tags": ["tag1", "tag2"]
313
+ }
314
+
315
+ // Test 3: Missing required field (400)
316
+ {
317
+ "type": "PLATFORM"
318
+ // name is missing
319
+ }
320
+
321
+ // Test 4: Invalid enum value (400)
322
+ {
323
+ "name": "Test App",
324
+ "type": "INVALID_TYPE"
325
+ }
326
+
327
+ // Test 5: Invalid field format (400)
328
+ {
329
+ "name": "", // Empty string
330
+ "type": "PLATFORM"
331
+ }
332
+ ```
333
+
334
+ ### Assertions for Body Validation Tests
335
+
336
+ ```javascript
337
+ // For 400 errors - verify error details
338
+ pm.test("Error response has details", function () {
339
+ const json = pm.response.json();
340
+ pm.expect(json).to.have.property('detail');
341
+ pm.expect(json.detail).to.include('name'); // Field name in error
342
+ });
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Collection Variables
348
+
349
+ Use variables for dynamic values:
350
+
351
+ - `{{base_url}}` - API base URL
352
+ - `{{auth_token}}` - JWT token
353
+ - `{{app_id}}` - Created app ID (from previous request)
354
+ - `{{version_id}}` - Created version ID
355
+ - `{{tenant_id}}` - Test tenant identifier
356
+ - `{{environment}}` - Current environment (local/staging)
357
+
358
+ ## Pre-request Script: Save Values
359
+
360
+ ```javascript
361
+ // Save value from previous response for chaining
362
+ pm.collectionVariables.set("app_id", pm.response.json().id);
363
+
364
+ // Generate timestamp
365
+ pm.collectionVariables.set("timestamp", Date.now());
366
+ ```
367
+
368
+ ---
369
+
370
+ ## Quick Checklist Before Submitting Tests
371
+
372
+ - [ ] Each test asserts exactly ONE status code
373
+ - [ ] No `oneOf`, `to.include([...])` for status codes
374
+ - [ ] No 5xx status code assertions
375
+ - [ ] All query parameters have tests with multiple values
376
+ - [ ] All include values tested individually
377
+ - [ ] POST/PUT/PATCH have body variation tests
378
+ - [ ] Strong assertions on response fields (not just `isNotNull`)
379
+ - [ ] Environment skips have `[SKIP] Reason: ... | Env: ...`
380
+ - [ ] Coverage matrix shows 100% 2xx coverage