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.
This commit is contained in:
Rossen Stoyanchev
2021-10-01 10:52:42 +01:00
parent 65b5ff9a7c
commit e644e5e192
2 changed files with 284 additions and 265 deletions

View File

@@ -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<DefaultBuilder> {
private final GraphQlService service;
static class BuilderSupport {
@Nullable
private Predicate<GraphQLError> errorFilter;
@@ -90,8 +85,49 @@ class DefaultGraphQlTester implements GraphQlTester {
@Nullable
private Configuration jsonPathConfig;
@Nullable
private Duration responseTimeout = Duration.ofSeconds(5);
protected void addErrorFilter(Predicate<GraphQLError> predicate) {
this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate);
}
@Nullable
protected Predicate<GraphQLError> 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<DefaultBuilder> {
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<GraphQLError> 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<GraphQLError> errorFilter;
@@ -164,7 +196,7 @@ class DefaultGraphQlTester implements GraphQlTester {
private final Duration responseTimeout;
protected DirectRequestStrategySupport(
protected RequestStrategySupport(
@Nullable Predicate<GraphQLError> 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<Runnable> 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<Runnable> 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<GraphQLError> 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<Runnable> assertDecorator = assertDecorator(input);
List<GraphQLError> errors = result.getErrors();
assertDecorator.accept(() -> AssertionErrors.assertTrue(
"Response has " + errors.size() + " unexpected error(s).", CollectionUtils.isEmpty(errors)));
Publisher<ExecutionResult> 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<? extends ExecutionResult> publisher = result.getData();
return Flux.from(publisher).map((current) -> createResponseSpec(current, assertDecorator));
};
}
private Consumer<Runnable> 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<DefaultRequestSpec> {
private final RequestStrategy requestStrategy;
static class RequestSpecSupport {
private final String query;
@@ -255,22 +317,51 @@ class DefaultGraphQlTester implements GraphQlTester {
private final Map<String, Object> 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<DefaultRequestSpec> {
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<List<TestGraphQlError>> ERROR_LIST_TYPE = new TypeRef<List<TestGraphQlError>>() {};
private static final JsonPath ERRORS_PATH = JsonPath.compile("$.errors");
private static final Predicate<GraphQLError> MATCH_ALL_PREDICATE = (error) -> true;
private final DocumentContext documentContext;
private final String jsonContent;
private final List<TestGraphQlError> errors;
private final Consumer<Runnable> assertDecorator;
ErrorsContainer(
List<TestGraphQlError> errors, @Nullable Predicate<GraphQLError> errorFilter,
ResponseContainer(
DocumentContext documentContext, @Nullable Predicate<GraphQLError> errorFilter,
Consumer<Runnable> 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<TestGraphQlError> 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> T read(JsonPath jsonPath, TypeRef<T> 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<List<TestGraphQlError>> ERROR_LIST_TYPE = new TypeRef<List<TestGraphQlError>>() {};
private static final JsonPath ERRORS_PATH = JsonPath.compile("$.errors");
private final DocumentContext documentContext;
private final String jsonContent;
ResponseContainer(
DocumentContext documentContext, @Nullable Predicate<GraphQLError> errorFilter,
Consumer<Runnable> assertDecorator) {
super(readErrors(documentContext), errorFilter, assertDecorator);
this.documentContext = documentContext;
this.jsonContent = this.documentContext.jsonString();
}
private static List<TestGraphQlError> 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> T read(JsonPath jsonPath, TypeRef<T> 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<ExecutionResult> publisher;
@Nullable
private final Predicate<GraphQLError> errorFilter;
private final Configuration jsonPathConfig;
private final Consumer<Runnable> assertDecorator;
protected <T> DefaultSubscriptionSpec(
Publisher<ExecutionResult> publisher, @Nullable Predicate<GraphQLError> errorFilter,
Configuration jsonPathConfig, Consumer<Runnable> decorator) {
this.publisher = publisher;
this.errorFilter = errorFilter;
this.jsonPathConfig = jsonPathConfig;
this.assertDecorator = decorator;
}
@Override
public Flux<ResponseSpec> 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;

View File

@@ -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<GraphQLError> errorFilter;
private final WebTestClient client;
@Nullable
private Configuration jsonPathConfig;
@Nullable
private Duration responseTimeout;
private final Supplier<WebRequestStrategy> 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<GraphQLError> 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<GraphQLError> errorFilter;
private final Configuration jsonPathConfig;
public WebTestClientRequestStrategy(
WebTestClient client, @Nullable Predicate<GraphQLError> errorFilter, Configuration jsonPathConfig) {
public WebTestClientRequestStrategy(WebTestClient client,
@Nullable Predicate<GraphQLError> 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<byte[]> 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<TestExecutionResult> 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<ResponseSpec> 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<String, Object> 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<String, Object> 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();
}
}