Introduce transport specific GraphQlTester extensions
See gh-317
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for extensions of {@link GraphQlTester} that mainly assist with
|
||||
* building the underlying transport, but otherwise delegate to the default
|
||||
* {@link GraphQlTester} implementation for actual request execution.
|
||||
*
|
||||
* <p>Subclasses must implement {@link GraphQlTester#mutate()} to allow mutation
|
||||
* of both {@code GraphQlTester} and {@code GraphQlTransport} configuration.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see AbstractGraphQlTesterBuilder
|
||||
*/
|
||||
public abstract class AbstractDelegatingGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final GraphQlTester delegate;
|
||||
|
||||
|
||||
protected AbstractDelegatingGraphQlTester(GraphQlTester delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequestSpec<?> document(String document) {
|
||||
return this.delegate.document(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpec<?> documentName(String documentName) {
|
||||
return this.delegate.documentName(documentName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
|
||||
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.CachingDocumentSource;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Abstract, base class for transport specific {@link GraphQlTester.Builder}
|
||||
* implementations.
|
||||
*
|
||||
* <p>Subclasses must implement {@link #build()} and call
|
||||
* {@link #buildGraphQlTester(GraphQlTransport)} to obtain a default, transport
|
||||
* agnostic {@code GraphQlTester}. A transport specific extension can then wrap
|
||||
* this default tester by extending {@link AbstractDelegatingGraphQlTester}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see AbstractDelegatingGraphQlTester
|
||||
*/
|
||||
public abstract class AbstractGraphQlTesterBuilder<B extends AbstractGraphQlTesterBuilder<B>> implements GraphQlTester.Builder<B> {
|
||||
|
||||
private static final boolean jackson2Present;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = AbstractGraphQlTesterBuilder.class.getClassLoader();
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
}
|
||||
|
||||
private static final Duration DEFAULT_RESPONSE_DURATION = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
@Nullable
|
||||
private Predicate<GraphQLError> errorFilter;
|
||||
|
||||
private DocumentSource documentSource = new CachingDocumentSource(new ResourceDocumentSource());
|
||||
|
||||
private Duration responseTimeout = DEFAULT_RESPONSE_DURATION;
|
||||
|
||||
|
||||
@Override
|
||||
public B errorFilter(Predicate<GraphQLError> predicate) {
|
||||
this.errorFilter = (this.errorFilter != null ? errorFilter.and(predicate) : predicate);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B documentSource(DocumentSource documentSource) {
|
||||
this.documentSource = documentSource;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B responseTimeout(Duration timeout) {
|
||||
Assert.notNull(timeout, "'timeout' is required");
|
||||
this.responseTimeout = timeout;
|
||||
return self();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends B> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses call this from {@link #build()} to provide the transport and get
|
||||
* the default {@code GraphQlTester} to delegate to for request execution.
|
||||
*/
|
||||
protected GraphQlTester buildGraphQlTester(GraphQlTransport transport) {
|
||||
Assert.notNull(transport, "GraphQlTransport is required");
|
||||
return new DefaultGraphQlTester(
|
||||
transport, this.errorFilter, initJsonPathConfig(), this.documentSource, this.responseTimeout,
|
||||
getBuilderInitializer());
|
||||
}
|
||||
|
||||
private Configuration initJsonPathConfig() {
|
||||
// Allow configuring JSONPath with codecs from transport subclasses
|
||||
return (jackson2Present ? Jackson2Configuration.create() : Configuration.builder().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses call this from {@link #build()} to obtain a {@code Consumer} to
|
||||
* initialize new builder instances with, based on "this" builder.
|
||||
*/
|
||||
protected Consumer<GraphQlTester.Builder<?>> getBuilderInitializer() {
|
||||
return builder -> {
|
||||
if (this.errorFilter != null) {
|
||||
builder.errorFilter(this.errorFilter);
|
||||
}
|
||||
builder.documentSource(this.documentSource);
|
||||
builder.responseTimeout(this.responseTimeout);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static class Jackson2Configuration {
|
||||
|
||||
static Configuration create() {
|
||||
return Configuration.builder()
|
||||
.jsonProvider(new JacksonJsonProvider())
|
||||
.mappingProvider(new JacksonMappingProvider())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Default {@link GraphQlServiceTester} that uses a {@link GraphQlService} for
|
||||
* request execution.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultGraphQlServiceTester extends AbstractDelegatingGraphQlTester implements GraphQlServiceTester {
|
||||
|
||||
private final GraphQlServiceTransport transport;
|
||||
|
||||
private final Consumer<GraphQlTester.Builder<?>> builderInitializer;
|
||||
|
||||
|
||||
DefaultGraphQlServiceTester(GraphQlTester tester, GraphQlServiceTransport transport,
|
||||
Consumer<GraphQlTester.Builder<?>> builderInitializer) {
|
||||
|
||||
super(tester);
|
||||
|
||||
Assert.notNull(transport, "GraphQlServiceTransport is required");
|
||||
Assert.notNull(builderInitializer, "`builderInitializer` is required");
|
||||
|
||||
this.transport = transport;
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Builder<?> mutate() {
|
||||
Builder<?> builder = new Builder<>(this.transport);
|
||||
this.builderInitializer.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default {@link GraphQlServiceTester.Builder} implementation.
|
||||
*/
|
||||
static class Builder<B extends Builder<B>> extends AbstractGraphQlTesterBuilder<B>
|
||||
implements GraphQlServiceTester.Builder<B> {
|
||||
|
||||
private final GraphQlService service;
|
||||
|
||||
Builder(GraphQlService service) {
|
||||
Assert.notNull(service, "GraphQlService is required");
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
Builder(GraphQlServiceTransport transport) {
|
||||
this.service = transport.getGraphQlService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlServiceTester build() {
|
||||
GraphQlServiceTransport transport = new GraphQlServiceTransport(this.service);
|
||||
GraphQlTester tester = super.buildGraphQlTester(transport);
|
||||
return new DefaultGraphQlServiceTester(tester, transport, getBuilderInitializer());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,23 +16,29 @@
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.PathNotFoundException;
|
||||
import com.jayway.jsonpath.TypeRef;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.AssertionErrors;
|
||||
import org.springframework.test.util.JsonExpectationsHelper;
|
||||
@@ -42,98 +48,158 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link GraphQlTester}.
|
||||
* Default {@link GraphQlTester} implementation with the logic to initialize
|
||||
* requests and handle responses. It is transport agnostic and depends on a
|
||||
* {@link GraphQlTransport} to execute requests with.
|
||||
*
|
||||
* <p>This class is final but works with any transport.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
class DefaultGraphQlTester implements GraphQlTester {
|
||||
final class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
private final GraphQlTransport transport;
|
||||
|
||||
private final Function<String, String> queryNameResolver;
|
||||
@Nullable
|
||||
private final Predicate<GraphQLError> errorFilter;
|
||||
|
||||
private final Configuration jsonPathConfig;
|
||||
|
||||
private final DocumentSource documentSource;
|
||||
|
||||
private final Duration responseTimeout;
|
||||
|
||||
private final Consumer<GraphQlTester.Builder<?>> builderInitializer;
|
||||
|
||||
|
||||
DefaultGraphQlTester(RequestStrategy requestStrategy, Function<String, String> queryNameResolver) {
|
||||
Assert.notNull(requestStrategy, "RequestStrategy is required.");
|
||||
Assert.notNull(queryNameResolver, "'queryNameResolver' is required.");
|
||||
this.requestStrategy = requestStrategy;
|
||||
this.queryNameResolver = queryNameResolver;
|
||||
/**
|
||||
* Package private constructor for use from {@link AbstractGraphQlTesterBuilder}.
|
||||
*/
|
||||
DefaultGraphQlTester(
|
||||
GraphQlTransport transport, @Nullable Predicate<GraphQLError> errorFilter,
|
||||
Configuration jsonPathConfig, DocumentSource documentSource, Duration timeout,
|
||||
Consumer<GraphQlTester.Builder<?>> builderInitializer) {
|
||||
|
||||
this.transport = transport;
|
||||
this.errorFilter = errorFilter;
|
||||
this.jsonPathConfig = jsonPathConfig;
|
||||
this.documentSource = documentSource;
|
||||
this.responseTimeout = timeout;
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RequestSpec<?> query(String query) {
|
||||
return new DefaultRequestSpec(this.requestStrategy, query);
|
||||
public RequestSpec<?> document(String document) {
|
||||
return new DefaultRequestSpec(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpec<?> queryName(String queryName) {
|
||||
return query(this.queryNameResolver.apply(queryName));
|
||||
public RequestSpec<?> documentName(String documentName) {
|
||||
String document = this.documentSource.getDocument(documentName).block(this.responseTimeout);
|
||||
Assert.notNull(document, "Expected document content or an error");
|
||||
return document(document);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder mutate() {
|
||||
Builder builder = new Builder(this.transport);
|
||||
this.builderInitializer.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory for {@link GraphQlTester.ResponseSpec}, for use from
|
||||
* {@link RequestStrategy} implementations.
|
||||
*
|
||||
* @param documentContext the parsed response content
|
||||
* @param errorFilter a globally defined filter for expected errors (to be ignored)
|
||||
* @param assertDecorator decorator to apply around assertions, e.g. to add extra
|
||||
* Default {@link GraphQlTester.Builder} with a given transport.
|
||||
*/
|
||||
static GraphQlTester.ResponseSpec createResponseSpec(
|
||||
DocumentContext documentContext, @Nullable Predicate<GraphQLError> errorFilter,
|
||||
Consumer<Runnable> assertDecorator) {
|
||||
static final class Builder extends AbstractGraphQlTesterBuilder<Builder> {
|
||||
|
||||
private final GraphQlTransport transport;
|
||||
|
||||
Builder(GraphQlTransport transport) {
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlTester build() {
|
||||
return super.buildGraphQlTester(this.transport);
|
||||
}
|
||||
|
||||
return new DefaultResponseSpec(documentContext, errorFilter, assertDecorator);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RequestSpec} that collects the query, operationName, and variables.
|
||||
* {@link RequestSpec} that gathers the document, operationName, and variables.
|
||||
*/
|
||||
private static final class DefaultRequestSpec
|
||||
extends GraphQlTesterRequestSpecSupport implements RequestSpec<DefaultRequestSpec> {
|
||||
private final class DefaultRequestSpec implements RequestSpec<DefaultRequestSpec> {
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
private final String document;
|
||||
|
||||
private DefaultRequestSpec(RequestStrategy requestStrategy, String query) {
|
||||
super(query);
|
||||
Assert.notNull(requestStrategy, "RequestStrategy is required");
|
||||
this.requestStrategy = requestStrategy;
|
||||
@Nullable
|
||||
private String operationName;
|
||||
|
||||
private final Map<String, Object> variables = new LinkedHashMap<>();
|
||||
|
||||
private DefaultRequestSpec(String document) {
|
||||
Assert.notNull(document, "`document` is required");
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultRequestSpec operationName(@Nullable String name) {
|
||||
setOperationName(name);
|
||||
this.operationName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultRequestSpec variable(String name, @Nullable Object value) {
|
||||
addVariable(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultRequestSpec locale(Locale locale) {
|
||||
setLocale(locale);
|
||||
this.variables.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public ResponseSpec execute() {
|
||||
return this.requestStrategy.execute(createRequestInput());
|
||||
GraphQlRequest request = createRequest();
|
||||
return transport.execute(request)
|
||||
.map(result -> createResponseSpec(result, assertDecorator(request)))
|
||||
.block(responseTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAndVerify() {
|
||||
verify(execute());
|
||||
execute().path("$.errors").valueIsEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription() {
|
||||
return this.requestStrategy.executeSubscription(createRequestInput());
|
||||
GraphQlRequest request = createRequest();
|
||||
return () -> transport.executeSubscription(request)
|
||||
.map(result -> createResponseSpec(result, assertDecorator(request)));
|
||||
}
|
||||
|
||||
private GraphQlRequest createRequest() {
|
||||
return new GraphQlRequest(this.document, this.operationName, this.variables);
|
||||
}
|
||||
|
||||
private GraphQlTester.ResponseSpec createResponseSpec(
|
||||
ExecutionResult result, Consumer<Runnable> assertDecorator) {
|
||||
|
||||
DocumentContext jsonDocument = JsonPath.parse(result.toSpecification(), jsonPathConfig);
|
||||
return new DefaultResponseSpec(jsonDocument, errorFilter, assertDecorator);
|
||||
}
|
||||
|
||||
private Consumer<Runnable> assertDecorator(GraphQlRequest request) {
|
||||
return (assertion) -> {
|
||||
try {
|
||||
assertion.run();
|
||||
}
|
||||
catch (AssertionError ex) {
|
||||
throw new AssertionError(ex.getMessage() + "\nRequest: " + request, ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link GraphQlTester.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DefaultGraphQlTesterBuilder
|
||||
extends GraphQlTesterBuilderSupport implements GraphQlTester.Builder<DefaultGraphQlTesterBuilder> {
|
||||
|
||||
private final GraphQlService service;
|
||||
|
||||
|
||||
DefaultGraphQlTesterBuilder(GraphQlService service) {
|
||||
Assert.notNull(service, "GraphQlService is required.");
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DefaultGraphQlTesterBuilder errorFilter(Predicate<GraphQLError> predicate) {
|
||||
addErrorFilter(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultGraphQlTesterBuilder jsonPathConfig(Configuration config) {
|
||||
setJsonPathConfig(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultGraphQlTesterBuilder responseTimeout(Duration timeout) {
|
||||
setResponseTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlTester build() {
|
||||
RequestStrategy strategy = new GraphQlServiceRequestStrategy(
|
||||
this.service, getErrorFilter(), initJsonPathConfig(), initResponseTimeout());
|
||||
|
||||
return new DefaultGraphQlTester(strategy, getQueryNameResolver());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
import org.springframework.web.util.UriBuilderFactory;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Default {@link HttpGraphQlTester} that builds and uses a {@link WebTestClient}
|
||||
* for request execution.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultHttpGraphQlTester extends AbstractDelegatingGraphQlTester implements HttpGraphQlTester {
|
||||
|
||||
private final WebTestClient webTestClient;
|
||||
|
||||
private final Consumer<GraphQlTester.Builder<?>> builderInitializer;
|
||||
|
||||
|
||||
DefaultHttpGraphQlTester(GraphQlTester graphQlTester, WebTestClient webTestClient,
|
||||
Consumer<GraphQlTester.Builder<?>> builderInitializer) {
|
||||
|
||||
super(graphQlTester);
|
||||
this.webTestClient = webTestClient;
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Builder mutate() {
|
||||
Builder builder = new Builder(this.webTestClient.mutate());
|
||||
this.builderInitializer.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default {@link HttpGraphQlTester.Builder} implementation.
|
||||
*/
|
||||
static final class Builder extends AbstractGraphQlTesterBuilder<Builder>
|
||||
implements HttpGraphQlTester.Builder<Builder> {
|
||||
|
||||
private final WebTestClient.Builder webTestClientBuilder;
|
||||
|
||||
Builder(WebTestClient.Builder clientBuilder) {
|
||||
this.webTestClientBuilder = clientBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder url(String url) {
|
||||
this.webTestClientBuilder.baseUrl(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder url(URI url) {
|
||||
UriBuilderFactory factory = new DefaultUriBuilderFactory(UriComponentsBuilder.fromUri(url));
|
||||
this.webTestClientBuilder.uriBuilderFactory(factory);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder header(String name, String... values) {
|
||||
this.webTestClientBuilder.defaultHeader(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder headers(Consumer<HttpHeaders> headersConsumer) {
|
||||
this.webTestClientBuilder.defaultHeaders(headersConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder codecConfigurer(Consumer<CodecConfigurer> codecConsumer) {
|
||||
this.webTestClientBuilder.codecs(codecConsumer::accept);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder webTestClient(Consumer<WebTestClient.Builder> configurer) {
|
||||
configurer.accept(this.webTestClientBuilder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpGraphQlTester build() {
|
||||
WebTestClient client = this.webTestClientBuilder.build();
|
||||
GraphQlTester tester = super.buildGraphQlTester(new WebTestClientTransport(client));
|
||||
return new DefaultHttpGraphQlTester(tester, client, getBuilderInitializer());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,221 +16,108 @@
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.graphql.RequestInput;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link WebGraphQlTester}.
|
||||
* Default {@link WebGraphQlTester} that uses {@link WebGraphQlHandler} for
|
||||
* request execution.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class DefaultWebGraphQlTester implements WebGraphQlTester {
|
||||
final class DefaultWebGraphQlTester extends AbstractDelegatingGraphQlTester implements WebGraphQlTester {
|
||||
|
||||
private final WebRequestStrategy requestStrategy;
|
||||
private final WebGraphQlHandlerTransport transport;
|
||||
|
||||
@Nullable
|
||||
private final HttpHeaders defaultHeaders;
|
||||
|
||||
private final Function<String, String> queryNameResolver;
|
||||
private final Consumer<GraphQlTester.Builder<?>> builderInitializer;
|
||||
|
||||
|
||||
DefaultWebGraphQlTester(
|
||||
WebRequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders,
|
||||
Function<String, String> queryNameResolver) {
|
||||
DefaultWebGraphQlTester(GraphQlTester tester, WebGraphQlHandlerTransport transport,
|
||||
Consumer<GraphQlTester.Builder<?>> builderInitializer) {
|
||||
|
||||
Assert.notNull(requestStrategy, "WebRequestStrategy is required.");
|
||||
this.requestStrategy = requestStrategy;
|
||||
this.defaultHeaders = defaultHeaders;
|
||||
this.queryNameResolver = queryNameResolver;
|
||||
super(tester);
|
||||
this.transport = transport;
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebRequestSpec query(String query) {
|
||||
return new DefaultWebRequestSpec(this.requestStrategy, this.defaultHeaders, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec queryName(String queryName) {
|
||||
return query(this.queryNameResolver.apply(queryName));
|
||||
public Builder<?> mutate() {
|
||||
Builder<?> builder = new Builder<>(this.transport.getGraphQlHandler());
|
||||
builder.url(this.transport.getUrl());
|
||||
builder.headers(headers -> headers.putAll(this.transport.getHeaders()));
|
||||
this.builderInitializer.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory for {@link WebGraphQlTester.ResponseSpec}, for use from
|
||||
* {@link WebRequestStrategy} implementations.
|
||||
* Base builder implementation for all Web transport extensions.
|
||||
*/
|
||||
static WebResponseSpec createResponseSpec(
|
||||
ResponseSpec responseSpec, @Nullable HttpHeaders responseHeaders) {
|
||||
static class Builder<B extends Builder<B>> extends AbstractGraphQlTesterBuilder<B>
|
||||
implements WebGraphQlTester.Builder<B> {
|
||||
|
||||
return new DefaultWebResponseSpec(responseSpec, responseHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for {@link WebGraphQlTester.SubscriptionSpec}, for use from
|
||||
* {@link WebRequestStrategy} implementations.
|
||||
*/
|
||||
static WebSubscriptionSpec createSubscriptionSpec(
|
||||
SubscriptionSpec subscriptionSpec, @Nullable HttpHeaders responseHeaders) {
|
||||
|
||||
return new DefaultWebSubscriptionSpec(subscriptionSpec, responseHeaders);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link WebRequestSpec} that also collects HTTP request headers, in
|
||||
* addition to the query, operationName, and variables.
|
||||
*/
|
||||
private static final class DefaultWebRequestSpec
|
||||
extends GraphQlTesterRequestSpecSupport implements WebRequestSpec {
|
||||
|
||||
private static final URI DEFAULT_URL = URI.create("");
|
||||
|
||||
private final WebRequestStrategy requestStrategy;
|
||||
private URI url = URI.create("");
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private DefaultWebRequestSpec(
|
||||
WebRequestStrategy requestStrategy, @Nullable HttpHeaders defaultHeaders, String query) {
|
||||
private final WebGraphQlHandler handler;
|
||||
|
||||
super(query);
|
||||
Assert.notNull(requestStrategy, "WebRequestStrategy is required");
|
||||
this.requestStrategy = requestStrategy;
|
||||
if (!CollectionUtils.isEmpty(defaultHeaders)) {
|
||||
this.headers.putAll(defaultHeaders);
|
||||
}
|
||||
Builder(WebGraphQlHandler handler) {
|
||||
Assert.notNull(handler, "WebGraphQlHandler is required");
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec operationName(@Nullable String name) {
|
||||
setOperationName(name);
|
||||
return this;
|
||||
public B url(String url) {
|
||||
return url(new DefaultUriBuilderFactory().uriString(url).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec variable(String name, @Nullable Object value) {
|
||||
addVariable(name, value);
|
||||
return this;
|
||||
public B url(URI url) {
|
||||
this.url = url;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec locale(Locale locale) {
|
||||
setLocale(locale);
|
||||
return this;
|
||||
public B header(String name, String... values) {
|
||||
this.headers.put(name, Arrays.asList(values));
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec httpHeader(String headerName, String... headerValues) {
|
||||
for (String headerValue : headerValues) {
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec httpHeaders(Consumer<HttpHeaders> headersConsumer) {
|
||||
public B headers(Consumer<HttpHeaders> headersConsumer) {
|
||||
headersConsumer.accept(this.headers);
|
||||
return this;
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResponseSpec execute() {
|
||||
return this.requestStrategy.execute(createWebInput());
|
||||
public B codecConfigurer(Consumer<CodecConfigurer> codecConsumer) {
|
||||
// Ignore, no serialization needs at this level
|
||||
return self();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends B> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAndVerify() {
|
||||
verify(execute());
|
||||
public WebGraphQlTester build() {
|
||||
WebGraphQlHandlerTransport transport = new WebGraphQlHandlerTransport(this.url, this.headers, this.handler);
|
||||
GraphQlTester tester = super.buildGraphQlTester(transport);
|
||||
return new DefaultWebGraphQlTester(tester, transport, getBuilderInitializer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSubscriptionSpec executeSubscription() {
|
||||
return this.requestStrategy.executeSubscription(createWebInput());
|
||||
}
|
||||
|
||||
private WebInput createWebInput() {
|
||||
RequestInput input = createRequestInput();
|
||||
return new WebInput(DEFAULT_URL, this.headers, input.toMap(), input.getLocale(),
|
||||
(input.getId() != null) ? input.getId() : ObjectUtils.getIdentityHexString(input));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link WebResponseSpec} that exposes response headers and delegates
|
||||
* all other methods to the given {@link GraphQlTester.ResponseSpec}.
|
||||
*/
|
||||
private static final class DefaultWebResponseSpec implements WebResponseSpec {
|
||||
|
||||
private final ResponseSpec responseSpec;
|
||||
|
||||
private final HttpHeaders responseHeaders;
|
||||
|
||||
public DefaultWebResponseSpec(ResponseSpec responseSpec, @Nullable HttpHeaders responseHeaders) {
|
||||
this.responseSpec = responseSpec;
|
||||
this.responseHeaders = (responseHeaders != null ? responseHeaders : new HttpHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseSpec httpHeadersSatisfy(Consumer<HttpHeaders> consumer) {
|
||||
consumer.accept(this.responseHeaders);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathSpec path(String path) {
|
||||
return this.responseSpec.path(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorSpec errors() {
|
||||
return this.responseSpec.errors();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link WebSubscriptionSpec} that exposes response headers and delegates
|
||||
* all other methods to the given {@link GraphQlTester.SubscriptionSpec}.
|
||||
*/
|
||||
private static final class DefaultWebSubscriptionSpec implements WebSubscriptionSpec {
|
||||
|
||||
private final SubscriptionSpec delegate;
|
||||
|
||||
private final HttpHeaders headers;
|
||||
|
||||
private 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link WebGraphQlTester.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultWebGraphQlTesterBuilder
|
||||
extends GraphQlTesterBuilderSupport implements WebGraphQlTester.Builder {
|
||||
|
||||
@Nullable
|
||||
private final WebTestClient client;
|
||||
|
||||
@Nullable
|
||||
private final WebGraphQlHandler handler;
|
||||
|
||||
@Nullable
|
||||
private HttpHeaders headers;
|
||||
|
||||
|
||||
DefaultWebGraphQlTesterBuilder(WebTestClient client) {
|
||||
Assert.notNull(client, "WebTestClient is required.");
|
||||
this.client = client;
|
||||
this.handler = null;
|
||||
}
|
||||
|
||||
DefaultWebGraphQlTesterBuilder(WebGraphQlHandler handler) {
|
||||
Assert.notNull(handler, "WebGraphQlHandler is required.");
|
||||
this.handler = handler;
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebGraphQlTester.Builder errorFilter(Predicate<GraphQLError> predicate) {
|
||||
addErrorFilter(predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultWebGraphQlTesterBuilder jsonPathConfig(Configuration config) {
|
||||
setJsonPathConfig(config);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultWebGraphQlTesterBuilder responseTimeout(Duration timeout) {
|
||||
setResponseTimeout(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultWebGraphQlTesterBuilder defaultHttpHeader(String headerName, String... headerValues) {
|
||||
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
|
||||
for (String headerValue : headerValues) {
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebGraphQlTester.Builder defaultHttpHeaders(Consumer<HttpHeaders> headersConsumer) {
|
||||
this.headers = (this.headers != null ? this.headers : new HttpHeaders());
|
||||
headersConsumer.accept(this.headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebGraphQlTester build() {
|
||||
return new DefaultWebGraphQlTester(initRequestStrategy(), this.headers, getQueryNameResolver());
|
||||
}
|
||||
|
||||
private WebRequestStrategy initRequestStrategy() {
|
||||
if (this.client != null) {
|
||||
WebTestClient clientToUse = this.client;
|
||||
if (getResponseTimeout() != null) {
|
||||
clientToUse = this.client.mutate().responseTimeout(getResponseTimeout()).build();
|
||||
}
|
||||
return new WebTestClientRequestStrategy(
|
||||
clientToUse, getErrorFilter(), initJsonPathConfig(), getResponseTimeout());
|
||||
}
|
||||
|
||||
if (this.handler != null) {
|
||||
return new WebGraphQlHandlerRequestStrategy(
|
||||
this.handler, getErrorFilter(), initJsonPathConfig(), initResponseTimeout());
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Neither client nor handler");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import graphql.ExecutionResult;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.GraphQlRequest;
|
||||
import org.springframework.graphql.client.GraphQlClient;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.client.WebSocketGraphQlClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
|
||||
/**
|
||||
* Default {@link WebSocketGraphQlTester} that builds and uses a
|
||||
* {@link WebSocketGraphQlClient} for request execution.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class DefaultWebSocketGraphQlTester extends AbstractDelegatingGraphQlTester implements WebSocketGraphQlTester {
|
||||
|
||||
private final WebSocketGraphQlClient webSocketGraphQlClient;
|
||||
|
||||
private final Consumer<GraphQlTester.Builder<?>> builderInitializer;
|
||||
|
||||
|
||||
DefaultWebSocketGraphQlTester(
|
||||
GraphQlTester graphQlTester, WebSocketGraphQlClient webSocketGraphQlClient,
|
||||
Consumer<GraphQlTester.Builder<?>> builderInitializer) {
|
||||
|
||||
super(graphQlTester);
|
||||
this.webSocketGraphQlClient = webSocketGraphQlClient;
|
||||
this.builderInitializer = builderInitializer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> start() {
|
||||
return this.webSocketGraphQlClient.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> stop() {
|
||||
return this.webSocketGraphQlClient.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder mutate() {
|
||||
Builder builder = new Builder(this.webSocketGraphQlClient);
|
||||
this.builderInitializer.accept(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default {@link WebSocketGraphQlTester.Builder} implementation.
|
||||
*/
|
||||
static final class Builder extends AbstractGraphQlTesterBuilder<Builder> implements WebSocketGraphQlTester.Builder<Builder> {
|
||||
|
||||
private final WebSocketGraphQlClient.Builder<?> graphQlClientBuilder;
|
||||
|
||||
/**
|
||||
* Constructor to start via {@link WebSocketGraphQlTester#builder(URI, WebSocketClient)}.
|
||||
*/
|
||||
Builder(URI url, WebSocketClient webSocketClient) {
|
||||
Assert.notNull(webSocketClient, "WebSocketClient is required");
|
||||
this.graphQlClientBuilder = WebSocketGraphQlClient.builder(url, webSocketClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to mutate.
|
||||
* @param client the underlying client with the current state
|
||||
*/
|
||||
Builder(WebSocketGraphQlClient client) {
|
||||
Assert.notNull(client, "WebSocketGraphQlClient is required");
|
||||
this.graphQlClientBuilder = client.mutate();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Builder url(String url) {
|
||||
this.graphQlClientBuilder.url(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder url(URI url) {
|
||||
this.graphQlClientBuilder.url(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder header(String name, String... values) {
|
||||
this.graphQlClientBuilder.header(name, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder headers(Consumer<HttpHeaders> headersConsumer) {
|
||||
this.graphQlClientBuilder.headers(headersConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder codecConfigurer(Consumer<CodecConfigurer> codecsConsumer) {
|
||||
this.graphQlClientBuilder.codecConfigurer(codecsConsumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketGraphQlTester build() {
|
||||
WebSocketGraphQlClient client = this.graphQlClientBuilder.build();
|
||||
GraphQlTester graphQlTester = super.buildGraphQlTester(asTransport(client));
|
||||
return new DefaultWebSocketGraphQlTester(graphQlTester, client, getBuilderInitializer());
|
||||
}
|
||||
|
||||
/**
|
||||
* GraphQlTransport implementations are private, but we can create the
|
||||
* GraphQlClient for it and adapt it.
|
||||
*/
|
||||
private static GraphQlTransport asTransport(GraphQlClient client) {
|
||||
return new GraphQlTransport() {
|
||||
|
||||
@Override
|
||||
public Mono<ExecutionResult> execute(GraphQlRequest request) {
|
||||
return client
|
||||
.document(request.getDocument())
|
||||
.operationName(request.getOperationName())
|
||||
.variables(request.getVariables())
|
||||
.execute()
|
||||
.map(GraphQlClient.ResponseSpec::andReturn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ExecutionResult> executeSubscription(GraphQlRequest request) {
|
||||
return client
|
||||
.document(request.getDocument())
|
||||
.operationName(request.getOperationName())
|
||||
.variables(request.getVariables())
|
||||
.executeSubscription().map(GraphQlClient.ResponseSpec::andReturn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
|
||||
/**
|
||||
* {@link GraphQlTester} that executes requests through a {@link GraphQlService}
|
||||
* Use it for server-side tests, without a client.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface GraphQlServiceTester extends GraphQlTester {
|
||||
|
||||
|
||||
@Override
|
||||
Builder<?> mutate();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link GraphQlServiceTester} instance.
|
||||
*/
|
||||
static GraphQlServiceTester create(GraphQlService service) {
|
||||
return builder(service).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder for {@link GraphQlServiceTester}.
|
||||
*/
|
||||
static GraphQlServiceTester.Builder<?> builder(GraphQlService service) {
|
||||
return new DefaultGraphQlServiceTester.Builder<>(service);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default {@link GraphQlServiceTester.Builder} implementation.
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends GraphQlTester.Builder<B> {
|
||||
|
||||
/**
|
||||
* Build a {@link GraphQlServiceTester} instance.
|
||||
*/
|
||||
@Override
|
||||
GraphQlServiceTester build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,26 +18,34 @@ package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import graphql.GraphQLError;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.graphql.client.GraphQlTransport;
|
||||
import org.springframework.graphql.support.DocumentSource;
|
||||
import org.springframework.graphql.support.ResourceDocumentSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Contract for testing GraphQL requests.
|
||||
* Define a workflow to test GraphQL requests that is independent of the
|
||||
* underlying transport.
|
||||
*
|
||||
* <p>The workflow declared to prepare, execute, and verify requests is not tied
|
||||
* to any specific underlying transport. Use {@link WebGraphQlTester} to test
|
||||
* GraphQL requests over a Web transport. This class can also be used to perform
|
||||
* calls directly on {@link graphql.GraphQL}, without a transport, via
|
||||
* {@link GraphQlService}.
|
||||
* <p>To test using a client that connects to a server, with or without a live
|
||||
* server, see {@code GraphQlTester} extensions:
|
||||
* <ul>
|
||||
* <li>{@link HttpGraphQlTester}
|
||||
* <li>{@link WebSocketGraphQlTester}
|
||||
* </ul>
|
||||
*
|
||||
* <p>To test on the server side, without a client, see the following:
|
||||
* <ul>
|
||||
* <li>{@link GraphQlServiceTester}
|
||||
* <li>{@link WebGraphQlTester}
|
||||
* </ul>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
@@ -45,49 +53,49 @@ import org.springframework.lang.Nullable;
|
||||
public interface GraphQlTester {
|
||||
|
||||
/**
|
||||
* Prepare to perform a GraphQL request with the given operation which may
|
||||
* be a query, mutation, or a subscription.
|
||||
* @param query the operation to be performed
|
||||
* Start defining a GraphQL request with the given document, which is the
|
||||
* textual representation of an operation (or operations) to perform,
|
||||
* including selection sets and fragments.
|
||||
* @param document the document for the request
|
||||
* @return spec for response assertions
|
||||
* @throws AssertionError if the response status is not 200 (OK)
|
||||
*/
|
||||
RequestSpec<?> query(String query);
|
||||
RequestSpec<?> document(String document);
|
||||
|
||||
/**
|
||||
* Refer to a query by name where the given name is to look for a file with
|
||||
* the same name and extension {@code ".graphql"} or {@code ".gql"} under
|
||||
* classpath location {@code "graphql/"}.
|
||||
* Variant of {@link #document(String)} that uses the given key to resolve
|
||||
* the GraphQL document from a file, or in another way with the help of the
|
||||
* {@link DocumentSource} that the client is configured with.
|
||||
* @return spec for response assertions
|
||||
* @throws IllegalArgumentException if the queryName cannot be resolved
|
||||
* @throws IllegalArgumentException if the documentName cannot be resolved
|
||||
* @throws AssertionError if the response status is not 200 (OK)
|
||||
*/
|
||||
RequestSpec<?> queryName(String queryName);
|
||||
RequestSpec<?> documentName(String documentName);
|
||||
|
||||
/**
|
||||
* Create a builder initialized from the configuration of "this" tester.
|
||||
* Use it to build a new, independently configured instance.
|
||||
*/
|
||||
Builder<?> mutate();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code GraphQlTester} that performs GraphQL requests through the
|
||||
* given {@link GraphQlService}.
|
||||
* @param service the service to execute requests with
|
||||
* @return the created {@code GraphQlTester}
|
||||
* Create a builder with a custom {@code GraphQlTransport}.
|
||||
* <p>For most cases, use a transport specific extension such as
|
||||
* {@link HttpGraphQlTester} or {@link WebSocketGraphQlTester}. This method
|
||||
* is for use with a custom {@code GraphQlTransport}.
|
||||
* @param transport the transport to execute requests with
|
||||
* @return the builder for further initialization
|
||||
*/
|
||||
static GraphQlTester create(GraphQlService service) {
|
||||
return builder(service).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder with options to initialize a {@code GraphQlTester}.
|
||||
* @param service the service to execute requests with
|
||||
* @return the builder to use
|
||||
*/
|
||||
static Builder<?> builder(GraphQlService service) {
|
||||
return new DefaultGraphQlTesterBuilder(service);
|
||||
static GraphQlTester.Builder<?> builder(GraphQlTransport transport) {
|
||||
return new DefaultGraphQlTester.Builder(transport);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A builder to create a {@link GraphQlTester} instance.
|
||||
*/
|
||||
interface Builder<T extends Builder<T>> {
|
||||
interface Builder<B extends Builder<B>> {
|
||||
|
||||
/**
|
||||
* Configure a global {@link ErrorSpec#filter(Predicate) filter} that
|
||||
@@ -95,26 +103,21 @@ public interface GraphQlTester {
|
||||
* @param predicate the error filter to add
|
||||
* @return the same builder instance
|
||||
*/
|
||||
T errorFilter(Predicate<GraphQLError> predicate);
|
||||
B errorFilter(Predicate<GraphQLError> predicate);
|
||||
|
||||
/**
|
||||
* Provide JSONPath configuration settings, including a
|
||||
* {@link com.jayway.jsonpath.spi.json.JsonProvider} as well as a
|
||||
* {@link com.jayway.jsonpath.spi.mapper.MappingProvider} that are used
|
||||
* to serialize and deserialize GraphQL JSON content.
|
||||
* <p>By default the configuration is to use Jackson JSON if it is
|
||||
* present on the classpath.
|
||||
* @param config the JSONPath configuration to use
|
||||
* @return the same builder instance
|
||||
* Configure a {@link DocumentSource} for use with
|
||||
* {@link #documentName(String)} for resolving a document by name.
|
||||
* <p>By default, {@link ResourceDocumentSource} is used.
|
||||
*/
|
||||
T jsonPathConfig(Configuration config);
|
||||
B documentSource(DocumentSource contentLoader);
|
||||
|
||||
/**
|
||||
* Max amount of time to wait for a GraphQL response.
|
||||
* <p>By default this is set to 5 seconds.
|
||||
* @param timeout the response timeout value
|
||||
*/
|
||||
T responseTimeout(Duration timeout);
|
||||
B responseTimeout(Duration timeout);
|
||||
|
||||
/**
|
||||
* Build the {@code GraphQlTester}.
|
||||
@@ -174,13 +177,6 @@ public interface GraphQlTester {
|
||||
*/
|
||||
T variable(String name, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Set the locale to associate with the request.
|
||||
* @param locale the locale to use
|
||||
* @return this request spec
|
||||
*/
|
||||
T locale(Locale locale);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
|
||||
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Base class support for implementations of
|
||||
* {@link GraphQlTester.Builder} and {@link WebGraphQlTester.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GraphQlTesterBuilderSupport {
|
||||
|
||||
private static final boolean jackson2Present;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = GraphQlTesterBuilderSupport.class.getClassLoader();
|
||||
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
}
|
||||
|
||||
private static final Duration DEFAULT_RESPONSE_DURATION = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
@Nullable
|
||||
private Predicate<GraphQLError> errorFilter;
|
||||
|
||||
@Nullable
|
||||
private Configuration jsonPathConfig;
|
||||
|
||||
@Nullable
|
||||
private Duration responseTimeout;
|
||||
|
||||
private final Function<String, String> queryNameResolver = new QueryNameResolver();
|
||||
|
||||
|
||||
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 Function<String, String> getQueryNameResolver() {
|
||||
return this.queryNameResolver;
|
||||
}
|
||||
|
||||
protected Configuration initJsonPathConfig() {
|
||||
if (this.jsonPathConfig != null) {
|
||||
return this.jsonPathConfig;
|
||||
}
|
||||
else if (jackson2Present) {
|
||||
return Jackson2Configuration.create();
|
||||
}
|
||||
else {
|
||||
return Configuration.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
protected Duration initResponseTimeout() {
|
||||
return (this.responseTimeout != null ? this.responseTimeout : DEFAULT_RESPONSE_DURATION);
|
||||
}
|
||||
|
||||
|
||||
private static class QueryNameResolver implements Function<String, String> {
|
||||
|
||||
private static final ClassPathResource LOCATION = new ClassPathResource("graphql/");
|
||||
|
||||
private static final String[] EXTENSIONS = new String[] {".graphql", ".gql"};
|
||||
|
||||
@Override
|
||||
public String apply(String queryName) {
|
||||
Resource queryResource = getQueryResource(queryName);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
try {
|
||||
FileCopyUtils.copy(queryResource.getInputStream(), outputStream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalArgumentException("Failed to read query from: " + LOCATION.getPath());
|
||||
}
|
||||
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private Resource getQueryResource(String queryName) {
|
||||
for (String extension : EXTENSIONS) {
|
||||
Resource resource = LOCATION.createRelative(queryName + extension);
|
||||
if (resource.exists()) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find file '" + queryName + "' with extensions " + Arrays.toString(EXTENSIONS) +
|
||||
" under " + LOCATION.getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Jackson2Configuration {
|
||||
|
||||
static Configuration create() {
|
||||
return Configuration.builder()
|
||||
.jsonProvider(new JacksonJsonProvider())
|
||||
.mappingProvider(new JacksonMappingProvider())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.graphql.RequestInput;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.AlternativeJdkIdGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.IdGenerator;
|
||||
|
||||
/**
|
||||
* Base class support for implementations of
|
||||
* {@link GraphQlTester.RequestSpec} and {@link WebGraphQlTester.RequestSpec}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GraphQlTesterRequestSpecSupport {
|
||||
|
||||
private static final IdGenerator idGenerator = new AlternativeJdkIdGenerator();
|
||||
|
||||
private final String query;
|
||||
|
||||
@Nullable
|
||||
private String operationName;
|
||||
|
||||
private final Map<String, Object> variables = new LinkedHashMap<>();
|
||||
|
||||
@Nullable
|
||||
private Locale locale;
|
||||
|
||||
|
||||
protected GraphQlTesterRequestSpecSupport(String query) {
|
||||
Assert.notNull(query, "`query` is required");
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
|
||||
protected void setOperationName(@Nullable String name) {
|
||||
this.operationName = name;
|
||||
}
|
||||
|
||||
protected void addVariable(String name, @Nullable Object value) {
|
||||
this.variables.put(name, value);
|
||||
}
|
||||
|
||||
protected void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
protected void verify(GraphQlTester.ResponseSpec responseSpec) {
|
||||
responseSpec.path("$.errors").valueIsEmpty();
|
||||
}
|
||||
|
||||
protected RequestInput createRequestInput() {
|
||||
return new RequestInput(this.query, this.operationName, this.variables, idGenerator.generateId().toString(), this.locale);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* GraphQL over HTTP tester that uses {@link WebTestClient} and supports tests
|
||||
* with or without a running server, depending on how {@code WebTestClient} is
|
||||
* configured.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface HttpGraphQlTester extends WebGraphQlTester {
|
||||
|
||||
|
||||
@Override
|
||||
Builder<?> mutate();
|
||||
|
||||
|
||||
/**
|
||||
* Create an {@link HttpGraphQlTester} that uses the given {@link WebTestClient}.
|
||||
*/
|
||||
static HttpGraphQlTester create(WebTestClient webTestClient) {
|
||||
return builder(webTestClient.mutate()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder to initialize an {@link HttpGraphQlTester} by creating
|
||||
* the underlying {@link WebTestClient} through the given builder.
|
||||
*/
|
||||
static HttpGraphQlTester.Builder<?> builder(WebTestClient.Builder webTestClientBuilder) {
|
||||
return new DefaultHttpGraphQlTester.Builder(webTestClientBuilder);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for the GraphQL over HTTP tester.
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlTester.Builder<B> {
|
||||
|
||||
/**
|
||||
* Customize the {@code WebTestClient} to use.
|
||||
* <p>Note that some properties of {@code WebTestClient.Builder} like the
|
||||
* base URL, headers, and codecs can be customized through this builder.
|
||||
* @see #url(String)
|
||||
* @see #header(String, String...)
|
||||
* @see #codecConfigurer(Consumer)
|
||||
*/
|
||||
B webTestClient(Consumer<WebTestClient.Builder> webClient);
|
||||
|
||||
/**
|
||||
* Build the {@code HttpGraphQlTester} instance.
|
||||
*/
|
||||
@Override
|
||||
HttpGraphQlTester build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.ExecutionResultImpl;
|
||||
import graphql.GraphQLError;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} with setters, for internal use to use to deserialize
|
||||
* from a response.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class TestExecutionResult implements ExecutionResult {
|
||||
|
||||
@Nullable
|
||||
private Object data;
|
||||
|
||||
private List<GraphQLError> errors = Collections.emptyList();
|
||||
|
||||
private Map<Object, Object> extensions = Collections.emptyMap();
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T> T getData() {
|
||||
return (T) this.data;
|
||||
}
|
||||
|
||||
public void setErrors(List<TestGraphQlError> errors) {
|
||||
this.errors = new ArrayList<>(errors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GraphQLError> getErrors() {
|
||||
return this.errors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataPresent() {
|
||||
return getData() != null;
|
||||
}
|
||||
|
||||
public void setExtensions(Map<Object, Object> extensions) {
|
||||
this.extensions = new LinkedHashMap<>(extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> getExtensions() {
|
||||
return this.extensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toSpecification() {
|
||||
ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult()
|
||||
.addErrors(this.errors)
|
||||
.extensions(this.extensions);
|
||||
|
||||
if (isDataPresent()) {
|
||||
builder.data(this.data);
|
||||
}
|
||||
|
||||
return builder.build().toSpecification();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,8 +30,7 @@ import graphql.language.SourceLocation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link GraphQLError} with setters, for internal use to use to deserialize
|
||||
* from a response.
|
||||
* {@link GraphQLError} with setters, for internal use to deserialize from a response.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
|
||||
@@ -24,17 +24,17 @@ import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
|
||||
/**
|
||||
* Adapter for a JSONPath {@link TypeRef} with a {@link #getType() type} that
|
||||
* returns fixed type information rather than obtained from the generic type
|
||||
* declaration.
|
||||
* Adapt a JSONPath {@link TypeRef} to {@link ParameterizedTypeReference} and
|
||||
* {@link ResolvableType} for classes with generics.
|
||||
*
|
||||
* @param <T> the referenced type
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class TypeRefAdapter<T> extends TypeRef<T> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
|
||||
TypeRefAdapter(Class<T> clazz) {
|
||||
this.type = clazz;
|
||||
}
|
||||
@@ -51,6 +51,7 @@ final class TypeRefAdapter<T> extends TypeRef<T> {
|
||||
this.type = ResolvableType.forClassWithGenerics(clazz, ResolvableType.forType(generic)).getType();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,180 +13,91 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
|
||||
/**
|
||||
* Main entry point for testing GraphQL over a Web transport with requests
|
||||
* executed {@link #create(WebTestClient) via WebTestClient} or
|
||||
* {@link #create(WebGraphQlHandler) via WebGraphQlHandler}.
|
||||
* Server-side tester, without a client, that executes requests through a
|
||||
* {@link WebGraphQlHandler}. Similar to {@link GraphQlServiceTester} but also
|
||||
* adding a web processing layer with a {@code WebInterceptor} chain.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
* @see HttpGraphQlTester
|
||||
* @see WebSocketGraphQlTester
|
||||
*/
|
||||
public interface WebGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The returned spec for Web request input also allows adding HTTP headers.
|
||||
*/
|
||||
WebRequestSpec query(String query);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The returned spec for Web request input also allows adding HTTP headers.
|
||||
*/
|
||||
WebRequestSpec queryName(String queryName);
|
||||
|
||||
@Override
|
||||
Builder<?> mutate();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@code WebGraphQlTester} that performs GraphQL requests as an
|
||||
* HTTP client through the given {@link WebTestClient}. Depending on how the
|
||||
* {@code WebTestClient} is set up, tests may be with or without a server.
|
||||
* See setup examples in class-level Javadoc.
|
||||
* @param client the web client to perform requests with
|
||||
* @return the created {@code WebGraphQlTester}
|
||||
* Create a {@link WebGraphQlTester} instance.
|
||||
*/
|
||||
static WebGraphQlTester create(WebTestClient client) {
|
||||
return builder(client).build();
|
||||
static WebGraphQlTester create(WebGraphQlHandler graphQlHandler) {
|
||||
return builder(graphQlHandler).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code WebGraphQlTester} that performs GraphQL requests through
|
||||
* the given {@link WebGraphQlHandler}.
|
||||
* @param handler the handler to execute requests with
|
||||
* @return the created {@code WebGraphQlTester}
|
||||
* Return a builder for a {@link WebGraphQlTester}.
|
||||
* @param graphQlHandler the handler to execute requests
|
||||
*/
|
||||
static WebGraphQlTester create(WebGraphQlHandler handler) {
|
||||
return builder(handler).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder with options to initialize a {@code WebGraphQlTester}.
|
||||
* @param client the client to execute requests with
|
||||
* @return the builder to use
|
||||
*/
|
||||
static Builder builder(WebTestClient client) {
|
||||
return new DefaultWebGraphQlTesterBuilder(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder with options to initialize a {@code WebGraphQlHandler}.
|
||||
* @param handler the handler to execute requests with
|
||||
* @return the builder to use
|
||||
*/
|
||||
static Builder builder(WebGraphQlHandler handler) {
|
||||
return new DefaultWebGraphQlTesterBuilder(handler);
|
||||
static WebGraphQlTester.Builder<?> builder(WebGraphQlHandler graphQlHandler) {
|
||||
return new DefaultWebGraphQlTester.Builder<>(graphQlHandler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A builder to create a {@link WebGraphQlTester} instance.
|
||||
* Common builder for Web {@code GraphQlTester} extensions.
|
||||
*/
|
||||
interface Builder extends GraphQlTester.Builder<Builder> {
|
||||
interface Builder<B extends Builder<B>> extends GraphQlTester.Builder<B> {
|
||||
|
||||
/**
|
||||
* Add the given header to all requests that haven't added it.
|
||||
* @param headerName the header name
|
||||
* @param headerValues the header values
|
||||
* Set the GraphQL endpoint URL as a String.
|
||||
* @param url the url to send HTTP requests to or connect over WebSocket
|
||||
*/
|
||||
Builder defaultHttpHeader(String headerName, String... headerValues);
|
||||
B url(String url);
|
||||
|
||||
/**
|
||||
* Variant of {@link #defaultHttpHeader(String, String...)} that provides
|
||||
* access to the underlying headers to inspect or modify directly.
|
||||
* Set the GraphQL endpoint URL.
|
||||
* @param url the url to send HTTP requests to or connect over WebSocket
|
||||
*/
|
||||
B url(URI url);
|
||||
|
||||
/**
|
||||
* Add the given header to HTTP requests or to the WebSocket handshake request.
|
||||
* @param name the header name
|
||||
* @param values the header values
|
||||
*/
|
||||
B header(String name, String... values);
|
||||
|
||||
/**
|
||||
* Variant of {@link #header(String, String...)} that provides access
|
||||
* to the underlying headers to inspect or modify directly.
|
||||
* @param headersConsumer a function that consumes the {@code HttpHeaders}
|
||||
*/
|
||||
Builder defaultHttpHeaders(Consumer<HttpHeaders> headersConsumer);
|
||||
B headers(Consumer<HttpHeaders> headersConsumer);
|
||||
|
||||
/**
|
||||
* Build the {@code WebGraphQlTester}.
|
||||
* @return the created instance
|
||||
* Configure the underlying {@code CodecConfigurer} to use for all JSON
|
||||
* encoding and decoding needs.
|
||||
*/
|
||||
B codecConfigurer(Consumer<CodecConfigurer> codecsConsumer);
|
||||
|
||||
/**
|
||||
* Build a {@link WebGraphQlTester} instance.
|
||||
*/
|
||||
@Override
|
||||
WebGraphQlTester build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extends {@link GraphQlTester.RequestSpec} with further input options
|
||||
* applicable to Web requests.
|
||||
*/
|
||||
interface WebRequestSpec extends RequestSpec<WebRequestSpec> {
|
||||
|
||||
/**
|
||||
* Add the given, single header value under the given name.
|
||||
* @param headerName the header name
|
||||
* @param headerValues the header value(s)
|
||||
* @return the same instance
|
||||
*/
|
||||
WebRequestSpec httpHeader(String headerName, String... headerValues);
|
||||
|
||||
/**
|
||||
* Manipulate the request's headers with the given consumer. The
|
||||
* headers provided to the consumer are "live", so that the consumer can
|
||||
* be used to {@linkplain HttpHeaders#set(String, String) overwrite}
|
||||
* existing header values, {@linkplain HttpHeaders#remove(Object) remove}
|
||||
* values, or use any of the other {@link HttpHeaders} methods.
|
||||
* @param headersConsumer a function that consumes the {@code HttpHeaders}
|
||||
* @return this builder
|
||||
*/
|
||||
WebRequestSpec httpHeaders(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} to expose 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} to expose 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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.client.WebSocketGraphQlClient;
|
||||
import org.springframework.web.reactive.socket.client.WebSocketClient;
|
||||
|
||||
/**
|
||||
* GraphQL over WebSocket client that uses {@link WebSocketClient}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface WebSocketGraphQlTester extends WebGraphQlTester {
|
||||
|
||||
/**
|
||||
* This is delegated to the {@code start()} method of the underlying
|
||||
* {@link WebSocketGraphQlClient}.
|
||||
*/
|
||||
Mono<Void> start();
|
||||
|
||||
/**
|
||||
* This is delegated to the {@code stop()} method of the underlying
|
||||
* {@link WebSocketGraphQlClient}.
|
||||
*/
|
||||
Mono<Void> stop();
|
||||
|
||||
@Override
|
||||
Builder<?> mutate();
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link WebSocketGraphQlTester}.
|
||||
* @param url the GraphQL endpoint URL
|
||||
* @param webSocketClient the underlying transport client to use
|
||||
*/
|
||||
static WebSocketGraphQlTester create(URI url, WebSocketClient webSocketClient) {
|
||||
return builder(url, webSocketClient).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder for a {@link WebSocketGraphQlClient}.
|
||||
* @param url the GraphQL endpoint URL
|
||||
* @param webSocketClient the underlying transport client to use
|
||||
*/
|
||||
static WebSocketGraphQlTester.Builder<?> builder(URI url, WebSocketClient webSocketClient) {
|
||||
return new DefaultWebSocketGraphQlTester.Builder(url, webSocketClient);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for a GraphQL over WebSocket tester.
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> extends WebGraphQlTester.Builder<B> {
|
||||
|
||||
/**
|
||||
* Build the {@code WebSocketGraphQlTester}.
|
||||
*/
|
||||
@Override
|
||||
WebSocketGraphQlTester build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user