Polishing.

Imports, Javadoc, ticket references in test cases.

Removed the need for MethodParameterAware… flavors of the ResourceAssemblers by keeping the MethodParameter reference in the original assemblers in the first place. Extracted common Pageable MethodParameter lookup code into PageableMethodParameterUtils.

Related ticket: #1307.
This commit is contained in:
Oliver Drotbohm
2023-02-28 17:00:37 +01:00
parent 70f21bda9f
commit 116730c168
11 changed files with 327 additions and 422 deletions

View File

@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
/**
@@ -52,7 +53,6 @@ class PagedResourcesAssemblerArgumentResolverUnitTests {
var result = resolver.resolveArgument(new MethodParameter(method, 0), null, null, null);
assertThat(result).isInstanceOf(PagedResourcesAssembler.class);
assertThat(result).isNotInstanceOf(MethodParameterAwarePagedResourcesAssembler.class);
}
@Test // DATACMNS-418
@@ -118,10 +118,9 @@ class PagedResourcesAssemblerArgumentResolverUnitTests {
private static void assertMethodParameterAwarePagedResourcesAssemblerFor(Object result, MethodParameter parameter) {
assertThat(result).isInstanceOf(MethodParameterAwarePagedResourcesAssembler.class);
var assembler = (MethodParameterAwarePagedResourcesAssembler<?>) result;
assertThat(assembler.getMethodParameter()).isEqualTo(parameter);
assertThat(result).isInstanceOfSatisfying(PagedResourcesAssembler.class, it -> {
assertThat(ReflectionTestUtils.getField(it, "parameter")).isEqualTo(parameter);
});
}
private void assertRejectsAmbiguity(String methodName) throws Exception {

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.web;
import static org.assertj.core.api.Assertions.*;
@@ -9,81 +24,90 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Unit tests for {@link SlicedResourcesAssemblerArgumentResolver}.
*
* @author Michael Schout
* @author Oliver Drotbohm
* @since 3.1
*/
class SlicedResourcesAssemblerArgumentResolverUnitTest {
SlicedResourcesAssemblerArgumentResolver resolver;
private static void assertMethodParameterAwareSlicedResourcesAssemblerFor(Object result,
MethodParameter parameter) {
assertThat(result).isInstanceOf(MethodParameterAwareSlicedResourcesAssembler.class);
var assembler = (MethodParameterAwareSlicedResourcesAssembler<?>) result;
assertThat(assembler.getMethodParameter()).isEqualTo(parameter);
}
@BeforeEach
void setUp() {
WebTestUtils.initWebTest();
var hateoasPageableHandlerMethodArgumentResolver = new HateoasPageableHandlerMethodArgumentResolver();
this.resolver = new SlicedResourcesAssemblerArgumentResolver(hateoasPageableHandlerMethodArgumentResolver);
}
@Test
@Test // GH-1307
void createsPlainAssemblerWithoutContext() throws Exception {
var method = Controller.class.getMethod("noContext", SlicedResourcesAssembler.class);
var result = resolver.resolveArgument(new MethodParameter(method, 0), null, null, null);
assertThat(result).isInstanceOf(SlicedResourcesAssembler.class);
assertThat(result).isNotInstanceOf(MethodParameterAwareSlicedResourcesAssembler.class);
}
@Test
@Test // GH-1307
void selectsUniquePageableParameter() throws Exception {
var method = Controller.class.getMethod("unique", SlicedResourcesAssembler.class, Pageable.class);
assertSelectsParameter(method, 1);
}
@Test
@Test // GH-1307
void selectsUniquePageableParameterForQualifiedAssembler() throws Exception {
var method = Controller.class.getMethod("unnecessarilyQualified", SlicedResourcesAssembler.class,
Pageable.class);
assertSelectsParameter(method, 1);
}
@Test
@Test // GH-1307
void selectsUniqueQualifiedPageableParameter() throws Exception {
var method = Controller.class.getMethod("qualifiedUnique", SlicedResourcesAssembler.class, Pageable.class);
assertSelectsParameter(method, 1);
}
@Test
@Test // GH-1307
void selectsQualifiedPageableParameter() throws Exception {
var method = Controller.class.getMethod("qualified", SlicedResourcesAssembler.class, Pageable.class,
Pageable.class);
assertSelectsParameter(method, 1);
}
@Test
@Test // GH-1307
void rejectsAmbiguousPageableParameters() throws Exception {
assertRejectsAmbiguity("unqualifiedAmbiguity");
}
@Test
@Test // GH-1307
void rejectsAmbiguousPageableParametersForQualifiedAssembler() throws Exception {
assertRejectsAmbiguity("assemblerQualifiedAmbiguity");
}
@Test
@Test // GH-1307
void rejectsAmbiguityWithoutMatchingQualifiers() throws Exception {
assertRejectsAmbiguity("noMatchingQualifiers");
}
@Test
@Test // GH-1307
void doesNotFailForTemplatedMethodMapping() throws Exception {
var method = Controller.class.getMethod("methodWithPathVariable", SlicedResourcesAssembler.class);
var result = resolver.resolveArgument(new MethodParameter(method, 0), null, null, null);
@@ -91,6 +115,7 @@ class SlicedResourcesAssemblerArgumentResolverUnitTest {
}
private void assertSelectsParameter(Method method, int expectedIndex) {
var parameter = new MethodParameter(method, 0);
var result = resolver.resolveArgument(parameter, null, null, null);
@@ -98,13 +123,20 @@ class SlicedResourcesAssemblerArgumentResolverUnitTest {
}
private void assertRejectsAmbiguity(String methodName) throws Exception {
var method = Controller.class.getMethod(methodName, SlicedResourcesAssembler.class, Pageable.class,
Pageable.class);
var method = Controller.class.getMethod(methodName, SlicedResourcesAssembler.class, Pageable.class, Pageable.class);
assertThatIllegalStateException()
.isThrownBy(() -> resolver.resolveArgument(new MethodParameter(method, 0), null, null, null));
}
private static void assertMethodParameterAwareSlicedResourcesAssemblerFor(Object result, MethodParameter parameter) {
assertThat(result).isInstanceOfSatisfying(SlicedResourcesAssembler.class, it -> {
assertThat(ReflectionTestUtils.getField(it, "parameter")).isEqualTo(parameter);
});
}
@RequestMapping("/")
interface Controller {
void noContext(SlicedResourcesAssembler<Object> resolver);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 the original author or authors.
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
*/
package org.springframework.data.web;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.*;
import java.net.URI;
import java.util.Collection;
@@ -26,8 +25,15 @@ import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.*;
import org.springframework.hateoas.*;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.SlicedModel;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.hateoas.server.core.EmbeddedWrapper;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -38,37 +44,25 @@ import org.springframework.web.util.UriComponentsBuilder;
* Unit tests for {@link SlicedResourcesAssembler}.
*
* @author Michael Schout
* @author Oliver Drotbohm
* @since 3.1
*/
class SlicedResourcesAssemblerUnitTest {
static final Pageable PAGEABLE = PageRequest.of(0, 20);
static final Slice<Person> EMPTY_SLICE = new SliceImpl<>(Collections.emptyList(), PAGEABLE, false);
HateoasPageableHandlerMethodArgumentResolver resolver = new HateoasPageableHandlerMethodArgumentResolver();
SlicedResourcesAssembler<Person> assembler = new SlicedResourcesAssembler<>(resolver, null);
private static Slice<Person> createSlice(int index) {
Pageable request = PageRequest.of(index, 1);
var person = new Person();
person.name = "Dave";
boolean hasNext = index < 2;
return new SliceImpl<>(Collections.singletonList(person), request, hasNext);
}
private static Map<String, String> getQueryParameters(Link link) {
var uriComponents = UriComponentsBuilder.fromUri(URI.create(link.expand().getHref())).build();
return uriComponents.getQueryParams().toSingleValueMap();
}
@BeforeEach
void setUp() {
WebTestUtils.initWebTest();
}
@Test
@Test // GH-1307
void addsNextLinkForFirstSlice() {
var resources = assembler.toModel(createSlice(0));
assertThat(resources.getLink(IanaLinkRelations.PREV)).isEmpty();
@@ -76,8 +70,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getLink(IanaLinkRelations.NEXT)).isNotEmpty();
}
@Test
@Test // GH-1307
void addsPreviousAndNextLinksForMiddleSlice() {
var resources = assembler.toModel(createSlice(1));
assertThat(resources.getLink(IanaLinkRelations.PREV)).isNotEmpty();
@@ -85,8 +80,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getLink(IanaLinkRelations.NEXT)).isNotEmpty();
}
@Test
@Test // GH-1307
void addsPreviousLinkForLastSlice() {
var resources = assembler.toModel(createSlice(2));
assertThat(resources.getLink(IanaLinkRelations.PREV)).isNotEmpty();
@@ -94,8 +90,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getLink(IanaLinkRelations.NEXT)).isEmpty();
}
@Test
@Test // GH-1307
void usesBaseUriIfConfigured() {
var baseUri = UriComponentsBuilder.fromUriString("https://foo:9090").build();
var assembler = new SlicedResourcesAssembler<Person>(resolver, baseUri);
@@ -106,8 +103,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getRequiredLink(IanaLinkRelations.NEXT).getHref()).startsWith(baseUri.toUriString());
}
@Test
@Test // GH-1307
void usesCustomLinkProvided() {
var link = Link.of("https://foo:9090", "rel");
var resources = assembler.toModel(createSlice(1), link);
@@ -117,8 +115,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getRequiredLink(IanaLinkRelations.NEXT).getHref()).startsWith(link.getHref());
}
@Test
@Test // GH-1307
void createsSlicedResourcesForOneIndexedArgumentResolver() {
resolver.setOneIndexedParameters(true);
AbstractPageRequest request = PageRequest.of(0, 1);
@@ -127,15 +126,17 @@ class SlicedResourcesAssemblerUnitTest {
assembler.toModel(slice);
}
@Test
@Test // GH-1307
void createsACanonicalLinkWithoutTemplateParameters() {
var resources = assembler.toModel(createSlice(1));
assertThat(resources.getRequiredLink(IanaLinkRelations.SELF).getHref()).doesNotContain("{").doesNotContain("}");
}
@Test
@Test // GH-1307
void invokesCustomElementResourceAssembler() {
var personAssembler = new PersonResourceAssembler();
var resources = assembler.toModel(createSlice(0), personAssembler);
@@ -148,8 +149,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(content.iterator().next().name).isEqualTo("Dave");
}
@Test
@Test // GH-1307
void createsPaginationLinksForOneIndexedArgumentResolverCorrectly() {
var argumentResolver = new HateoasPageableHandlerMethodArgumentResolver();
argumentResolver.setOneIndexedParameters(true);
@@ -167,8 +169,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(getQueryParameters(resource.getRequiredLink("next"))).containsEntry("page", "3");
}
@Test
@Test // GH-1307
void generatedLinksShouldNotBeTemplated() {
var resources = assembler.toModel(createSlice(1));
assertThat(resources.getRequiredLink(IanaLinkRelations.SELF).getHref()).doesNotContain("{").doesNotContain("}");
@@ -176,8 +179,9 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getRequiredLink(IanaLinkRelations.PREV).getHref()).endsWith("?page=0&size=1");
}
@Test
@Test // GH-1307
void generatesEmptySliceResourceWithEmbeddedWrapper() {
var result = assembler.toEmptyModel(EMPTY_SLICE, Person.class);
var content = result.getContent();
@@ -188,39 +192,40 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(((EmbeddedWrapper) element).getRelTargetType()).isEqualTo(Person.class);
}
@Test
@Test // GH-1307
void emptySliceCreatorRejectsSliceWithContent() {
assertThatIllegalArgumentException().isThrownBy(() -> assembler.toEmptyModel(createSlice(1), Person.class));
}
@Test
@Test // GH-1307
void emptySliceCreatorRejectsNullType() {
assertThatIllegalArgumentException().isThrownBy(() -> assembler.toEmptyModel(EMPTY_SLICE, null));
}
@Test
@Test // GH-1307
void addsFirstLinkForMultipleSlices() {
var resources = assembler.toModel(createSlice(1));
assertThat(resources.getRequiredLink(IanaLinkRelations.FIRST).getHref()).endsWith("?page=0&size=1");
}
@Test
@Test // GH-1307
void addsFirstLinkForFirstSlice() {
var resources = assembler.toModel(createSlice(0));
assertThat(resources.getRequiredLink(IanaLinkRelations.FIRST).getHref()).endsWith("?page=0&size=1");
}
@Test
@Test // GH-1307
void addsFirstLinkForLastSlice() {
var resources = assembler.toModel(createSlice(2));
assertThat(resources.getRequiredLink(IanaLinkRelations.FIRST).getHref()).endsWith("?page=0&size=1");
}
@Test
@Test // GH-1307
void alwaysAddsFirstLinkIfConfiguredTo() {
var assembler = new SlicedResourcesAssembler<Person>(resolver, null);
assembler.setForceFirstRel(true);
@@ -229,22 +234,22 @@ class SlicedResourcesAssemblerUnitTest {
assertThat(resources.getRequiredLink(IanaLinkRelations.FIRST).getHref()).endsWith("?page=0&size=20");
}
@Test
@Test // GH-1307
void usesCustomSlicedResources() {
RepresentationModelAssembler<Slice<Person>, SlicedModel<EntityModel<Person>>> assembler = new CustomSlicedResourcesAssembler<>(
resolver, null);
var assembler = new CustomSlicedResourcesAssembler<Person>(resolver, null);
assertThat(assembler.toModel(EMPTY_SLICE)).isInstanceOf(CustomSlicedResources.class);
}
@Test
@Test // GH-1307
void selfLinkContainsCoordinatesForCurrentSlice() {
var resource = assembler.toModel(createSlice(0));
assertThat(resource.getRequiredLink(IanaLinkRelations.SELF).getHref()).endsWith("?page=0&size=1");
}
@Test
@Test // GH-1307
void keepsRequestParametersOfOriginalRequestUri() {
WebTestUtils.initWebTest(new MockHttpServletRequest("GET", "/sample?foo=bar"));
@@ -254,6 +259,23 @@ class SlicedResourcesAssemblerUnitTest {
.isEqualTo("http://localhost/sample?foo=bar&page=0&size=1");
}
private static Slice<Person> createSlice(int index) {
Pageable request = PageRequest.of(index, 1);
var person = new Person();
person.name = "Dave";
boolean hasNext = index < 2;
return new SliceImpl<>(Collections.singletonList(person), request, hasNext);
}
private static Map<String, String> getQueryParameters(Link link) {
var uriComponents = UriComponentsBuilder.fromUri(URI.create(link.expand().getHref())).build();
return uriComponents.getQueryParams().toSingleValueMap();
}
static class Person {
String name;
}
@@ -288,4 +310,4 @@ class SlicedResourcesAssemblerUnitTest {
super(content, metadata);
}
}
}
}