From 2c8638dd6aec5d994f200484e58965c16ad904cf Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 29 Oct 2021 11:37:51 +0000 Subject: [PATCH] HTTP handlers set Locale in ExecutionInput See gh-3 --- .../test/tester/DefaultGraphQlTester.java | 7 ++ .../test/tester/DefaultWebGraphQlTester.java | 11 +- .../graphql/test/tester/GraphQlTester.java | 8 ++ .../GraphQlTesterRequestSpecSupport.java | 10 +- .../tester/WebTestClientRequestStrategy.java | 10 +- .../test/tester/WebGraphQlTesterTests.java | 2 +- .../springframework/graphql/RequestInput.java | 50 +++++--- .../springframework/graphql/web/WebInput.java | 18 ++- .../web/webflux/GraphQlHttpHandler.java | 6 +- .../web/webflux/GraphQlWebSocketHandler.java | 3 +- .../web/webmvc/GraphQlHttpHandler.java | 9 +- .../web/webmvc/GraphQlWebSocketHandler.java | 2 +- .../support/BatchMappingInvocationTests.java | 4 +- .../support/SchemaMappingInvocationTests.java | 10 +- .../querydsl/QuerydslDataFetcherTests.java | 5 +- .../graphql/execution/BatchLoadingTests.java | 2 +- .../execution/ClassNameTypeResolverTests.java | 4 +- .../graphql/web/WebGraphQlHandlerTests.java | 3 +- .../graphql/web/WebInterceptorTests.java | 5 +- .../web/webflux/GraphQlHttpHandlerTests.java | 110 ++++++++++++++++++ .../web/webmvc/GraphQlHttpHandlerTests.java | 107 +++++++++++++++++ 21 files changed, 341 insertions(+), 45 deletions(-) create mode 100644 spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java create mode 100644 spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java 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 aa15b134..d202d181 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 @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -113,6 +114,12 @@ class DefaultGraphQlTester implements GraphQlTester { return this; } + @Override + public DefaultRequestSpec locale(Locale locale) { + setLocale(locale); + return this; + } + @Override public ResponseSpec execute() { return this.requestStrategy.execute(createRequestInput()); diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java index 08adbd02..25978090 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/DefaultWebGraphQlTester.java @@ -17,11 +17,13 @@ package org.springframework.graphql.test.tester; import java.net.URI; +import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; import reactor.core.publisher.Flux; +import org.springframework.graphql.RequestInput; import org.springframework.graphql.web.WebInput; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; @@ -122,6 +124,12 @@ class DefaultWebGraphQlTester implements WebGraphQlTester { return this; } + @Override + public WebRequestSpec locale(Locale locale) { + setLocale(locale); + return this; + } + @Override public WebRequestSpec httpHeader(String headerName, String... headerValues) { for (String headerValue : headerValues) { @@ -152,7 +160,8 @@ class DefaultWebGraphQlTester implements WebGraphQlTester { } private WebInput createWebInput() { - return new WebInput(DEFAULT_URL, this.headers, createRequestInput().toMap(), null); + RequestInput input = createRequestInput(); + return new WebInput(DEFAULT_URL, this.headers, input.toMap(), input.getLocale(), null); } } diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTester.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTester.java index 035ec428..484d66ac 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTester.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTester.java @@ -18,6 +18,7 @@ package org.springframework.graphql.test.tester; import java.time.Duration; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; import java.util.function.Predicate; @@ -174,6 +175,13 @@ public interface GraphQlTester { */ T variable(String name, Object value); + /** + * Set the locale to associate with the request. + * @param locale the locale to use + * @return this request spec + */ + T locale(Locale locale); + } /** diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java index 7e973136..283f8c03 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/GraphQlTesterRequestSpecSupport.java @@ -16,6 +16,7 @@ package org.springframework.graphql.test.tester; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import org.springframework.graphql.RequestInput; @@ -38,6 +39,9 @@ class GraphQlTesterRequestSpecSupport { private final Map variables = new LinkedHashMap<>(); + @Nullable + private Locale locale; + protected GraphQlTesterRequestSpecSupport(String query) { Assert.notNull(query, "`query` is required"); @@ -53,12 +57,16 @@ class GraphQlTesterRequestSpecSupport { this.variables.put(name, value); } + protected void setLocale(Locale locale) { + this.locale = locale; + } + protected void verify(GraphQlTester.ResponseSpec responseSpec) { responseSpec.path("$.errors").valueIsEmpty(); } protected RequestInput createRequestInput() { - return new RequestInput(this.query, this.operationName, this.variables); + return new RequestInput(this.query, this.operationName, this.variables, this.locale); } } diff --git a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientRequestStrategy.java b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientRequestStrategy.java index 6172a4e3..5706c74f 100644 --- a/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientRequestStrategy.java +++ b/spring-graphql-test/src/main/java/org/springframework/graphql/test/tester/WebTestClientRequestStrategy.java @@ -17,6 +17,8 @@ package org.springframework.graphql.test.tester; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Collections; +import java.util.Locale; import java.util.function.Predicate; import com.jayway.jsonpath.Configuration; @@ -82,7 +84,13 @@ final class WebTestClientRequestStrategy extends RequestStrategySupport implemen FluxExchangeResult exchangeResult = this.client.post() .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.TEXT_EVENT_STREAM) - .headers(headers -> headers.putAll(webInput.getHeaders())) + .headers(headers -> { + Locale locale = webInput.getLocale(); + if (locale != null) { + headers.setAcceptLanguageAsLocales(Collections.singletonList(locale)); + } + headers.putAll(webInput.getHeaders()); + }) .bodyValue(webInput.toMap()) .exchange() .expectStatus() diff --git a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java index 619843f7..42d054aa 100644 --- a/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java +++ b/spring-graphql-test/src/test/java/org/springframework/graphql/test/tester/WebGraphQlTesterTests.java @@ -227,7 +227,7 @@ public class WebGraphQlTesterTests { String content = request.getBody().readUtf8(); Map map = new ObjectMapper().readValue(content, new TypeReference>() {}); - WebInput webInput = new WebInput(request.getRequestUrl().uri(), headers, map, null); + WebInput webInput = new WebInput(request.getRequestUrl().uri(), headers, map, null, null); consumer.accept(webInput); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java b/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java index 6e08d18e..7f7306cf 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/RequestInput.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.BiFunction; @@ -47,18 +48,23 @@ public class RequestInput { private final Map variables; + @Nullable + private final Locale locale; + private final List> executionInputConfigurers = new ArrayList<>(); - public RequestInput(String query, @Nullable String operationName, @Nullable Map vars) { + + public RequestInput( + String query, @Nullable String operationName, @Nullable Map vars, + @Nullable Locale locale) { + Assert.notNull(query, "'query' is required"); this.query = query; this.operationName = operationName; this.variables = ((vars != null) ? vars : Collections.emptyMap()); + this.locale = locale; } - public RequestInput(Map body) { - this(getKey("query", body), getKey("operationName", body), getKey("variables", body)); - } @SuppressWarnings("unchecked") private static T getKey(String key, Map body) { @@ -66,8 +72,8 @@ public class RequestInput { } /** - * Return the query name extracted from the request body. This is guaranteed to be a - * non-empty string. + * Return the query name extracted from the request. This is guaranteed to + * be a non-empty string. * @return the query name */ public String getQuery() { @@ -75,8 +81,8 @@ public class RequestInput { } /** - * Return the operation name extracted from the request body or {@code null} if not - * provided. + * Return the operation name extracted from the request or {@code null} if + * not provided. * @return the operation name or {@code null} */ @Nullable @@ -85,14 +91,23 @@ public class RequestInput { } /** - * Return the variables that can be referenced via $syntax extracted from the request - * body or a {@code null} if not provided. + * Return the variables that can be referenced via $syntax extracted from + * the request body or a {@code null} if not provided. * @return the request variables or {@code null} */ public Map getVariables() { return this.variables; } + /** + * Return the locale associated with the request, if available. + * @return the locale of {@code null} + */ + @Nullable + public Locale getLocale() { + return this.locale; + } + /** * Provide a consumer to configure the {@link ExecutionInput} used for input to * {@link graphql.GraphQL#executeAsync(ExecutionInput)}. The builder is initially @@ -113,8 +128,12 @@ public class RequestInput { * @return the execution input */ public ExecutionInput toExecutionInput() { - ExecutionInput executionInput = ExecutionInput.newExecutionInput().query(this.query) - .operationName(this.operationName).variables(this.variables).build(); + ExecutionInput executionInput = ExecutionInput.newExecutionInput() + .query(this.query) + .operationName(this.operationName) + .variables(this.variables) + .locale(this.locale) + .build(); for (BiFunction configurer : this.executionInputConfigurers) { ExecutionInput current = executionInput; @@ -142,9 +161,10 @@ public class RequestInput { @Override public String toString() { - return "Query='" + getQuery() + "'" - + ((getOperationName() != null) ? ", Operation='" + getOperationName() + "'" : "") - + (!CollectionUtils.isEmpty(getVariables()) ? ", Variables=" + getVariables() : ""); + return "Query='" + getQuery() + "'" + + ((getOperationName() != null) ? ", Operation='" + getOperationName() + "'" : "") + + (!CollectionUtils.isEmpty(getVariables()) ? ", Variables=" + getVariables() : "") + + (getLocale() != null ? ", Locale=" + getLocale() : ""); } } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java index e49927db..92c6da81 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/WebInput.java @@ -17,6 +17,7 @@ package org.springframework.graphql.web; import java.net.URI; +import java.util.Locale; import java.util.Map; import org.springframework.graphql.RequestInput; @@ -50,12 +51,16 @@ public class WebInput extends RequestInput { * @param uri the url for the HTTP request, or WebSocket handshake * @param headers the HTTP request headers * @param body the content of the request deserialized from JSON + * @param locale the locale associated with the request, if any * @param id an identifier for the GraphQL request, e.g. a subscription id for * correlating request and response messages, or it could be an id associated with the * underlying request/connection id, if available */ - public WebInput(URI uri, HttpHeaders headers, Map body, @Nullable String id) { - super(validateQuery(body)); + public WebInput( + URI uri, HttpHeaders headers, Map body, + @Nullable Locale locale, @Nullable String id) { + + super(getKey("query", body), getKey("operationName", body), getKey("variables", body), locale); Assert.notNull(uri, "URI is required'"); Assert.notNull(headers, "HttpHeaders is required'"); this.uri = UriComponentsBuilder.fromUri(uri).build(true); @@ -63,14 +68,15 @@ public class WebInput extends RequestInput { this.id = (id != null) ? id : ObjectUtils.identityToString(this); } - private static Map validateQuery(Map body) { - String query = (String) body.get("query"); - if (!StringUtils.hasText(query)) { + @SuppressWarnings("unchecked") + private static T getKey(String key, Map body) { + if (key.equals("query") && !StringUtils.hasText((String) body.get(key))) { throw new ServerWebInputException("Query is required"); } - return body; + return (T) body.get(key); } + /** * Return the URI of the HTTP request including {@link UriComponents#getQueryParams() * URL query parameters}. diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java index 6ab94779..23942853 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlHttpHandler.java @@ -61,8 +61,10 @@ public class GraphQlHttpHandler { public Mono handleRequest(ServerRequest request) { return request.bodyToMono(MAP_PARAMETERIZED_TYPE_REF) .flatMap((body) -> { - String id = request.exchange().getRequest().getId(); - WebInput input = new WebInput(request.uri(), request.headers().asHttpHeaders(), body, id); + WebInput input = new WebInput( + request.uri(), request.headers().asHttpHeaders(), body, + request.exchange().getLocaleContext().getLocale(), + request.exchange().getRequest().getId()); if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java index c6457343..c983e5a3 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webflux/GraphQlWebSocketHandler.java @@ -161,7 +161,8 @@ public class GraphQlWebSocketHandler implements WebSocketHandler { if (id == null) { return GraphQlStatus.close(session, GraphQlStatus.INVALID_MESSAGE_STATUS); } - WebInput input = new WebInput(handshakeInfo.getUri(), handshakeInfo.getHeaders(), getPayload(map), id); + WebInput input = new WebInput( + handshakeInfo.getUri(), handshakeInfo.getHeaders(), getPayload(map), null, id); if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java index 768d4caa..a2496672 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandler.java @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.graphql.web.WebInput; @@ -68,10 +69,15 @@ public class GraphQlHttpHandler { * {@link HttpMediaTypeNotSupportedException}. */ public ServerResponse handleRequest(ServerRequest request) throws ServletException { - WebInput input = new WebInput(request.uri(), request.headers().asHttpHeaders(), readBody(request), null); + + WebInput input = new WebInput( + request.uri(), request.headers().asHttpHeaders(), readBody(request), + LocaleContextHolder.getLocale(), null); + if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } + Mono responseMono = this.graphQlHandler.handleRequest(input).map((output) -> { if (logger.isDebugEnabled()) { logger.debug("Execution complete"); @@ -82,6 +88,7 @@ public class GraphQlHttpHandler { } return builder.body(output.toSpecification()); }); + return ServerResponse.async(responseMono); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java index c5c4d19d..caaafe19 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/webmvc/GraphQlWebSocketHandler.java @@ -156,7 +156,7 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub URI uri = session.getUri(); Assert.notNull(uri, "Expected handshake url"); HttpHeaders headers = session.getHandshakeHeaders(); - WebInput input = new WebInput(uri, headers, getPayload(map), id); + WebInput input = new WebInput(uri, headers, getPayload(map), null, id); if (logger.isDebugEnabled()) { logger.debug("Executing: " + input); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java index e99cccdd..21fd56be 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/BatchMappingInvocationTests.java @@ -118,7 +118,7 @@ public class BatchMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService(controllerClass, CourseConfig.class) - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); List> actualCourses = GraphQlTestUtils.checkErrorsAndGetData(result, "courses"); @@ -150,7 +150,7 @@ public class BatchMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService(controllerClass, CourseConfig.class) - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); List> actualCourses = GraphQlTestUtils.checkErrorsAndGetData(result, "courses"); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java index a522a45f..3b382348 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java @@ -74,7 +74,7 @@ public class SchemaMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService() - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); Map book = GraphQlTestUtils.checkErrorsAndGetData(result, "bookById"); @@ -96,7 +96,7 @@ public class SchemaMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService() - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); List> bookList = GraphQlTestUtils.checkErrorsAndGetData(result, "booksByCriteria"); @@ -117,7 +117,7 @@ public class SchemaMappingInvocationTests { "}"; AtomicReference contextRef = new AtomicReference<>(); - RequestInput requestInput = new RequestInput(query, null, null); + RequestInput requestInput = new RequestInput(query, null, null, null); requestInput.configureExecutionInput((executionInput, builder) -> { contextRef.set(executionInput.getGraphQLContext()); return executionInput; @@ -147,7 +147,7 @@ public class SchemaMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService() - .execute(new RequestInput(operation, null, null)) + .execute(new RequestInput(operation, null, null, null)) .block(); Map author = GraphQlTestUtils.checkErrorsAndGetData(result, "addAuthor"); @@ -166,7 +166,7 @@ public class SchemaMappingInvocationTests { "}"; ExecutionResult result = initGraphQlService() - .execute(new RequestInput(operation, null, null)) + .execute(new RequestInput(operation, null, null, null)) .block(); Publisher publisher = GraphQlTestUtils.checkErrorsAndGetData(result); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java index e3a93600..7c096753 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/querydsl/QuerydslDataFetcherTests.java @@ -298,8 +298,9 @@ class QuerydslDataFetcherTests { } private WebInput input(String query) { - return new WebInput(URI.create("http://abc.org"), new HttpHeaders(), - Collections.singletonMap("query", query), "1"); + return new WebInput( + URI.create("http://abc.org"), new HttpHeaders(), Collections.singletonMap("query", query), + null, "1"); } interface BookProjection { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java index 3e35be45..ea773a51 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/BatchLoadingTests.java @@ -72,7 +72,7 @@ public class BatchLoadingTests { " }" + "}"; - RequestInput input = new RequestInput(query, null, null); + RequestInput input = new RequestInput(query, null, null, null); ExecutionResult result = service.execute(input).block(); assertThat(result.getErrors()).isEmpty(); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java index ee2d7f74..1435889b 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/ClassNameTypeResolverTests.java @@ -84,7 +84,7 @@ public class ClassNameTypeResolverTests { "}"; ExecutionResult result = new ExecutionGraphQlService(graphQlSource) - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); List> actualAnimals = GraphQlTestUtils.checkErrorsAndGetData(result, "animals"); @@ -134,7 +134,7 @@ public class ClassNameTypeResolverTests { "}"; ExecutionResult result = new ExecutionGraphQlService(graphQlSource) - .execute(new RequestInput(query, null, null)) + .execute(new RequestInput(query, null, null, null)) .block(); List> actualSightings = GraphQlTestUtils.checkErrorsAndGetData(result, "sightings"); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java index 0bc5f5c5..c114f0e1 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java @@ -48,7 +48,8 @@ import static org.assertj.core.api.Assertions.assertThat; public class WebGraphQlHandlerTests { private static final WebInput webInput = new WebInput( - URI.create("http://abc.org"), new HttpHeaders(), Collections.singletonMap("query", "{ greeting }"), "1"); + URI.create("http://abc.org"), new HttpHeaders(), Collections.singletonMap("query", "{ greeting }"), + null, "1"); @Test void reactorContextPropagation() { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java index 4ef8f5fa..eb039670 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java @@ -37,8 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class WebInterceptorTests { - private static final WebInput webInput = new WebInput(URI.create("http://abc.org"), new HttpHeaders(), - Collections.singletonMap("query", "{ notUsed }"), "1"); + private static final WebInput webInput = new WebInput( + URI.create("http://abc.org"), new HttpHeaders(), Collections.singletonMap("query", "{ notUsed }"), + null, "1"); @Test void interceptorOrder() { diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java new file mode 100644 index 00000000..539040fe --- /dev/null +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webflux/GraphQlHttpHandlerTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2021 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.web.webflux; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import graphql.GraphQL; +import graphql.schema.DataFetcher; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.GraphQlTestUtils; +import org.springframework.graphql.TestGraphQlSource; +import org.springframework.graphql.execution.ExecutionGraphQlService; +import org.springframework.graphql.web.WebGraphQlHandler; +import org.springframework.http.codec.EncoderHttpMessageWriter; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.mock.web.reactive.function.server.MockServerRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.result.view.ViewResolver; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GraphQlHttpHandler}. + * @author Rossen Stoyanchev + */ +public class GraphQlHttpHandlerTests { + + @Test + void locale() { + GraphQlHttpHandler handler = createHttpHandler( + "type Query { greeting: String }", "Query", "greeting", + (env) -> "Hello in " + env.getLocale()); + + MockServerHttpRequest httpRequest = + MockServerHttpRequest.post("/").acceptLanguageAsLocales(Locale.FRENCH).build(); + + MockServerHttpResponse httpResponse = handleRequest( + httpRequest, handler, Collections.singletonMap("query", "{greeting}")); + + assertThat(httpResponse.getBodyAsString().block()) + .isEqualTo("{\"data\":{\"greeting\":\"Hello in fr\"}}"); + } + + private GraphQlHttpHandler createHttpHandler( + String schemaContent, String type, String field, DataFetcher dataFetcher) { + + GraphQL graphQl = GraphQlTestUtils.initGraphQl(schemaContent, type, field, dataFetcher); + GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl)); + return new GraphQlHttpHandler(WebGraphQlHandler.builder(service).build()); + } + + private MockServerHttpResponse handleRequest( + MockServerHttpRequest httpRequest, GraphQlHttpHandler handler, Map body) { + + MockServerWebExchange exchange = MockServerWebExchange.from(httpRequest); + + MockServerRequest serverRequest = MockServerRequest.builder() + .exchange(exchange) + .uri(((ServerWebExchange) exchange).getRequest().getURI()) + .method(((ServerWebExchange) exchange).getRequest().getMethod()) + .headers(((ServerWebExchange) exchange).getRequest().getHeaders()) + .body(Mono.just((Object) body)); + + handler.handleRequest(serverRequest) + .flatMap(response -> response.writeTo(exchange, new DefaultContext())) + .block(); + + return exchange.getResponse(); + } + + + private static class DefaultContext implements ServerResponse.Context { + + @Override + public List> messageWriters() { + return Collections.singletonList(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder())); + } + + @Override + public List viewResolvers() { + return Collections.emptyList(); + } + + } + +} diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java new file mode 100644 index 00000000..c734409b --- /dev/null +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/webmvc/GraphQlHttpHandlerTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2021 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.web.webmvc; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.servlet.ServletException; + +import graphql.GraphQL; +import graphql.schema.DataFetcher; +import org.junit.jupiter.api.Test; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.graphql.GraphQlService; +import org.springframework.graphql.GraphQlTestUtils; +import org.springframework.graphql.TestGraphQlSource; +import org.springframework.graphql.execution.ExecutionGraphQlService; +import org.springframework.graphql.web.WebGraphQlHandler; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.function.AsyncServerResponse; +import org.springframework.web.servlet.function.ServerRequest; +import org.springframework.web.servlet.function.ServerResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GraphQlHttpHandler}. + * @author Rossen Stoyanchev + */ +public class GraphQlHttpHandlerTests { + + private static final List> MESSAGE_READERS = + Collections.singletonList(new MappingJackson2HttpMessageConverter()); + + + @Test + void locale() throws Exception { + GraphQlHttpHandler handler = createHttpHandler( + "type Query { greeting: String }", "Query", "greeting", (env) -> "Hello in " + env.getLocale()); + + MockHttpServletRequest servletRequest = new MockHttpServletRequest("POST", "/"); + servletRequest.setContentType("application/json"); + servletRequest.setContent("{\"query\":\"{ greeting }\"}".getBytes(StandardCharsets.UTF_8)); + servletRequest.setAsyncSupported(true); + + LocaleContextHolder.setLocale(Locale.FRENCH); + try { + MockHttpServletResponse servletResponse = handleRequest(servletRequest, handler); + + assertThat(servletResponse.getContentAsString()) + .isEqualTo("{\"data\":{\"greeting\":\"Hello in fr\"}}"); + } + finally { + LocaleContextHolder.resetLocaleContext(); + } + } + + private GraphQlHttpHandler createHttpHandler( + String schemaContent, String type, String field, DataFetcher dataFetcher) { + + GraphQL graphQl = GraphQlTestUtils.initGraphQl(schemaContent, type, field, dataFetcher); + GraphQlService service = new ExecutionGraphQlService(new TestGraphQlSource(graphQl)); + return new GraphQlHttpHandler(WebGraphQlHandler.builder(service).build()); + } + + private MockHttpServletResponse handleRequest( + MockHttpServletRequest servletRequest, GraphQlHttpHandler handler) throws ServletException, IOException { + + ServerRequest request = ServerRequest.create(servletRequest, MESSAGE_READERS); + ServerResponse response = ((AsyncServerResponse) handler.handleRequest(request)).block(); + + MockHttpServletResponse servletResponse = new MockHttpServletResponse(); + response.writeTo(servletRequest, servletResponse, new DefaultContext()); + return servletResponse; + } + + + private static class DefaultContext implements ServerResponse.Context { + + @Override + public List> messageConverters() { + return MESSAGE_READERS; + } + + } + +}