Skip to content

Commit 57ad8cc

Browse files
committed
Refactored entity update controller and tests.
- Removed office-id query param and redundant checks - Added missing entity-id validation test - Added invalid office authorization test - Improved variable naming - Fixed failing .replace() logic
1 parent 663866a commit 57ad8cc

File tree

3 files changed

+113
-61
lines changed

3 files changed

+113
-61
lines changed

cwms-data-api/src/main/java/cwms/cda/api/EntityController.java

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,6 @@ public void create(@NotNull Context ctx) {
167167
@OpenApiParam(name = ENTITY_ID, required = true, description = "Specifies the entity ID of the " +
168168
" Entity to be updated. (e.g., NWS)")
169169
},
170-
queryParams = {
171-
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office "+
172-
" of the entity to be updated. (e.g., SPK)")
173-
},
174170
method = HttpMethod.PATCH,
175171
tags = {TAG},
176172
responses = {
@@ -182,28 +178,17 @@ public void create(@NotNull Context ctx) {
182178
public void update(@NotNull Context ctx, @NotNull String entityId) {
183179
try (final Timer.Context ignored = markAndTime(UPDATE)) {
184180
DSLContext dsl = getDslContext(ctx);
185-
String officeId = requiredParam(ctx, OFFICE);
186181
String formatHeader = ctx.req.getContentType();
187182
ContentType contentType = Formats.parseHeader(formatHeader, Entity.class);
188183
Entity entity = Formats.parseContent(contentType, ctx.bodyAsInputStream(), Entity.class);
189-
if (entity.getId() == null || entity.getId().getOfficeId() == null || entity.getId().getName() == null) {
190-
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
191-
ctx.result("Entity ID and Office ID must be provided in the request body.");
192-
return;
193-
}
184+
// Validate the office ID and entity ID are provided.
185+
entity.validate();
194186

195187
if (!entityId.equalsIgnoreCase(entity.getId().getName())) {
196188
ctx.status(HttpServletResponse.SC_NOT_FOUND);
197-
ctx.result("Entity not found for the given entity-id.");
189+
ctx.result("Entity ID in path parameter must match the Entity ID in the request body.");
198190
return;
199191
}
200-
201-
if (!officeId.equalsIgnoreCase(entity.getId().getOfficeId())) {
202-
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
203-
ctx.result("Office ID in query parameter must match the Office ID in the request body.");
204-
return;
205-
}
206-
207192
EntityDao dao = new EntityDao(dsl);
208193
dao.updateEntity(entity);
209194
ctx.status(HttpServletResponse.SC_OK);

cwms-data-api/src/test/java/cwms/cda/api/EntityControllerTestIT.java

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cwms.cda.api;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import cwms.cda.formatters.Formats;
56
import fixtures.TestAccounts;
67
import io.restassured.filter.log.LogDetail;
@@ -60,15 +61,15 @@ void tearDown() throws Exception {
6061
void test_entity_create_get_update_delete(String format) throws Exception {
6162

6263
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;
63-
String entityJson = getUniqueTestEntityJsonByIndex(0);
64-
String entityName = JsonPath.from(entityJson).getString("id.name");
65-
String longName = JsonPath.from(entityJson).getString("long-name");
64+
String crudEntityJson = getUniqueTestEntityJsonByIndex(0);
65+
String entityName = JsonPath.from(crudEntityJson).getString("id.name");
66+
String longName = JsonPath.from(crudEntityJson).getString("long-name");
6667

6768
// CREATE
6869
given()
6970
.log().ifValidationFails(LogDetail.ALL, true)
7071
.contentType(Formats.JSONV2)
71-
.body(entityJson)
72+
.body(crudEntityJson)
7273
.header(AUTH_HEADER, user.toHeaderValue())
7374
.when()
7475
.redirects().follow(true)
@@ -97,15 +98,15 @@ void test_entity_create_get_update_delete(String format) throws Exception {
9798
.body("long-name", equalTo(longName));
9899

99100
// UPDATE — modify long-name to verify persistence
100-
String updatedEntityJson = entityJson.replace(
101-
"\"" + longName + "\"",
102-
"\"Updated long name\"");
101+
ObjectMapper mapper = new ObjectMapper();
102+
ObjectNode root = (ObjectNode) mapper.readTree(crudEntityJson);
103+
root.put("long-name", "Updated long name");
103104

105+
String updatedEntityJson = mapper.writeValueAsString(root);
104106
given()
105107
.contentType(Formats.JSONV2)
106108
.body(updatedEntityJson)
107109
.header(AUTH_HEADER, user.toHeaderValue())
108-
.queryParam(OFFICE, OFFICE_ID)
109110
.when()
110111
.redirects().follow(true)
111112
.redirects().max(3)
@@ -160,18 +161,17 @@ void test_entity_create_get_update_delete(String format) throws Exception {
160161
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));
161162
}
162163

163-
164164
// create fails if entity already exists
165165
@Test
166-
void create_duplicate_entity_bad_request() throws Exception {
166+
void create_duplicate_entity_400() throws Exception {
167167
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;
168-
String entityJson1 = getUniqueTestEntityJsonByIndex(1);
168+
String duplicateEntityJson = getUniqueTestEntityJsonByIndex(1);
169169

170170
// CREATE
171171
given()
172172
.log().ifValidationFails(LogDetail.ALL, true)
173173
.contentType(Formats.JSONV2)
174-
.body(entityJson1)
174+
.body(duplicateEntityJson)
175175
.header(AUTH_HEADER, user.toHeaderValue())
176176
.when()
177177
.redirects().follow(true)
@@ -187,7 +187,7 @@ void create_duplicate_entity_bad_request() throws Exception {
187187
given()
188188
.log().ifValidationFails(LogDetail.ALL, true)
189189
.contentType(Formats.JSONV2)
190-
.body(entityJson1)
190+
.body(duplicateEntityJson)
191191
.header(AUTH_HEADER, user.toHeaderValue())
192192
.when()
193193
.redirects().follow(true)
@@ -203,24 +203,24 @@ void create_duplicate_entity_bad_request() throws Exception {
203203

204204
// Controller-owned validation: missing required query param
205205
@Test
206-
void get_one_missing_office_bad_request() throws Exception {
206+
void get_one_missing_office_400() throws Exception {
207207
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;
208-
String entityJson2 = getUniqueTestEntityJsonByIndex(2);
209-
String entityName = JsonPath.from(entityJson2).getString("id.name");
208+
String getOneEntityJson = getUniqueTestEntityJsonByIndex(2);
209+
String entityName = JsonPath.from(getOneEntityJson).getString("id.name");
210210

211211
// CREATE
212212
given()
213213
.log().ifValidationFails(LogDetail.ALL, true)
214214
.contentType(Formats.JSONV2)
215-
.body(entityJson2)
215+
.body(getOneEntityJson)
216216
.header(AUTH_HEADER, user.toHeaderValue())
217217
.when()
218218
.redirects().follow(true)
219219
.redirects().max(3)
220220
.post("/entity")
221221
.then()
222222
.log().ifValidationFails(LogDetail.ALL, true)
223-
.assertThat()
223+
.assertThat()
224224
.statusCode(is(HttpServletResponse.SC_CREATED));
225225

226226
// getOne with no office param
@@ -238,19 +238,62 @@ void get_one_missing_office_bad_request() throws Exception {
238238
}
239239

240240

241-
// Entity ID in the URL must match the id.name in the request body
241+
// Test CREATE as SPK user -> UPDATE as SWT user
242+
@Test
243+
void update_entity_other_office_forbidden_401() throws Exception {
244+
TestAccounts.KeyUser spkUser = TestAccounts.KeyUser.SPK_NORMAL;
245+
TestAccounts.KeyUser swtUser = TestAccounts.KeyUser.SWT_NORMAL;
246+
247+
String updateOfficeEntityJson = getUniqueTestEntityJsonByIndex(4);
248+
249+
// CREATE the entity in SPK as SPK user
250+
given()
251+
.contentType(Formats.JSONV2)
252+
.body(updateOfficeEntityJson)
253+
.header(AUTH_HEADER, spkUser.toHeaderValue()) // authenticated as SPK
254+
.when()
255+
.redirects().follow(true)
256+
.redirects().max(3)
257+
.post("/entity")
258+
.then()
259+
.log().ifValidationFails(LogDetail.ALL, true)
260+
.assertThat()
261+
.statusCode(is(HttpServletResponse.SC_CREATED));
262+
263+
// Try UPDATE on the SAME SPK entity with SWT credentials
264+
ObjectMapper mapper = new ObjectMapper();
265+
ObjectNode root = (ObjectNode) mapper.readTree(updateOfficeEntityJson);
266+
root.put("long-name", "Malicious Update from SWT");
267+
String updateJson = mapper.writeValueAsString(root);
268+
String entityName = JsonPath.from(updateJson).getString("id.name");
269+
270+
given()
271+
.contentType(Formats.JSONV2)
272+
.body(updateJson)
273+
.header(AUTH_HEADER, swtUser.toHeaderValue()) // authenticated as SWT
274+
.when()
275+
.redirects().follow(true)
276+
.redirects().max(3)
277+
.patch("/entity/" + entityName)
278+
.then()
279+
.log().ifValidationFails(LogDetail.ALL, true)
280+
.assertThat()
281+
.statusCode(is(HttpServletResponse.SC_UNAUTHORIZED));
282+
}
283+
284+
// Test UPDATE entity before create -> CREATE -> UPDATE with missing entity id.
242285
@Test
243-
void update_non_existing_entity_id_or_missing_office_id_400_or_404() throws Exception {
286+
void update_entity_expected_failing_behavior_400_or_404() throws Exception {
244287
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;
245-
String entityJson3 = getUniqueTestEntityJsonByIndex(3);
246-
String entityName = JsonPath.from(entityJson3).getString("id.name");
288+
String newEntityJson = getUniqueTestEntityJsonByIndex(3);
289+
String entityName = JsonPath.from(newEntityJson).getString("id.name");
247290

248-
// UPDATE - non-existing entity id - 404
291+
// UPDATE - non-existing new entity id - 404
249292
given()
250293
.log().ifValidationFails(LogDetail.ALL, true)
251294
.accept(Formats.JSONV2)
252295
.contentType(Formats.JSONV2)
253-
.body(entityJson3)
296+
.body(newEntityJson)
254297
.header(AUTH_HEADER, user.toHeaderValue())
255298
.queryParam(OFFICE, OFFICE_ID)
256299
.when()
@@ -262,27 +305,36 @@ void update_non_existing_entity_id_or_missing_office_id_400_or_404() throws Exce
262305
.assertThat()
263306
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));
264307

265-
// CREATE the non-existing entity id
308+
// CREATE the new Entity, then update it without an entity id
266309
given()
267310
.log().ifValidationFails(LogDetail.ALL, true)
268311
.contentType(Formats.JSONV2)
269-
.body(entityJson3)
312+
.body(newEntityJson)
270313
.header(AUTH_HEADER, user.toHeaderValue())
271314
.when()
272315
.redirects().follow(true)
273316
.redirects().max(3)
274317
.post("/entity")
275318
.then()
276319
.log().ifValidationFails(LogDetail.ALL, true)
277-
.assertThat()
320+
.assertThat()
278321
.statusCode(is(HttpServletResponse.SC_CREATED));
279322

280-
// UPDATE - missing office id - 400
323+
// UPDATE - missing entity-id (id.name) in the BODY
324+
ObjectMapper mapper = new ObjectMapper();
325+
ObjectNode root = (ObjectNode) mapper.readTree(newEntityJson);
326+
327+
ObjectNode idNode = (ObjectNode) root.get("id");
328+
idNode.remove("name");
329+
330+
root.put("long-name", "Updated long name");
331+
String missingIdInBodyJson = mapper.writeValueAsString(root);
332+
281333
given()
282334
.log().ifValidationFails(LogDetail.ALL, true)
283335
.accept(Formats.JSONV2)
284336
.contentType(Formats.JSONV2)
285-
.body(entityJson3)
337+
.body(missingIdInBodyJson)
286338
.header(AUTH_HEADER, user.toHeaderValue())
287339
.when()
288340
.redirects().follow(true)
@@ -294,7 +346,6 @@ void update_non_existing_entity_id_or_missing_office_id_400_or_404() throws Exce
294346
.statusCode(is(HttpServletResponse.SC_BAD_REQUEST));
295347
}
296348

297-
298349
// getAll with no query params: must return 200 and a list (empty allowed)
299350
@ParameterizedTest
300351
@ValueSource(strings = {Formats.JSONV2, Formats.DEFAULT})
@@ -332,18 +383,18 @@ void getAll_with_parent_filter_returns_200_empty_list_ok() {
332383
.statusCode(is(HttpServletResponse.SC_OK));
333384
}
334385

335-
386+
// test CREATE null parent entity -> GET + match-null-parents true/false -> DELETE
336387
@Test
337388
void getAll_match_null_parents_flag() throws Exception {
338389
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;
339-
String entityJson4 = getUniqueTestEntityJsonByIndex(4);
340-
String entityName = JsonPath.from(entityJson4).getString("id.name");
390+
String nullParentEntityJson = getUniqueTestEntityJsonByIndex(5);
391+
String entityName = JsonPath.from(nullParentEntityJson).getString("id.name");
341392

342393
// CREATE entity with null parent - default match-null-parents = true
343394
given()
344395
.log().ifValidationFails(LogDetail.ALL, true)
345396
.contentType(Formats.JSONV2)
346-
.body(entityJson4)
397+
.body(nullParentEntityJson)
347398
.header(AUTH_HEADER, user.toHeaderValue())
348399
.when()
349400
.redirects().follow(true)
Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,62 @@
11
[
2+
23
{
34
"id" : {
45
"office-id" : "SPK",
5-
"name" : "TE"
6+
"name" : "0_CRUD_TE"
67
},
78
"parent-entity-id" : "NOAA",
89
"category-id" : "GOV",
910
"long-name" : "Test Entity"
1011
},
12+
1113
{
1214
"id" : {
1315
"office-id" : "SPK",
14-
"name" : "TE1"
16+
"name" : "1_DUP_ENTITY_ID_TE"
1517
},
1618
"parent-entity-id" : "NOAA",
1719
"category-id" : "GOV",
18-
"long-name" : "Test Entity1"
20+
"long-name" : "Duplicate Test Entity"
1921
},
22+
23+
{
24+
"id" : {
25+
"office-id": "SPK",
26+
"name": "2_GET_ONE_TE"
27+
},
28+
"parent-entity-id" : "NOAA",
29+
"category-id" : "GOV",
30+
"long-name" : "Test Entity for Get One"
31+
},
32+
2033
{
2134
"id" : {
2235
"office-id" : "SPK",
23-
"name" : "TE2"
36+
"name" : "3_NON_EXISTENT_TE"
2437
},
2538
"parent-entity-id" : "NOAA",
2639
"category-id" : "GOV",
27-
"long-name" : "Test Entity2"
40+
"long-name" : "Test Entity that does not exist"
2841
},
42+
2943
{
3044
"id" : {
3145
"office-id" : "SPK",
32-
"name" : "TE3"
46+
"name" : "4_UPDATE_OFFICE_TE"
3347
},
3448
"parent-entity-id" : "NOAA",
3549
"category-id" : "GOV",
36-
"long-name" : "Test Entity3"
50+
"long-name" : "Test Entity for Update Office"
3751
},
52+
3853
{
3954
"id" : {
4055
"office-id" : "SPK",
41-
"name" : "TE4"
56+
"name" : "5_NULL_PARENT_TE"
4257
},
4358
"category-id" : "GOV",
44-
"long-name" : "Test Entity4"
59+
"long-name" : "Null Parent Test Entity"
4560
}
61+
4662
]

0 commit comments

Comments
 (0)