From 5191a981ace782b2a2243929f4d15ff49eeadbec Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Mon, 27 Jul 2020 14:59:08 +0200 Subject: [PATCH 1/2] #77 Upgrade GraphQL example to Knot.x 2.2.1 --- api-gateway/graphql-api/build.gradle.kts | 16 ++- .../graphql-api/buildSrc/build.gradle.kts | 6 +- api-gateway/graphql-api/gradle.properties | 4 +- .../gradle/javaAndUnitTests.gradle.kts | 54 ------- .../conf/routes/handlers/graphqlHandler.conf | 135 ++++++++++-------- .../modules/books/build.gradle.kts | 45 +++++- .../rx => }/GraphQLHandlerFactory.java | 4 +- .../action/ExposePayloadActionFactory.java | 7 +- .../example/books/data/TaskDataFetcher.java | 49 ++----- ....knotx.fragments.action.api.ActionFactory} | 0 ...x.server.api.handler.RoutingHandlerFactory | 2 +- .../books/src/main/resources/books.graphqls | 28 ++-- .../books/GraphQLHandlerFactoryTest.java | 131 +++++++++++++++++ .../modules/healthcheck/build.gradle.kts | 5 +- api-gateway/graphql-api/settings.gradle.kts | 14 ++ 15 files changed, 308 insertions(+), 192 deletions(-) delete mode 100644 api-gateway/graphql-api/gradle/javaAndUnitTests.gradle.kts rename api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/{handler/rx => }/GraphQLHandlerFactory.java (97%) rename api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/{io.knotx.fragments.handler.api.ActionFactory => io.knotx.fragments.action.api.ActionFactory} (100%) create mode 100644 api-gateway/graphql-api/modules/books/src/test/java/io/knotx/example/books/GraphQLHandlerFactoryTest.java diff --git a/api-gateway/graphql-api/build.gradle.kts b/api-gateway/graphql-api/build.gradle.kts index 8376f21..7bd2d30 100644 --- a/api-gateway/graphql-api/build.gradle.kts +++ b/api-gateway/graphql-api/build.gradle.kts @@ -19,8 +19,17 @@ plugins { id("java") } +repositories { + jcenter() + gradlePluginPortal() +} + dependencies { subprojects.forEach { "dist"(project(":${it.name}")) } + + testImplementation(group = "io.vertx", name = "vertx-core") + testImplementation(group = "io.rest-assured", name = "rest-assured", version = "3.3.0") + testImplementation(group = "com.graphql-java", name = "graphql-java", version = "6.0") } sourceSets.named("test") { @@ -32,10 +41,7 @@ allprojects { repositories { jcenter() - mavenLocal() maven { url = uri("https://plugins.gradle.org/m2/") } - maven { url = uri("https://oss.sonatype.org/content/groups/staging/") } - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } } } @@ -43,5 +49,5 @@ tasks.named("build") { dependsOn("runTest") } -apply(from = "gradle/javaAndUnitTests.gradle.kts") -apply(from = "https://raw.githubusercontent.com/Knotx/knotx-starter-kit/2.2.0/gradle/docker.gradle.kts") \ No newline at end of file +apply(from = "https://raw.githubusercontent.com/Knotx/knotx-starter-kit/${project.property("knotxVersion")}/gradle/docker.gradle.kts") +apply(from = "https://raw.githubusercontent.com/Knotx/knotx-starter-kit/${project.property("knotxVersion")}/gradle/javaAndUnitTests.gradle.kts") \ No newline at end of file diff --git a/api-gateway/graphql-api/buildSrc/build.gradle.kts b/api-gateway/graphql-api/buildSrc/build.gradle.kts index 61eee1e..4825142 100644 --- a/api-gateway/graphql-api/buildSrc/build.gradle.kts +++ b/api-gateway/graphql-api/buildSrc/build.gradle.kts @@ -1,9 +1,7 @@ repositories { - mavenLocal() - mavenCentral() + jcenter() gradlePluginPortal() } dependencies { implementation("com.bmuschko:gradle-docker-plugin:6.6.0") - implementation("io.knotx:knotx-gradle-plugins:0.1.2") -} \ No newline at end of file +} diff --git a/api-gateway/graphql-api/gradle.properties b/api-gateway/graphql-api/gradle.properties index 43766ff..d8ee4d4 100644 --- a/api-gateway/graphql-api/gradle.properties +++ b/api-gateway/graphql-api/gradle.properties @@ -1,6 +1,4 @@ version=2.0.0-SNAPSHOT -knotx.version=2.0.0 -knotxVersion=2.0.0 -knotx.conf=knotx +knotxVersion=2.2.1 knotxConf=knotx docker.image.name=knotx-example/graphql-api \ No newline at end of file diff --git a/api-gateway/graphql-api/gradle/javaAndUnitTests.gradle.kts b/api-gateway/graphql-api/gradle/javaAndUnitTests.gradle.kts deleted file mode 100644 index 36ae250..0000000 --- a/api-gateway/graphql-api/gradle/javaAndUnitTests.gradle.kts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2019 Knot.x Project - * - * 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 - * - * http://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. - */ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent - -allprojects { - plugins.withId("java") { - tasks.withType().configureEach { - with(options) { - sourceCompatibility = "1.8" - targetCompatibility = "1.8" - compilerArgs = listOf("-parameters") - encoding = "UTF-8" - } - } - - tasks.withType().configureEach { - systemProperties(Pair("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory")) - - failFast = true - useJUnitPlatform() - testLogging { - events = setOf(TestLogEvent.FAILED) - exceptionFormat = TestExceptionFormat.SHORT - } - - dependencies { - "testImplementation"(platform("io.knotx:knotx-dependencies:${project.property("knotx.version")}")) - "testImplementation"("org.junit.jupiter:junit-jupiter-api") - "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine") - "testImplementation"("io.vertx:vertx-core") - "testImplementation"(group = "io.rest-assured", name = "rest-assured", version = "3.3.0") - "testImplementation"(group = "com.graphql-java", name = "graphql-java", version = "6.0") - } - } - - tasks.withType().configureEach { - include("**/*Test*") - } - } -} \ No newline at end of file diff --git a/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf b/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf index 6d6be95..59185df 100644 --- a/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf +++ b/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf @@ -1,73 +1,84 @@ -schema = "books.graphqls" +schema = "/books.graphqls" -# Task is a graph of Actions, see more https://github.com/Knotx/knotx-fragments/tree/master/handler#task -tasks { - # fetch a list of books from Google Books API and expose their data to GraphQL fetcher - get-books { - action = getBooks - onTransitions { - _success { - action = exposeInPayload-getBooks +# task factories array order determines which factory creates a task for fragment +taskFactory { + # Task is a graph of Actions, see more https://github.com/Knotx/knotx-fragments/tree/master/handler#task + tasks { + # fetch a list of books from Google Books API and expose their data to GraphQL fetcher + get-books { + action = getBooks + onTransitions { + _success { + action = exposeInPayload-getBooks + } } } - } - # fetch a single book from Google Books API and expose its data to GraphQL fetcher - get-book { - action = getBook - onTransitions { - _success { - action = exposeInPayload-getBook + # fetch a single book from Google Books API and expose its data to GraphQL fetcher + get-book { + action = getBook + onTransitions { + _success { + action = exposeInPayload-getBook + } } } } -} + nodeFactories = [ + { + factory = action + # Actions transform Fragment, see more https://github.com/Knotx/knotx-fragments/blob/master/handler/README.md#actions + config.actions = { + getBooks { + factory = http + config { + endpointOptions { + # Google Books API with a list of books + path = "/books/v1/volumes?q={config.gql.match}" + domain = www.googleapis.com + port = 443 + allowedRequestHeaders = ["Content-Type"] + } + webClientOptions { + ssl = true + } + } + } + exposeInPayload-getBooks { + factory = expose-payload-data + config { + key = getBooks + exposeAs = fetchedData + } + } -# Actions transform Fragment, see more https://github.com/Knotx/knotx-fragments/blob/master/handler/README.md#actions -actions { - getBooks { - factory = http - config { - endpointOptions { - # Google Books API with a list of books - path = "/books/v1/volumes?q={config.gql.match}" - domain = www.googleapis.com - port = 443 - allowedRequestHeaders = ["Content-Type"] - } - webClientOptions { - ssl = true - } - } - } - exposeInPayload-getBooks { - factory = expose-payload-data - config { - key = getBooks - exposeAs = fetchedData - } - } + getBook { + factory = http + config { + endpointOptions { + # Google Books API with a single book + path = "/books/v1/volumes/{config.gql.id}" + domain = www.googleapis.com + port = 443 + allowedRequestHeaders = ["Content-Type"] + } + webClientOptions { + ssl = true + } + } + } + exposeInPayload-getBook { + factory = expose-payload-data + config { + key = getBook + exposeAs = fetchedData + } + } - getBook { - factory = http - config { - endpointOptions { - # Google Books API with a single book - path = "/books/v1/volumes/{config.gql.id}" - domain = www.googleapis.com - port = 443 - allowedRequestHeaders = ["Content-Type"] } - webClientOptions { - ssl = true - } - } - } - exposeInPayload-getBook { - factory = expose-payload-data - config { - key = getBook - exposeAs = fetchedData } - } -} \ No newline at end of file + ] +} + + + diff --git a/api-gateway/graphql-api/modules/books/build.gradle.kts b/api-gateway/graphql-api/modules/books/build.gradle.kts index fd3a9ff..c3bf377 100644 --- a/api-gateway/graphql-api/modules/books/build.gradle.kts +++ b/api-gateway/graphql-api/modules/books/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - `java-library` + id("io.knotx.java-library") } dependencies { @@ -8,12 +8,15 @@ dependencies { implementation(group = "org.apache.commons", name = "commons-lang3") "io.knotx:knotx".let { v -> - implementation(platform("$v-dependencies:${project.property("knotx.version")}")) - implementation("$v-server-http-api:${project.property("knotx.version")}") - implementation("$v-fragments-engine:${project.property("knotx.version")}") - implementation("$v-fragments-api:${project.property("knotx.version")}") - implementation("$v-fragments-handler-api:${project.property("knotx.version")}") - implementation("$v-fragments-handler-core:${project.property("knotx.version")}") + implementation(platform("$v-dependencies:${project.property("knotxVersion")}")) + implementation("$v-server-http-api:${project.property("knotxVersion")}") + implementation("$v-fragments-action-api:${project.property("knotxVersion")}") + implementation("$v-fragments-action-library:${project.property("knotxVersion")}") + implementation("$v-fragments-task-api:${project.property("knotxVersion")}") + implementation("$v-fragments-task-engine:${project.property("knotxVersion")}") + implementation("$v-fragments-task-factory-default:${project.property("knotxVersion")}") + + testImplementation("$v-junit5:${project.property("knotxVersion")}") } "io.vertx:vertx".let { v -> implementation("$v-web-graphql") @@ -21,5 +24,33 @@ dependencies { implementation("$v-web-client") implementation("$v-rx-java2") implementation("$v-circuit-breaker") + + testImplementation("$v-unit") + testImplementation("$v-junit5") + } + + testImplementation(group = "org.mockito", name = "mockito-core", version = "3.4.4") + testImplementation(group = "org.mockito", name = "mockito-junit-jupiter", version = "3.4.4") +} + +sourceSets { + test { + resources.srcDir("../../knotx/conf") + } +} + +tasks.withType().configureEach { + systemProperties(Pair("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory")) + + failFast = true + useJUnitPlatform() + testLogging { + events = setOf(org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED) + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.SHORT + } + + dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } } diff --git a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/handler/rx/GraphQLHandlerFactory.java b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/GraphQLHandlerFactory.java similarity index 97% rename from api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/handler/rx/GraphQLHandlerFactory.java rename to api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/GraphQLHandlerFactory.java index 3f225bf..7dcd023 100644 --- a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/handler/rx/GraphQLHandlerFactory.java +++ b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/GraphQLHandlerFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.knotx.example.books.handler.rx; +package io.knotx.example.books; import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring; @@ -76,7 +76,7 @@ private GraphQL setupGraphQL(Vertx vertx, JsonObject config, RoutingContext rout } private Reader loadResource(String path) { - return new InputStreamReader(GraphQLHandlerFactory.class.getResourceAsStream("/" + path)); + return new InputStreamReader(GraphQLHandlerFactory.class.getResourceAsStream(path)); } } diff --git a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/action/ExposePayloadActionFactory.java b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/action/ExposePayloadActionFactory.java index 4ea2ee7..fba7d65 100644 --- a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/action/ExposePayloadActionFactory.java +++ b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/action/ExposePayloadActionFactory.java @@ -1,8 +1,9 @@ package io.knotx.example.books.action; -import io.knotx.fragments.handler.api.Action; -import io.knotx.fragments.handler.api.ActionFactory; -import io.knotx.fragments.handler.api.domain.FragmentResult; +import io.knotx.fragments.action.api.Action; +import io.knotx.fragments.action.api.ActionFactory; +import io.knotx.fragments.api.FragmentResult; + import io.reactivex.Single; import io.vertx.core.AsyncResult; import io.vertx.core.Future; diff --git a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/data/TaskDataFetcher.java b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/data/TaskDataFetcher.java index b96678d..3b10d76 100644 --- a/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/data/TaskDataFetcher.java +++ b/api-gateway/graphql-api/modules/books/src/main/java/io/knotx/example/books/data/TaskDataFetcher.java @@ -3,27 +3,22 @@ import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import io.knotx.fragments.api.Fragment; -import io.knotx.fragments.engine.FragmentEvent; -import io.knotx.fragments.engine.FragmentEventContext; -import io.knotx.fragments.engine.FragmentEventContextTaskAware; -import io.knotx.fragments.engine.FragmentsEngine; -import io.knotx.fragments.engine.Task; -import io.knotx.fragments.handler.action.ActionProvider; -import io.knotx.fragments.handler.api.ActionFactory; -import io.knotx.fragments.handler.options.FragmentsHandlerOptions; -import io.knotx.fragments.task.TaskBuilder; +import io.knotx.fragments.task.api.Task; +import io.knotx.fragments.task.engine.FragmentEvent; +import io.knotx.fragments.task.engine.FragmentEventContext; +import io.knotx.fragments.task.engine.FragmentEventContextTaskAware; +import io.knotx.fragments.task.engine.FragmentsEngine; +import io.knotx.fragments.task.factory.api.TaskFactory; +import io.knotx.fragments.task.factory.api.metadata.TaskWithMetadata; +import io.knotx.fragments.task.factory.generic.DefaultTaskFactory; import io.knotx.server.api.context.ClientRequest; import io.knotx.server.api.context.RequestContext; import io.vertx.core.json.JsonObject; -import io.vertx.core.logging.Logger; -import io.vertx.core.logging.LoggerFactory; import io.vertx.reactivex.core.Vertx; import io.vertx.reactivex.ext.web.RoutingContext; import java.util.Collections; -import java.util.Iterator; -import java.util.ServiceLoader; + import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; /** * {@link DataFetcher} that executes {@link Task} to fetch data for {@link graphql.GraphQL}. @@ -40,7 +35,7 @@ public abstract class TaskDataFetcher implements DataFetcher implements DataFetcher new IllegalStateException("No task built from fragment:\n" + fragment.toString())); + TaskWithMetadata task = taskFactory.newInstance(fragment, clientRequest); - return new FragmentEventContextTaskAware(task, eventContext); + return new FragmentEventContextTaskAware(task.getTask(), eventContext); } private Fragment createFragment(DataFetchingEnvironment env) { JsonObject fragmentConfig = new JsonObject(); - fragmentConfig.put(FRAGMENT_TYPE, taskName); + fragmentConfig.put("data-knotx-task", taskName); fragmentConfig.put("gql", new JsonObject(env.getArguments())); return new Fragment(FRAGMENT_TYPE, fragmentConfig, ""); } - private TaskBuilder initTaskBuilder(JsonObject config, Vertx vertx) { - FragmentsHandlerOptions options = new FragmentsHandlerOptions(config); - ActionProvider proxyProvider = new ActionProvider(options.getActions(), supplyFactories(), vertx.getDelegate()); - return new TaskBuilder(FRAGMENT_TYPE, options.getTasks(), proxyProvider); - } - - private Supplier> supplyFactories() { - return () -> { - ServiceLoader factories = ServiceLoader - .load(ActionFactory.class); - return factories.iterator(); - }; - } - abstract T getDataObjectFromJson(JsonObject json, DataFetchingEnvironment environment) throws IllegalAccessException, InstantiationException; } diff --git a/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.fragments.handler.api.ActionFactory b/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.fragments.action.api.ActionFactory similarity index 100% rename from api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.fragments.handler.api.ActionFactory rename to api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.fragments.action.api.ActionFactory diff --git a/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory b/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory index 3c1134b..802859b 100644 --- a/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory +++ b/api-gateway/graphql-api/modules/books/src/main/resources/META-INF/services/io.knotx.server.api.handler.RoutingHandlerFactory @@ -1 +1 @@ -io.knotx.example.books.handler.rx.GraphQLHandlerFactory \ No newline at end of file +io.knotx.example.books.GraphQLHandlerFactory \ No newline at end of file diff --git a/api-gateway/graphql-api/modules/books/src/main/resources/books.graphqls b/api-gateway/graphql-api/modules/books/src/main/resources/books.graphqls index b28ece0..1f665c1 100644 --- a/api-gateway/graphql-api/modules/books/src/main/resources/books.graphqls +++ b/api-gateway/graphql-api/modules/books/src/main/resources/books.graphqls @@ -1,14 +1,14 @@ -schema { - query: QueryType -} - -type QueryType { - books(match: String): [Book] - book(id: String): Book -} - -type Book { - title: String! - publisher: String! - authors: [String] -} +schema { + query: QueryType +} + +type QueryType { + books(match: String): [Book] + book(id: String): Book +} + +type Book { + title: String! + publisher: String! + authors: [String] +} diff --git a/api-gateway/graphql-api/modules/books/src/test/java/io/knotx/example/books/GraphQLHandlerFactoryTest.java b/api-gateway/graphql-api/modules/books/src/test/java/io/knotx/example/books/GraphQLHandlerFactoryTest.java new file mode 100644 index 0000000..2a80d2d --- /dev/null +++ b/api-gateway/graphql-api/modules/books/src/test/java/io/knotx/example/books/GraphQLHandlerFactoryTest.java @@ -0,0 +1,131 @@ +package io.knotx.example.books; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.knotx.junit5.util.HoconLoader; +import io.knotx.server.api.context.ClientRequest; +import io.knotx.server.api.context.RequestContext; +import io.knotx.server.api.context.RequestEvent; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferImpl; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.impl.ParsableHeaderValuesContainer; +import io.vertx.ext.web.impl.ParsableMIMEValue; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import io.vertx.reactivex.core.Vertx; +import io.vertx.reactivex.ext.web.RoutingContext; +import java.net.URL; +import java.util.Collections; +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(VertxExtension.class) +class GraphQLHandlerFactoryTest { + + @Test + void expectBook(Vertx vertx, VertxTestContext testContext) throws Throwable { + GraphQLHandlerFactory factory = new GraphQLHandlerFactory(); + getConfig(conf -> { + // given + String query = "{\"query\": \"{" + + " book(id: \\\"q5NoDwAAQBAJ\\\") {" + + " title " + + " publisher " + + " authors " + + " }" + + "}\"}"; + Handler tested = factory.create(vertx, conf); + RoutingContext rxRoutingContext = mockRoutingContext(vertx, testContext, query); + + // when, then + tested.handle(rxRoutingContext); + }, vertx); + } + + @Test + void expectBooks(Vertx vertx, VertxTestContext testContext) throws Throwable { + GraphQLHandlerFactory factory = new GraphQLHandlerFactory(); + getConfig(conf -> { + // given + String query = "{\"query\": \"{" + + " books(match: \\\"graphql\\\") {" + + " title " + + " publisher " + + " authors " + + " }" + + "}\"}"; + Handler tested = factory.create(vertx, conf); + RoutingContext rxRoutingContext = mockRoutingContext(vertx, testContext, query); + + // when, then + tested.handle(rxRoutingContext); + }, vertx); + } + + private HttpServerResponse mockResponse(VertxTestContext testContext) { + HttpServerResponse response = mock(HttpServerResponse.class); + when(response.putHeader(eq(HttpHeaders.CONTENT_TYPE), eq("application/json"))) + .thenReturn(response); + doAnswer(invocation -> { + testContext.verify(() -> { + String responseContent = invocation.getArgument(0).toString(); + assertTrue(responseContent.contains("Learning GraphQL")); + }); + testContext.completeNow(); + return null; + }).when(response).end(any(Buffer.class)); + return response; + } + + private void getConfig(Consumer consumer, Vertx vertx) throws Throwable { + URL resource = GraphQLHandlerFactoryTest.class + .getResource("/routes/handlers/graphqlHandler.conf"); + HoconLoader.verify(resource.getPath(), consumer, vertx); + } + + private RoutingContext mockRoutingContext(Vertx vertx, VertxTestContext testContext, + String query) { + HttpServerRequest httpServerRequest = mock(HttpServerRequest.class); + when(httpServerRequest.method()).thenReturn(HttpMethod.POST); + RequestContext requestContext = getRequestContext(); + + io.vertx.ext.web.RoutingContext routingContext = mock(io.vertx.ext.web.RoutingContext.class); + when(routingContext.request()).thenReturn(httpServerRequest); + when(routingContext.getBody()).thenReturn(new BufferImpl().appendString(query)); + when(routingContext.get(eq(RequestContext.KEY))).thenReturn(requestContext); + when(routingContext.queryParams()).thenReturn(MultiMap.caseInsensitiveMultiMap()); + when(routingContext.parsedHeaders()).thenReturn(new ParsableHeaderValuesContainer( + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), + new ParsableMIMEValue("application/json"))); + when(routingContext.vertx()).thenReturn(vertx.getDelegate()); + HttpServerResponse responseMock = mockResponse(testContext); + when(routingContext.response()).thenReturn(responseMock); + doAnswer(invocation -> { + testContext.failNow(invocation.getArgument(0)); + return null; + }).when(routingContext).fail(any()); + + RoutingContext rxRoutingContext = mock(RoutingContext.class); + when(rxRoutingContext.getDelegate()).thenReturn(routingContext); + when(rxRoutingContext.get(eq(RequestContext.KEY))).thenReturn(requestContext); + return rxRoutingContext; + } + + private RequestContext getRequestContext() { + return new RequestContext(new RequestEvent(new ClientRequest(), new JsonObject())); + } +} \ No newline at end of file diff --git a/api-gateway/graphql-api/modules/healthcheck/build.gradle.kts b/api-gateway/graphql-api/modules/healthcheck/build.gradle.kts index 4d7f9cd..d0f1e28 100644 --- a/api-gateway/graphql-api/modules/healthcheck/build.gradle.kts +++ b/api-gateway/graphql-api/modules/healthcheck/build.gradle.kts @@ -4,10 +4,11 @@ plugins { dependencies { "io.knotx:knotx".let { v -> - implementation(platform("$v-dependencies:${project.property("knotx.version")}")) - implementation("$v-server-http-api:${project.property("knotx.version")}") + implementation(platform("$v-dependencies:${project.property("knotxVersion")}")) + implementation("$v-server-http-api:${project.property("knotxVersion")}") } "io.vertx:vertx".let { v -> + implementation("$v-core") implementation("$v-web") implementation("$v-web-client") implementation("$v-rx-java2") diff --git a/api-gateway/graphql-api/settings.gradle.kts b/api-gateway/graphql-api/settings.gradle.kts index 92ac9ea..9a4a846 100644 --- a/api-gateway/graphql-api/settings.gradle.kts +++ b/api-gateway/graphql-api/settings.gradle.kts @@ -13,6 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +pluginManagement { + val knotxVersion: String by settings + plugins { + id("io.knotx.distribution") version knotxVersion + id("io.knotx.java-library") version knotxVersion + id("io.knotx.unit-test") version knotxVersion + id("org.nosphere.apache.rat") version "0.6.0" + } + repositories { + jcenter() + gradlePluginPortal() + } +} + rootProject.name = "graphql-api" include("healthcheck") From bd97d83af68420360e25d2eb682c835eaeb8d927 Mon Sep 17 00:00:00 2001 From: Tomasz Michalak Date: Mon, 27 Jul 2020 15:45:02 +0200 Subject: [PATCH 2/2] #77 CR fixes. --- api-gateway/graphql-api/README.md | 16 ++++------------ .../conf/routes/handlers/graphqlHandler.conf | 12 ++++++++---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/api-gateway/graphql-api/README.md b/api-gateway/graphql-api/README.md index f958f88..028be9d 100644 --- a/api-gateway/graphql-api/README.md +++ b/api-gateway/graphql-api/README.md @@ -1,10 +1,8 @@ -[![][license img]][license] -[![][gitter img]][gitter] +# GraphQL Handler +This project provides an example implementation of using GraphQL with [Knot.x HTTP Server](https://github.com/Knotx/knotx-server-http). +It uses [Knot.x Fragments](https://github.com/Knotx/knotx-fragments) to deliver fault-tolerant +GraphQL fetchers logic. - -# GraphQL - -This project provides an example implementation of using GraphQL with Knotx. See the [Using GraphQL with Knot.x](http://knotx.io/tutorials/graphql-usage/2_0.html) tutorial for an in depth explanation. It was created with [Knot.x Starter Kit](https://github.com/Knotx/knotx-starter-kit). @@ -51,10 +49,4 @@ The same request in curl: curl -i -H 'Content-Type: application/json' -X POST -d '{"query": "{book(id: \"q5NoDwAAQBAJ\") {title publisher authors} books(match: \"java\") {title publisher authors}}"}' http://localhost:8092/api/graphql ``` -[license]:https://github.com/Cognifide/knotx/blob/master/LICENSE -[license img]:https://img.shields.io/badge/License-Apache%202.0-blue.svg - -[gitter]:https://gitter.im/Knotx/Lobby -[gitter img]:https://badges.gitter.im/Knotx/knotx-extensions.svg - diff --git a/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf b/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf index 59185df..90c1e06 100644 --- a/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf +++ b/api-gateway/graphql-api/knotx/conf/routes/handlers/graphqlHandler.conf @@ -1,8 +1,9 @@ +# GraphQL Schema schema = "/books.graphqls" -# task factories array order determines which factory creates a task for fragment +# task factory (https://github.com/Knotx/knotx-fragments/tree/master/task/factory/default) +# that is able to create a task for the fragment is used taskFactory { - # Task is a graph of Actions, see more https://github.com/Knotx/knotx-fragments/tree/master/handler#task tasks { # fetch a list of books from Google Books API and expose their data to GraphQL fetcher get-books { @@ -27,8 +28,9 @@ taskFactory { nodeFactories = [ { factory = action - # Actions transform Fragment, see more https://github.com/Knotx/knotx-fragments/blob/master/handler/README.md#actions + # pre-configured actions, https://github.com/Knotx/knotx-fragments/tree/master/action config.actions = { + # https://github.com/Knotx/knotx-fragments/tree/master/action/library#http-action getBooks { factory = http config { @@ -44,6 +46,7 @@ taskFactory { } } } + # custom implementation exposeInPayload-getBooks { factory = expose-payload-data config { @@ -52,6 +55,7 @@ taskFactory { } } + # https://github.com/Knotx/knotx-fragments/tree/master/action/library#http-action getBook { factory = http config { @@ -67,6 +71,7 @@ taskFactory { } } } + # custom implementation exposeInPayload-getBook { factory = expose-payload-data config { @@ -74,7 +79,6 @@ taskFactory { exposeAs = fetchedData } } - } } ]