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:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user