WebGraphQlTester exposes HTTP response headers
Closes gh-74
This commit is contained in:
@@ -155,7 +155,7 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
* without an underlying transport and where {@link RequestInput} provides
|
||||
* sufficient input.
|
||||
*/
|
||||
protected abstract static class AbstractDirectRequestStrategy implements RequestStrategy {
|
||||
protected static class DirectRequestStrategySupport {
|
||||
|
||||
@Nullable
|
||||
private final Predicate<GraphQLError> errorFilter;
|
||||
@@ -164,7 +164,7 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final Duration responseTimeout;
|
||||
|
||||
protected AbstractDirectRequestStrategy(
|
||||
protected DirectRequestStrategySupport(
|
||||
@Nullable Predicate<GraphQLError> errorFilter, Configuration jsonPathConfig, Duration timeout) {
|
||||
|
||||
this.errorFilter = errorFilter;
|
||||
@@ -176,32 +176,24 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
return this.responseTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec execute(RequestInput input) {
|
||||
ExecutionResult result = executeInternal(input);
|
||||
protected ResponseSpec createResponseSpec(RequestInput input, ExecutionResult result) {
|
||||
DocumentContext context = JsonPath.parse(result.toSpecification(), this.jsonPathConfig);
|
||||
return new DefaultResponseSpec(context, this.errorFilter, assertDecorator(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription(RequestInput input) {
|
||||
ExecutionResult result = executeInternal(input);
|
||||
AssertionErrors.assertTrue("Subscription did not return Publisher", result.getData() instanceof Publisher);
|
||||
protected SubscriptionSpec createSubscriptionSpec(RequestInput input, ExecutionResult result) {
|
||||
AssertionErrors.assertTrue(
|
||||
"Subscription did not return Publisher", result.getData() instanceof Publisher);
|
||||
|
||||
List<GraphQLError> errors = result.getErrors();
|
||||
Consumer<Runnable> assertDecorator = assertDecorator(input);
|
||||
List<GraphQLError> errors = result.getErrors();
|
||||
assertDecorator.accept(() -> AssertionErrors.assertTrue(
|
||||
"Response has " + errors.size() + " unexpected error(s).", CollectionUtils.isEmpty(errors)));
|
||||
|
||||
return new DefaultSubscriptionSpec(
|
||||
result.getData(), this.errorFilter, this.jsonPathConfig, assertDecorator);
|
||||
Publisher<ExecutionResult> publisher = result.getData();
|
||||
return new DefaultSubscriptionSpec(publisher, this.errorFilter, this.jsonPathConfig, assertDecorator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-classes implement this to actual perform the request.
|
||||
*/
|
||||
protected abstract ExecutionResult executeInternal(RequestInput input);
|
||||
|
||||
private Consumer<Runnable> assertDecorator(RequestInput input) {
|
||||
return (assertion) -> {
|
||||
try {
|
||||
@@ -218,7 +210,8 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
/**
|
||||
* {@link RequestStrategy} that performs requests through a {@link GraphQlService}.
|
||||
*/
|
||||
protected static class GraphQlServiceRequestStrategy extends AbstractDirectRequestStrategy {
|
||||
protected static class GraphQlServiceRequestStrategy
|
||||
extends DirectRequestStrategySupport implements RequestStrategy {
|
||||
|
||||
private final GraphQlService graphQlService;
|
||||
|
||||
@@ -230,7 +223,17 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.graphQlService = service;
|
||||
}
|
||||
|
||||
protected ExecutionResult executeInternal(RequestInput input) {
|
||||
@Override
|
||||
public ResponseSpec execute(RequestInput input) {
|
||||
return createResponseSpec(input, executeInternal(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription(RequestInput input) {
|
||||
return createSubscriptionSpec(input, executeInternal(input));
|
||||
}
|
||||
|
||||
private ExecutionResult executeInternal(RequestInput input) {
|
||||
ExecutionResult result = this.graphQlService.execute(input).block(responseTimeout());
|
||||
Assert.notNull(result, "Expected ExecutionResult");
|
||||
return result;
|
||||
|
||||
@@ -30,10 +30,12 @@ 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;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -54,7 +56,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
private final HttpHeaders defaultHeaders;
|
||||
|
||||
|
||||
DefaultWebGraphQlTester(RequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders) {
|
||||
DefaultWebGraphQlTester(WebRequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders) {
|
||||
super(requestStrategy);
|
||||
this.defaultHeaders = defaultHeaders;
|
||||
}
|
||||
@@ -62,7 +64,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
|
||||
@Override
|
||||
public WebRequestSpec query(String query) {
|
||||
return new DefaultWebRequestSpec(getRequestStrategy(), query, this.defaultHeaders);
|
||||
return new DefaultWebRequestSpec((WebRequestStrategy) getRequestStrategy(), query, this.defaultHeaders);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +82,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
@Nullable
|
||||
private Duration responseTimeout;
|
||||
|
||||
private final Supplier<RequestStrategy> requestStrategySupplier;
|
||||
private final Supplier<WebRequestStrategy> requestStrategySupplier;
|
||||
|
||||
@Nullable
|
||||
private HttpHeaders headers;
|
||||
@@ -145,12 +147,37 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension of {@code RequestStrategy} for performing a GraphQL request
|
||||
* in a web environment.
|
||||
*/
|
||||
interface WebRequestStrategy extends RequestStrategy {
|
||||
|
||||
/**
|
||||
* Perform a request with the given {@link RequestInput} container.
|
||||
* @param input the request input
|
||||
* @return the response spec
|
||||
*/
|
||||
WebResponseSpec execute(RequestInput input);
|
||||
|
||||
/**
|
||||
* Perform a subscription with the given {@link RequestInput} container.
|
||||
* @param input the request input
|
||||
* @return the subscription spec
|
||||
*/
|
||||
WebSubscriptionSpec executeSubscription(RequestInput input);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@link RequestStrategy} 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 RequestStrategy {
|
||||
private static class WebTestClientRequestStrategy implements WebRequestStrategy {
|
||||
|
||||
private final WebTestClient client;
|
||||
|
||||
@@ -168,7 +195,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec execute(RequestInput requestInput) {
|
||||
public WebResponseSpec execute(RequestInput requestInput) {
|
||||
EntityExchangeResult<byte[]> result = this.client.post()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.headers(headers -> headers.putAll(getHeaders(requestInput)))
|
||||
@@ -186,11 +213,14 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
String content = new String(bytes, StandardCharsets.UTF_8);
|
||||
DocumentContext documentContext = JsonPath.parse(content, this.jsonPathConfig);
|
||||
|
||||
return new DefaultResponseSpec(documentContext, this.errorFilter, result::assertWithDiagnostics);
|
||||
ResponseSpec responseSpec =
|
||||
new DefaultResponseSpec(documentContext, this.errorFilter, result::assertWithDiagnostics);
|
||||
|
||||
return new DefaultWebResponseSpec(responseSpec, result.getResponseHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription(RequestInput requestInput) {
|
||||
public WebSubscriptionSpec executeSubscription(RequestInput requestInput) {
|
||||
FluxExchangeResult<TestExecutionResult> exchangeResult = this.client.post()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
@@ -203,9 +233,11 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
.contentType(MediaType.TEXT_EVENT_STREAM)
|
||||
.returnResult(TestExecutionResult.class);
|
||||
|
||||
return new DefaultSubscriptionSpec(
|
||||
SubscriptionSpec subscriptionSpec = new DefaultSubscriptionSpec(
|
||||
exchangeResult.getResponseBody().cast(ExecutionResult.class),
|
||||
this.errorFilter, this.jsonPathConfig, exchangeResult::assertWithDiagnostics);
|
||||
|
||||
return new DefaultWebSubscriptionSpec(subscriptionSpec, exchangeResult.getResponseHeaders());
|
||||
}
|
||||
|
||||
private HttpHeaders getHeaders(RequestInput requestInput) {
|
||||
@@ -219,7 +251,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
* {@link RequestStrategy} that performs requests directly on
|
||||
* {@link WebGraphQlHandler}, i.e. Web request testing without a transport.
|
||||
*/
|
||||
private static class WebGraphQlHandlerRequestStrategy extends AbstractDirectRequestStrategy {
|
||||
private static class WebGraphQlHandlerRequestStrategy extends DirectRequestStrategySupport implements WebRequestStrategy {
|
||||
|
||||
private final WebGraphQlHandler graphQlHandler;
|
||||
|
||||
@@ -230,21 +262,34 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
this.graphQlHandler = handler;
|
||||
}
|
||||
|
||||
protected ExecutionResult executeInternal(RequestInput input) {
|
||||
Assert.isInstanceOf(WebInput.class, input);
|
||||
WebInput webInput = (WebInput) input;
|
||||
ExecutionResult result = this.graphQlHandler.handle(webInput).block(responseTimeout());
|
||||
Assert.notNull(result, "Expected ExecutionResult");
|
||||
return result;
|
||||
@Override
|
||||
public WebResponseSpec execute(RequestInput input) {
|
||||
WebOutput webOutput = executeInternal(input);
|
||||
ResponseSpec responseSpec = createResponseSpec(input, webOutput);
|
||||
return new DefaultWebResponseSpec(responseSpec, webOutput.getResponseHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSubscriptionSpec executeSubscription(RequestInput input) {
|
||||
WebOutput webOutput = executeInternal(input);
|
||||
SubscriptionSpec spec = createSubscriptionSpec(input, webOutput);
|
||||
return new DefaultWebSubscriptionSpec(spec, webOutput.getResponseHeaders());
|
||||
}
|
||||
|
||||
private WebOutput executeInternal(RequestInput input) {
|
||||
Assert.isInstanceOf(WebInput.class, input);
|
||||
WebInput webInput = (WebInput) input;
|
||||
WebOutput webOutput = this.graphQlHandler.handle(webInput).block(responseTimeout());
|
||||
Assert.notNull(webOutput, "Expected WebOutput");
|
||||
return webOutput;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DefaultWebRequestSpec implements WebRequestSpec {
|
||||
|
||||
private static final URI DEFAULT_URL = URI.create("");
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
private final WebRequestStrategy requestStrategy;
|
||||
|
||||
private final String query;
|
||||
|
||||
@@ -255,8 +300,8 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
DefaultWebRequestSpec(RequestStrategy requestStrategy, String query, @Nullable HttpHeaders headers) {
|
||||
Assert.notNull(requestStrategy, "RequestStrategy is required");
|
||||
DefaultWebRequestSpec(WebRequestStrategy requestStrategy, String query, @Nullable HttpHeaders headers) {
|
||||
Assert.notNull(requestStrategy, "WebRequestStrategy is required");
|
||||
Assert.notNull(query, "`query` is required");
|
||||
this.requestStrategy = requestStrategy;
|
||||
this.query = query;
|
||||
@@ -292,7 +337,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec execute() {
|
||||
public WebResponseSpec execute() {
|
||||
return this.requestStrategy.execute(createRequestInput());
|
||||
}
|
||||
|
||||
@@ -302,7 +347,7 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription() {
|
||||
public WebSubscriptionSpec executeSubscription() {
|
||||
return this.requestStrategy.executeSubscription(createRequestInput());
|
||||
}
|
||||
|
||||
@@ -319,4 +364,62 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class DefaultWebResponseSpec implements WebResponseSpec {
|
||||
|
||||
private final ResponseSpec delegate;
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
public DefaultWebResponseSpec(ResponseSpec delegate, @Nullable HttpHeaders headers) {
|
||||
this.delegate = delegate;
|
||||
this.headers = (headers != null ? headers : new HttpHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec httpHeadersSatisfy(Consumer<HttpHeaders> consumer) {
|
||||
consumer.accept(this.headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec path(String path) {
|
||||
return this.delegate.path(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorSpec errors() {
|
||||
return this.delegate.errors();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class DefaultWebSubscriptionSpec implements WebSubscriptionSpec {
|
||||
|
||||
private final SubscriptionSpec delegate;
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
public DefaultWebSubscriptionSpec(SubscriptionSpec delegate, @Nullable HttpHeaders headers) {
|
||||
this.delegate = delegate;
|
||||
this.headers = (headers != null ? headers : new HttpHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec httpHeadersSatisfy(Consumer<HttpHeaders> consumer) {
|
||||
consumer.accept(this.headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Flux<T> toFlux(String path, Class<T> entityType) {
|
||||
return this.delegate.toFlux(path, entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ResponseSpec> toFlux() {
|
||||
return this.delegate.toFlux();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -155,9 +155,9 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
*/
|
||||
@Override
|
||||
WebGraphQlTester build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extends {@link GraphQlTester.RequestSpec} with further input options
|
||||
* applicable to Web requests.
|
||||
@@ -183,6 +183,55 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
*/
|
||||
WebRequestSpec headers(Consumer<HttpHeaders> headersConsumer);
|
||||
|
||||
|
||||
/**
|
||||
* Execute the GraphQL request and return a spec for further inspection of
|
||||
* response data and errors.
|
||||
* @return options for asserting the response
|
||||
* @throws AssertionError if the request is performed over HTTP and the response
|
||||
* status is not 200 (OK).
|
||||
*/
|
||||
WebResponseSpec execute();
|
||||
|
||||
/**
|
||||
* Execute the GraphQL request as a subscription and return a spec with options to
|
||||
* transform the result stream.
|
||||
* @return spec with options to transform the subscription result stream
|
||||
* @throws AssertionError if the request is performed over HTTP and the response
|
||||
* status is not 200 (OK).
|
||||
*/
|
||||
WebSubscriptionSpec executeSubscription();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension of {@code ResponseSpec} with access to HTTP response headers.
|
||||
*/
|
||||
interface WebResponseSpec extends ResponseSpec {
|
||||
|
||||
/**
|
||||
* Perform any necessary assertions on the HTTP response headers.
|
||||
* @param consumer the consumer to check the headers
|
||||
* @return options for asserting the response
|
||||
*/
|
||||
ResponseSpec httpHeadersSatisfy(Consumer<HttpHeaders> consumer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension of {@code SubscriptionSpec} with access to HTTP response headers.
|
||||
*/
|
||||
interface WebSubscriptionSpec extends SubscriptionSpec {
|
||||
|
||||
/**
|
||||
* Perform any necessary assertions on the HTTP response headers.
|
||||
* @param consumer the consumer to check the headers
|
||||
* @return options for asserting the response
|
||||
*/
|
||||
SubscriptionSpec httpHeadersSatisfy(Consumer<HttpHeaders> consumer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import graphql.GraphqlErrorBuilder;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
Reference in New Issue
Block a user