From e644e5e192ba4a7aee896b586ccfeace309e75da Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 1 Oct 2021 10:52:42 +0100 Subject: [PATCH] Support classes for Builder, RequestStrategy, and RequestSpec Also separate RequestStrategy from WebRequestStrategy since those are delegated to internally and never used as a hierarchy. The generics required to make them extend from each other bring no benefit. --- .../test/tester/DefaultGraphQlTester.java | 327 ++++++++++-------- .../test/tester/DefaultWebGraphQlTester.java | 222 ++++++------ 2 files changed, 284 insertions(+), 265 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 6d4ea217..08bfdd83 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 @@ -62,15 +62,11 @@ class DefaultGraphQlTester implements GraphQlTester { DefaultGraphQlTester(RequestStrategy requestStrategy) { + Assert.notNull(requestStrategy, "RequestStrategy is required."); this.requestStrategy = requestStrategy; } - protected RequestStrategy getRequestStrategy() { - return this.requestStrategy; - } - - @Override public RequestSpec query(String query) { return new DefaultRequestSpec(this.requestStrategy, query); @@ -78,11 +74,10 @@ class DefaultGraphQlTester implements GraphQlTester { /** - * Default implementation to build {@link GraphQlTester}. + * Base class support for + * {@link GraphQlTester.Builder} and {@link WebGraphQlTester.Builder}. */ - final static class DefaultBuilder implements Builder { - - private final GraphQlService service; + static class BuilderSupport { @Nullable private Predicate errorFilter; @@ -90,8 +85,49 @@ class DefaultGraphQlTester implements GraphQlTester { @Nullable private Configuration jsonPathConfig; + @Nullable private Duration responseTimeout = Duration.ofSeconds(5); + protected void addErrorFilter(Predicate predicate) { + this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate); + } + + @Nullable + protected Predicate getErrorFilter() { + return errorFilter; + } + + protected void setJsonPathConfig(Configuration config) { + this.jsonPathConfig = config; + } + + protected void setResponseTimeout(Duration timeout) { + Assert.notNull(timeout, "'timeout' is required"); + this.responseTimeout = timeout; + } + + @Nullable + protected Duration getResponseTimeout() { + return this.responseTimeout; + } + + protected Configuration initJsonPathConfig() { + return JsonPathConfiguration.initialize(this.jsonPathConfig); + } + + protected Duration initResponseTimeout() { + return (this.responseTimeout != null ? this.responseTimeout : Duration.ofSeconds(5)); + } + } + + + /** + * Default implementation to build {@link GraphQlTester}. + */ + static class DefaultBuilder extends BuilderSupport implements Builder { + + private final GraphQlService service; + DefaultBuilder(GraphQlService service) { Assert.notNull(service, "GraphQlService is required."); this.service = service; @@ -99,29 +135,26 @@ class DefaultGraphQlTester implements GraphQlTester { @Override public DefaultBuilder errorFilter(Predicate predicate) { - this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate); + addErrorFilter(predicate); return this; } @Override public DefaultBuilder jsonPathConfig(Configuration config) { - this.jsonPathConfig = config; + setJsonPathConfig(config); return this; } @Override public DefaultBuilder responseTimeout(Duration timeout) { - Assert.notNull(timeout, "'timeout' is required"); - this.responseTimeout = timeout; + setResponseTimeout(timeout); return this; } @Override public GraphQlTester build() { - Configuration jsonPathConfig = JsonPathConfiguration.initialize(this.jsonPathConfig); - RequestStrategy strategy = new GraphQlServiceRequestStrategy( - this.service, this.errorFilter, jsonPathConfig, this.responseTimeout); + this.service, getErrorFilter(), initJsonPathConfig(), initResponseTimeout()); return new DefaultGraphQlTester(strategy); } @@ -138,24 +171,23 @@ class DefaultGraphQlTester implements GraphQlTester { * @param input the request input * @return the response spec */ - GraphQlTester.ResponseSpec execute(RequestInput input); + ResponseSpec execute(RequestInput input); /** * Perform a subscription with the given {@link RequestInput} container. * @param input the request input * @return the subscription spec */ - GraphQlTester.SubscriptionSpec executeSubscription(RequestInput input); + SubscriptionSpec executeSubscription(RequestInput input); } /** - * Base class for a {@link RequestStrategy} that perform GraphQL requests - * without an underlying transport and where {@link RequestInput} provides - * sufficient input. + * Base class support for {@link RequestStrategy} and + * {@link DefaultWebGraphQlTester.WebRequestStrategy} implementations. */ - protected static class DirectRequestStrategySupport { + static class RequestStrategySupport { @Nullable private final Predicate errorFilter; @@ -164,7 +196,7 @@ class DefaultGraphQlTester implements GraphQlTester { private final Duration responseTimeout; - protected DirectRequestStrategySupport( + protected RequestStrategySupport( @Nullable Predicate errorFilter, Configuration jsonPathConfig, Duration timeout) { this.errorFilter = errorFilter; @@ -172,26 +204,57 @@ class DefaultGraphQlTester implements GraphQlTester { this.responseTimeout = timeout; } - protected Duration responseTimeout() { + protected Configuration getJsonPathConfig() { + return this.jsonPathConfig; + } + + protected Duration getResponseTimeout() { return this.responseTimeout; } - protected ResponseSpec createResponseSpec(RequestInput input, ExecutionResult result) { + protected ResponseSpec createResponseSpec(ExecutionResult result, Consumer assertDecorator) { DocumentContext context = JsonPath.parse(result.toSpecification(), this.jsonPathConfig); - return new DefaultResponseSpec(context, this.errorFilter, assertDecorator(input)); + return createResponseSpec(context, assertDecorator); + } + + protected ResponseSpec createResponseSpec(DocumentContext context, Consumer assertDecorator) { + return new DefaultResponseSpec(context, this.errorFilter, assertDecorator); + } + } + + + /** + * Base class for a {@link RequestStrategy} that perform GraphQL requests + * without an underlying transport and where {@link RequestInput} provides + * sufficient input. + */ + static class DirectRequestStrategySupport extends RequestStrategySupport { + + protected DirectRequestStrategySupport( + @Nullable Predicate errorFilter, Configuration jsonPathConfig, Duration timeout) { + + super(errorFilter, jsonPathConfig, timeout); + } + + protected ResponseSpec createResponseSpec(RequestInput input, ExecutionResult result) { + return createResponseSpec(result, assertDecorator(input)); } protected SubscriptionSpec createSubscriptionSpec(RequestInput input, ExecutionResult result) { - AssertionErrors.assertTrue( - "Subscription did not return Publisher", result.getData() instanceof Publisher); - Consumer assertDecorator = assertDecorator(input); - List errors = result.getErrors(); - assertDecorator.accept(() -> AssertionErrors.assertTrue( - "Response has " + errors.size() + " unexpected error(s).", CollectionUtils.isEmpty(errors))); - Publisher publisher = result.getData(); - return new DefaultSubscriptionSpec(publisher, this.errorFilter, this.jsonPathConfig, assertDecorator); + assertDecorator.accept(() -> AssertionErrors.assertTrue( + "Subscription did not return Publisher", + result.getData() instanceof Publisher)); + + assertDecorator.accept(() -> AssertionErrors.assertTrue( + "Response has " + result.getErrors().size() + " unexpected error(s).", + CollectionUtils.isEmpty(result.getErrors()))); + + return () -> { + Publisher publisher = result.getData(); + return Flux.from(publisher).map((current) -> createResponseSpec(current, assertDecorator)); + }; } private Consumer assertDecorator(RequestInput input) { @@ -234,7 +297,7 @@ class DefaultGraphQlTester implements GraphQlTester { } private ExecutionResult executeInternal(RequestInput input) { - ExecutionResult result = this.graphQlService.execute(input).block(responseTimeout()); + ExecutionResult result = this.graphQlService.execute(input).block(getResponseTimeout()); Assert.notNull(result, "Expected ExecutionResult"); return result; } @@ -242,11 +305,10 @@ class DefaultGraphQlTester implements GraphQlTester { /** - * {@link RequestSpec} that collects the query, operationName, and variables. + * Base class support for + * {@link GraphQlTester.RequestSpec} and {@link WebGraphQlTester.RequestSpec}. */ - static class DefaultRequestSpec implements RequestSpec { - - private final RequestStrategy requestStrategy; + static class RequestSpecSupport { private final String query; @@ -255,22 +317,51 @@ class DefaultGraphQlTester implements GraphQlTester { private final Map variables = new LinkedHashMap<>(); - protected DefaultRequestSpec(RequestStrategy requestStrategy, String query) { - Assert.notNull(requestStrategy, "RequestStrategy is required"); + protected RequestSpecSupport(String query) { Assert.notNull(query, "`query` is required"); - this.requestStrategy = requestStrategy; this.query = query; } + protected void setOperationName(@Nullable String name) { + this.operationName = name; + } + + protected void addVariable(String name, Object value) { + this.variables.put(name, value); + } + + protected void verify(ResponseSpec responseSpec) { + responseSpec.path("$.errors").valueIsEmpty(); + } + + protected RequestInput createRequestInput() { + return new RequestInput(this.query, this.operationName, this.variables); + } + } + + + /** + * {@link RequestSpec} that collects the query, operationName, and variables. + */ + static class DefaultRequestSpec extends RequestSpecSupport implements RequestSpec { + + private final RequestStrategy requestStrategy; + + protected DefaultRequestSpec(RequestStrategy requestStrategy, String query) { + super(query); + Assert.notNull(requestStrategy, "RequestStrategy is required"); + this.requestStrategy = requestStrategy; + } + @Override public DefaultRequestSpec operationName(@Nullable String name) { - this.operationName = name; + setOperationName(name); return this; } @Override public DefaultRequestSpec variable(String name, Object value) { - this.variables.put(name, value); + addVariable(name, value); return this; } @@ -281,39 +372,78 @@ class DefaultGraphQlTester implements GraphQlTester { @Override public void executeAndVerify() { - execute().path("$.errors").valueIsEmpty(); + verify(execute()); } @Override public SubscriptionSpec executeSubscription() { return this.requestStrategy.executeSubscription(createRequestInput()); } - - public RequestInput createRequestInput() { - return new RequestInput(this.query, this.operationName, this.variables); - } } - private static class ErrorsContainer { + /** + * Container for a GraphQL response with access to data and errors. + */ + private static class ResponseContainer { + + private static final TypeRef> ERROR_LIST_TYPE = new TypeRef>() {}; + + private static final JsonPath ERRORS_PATH = JsonPath.compile("$.errors"); private static final Predicate MATCH_ALL_PREDICATE = (error) -> true; + + private final DocumentContext documentContext; + + private final String jsonContent; + private final List errors; private final Consumer assertDecorator; - ErrorsContainer( - List errors, @Nullable Predicate errorFilter, + + ResponseContainer( + DocumentContext documentContext, @Nullable Predicate errorFilter, Consumer assertDecorator) { - Assert.notNull(errors, "`errors` is required"); Assert.notNull(assertDecorator, "`assertDecorator` is required"); - this.errors = errors; + this.documentContext = documentContext; + this.jsonContent = this.documentContext.jsonString(); + this.errors = readErrors(documentContext); this.assertDecorator = assertDecorator; + filterErrors(errorFilter); } + private static List readErrors(DocumentContext documentContext) { + Assert.notNull(documentContext, "DocumentContext is required"); + try { + return documentContext.read(ERRORS_PATH, ERROR_LIST_TYPE); + } + catch (PathNotFoundException ex) { + return Collections.emptyList(); + } + } + + String jsonContent() { + return this.jsonContent; + } + + String jsonContent(JsonPath jsonPath) { + try { + Object content = this.documentContext.read(jsonPath); + return this.documentContext.configuration().jsonProvider().toJson(content); + } + catch (Exception ex) { + throw new AssertionError("JSON parsing error", ex); + } + } + + T read(JsonPath jsonPath, TypeRef typeRef) { + return this.documentContext.read(jsonPath, typeRef); + } + void doAssert(Runnable task) { this.assertDecorator.accept(task); } @@ -341,7 +471,7 @@ class DefaultGraphQlTester implements GraphQlTester { .accept(() -> AssertionErrors.assertTrue( "Response has " + unexpected.size() + " unexpected error(s)" + ((unexpected.size() != this.errors.size()) - ? " of " + this.errors.size() + " total" : "") + ? " of " + this.errors.size() + " total" : "") + ". " + "If expected, please use ResponseSpec#errors to filter them out: " + unexpected, CollectionUtils.isEmpty(unexpected))); @@ -349,58 +479,6 @@ class DefaultGraphQlTester implements GraphQlTester { } - /** - * Container for a GraphQL response with access to data and errors. - */ - private static class ResponseContainer extends ErrorsContainer { - - private static final TypeRef> ERROR_LIST_TYPE = new TypeRef>() {}; - - private static final JsonPath ERRORS_PATH = JsonPath.compile("$.errors"); - - private final DocumentContext documentContext; - - private final String jsonContent; - - ResponseContainer( - DocumentContext documentContext, @Nullable Predicate errorFilter, - Consumer assertDecorator) { - - super(readErrors(documentContext), errorFilter, assertDecorator); - this.documentContext = documentContext; - this.jsonContent = this.documentContext.jsonString(); - } - - private static List readErrors(DocumentContext documentContext) { - Assert.notNull(documentContext, "DocumentContext is required"); - try { - return documentContext.read(ERRORS_PATH, ERROR_LIST_TYPE); - } - catch (PathNotFoundException ex) { - return Collections.emptyList(); - } - } - - String jsonContent() { - return this.jsonContent; - } - - String jsonContent(JsonPath jsonPath) { - try { - Object content = this.documentContext.read(jsonPath); - return this.documentContext.configuration().jsonProvider().toJson(content); - } - catch (Exception ex) { - throw new AssertionError("JSON parsing error", ex); - } - } - - T read(JsonPath jsonPath, TypeRef typeRef) { - return this.documentContext.read(jsonPath, typeRef); - } - } - - /** * {@link ResponseSpec} that operates on the response from a GraphQL HTTP request. */ @@ -742,41 +820,6 @@ class DefaultGraphQlTester implements GraphQlTester { } - /** - * {@link SubscriptionSpec} implementation that operates on a {@link Publisher} of - * {@link ExecutionResult}. - */ - protected static class DefaultSubscriptionSpec implements SubscriptionSpec { - - private final Publisher publisher; - - @Nullable - private final Predicate errorFilter; - - private final Configuration jsonPathConfig; - - private final Consumer assertDecorator; - - protected DefaultSubscriptionSpec( - Publisher publisher, @Nullable Predicate errorFilter, - Configuration jsonPathConfig, Consumer decorator) { - - this.publisher = publisher; - this.errorFilter = errorFilter; - this.jsonPathConfig = jsonPathConfig; - this.assertDecorator = decorator; - } - - @Override - public Flux toFlux() { - return Flux.from(this.publisher).map((result) -> { - DocumentContext context = JsonPath.parse(result.toSpecification(), this.jsonPathConfig); - return new DefaultResponseSpec(context, this.errorFilter, this.assertDecorator); - }); - } - } - - static class JsonPathConfiguration { private static final boolean jackson2Present; 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 2a069046..f2587ee5 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 @@ -19,20 +19,15 @@ package org.springframework.graphql.test.tester; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.function.Supplier; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; -import graphql.ExecutionResult; import graphql.GraphQLError; import reactor.core.publisher.Flux; -import org.springframework.graphql.RequestInput; import org.springframework.graphql.web.WebGraphQlHandler; import org.springframework.graphql.web.WebInput; import org.springframework.graphql.web.WebOutput; @@ -50,78 +45,69 @@ import org.springframework.util.CollectionUtils; * * @author Rossen Stoyanchev */ -class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQlTester { +class DefaultWebGraphQlTester implements WebGraphQlTester { + + private final WebRequestStrategy requestStrategy; @Nullable private final HttpHeaders defaultHeaders; DefaultWebGraphQlTester(WebRequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders) { - super(requestStrategy); + Assert.notNull(requestStrategy, "WebRequestStrategy is required."); + this.requestStrategy = requestStrategy; this.defaultHeaders = defaultHeaders; } @Override public WebRequestSpec query(String query) { - return new DefaultWebRequestSpec((WebRequestStrategy) getRequestStrategy(), query, this.defaultHeaders); + return new DefaultWebRequestSpec(this.requestStrategy, this.defaultHeaders, query); } /** * Default implementation to build {@link WebGraphQlTester}. */ - final static class DefaultBuilder implements WebGraphQlTester.Builder { + final static class DefaultBuilder + extends DefaultGraphQlTester.BuilderSupport implements WebGraphQlTester.Builder { @Nullable - private Predicate errorFilter; + private final WebTestClient client; @Nullable - private Configuration jsonPathConfig; - - @Nullable - private Duration responseTimeout; - - private final Supplier requestStrategySupplier; + private final WebGraphQlHandler handler; @Nullable private HttpHeaders headers; DefaultBuilder(WebTestClient client) { - this.requestStrategySupplier = () -> { - WebTestClient clientToUse = (this.responseTimeout != null ? - client.mutate().responseTimeout(this.responseTimeout).build() : client); - return new WebTestClientRequestStrategy(clientToUse, this.errorFilter, initJsonPathConfig()); - }; + Assert.notNull(client, "WebTestClient is required."); + this.client = client; + this.handler = null; } DefaultBuilder(WebGraphQlHandler handler) { - this.requestStrategySupplier = () -> - new WebGraphQlHandlerRequestStrategy( - handler, this.errorFilter, initJsonPathConfig(), - (this.responseTimeout != null ? this.responseTimeout : Duration.ofSeconds(5))); - } - - private Configuration initJsonPathConfig() { - return JsonPathConfiguration.initialize(this.jsonPathConfig); + Assert.notNull(handler, "WebGraphQlHandler is required."); + this.handler = handler; + this.client = null; } @Override public WebGraphQlTester.Builder errorFilter(Predicate predicate) { - this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate); + addErrorFilter(predicate); return this; } @Override public DefaultBuilder jsonPathConfig(Configuration config) { - this.jsonPathConfig = config; + setJsonPathConfig(config); return this; } @Override public DefaultBuilder responseTimeout(Duration timeout) { - Assert.notNull(timeout, "'timeout' is required"); - this.responseTimeout = timeout; + setResponseTimeout(timeout); return this; } @@ -143,7 +129,23 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl @Override public WebGraphQlTester build() { - return new DefaultWebGraphQlTester(this.requestStrategySupplier.get(), this.headers); + WebRequestStrategy requestStrategy; + if (this.client != null) { + WebTestClient clientToUse = this.client; + if (getResponseTimeout() != null) { + clientToUse = this.client.mutate().responseTimeout(getResponseTimeout()).build(); + } + requestStrategy = new WebTestClientRequestStrategy( + clientToUse, getErrorFilter(), initJsonPathConfig(), getResponseTimeout()); + } + else if (this.handler != null) { + requestStrategy = new WebGraphQlHandlerRequestStrategy( + this.handler, getErrorFilter(), initJsonPathConfig(), initResponseTimeout()); + } + else { + throw new IllegalStateException("Neither client nor handler"); + } + return new DefaultWebGraphQlTester(requestStrategy, this.headers); } } @@ -152,54 +154,49 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl * Extension of {@code RequestStrategy} for performing a GraphQL request * in a web environment. */ - interface WebRequestStrategy extends RequestStrategy { + interface WebRequestStrategy { /** - * Perform a request with the given {@link RequestInput} container. + * Perform a request with the given {@link WebInput} container. * @param input the request input * @return the response spec */ - WebResponseSpec execute(RequestInput input); + WebResponseSpec execute(WebInput input); /** - * Perform a subscription with the given {@link RequestInput} container. + * Perform a subscription with the given {@link WebInput} container. * @param input the request input * @return the subscription spec */ - WebSubscriptionSpec executeSubscription(RequestInput input); + WebSubscriptionSpec executeSubscription(WebInput input); } /** - * {@link RequestStrategy} that works as an HTTP client with requests executed through + * {@link WebRequestStrategy} that works as an HTTP client with requests executed through * {@link WebTestClient} that in turn may work connect with or without a live server * for Spring MVC and WebFlux. */ - private static class WebTestClientRequestStrategy implements WebRequestStrategy { + private static class WebTestClientRequestStrategy + extends DefaultGraphQlTester.RequestStrategySupport implements WebRequestStrategy { private final WebTestClient client; - @Nullable - private final Predicate errorFilter; - - private final Configuration jsonPathConfig; - - public WebTestClientRequestStrategy( - WebTestClient client, @Nullable Predicate errorFilter, Configuration jsonPathConfig) { + public WebTestClientRequestStrategy(WebTestClient client, + @Nullable Predicate errorFilter, Configuration jsonPathConfig, Duration responseTimeout) { + super(errorFilter, jsonPathConfig, responseTimeout); this.client = client; - this.errorFilter = errorFilter; - this.jsonPathConfig = jsonPathConfig; } @Override - public WebResponseSpec execute(RequestInput requestInput) { + public WebResponseSpec execute(WebInput webInput) { EntityExchangeResult result = this.client.post() .contentType(MediaType.APPLICATION_JSON) - .headers(headers -> headers.putAll(getHeaders(requestInput))) - .bodyValue(requestInput.toMap()) + .headers(headers -> headers.putAll(webInput.getHeaders())) + .bodyValue(webInput.toMap()) .exchange() .expectStatus() .isOk() @@ -211,21 +208,19 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl byte[] bytes = result.getResponseBodyContent(); Assert.notNull(bytes, "Expected GraphQL response content"); String content = new String(bytes, StandardCharsets.UTF_8); - DocumentContext documentContext = JsonPath.parse(content, this.jsonPathConfig); - - ResponseSpec responseSpec = - new DefaultResponseSpec(documentContext, this.errorFilter, result::assertWithDiagnostics); + DocumentContext documentContext = JsonPath.parse(content, getJsonPathConfig()); + ResponseSpec responseSpec = createResponseSpec(documentContext, result::assertWithDiagnostics); return new DefaultWebResponseSpec(responseSpec, result.getResponseHeaders()); } @Override - public WebSubscriptionSpec executeSubscription(RequestInput requestInput) { + public WebSubscriptionSpec executeSubscription(WebInput webInput) { FluxExchangeResult exchangeResult = this.client.post() .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.TEXT_EVENT_STREAM) - .headers(headers -> headers.putAll(getHeaders(requestInput))) - .bodyValue(requestInput.toMap()) + .headers(headers -> headers.putAll(webInput.getHeaders())) + .bodyValue(webInput.toMap()) .exchange() .expectStatus() .isOk() @@ -233,25 +228,20 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl .contentType(MediaType.TEXT_EVENT_STREAM) .returnResult(TestExecutionResult.class); - SubscriptionSpec subscriptionSpec = new DefaultSubscriptionSpec( - exchangeResult.getResponseBody().cast(ExecutionResult.class), - this.errorFilter, this.jsonPathConfig, exchangeResult::assertWithDiagnostics); + Flux flux = exchangeResult.getResponseBody() + .map((result) -> createResponseSpec(result, exchangeResult::assertWithDiagnostics)); - return new DefaultWebSubscriptionSpec(subscriptionSpec, exchangeResult.getResponseHeaders()); + return new DefaultWebSubscriptionSpec(() -> flux, exchangeResult.getResponseHeaders()); } - - private HttpHeaders getHeaders(RequestInput requestInput) { - Assert.isInstanceOf(WebInput.class, requestInput); - return ((WebInput) requestInput).getHeaders(); - } - } + /** - * {@link RequestStrategy} that performs requests directly on + * {@link WebRequestStrategy} that performs requests directly on * {@link WebGraphQlHandler}, i.e. Web request testing without a transport. */ - private static class WebGraphQlHandlerRequestStrategy extends DirectRequestStrategySupport implements WebRequestStrategy { + private static class WebGraphQlHandlerRequestStrategy + extends DefaultGraphQlTester.DirectRequestStrategySupport implements WebRequestStrategy { private final WebGraphQlHandler graphQlHandler; @@ -263,53 +253,59 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl } @Override - public WebResponseSpec execute(RequestInput input) { + public WebResponseSpec execute(WebInput input) { WebOutput webOutput = executeInternal(input); ResponseSpec responseSpec = createResponseSpec(input, webOutput); return new DefaultWebResponseSpec(responseSpec, webOutput.getResponseHeaders()); } @Override - public WebSubscriptionSpec executeSubscription(RequestInput input) { + public WebSubscriptionSpec executeSubscription(WebInput input) { WebOutput webOutput = executeInternal(input); - SubscriptionSpec spec = createSubscriptionSpec(input, webOutput); - return new DefaultWebSubscriptionSpec(spec, webOutput.getResponseHeaders()); + SubscriptionSpec subscriptionSpec = createSubscriptionSpec(input, webOutput); + return new DefaultWebSubscriptionSpec(subscriptionSpec, webOutput.getResponseHeaders()); } - private WebOutput executeInternal(RequestInput input) { - Assert.isInstanceOf(WebInput.class, input); - WebInput webInput = (WebInput) input; - WebOutput webOutput = this.graphQlHandler.handle(webInput).block(responseTimeout()); + private WebOutput executeInternal(WebInput webInput) { + WebOutput webOutput = this.graphQlHandler.handle(webInput).block(getResponseTimeout()); Assert.notNull(webOutput, "Expected WebOutput"); return webOutput; } } - private static final class DefaultWebRequestSpec implements WebRequestSpec { + + private static final class DefaultWebRequestSpec + extends DefaultGraphQlTester.RequestSpecSupport implements WebRequestSpec { private static final URI DEFAULT_URL = URI.create(""); private final WebRequestStrategy requestStrategy; - private final String query; - - @Nullable - private String operationName; - - private final Map variables = new LinkedHashMap<>(); - private final HttpHeaders headers = new HttpHeaders(); - DefaultWebRequestSpec(WebRequestStrategy requestStrategy, String query, @Nullable HttpHeaders headers) { + DefaultWebRequestSpec( + WebRequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders, String query) { + + super(query); Assert.notNull(requestStrategy, "WebRequestStrategy is required"); - Assert.notNull(query, "`query` is required"); this.requestStrategy = requestStrategy; - this.query = query; - if (!CollectionUtils.isEmpty(headers)) { - this.headers.putAll(headers); + if (!CollectionUtils.isEmpty(defaultHeaders)) { + this.headers.putAll(defaultHeaders); } } + @Override + public WebRequestSpec operationName(@Nullable String name) { + setOperationName(name); + return this; + } + + @Override + public WebRequestSpec variable(String name, Object value) { + addVariable(name, value); + return this; + } + @Override public WebRequestSpec header(String headerName, String... headerValues) { for (String headerValue : headerValues) { @@ -324,55 +320,35 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl return this; } - @Override - public WebRequestSpec operationName(@Nullable String name) { - this.operationName = name; - return this; - } - - @Override - public WebRequestSpec variable(String name, Object value) { - this.variables.put(name, value); - return this; - } - @Override public WebResponseSpec execute() { - return this.requestStrategy.execute(createRequestInput()); + return this.requestStrategy.execute(createWebInput()); } @Override public void executeAndVerify() { - execute().path("$.errors").valueIsEmpty(); + verify(execute()); } @Override public WebSubscriptionSpec executeSubscription() { - return this.requestStrategy.executeSubscription(createRequestInput()); + return this.requestStrategy.executeSubscription(createWebInput()); } - private RequestInput createRequestInput() { - Map body = new LinkedHashMap<>(3); - body.put("query", this.query); - if (this.operationName != null) { - body.put("operationName", this.operationName); - } - if (!CollectionUtils.isEmpty(this.variables)) { - body.put("variables", new LinkedHashMap<>(this.variables)); - } - return new WebInput(DEFAULT_URL, this.headers, body, null); + private WebInput createWebInput() { + return new WebInput(DEFAULT_URL, this.headers, createRequestInput().toMap(), null); } } private static final class DefaultWebResponseSpec implements WebResponseSpec { - private final ResponseSpec delegate; + private final ResponseSpec responseSpec; private final HttpHeaders headers; - public DefaultWebResponseSpec(ResponseSpec delegate, @Nullable HttpHeaders headers) { - this.delegate = delegate; + public DefaultWebResponseSpec(ResponseSpec responseSpec, @Nullable HttpHeaders headers) { + this.responseSpec = responseSpec; this.headers = (headers != null ? headers : new HttpHeaders()); } @@ -384,12 +360,12 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl @Override public PathSpec path(String path) { - return this.delegate.path(path); + return this.responseSpec.path(path); } @Override public ErrorSpec errors() { - return this.delegate.errors(); + return this.responseSpec.errors(); } }