11package cwms .cda .api ;
22
33import com .fasterxml .jackson .databind .ObjectMapper ;
4+ import com .fasterxml .jackson .databind .node .ObjectNode ;
45import cwms .cda .formatters .Formats ;
56import fixtures .TestAccounts ;
67import 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 )
0 commit comments