From da21f7438cbb85455d4ca64c377dc523dbb6e76c Mon Sep 17 00:00:00 2001 From: Koen Punt Date: Mon, 17 Oct 2022 11:22:13 +0200 Subject: [PATCH 1/3] Print actual elements in GraphQlTester See gh-507 --- .../graphql/test/tester/DefaultGraphQlTester.java | 6 ++++-- .../graphql/test/tester/GraphQlTesterTests.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java index 36157ce0..d6b69411 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java @@ -563,9 +563,11 @@ final class DefaultGraphQlTester implements GraphQlTester { public EntityList containsExactly(E... values) { doAssert(() -> { List expected = Arrays.asList(values); + List actual = getEntity(); AssertionErrors.assertTrue( - "List at path '" + getPath() + "' should have contained exactly " + expected, - getEntity().equals(expected)); + "List at path '" + getPath() + "' should have contained exactly " + expected + ", " + + "but did contain " + actual, + actual.equals(expected)); }); return this; } diff --git a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java index 4c171f03..7aacb44e 100644 --- a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java +++ b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java @@ -179,7 +179,8 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport { assertThatThrownBy(() -> entityList.containsExactly(leia, han)) .as("Should be exactly the same order") - .hasMessageStartingWith("List at path 'me.friends' should have contained exactly"); + .hasMessageStartingWith("List at path 'me.friends' should have contained exactly") + .hasMessageContaining("but did contain "); response.path("me.friends") .entityList(new ParameterizedTypeReference() {}) From 560c2b53f25d27b5b5f462583afb7353fd840726 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 17 Oct 2022 16:58:56 +0100 Subject: [PATCH 2/3] Polishing contribution Closes gh-507 --- .../test/tester/DefaultGraphQlTester.java | 18 +++++++++--------- .../test/tester/GraphQlTesterTests.java | 7 +++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java index d6b69411..003f7c3b 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultGraphQlTester.java @@ -540,7 +540,8 @@ final class DefaultGraphQlTester implements GraphQlTester { public EntityList contains(E... values) { doAssert(() -> { List expected = Arrays.asList(values); - AssertionErrors.assertTrue("List at path '" + getPath() + "' does not contain " + expected, + AssertionErrors.assertTrue( + "Expecting list " + getEntity() + " at path '" + getPath() + "' to contain " + expected, getEntity().containsAll(expected)); }); return this; @@ -552,7 +553,7 @@ final class DefaultGraphQlTester implements GraphQlTester { doAssert(() -> { List expected = Arrays.asList(values); AssertionErrors.assertTrue( - "List at path '" + getPath() + "' should not have contained " + expected, + "Expecting list " + getEntity() + " at path '" + getPath() + "' to not contain " + expected, !getEntity().containsAll(expected)); }); return this; @@ -563,18 +564,17 @@ final class DefaultGraphQlTester implements GraphQlTester { public EntityList containsExactly(E... values) { doAssert(() -> { List expected = Arrays.asList(values); - List actual = getEntity(); AssertionErrors.assertTrue( - "List at path '" + getPath() + "' should have contained exactly " + expected + ", " + - "but did contain " + actual, - actual.equals(expected)); + "Expecting list " + getEntity() + " at path '" + getPath() + "' to contain exactly " + expected, + getEntity().equals(expected)); }); return this; } @Override public EntityList hasSize(int size) { - doAssert(() -> AssertionErrors.assertTrue("List at path '" + getPath() + "' should have size " + size, + doAssert(() -> AssertionErrors.assertTrue( + "Expecting list " + getEntity() + " at path '" + getPath() + "' to have size == " + size, getEntity().size() == size)); return this; } @@ -582,7 +582,7 @@ final class DefaultGraphQlTester implements GraphQlTester { @Override public EntityList hasSizeLessThan(int size) { doAssert(() -> AssertionErrors.assertTrue( - "List at path '" + getPath() + "' should have size less than " + size, + "Expecting list " + getEntity() + " at path '" + getPath() + "' to have size < " + size, getEntity().size() < size)); return this; } @@ -590,7 +590,7 @@ final class DefaultGraphQlTester implements GraphQlTester { @Override public EntityList hasSizeGreaterThan(int size) { doAssert(() -> AssertionErrors.assertTrue( - "List at path '" + getPath() + "' should have size greater than " + size, + "Expecting list " + getEntity() + " at path '" + getPath() + "' to have size > " + size, getEntity().size() > size)); return this; } diff --git a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java index 7aacb44e..dddf261d 100644 --- a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java +++ b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/GraphQlTesterTests.java @@ -179,8 +179,11 @@ public class GraphQlTesterTests extends GraphQlTesterTestSupport { assertThatThrownBy(() -> entityList.containsExactly(leia, han)) .as("Should be exactly the same order") - .hasMessageStartingWith("List at path 'me.friends' should have contained exactly") - .hasMessageContaining("but did contain "); + .hasMessage("Expecting list " + + "[MovieCharacter[name='Han Solo'], MovieCharacter[name='Leia Organa']] " + + "at path 'me.friends' to contain exactly " + + "[MovieCharacter[name='Leia Organa'], MovieCharacter[name='Han Solo']]\n" + + "Request: document='{me {name, friends}}'"); response.path("me.friends") .entityList(new ParameterizedTypeReference() {}) From 13683eea17bbb0dff5b07ce883cef490a9665452 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 17 Oct 2022 17:27:44 +0100 Subject: [PATCH 3/3] Support Optional for ProjectPayload arguments Closes gh-506 --- ...rojectedPayloadMethodArgumentResolver.java | 29 ++++-- ...tedPayloadMethodArgumentResolverTests.java | 93 +++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java index ed588141..ab6be6ee 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -16,6 +16,8 @@ package org.springframework.graphql.data.method.annotation.support; +import java.util.Optional; + import graphql.schema.DataFetchingEnvironment; import org.springframework.context.ApplicationContext; @@ -77,24 +79,33 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu @Override public boolean supportsParameter(MethodParameter parameter) { - Class type = parameter.getParameterType(); - - if (!type.isInterface()) { - return false; - } - - return AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null; + Class type = parameter.nestedIfOptional().getNestedParameterType(); + return (type.isInterface() && + AnnotatedElementUtils.findMergedAnnotation(type, ProjectedPayload.class) != null); } @Override public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception { + String name = (parameter.hasParameterAnnotation(Argument.class) ? ArgumentMethodArgumentResolver.getArgumentName(parameter) : null); + Class projectionType = parameter.getParameterType(); + + boolean isOptional = parameter.isOptional(); + if (isOptional) { + projectionType = parameter.nestedIfOptional().getNestedParameterType(); + } + Object projectionSource = (name != null ? environment.getArgument(name) : environment.getArguments()); - return project(parameter.getParameterType(), projectionSource); + Object value = null; + if (!isOptional || projectionSource != null) { + value = project(projectionType, projectionSource); + } + + return (isOptional ? Optional.ofNullable(value) : value); } protected Object project(Class projectionType, Object projectionSource){ diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java new file mode 100644 index 00000000..22ac8108 --- /dev/null +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolverTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020-2022 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.graphql.data.method.annotation.support; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.core.MethodParameter; +import org.springframework.data.web.ProjectedPayload; +import org.springframework.graphql.Book; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.stereotype.Controller; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class ProjectedPayloadMethodArgumentResolverTests extends ArgumentResolverTestSupport { + + private ProjectedPayloadMethodArgumentResolver resolver; + + + @BeforeEach + void setUp() { + StaticApplicationContext context = new StaticApplicationContext(); + this.resolver = new ProjectedPayloadMethodArgumentResolver(context); + } + + + @Test + void supports() { + MethodParameter param = methodParam(BookController.class, "optionalProjection", Optional.class); + assertThat(this.resolver.supportsParameter(param)).isTrue(); + + param = methodParam(BookController.class, "optionalString", Optional.class); + assertThat(this.resolver.supportsParameter(param)).isFalse(); + } + + @Test + void optionalWrapper() throws Exception { + + Object result = this.resolver.resolveArgument( + methodParam(BookController.class, "optionalProjection", Optional.class), + environment("{}")); + + assertThat(result).isNotNull().isInstanceOf(Optional.class); + assertThat((Optional) result).isNotPresent(); + } + + + @SuppressWarnings({"ConstantConditions", "unused"}) + @Controller + static class BookController { + + @QueryMapping + public List optionalProjection(@Argument(name = "where") Optional projection) { + return null; + } + + @QueryMapping + public void optionalString(@Argument Optional projection) { + } + + } + + + @ProjectedPayload + interface BookProjection { + + String getAuthor(); + + } + +}