Skip to content

Commit 07cbc0a

Browse files
authored
Load GraphQL schema via introspection (#616)
* load GraphQL schema via introspection * minor refactor * sneaking in a small timing data change * add graphql-groovy dep to webtau-groovy so it shows up in maven plugin * rename common sounding classes * use lower level http constructs to avoid reporter problems * licenses...
1 parent 92ca484 commit 07cbc0a

27 files changed

Lines changed: 423 additions & 88 deletions

File tree

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@
500500
<version>${jackson.version}</version>
501501
</dependency>
502502

503+
<dependency>
504+
<groupId>com.fasterxml.jackson.datatype</groupId>
505+
<artifactId>jackson-datatype-jdk8</artifactId>
506+
<version>${jackson.version}</version>
507+
</dependency>
508+
503509
<dependency>
504510
<groupId>com.google.guava</groupId>
505511
<artifactId>guava</artifactId>

webtau-config/src/main/java/org/testingisdocumenting/webtau/cfg/WebTauConfigHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/*
2+
* Copyright 2020 webtau maintainers
23
* Copyright 2019 TWO SIGMA OPEN SOURCE, LLC
34
*
45
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,6 +21,6 @@
2021

2122
public interface WebTauConfigHandler {
2223
default void onBeforeCreate(WebTauConfig cfg) {}
23-
default void onAfterCreate(WebTauConfig cfg) {};
24+
default void onAfterCreate(WebTauConfig cfg) {}
2425
default Stream<ConfigValue> additionalConfigValues() { return Stream.empty(); }
2526
}

webtau-core-groovy/src/main/groovy/org/testingisdocumenting/webtau/data/json/GStringJsonSerializationModuleProvider.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ package org.testingisdocumenting.webtau.data.json
1818

1919
import com.fasterxml.jackson.core.JsonGenerator
2020
import com.fasterxml.jackson.databind.JsonSerializer
21+
import com.fasterxml.jackson.databind.Module
2122
import com.fasterxml.jackson.databind.SerializerProvider
2223
import com.fasterxml.jackson.databind.module.SimpleModule
2324
import org.testingisdocumenting.webtau.utils.json.JsonSerializationModuleProvider
2425

2526
class GStringJsonSerializationModuleProvider implements JsonSerializationModuleProvider {
2627
@Override
27-
SimpleModule provide() {
28+
Module provide() {
2829
SimpleModule module = new SimpleModule()
2930
module.addSerializer(GString.class, new JsonSerializer<GString>() {
3031
@Override

webtau-feature-testing/examples/scenarios/graphql/webtau.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import scenarios.graphql.Report
22

33
url = "http://localhost:8180"
44

5-
graphQLSchema = "scenarios/graphql/schema.graphql"
5+
graphQLEnabled = true
66

77
reportGenerator = Report.&generateReport

webtau-graphql/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<artifactId>graphql-java</artifactId>
4040
</dependency>
4141

42+
<dependency>
43+
<groupId>com.google.guava</groupId>
44+
<artifactId>guava</artifactId>
45+
</dependency>
46+
4247
<dependency>
4348
<groupId>org.testingisdocumenting.webtau</groupId>
4449
<artifactId>webtau-core-groovy</artifactId>

webtau-graphql/src/main/java/org/testingisdocumenting/webtau/graphql/GraphQL.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@
1616

1717
package org.testingisdocumenting.webtau.graphql;
1818

19+
import org.testingisdocumenting.webtau.graphql.model.GraphQLRequest;
1920
import org.testingisdocumenting.webtau.http.HttpHeader;
20-
import org.testingisdocumenting.webtau.http.json.JsonRequestBody;
2121
import org.testingisdocumenting.webtau.http.validation.HttpResponseValidatorWithReturn;
2222

23-
import java.util.HashMap;
2423
import java.util.Map;
2524

2625
import static org.testingisdocumenting.webtau.Matchers.equal;
2726
import static org.testingisdocumenting.webtau.http.Http.http;
28-
import static org.testingisdocumenting.webtau.utils.CollectionUtils.notNullOrEmpty;
29-
import static org.testingisdocumenting.webtau.utils.StringUtils.notNullOrEmpty;
3027

3128
public class GraphQL {
3229
public static final GraphQL graphql = new GraphQL();
@@ -40,8 +37,12 @@ static GraphQLCoverage getCoverage() {
4037
return coverage;
4138
}
4239

40+
public static GraphQLSchema getSchema() {
41+
return schema;
42+
}
43+
4344
static void reset() {
44-
schema = new GraphQLSchema(GraphQLConfig.schemaFullPath());
45+
schema = new GraphQLSchema(GraphQLConfig.isEnabled());
4546
coverage = new GraphQLCoverage(schema);
4647
}
4748

@@ -54,19 +55,7 @@ public <E> E execute(String query, Map<String, Object> variables, HttpResponseVa
5455
}
5556

5657
public <E> E execute(String query, Map<String, Object> variables, String operationName, HttpHeader header, HttpResponseValidatorWithReturn validator) {
57-
Map<String, Object> request = new HashMap<>();
58-
request.put("query", query);
59-
60-
if (notNullOrEmpty(variables)) {
61-
request.put("variables", variables);
62-
}
63-
64-
if (notNullOrEmpty(operationName)) {
65-
request.put("operationName", operationName);
66-
}
67-
68-
// TODO make the url configurable
69-
return http.post("/graphql", header, new JsonRequestBody(request), (headerDataNode, body) -> {
58+
return http.post("/graphql", header, GraphQLRequest.body(query, variables, operationName), (headerDataNode, body) -> {
7059
headerDataNode.statusCode().should(equal(SUCCESS_CODE));
7160
return validator.validate(headerDataNode, body);
7261
});

webtau-graphql/src/main/java/org/testingisdocumenting/webtau/graphql/GraphQLConfig.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,20 @@
2525
import static org.testingisdocumenting.webtau.cfg.ConfigValue.declare;
2626

2727
public class GraphQLConfig implements WebTauConfigHandler {
28-
static final ConfigValue schemaPath = declare("graphQLSchema",
29-
"path to GraphQL schema against which to validate http calls", () -> "");
28+
static final ConfigValue graphQLEnabled = declare("graphQLEnabled",
29+
"enable graphQL coverage and timing capture", () -> false);
3030

31-
private static String fullPath;
32-
33-
static String schemaFullPath() {
34-
return fullPath;
31+
static boolean isEnabled() {
32+
return graphQLEnabled.getAsBoolean();
3533
}
3634

3735
@Override
3836
public void onAfterCreate(WebTauConfig cfg) {
39-
fullPath = schemaPath.getAsString().isEmpty() ? "" :
40-
cfg.getWorkingDir().resolve(schemaPath.getAsString()).toString();
4137
GraphQL.reset();
4238
}
4339

4440
@Override
4541
public Stream<ConfigValue> additionalConfigValues() {
46-
return Stream.of(schemaPath);
42+
return Stream.of(graphQLEnabled);
4743
}
4844
}

webtau-graphql/src/main/java/org/testingisdocumenting/webtau/graphql/GraphQLQuery.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class GraphQLQuery {
2424
private final String name;
2525
private final GraphQLQueryType type;
2626

27-
GraphQLQuery(String name, GraphQLQueryType type) {
27+
public GraphQLQuery(String name, GraphQLQueryType type) {
2828
this.name = name;
2929
this.type = type;
3030
}

webtau-graphql/src/main/java/org/testingisdocumenting/webtau/graphql/GraphQLReportDataProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public Stream<ReportCustomData> provide(WebTauTestList tests) {
8080
statistics.put("mean", summaryStatistics.getAverage());
8181
statistics.put("min", summaryStatistics.getMin());
8282
statistics.put("max", summaryStatistics.getMax());
83+
statistics.put("count", summaryStatistics.getCount());
8384

8485
double[] times = entry.getValue().stream().map(GraphQLCoveredQueries.Call::getElapsedTime).mapToDouble(Long::doubleValue).sorted().toArray();
8586
Percentile percentile = new Percentile();

webtau-graphql/src/main/java/org/testingisdocumenting/webtau/graphql/GraphQLSchema.java

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,69 +16,51 @@
1616

1717
package org.testingisdocumenting.webtau.graphql;
1818

19+
import com.google.common.base.Suppliers;
1920
import graphql.ExecutionInput;
2021
import graphql.ParseAndValidate;
2122
import graphql.ParseAndValidateResult;
2223
import graphql.language.Field;
2324
import graphql.language.OperationDefinition;
24-
import graphql.schema.GraphQLObjectType;
25-
import graphql.schema.idl.RuntimeWiring;
26-
import graphql.schema.idl.SchemaGenerator;
27-
import graphql.schema.idl.SchemaParser;
28-
import graphql.schema.idl.TypeDefinitionRegistry;
2925
import org.testingisdocumenting.webtau.http.request.HttpRequestBody;
30-
import org.testingisdocumenting.webtau.utils.FileUtils;
3126
import org.testingisdocumenting.webtau.utils.JsonUtils;
3227

33-
import java.nio.file.Path;
34-
import java.nio.file.Paths;
3528
import java.util.Collections;
36-
import java.util.HashSet;
3729
import java.util.List;
3830
import java.util.Map;
3931
import java.util.Optional;
4032
import java.util.Set;
41-
import java.util.function.BiConsumer;
33+
import java.util.function.Supplier;
4234
import java.util.stream.Collectors;
4335
import java.util.stream.Stream;
4436

4537
import static java.util.Collections.emptySet;
4638

4739
public class GraphQLSchema {
48-
private final boolean isSchemaDefined;
49-
private final Set<GraphQLQuery> schemaDeclaredQueries;
50-
51-
public GraphQLSchema(String schemaPathStr) {
52-
isSchemaDefined = !schemaPathStr.isEmpty();
53-
schemaDeclaredQueries = new HashSet<>();
54-
55-
if (isSchemaDefined) {
56-
SchemaParser schemaParser = new SchemaParser();
57-
Path schemaPath = Paths.get(schemaPathStr);
58-
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(FileUtils.fileTextContent(schemaPath));
59-
60-
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring().build();
61-
62-
SchemaGenerator schemaGenerator = new SchemaGenerator();
63-
graphql.schema.GraphQLSchema schema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
64-
65-
BiConsumer<Optional<GraphQLObjectType>, GraphQLQueryType> registerTypes = (objectType, queryType) ->
66-
objectType.ifPresent(type ->
67-
type.getFieldDefinitions().forEach(definition -> schemaDeclaredQueries.add(
68-
new GraphQLQuery(definition.getName(), queryType)
69-
)));
70-
registerTypes.accept(Optional.ofNullable(schema.getQueryType()), GraphQLQueryType.QUERY);
71-
registerTypes.accept(Optional.ofNullable(schema.getMutationType()), GraphQLQueryType.MUTATION);
72-
registerTypes.accept(Optional.ofNullable(schema.getSubscriptionType()), GraphQLQueryType.SUBSCRIPTION);
40+
private static final Supplier<Optional<Set<GraphQLQuery>>> NO_QUERIES_SUPPLIER = Optional::empty;
41+
42+
private final Supplier<Optional<Set<GraphQLQuery>>> schemaDeclaredQueriesSupplier;
43+
44+
public GraphQLSchema(boolean isEnabled) {
45+
if (!isEnabled) {
46+
this.schemaDeclaredQueriesSupplier = NO_QUERIES_SUPPLIER;
47+
return;
7348
}
49+
50+
this.schemaDeclaredQueriesSupplier = Suppliers.memoize(() ->
51+
Optional.of(GraphQLSchemaLoader.fetchSchemaDeclaredQueries()));
52+
}
53+
54+
public GraphQLSchema(Set<GraphQLQuery> schemaDeclaredQueries) {
55+
this.schemaDeclaredQueriesSupplier = () -> Optional.of(schemaDeclaredQueries);
7456
}
7557

7658
public boolean isSchemaDefined() {
77-
return isSchemaDefined;
59+
return schemaDeclaredQueriesSupplier.get().isPresent();
7860
}
7961

8062
public Stream<GraphQLQuery> getSchemaDeclaredQueries() {
81-
return schemaDeclaredQueries.stream();
63+
return schemaDeclaredQueriesSupplier.get().map(Set::stream).orElseGet(Stream::empty);
8264
}
8365

8466
public Set<GraphQLQuery> findQueries(HttpRequestBody requestBody) {
@@ -98,7 +80,9 @@ public Set<GraphQLQuery> findQueries(HttpRequestBody requestBody) {
9880

9981
List<OperationDefinition> operations = parsingResult.getDocument().getDefinitionsOfType(OperationDefinition.class);
10082
if (operationName != null) {
101-
List<OperationDefinition> matchingOperations = operations.stream().filter(operationDefinition -> operationName.equals(operationDefinition.getName())).collect(Collectors.toList());
83+
List<OperationDefinition> matchingOperations = operations.stream()
84+
.filter(operationDefinition -> operationName.equals(operationDefinition.getName()))
85+
.collect(Collectors.toList());
10286
if (matchingOperations.size() != 1) {
10387
// Either no matching operation or more than one, either way it's not valid GraphQL
10488
return emptySet();

0 commit comments

Comments
 (0)