Skip to content

Commit 1e224da

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

File tree

2 files changed

+92
-32
lines changed

2 files changed

+92
-32
lines changed

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

Lines changed: 20 additions & 25 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,15 +248,15 @@ protected ResponseEntity<Object> getPropertyItem(
245248
Relation property,
246249
EntityId itemId
247250
) {
248-
try {
249-
if (datamodelApi.hasRelationTarget(application, property, instanceId, itemId)) {
250-
var uri = linkTo(methodOn(EntityRestController.class).getEntity(application, entity.getPathSegment(), instanceId)).toUri();
251-
return ResponseEntity.status(HttpStatus.FOUND).location(uri).build();
252-
} else {
253-
return ResponseEntity.notFound().build();
254-
}
255-
} catch (EntityNotFoundException e) {
256-
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage(), e);
251+
if (!(property instanceof OneToManyRelation || property instanceof ManyToManyRelation)) {
252+
return ResponseEntity.notFound().build();
253+
}
254+
255+
if (datamodelApi.hasRelationTarget(application, property, instanceId, itemId)) {
256+
var uri = linkTo(methodOn(EntityRestController.class).getEntity(application, property.getTargetEndPoint().getEntity().getPathSegment(), itemId)).toUri();
257+
return ResponseEntity.status(HttpStatus.FOUND).location(uri).build();
258+
} else {
259+
return ResponseEntity.notFound().build();
257260
}
258261
}
259262

@@ -266,9 +269,7 @@ protected ResponseEntity<Object> postPropertyItem(
266269
EntityId itemId,
267270
List<URI> body
268271
) {
269-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
270-
.allow(supportedPropertyItemMethods(property))
271-
.build();
272+
return propertyItemUnsupportedMethodResponse(property);
272273
}
273274

274275
@Override
@@ -280,9 +281,7 @@ protected ResponseEntity<Object> putPropertyItem(
280281
EntityId itemId,
281282
List<URI> body
282283
) {
283-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
284-
.allow(supportedPropertyItemMethods(property))
285-
.build();
284+
return propertyItemUnsupportedMethodResponse(property);
286285
}
287286

288287
@Override
@@ -294,9 +293,7 @@ protected ResponseEntity<Object> patchPropertyItem(
294293
EntityId itemId,
295294
List<URI> body
296295
) {
297-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
298-
.allow(supportedPropertyItemMethods(property))
299-
.build();
296+
return propertyItemUnsupportedMethodResponse(property);
300297
}
301298

302299
@Override
@@ -308,9 +305,7 @@ protected ResponseEntity<Object> deletePropertyItem(
308305
EntityId itemId
309306
) {
310307
if (!(property instanceof OneToManyRelation || property instanceof ManyToManyRelation)) {
311-
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
312-
.allow(SINGLE_TARGET_PROPERTY_ITEM_METHODS)
313-
.build();
308+
return ResponseEntity.notFound().build();
314309
}
315310

316311
try {

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

Lines changed: 72 additions & 7 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());
@@ -315,8 +347,9 @@ static Stream<String> invalidContentType() {
315347

316348
@ParameterizedTest
317349
@CsvSource({
318-
"/invoices/01234567-89ab-cdef-0123-456789abcdef/previous", // non-existent relation
319-
"/invoice/01234567-89ab-cdef-0123-456789abcdef/previous-invoice", // non-existent entity
350+
"/invoices/01234567-89ab-cdef-0123-456789abcdef/non-existing", // non-existing relation
351+
"/non-existing/01234567-89ab-cdef-0123-456789abcdef/previous-invoice", // non-existing entity
352+
"/invoices/01234567-89ab-cdef-0123-456789abcdef/non-existing/01234567-89ab-cdef-0123-456789abcdef", // non-existing relation
320353
})
321354
void followRelationInvalidUrl(String url) throws Exception {
322355
mockMvc.perform(get(url))
@@ -410,12 +443,8 @@ static Stream<Arguments> unsupportedMethod() {
410443
Arguments.of(HttpMethod.PATCH, "/invoices/%s/previous-invoice".formatted(INVOICE_ID)),
411444
// property item endpoint
412445
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)),
414446
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))
447+
Arguments.of(HttpMethod.PATCH, "/persons/%s/invoices/%s".formatted(PERSON_ID, targetId))
419448
);
420449
}
421450

@@ -434,6 +463,30 @@ void unsupportedMethod(HttpMethod method, String url) throws Exception {
434463
.andExpect(header().string(HttpHeaders.ALLOW, not(containsString(method.name()))));
435464
}
436465

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

439492
@Nested
@@ -470,6 +523,18 @@ void followToManyRelationSourceIdNotFound() throws Exception {
470523
.andExpect(status().isNotFound());
471524
}
472525

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

0 commit comments

Comments
 (0)