Skip to content

Commit 40267c9

Browse files
committed
ACC-2100 return 404 when performing item resource request on a *-to-one relation
1 parent 3e2b894 commit 40267c9

File tree

2 files changed

+85
-22
lines changed

2 files changed

+85
-22
lines changed

contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/property/handler/RelationRequestHandler.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ public class RelationRequestHandler extends AbstractPropertyItemRequestHandler<L
3737

3838
private static final HttpMethod[] SINGLE_TARGET_PROPERTY_METHODS = {HttpMethod.GET, HttpMethod.HEAD, HttpMethod.PUT, HttpMethod.DELETE};
3939
private static final HttpMethod[] MULTI_TARGET_PROPERTY_METHODS = {HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST, HttpMethod.DELETE};
40-
private static final HttpMethod[] SINGLE_TARGET_PROPERTY_ITEM_METHODS = {HttpMethod.GET, HttpMethod.HEAD};
4140
private static final HttpMethod[] MULTI_TARGET_PROPERTY_ITEM_METHODS = {HttpMethod.GET, HttpMethod.HEAD, HttpMethod.DELETE};
4241

4342
private static HttpMethod[] supportedPropertyMethods(Relation relation) {
@@ -48,11 +47,15 @@ private static HttpMethod[] supportedPropertyMethods(Relation relation) {
4847
}
4948
}
5049

51-
private static HttpMethod[] supportedPropertyItemMethods(Relation relation) {
50+
private static ResponseEntity<Object> propertyItemUnsupportedMethodResponse(Relation relation) {
5251
if (relation instanceof OneToManyRelation || relation instanceof ManyToManyRelation) {
53-
return MULTI_TARGET_PROPERTY_ITEM_METHODS;
52+
// *-to-many relation
53+
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
54+
.allow(MULTI_TARGET_PROPERTY_ITEM_METHODS)
55+
.build();
5456
} else {
55-
return SINGLE_TARGET_PROPERTY_ITEM_METHODS;
57+
// *-to-one relation
58+
return ResponseEntity.notFound().build();
5659
}
5760
}
5861

@@ -245,9 +248,13 @@ protected ResponseEntity<Object> getPropertyItem(
245248
Relation property,
246249
EntityId itemId
247250
) {
251+
if (!(property instanceof OneToManyRelation || property instanceof ManyToManyRelation)) {
252+
return ResponseEntity.notFound().build();
253+
}
254+
248255
try {
249256
if (datamodelApi.hasRelationTarget(application, property, instanceId, itemId)) {
250-
var uri = linkTo(methodOn(EntityRestController.class).getEntity(application, entity.getPathSegment(), instanceId)).toUri();
257+
var uri = linkTo(methodOn(EntityRestController.class).getEntity(application, property.getTargetEndPoint().getEntity().getPathSegment(), itemId)).toUri();
251258
return ResponseEntity.status(HttpStatus.FOUND).location(uri).build();
252259
} else {
253260
return ResponseEntity.notFound().build();
@@ -266,9 +273,7 @@ protected ResponseEntity<Object> postPropertyItem(
266273
EntityId itemId,
267274
List<URI> body
268275
) {
269-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
270-
.allow(supportedPropertyItemMethods(property))
271-
.build();
276+
return propertyItemUnsupportedMethodResponse(property);
272277
}
273278

274279
@Override
@@ -280,9 +285,7 @@ protected ResponseEntity<Object> putPropertyItem(
280285
EntityId itemId,
281286
List<URI> body
282287
) {
283-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
284-
.allow(supportedPropertyItemMethods(property))
285-
.build();
288+
return propertyItemUnsupportedMethodResponse(property);
286289
}
287290

288291
@Override
@@ -294,9 +297,7 @@ protected ResponseEntity<Object> patchPropertyItem(
294297
EntityId itemId,
295298
List<URI> body
296299
) {
297-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
298-
.allow(supportedPropertyItemMethods(property))
299-
.build();
300+
return propertyItemUnsupportedMethodResponse(property);
300301
}
301302

302303
@Override
@@ -308,9 +309,7 @@ protected ResponseEntity<Object> deletePropertyItem(
308309
EntityId itemId
309310
) {
310311
if (!(property instanceof OneToManyRelation || property instanceof ManyToManyRelation)) {
311-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
312-
.allow(SINGLE_TARGET_PROPERTY_ITEM_METHODS)
313-
.build();
312+
return ResponseEntity.notFound().build();
314313
}
315314

316315
try {

contentgrid-appserver-rest/src/test/java/com/contentgrid/appserver/rest/property/handler/RelationRequestHandlerTest.java

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,38 @@ void followManyToManyRelation() throws Exception {
164164
.findById(TestApplication.APPLICATION, TestApplication.PRODUCT, PRODUCT_ID);
165165
}
166166

167+
@Test
168+
void followOneToManyRelationItem() throws Exception {
169+
var targetId = EntityId.of(UUID.randomUUID());
170+
171+
Mockito.doReturn(true).when(datamodelApi)
172+
.hasRelationTarget(TestApplication.APPLICATION, TestApplication.PERSON_INVOICES, PERSON_ID, targetId);
173+
174+
mockMvc.perform(get("/persons/{sourceId}/invoices/{targetId}", PERSON_ID, targetId))
175+
.andExpect(status().isFound())
176+
.andExpect(
177+
header().string(HttpHeaders.LOCATION, "http://localhost/invoices/%s".formatted(targetId)));
178+
179+
Mockito.verify(datamodelApi)
180+
.hasRelationTarget(TestApplication.APPLICATION, TestApplication.PERSON_INVOICES, PERSON_ID, targetId);
181+
}
182+
183+
@Test
184+
void followManyToManyRelationItem() throws Exception {
185+
var targetId = EntityId.of(UUID.randomUUID());
186+
187+
Mockito.doReturn(true).when(datamodelApi)
188+
.hasRelationTarget(TestApplication.APPLICATION, TestApplication.PRODUCT_INVOICES, PRODUCT_ID, targetId);
189+
190+
mockMvc.perform(get("/products/{sourceId}/invoices/{targetId}", PRODUCT_ID, targetId))
191+
.andExpect(status().isFound())
192+
.andExpect(
193+
header().string(HttpHeaders.LOCATION, "http://localhost/invoices/%s".formatted(targetId)));
194+
195+
Mockito.verify(datamodelApi)
196+
.hasRelationTarget(TestApplication.APPLICATION, TestApplication.PRODUCT_INVOICES, PRODUCT_ID, targetId);
197+
}
198+
167199
@Test
168200
void setOneToOneRelation() throws Exception {
169201
var targetId = EntityId.of(UUID.randomUUID());
@@ -410,12 +442,8 @@ static Stream<Arguments> unsupportedMethod() {
410442
Arguments.of(HttpMethod.PATCH, "/invoices/%s/previous-invoice".formatted(INVOICE_ID)),
411443
// property item endpoint
412444
Arguments.of(HttpMethod.POST, "/persons/%s/invoices/%s".formatted(PERSON_ID, targetId)),
413-
Arguments.of(HttpMethod.POST, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
414445
Arguments.of(HttpMethod.PUT, "/persons/%s/invoices/%s".formatted(PERSON_ID, targetId)),
415-
Arguments.of(HttpMethod.PUT, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
416-
Arguments.of(HttpMethod.PATCH, "/persons/%s/invoices/%s".formatted(PERSON_ID, targetId)),
417-
Arguments.of(HttpMethod.PATCH, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
418-
Arguments.of(HttpMethod.DELETE, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId))
446+
Arguments.of(HttpMethod.PATCH, "/persons/%s/invoices/%s".formatted(PERSON_ID, targetId))
419447
);
420448
}
421449

@@ -434,6 +462,30 @@ void unsupportedMethod(HttpMethod method, String url) throws Exception {
434462
.andExpect(header().string(HttpHeaders.ALLOW, not(containsString(method.name()))));
435463
}
436464

465+
static Stream<Arguments> unsupportedUrl() {
466+
var targetId = EntityId.of(UUID.randomUUID());
467+
return Stream.of(
468+
// property item endpoint of *-to-one relation
469+
Arguments.of(HttpMethod.GET, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
470+
Arguments.of(HttpMethod.POST, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
471+
Arguments.of(HttpMethod.PUT, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
472+
Arguments.of(HttpMethod.PATCH, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId)),
473+
Arguments.of(HttpMethod.DELETE, "/invoices/%s/previous-invoice/%s".formatted(INVOICE_ID, targetId))
474+
);
475+
}
476+
477+
@ParameterizedTest
478+
@MethodSource
479+
void unsupportedUrl(HttpMethod method, String url) throws Exception {
480+
var requestBuilder = request(method, url);
481+
if (Set.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH).contains(method)) {
482+
requestBuilder = requestBuilder.contentType("text/uri-list")
483+
.content("http://localhost/invoices/%s%n".formatted(UUID.randomUUID()));
484+
}
485+
mockMvc.perform(requestBuilder)
486+
.andExpect(status().isNotFound());
487+
}
488+
437489
}
438490

439491
@Nested
@@ -470,6 +522,18 @@ void followToManyRelationSourceIdNotFound() throws Exception {
470522
.andExpect(status().isNotFound());
471523
}
472524

525+
@Test
526+
void followToManyRelationItemSourceIdOrTargetIdNotFound() throws Exception {
527+
var targetId = EntityId.of(UUID.randomUUID());
528+
529+
// Returns false if sourceId or targetId does not exist, or if there is no relation between sourceId and targetId
530+
Mockito.doReturn(false).when(datamodelApi)
531+
.hasRelationTarget(TestApplication.APPLICATION, TestApplication.PERSON_INVOICES, PERSON_ID, targetId);
532+
533+
mockMvc.perform(get("/persons/{sourceId}/invoices/{targetId}", PERSON_ID, targetId))
534+
.andExpect(status().isNotFound());
535+
}
536+
473537
@Test
474538
void setRelationEntityIdNotFound() throws Exception {
475539
var targetId = EntityId.of(UUID.randomUUID());

0 commit comments

Comments
 (0)