Auto-configure HTTP transport for MVC and WebFlux
This commit adds two new auto-configuration classes for Spring GraphQL support. Once the base GraphQL infrastructure is in place, we can now expose the `GraphQlService` over an HTTP transport. Spring GraphQL supports both MVC and WebFlux, so this commit ships with one auto-configuration for each. Developers can configure the HTTP path where the GraphQL resource is exposed using the `spring.graphql.path` configuration property (this defaults to `"/graphql"`). See gh-29140
This commit is contained in:
parent
5cf0a4db94
commit
b38d04556e
|
|
@ -29,8 +29,21 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||
@ConfigurationProperties(prefix = "spring.graphql")
|
||||
public class GraphQlProperties {
|
||||
|
||||
/**
|
||||
* Path at which to expose a GraphQL request HTTP endpoint.
|
||||
*/
|
||||
private String path = "/graphql";
|
||||
|
||||
private final Schema schema = new Schema();
|
||||
|
||||
public String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Schema getSchema() {
|
||||
return this.schema;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql.reactive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.webflux.GraphQlHttpHandler;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for enabling Spring GraphQL over
|
||||
* WebFlux.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
@ConditionalOnClass({ GraphQL.class, GraphQlHttpHandler.class })
|
||||
@ConditionalOnBean(GraphQlService.class)
|
||||
@AutoConfigureAfter(GraphQlAutoConfiguration.class)
|
||||
public class GraphQlWebFluxAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) {
|
||||
return new GraphQlHttpHandler(webGraphQlHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebGraphQlHandler webGraphQlHandler(GraphQlService service,
|
||||
ObjectProvider<WebInterceptor> interceptorsProvider) {
|
||||
return WebGraphQlHandler.builder(service)
|
||||
.interceptors(interceptorsProvider.orderedStream().collect(Collectors.toList())).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> graphQlEndpoint(GraphQlHttpHandler handler, GraphQlSource graphQlSource,
|
||||
GraphQlProperties properties, ResourceLoader resourceLoader) {
|
||||
|
||||
String graphQLPath = properties.getPath();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("GraphQL endpoint HTTP POST " + graphQLPath);
|
||||
}
|
||||
|
||||
RouterFunctions.Builder builder = RouterFunctions.route()
|
||||
.GET(graphQLPath,
|
||||
(request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
.headers((headers) -> headers.setAllow(Collections.singleton(HttpMethod.POST))).build())
|
||||
.POST(graphQLPath, accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)),
|
||||
handler::handleRequest);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2020-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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration classes for WebFlux support in Spring GraphQL.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.graphql.reactive;
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
import org.springframework.graphql.execution.ThreadLocalAccessor;
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.webmvc.GraphQlHttpHandler;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.servlet.function.RequestPredicates;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for enabling Spring GraphQL over
|
||||
* Spring MVC.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.7.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnClass({ GraphQL.class, GraphQlHttpHandler.class })
|
||||
@ConditionalOnBean(GraphQlService.class)
|
||||
@AutoConfigureAfter(GraphQlAutoConfiguration.class)
|
||||
public class GraphQlWebMvcAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GraphQlWebMvcAutoConfiguration.class);
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQlHttpHandler graphQlHttpHandler(WebGraphQlHandler webGraphQlHandler) {
|
||||
return new GraphQlHttpHandler(webGraphQlHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebGraphQlHandler webGraphQlHandler(GraphQlService service,
|
||||
ObjectProvider<WebInterceptor> interceptorsProvider,
|
||||
ObjectProvider<ThreadLocalAccessor> accessorsProvider) {
|
||||
return WebGraphQlHandler.builder(service)
|
||||
.interceptors(interceptorsProvider.orderedStream().collect(Collectors.toList()))
|
||||
.threadLocalAccessors(accessorsProvider.orderedStream().collect(Collectors.toList())).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler handler, GraphQlSource graphQlSource,
|
||||
GraphQlProperties properties, ResourceLoader resourceLoader) {
|
||||
|
||||
String graphQLPath = properties.getPath();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("GraphQL endpoint HTTP POST " + graphQLPath);
|
||||
}
|
||||
|
||||
RouterFunctions.Builder builder = RouterFunctions.route()
|
||||
.GET(graphQLPath,
|
||||
(request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
.headers((headers) -> headers.setAllow(Collections.singleton(HttpMethod.POST))).build())
|
||||
.POST(graphQLPath, RequestPredicates.contentType(MediaType.APPLICATION_JSON)
|
||||
.and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), handler::handleRequest);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2020-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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration classes for MVC support in Spring GraphQL.
|
||||
*/
|
||||
package org.springframework.boot.autoconfigure.graphql.servlet;
|
||||
|
|
@ -69,6 +69,8 @@ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAuto
|
|||
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.graphql.reactive.GraphQlWebFluxAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.graphql.servlet.GraphQlWebMvcAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql;
|
||||
|
||||
/**
|
||||
* Sample class for
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class Book {
|
||||
|
||||
String id;
|
||||
|
||||
String name;
|
||||
|
||||
int pageCount;
|
||||
|
||||
String author;
|
||||
|
||||
public Book() {
|
||||
}
|
||||
|
||||
public Book(String id, String name, int pageCount, String author) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pageCount = pageCount;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getPageCount() {
|
||||
return this.pageCount;
|
||||
}
|
||||
|
||||
public void setPageCount(int pageCount) {
|
||||
this.pageCount = pageCount;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return this.author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import graphql.schema.DataFetcher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Test utility class holding {@link DataFetcher} implementations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public final class GraphQlTestDataFetchers {
|
||||
|
||||
private static List<Book> books = Arrays.asList(new Book("book-1", "GraphQL for beginners", 100, "John GraphQL"),
|
||||
new Book("book-2", "Harry Potter and the Philosopher's Stone", 223, "Joanne Rowling"),
|
||||
new Book("book-3", "Moby Dick", 635, "Moby Dick"), new Book("book-3", "Moby Dick", 635, "Moby Dick"));
|
||||
|
||||
private GraphQlTestDataFetchers() {
|
||||
|
||||
}
|
||||
|
||||
public static DataFetcher getBookByIdDataFetcher() {
|
||||
return (environment) -> getBookById(environment.getArgument("id"));
|
||||
}
|
||||
|
||||
public static DataFetcher getBooksOnSaleDataFetcher() {
|
||||
return (environment) -> getBooksOnSale(environment.getArgument("minPages"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Book getBookById(String id) {
|
||||
return books.stream().filter((book) -> book.getId().equals(id)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public static Flux<Book> getBooksOnSale(int minPages) {
|
||||
return Flux.fromIterable(books).filter((book) -> book.getPageCount() >= minPages);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql.reactive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import graphql.schema.idl.TypeRuntimeWiring;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlTestDataFetchers;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlWebFluxAutoConfiguration}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlWebFluxAutoConfigurationTests {
|
||||
|
||||
private static final String BASE_URL = "https://spring.example.org/graphql";
|
||||
|
||||
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class,
|
||||
CodecsAutoConfiguration.class, JacksonAutoConfiguration.class, GraphQlAutoConfiguration.class,
|
||||
GraphQlWebFluxAutoConfiguration.class))
|
||||
.withUserConfiguration(DataFetchersConfiguration.class, CustomWebInterceptor.class)
|
||||
.withPropertyValues("spring.main.web-application-type=reactive");
|
||||
|
||||
@Test
|
||||
void simpleQueryShouldWork() {
|
||||
testWithWebClient((client) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
client.post().uri("").bodyValue("{ \"query\": \"" + query + "\"}").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("data.bookById.name").isEqualTo("GraphQL for beginners");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpGetQueryShouldBeSupported() {
|
||||
testWithWebClient((client) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
client.get().uri("?query={query}", "{ \"query\": \"" + query + "\"}").exchange().expectStatus()
|
||||
.isEqualTo(HttpStatus.METHOD_NOT_ALLOWED).expectHeader().valueEquals("Allow", "POST");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectMissingQuery() {
|
||||
testWithWebClient((client) -> client.post().uri("").bodyValue("{}").exchange().expectStatus().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectQueryWithInvalidJson() {
|
||||
testWithWebClient((client) -> client.post().uri("").bodyValue(":)").exchange().expectStatus().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureWebInterceptors() {
|
||||
testWithWebClient((client) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
|
||||
client.post().uri("").bodyValue("{ \"query\": \"" + query + "\"}").exchange().expectStatus().isOk()
|
||||
.expectHeader().valueEquals("X-Custom-Header", "42");
|
||||
});
|
||||
}
|
||||
|
||||
private void testWithWebClient(Consumer<WebTestClient> consumer) {
|
||||
this.contextRunner.run((context) -> {
|
||||
WebTestClient client = WebTestClient.bindToApplicationContext(context).configureClient()
|
||||
.defaultHeaders((headers) -> {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
}).baseUrl(BASE_URL).build();
|
||||
consumer.accept(client);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DataFetchersConfiguration {
|
||||
|
||||
@Bean
|
||||
RuntimeWiringConfigurer bookDataFetcher() {
|
||||
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("bookById",
|
||||
GraphQlTestDataFetchers.getBookByIdDataFetcher()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomWebInterceptor {
|
||||
|
||||
@Bean
|
||||
WebInterceptor customWebInterceptor() {
|
||||
return (webInput, interceptorChain) -> interceptorChain.next(webInput)
|
||||
.map((output) -> output.transform((builder) -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright 2012-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.boot.autoconfigure.graphql.servlet;
|
||||
|
||||
import graphql.schema.idl.TypeRuntimeWiring;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.graphql.GraphQlTestDataFetchers;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link GraphQlWebMvcAutoConfiguration}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class GraphQlWebMvcAutoConfigurationTests {
|
||||
|
||||
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class,
|
||||
GraphQlAutoConfiguration.class, GraphQlWebMvcAutoConfiguration.class))
|
||||
.withUserConfiguration(DataFetchersConfiguration.class, CustomWebInterceptor.class)
|
||||
.withPropertyValues("spring.main.web-application-type=servlet");
|
||||
|
||||
@Test
|
||||
void simpleQueryShouldWork() {
|
||||
testWith((mockMvc) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
MvcResult result = mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")).andReturn();
|
||||
mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk())
|
||||
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
|
||||
.andExpect(jsonPath("data.bookById.name").value("GraphQL for beginners"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpGetQueryShouldBeSupported() {
|
||||
testWith((mockMvc) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
mockMvc.perform(get("/graphql?query={query}", "{\"query\": \"" + query + "\"}"))
|
||||
.andExpect(status().isMethodNotAllowed()).andExpect(header().string("Allow", "POST"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectMissingQuery() {
|
||||
testWith((mockMvc) -> mockMvc.perform(post("/graphql").content("{}")).andExpect(status().isBadRequest()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectQueryWithInvalidJson() {
|
||||
testWith((mockMvc) -> mockMvc.perform(post("/graphql").content(":)")).andExpect(status().isBadRequest()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureWebInterceptors() {
|
||||
testWith((mockMvc) -> {
|
||||
String query = "{ bookById(id: \\\"book-1\\\"){ id name pageCount author } }";
|
||||
MvcResult result = mockMvc.perform(post("/graphql").content("{\"query\": \"" + query + "\"}")).andReturn();
|
||||
mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk())
|
||||
.andExpect(header().string("X-Custom-Header", "42"));
|
||||
});
|
||||
}
|
||||
|
||||
private void testWith(MockMvcConsumer mockMvcConsumer) {
|
||||
this.contextRunner.run((context) -> {
|
||||
MediaType mediaType = MediaType.APPLICATION_JSON;
|
||||
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
||||
.defaultRequest(post("/graphql").contentType(mediaType).accept(mediaType)).build();
|
||||
mockMvcConsumer.accept(mockMvc);
|
||||
});
|
||||
}
|
||||
|
||||
private interface MockMvcConsumer {
|
||||
|
||||
void accept(MockMvc mockMvc) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DataFetchersConfiguration {
|
||||
|
||||
@Bean
|
||||
RuntimeWiringConfigurer bookDataFetcher() {
|
||||
return (builder) -> builder.type(TypeRuntimeWiring.newTypeWiring("Query").dataFetcher("bookById",
|
||||
GraphQlTestDataFetchers.getBookByIdDataFetcher()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomWebInterceptor {
|
||||
|
||||
@Bean
|
||||
WebInterceptor customWebInterceptor() {
|
||||
return (webInput, interceptorChain) -> interceptorChain.next(webInput)
|
||||
.map((output) -> output.transform((builder) -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue