From 5ce3d15b7fca7b499d38afd027d73c67b8765e28 Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 19:24:21 +0530 Subject: [PATCH 1/6] DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for empty links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar --- ...RepositoryPropertyReferenceController.java | 8 +++++ ...yPropertyReferenceControllerUnitTests.java | 31 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 21eb819f7..9e38174e5 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -19,6 +19,8 @@ import static org.springframework.data.rest.webmvc.RestMediaTypes.*; import static org.springframework.web.bind.annotation.RequestMethod.*; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; @@ -42,6 +44,7 @@ import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent; import org.springframework.data.rest.core.event.BeforeLinkSaveEvent; import org.springframework.data.rest.webmvc.support.BackendId; +import org.springframework.data.rest.webmvc.util.InputStreamHttpInputMessage; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; @@ -54,6 +57,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -248,6 +252,10 @@ public ResponseEntity> createPropertyReference( Class propertyType = prop.property.getType(); if (prop.property.isCollectionLike()) { + if(source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Collection collection = AUGMENTING_METHODS.contains(requestMethod) // ? (Collection) prop.propertyValue // diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index c2d9c1f94..75ffc1653 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.rest.webmvc; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -45,7 +46,11 @@ import org.springframework.data.rest.core.mapping.SupportedHttpMethods; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.Link; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.server.ResponseStatusException; /** * Unit tests for {@link RepositoryPropertyReferenceController}. @@ -90,7 +95,31 @@ void usesRepositoryInvokerToLookupRelatedInstance() throws Exception { verify(invoker).invokeFindById("some-id"); } - @RestResource + @Test // DATAREST-2495 + void rejectsEmptyLinksForAssociationUpdate() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, + "references")); + } + + + @RestResource static class Sample { @org.springframework.data.annotation.Reference List references = new ArrayList(); } From 053d07f75d9efef343e6972dcc30cdca960a46cd Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 19:53:24 +0530 Subject: [PATCH 2/6] DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for multiple links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar --- ...RepositoryPropertyReferenceController.java | 5 +-- ...yPropertyReferenceControllerUnitTests.java | 36 ++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 9e38174e5..c2925de78 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -291,8 +291,9 @@ public ResponseEntity> createPropertyReference( } if (!source.getLinks().hasSingleLink()) { - throw new IllegalArgumentException( - "Must send only 1 link to update a property reference that isn't a List or a Map."); + throw new HttpMessageNotReadableException( + "Must send only 1 link to update a property reference that isn't a List or a Map.", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); } prop.accessor.setProperty(prop.property, diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index 75ffc1653..da6854204 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -19,10 +19,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -113,17 +110,48 @@ void rejectsEmptyLinksForAssociationUpdate() throws Exception { RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + //Do we need integration test to verify HTTP response code? assertThatExceptionOfType(HttpMessageNotReadableException.class) .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, "references")); } + @Test // GH-2495 + void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(SingleSample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(SingleSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new SingleSample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + CollectionModel request = CollectionModel.empty(List.of(Link.of("/reference/1"), Link.of("/reference/2"))); + + //Do we need integration test to verify HTTP response code? + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, request, 4711, + "reference")); + } + @RestResource static class Sample { @org.springframework.data.annotation.Reference List references = new ArrayList(); } + @RestResource + static class SingleSample { + @org.springframework.data.annotation.Reference Reference reference; + } + @RestResource static class Reference {} From 823f8c2bf2b211faffef98f59e91317ec5ef2468 Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 20:03:56 +0530 Subject: [PATCH 3/6] DATAREST-2495 In RepositoryPropertyReferenceController, handled response code where no links are sent (List of Map). Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar --- ...RepositoryPropertyReferenceController.java | 4 +++ ...yPropertyReferenceControllerUnitTests.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index c2925de78..676152155 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -269,6 +269,10 @@ public ResponseEntity> createPropertyReference( prop.accessor.setProperty(prop.property, collection); } else if (prop.property.isMap()) { + if(source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Map map = AUGMENTING_METHODS.contains(requestMethod) // ? (Map) prop.propertyValue // diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index da6854204..e34be6a3f 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -141,6 +141,30 @@ void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { "reference")); } + @Test // GH-2495 + void rejectsMapLinksForSingleValuedAssociation() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(MapSample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(MapSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new MapSample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + + //Do we need integration test to verify HTTP response code? + assertThatExceptionOfType(HttpMessageNotReadableException.class) + .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, + "reference")); + } + @RestResource static class Sample { @@ -152,6 +176,11 @@ static class SingleSample { @org.springframework.data.annotation.Reference Reference reference; } + @RestResource + static class MapSample { + @org.springframework.data.annotation.Reference Map reference; + } + @RestResource static class Reference {} From a9d633bc0600491a51a237a43ced99b165822ea6 Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 23:02:27 +0530 Subject: [PATCH 4/6] DATAREST-2495 Fixed test cases Signed-off-by: Anayonkar Shivalkar --- ...RepositoryPropertyReferenceController.java | 34 ++++++++-- ...yPropertyReferenceControllerUnitTests.java | 66 +++++++++++++++---- 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 676152155..e41be1364 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -19,15 +19,10 @@ import static org.springframework.data.rest.webmvc.RestMediaTypes.*; import static org.springframework.web.bind.annotation.RequestMethod.*; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; import java.util.function.Function; import org.springframework.context.ApplicationEventPublisher; @@ -82,6 +77,7 @@ class RepositoryPropertyReferenceController /*extends AbstractRepositoryRestCont private static final String BASE_MAPPING = "/{repository}/{id}/{property}"; private static final Collection AUGMENTING_METHODS = Arrays.asList(HttpMethod.PATCH, HttpMethod.POST); + private static final List SUPPORTED_CONTENT_TYPE = Arrays.asList(MediaType.APPLICATION_JSON, TEXT_URI_LIST); private final Repositories repositories; private final RepositoryInvokerFactory repositoryInvokerFactory; @@ -241,17 +237,31 @@ public ResponseEntity> followPropertyReferenceCompact(Roo consumes = { MediaType.APPLICATION_JSON_VALUE, SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity> createPropertyReference( RootResourceInformation resourceInformation, HttpMethod requestMethod, - @RequestBody(required = false) CollectionModel incoming, @BackendId Serializable id, + @RequestBody(required = false) CollectionModel incoming, + @RequestHeader(required = false) HttpHeaders requestHeaders, + @BackendId Serializable id, @PathVariable String property) throws Exception { var source = incoming == null ? CollectionModel.empty() : incoming; var invoker = resourceInformation.getInvoker(); + MediaType contentType = requestHeaders == null ? null : requestHeaders.getContentType(); + Function> handler = prop -> { Class propertyType = prop.property.getType(); if (prop.property.isCollectionLike()) { + /*if(HttpMethod.PATCH.equals(requestMethod) + || HttpMethod.POST.equals(requestMethod) + || HttpMethod.PUT.equals(requestMethod)) { + if(contentType == null + || (!TEXT_URI_LIST.isCompatibleWith(contentType) + && !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) + ) { + throw new UnsupportedMediaTypeStatusException("Unsuppoted Content Type", SUPPORTED_CONTENT_TYPE); + } + }*/ if(source.getLinks().isEmpty()) { throw new HttpMessageNotReadableException("No links provided", InputStreamHttpInputMessage.of(InputStream.nullInputStream())); @@ -269,6 +279,16 @@ public ResponseEntity> createPropertyReference( prop.accessor.setProperty(prop.property, collection); } else if (prop.property.isMap()) { + /*if(HttpMethod.PATCH.equals(requestMethod) + || HttpMethod.POST.equals(requestMethod) + || HttpMethod.PUT.equals(requestMethod)) { + if(contentType == null + || (!TEXT_URI_LIST.isCompatibleWith(contentType) + && !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) + ) { + throw new UnsupportedMediaTypeStatusException("Unsuppoted Content Type", SUPPORTED_CONTENT_TYPE); + } + }*/ if(source.getLinks().isEmpty()) { throw new HttpMessageNotReadableException("No links provided", InputStreamHttpInputMessage.of(InputStream.nullInputStream())); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index e34be6a3f..9c0171576 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; import java.util.*; @@ -45,9 +46,7 @@ import org.springframework.hateoas.Link; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.server.ResponseStatusException; /** * Unit tests for {@link RepositoryPropertyReferenceController}. @@ -86,7 +85,7 @@ void usesRepositoryInvokerToLookupRelatedInstance() throws Exception { RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); CollectionModel request = CollectionModel.empty(Link.of("/reference/some-id")); - controller.createPropertyReference(information, HttpMethod.POST, request, 4711, "references"); + controller.createPropertyReference(information, HttpMethod.POST, request, HttpHeaders.EMPTY, 4711, "references"); verify(invokerFactory).getInvokerFor(Reference.class); verify(invoker).invokeFindById("some-id"); @@ -111,9 +110,11 @@ void rejectsEmptyLinksForAssociationUpdate() throws Exception { RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); //Do we need integration test to verify HTTP response code? - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, - "references")); + Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, HttpHeaders.EMPTY, 4711, + "references")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invoker, never()).invokeFindById("some-id"); } @Test // GH-2495 @@ -133,12 +134,16 @@ void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { doReturn(Optional.of(new SingleSample())).when(invoker).invokeFindById(4711); RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - CollectionModel request = CollectionModel.empty(List.of(Link.of("/reference/1"), Link.of("/reference/2"))); + CollectionModel request = CollectionModel.empty(List.of(Link.of("/reference/some-id"), Link.of("/reference/some-another-id"))); //Do we need integration test to verify HTTP response code? - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, request, 4711, - "reference")); + Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, request, HttpHeaders.EMPTY, 4711, + "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + verify(invoker, never()).invokeFindById("some-another-id"); } @Test // GH-2495 @@ -159,12 +164,47 @@ void rejectsMapLinksForSingleValuedAssociation() throws Exception { RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(TEXT_URI_LIST); + //Do we need integration test to verify HTTP response code? - assertThatExceptionOfType(HttpMessageNotReadableException.class) - .isThrownBy(() -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, - "reference")); + Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, headers, 4711, + "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); } + /*@Test // GH-2495 + void rejectsInvalidContentTypeForSingleValuedAssociation() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + CollectionModel request = CollectionModel.empty(Link.of("/reference/some-id")); + + //Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, HttpHeaders.EMPTY, 4711, + "references")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + + }*/ + @RestResource static class Sample { From 1c634ea91d198f16fc7b7d73cb3cbc46516457b3 Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 19:24:21 +0530 Subject: [PATCH 5/6] DATAREST-2495 Fixed merge conflicts Signed-off-by: Anayonkar Shivalkar DATAREST-2495 Cleaned up unnecessary code Signed-off-by: Anayonkar Shivalkar DATAREST-2495 Fixed test cases Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code where no links are sent (List of Map). Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for multiple links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for empty links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar # Conflicts: # spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java # spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java --- ...RepositoryPropertyReferenceController.java | 49 ++--- ...yPropertyReferenceControllerUnitTests.java | 172 +++++++----------- 2 files changed, 82 insertions(+), 139 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index e41be1364..70766124f 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -77,7 +77,6 @@ class RepositoryPropertyReferenceController /*extends AbstractRepositoryRestCont private static final String BASE_MAPPING = "/{repository}/{id}/{property}"; private static final Collection AUGMENTING_METHODS = Arrays.asList(HttpMethod.PATCH, HttpMethod.POST); - private static final List SUPPORTED_CONTENT_TYPE = Arrays.asList(MediaType.APPLICATION_JSON, TEXT_URI_LIST); private final Repositories repositories; private final RepositoryInvokerFactory repositoryInvokerFactory; @@ -237,35 +236,21 @@ public ResponseEntity> followPropertyReferenceCompact(Roo consumes = { MediaType.APPLICATION_JSON_VALUE, SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity> createPropertyReference( RootResourceInformation resourceInformation, HttpMethod requestMethod, - @RequestBody(required = false) CollectionModel incoming, - @RequestHeader(required = false) HttpHeaders requestHeaders, - @BackendId Serializable id, + @RequestBody(required = false) CollectionModel incoming, @BackendId Serializable id, @PathVariable String property) throws Exception { var source = incoming == null ? CollectionModel.empty() : incoming; var invoker = resourceInformation.getInvoker(); - MediaType contentType = requestHeaders == null ? null : requestHeaders.getContentType(); - Function> handler = prop -> { Class propertyType = prop.property.getType(); if (prop.property.isCollectionLike()) { - /*if(HttpMethod.PATCH.equals(requestMethod) - || HttpMethod.POST.equals(requestMethod) - || HttpMethod.PUT.equals(requestMethod)) { - if(contentType == null - || (!TEXT_URI_LIST.isCompatibleWith(contentType) - && !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) - ) { - throw new UnsupportedMediaTypeStatusException("Unsuppoted Content Type", SUPPORTED_CONTENT_TYPE); - } - }*/ - if(source.getLinks().isEmpty()) { - throw new HttpMessageNotReadableException("No links provided", - InputStreamHttpInputMessage.of(InputStream.nullInputStream())); - } + if (source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Collection collection = AUGMENTING_METHODS.contains(requestMethod) // ? (Collection) prop.propertyValue // @@ -279,20 +264,10 @@ public ResponseEntity> createPropertyReference( prop.accessor.setProperty(prop.property, collection); } else if (prop.property.isMap()) { - /*if(HttpMethod.PATCH.equals(requestMethod) - || HttpMethod.POST.equals(requestMethod) - || HttpMethod.PUT.equals(requestMethod)) { - if(contentType == null - || (!TEXT_URI_LIST.isCompatibleWith(contentType) - && !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) - ) { - throw new UnsupportedMediaTypeStatusException("Unsuppoted Content Type", SUPPORTED_CONTENT_TYPE); - } - }*/ - if(source.getLinks().isEmpty()) { - throw new HttpMessageNotReadableException("No links provided", - InputStreamHttpInputMessage.of(InputStream.nullInputStream())); - } + if (source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Map map = AUGMENTING_METHODS.contains(requestMethod) // ? (Map) prop.propertyValue // @@ -315,9 +290,9 @@ public ResponseEntity> createPropertyReference( } if (!source.getLinks().hasSingleLink()) { - throw new HttpMessageNotReadableException( - "Must send only 1 link to update a property reference that isn't a List or a Map.", - InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + throw new HttpMessageNotReadableException( + "Must send only 1 link to update a property reference that isn't a List or a Map.", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); } prop.accessor.setProperty(prop.property, diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index 9c0171576..491b343e5 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -15,10 +15,10 @@ */ package org.springframework.data.rest.webmvc; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import static org.springframework.data.rest.webmvc.RestMediaTypes.TEXT_URI_LIST; import java.util.*; @@ -44,7 +44,6 @@ import org.springframework.data.rest.core.mapping.SupportedHttpMethods; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.Link; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -85,125 +84,94 @@ void usesRepositoryInvokerToLookupRelatedInstance() throws Exception { RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); CollectionModel request = CollectionModel.empty(Link.of("/reference/some-id")); - controller.createPropertyReference(information, HttpMethod.POST, request, HttpHeaders.EMPTY, 4711, "references"); + controller.createPropertyReference(information, HttpMethod.POST, request, 4711, "references"); verify(invokerFactory).getInvokerFor(Reference.class); verify(invoker).invokeFindById("some-id"); } - @Test // DATAREST-2495 - void rejectsEmptyLinksForAssociationUpdate() throws Exception { + @Test // DATAREST-2495 + void rejectsEmptyLinksForAssociationUpdate() throws Exception { - KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); - - ResourceMappings mappings = new PersistentEntitiesResourceMappings( - new PersistentEntities(Collections.singleton(mappingContext))); - ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); - when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); - - RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, - invokerFactory); - controller.setApplicationEventPublisher(publisher); - - doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); - - RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - - //Do we need integration test to verify HTTP response code? - Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, HttpHeaders.EMPTY, 4711, - "references")); - assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); - - verify(invoker, never()).invokeFindById("some-id"); - } - - @Test // GH-2495 - void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { - - KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(SingleSample.class); - - ResourceMappings mappings = new PersistentEntitiesResourceMappings( - new PersistentEntities(Collections.singleton(mappingContext))); - ResourceMetadata metadata = spy(mappings.getMetadataFor(SingleSample.class)); - when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); - - RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, - invokerFactory); - controller.setApplicationEventPublisher(publisher); + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); - doReturn(Optional.of(new SingleSample())).when(invoker).invokeFindById(4711); + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); - RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - CollectionModel request = CollectionModel.empty(List.of(Link.of("/reference/some-id"), Link.of("/reference/some-another-id"))); + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); - //Do we need integration test to verify HTTP response code? - Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, request, HttpHeaders.EMPTY, 4711, - "reference")); - assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); - verify(invokerFactory, never()).getInvokerFor(Reference.class); - verify(invoker, never()).invokeFindById("some-id"); - verify(invoker, never()).invokeFindById("some-another-id"); - } + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - @Test // GH-2495 - void rejectsMapLinksForSingleValuedAssociation() throws Exception { + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, "references")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); - KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(MapSample.class); + verify(invoker, never()).invokeFindById("some-id"); + } - ResourceMappings mappings = new PersistentEntitiesResourceMappings( - new PersistentEntities(Collections.singleton(mappingContext))); - ResourceMetadata metadata = spy(mappings.getMetadataFor(MapSample.class)); - when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + @Test // GH-2495 + void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { - RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, - invokerFactory); - controller.setApplicationEventPublisher(publisher); + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(SingleSample.class); - doReturn(Optional.of(new MapSample())).when(invoker).invokeFindById(4711); + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(SingleSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); - RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(TEXT_URI_LIST); + doReturn(Optional.of(new SingleSample())).when(invoker).invokeFindById(4711); - //Do we need integration test to verify HTTP response code? - Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, headers, 4711, - "reference")); - assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + CollectionModel request = CollectionModel + .empty(List.of(Link.of("/reference/some-id"), Link.of("/reference/some-another-id"))); - verify(invokerFactory, never()).getInvokerFor(Reference.class); - verify(invoker, never()).invokeFindById("some-id"); - } + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, request, 4711, "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); - /*@Test // GH-2495 - void rejectsInvalidContentTypeForSingleValuedAssociation() throws Exception { + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + verify(invoker, never()).invokeFindById("some-another-id"); + } - KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); + @Test // GH-2495 + void rejectsMapLinksForSingleValuedAssociation() throws Exception { - ResourceMappings mappings = new PersistentEntitiesResourceMappings( - new PersistentEntities(Collections.singleton(mappingContext))); - ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); - when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(MapSample.class); - RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, - invokerFactory); - controller.setApplicationEventPublisher(publisher); + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(MapSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); - doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); - RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - CollectionModel request = CollectionModel.empty(Link.of("/reference/some-id")); + doReturn(Optional.of(new MapSample())).when(invoker).invokeFindById(4711); - //Do we need integration test to verify HTTP response code? - Throwable thrown = catchThrowable(() -> controller.createPropertyReference(information, HttpMethod.POST, null, HttpHeaders.EMPTY, 4711, - "references")); - assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); - verify(invokerFactory, never()).getInvokerFor(Reference.class); - verify(invoker, never()).invokeFindById("some-id"); + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); - }*/ + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + } @RestResource @@ -211,15 +179,15 @@ static class Sample { @org.springframework.data.annotation.Reference List references = new ArrayList(); } - @RestResource - static class SingleSample { - @org.springframework.data.annotation.Reference Reference reference; - } + @RestResource + static class SingleSample { + @org.springframework.data.annotation.Reference Reference reference; + } - @RestResource - static class MapSample { - @org.springframework.data.annotation.Reference Map reference; - } + @RestResource + static class MapSample { + @org.springframework.data.annotation.Reference Map reference; + } @RestResource static class Reference {} From 247a793fe64e17012c9432f49a9f2bcc61cf499b Mon Sep 17 00:00:00 2001 From: Anayonkar Shivalkar Date: Sun, 29 Jun 2025 19:24:21 +0530 Subject: [PATCH 6/6] DATAREST-2495 Fixed merge conflicts Signed-off-by: Anayonkar Shivalkar DATAREST-2495 Cleaned up unnecessary code Signed-off-by: Anayonkar Shivalkar DATAREST-2495 Fixed test cases Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code where no links are sent (List of Map). Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for multiple links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for empty links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar # Conflicts: # spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java # spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java DATAREST-2495 Fixed test cases Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code where no links are sent (List of Map). Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for multiple links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar DATAREST-2495 In RepositoryPropertyReferenceController, handled response code for empty links. Instead of IllegalArgumentException and HTTP 500, now it throws HttpMessageNotReadableException and HTTP 400 Signed-off-by: Anayonkar Shivalkar --- ...RepositoryPropertyReferenceController.java | 22 ++-- ...yPropertyReferenceControllerUnitTests.java | 104 +++++++++++++++++- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 21eb819f7..70766124f 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -19,13 +19,10 @@ import static org.springframework.data.rest.webmvc.RestMediaTypes.*; import static org.springframework.web.bind.annotation.RequestMethod.*; +import java.io.InputStream; import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Optional; import java.util.function.Function; import org.springframework.context.ApplicationEventPublisher; @@ -42,6 +39,7 @@ import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent; import org.springframework.data.rest.core.event.BeforeLinkSaveEvent; import org.springframework.data.rest.webmvc.support.BackendId; +import org.springframework.data.rest.webmvc.util.InputStreamHttpInputMessage; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; @@ -54,6 +52,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -248,6 +247,10 @@ public ResponseEntity> createPropertyReference( Class propertyType = prop.property.getType(); if (prop.property.isCollectionLike()) { + if (source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Collection collection = AUGMENTING_METHODS.contains(requestMethod) // ? (Collection) prop.propertyValue // @@ -261,6 +264,10 @@ public ResponseEntity> createPropertyReference( prop.accessor.setProperty(prop.property, collection); } else if (prop.property.isMap()) { + if (source.getLinks().isEmpty()) { + throw new HttpMessageNotReadableException("No links provided", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); + } Map map = AUGMENTING_METHODS.contains(requestMethod) // ? (Map) prop.propertyValue // @@ -283,8 +290,9 @@ public ResponseEntity> createPropertyReference( } if (!source.getLinks().hasSingleLink()) { - throw new IllegalArgumentException( - "Must send only 1 link to update a property reference that isn't a List or a Map."); + throw new HttpMessageNotReadableException( + "Must send only 1 link to update a property reference that isn't a List or a Map.", + InputStreamHttpInputMessage.of(InputStream.nullInputStream())); } prop.accessor.setProperty(prop.property, diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java index c2d9c1f94..491b343e5 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -15,13 +15,12 @@ */ package org.springframework.data.rest.webmvc; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -46,6 +45,7 @@ import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.Link; import org.springframework.http.HttpMethod; +import org.springframework.http.converter.HttpMessageNotReadableException; /** * Unit tests for {@link RepositoryPropertyReferenceController}. @@ -90,11 +90,105 @@ void usesRepositoryInvokerToLookupRelatedInstance() throws Exception { verify(invoker).invokeFindById("some-id"); } - @RestResource + @Test // DATAREST-2495 + void rejectsEmptyLinksForAssociationUpdate() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(Sample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new Sample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, "references")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invoker, never()).invokeFindById("some-id"); + } + + @Test // GH-2495 + void rejectsMultipleLinksForSingleValuedAssociation() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(SingleSample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(SingleSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new SingleSample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + CollectionModel request = CollectionModel + .empty(List.of(Link.of("/reference/some-id"), Link.of("/reference/some-another-id"))); + + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, request, 4711, "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + verify(invoker, never()).invokeFindById("some-another-id"); + } + + @Test // GH-2495 + void rejectsMapLinksForSingleValuedAssociation() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getRequiredPersistentEntity(MapSample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(MapSample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory); + controller.setApplicationEventPublisher(publisher); + + doReturn(Optional.of(new MapSample())).when(invoker).invokeFindById(4711); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + + // Do we need integration test to verify HTTP response code? + Throwable thrown = catchThrowable( + () -> controller.createPropertyReference(information, HttpMethod.POST, null, 4711, "reference")); + assertThat(thrown).isInstanceOf(HttpMessageNotReadableException.class); + + verify(invokerFactory, never()).getInvokerFor(Reference.class); + verify(invoker, never()).invokeFindById("some-id"); + } + + + @RestResource static class Sample { @org.springframework.data.annotation.Reference List references = new ArrayList(); } + @RestResource + static class SingleSample { + @org.springframework.data.annotation.Reference Reference reference; + } + + @RestResource + static class MapSample { + @org.springframework.data.annotation.Reference Map reference; + } + @RestResource static class Reference {}