From 64b78d647750e16b0ab3d05fff5769d94e9ca0ff Mon Sep 17 00:00:00 2001 From: Brandon Sara Date: Tue, 19 Apr 2022 14:14:37 -0600 Subject: [PATCH] feat: added ability to use strings for GraphQl queries when using GraphQLTestTemplate --- .../spring/boot/test/GraphQLTestTemplate.java | 113 +++++++++- .../GraphQLTestTemplateIntegrationTest.java | 194 ++++++++++++++++++ 2 files changed, 306 insertions(+), 1 deletion(-) diff --git a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java index 06d2104e..ec857221 100644 --- a/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java +++ b/graphql-spring-boot-test/src/main/java/com/graphql/spring/boot/test/GraphQLTestTemplate.java @@ -1,6 +1,8 @@ package com.graphql.spring.boot.test; +import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static java.util.Objects.requireNonNull; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,7 +14,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.function.IntFunction; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.Getter; import lombok.NonNull; import org.springframework.beans.factory.annotation.Value; @@ -32,6 +37,11 @@ /** Helper class to test GraphQL queries and mutations. */ public class GraphQLTestTemplate { + private static final Pattern GRAPHQL_OP_NAME_PATTERN = Pattern.compile( + "(query|mutation|subscription)\\s+([a-z0-9]+)\\s*[({]", + (Pattern.CASE_INSENSITIVE | Pattern.MULTILINE) + ); + private final ResourceLoader resourceLoader; private final TestRestTemplate restTemplate; private final String graphqlMapping; @@ -57,7 +67,9 @@ private String createJsonQuery(String graphql, String operation, ObjectNode vari if (nonNull(operation)) { wrapper.put("operationName", operation); } - wrapper.set("variables", variables); + if (nonNull(variables)) { + wrapper.set("variables", variables); + } return objectMapper.writeValueAsString(wrapper); } @@ -72,6 +84,16 @@ private String loadResource(Resource resource) throws IOException { } } + private String getOperationName(String graphql) { + if (isNull(graphql)) { + return null; + } + + Matcher matcher = GRAPHQL_OP_NAME_PATTERN.matcher(graphql); + + return (matcher.find() ? matcher.group(2) : null); + } + /** * Add an HTTP header that will be sent with each request this sends. * @@ -411,6 +433,95 @@ public GraphQLResponse postFiles( return postRequest(RequestFactory.forMultipart(values, headers)); } + /** + * Performs a GraphQL request using the provided GraphQL query string. + * + * Operation name will be derived from the provided GraphQL query string. + * + * @param graphql the GraphQL query + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql) throws IOException { + return postForString(graphql, getOperationName(graphql), ((ObjectNode) null)); + } + + /** + * Performs a GraphQL request using the provided GraphQL query string and operation name. + * + * @param graphql the GraphQL query + * @param operation the name of the GraphQL operation to be executed + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql, String operation) throws IOException { + return postForString(graphql, operation, ((ObjectNode) null)); + } + + /** + * Performs a GraphQL request using the provided GraphQL query string and variables. + * + * Operation name will be derived from the provided GraphQL query string. + * + * @param graphql the GraphQL query + * @param variables the input variables for the GraphQL query + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql, Map variables) throws IOException { + return postForString(graphql, getOperationName(graphql), variables); + } + + /** + * Performs a GraphQL request using the provided GraphQL query string, operation name, and + * variables. + * + * @param graphql the GraphQL query + * @param operation the name of the GraphQL operation to be executed + * @param variables the input variables for the GraphQL query + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql, String operation, Map variables) throws IOException { + return postForString(graphql, operation, ((ObjectNode) new ObjectMapper().valueToTree(variables))); + } + + /** + * Performs a GraphQL request using the provided GraphQL query string and variables. + * + * Operation name will be derived from the provided GraphQL query string. + * + * @param graphql the GraphQL query + * @param variables the input variables for the GraphQL query + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql, ObjectNode variables) throws IOException { + return post(createJsonQuery(graphql, getOperationName(graphql), variables)); + } + + /** + * Performs a GraphQL request using the provided GraphQL query string, operation name, and + * variables. + * + * @param graphql the GraphQL query + * @param operation the name of the GraphQL operation to be executed + * @param variables the input variables for the GraphQL query + * @return {@link GraphQLResponse} containing the result of the query execution + * @throws IOException if the request json cannot be created because of issues with one of the + * provided arguments + */ + public GraphQLResponse postForString(String graphql, String operation, ObjectNode variables) throws IOException { + requireNonNull(graphql, "GraphQL query string cannot be null"); + + return post(createJsonQuery(graphql, operation, variables)); + } + /** * Performs a GraphQL request with the provided payload. * diff --git a/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLTestTemplateIntegrationTest.java b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLTestTemplateIntegrationTest.java index ddc381ca..8dab4f99 100644 --- a/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLTestTemplateIntegrationTest.java +++ b/graphql-spring-boot-test/src/test/java/com/graphql/spring/boot/test/GraphQLTestTemplateIntegrationTest.java @@ -1,5 +1,7 @@ package com.graphql.spring.boot.test; +import static org.junit.jupiter.api.Assertions.assertThrows; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -8,7 +10,9 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; @@ -274,4 +278,194 @@ void testPerformWithIndividualFileUpload() throws IOException { .asString() .isEqualTo(fileNames.get(0)); } + + @Test + @DisplayName("Test postForString without operation name and without variables.") + void testPostString() throws IOException { + // GIVEN + final String graphql = "query {\n" + + " otherQuery\n" + + "}"; + // WHEN - THEN + graphQLTestTemplate + .postForString(graphql) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_OTHER_QUERY) + .asString() + .isEqualTo(TEST); + } + + @Test + @DisplayName("Test postForString with embedded operation name and without variables.") + void testPostStringWithEmbeddedOperationName() throws IOException { + // GIVEN + final String graphql = "query TestOperationYo {\n" + + " otherQuery\n" + + "}"; + // WHEN - THEN + graphQLTestTemplate + .postForString(graphql) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_OTHER_QUERY) + .asString() + .isEqualTo(TEST); + } + + @Test + @DisplayName("Test postForString with explicit operation name and without variables.") + void testPostStringWithExplicitOperationName() throws IOException { + // GIVEN + final String graphql = "query TestOperationYo {\n" + + " otherQuery\n" + + "}"; + // WHEN - THEN + graphQLTestTemplate + .postForString(graphql, "TestOperationYo") + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_OTHER_QUERY) + .asString() + .isEqualTo(TEST); + } + + @Test + @DisplayName("Test postForString with fragments.") + void testPostForStringWithFragments() throws IOException { + graphQLTestTemplate + .postForString( + "query($foo: String, $bar: String) {\n" + + " fooBar(foo: $foo, bar: $bar) {\n" + + " ...FooBarFragment\n" + + " }\n" + + "}" + + "fragment FooBarFragment on FooBar {\n" + + " foo\n" + + " bar\n" + + "}" + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_FOO_BAR) + .as(FooBar.class) + .usingRecursiveComparison() + .ignoringAllOverriddenEquals() + .isEqualTo(FooBar.builder().foo(FOO).bar(BAR).build()); + } + + @Test + @DisplayName("Test postForString with ObjectNode variables.") + void testPostForStringWithObjectNodeVariables() throws IOException { + // GIVEN + final ObjectNode variables = objectMapper.createObjectNode(); + variables.put(INPUT_STRING_NAME, INPUT_STRING_VALUE); + + // WHEN - THEN + graphQLTestTemplate + .postForString( + "query ($input: String!) {" + + " queryWithVariables(input: $input)" + + "}", + variables + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_QUERY_WITH_VARIABLES) + .asString() + .isEqualTo(INPUT_STRING_VALUE); + } + + @Test + @DisplayName("Test postForString with Map variables.") + void testPostForStringWithMapVariables() throws IOException { + // GIVEN + final Map variables = new LinkedHashMap<>(); + variables.put(INPUT_STRING_NAME, INPUT_STRING_VALUE); + + // WHEN - THEN + graphQLTestTemplate + .postForString( + "query ($input: String!) {" + + " queryWithVariables(input: $input)" + + "}", + variables + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_QUERY_WITH_VARIABLES) + .asString() + .isEqualTo(INPUT_STRING_VALUE); + } + + @Test + @DisplayName("Test postForString with embedded operation name and variables.") + void testPostForStringWithEmbeddedOperationNameAndVariables() throws IOException { + // GIVEN + final ObjectNode variables = objectMapper.createObjectNode(); + variables.put(INPUT_STRING_NAME, INPUT_STRING_VALUE); + + // WHEN - THEN + graphQLTestTemplate + .postForString( + "query TestOperationYo ($input: String!) {" + + " queryWithVariables(input: $input)" + + "}", + variables + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_QUERY_WITH_VARIABLES) + .asString() + .isEqualTo(INPUT_STRING_VALUE); + } + + @Test + @DisplayName("Test postForString with explicit operation name and ObjectNode variables.") + void testPostForStringWithExplicitOperationNameAndObjectNodeVariables() throws IOException { + // GIVEN + final ObjectNode variables = objectMapper.createObjectNode(); + variables.put(INPUT_STRING_NAME, INPUT_STRING_VALUE); + + // WHEN - THEN + graphQLTestTemplate + .postForString( + "query TestOperationYo ($input: String!) {" + + " queryWithVariables(input: $input)" + + "}", + "TestOperationYo", + variables + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_QUERY_WITH_VARIABLES) + .asString() + .isEqualTo(INPUT_STRING_VALUE); + } + + @Test + @DisplayName("Test postForString with explicit operation name and Map variables.") + void testPostForStringWithExplicitOperationNameAndMapVariables() throws IOException { + // GIVEN + final Map variables = new LinkedHashMap<>(); + variables.put(INPUT_STRING_NAME, INPUT_STRING_VALUE); + + // WHEN - THEN + graphQLTestTemplate + .postForString( + "query TestOperationYo ($input: String!) {" + + " queryWithVariables(input: $input)" + + "}", + "TestOperationYo", + variables + ) + .assertThatNoErrorsArePresent() + .assertThatField(DATA_FIELD_QUERY_WITH_VARIABLES) + .asString() + .isEqualTo(INPUT_STRING_VALUE); + } + + @Test + @DisplayName("Test postForString with null string.") + void testPostStringWithNullString() { + // GIVEN + final String graphql = null; + + // WHEN - THEN + assertThrows(NullPointerException.class, () -> { + graphQLTestTemplate.postForString(graphql); + }); + } }