WebGraphQlTester supports HTTP header input
Closes gh-64
This commit is contained in:
@@ -13,6 +13,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
testImplementation project(':spring-graphql-test')
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package io.spring.sample.graphql;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -24,7 +24,7 @@ import org.springframework.stereotype.Component;
|
||||
public class EmployeeService {
|
||||
|
||||
public List<Employee> getAllEmployees() {
|
||||
return Arrays.asList(new Employee("1", "Andi"));
|
||||
return Collections.singletonList(new Employee("1", "Andi"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,45 +15,27 @@
|
||||
*/
|
||||
package io.spring.sample.graphql;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
|
||||
import org.springframework.graphql.boot.test.tester.AutoConfigureGraphQlTester;
|
||||
import org.springframework.graphql.execution.ErrorType;
|
||||
import org.springframework.graphql.test.tester.WebGraphQlTester;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
// @formatter:off
|
||||
|
||||
@SpringBootTest()
|
||||
@AutoConfigureWebTestClient
|
||||
@AutoConfigureGraphQlTester
|
||||
class SampleApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private ReactiveWebApplicationContext context;
|
||||
private static final String BASE_URL = "https://spring.example.org/graphql";
|
||||
|
||||
|
||||
WebTestClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(this.context)
|
||||
.apply(SecurityMockServerConfigurers.springSecurity())
|
||||
.configureClient()
|
||||
.filter(ExchangeFilterFunctions.basicAuthentication())
|
||||
.defaultHeaders(headers -> {
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
|
||||
})
|
||||
.baseUrl(BASE_URL)
|
||||
.build();
|
||||
}
|
||||
private WebGraphQlTester graphQlTester;
|
||||
|
||||
@Test
|
||||
void printError() {
|
||||
@@ -64,18 +46,14 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
|
||||
client.post().uri("")
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class)
|
||||
.consumeWith(System.out::println);
|
||||
|
||||
this.graphQlTester.query(query)
|
||||
.execute()
|
||||
.errors()
|
||||
.satisfy(System.out::println);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anonoymousThenUnauthorized() {
|
||||
void anonymousThenUnauthorized() {
|
||||
String query = "{" +
|
||||
" employees{ " +
|
||||
" name" +
|
||||
@@ -83,12 +61,14 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
client.post().uri("")
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().jsonPath("errors[0].extensions.classification").isEqualTo("UNAUTHORIZED");
|
||||
|
||||
this.graphQlTester.query(query)
|
||||
.execute()
|
||||
.errors()
|
||||
.satisfy(errors -> {
|
||||
assertThat(errors).hasSize(1);
|
||||
assertThat(errors.get(0).getExtensions().get("classification"))
|
||||
.isEqualTo(ErrorType.UNAUTHORIZED.name());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -100,12 +80,15 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
client.post().uri("")
|
||||
.headers(h -> h.setBasicAuth("rob", "rob"))
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().jsonPath("errors[0].extensions.classification").isEqualTo("FORBIDDEN");
|
||||
this.graphQlTester.query(query)
|
||||
.headers(headers -> headers.setBasicAuth("rob", "rob"))
|
||||
.execute()
|
||||
.errors()
|
||||
.satisfy(errors -> {
|
||||
assertThat(errors).hasSize(1);
|
||||
assertThat(errors.get(0).getExtensions().get("classification"))
|
||||
.isEqualTo(ErrorType.FORBIDDEN.name());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -116,13 +99,9 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
|
||||
client.post().uri("")
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().jsonPath("data.employees[0].name").isEqualTo("Andi");
|
||||
|
||||
this.graphQlTester.query(query)
|
||||
.execute()
|
||||
.path("employees[0].name").entity(String.class).isEqualTo("Andi");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,15 +113,14 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
|
||||
client.post().uri("")
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("data.employees[0].name").isEqualTo("Andi")
|
||||
.jsonPath("data.employees[0].salary").doesNotExist();
|
||||
|
||||
this.graphQlTester.query(query)
|
||||
.execute()
|
||||
.errors()
|
||||
.satisfy(errors -> {
|
||||
assertThat(errors).hasSize(1);
|
||||
assertThat(errors.get(0).getExtensions().get("classification"))
|
||||
.isEqualTo(ErrorType.UNAUTHORIZED.name());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -154,16 +132,11 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
|
||||
client.post().uri("")
|
||||
.headers(h -> h.setBasicAuth("admin", "admin"))
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody()
|
||||
.jsonPath("data.employees[0].name").isEqualTo("Andi")
|
||||
.jsonPath("data.employees[0].salary").isEqualTo("42");
|
||||
|
||||
this.graphQlTester.query(query)
|
||||
.headers(headers -> headers.setBasicAuth("admin", "admin"))
|
||||
.execute()
|
||||
.path("employees[0].name").entity(String.class).isEqualTo("Andi")
|
||||
.path("employees[0].salary").entity(int.class).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -175,14 +148,11 @@ class SampleApplicationTests {
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
|
||||
client.post().uri("")
|
||||
.headers(h -> h.setBasicAuth("admin", "INVALID"))
|
||||
.bodyValue("{ \"query\": \"" + query + "\"}")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized()
|
||||
.expectBody()
|
||||
.isEmpty();
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
this.graphQlTester.query(query)
|
||||
.headers(headers -> headers.setBasicAuth("admin", "INVALID"))
|
||||
.executeAndVerify())
|
||||
.hasMessage("Status expected:<200 OK> but was:<401 UNAUTHORIZED>");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,6 +66,7 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
|
||||
}
|
||||
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
|
||||
|
||||
@@ -77,13 +78,19 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
this.requestStrategy = requestStrategy;
|
||||
}
|
||||
|
||||
|
||||
protected RequestStrategy getRequestStrategy() {
|
||||
return this.requestStrategy;
|
||||
}
|
||||
|
||||
|
||||
protected static Configuration initJsonPathConfig() {
|
||||
return (jackson2Present ? Jackson2Configuration.create() : Configuration.builder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpec query(String query) {
|
||||
return new DefaultRequestSpec(query);
|
||||
return new DefaultRequestSpec(this.requestStrategy, query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +189,9 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
/**
|
||||
* {@link RequestSpec} that collects the query, operationName, and variables.
|
||||
*/
|
||||
private final class DefaultRequestSpec implements RequestSpec {
|
||||
protected static class DefaultRequestSpec implements RequestSpec {
|
||||
|
||||
private final RequestStrategy requestStrategy;
|
||||
|
||||
private final String query;
|
||||
|
||||
@@ -191,8 +200,10 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
private final Map<String, Object> variables = new LinkedHashMap<>();
|
||||
|
||||
private DefaultRequestSpec(String query) {
|
||||
protected DefaultRequestSpec(RequestStrategy requestStrategy, String query) {
|
||||
Assert.notNull(requestStrategy, "RequestStrategy is required");
|
||||
Assert.notNull(query, "`query` is required");
|
||||
this.requestStrategy = requestStrategy;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@@ -216,21 +227,25 @@ class DefaultGraphQlTester implements GraphQlTester {
|
||||
|
||||
@Override
|
||||
public ResponseSpec execute() {
|
||||
RequestInput input = new RequestInput(this.query, this.operationName, this.variables);
|
||||
return DefaultGraphQlTester.this.requestStrategy.execute(input);
|
||||
RequestInput input = createRequestInput();
|
||||
return this.requestStrategy.execute(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAndVerify() {
|
||||
RequestInput input = new RequestInput(this.query, this.operationName, this.variables);
|
||||
ResponseSpec spec = DefaultGraphQlTester.this.requestStrategy.execute(input);
|
||||
RequestInput input = createRequestInput();
|
||||
ResponseSpec spec = this.requestStrategy.execute(input);
|
||||
spec.path("$.errors").valueIsEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription() {
|
||||
RequestInput input = new RequestInput(this.query, this.operationName, this.variables);
|
||||
return DefaultGraphQlTester.this.requestStrategy.executeSubscription(input);
|
||||
RequestInput input = createRequestInput();
|
||||
return this.requestStrategy.executeSubscription(input);
|
||||
}
|
||||
|
||||
protected RequestInput createRequestInput() {
|
||||
return new RequestInput(this.query, this.operationName, this.variables);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jayway.jsonpath.Configuration;
|
||||
import com.jayway.jsonpath.DocumentContext;
|
||||
@@ -51,6 +52,12 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebRequestSpec query(String query) {
|
||||
return new DefaultWebRequestSpec(getRequestStrategy(), query);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link RequestStrategy} that works as an HTTP client with requests executed through
|
||||
* {@link WebTestClient} that in turn may work connect with or without a live server
|
||||
@@ -69,9 +76,20 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
|
||||
@Override
|
||||
public ResponseSpec execute(RequestInput requestInput) {
|
||||
EntityExchangeResult<byte[]> result = this.client.post().contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestInput).exchange().expectStatus().isOk().expectHeader()
|
||||
.contentType(MediaType.APPLICATION_JSON).expectBody().returnResult();
|
||||
Assert.isInstanceOf(WebInput.class, requestInput);
|
||||
WebInput webInput = (WebInput) requestInput;
|
||||
|
||||
EntityExchangeResult<byte[]> result = this.client.post()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.headers(headers -> headers.putAll(webInput.getHeaders()))
|
||||
.bodyValue(requestInput)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody()
|
||||
.returnResult();
|
||||
|
||||
byte[] bytes = result.getResponseBodyContent();
|
||||
Assert.notNull(bytes, "Expected GraphQL response content");
|
||||
@@ -83,9 +101,19 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
|
||||
@Override
|
||||
public SubscriptionSpec executeSubscription(RequestInput requestInput) {
|
||||
Assert.isInstanceOf(WebInput.class, requestInput);
|
||||
WebInput webInput = (WebInput) requestInput;
|
||||
|
||||
FluxExchangeResult<TestExecutionResult> exchangeResult = this.client.post()
|
||||
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.TEXT_EVENT_STREAM).bodyValue(requestInput)
|
||||
.exchange().expectStatus().isOk().expectHeader().contentType(MediaType.TEXT_EVENT_STREAM)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.TEXT_EVENT_STREAM)
|
||||
.headers(headers -> headers.putAll(webInput.getHeaders()))
|
||||
.bodyValue(requestInput)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectHeader()
|
||||
.contentType(MediaType.TEXT_EVENT_STREAM)
|
||||
.returnResult(TestExecutionResult.class);
|
||||
|
||||
return new DefaultSubscriptionSpec(exchangeResult.getResponseBody().cast(ExecutionResult.class),
|
||||
@@ -100,10 +128,6 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
*/
|
||||
private static class WebGraphQlHandlerRequestStrategy extends AbstractDirectRequestStrategy {
|
||||
|
||||
private static final URI DEFAULT_URL = URI.create("http://localhost:8080/graphql");
|
||||
|
||||
private static final HttpHeaders DEFAULT_HEADERS = new HttpHeaders();
|
||||
|
||||
private final WebGraphQlHandler graphQlHandler;
|
||||
|
||||
WebGraphQlHandlerRequestStrategy(WebGraphQlHandler handler, Configuration jsonPathConfig) {
|
||||
@@ -111,12 +135,44 @@ class DefaultWebGraphQlTester extends DefaultGraphQlTester implements WebGraphQl
|
||||
this.graphQlHandler = handler;
|
||||
}
|
||||
|
||||
protected ExecutionResult executeInternal(RequestInput input) {
|
||||
WebInput webInput = new WebInput(DEFAULT_URL, DEFAULT_HEADERS, input.toMap(), null);
|
||||
ExecutionResult result = this.graphQlHandler.handle(webInput).block(DEFAULT_TIMEOUT);
|
||||
protected ExecutionResult executeInternal(RequestInput requestInput) {
|
||||
Assert.isInstanceOf(WebInput.class, requestInput);
|
||||
ExecutionResult result = this.graphQlHandler.handle((WebInput) requestInput).block(DEFAULT_TIMEOUT);
|
||||
Assert.notNull(result, "Expected ExecutionResult");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected static final class DefaultWebRequestSpec extends DefaultRequestSpec implements WebRequestSpec {
|
||||
|
||||
private static final URI DEFAULT_URL = URI.create("");
|
||||
|
||||
private final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
public DefaultWebRequestSpec(RequestStrategy requestStrategy, String query) {
|
||||
super(requestStrategy, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec header(String headerName, String... headerValues) {
|
||||
for (String headerValue : headerValues) {
|
||||
this.headers.add(headerName, headerValue);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebRequestSpec headers(Consumer<HttpHeaders> headersConsumer) {
|
||||
headersConsumer.accept(this.headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestInput createRequestInput() {
|
||||
RequestInput requestInput = super.createRequestInput();
|
||||
return new WebInput(DEFAULT_URL, headers, requestInput.toMap(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.graphql.test.tester;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
@@ -79,6 +82,12 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
*/
|
||||
public interface WebGraphQlTester extends GraphQlTester {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>The returned spec for Web request input also allows adding HTTP headers.
|
||||
*/
|
||||
WebRequestSpec query(String query);
|
||||
|
||||
/**
|
||||
* Create a {@code WebGraphQlTester} that performs GraphQL requests as an
|
||||
* HTTP client through the given {@link WebTestClient}. Depending on how the
|
||||
@@ -101,4 +110,31 @@ public interface WebGraphQlTester extends GraphQlTester {
|
||||
return new DefaultWebGraphQlTester(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends {@link GraphQlTester.RequestSpec} with further input options
|
||||
* applicable to Web requests.
|
||||
*/
|
||||
interface WebRequestSpec extends RequestSpec {
|
||||
|
||||
/**
|
||||
* 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 header(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 headers(Consumer<HttpHeaders> headersConsumer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user