diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 059043d702..7ebef551e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,6 @@ icu = "71.1" junit = "5.8.2" logback = "1.2.1" mockk = "1.12.5" -mustache = "0.9.10" rxjava = "3.1.5" wiremock = "2.33.2" @@ -97,7 +96,6 @@ ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = " ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" } ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } -mustache = { group = "com.github.spullara.mustache.java", name = "compiler", version.ref = "mustache" } reactor-core = { group = "io.projectreactor", name = "reactor-core", version.ref = "reactor-core" } reactor-extensions = { group = "io.projectreactor.kotlin", name = "reactor-kotlin-extensions", version.ref = "reactor-extensions" } reactor-test = { group = "io.projectreactor", name = "reactor-test", version.ref = "reactor-core" } diff --git a/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/main/kotlin/com/expediagroup/scalars/KtorGraphQLServer.kt b/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/main/kotlin/com/expediagroup/scalars/KtorGraphQLServer.kt index 7590c0800b..cde0c5efdf 100644 --- a/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/main/kotlin/com/expediagroup/scalars/KtorGraphQLServer.kt +++ b/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/main/kotlin/com/expediagroup/scalars/KtorGraphQLServer.kt @@ -2,7 +2,6 @@ package com.expediagroup.scalars import com.expediagroup.graphql.generator.SchemaGeneratorConfig import com.expediagroup.graphql.generator.TopLevelObject -import com.expediagroup.graphql.generator.scalars.ID import com.expediagroup.graphql.generator.scalars.IDValueUnboxer import com.expediagroup.graphql.generator.toSchema import com.expediagroup.graphql.server.execution.GraphQLContextFactory @@ -13,8 +12,6 @@ import com.expediagroup.graphql.server.types.GraphQLServerRequest import com.expediagroup.scalars.queries.ScalarQuery import com.fasterxml.jackson.databind.ObjectMapper import graphql.GraphQL -import graphql.execution.DefaultValueUnboxer -import graphql.execution.ValueUnboxer import io.ktor.server.request.ApplicationRequest import io.ktor.server.request.receiveText import java.io.IOException diff --git a/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/test/kotlin/com/expediagroup/scalars/CustomScalarKotlinxTests.kt b/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/test/kotlin/com/expediagroup/scalars/CustomScalarKotlinxTests.kt index 3d611cb57b..5baebb8181 100644 --- a/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/test/kotlin/com/expediagroup/scalars/CustomScalarKotlinxTests.kt +++ b/integration/gradle-plugin-integration-tests/client-generator/custom-scalars-kotlinx/src/test/kotlin/com/expediagroup/scalars/CustomScalarKotlinxTests.kt @@ -31,11 +31,12 @@ class CustomScalarKotlinxTests { @Test fun `verify custom scalars are correctly serialized and deserialized`() { - val engine = embeddedServer(CIO, port = 8080, module = Application::graphQLModule) + val engine = embeddedServer(CIO, port = 0, module = Application::graphQLModule) try { engine.start() runBlocking { - val client = GraphQLKtorClient(url = URL("http://localhost:8080/graphql")) + val port = engine.resolvedConnectors().first().port + val client = GraphQLKtorClient(url = URL("http://localhost:$port/graphql")) val undefinedLocaleQuery = LocaleQuery(variables = LocaleQuery.Variables()) val undefinedLocaleResult = client.execute(undefinedLocaleQuery) @@ -56,11 +57,12 @@ class CustomScalarKotlinxTests { @Test fun `verify undefined optionals are correctly serialized and deserialized`() { - val engine = embeddedServer(CIO, port = 8080, module = Application::graphQLModule) + val engine = embeddedServer(CIO, port = 0, module = Application::graphQLModule) try { engine.start() runBlocking { - val client = GraphQLKtorClient(url = URL("http://localhost:8080/graphql")) + val port = engine.resolvedConnectors().first().port + val client = GraphQLKtorClient(url = URL("http://localhost:$port/graphql")) val undefinedWrapperQuery = OptionalScalarQuery(variables = OptionalScalarQuery.Variables()) val undefinedWrapperResult = client.execute(undefinedWrapperQuery).data?.optionalScalarQuery diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/build.gradle b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/build.gradle new file mode 100644 index 0000000000..c230acc528 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/build.gradle @@ -0,0 +1,45 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +plugins { + id 'com.expediagroup.graphql' + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + implementation "com.expediagroup:graphql-kotlin-spring-client" + implementation(libs.kotlin.stdlib) +} + +graphql { + client { + schemaFile = file("${project.projectDir}/schema.graphql") + packageName = "com.expediagroup.generated" + // optional configuration + allowDeprecatedFields = true + headers = ["X-Custom-Header": "My-Custom-Header-Value"] + queryFiles = [ + file("${project.projectDir}/src/main/resources/queries/HelloWorldQuery.graphql"), + file("${project.projectDir}/src/main/resources/queries/UpdateNameMutation.graphql") + ] + serializer = GraphQLSerializer.JACKSON + timeout { t -> + t.connect = 10000 + t.read = 30000 + } + } +} + +tasks.named("test", Test) { + dependsOn("graphqlGenerateClient") + + doLast { + // verify files were generated + if (!new File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/HelloWorldQuery.kt").exists()) { + throw new RuntimeException("failed to generate client for HelloWorldQuery") + } + if (!new File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/UpdateNameMutation.kt").exists()) { + throw new RuntimeException("failed to generate client for UpdateNameMutation") + } + } +} + diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/schema.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/schema.graphql new file mode 100644 index 0000000000..0c084129a5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/schema.graphql @@ -0,0 +1,7 @@ +type Query { + helloWorld(name: String): String! @deprecated(reason: "this is a test schema") +} + +type Mutation { + updateName(name: String!): String! +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/HelloWorldQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/HelloWorldQuery.graphql new file mode 100644 index 0000000000..e1f7ecac88 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/HelloWorldQuery.graphql @@ -0,0 +1,3 @@ +query HelloWorldQuery { + helloWorld +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/UpdateNameMutation.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/UpdateNameMutation.graphql new file mode 100644 index 0000000000..d9e1db9bcf --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-extension/src/main/resources/queries/UpdateNameMutation.graphql @@ -0,0 +1,3 @@ +mutation UpdateNameMutation($name: String!) { + updateName(name: $name) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-task/build.gradle b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/build.gradle new file mode 100644 index 0000000000..4f28b4f394 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/build.gradle @@ -0,0 +1,35 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer + +plugins { + id 'com.expediagroup.graphql' + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + implementation "com.expediagroup:graphql-kotlin-spring-client" + implementation(libs.kotlin.stdlib) +} + +graphqlGenerateClient { + packageName = "com.expediagroup.generated" + schemaFile = file("${project.projectDir}/schema.graphql") + // optional config + allowDeprecatedFields = true + serializer = GraphQLSerializer.JACKSON + queryFiles.from("${project.projectDir}/src/main/resources/queries/HelloWorldQuery.graphql", + "${project.projectDir}/src/main/resources/queries/UpdateNameMutation.graphql") +} + +tasks.named("test", Test) { + dependsOn("graphqlGenerateClient") + + doLast { + // verify files were generated + if (!new File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/HelloWorldQuery.kt").exists()) { + throw new RuntimeException("failed to generate client for HelloWorldQuery") + } + if (!new File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/UpdateNameMutation.kt").exists()) { + throw new RuntimeException("failed to generate client for UpdateNameMutation") + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-task/schema.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/schema.graphql new file mode 100644 index 0000000000..0c084129a5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/schema.graphql @@ -0,0 +1,7 @@ +type Query { + helloWorld(name: String): String! @deprecated(reason: "this is a test schema") +} + +type Mutation { + updateName(name: String!): String! +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/HelloWorldQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/HelloWorldQuery.graphql new file mode 100644 index 0000000000..e1f7ecac88 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/HelloWorldQuery.graphql @@ -0,0 +1,3 @@ +query HelloWorldQuery { + helloWorld +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/UpdateNameMutation.graphql b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/UpdateNameMutation.graphql new file mode 100644 index 0000000000..d9e1db9bcf --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/groovy-task/src/main/resources/queries/UpdateNameMutation.graphql @@ -0,0 +1,3 @@ +mutation UpdateNameMutation($name: String!) { + updateName(name: $name) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/build.gradle.kts new file mode 100644 index 0000000000..dacf11eca7 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/build.gradle.kts @@ -0,0 +1,43 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.graphql + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation(libs.kotlin.stdlib) +} + +graphql { + client { + schemaFile = file("${project.projectDir}/schema.graphql") + packageName = "com.expediagroup.generated" + // optional + allowDeprecatedFields = true + headers = mapOf("X-Custom-Header" to "My-Custom-Header-Value") + queryFiles = listOf( + file("${project.projectDir}/src/main/resources/queries/HelloWorldQuery.graphql"), + file("${project.projectDir}/src/main/resources/queries/UpdateNameMutation.graphql") + ) + } +} + +tasks { + named("test") { + dependsOn("graphqlGenerateClient") + + doLast { + // verify files were generated + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/HelloWorldQuery.kt").exists()) { + throw RuntimeException("failed to generate client for HelloWorldQuery") + } + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/UpdateNameMutation.kt").exists()) { + throw RuntimeException("failed to generate client for UpdateNameMutation") + } + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/schema.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/schema.graphql new file mode 100644 index 0000000000..0c084129a5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/schema.graphql @@ -0,0 +1,7 @@ +type Query { + helloWorld(name: String): String! @deprecated(reason: "this is a test schema") +} + +type Mutation { + updateName(name: String!): String! +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/HelloWorldQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/HelloWorldQuery.graphql new file mode 100644 index 0000000000..e1f7ecac88 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/HelloWorldQuery.graphql @@ -0,0 +1,3 @@ +query HelloWorldQuery { + helloWorld +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/UpdateNameMutation.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/UpdateNameMutation.graphql new file mode 100644 index 0000000000..d9e1db9bcf --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-extension/src/main/resources/queries/UpdateNameMutation.graphql @@ -0,0 +1,3 @@ +mutation UpdateNameMutation($name: String!) { + updateName(name: $name) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/build.gradle.kts new file mode 100644 index 0000000000..099ba010be --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/build.gradle.kts @@ -0,0 +1,40 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation(libs.kotlin.stdlib) +} + +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/schema.graphql")) + // optional config + allowDeprecatedFields.set(true) + queryFiles.from( + "${project.projectDir}/src/main/resources/queries/HelloWorldQuery.graphql", + "${project.projectDir}/src/main/resources/queries/UpdateNameMutation.graphql" + ) +} + +tasks { + named("test") { + dependsOn("graphqlGenerateClient") + + doLast { + // verify files were generated + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/HelloWorldQuery.kt").exists()) { + throw RuntimeException("failed to generate client for HelloWorldQuery") + } + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/UpdateNameMutation.kt").exists()) { + throw RuntimeException("failed to generate client for UpdateNameMutation") + } + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/schema.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/schema.graphql new file mode 100644 index 0000000000..0c084129a5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/schema.graphql @@ -0,0 +1,7 @@ +type Query { + helloWorld(name: String): String! @deprecated(reason: "this is a test schema") +} + +type Mutation { + updateName(name: String!): String! +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/HelloWorldQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/HelloWorldQuery.graphql new file mode 100644 index 0000000000..e1f7ecac88 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/HelloWorldQuery.graphql @@ -0,0 +1,3 @@ +query HelloWorldQuery { + helloWorld +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/UpdateNameMutation.graphql b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/UpdateNameMutation.graphql new file mode 100644 index 0000000000..d9e1db9bcf --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/kotlin-task/src/main/resources/queries/UpdateNameMutation.graphql @@ -0,0 +1,3 @@ +mutation UpdateNameMutation($name: String!) { + updateName(name: $name) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/build.gradle.kts new file mode 100644 index 0000000000..cd3c89e8e0 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/build.gradle.kts @@ -0,0 +1,43 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) + application +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-ktor-client") { + exclude("com.expediagroup", "graphql-kotlin-client-serialization") + } + implementation("com.expediagroup", "graphql-kotlin-client-jackson") + implementation("com.expediagroup:graphql-kotlin-server") + implementation(libs.icu) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.netty) + implementation(libs.logback) + testImplementation(libs.junit.api) + testImplementation(libs.junit.engine) + testImplementation(libs.kotlin.junit.test) + testImplementation(libs.ktor.server.test.host) +} + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.expediagroup.ktor.jackson")) +} +val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { + packageName.set("com.expediagroup.generated") + schemaFile.set(graphqlGenerateSDL.schemaFile) + serializer.set(GraphQLSerializer.JACKSON) + useOptionalInputWrapper.set(true) +} + +tasks { + test { + useJUnitPlatform() + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/Application.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/Application.kt new file mode 100644 index 0000000000..2eae7b669e --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/Application.kt @@ -0,0 +1,44 @@ +package com.expediagroup.ktor.jackson + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing + +fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) + +fun Application.graphQLModule() { + val jacksonObjectMapper: ObjectMapper = jacksonObjectMapper() + val ktorGraphQLServer: KtorGraphQLServer = KtorGraphQLServer(jacksonObjectMapper) + + install(Routing) + routing { + post("graphql") { + val result = ktorGraphQLServer.execute(call.request) + if (result != null) { + val json = jacksonObjectMapper.writeValueAsString(result) + call.response.call.respond(json) + } else { + call.response.call.respond(HttpStatusCode.BadRequest, "Invalid request") + } + } + get("playground") { + this.call.respondText(buildPlaygroundHtml("graphql", "subscriptions"), ContentType.Text.Html) + } + } +} + +private fun buildPlaygroundHtml(graphQLEndpoint: String, subscriptionsEndpoint: String) = + Application::class.java.classLoader.getResource("graphql-playground.html")?.readText() + ?.replace("\${graphQLEndpoint}", graphQLEndpoint) + ?.replace("\${subscriptionsEndpoint}", subscriptionsEndpoint) + ?: throw IllegalStateException("graphql-playground.html cannot be found in the classpath") diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/KtorGraphQLServer.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/KtorGraphQLServer.kt new file mode 100644 index 0000000000..6283993cad --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/KtorGraphQLServer.kt @@ -0,0 +1,53 @@ +package com.expediagroup.ktor.jackson + +import com.expediagroup.graphql.generator.SchemaGeneratorConfig +import com.expediagroup.graphql.generator.TopLevelObject +import com.expediagroup.graphql.generator.scalars.IDValueUnboxer +import com.expediagroup.graphql.generator.toSchema +import com.expediagroup.graphql.server.execution.GraphQLContextFactory +import com.expediagroup.graphql.server.execution.GraphQLRequestHandler +import com.expediagroup.graphql.server.execution.GraphQLRequestParser +import com.expediagroup.graphql.server.execution.GraphQLServer +import com.expediagroup.graphql.server.types.GraphQLServerRequest +import com.expediagroup.ktor.jackson.queries.HelloWorld +import com.expediagroup.ktor.jackson.queries.ObjectQuery +import com.fasterxml.jackson.databind.ObjectMapper +import graphql.GraphQL +import io.ktor.server.request.ApplicationRequest +import io.ktor.server.request.receiveText +import java.io.IOException + +class KtorGraphQLServer( + requestParser: GraphQLRequestParser, + contextFactory: GraphQLContextFactory, + requestHandler: GraphQLRequestHandler +) : GraphQLServer(requestParser, contextFactory, requestHandler) { + + companion object { + operator fun invoke(jacksonObjectMapper: ObjectMapper): KtorGraphQLServer { + val requestParser = object: GraphQLRequestParser { + override suspend fun parseRequest(request: ApplicationRequest): GraphQLServerRequest = try { + val rawRequest = request.call.receiveText() + jacksonObjectMapper.readValue(rawRequest, GraphQLServerRequest::class.java) + } catch (e: IOException) { + throw IOException("Unable to parse GraphQL payload.") + } + } + val contextFactory = object: GraphQLContextFactory {} + + val config = SchemaGeneratorConfig( + supportedPackages = listOf("com.expediagroup.ktor.jackson"), + ) + val graphQLSchema = toSchema(config, listOf( + TopLevelObject(HelloWorld()), + TopLevelObject(ObjectQuery()), + )) + val graphQL: GraphQL = GraphQL.newGraphQL(graphQLSchema) + .valueUnboxer(IDValueUnboxer()) + .build() + val requestHandler = GraphQLRequestHandler(graphQL) + + return KtorGraphQLServer(requestParser, contextFactory, requestHandler) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/HelloWorld.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/HelloWorld.kt new file mode 100644 index 0000000000..f4b9235c5a --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/HelloWorld.kt @@ -0,0 +1,11 @@ +package com.expediagroup.ktor.jackson.queries + +import com.expediagroup.graphql.server.operations.Query + +class HelloWorld : Query { + fun helloWorld(name: String? = null): String = if (name != null) { + "Hello $name!" + } else { + "Hello World!" + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/ObjectQuery.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/ObjectQuery.kt new file mode 100644 index 0000000000..61739221c0 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/kotlin/com/expediagroup/ktor/jackson/queries/ObjectQuery.kt @@ -0,0 +1,23 @@ +package com.expediagroup.ktor.jackson.queries + +import com.expediagroup.graphql.generator.scalars.ID +import com.expediagroup.graphql.server.operations.Query +import java.util.UUID + +class ObjectQuery : Query { + + fun objectQuery(): ExampleObject = ExampleObject( + id = ID(UUID.randomUUID().toString()), + description = null, + count = 123, + flag = false, + choice = ExampleEnum.ONE + ) +} + +data class ExampleObject(val id: ID, val description: String?, val count: Int, val flag: Boolean, val choice: ExampleEnum) + +enum class ExampleEnum { + ONE, + TWO +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/application.conf b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/application.conf new file mode 100644 index 0000000000..2f923bbeb4 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/application.conf @@ -0,0 +1,10 @@ +ktor { + deployment { + port = 8080 + } + application { + modules = [ + com.expediagroup.ktor.jackson.ApplicationKt.graphQLModule + ] + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/graphql-playground.html b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/graphql-playground.html new file mode 100644 index 0000000000..894ff66f6d --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/graphql-playground.html @@ -0,0 +1,60 @@ + + + + + + + GraphQL Playground + + + + + + +
+ + +
Loading + GraphQL Playground +
+
+ + + + diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/logback.xml b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/logback.xml new file mode 100644 index 0000000000..5e91b5a8c8 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/kotlin/com/expediagroup/ktor/jackson/ApplicationTest.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/kotlin/com/expediagroup/ktor/jackson/ApplicationTest.kt new file mode 100644 index 0000000000..7fd9056517 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/kotlin/com/expediagroup/ktor/jackson/ApplicationTest.kt @@ -0,0 +1,46 @@ +package com.expediagroup.ktor.jackson + +import com.expediagroup.generated.TestQuery +import com.expediagroup.generated.enums.ExampleEnum +import com.expediagroup.graphql.client.jackson.types.OptionalInput +import com.expediagroup.graphql.client.ktor.GraphQLKtorClient +import io.ktor.server.application.Application +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import java.net.URL +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ApplicationTest { + @Test + fun `verify ktor client can execute queries`() { + val engine = embeddedServer(CIO, port = 0, module = Application::graphQLModule) + try { + engine.start() + runBlocking { + val port = engine.resolvedConnectors().first().port + val client = GraphQLKtorClient(url = URL("http://localhost:$port/graphql")) + + val result = client.execute(TestQuery(variables = TestQuery.Variables(name = OptionalInput.Defined("junit")))) + + assertNotNull(result) + assertEquals("Hello World!", result.data?.helloWorld) + assertEquals("Hello junit!", result.data?.helloJunit) + + val testObject = result.data?.objectQuery + assertNotNull(testObject) + assertNotNull(testObject.id) + assertNull(testObject.description) + assertEquals(123, testObject.count) + assertFalse(testObject.flag) + assertEquals(ExampleEnum.ONE.name, testObject.choice.name) + } + } finally { + engine.stop(1000, 1000) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/resources/TestQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/resources/TestQuery.graphql new file mode 100644 index 0000000000..fbc5427de9 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-jackson/src/test/resources/TestQuery.graphql @@ -0,0 +1,11 @@ +query TestQuery($name: String) { + helloWorld + helloJunit : helloWorld(name: $name) + objectQuery { + id + description + count + flag + choice + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/build.gradle.kts new file mode 100644 index 0000000000..34b6e184b7 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/build.gradle.kts @@ -0,0 +1,40 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-ktor-client") + implementation("com.expediagroup:graphql-kotlin-server") + implementation(libs.icu) + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.cio) + implementation(libs.ktor.server.netty) + implementation(libs.logback) + testImplementation(libs.junit.api) + testImplementation(libs.junit.engine) + testImplementation(libs.kotlin.junit.test) + testImplementation(libs.ktor.server.test.host) +} + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.expediagroup.ktor.kotlinx")) +} +val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { + packageName.set("com.expediagroup.generated") + schemaFile.set(graphqlGenerateSDL.schemaFile) + serializer.set(GraphQLSerializer.KOTLINX) + useOptionalInputWrapper.set(true) +} + +tasks { + test { + useJUnitPlatform() + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/Application.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/Application.kt new file mode 100644 index 0000000000..87b3c536e6 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/Application.kt @@ -0,0 +1,44 @@ +package com.expediagroup.ktor.kotlinx + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.Routing +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing + +fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) + +fun Application.graphQLModule() { + val jacksonObjectMapper: ObjectMapper = jacksonObjectMapper() + val ktorGraphQLServer: KtorGraphQLServer = KtorGraphQLServer(jacksonObjectMapper) + + install(Routing) + routing { + post("graphql") { + val result = ktorGraphQLServer.execute(call.request) + if (result != null) { + val json = jacksonObjectMapper.writeValueAsString(result) + call.response.call.respond(json) + } else { + call.response.call.respond(HttpStatusCode.BadRequest, "Invalid request") + } + } + get("playground") { + this.call.respondText(buildPlaygroundHtml("graphql", "subscriptions"), ContentType.Text.Html) + } + } +} + +private fun buildPlaygroundHtml(graphQLEndpoint: String, subscriptionsEndpoint: String) = + Application::class.java.classLoader.getResource("graphql-playground.html")?.readText() + ?.replace("\${graphQLEndpoint}", graphQLEndpoint) + ?.replace("\${subscriptionsEndpoint}", subscriptionsEndpoint) + ?: throw IllegalStateException("graphql-playground.html cannot be found in the classpath") diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/KtorGraphQLServer.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/KtorGraphQLServer.kt new file mode 100644 index 0000000000..927f169423 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/KtorGraphQLServer.kt @@ -0,0 +1,53 @@ +package com.expediagroup.ktor.kotlinx + +import com.expediagroup.graphql.generator.SchemaGeneratorConfig +import com.expediagroup.graphql.generator.TopLevelObject +import com.expediagroup.graphql.generator.scalars.IDValueUnboxer +import com.expediagroup.graphql.generator.toSchema +import com.expediagroup.graphql.server.execution.GraphQLContextFactory +import com.expediagroup.graphql.server.execution.GraphQLRequestHandler +import com.expediagroup.graphql.server.execution.GraphQLRequestParser +import com.expediagroup.graphql.server.execution.GraphQLServer +import com.expediagroup.graphql.server.types.GraphQLServerRequest +import com.expediagroup.ktor.kotlinx.queries.HelloWorld +import com.expediagroup.ktor.kotlinx.queries.ObjectQuery +import com.fasterxml.jackson.databind.ObjectMapper +import graphql.GraphQL +import io.ktor.server.request.ApplicationRequest +import io.ktor.server.request.receiveText +import java.io.IOException + +class KtorGraphQLServer( + requestParser: GraphQLRequestParser, + contextFactory: GraphQLContextFactory, + requestHandler: GraphQLRequestHandler +) : GraphQLServer(requestParser, contextFactory, requestHandler) { + + companion object { + operator fun invoke(jacksonObjectMapper: ObjectMapper): KtorGraphQLServer { + val requestParser = object: GraphQLRequestParser { + override suspend fun parseRequest(request: ApplicationRequest): GraphQLServerRequest = try { + val rawRequest = request.call.receiveText() + jacksonObjectMapper.readValue(rawRequest, GraphQLServerRequest::class.java) + } catch (e: IOException) { + throw IOException("Unable to parse GraphQL payload.") + } + } + val contextFactory = object: GraphQLContextFactory {} + + val config = SchemaGeneratorConfig( + supportedPackages = listOf("com.expediagroup.ktor.kotlinx"), + ) + val graphQLSchema = toSchema(config, listOf( + TopLevelObject(HelloWorld()), + TopLevelObject(ObjectQuery()), + )) + val graphQL: GraphQL = GraphQL.newGraphQL(graphQLSchema) + .valueUnboxer(IDValueUnboxer()) + .build() + val requestHandler = GraphQLRequestHandler(graphQL) + + return KtorGraphQLServer(requestParser, contextFactory, requestHandler) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/HelloWorld.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/HelloWorld.kt new file mode 100644 index 0000000000..992c99e384 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/HelloWorld.kt @@ -0,0 +1,11 @@ +package com.expediagroup.ktor.kotlinx.queries + +import com.expediagroup.graphql.server.operations.Query + +class HelloWorld : Query { + fun helloWorld(name: String? = null): String = if (name != null) { + "Hello $name!" + } else { + "Hello World!" + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/ObjectQuery.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/ObjectQuery.kt new file mode 100644 index 0000000000..bdbc716cd5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/kotlin/com/expediagroup/ktor/kotlinx/queries/ObjectQuery.kt @@ -0,0 +1,23 @@ +package com.expediagroup.ktor.kotlinx.queries + +import com.expediagroup.graphql.generator.scalars.ID +import com.expediagroup.graphql.server.operations.Query +import java.util.UUID + +class ObjectQuery : Query { + + fun objectQuery(): ExampleObject = ExampleObject( + id = ID(UUID.randomUUID().toString()), + description = null, + count = 123, + flag = false, + choice = ExampleEnum.ONE + ) +} + +data class ExampleObject(val id: ID, val description: String?, val count: Int, val flag: Boolean, val choice: ExampleEnum) + +enum class ExampleEnum { + ONE, + TWO +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/application.conf b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/application.conf new file mode 100644 index 0000000000..bde1a738b3 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/application.conf @@ -0,0 +1,10 @@ +ktor { + deployment { + port = 8080 + } + application { + modules = [ + com.expediagroup.ktor.kotlinx.ApplicationKt.graphQLModule + ] + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/graphql-playground.html b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/graphql-playground.html new file mode 100644 index 0000000000..894ff66f6d --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/graphql-playground.html @@ -0,0 +1,60 @@ + + + + + + + GraphQL Playground + + + + + + +
+ + +
Loading + GraphQL Playground +
+
+ + + + diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/logback.xml b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/logback.xml new file mode 100644 index 0000000000..5e91b5a8c8 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/kotlin/com/expediagroup/ktor/kotlinx/ApplicationTest.kt b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/kotlin/com/expediagroup/ktor/kotlinx/ApplicationTest.kt new file mode 100644 index 0000000000..4910749fb8 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/kotlin/com/expediagroup/ktor/kotlinx/ApplicationTest.kt @@ -0,0 +1,46 @@ +package com.expediagroup.ktor.kotlinx + +import com.expediagroup.generated.TestQuery +import com.expediagroup.generated.enums.ExampleEnum +import com.expediagroup.graphql.client.ktor.GraphQLKtorClient +import com.expediagroup.graphql.client.serialization.types.OptionalInput +import io.ktor.server.application.Application +import io.ktor.server.cio.CIO +import io.ktor.server.engine.embeddedServer +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import java.net.URL +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class ApplicationTest { + @Test + fun `verify ktor client can execute queries`() { + val engine = embeddedServer(CIO, port = 0, module = Application::graphQLModule) + try { + engine.start() + runBlocking { + val port = engine.resolvedConnectors().first().port + val client = GraphQLKtorClient(url = URL("http://localhost:$port/graphql")) + + val result = client.execute(TestQuery(variables = TestQuery.Variables(name = OptionalInput.Defined("junit")))) + + assertNotNull(result) + assertEquals("Hello World!", result.data?.helloWorld) + assertEquals("Hello junit!", result.data?.helloJunit) + + val testObject = result.data?.objectQuery + assertNotNull(testObject) + assertNotNull(testObject.id) + assertNull(testObject.description) + assertEquals(123, testObject.count) + assertFalse(testObject.flag) + assertEquals(ExampleEnum.ONE.name, testObject.choice.name) + } + } finally { + engine.stop(1000, 1000) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/resources/TestQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/resources/TestQuery.graphql new file mode 100644 index 0000000000..fbc5427de9 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/ktor-kotlinx/src/test/resources/TestQuery.graphql @@ -0,0 +1,11 @@ +query TestQuery($name: String) { + helloWorld + helloJunit : helloWorld(name: $name) + objectQuery { + id + description + count + flag + choice + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/build.gradle.kts new file mode 100644 index 0000000000..bc1a81cb47 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/build.gradle.kts @@ -0,0 +1,46 @@ +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation(libs.kotlin.stdlib) +} + +tasks.create("generateFirst", GraphQLGenerateClientTask::class) { + packageName.set("com.expediagroup.generated.first") + schemaFile.set(file("${project.projectDir}/schema.graphql")) + // optional config + queryFiles.from( + "${project.projectDir}/src/main/resources/queries/HelloWorldQuery.graphql" + ) +} + +tasks.create("generateSecond", GraphQLGenerateClientTask::class) { + packageName.set("com.expediagroup.generated.second") + schemaFile.set(file("${project.projectDir}/schema.graphql")) + // optional config + queryFiles.from( + "${project.projectDir}/src/main/resources/queries/UpdateNameMutation.graphql" + ) +} + +tasks { + named("test") { + dependsOn("generateFirst", "generateSecond") + + doLast { + // verify files were generated + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/first/HelloWorldQuery.kt").exists()) { + throw RuntimeException("failed to generate client for HelloWorldQuery") + } + if (!File(project.buildDir, "generated/source/graphql/main/com/expediagroup/generated/second/UpdateNameMutation.kt").exists()) { + throw RuntimeException("failed to generate client for UpdateNameMutation") + } + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/schema.graphql b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/schema.graphql new file mode 100644 index 0000000000..f8af6cf5d9 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/schema.graphql @@ -0,0 +1,7 @@ +type Query { + helloWorld(name: String): String! +} + +type Mutation { + updateName(name: String!): String! +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/HelloWorldQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/HelloWorldQuery.graphql new file mode 100644 index 0000000000..e1f7ecac88 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/HelloWorldQuery.graphql @@ -0,0 +1,3 @@ +query HelloWorldQuery { + helloWorld +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/UpdateNameMutation.graphql b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/UpdateNameMutation.graphql new file mode 100644 index 0000000000..d9e1db9bcf --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/multiple-tasks/src/main/resources/queries/UpdateNameMutation.graphql @@ -0,0 +1,3 @@ +mutation UpdateNameMutation($name: String!) { + updateName(name: $name) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/build.gradle.kts new file mode 100644 index 0000000000..cd08cde2fa --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/build.gradle.kts @@ -0,0 +1,36 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.spring) + alias(libs.plugins.spring.boot) +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation("com.expediagroup:graphql-kotlin-spring-server") + testImplementation(libs.junit.api) + testImplementation(libs.junit.engine) + testImplementation(libs.kotlin.junit.test) + testImplementation(libs.spring.boot.test) +} + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.expediagroup.webclient")) +} +val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { + packageName.set("com.expediagroup.generated") + schemaFile.set(graphqlGenerateSDL.schemaFile) + serializer.set(GraphQLSerializer.JACKSON) + useOptionalInputWrapper.set(true) +} + +tasks { + test { + useJUnitPlatform() + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/Application.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/Application.kt new file mode 100644 index 0000000000..e4f9c01990 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/Application.kt @@ -0,0 +1,11 @@ +package com.expediagroup.webclient + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class Application + +fun main(args: Array) { + runApplication(*args) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt new file mode 100644 index 0000000000..f413d5421a --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt @@ -0,0 +1,13 @@ +package com.expediagroup.webclient + +import com.expediagroup.graphql.server.operations.Query +import org.springframework.stereotype.Component + +@Component +class HelloWorld : Query { + fun helloWorld(name: String? = null): String = if (name != null) { + "Hello $name!" + } else { + "Hello World!" + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt new file mode 100644 index 0000000000..2d1209f1f0 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt @@ -0,0 +1,25 @@ +package com.expediagroup.webclient + +import com.expediagroup.graphql.generator.scalars.ID +import com.expediagroup.graphql.server.operations.Query +import org.springframework.stereotype.Component +import java.util.UUID + +@Component +class ObjectQuery : Query { + + fun objectQuery(): ExampleObject = ExampleObject( + id = ID(UUID.randomUUID().toString()), + description = null, + count = 123, + flag = false, + choice = ExampleEnum.ONE + ) +} + +data class ExampleObject(val id: ID, val description: String?, val count: Int, val flag: Boolean, val choice: ExampleEnum) + +enum class ExampleEnum { + ONE, + TWO +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/resources/application.yml b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/resources/application.yml new file mode 100644 index 0000000000..08cafe9c13 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/main/resources/application.yml @@ -0,0 +1,3 @@ +graphql: + packages: + - "com.expediagroup.webclient" diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt new file mode 100644 index 0000000000..5ba60b2ac8 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt @@ -0,0 +1,39 @@ +package com.expediagroup.webclient + +import com.expediagroup.generated.TestQuery +import com.expediagroup.generated.enums.ExampleEnum +import com.expediagroup.graphql.client.jackson.types.OptionalInput +import com.expediagroup.graphql.client.spring.GraphQLWebClient +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.server.LocalServerPort +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ApplicationTest(@LocalServerPort val serverPort: Int) { + + @Test + fun `verify webclient can execute query`() { + val client = GraphQLWebClient(url = "http://localhost:$serverPort/graphql") + + runBlocking { + val result = client.execute(TestQuery(variables = TestQuery.Variables(name = OptionalInput.Defined("junit")))) + + assertNotNull(result) + assertEquals("Hello World!", result.data?.helloWorld) + assertEquals("Hello junit!", result.data?.helloJunit) + + val testObject = result.data?.objectQuery + assertNotNull(testObject) + assertNotNull(testObject.id) + assertNull(testObject.description) + assertEquals(123, testObject.count) + assertFalse(testObject.flag) + assertEquals(ExampleEnum.ONE.name, testObject.choice.name) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/resources/TestQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/resources/TestQuery.graphql new file mode 100644 index 0000000000..fbc5427de9 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-jackson/src/test/resources/TestQuery.graphql @@ -0,0 +1,11 @@ +query TestQuery($name: String) { + helloWorld + helloJunit : helloWorld(name: $name) + objectQuery { + id + description + count + flag + choice + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/build.gradle.kts b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/build.gradle.kts new file mode 100644 index 0000000000..3ed3f2786d --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/build.gradle.kts @@ -0,0 +1,41 @@ +import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed +plugins { + id("com.expediagroup.graphql") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) + // issue https://github.com/ExpediaGroup/graphql-kotlin/issues/1625 +// alias(libs.plugins.kotlin.spring) + alias(libs.plugins.spring.boot) +} + +dependencies { + implementation("com.expediagroup", "graphql-kotlin-spring-client") { + exclude("com.expediagroup", "graphql-kotlin-client-jackson") + } + implementation("com.expediagroup", "graphql-kotlin-client-serialization") + implementation("com.expediagroup", "graphql-kotlin-spring-server") + testImplementation(libs.junit.api) + testImplementation(libs.junit.engine) + testImplementation(libs.kotlin.junit.test) + testImplementation(libs.spring.boot.test) +} + +val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) { + packages.set(listOf("com.expediagroup.webclient")) +} +val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { + packageName.set("com.expediagroup.generated") + schemaFile.set(graphqlGenerateSDL.schemaFile) + serializer.set(GraphQLSerializer.KOTLINX) + useOptionalInputWrapper.set(true) +} + +tasks { + test { + useJUnitPlatform() + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/Application.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/Application.kt new file mode 100644 index 0000000000..30b2676fe5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/Application.kt @@ -0,0 +1,11 @@ +package com.expediagroup.webclient + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +open class Application + +fun main(args: Array) { + runApplication(*args) +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt new file mode 100644 index 0000000000..081a52aa93 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/HelloWorld.kt @@ -0,0 +1,13 @@ +package com.expediagroup.webclient + +import com.expediagroup.graphql.server.operations.Query +import org.springframework.stereotype.Component + +@Component +open class HelloWorld : Query { + fun helloWorld(name: String? = null): String = if (name != null) { + "Hello $name!" + } else { + "Hello World!" + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt new file mode 100644 index 0000000000..548008c8d2 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/kotlin/com/expediagroup/webclient/ObjectQuery.kt @@ -0,0 +1,25 @@ +package com.expediagroup.webclient + +import com.expediagroup.graphql.generator.scalars.ID +import com.expediagroup.graphql.server.operations.Query +import org.springframework.stereotype.Component +import java.util.UUID + +@Component +open class ObjectQuery : Query { + + fun objectQuery(): ExampleObject = ExampleObject( + id = ID(UUID.randomUUID().toString()), + description = null, + count = 123, + flag = false, + choice = ExampleEnum.ONE + ) +} + +data class ExampleObject(val id: ID, val description: String?, val count: Int, val flag: Boolean, val choice: ExampleEnum) + +enum class ExampleEnum { + ONE, + TWO +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/resources/application.yml b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/resources/application.yml new file mode 100644 index 0000000000..08cafe9c13 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/main/resources/application.yml @@ -0,0 +1,3 @@ +graphql: + packages: + - "com.expediagroup.webclient" diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt new file mode 100644 index 0000000000..41b63ff0b5 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/kotlin/com/expediagroup/webclient/ApplicationTest.kt @@ -0,0 +1,39 @@ +package com.expediagroup.webclient + +import com.expediagroup.generated.TestQuery +import com.expediagroup.generated.enums.ExampleEnum +import com.expediagroup.graphql.client.serialization.types.OptionalInput +import com.expediagroup.graphql.client.spring.GraphQLWebClient +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.web.server.LocalServerPort +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +open class ApplicationTest(@LocalServerPort val serverPort: Int) { + + @Test + fun `verify webclient can execute query`() { + val client = GraphQLWebClient(url = "http://localhost:$serverPort/graphql") + + runBlocking { + val result = client.execute(TestQuery(variables = TestQuery.Variables(name = OptionalInput.Defined("junit")))) + + assertNotNull(result) + assertEquals("Hello World!", result.data?.helloWorld) + assertEquals("Hello junit!", result.data?.helloJunit) + + val testObject = result.data?.objectQuery + assertNotNull(testObject) + assertNotNull(testObject.id) + assertNull(testObject.description) + assertEquals(123, testObject.count) + assertFalse(testObject.flag) + assertEquals(ExampleEnum.ONE.name, testObject.choice.name) + } + } +} diff --git a/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/resources/TestQuery.graphql b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/resources/TestQuery.graphql new file mode 100644 index 0000000000..fbc5427de9 --- /dev/null +++ b/integration/gradle-plugin-integration-tests/client-generator/webclient-kotlinx/src/test/resources/TestQuery.graphql @@ -0,0 +1,11 @@ +query TestQuery($name: String) { + helloWorld + helloJunit : helloWorld(name: $name) + objectQuery { + id + description + count + flag + choice + } +} diff --git a/integration/gradle-plugin-integration-tests/settings.gradle.kts b/integration/gradle-plugin-integration-tests/settings.gradle.kts index c23e5f9d17..f1ede890ee 100644 --- a/integration/gradle-plugin-integration-tests/settings.gradle.kts +++ b/integration/gradle-plugin-integration-tests/settings.gradle.kts @@ -12,19 +12,37 @@ dependencyResolutionManagement { includeBuild("../..") // client generator integration tests +include(":client-groovy-extension-it") +include(":client-groovy-task-it") +include(":client-kotlin-extension-it") +include(":client-kotlin-task-it") +include(":client-ktor-jackson-it") +include(":client-ktor-kotlinx-it") +include(":client-webclient-jackson-it") +include(":client-webclient-kotlinx-it") include(":client-custom-scalars-jackson-it") include(":client-custom-scalars-kotlinx-it") include(":client-jacoco-it") include(":client-polymorphic-types-jackson-it") include(":client-polymorphic-types-kotlinx-it") include(":client-skip-include-it") +include(":client-multiple-tasks-it") +project(":client-groovy-extension-it").projectDir = file("client-generator/groovy-extension") +project(":client-groovy-task-it").projectDir = file("client-generator/groovy-task") +project(":client-kotlin-extension-it").projectDir = file("client-generator/kotlin-extension") +project(":client-kotlin-task-it").projectDir = file("client-generator/kotlin-task") +project(":client-ktor-jackson-it").projectDir = file("client-generator/ktor-jackson") +project(":client-ktor-kotlinx-it").projectDir = file("client-generator/ktor-kotlinx") +project(":client-webclient-jackson-it").projectDir = file("client-generator/webclient-jackson") +project(":client-webclient-kotlinx-it").projectDir = file("client-generator/webclient-kotlinx") project(":client-custom-scalars-jackson-it").projectDir = file("client-generator/custom-scalars-jackson") project(":client-custom-scalars-kotlinx-it").projectDir = file("client-generator/custom-scalars-kotlinx") project(":client-jacoco-it").projectDir = file("client-generator/jacoco") project(":client-polymorphic-types-jackson-it").projectDir = file("client-generator/polymorphic-types-jackson") project(":client-polymorphic-types-kotlinx-it").projectDir = file("client-generator/polymorphic-types-kotlinx") project(":client-skip-include-it").projectDir = file("client-generator/skip-include") +project(":client-multiple-tasks-it").projectDir = file("client-generator/multiple-tasks") // download sdl task integration tests include(":download-sdl-kotlin-it") diff --git a/plugins/graphql-kotlin-gradle-plugin/build.gradle.kts b/plugins/graphql-kotlin-gradle-plugin/build.gradle.kts index 17643b3c7c..601a24ee6c 100644 --- a/plugins/graphql-kotlin-gradle-plugin/build.gradle.kts +++ b/plugins/graphql-kotlin-gradle-plugin/build.gradle.kts @@ -1,5 +1,3 @@ -import java.time.LocalDate - description = "Gradle Kotlin Gradle Plugin that can generate type-safe GraphQL Kotlin client and GraphQL schema in SDL format using reflections" @Suppress("DSL_SCOPE_VIOLATION") // TODO: remove once KTIJ-19369 / Gradle#22797 is fixed @@ -17,7 +15,6 @@ dependencies { compileOnly(projects.graphqlKotlinSdlGenerator) testImplementation(libs.wiremock.jre8) - testImplementation(libs.mustache) testImplementation(libs.junit.params) } @@ -76,27 +73,13 @@ tasks { compileKotlin { dependsOn(generateDefaultVersion) } + publishPlugins { doFirst { System.setProperty("gradle.publish.key", System.getenv("PLUGIN_PORTAL_KEY")) System.setProperty("gradle.publish.secret", System.getenv("PLUGIN_PORTAL_SECRET")) } } - test { - // ensure we always run tests by setting new inputs - // - // tests are parameterized and run IT based on projects under src/integration directories - // Gradle is unaware of this and does not run tests if no sources/inputs changed - inputs.property("integration.date", LocalDate.now()) - - maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).takeIf { it > 0 } ?: 1 - dependsOn(":resolveIntegrationTestDependencies") - - systemProperty("androidPluginVersion", libs.versions.android.plugin.get()) - systemProperty("kotlinVersion", libs.versions.kotlin.get()) - systemProperty("springBootVersion", libs.versions.spring.boot.get()) - systemProperty("junitVersion", libs.versions.junit.get()) - } publishing { afterEvaluate { diff --git a/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/build.gradle.kts b/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/build.gradle.kts new file mode 100644 index 0000000000..a5e1a432c8 --- /dev/null +++ b/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/build.gradle.kts @@ -0,0 +1,25 @@ +import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask + +plugins { + id("com.expediagroup.graphql") + kotlin("jvm") version "1.7.21" +} + +repositories { + mavenCentral() + mavenLocal { + content { + includeGroup("com.expediagroup") + } + } +} + +dependencies { + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.21") +} + +val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { + packageName.set("com.example.generated") + schemaFile.set(file("${project.projectDir}/schema.graphql")) +} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/schema.graphql b/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/schema.graphql new file mode 100644 index 0000000000..3c8b160178 --- /dev/null +++ b/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/schema.graphql @@ -0,0 +1,4 @@ +type Query { + "Deprecated query that should not be used anymore" + deprecatedQuery: String! @deprecated(reason : "old query should not be used") +} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/DeprecatedQuery.graphql b/plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/src/main/resources/queries/DeprecatedQuery.graphql similarity index 100% rename from plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/DeprecatedQuery.graphql rename to plugins/graphql-kotlin-gradle-plugin/src/integration/client-deprecated-type/src/main/resources/queries/DeprecatedQuery.graphql diff --git a/plugins/graphql-kotlin-gradle-plugin/src/integration/download-sdl-timeout/build.gradle.kts b/plugins/graphql-kotlin-gradle-plugin/src/integration/download-sdl-timeout/build.gradle.kts index d86c2c0f33..47fc35fcde 100644 --- a/plugins/graphql-kotlin-gradle-plugin/src/integration/download-sdl-timeout/build.gradle.kts +++ b/plugins/graphql-kotlin-gradle-plugin/src/integration/download-sdl-timeout/build.gradle.kts @@ -2,26 +2,26 @@ import com.expediagroup.graphql.plugin.gradle.config.TimeoutConfiguration import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask plugins { - kotlin("jvm") version "1.7.21" - id("com.expediagroup.graphql") + id("com.expediagroup.graphql") + kotlin("jvm") version "1.7.21" } repositories { - mavenCentral() - mavenLocal { - content { - includeGroup("com.expediagroup") + mavenCentral() + mavenLocal { + content { + includeGroup("com.expediagroup") + } } - } } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.21") - implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation("com.expediagroup:graphql-kotlin-spring-client") + implementation("org.jetbrains.kotlin:kotlin-stdlib:1.7.21") } val serverUrl = System.getenv("wireMockServerUrl") ?: System.getProperty("wireMockServerUrl") val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) { - endpoint.set("$serverUrl/sdl") - timeoutConfig.set(TimeoutConfiguration(connect = 100, read = 100)) + endpoint.set("$serverUrl/sdl") + timeoutConfig.set(TimeoutConfiguration(connect = 100, read = 100)) } diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginAbstractIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginAbstractIT.kt deleted file mode 100755 index bdaf695819..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginAbstractIT.kt +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2021 Expedia, Inc - * - * 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 com.expediagroup.graphql.plugin.gradle - -import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer -import com.github.mustachejava.DefaultMustacheFactory -import com.github.mustachejava.MustacheFactory -import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.MappingBuilder -import com.github.tomakehurst.wiremock.client.WireMock -import com.github.tomakehurst.wiremock.core.WireMockConfiguration -import com.github.tomakehurst.wiremock.matching.ContainsPattern -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.BeforeEach -import java.io.BufferedReader -import java.io.File -import java.io.StringWriter - -abstract class GraphQLGradlePluginAbstractIT { - - // unsure if there is a better way - correct values are set from Gradle build - // when running directly from IDE you will need to manually update those to correct values - private val kotlinVersion = System.getProperty("kotlinVersion") ?: "1.7.21" - private val junitVersion = System.getProperty("junitVersion") ?: "5.8.2" - private val springBootVersion = System.getProperty("springBootVersion") ?: "2.7.7" - - val testSchema = loadResource("mocks/schema.graphql") - val introspectionResult = loadResource("mocks/IntrospectionResult.json") - val testQuery = loadResource("mocks/JUnitQuery.graphql") - val testResponse = loadResource("mocks/JUnitQueryResponse.json") - - @BeforeEach - fun setUp() { - WireMock.reset() - WireMock.stubFor(stubSdlEndpoint()) - WireMock.stubFor(stubIntrospectionResult()) - WireMock.stubFor(stubGraphQLResponse()) - } - - fun stubSdlEndpoint(delay: Int? = null): MappingBuilder = WireMock.get("/sdl") - .withResponse(content = testSchema, contentType = "text/plain", delay = delay) - - fun stubIntrospectionResult(delay: Int? = null): MappingBuilder = WireMock.post("/graphql") - .withRequestBody(ContainsPattern("IntrospectionQuery")) - .withResponse(content = introspectionResult, delay = delay) - - fun stubGraphQLResponse(delay: Int? = null): MappingBuilder = WireMock.post("/graphql") - .withRequestBody(ContainsPattern("JUnitQuery")) - .withResponse(content = testResponse, delay = delay) - - private fun MappingBuilder.withResponse(content: String, contentType: String = "application/json", delay: Int? = null) = this.willReturn( - WireMock.aResponse() - .withStatus(200) - .withHeader("Content-Type", contentType) - .withBody(content) - .withFixedDelay(delay ?: 0) - ) - - fun loadResource(resourceName: String) = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)?.use { - BufferedReader(it.reader()).readText() - } ?: throw RuntimeException("unable to load $resourceName") - - fun loadTemplate(templateName: String, configuration: Map = emptyMap()): String { - val testApplicationMustache = mustacheFactory.compile("templates/$templateName.mustache") - return testApplicationMustache.execute(StringWriter(), configuration).toString() - } - - internal fun File.generateBuildFileForClient( - contents: String, - graphQLClientDependency: String = "implementation(\"com.expediagroup:graphql-kotlin-spring-client:$DEFAULT_PLUGIN_VERSION\")", - serializer: GraphQLSerializer = GraphQLSerializer.JACKSON - ) { - val kotlinxSerializerPlugin = if (serializer == GraphQLSerializer.KOTLINX) { - """kotlin("plugin.serialization") version "$kotlinVersion"""" - } else { - "" - } - - val plugins = - """ - |plugins { - | kotlin("jvm") version "$kotlinVersion" - | $kotlinxSerializerPlugin - | id("com.expediagroup.graphql") - | application - |} - """.trimMargin() - val dependencies = - """ - |dependencies { - | implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") - | $graphQLClientDependency - | testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") - | testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion") - |} - """.trimMargin() - this.generateBuildFile(plugins, dependencies, contents) - } - - internal fun File.generateBuildFileForServer(contents: String, additionalDependencies: String = "") { - val plugins = - """ - |plugins { - | kotlin("jvm") version "$kotlinVersion" - | kotlin("plugin.spring") version "$kotlinVersion" - | id("org.springframework.boot") version "$springBootVersion" - | id("com.expediagroup.graphql") - |} - """.trimMargin() - val dependencies = - """ - |dependencies { - | implementation("org.jetbrains.kotlin:kotlin-stdlib") - | implementation("com.expediagroup", "graphql-kotlin-spring-server", "$DEFAULT_PLUGIN_VERSION") - | implementation("com.expediagroup", "graphql-kotlin-hooks-provider", "$DEFAULT_PLUGIN_VERSION") - | $additionalDependencies - |} - """.trimMargin() - this.generateBuildFile(plugins, dependencies, contents) - } - - private fun File.generateBuildFile(plugins: String, dependencies: String, contents: String) { - val buildFileContents = - """ - |import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar - |import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer - |import com.expediagroup.graphql.plugin.gradle.config.TimeoutConfiguration - |import com.expediagroup.graphql.plugin.gradle.graphql - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask - | - |$plugins - | - |repositories { - | mavenCentral() - | mavenLocal { - | content { - | includeGroup("com.expediagroup") - | } - | } - |} - | - |$dependencies - | - |$contents - """.trimMargin() - - val buildFile = File(this, "build.gradle.kts") - buildFile.writeText(buildFileContents) - } - - internal fun File.generateGroovyBuildFileForClient( - contents: String, - graphQLClientDependency: String = "implementation \"com.expediagroup:graphql-kotlin-spring-client:$DEFAULT_PLUGIN_VERSION\"", - serializer: GraphQLSerializer = GraphQLSerializer.JACKSON - ) { - val kotlinxSerializerPlugin = if (serializer == GraphQLSerializer.KOTLINX) { - """id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlinVersion'""" - } else { - "" - } - - val plugins = - """ - |plugins { - | id 'org.jetbrains.kotlin.jvm' version '$kotlinVersion' - | $kotlinxSerializerPlugin - | id 'com.expediagroup.graphql' - | id 'application' - |} - """.trimMargin() - val dependencies = - """ - |dependencies { - | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - | $graphQLClientDependency - | testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" - | testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - |} - """.trimMargin() - return this.generateGroovyBuildFile(plugins, dependencies, contents) - } - - internal fun File.generateGroovyBuildFileForServer(contents: String, additionalDependencies: String = "") { - val plugins = - """ - |plugins { - | id 'org.jetbrains.kotlin.jvm' version '$kotlinVersion' - | id 'org.jetbrains.kotlin.plugin.spring' version '$kotlinVersion' - | id 'org.springframework.boot' version '$springBootVersion' - | id 'com.expediagroup.graphql' - | id 'application' - |} - """.trimMargin() - val dependencies = - """ - |dependencies { - | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" - | implementation "com.expediagroup:graphql-kotlin-spring-server:$DEFAULT_PLUGIN_VERSION" - | implementation "com.expediagroup:graphql-kotlin-hooks-provider:$DEFAULT_PLUGIN_VERSION" - | $additionalDependencies - |} - """.trimMargin() - return this.generateGroovyBuildFile(plugins, dependencies, contents) - } - - private fun File.generateGroovyBuildFile(plugins: String, dependencies: String, contents: String) { - val buildFileContents = - """ - |import com.expediagroup.graphql.plugin.gradle.config.GraphQLScalar - |import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer - |import com.expediagroup.graphql.plugin.gradle.config.TimeoutConfiguration - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateClientTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask - |import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask - | - |$plugins - | - |repositories { - | mavenCentral() - | mavenLocal { - | content { - | includeGroup "com.expediagroup" - | } - | } - |} - | - |compileKotlin { - | kotlinOptions.jvmTarget = "1.8" - |} - | - |$dependencies - | - |$contents - """.trimMargin() - - val buildFile = File(this, "build.gradle") - buildFile.writeText(buildFileContents) - } - - internal fun File.createTestFile(fileName: String, subDirectory: String? = null): File { - val targetDirectory = if (subDirectory != null) { - File(this, subDirectory) - } else { - this - } - targetDirectory.mkdirs() - return File(targetDirectory, fileName) - } - - companion object { - internal val wireMockServer: WireMockServer = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort()) - internal val mustacheFactory: MustacheFactory = DefaultMustacheFactory() - - @BeforeAll - @JvmStatic - fun oneTimeSetup() { - wireMockServer.start() - WireMock.configureFor(wireMockServer.port()) - } - - @AfterAll - @JvmStatic - fun oneTimeTearDown() { - wireMockServer.stop() - } - } -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginIT.kt deleted file mode 100755 index 77de2955c6..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginIT.kt +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright 2023 Expedia, Inc - * - * 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 com.expediagroup.graphql.plugin.gradle - -import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer -import com.expediagroup.graphql.plugin.gradle.tasks.DOWNLOAD_SDL_TASK_NAME -import com.expediagroup.graphql.plugin.gradle.tasks.GENERATE_CLIENT_TASK_NAME -import com.expediagroup.graphql.plugin.gradle.tasks.INTROSPECT_SCHEMA_TASK_NAME -import com.github.tomakehurst.wiremock.client.WireMock -import com.github.tomakehurst.wiremock.matching.EqualToPattern -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.jupiter.api.Tag -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.io.TempDir -import java.io.File -import java.nio.file.Path -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class GraphQLGradlePluginIT : GraphQLGradlePluginAbstractIT() { - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client with defaults (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | endpoint = "${wireMockServer.baseUrl()}/graphql" - | packageName = "com.example.generated" - | } - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources") - .writeText(testQuery) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to false))) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$INTROSPECT_SCHEMA_TASK_NAME")?.outcome) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/schema.graphql").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client with local schema file (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | schemaFile = file("${'$'}{project.projectDir}/src/main/resources/schema.graphql") - | packageName = "com.example.generated" - | } - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources") - .writeText(testQuery) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to false))) - testProjectDirectory.createTestFile("schema.graphql", "src/main/resources") - .writeText(testSchema) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - // custom header to pass to SDL endpoint - val customHeaderName = "X-Custom-Header" - val customHeaderValue = "My-Custom-Header-Value" - WireMock.reset() - WireMock.stubFor(stubSdlEndpoint().withHeader(customHeaderName, EqualToPattern(customHeaderValue))) - WireMock.stubFor(stubGraphQLResponse()) - - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | sdlEndpoint = "${wireMockServer.baseUrl()}/sdl" - | packageName = "com.example.generated" - | // optional - | allowDeprecatedFields = true - | headers = mapOf("$customHeaderName" to "$customHeaderValue") - | customScalars = listOf(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) - | queryFiles = listOf( - | file("${'$'}{project.projectDir}/src/main/resources/queries/JUnitQuery.graphql"), - | file("${'$'}{project.projectDir}/src/main/resources/queries/DeprecatedQuery.graphql") - | ) - | } - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to true))) - testProjectDirectory.createTestFile("UUIDScalarConverter.kt", "src/main/kotlin/com/example") - .writeText(loadResource("mocks/UUIDScalarConverter.txt")) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/schema.graphql").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/DeprecatedQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client using ktor and kotlinx serialization (kts)`(@TempDir tempDir: Path) { - verifyCustomizedClient(tempDir.toFile(), true, GraphQLSerializer.KOTLINX) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client using ktor and jackson (kts)`(@TempDir tempDir: Path) { - verifyCustomizedClient(tempDir.toFile(), true, GraphQLSerializer.JACKSON) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client using spring webclient and jackson (kts)`(@TempDir tempDir: Path) { - verifyCustomizedClient(tempDir.toFile(), false, GraphQLSerializer.JACKSON) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client using spring webclient and kotlinx serialization (kts)`(@TempDir tempDir: Path) { - verifyCustomizedClient(tempDir.toFile(), false, GraphQLSerializer.KOTLINX) - } - - private fun verifyCustomizedClient(testProjectDirectory: File, useKtorClient: Boolean = false, serializer: GraphQLSerializer = GraphQLSerializer.JACKSON) { - // default global header - val defaultHeaderName = "X-Default-Header" - val defaultHeaderValue = "default" - // custom header specified per request - val customHeaderName = "X-Custom-Header" - val customHeaderValue = "My-Custom-Header-Value" - WireMock.reset() - WireMock.stubFor(stubSdlEndpoint()) - WireMock.stubFor( - stubGraphQLResponse() - .withHeader(defaultHeaderName, EqualToPattern(defaultHeaderValue)) - .withHeader(customHeaderName, EqualToPattern(customHeaderValue)) - ) - - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | sdlEndpoint = "${wireMockServer.baseUrl()}/sdl" - | packageName = "com.example.generated" - | serializer = GraphQLSerializer.$serializer - | useOptionalInputWrapper = true - | } - |} - """.trimMargin() - val clientDependencies = when { - useKtorClient && serializer == GraphQLSerializer.KOTLINX -> { - "implementation(\"com.expediagroup:graphql-kotlin-ktor-client:$DEFAULT_PLUGIN_VERSION\")" - } - useKtorClient -> { - """ - |implementation("com.expediagroup", "graphql-kotlin-ktor-client", "$DEFAULT_PLUGIN_VERSION") { - | exclude("com.expediagroup", "graphql-kotlin-client-serialization") - |} - |implementation("com.expediagroup", "graphql-kotlin-client-jackson", "$DEFAULT_PLUGIN_VERSION") - """.trimMargin() - } - serializer == GraphQLSerializer.KOTLINX -> { - """ - |implementation("com.expediagroup", "graphql-kotlin-spring-client", "$DEFAULT_PLUGIN_VERSION") { - | exclude("com.expediagroup", "graphql-kotlin-client-jackson") - |} - |implementation("com.expediagroup", "graphql-kotlin-client-serialization", "$DEFAULT_PLUGIN_VERSION") - """.trimMargin() - } - else -> { - "implementation(\"com.expediagroup:graphql-kotlin-spring-client:$DEFAULT_PLUGIN_VERSION\")" - } - } - testProjectDirectory.generateBuildFileForClient(buildFileContents, clientDependencies, serializer) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources") - .writeText(testQuery) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText( - loadTemplate( - "Application", - mapOf( - "ktorClient" to useKtorClient, - "useJackson" to (serializer == GraphQLSerializer.JACKSON), - "defaultHeader" to mapOf("name" to defaultHeaderName, "value" to defaultHeaderValue), - "requestHeader" to mapOf("name" to customHeaderName, "value" to customHeaderValue), - "optionalInputWrapper" to true - ) - ) - ) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/schema.graphql").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } - - @Test - @Tag("groovy") - fun `apply the plugin extension to generate client (groovy)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - // custom header to pass to SDL endpoint - val customHeaderName = "X-Custom-Header" - val customHeaderValue = "My-Custom-Header-Value" - WireMock.reset() - WireMock.stubFor(stubSdlEndpoint().withHeader(customHeaderName, EqualToPattern(customHeaderValue))) - WireMock.stubFor(stubGraphQLResponse()) - - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = ["-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql"] - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | sdlEndpoint = "${wireMockServer.baseUrl()}/sdl" - | packageName = "com.example.generated" - | // optional configuration - | allowDeprecatedFields = true - | customScalars = [new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")] - | headers = ["X-Custom-Header" : "My-Custom-Header-Value"] - | queryFiles = [ - | file("$testProjectDirectory/src/main/resources/queries/JUnitQuery.graphql"), - | file("$testProjectDirectory/src/main/resources/queries/DeprecatedQuery.graphql") - | ] - | serializer = GraphQLSerializer.JACKSON - | timeout { t -> - | t.connect = 10000 - | t.read = 30000 - | } - | } - |} - """.trimMargin() - testProjectDirectory.generateGroovyBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to true))) - testProjectDirectory.createTestFile("UUIDScalarConverter.kt", "src/main/kotlin/com/example") - .writeText(loadResource("mocks/UUIDScalarConverter.txt")) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/schema.graphql").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/DeprecatedQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } - - @Test - @Tag("kts") - fun `apply the plugin extension to generate client using custom directory (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphql { - | client { - | sdlEndpoint = "${wireMockServer.baseUrl()}/sdl" - | packageName = "com.example.generated" - | - | // optional - | allowDeprecatedFields = true - | queryFileDirectory = "${'$'}{project.projectDir}/src/main/resources/queries" - | } - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to false))) - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/schema.graphql").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/DeprecatedQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":run")?.outcome) - } -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/WireMockAbstractIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/WireMockAbstractIT.kt new file mode 100755 index 0000000000..eab3d1e174 --- /dev/null +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/WireMockAbstractIT.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2023 Expedia, Inc + * + * 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 com.expediagroup.graphql.plugin.gradle + +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.MappingBuilder +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.core.WireMockConfiguration +import com.github.tomakehurst.wiremock.matching.ContainsPattern +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import java.io.BufferedReader + +abstract class WireMockAbstractIT { + + private val testSchema = loadResource("mocks/schema.graphql") + private val introspectionResult = loadResource("mocks/IntrospectionResult.json") + + @BeforeEach + fun setUp() { + WireMock.reset() + WireMock.stubFor(stubSdlEndpoint()) + WireMock.stubFor(stubIntrospectionResult()) + } + + fun stubSdlEndpoint(delay: Int? = null): MappingBuilder = WireMock.get("/sdl") + .withResponse(content = testSchema, contentType = "text/plain", delay = delay) + + fun stubIntrospectionResult(delay: Int? = null): MappingBuilder = WireMock.post("/graphql") + .withRequestBody(ContainsPattern("IntrospectionQuery")) + .withResponse(content = introspectionResult, delay = delay) + + fun stubGraphQLResponse(queryName: String, content: String, delay: Int? = null): MappingBuilder = WireMock.post("/graphql") + .withRequestBody(ContainsPattern(queryName)) + .withResponse(content = content, delay = delay) + + private fun MappingBuilder.withResponse(content: String, contentType: String = "application/json", delay: Int? = null) = this.willReturn( + WireMock.aResponse() + .withStatus(200) + .withHeader("Content-Type", contentType) + .withBody(content) + .withFixedDelay(delay ?: 0) + ) + + fun loadResource(resourceName: String) = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)?.use { + BufferedReader(it.reader()).readText() + } ?: throw RuntimeException("unable to load $resourceName") + + companion object { + internal val wireMockServer: WireMockServer = WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort()) + + @BeforeAll + @JvmStatic + fun oneTimeSetup() { + wireMockServer.start() + WireMock.configureFor(wireMockServer.port()) + } + + @AfterAll + @JvmStatic + fun oneTimeTearDown() { + wireMockServer.stop() + } + } +} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLDownloadSDLTaskIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLDownloadSDLTaskIT.kt index 094f3b1bbf..1514e22d3a 100755 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLDownloadSDLTaskIT.kt +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLDownloadSDLTaskIT.kt @@ -16,7 +16,7 @@ package com.expediagroup.graphql.plugin.gradle.tasks -import com.expediagroup.graphql.plugin.gradle.GraphQLGradlePluginAbstractIT +import com.expediagroup.graphql.plugin.gradle.WireMockAbstractIT import com.github.tomakehurst.wiremock.client.WireMock import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome @@ -30,7 +30,7 @@ import kotlin.test.assertTrue /** * Verifies failure scenarios only. Happy path scenarios are tested as part of integration/gradle-plugin-integration-tests composite build. */ -class GraphQLDownloadSDLTaskIT : GraphQLGradlePluginAbstractIT() { +class GraphQLDownloadSDLTaskIT : WireMockAbstractIT() { @Test fun `verify downloadSDL task with timeout`(@TempDir tempDir: Path) { diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLGenerateClientTaskIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLGenerateClientTaskIT.kt index d0d937c76e..0dd7370bba 100755 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLGenerateClientTaskIT.kt +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLGenerateClientTaskIT.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Expedia, Inc + * Copyright 2023 Expedia, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,79 +16,26 @@ package com.expediagroup.graphql.plugin.gradle.tasks -import com.expediagroup.graphql.plugin.gradle.GraphQLGradlePluginAbstractIT +import com.expediagroup.graphql.plugin.gradle.WireMockAbstractIT import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome -import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.io.File import java.nio.file.Path import kotlin.test.assertEquals import kotlin.test.assertFalse -import kotlin.test.assertTrue -class GraphQLGenerateClientTaskIT : GraphQLGradlePluginAbstractIT() { - - @Test - @Tag("kts") - fun `verify generateClient task with defaults (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - - /* - project setup - ---- - build.gradle.kts - schema.graphql - src - |- main - |- kotlin - |- com.example.Application.kt - |- resources - |- JUnitQuery.graphql - */ - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { - | packageName.set("com.example.generated") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources") - .writeText(testQuery) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to false))) - // end project setup - - verifyGenerateClientTaskSuccess(testProjectDirectory) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/GraphQLTypeAliases.kt").exists()) - } +/** + * Verifies failure scenarios only. Happy path scenarios are tested as part of integration/gradle-plugin-integration-tests composite build. + */ +class GraphQLGenerateClientTaskIT : WireMockAbstractIT() { @Test - @Tag("kts") fun `generateClient task should fail on deprecated queries (kts)`(@TempDir tempDir: Path) { val testProjectDirectory = tempDir.toFile() - val buildFileContents = - """ - |val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { - | packageName.set("com.example.generated") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - // end project setup + val sourceDirectory = File("src/integration/download-sdl-timeout") + sourceDirectory.copyRecursively(testProjectDirectory) val buildResult = GradleRunner.create() .withProjectDir(testProjectDirectory) @@ -99,227 +46,4 @@ class GraphQLGenerateClientTaskIT : GraphQLGradlePluginAbstractIT() { assertEquals(TaskOutcome.FAILED, buildResult.task(":$GENERATE_CLIENT_TASK_NAME")?.outcome) assertFalse(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/JUnitQuery.kt").exists()) } - - @Test - @Tag("kts") - fun `verify generateClient task (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - /* - project setup - ---- - build.gradle.kts - src - |- main - |- kotlin - |- com.example.Application.kt - |- com.example.UUIDScalarConverter.kt - |- resources - |- JUnitQuery.graphql - |- DeprecatedQuery.graphql - */ - val customOutputDirectory = "/custom/output" - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) { - | packageName.set("com.example.generated") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - | // optional config - | customScalars.add(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) - | allowDeprecatedFields.set(true) - | queryFiles.from( - | "${'$'}{project.projectDir}/src/main/resources/queries/JUnitQuery.graphql", - | "${'$'}{project.projectDir}/src/main/resources/queries/DeprecatedQuery.graphql" - | ) - | outputDirectory.set(File("${'$'}{project.layout.projectDirectory}/$customOutputDirectory")) - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to true))) - testProjectDirectory.createTestFile("UUIDScalarConverter.kt", "src/main/kotlin/com/example") - .writeText(loadResource("mocks/UUIDScalarConverter.txt")) - // end project setup - - verifyGenerateClientTaskSuccess(testProjectDirectory, outputDirectory = customOutputDirectory) - assertTrue(File(testProjectDirectory, "$customOutputDirectory/com/example/generated/DeprecatedQuery.kt").exists()) - } - - @Test - @Tag("kts") - fun `verify generateTestClient task (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - - /* - project setup - ---- - build.gradle.kts - schema.graphql - src - |- test - |- kotlin - |- com.example.GenerateClientTest.kt - |- resources - |- JUnitQuery.graphql - */ - val buildFileContents = - """ - |val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) { - | packageName.set("com.example.generated") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - |} - | - |tasks { - | test { - | systemProperty("graphQLEndpoint", "${wireMockServer.baseUrl()}/graphql") - | } - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/test/resources") - .writeText(testQuery) - testProjectDirectory.createTestFile("GenerateClientTest.kt", "src/test/kotlin/com/example") - .writeText(loadTemplate("JUnit", mapOf("customScalarsEnabled" to false))) - // end project setup - - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments(GENERATE_TEST_CLIENT_TASK_NAME, "test", "--stacktrace") - .forwardOutput() - .build() - - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":$GENERATE_TEST_CLIENT_TASK_NAME")?.outcome) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/test/com/example/generated/JUnitQuery.kt").exists()) - assertEquals(TaskOutcome.SUCCESS, buildResult.task(":test")?.outcome) - } - - @Test - @Tag("groovy") - fun `verify generateClient task (groovy)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = ["-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql"] - | mainClassName = "com.example.ApplicationKt" - |} - | - |graphqlGenerateClient { - | packageName = "com.example.generated" - | schemaFile = file("${'$'}{project.projectDir}/schema.graphql") - | // optional config - | allowDeprecatedFields = true - | customScalars.add(new GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) - | queryFiles.from("${'$'}{project.projectDir}/src/main/resources/queries/JUnitQuery.graphql", - | "${'$'}{project.projectDir}/src/main/resources/queries/DeprecatedQuery.graphql") - |} - """.trimMargin() - testProjectDirectory.generateGroovyBuildFileForClient(buildFileContents) - - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to true))) - testProjectDirectory.createTestFile("UUIDScalarConverter.kt", "src/main/kotlin/com/example") - .writeText(loadResource("mocks/UUIDScalarConverter.txt")) - // end project setup - - verifyGenerateClientTaskSuccess(testProjectDirectory) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated/DeprecatedQuery.kt").exists()) - } - - @Test - @Tag("kts") - fun `verify multiple generateClient tasks (kts)`(@TempDir tempDir: Path) { - val testProjectDirectory = tempDir.toFile() - /* - project setup - ---- - build.gradle.kts - src - |- main - |- kotlin - |- com.example.Application.kt - |- com.example.UUIDScalarConverter.kt - |- resources - |- JUnitQuery.graphql - |- DeprecatedQuery.graphql - */ - val buildFileContents = - """ - |application { - | applicationDefaultJvmArgs = listOf("-DgraphQLEndpoint=${wireMockServer.baseUrl()}/graphql") - | mainClassName = "com.example.ApplicationKt" - |} - | - |tasks.create("generateClient1", GraphQLGenerateClientTask::class) { - | packageName.set("com.example.generated") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - | // optional config - | customScalars.add(GraphQLScalar("UUID", "java.util.UUID", "com.example.UUIDScalarConverter")) - | queryFiles.from( - | "${'$'}{project.projectDir}/src/main/resources/queries/JUnitQuery.graphql" - | ) - |} - | - |tasks.create("generateClient2", GraphQLGenerateClientTask::class) { - | packageName.set("com.example.generated2") - | schemaFile.set(file("${'$'}{project.projectDir}/schema.graphql")) - | // optional config - | allowDeprecatedFields.set(true) - | queryFiles.from( - | "${'$'}{project.projectDir}/src/main/resources/queries/DeprecatedQuery.graphql" - | ) - |} - """.trimMargin() - testProjectDirectory.generateBuildFileForClient(buildFileContents) - testProjectDirectory.createTestFile("schema.graphql") - .writeText(testSchema) - testProjectDirectory.createTestFile("JUnitQuery.graphql", "src/main/resources/queries") - .writeText(testQuery) - testProjectDirectory.createTestFile("DeprecatedQuery.graphql", "src/main/resources/queries") - .writeText(loadResource("mocks/DeprecatedQuery.graphql")) - testProjectDirectory.createTestFile("Application.kt", "src/main/kotlin/com/example") - .writeText(loadTemplate("Application", mapOf("customScalarsEnabled" to true))) - testProjectDirectory.createTestFile("UUIDScalarConverter.kt", "src/main/kotlin/com/example") - .writeText(loadResource("mocks/UUIDScalarConverter.txt")) - // end project setup - - verifyGenerateClientTaskSuccess(testProjectDirectory, tasks = listOf(":generateClient1", ":generateClient2", ":build", ":run")) - assertTrue(File(testProjectDirectory, "build/generated/source/graphql/main/com/example/generated2/DeprecatedQuery.kt").exists()) - } - - private fun verifyGenerateClientTaskSuccess( - testProjectDirectory: File, - tasks: List = listOf(":$GENERATE_CLIENT_TASK_NAME", ":build", ":run"), - outputDirectory: String = "build/generated/source/graphql/main" - ) { - val buildResult = GradleRunner.create() - .withProjectDir(testProjectDirectory) - .withPluginClasspath() - .withArguments("build", "run", "--stacktrace") - .build() - assertTrue(File(testProjectDirectory, "$outputDirectory/com/example/generated/JUnitQuery.kt").exists()) - for (taskName in tasks) { - assertEquals(TaskOutcome.SUCCESS, buildResult.task(taskName)?.outcome) - } - } } diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLIntrospectSchemaTaskIT.kt b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLIntrospectSchemaTaskIT.kt index a6d2d658be..93d5a1abd0 100755 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLIntrospectSchemaTaskIT.kt +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLIntrospectSchemaTaskIT.kt @@ -16,7 +16,7 @@ package com.expediagroup.graphql.plugin.gradle.tasks -import com.expediagroup.graphql.plugin.gradle.GraphQLGradlePluginAbstractIT +import com.expediagroup.graphql.plugin.gradle.WireMockAbstractIT import com.github.tomakehurst.wiremock.client.WireMock import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome @@ -30,7 +30,7 @@ import kotlin.test.assertTrue /** * Verifies failure scenarios only. Happy path scenarios are tested as part of integration/gradle-plugin-integration-tests composite build. */ -class GraphQLIntrospectSchemaTaskIT : GraphQLGradlePluginAbstractIT() { +class GraphQLIntrospectSchemaTaskIT : WireMockAbstractIT() { @Test fun `verify introspectSchema with timeout (kts)`(@TempDir tempDir: Path) { diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomFederatedHooks.txt b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomFederatedHooks.txt deleted file mode 100644 index 86d0fb3562..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomFederatedHooks.txt +++ /dev/null @@ -1,39 +0,0 @@ -package com.example - -import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorHooks -import graphql.language.StringValue -import graphql.schema.Coercing -import graphql.schema.CoercingParseLiteralException -import graphql.schema.GraphQLScalarType -import graphql.schema.GraphQLType -import java.util.UUID -import kotlin.reflect.KType - -private val graphqlUUIDType = GraphQLScalarType.newScalar() - .name("UUID") - .description("Custom scalar representing UUID") - .coercing(object : Coercing { - override fun parseValue(input: Any): UUID = UUID.fromString( - serialize(input) - ) - - override fun parseLiteral(input: Any): UUID { - val uuidString = (input as? StringValue)?.value - return if (uuidString != null) { - UUID.fromString(uuidString) - } else { - throw CoercingParseLiteralException("Unable to convert $input to Any scalar") - } - } - - override fun serialize(dataFetcherResult: Any): String = dataFetcherResult.toString() - }) - .build() - -class CustomFederatedHooks : FederatedSchemaGeneratorHooks(emptyList()) { - - override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier) { - UUID::class -> graphqlUUIDType - else -> super.willGenerateGraphQLType(type) - } -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomHooksProvider.txt b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomHooksProvider.txt deleted file mode 100644 index 17ab11df1e..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/CustomHooksProvider.txt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example - -import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks -import com.expediagroup.graphql.plugin.schema.hooks.SchemaGeneratorHooksProvider - -class CustomHooksProvider : SchemaGeneratorHooksProvider { - - override fun hooks(): SchemaGeneratorHooks = CustomFederatedHooks() -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/IntrospectionResult.json b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/IntrospectionResult.json index 37d8a576c1..3896d94347 100755 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/IntrospectionResult.json +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/IntrospectionResult.json @@ -9,762 +9,46 @@ }, "subscriptionType": null, "types": [ - { - "kind": "INTERFACE", - "name": "BasicInterface", - "description": "Very basic interface", - "fields": [ - { - "name": "id", - "description": "Unique identifier of an interface", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Name field", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "FirstInterfaceImplementation", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "SecondInterfaceImplementation", - "ofType": null - } - ] - }, - { - "kind": "OBJECT", - "name": "BasicObject", - "description": "Some basic description", - "fields": [ - { - "name": "id", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Object name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "UNION", - "name": "BasicUnion", - "description": "Very basic union of BasicObject and ComplexObject", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "BasicObject", - "ofType": null - }, - { - "kind": "OBJECT", - "name": "ComplexObject", - "ofType": null - } - ] - }, { "kind": "SCALAR", - "name": "Boolean", - "description": "Built-in Boolean", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "ComplexObject", - "description": "Multi line description of a complex type.\nThis is a second line of the paragraph.\nThis is final line of the description.", - "fields": [ - { - "name": "details", - "description": "Some additional details", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "DetailsObject", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "Some unique identifier", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Some object name", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "optional", - "description": "Optional value\nSecond line of the description", - "args": [], - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "CustomEnum", - "description": "Custom enum description", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "ONE", - "description": "First enum value", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "TWO", - "description": "Second enum value", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "THREE", - "description": "Third enum value", - "isDeprecated": true, - "deprecationReason": "only goes up to two" - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DetailsObject", - "description": "Inner type object description", - "fields": [ - { - "name": "flag", - "description": "Boolean flag", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "Unique identifier", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "Actual detail value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "FirstInterfaceImplementation", - "description": "Example interface implementation where value is an integer", - "fields": [ - { - "name": "id", - "description": "Unique identifier of the first implementation", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "intValue", - "description": "Custom field integer value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Name of the first implementation", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [ - { - "kind": "INTERFACE", - "name": "BasicInterface", - "ofType": null - } - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Float", - "description": "Built-in Float", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ID", - "description": "Built-in ID", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Int", - "description": "Built-in Int", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Mutation", - "description": null, - "fields": [ - { - "name": "simpleMutation", - "description": "Example of a muation", - "args": [ - { - "name": "update", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "SimpleArgumentInput", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "BasicObject", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "NestedObject", - "description": "Example of an object self-referencing itself", - "fields": [ - { - "name": "children", - "description": "Children elements", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "NestedObject", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "Unique identifier", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Name of the object", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Query", - "description": null, - "fields": [ - { - "name": "complexObjectQuery", - "description": "Query returning an object that references another object", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "ComplexObject", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deprecatedQuery", - "description": "Deprecated query that should not be used anymore", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": true, - "deprecationReason": "old query should not be used" - }, - { - "name": "enumQuery", - "description": "Query that returns enum value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "CustomEnum", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inputObjectQuery", - "description": "Query that accepts some input arguments", - "args": [ - { - "name": "criteria", - "description": null, - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "SimpleArgumentInput", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "interfaceQuery", - "description": "Query returning an interface", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INTERFACE", - "name": "BasicInterface", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "listQuery", - "description": "Query returning list of simple objects", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "BasicObject", - "ofType": null - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nestedObjectQuery", - "description": "Query returning object referencing itself", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "NestedObject", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "scalarQuery", - "description": "Query that returns wrapper object with all supported scalar types", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "ScalarWrapper", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "unionQuery", - "description": "Query returning union", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "UNION", - "name": "BasicUnion", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "ScalarWrapper", - "description": "Wrapper that holds all supported scalar types", - "fields": [ - { - "name": "count", - "description": "A signed 32-bit nullable integer value", - "args": [], - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "custom", - "description": "Custom scalar", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "UUID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "ID represents unique identifier that is not intended to be human readable", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "UTF-8 character sequence", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "rating", - "description": "A nullable signed double-precision floating-point value", - "args": [], - "type": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, + "name": "Boolean", + "description": "Built-in Boolean", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Mutation", + "description": null, + "fields": [ { - "name": "valid", - "description": "Either true or false", - "args": [], + "name": "updateName", + "description": null, + "args": [ + { + "name": "name", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Boolean", + "name": "String", "ofType": null } }, @@ -779,45 +63,24 @@ }, { "kind": "OBJECT", - "name": "SecondInterfaceImplementation", - "description": "Example interface implementation where value is a float", + "name": "Query", + "description": null, "fields": [ { - "name": "floatValue", - "description": "Custom field float value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "id", - "description": "Unique identifier of the second implementation", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null + "name": "helloWorld", + "description": null, + "args": [ + { + "name": "name", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "Name of the second implementation", - "args": [], + ], "type": { "kind": "NON_NULL", "name": null, @@ -832,54 +95,7 @@ } ], "inputFields": null, - "interfaces": [ - { - "kind": "INTERFACE", - "name": "BasicInterface", - "ofType": null - } - ], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "SimpleArgumentInput", - "description": "Test input object", - "fields": null, - "inputFields": [ - { - "name": "max", - "description": "Maximum value for test criteria", - "type": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - }, - "defaultValue": null - }, - { - "name": "min", - "description": "Minimum value for test criteria", - "type": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - }, - "defaultValue": null - }, - { - "name": "newName", - "description": "New value to be set", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "defaultValue": null - } - ], - "interfaces": null, + "interfaces": [], "enumValues": null, "possibleTypes": null }, @@ -893,16 +109,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "SCALAR", - "name": "UUID", - "description": "Custom scalar representing UUID", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "__Directive", @@ -910,12 +116,16 @@ "fields": [ { "name": "name", - "description": null, + "description": "The __Directive type represents a Directive that a server supports.", "args": [], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -932,20 +142,40 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "isRepeatable", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "locations", "description": null, "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "NON_NULL", + "kind": "LIST", "name": null, "ofType": { - "kind": "ENUM", - "name": "__DirectiveLocation", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } } } }, @@ -955,7 +185,18 @@ { "name": "args", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -1038,6 +279,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "SUBSCRIPTION", + "description": "Indicates the directive is valid on subscriptions.", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "FIELD", "description": "Indicates the directive is valid on fields.", @@ -1062,6 +309,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "VARIABLE_DEFINITION", + "description": "Indicates the directive is valid on variable definitions.", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "SCHEMA", "description": "Indicates the directive is valid on a schema SDL definition.", @@ -1234,7 +487,18 @@ { "name": "args", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -1365,6 +629,30 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1377,6 +665,18 @@ "name": "__Schema", "description": "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.", "fields": [ + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "types", "description": "A list of all types supported by this server.", @@ -1621,7 +921,18 @@ { "name": "inputFields", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": "false" + } + ], "type": { "kind": "LIST", "name": null, @@ -1649,6 +960,18 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "specifiedByUrl", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1666,7 +989,7 @@ "enumValues": [ { "name": "SCALAR", - "description": "Indicates this type is a scalar.", + "description": "Indicates this type is a scalar. 'specifiedByUrl' is a valid field", "isDeprecated": false, "deprecationReason": null }, @@ -1780,6 +1103,25 @@ { "name": "reason", "description": "The reason for the deprecation", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": "\"No longer supported\"" + } + ] + }, + { + "name": "specifiedBy", + "description": "Exposes a URL that specifies the behaviour of this scalar.", + "locations": [ + "SCALAR" + ], + "args": [ + { + "name": "url", + "description": "The URL that specifies the behaviour of this scalar.", "type": { "kind": "NON_NULL", "name": null, @@ -1789,7 +1131,7 @@ "ofType": null } }, - "defaultValue": "\"No longer supported\"" + "defaultValue": null } ] } diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQuery.graphql b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQuery.graphql deleted file mode 100755 index 597b3ed151..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQuery.graphql +++ /dev/null @@ -1,54 +0,0 @@ -query JUnitQuery($simpleCriteria: SimpleArgumentInput!) { - enumQuery - scalarQuery { - count - custom - id - name - rating - valid - } - listQuery { - id - name - } - complexObjectQuery { - id - name - optional - details { - id - flag - value - } - } - interfaceQuery { - __typename - id - name - ... on FirstInterfaceImplementation { - intValue - } - ... on SecondInterfaceImplementation { - floatValue - } - } - unionQuery { - __typename - ... on BasicObject { - id - name - } - ... on ComplexObject { - id - name - optional - details { - id - flag - value - } - } - } - inputObjectQuery(criteria: $simpleCriteria) -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQueryResponse.json b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQueryResponse.json deleted file mode 100755 index 90f7522d8d..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/JUnitQueryResponse.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "data": { - "enumQuery": "ONE", - "scalarQuery": { - "count": 1, - "custom": "549400d6-7047-4bbb-962b-0c8bc360d0f0", - "id": "2113503973", - "name": "Scalar Wrapper", - "rating": null, - "valid": true - }, - "listQuery": [ - { - "id": 195555040, - "name": "whatever" - } - ], - "complexObjectQuery": { - "id": 888961957, - "name": "whatever", - "optional": null, - "details": { - "id": 660269832, - "flag": false, - "value": "details" - } - }, - "interfaceQuery": { - "__typename": "SecondInterfaceImplementation", - "id": 877939910, - "name": "second", - "floatValue": 0.65281385 - }, - "unionQuery": { - "__typename": "BasicObject", - "id": 472199463, - "name": "basic" - }, - "inputObjectQuery": true - } -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/UUIDScalarConverter.txt b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/UUIDScalarConverter.txt deleted file mode 100644 index 02738655d7..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/UUIDScalarConverter.txt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example - -import com.expediagroup.graphql.client.converter.ScalarConverter -import java.util.UUID - -class UUIDScalarConverter : ScalarConverter { - override fun toScalar(rawValue: Any): UUID = UUID.fromString(rawValue.toString()) - override fun toJson(value: UUID): Any = value.toString() -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/schema.graphql b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/schema.graphql index 8a5e827e78..0c084129a5 100755 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/schema.graphql +++ b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/mocks/schema.graphql @@ -1,147 +1,7 @@ -schema { - query: Query - mutation: Mutation -} -"Directs the executor to include this field or fragment only when the `if` argument is true" -directive @include( - "Included when true." - if: Boolean! -) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -"Directs the executor to skip this field or fragment when the `if` argument is true." -directive @skip( - "Skipped when true." - if: Boolean! -) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -"Marks the field, argument, input field or enum value as deprecated" -directive @deprecated( - "The reason for the deprecation" - reason: String = "No longer supported" - ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION -"Very basic interface" -interface BasicInterface { - "Unique identifier of an interface" - id: Int! - "Name field" - name: String! -} -"Very basic union of BasicObject and ComplexObject" -union BasicUnion = BasicObject | ComplexObject -"Some basic description" -type BasicObject { - id: Int! - "Object name" - name: String! -} -""" -Multi line description of a complex type. -This is a second line of the paragraph. -This is final line of the description. -""" -type ComplexObject { - "Some additional details" - details: DetailsObject! - "Some unique identifier" - id: Int! - "Some object name" - name: String! - """ - Optional value - Second line of the description - """ - optional: String -} -"Inner type object description" -type DetailsObject { - "Boolean flag" - flag: Boolean! - "Unique identifier" - id: Int! - "Actual detail value" - value: String! -} -"Example interface implementation where value is an integer" -type FirstInterfaceImplementation implements BasicInterface { - "Unique identifier of the first implementation" - id: Int! - "Custom field integer value" - intValue: Int! - "Name of the first implementation" - name: String! -} -type Mutation { - "Example of a muation" - simpleMutation(update: SimpleArgumentInput!): BasicObject! -} -"Example of an object self-referencing itself" -type NestedObject { - "Children elements" - children: [NestedObject!]! - "Unique identifier" - id: Int! - "Name of the object" - name: String! -} type Query { - "Query returning an object that references another object" - complexObjectQuery: ComplexObject! - "Deprecated query that should not be used anymore" - deprecatedQuery: String! @deprecated(reason : "old query should not be used") - "Query that returns enum value" - enumQuery: CustomEnum! - "Query that accepts some input arguments" - inputObjectQuery(criteria: SimpleArgumentInput!): Boolean! - "Query returning an interface" - interfaceQuery: BasicInterface! - "Query returning list of simple objects" - listQuery: [BasicObject!]! - "Query returning object referencing itself" - nestedObjectQuery: NestedObject! - "Query that returns wrapper object with all supported scalar types" - scalarQuery: ScalarWrapper! - "Query returning union" - unionQuery: BasicUnion! -} -"Wrapper that holds all supported scalar types" -type ScalarWrapper { - "A signed 32-bit nullable integer value" - count: Int - "Custom scalar" - custom: UUID! - "ID represents unique identifier that is not intended to be human readable" - id: ID! - "UTF-8 character sequence" - name: String! - "A nullable signed double-precision floating-point value" - rating: Float - "Either true or false" - valid: Boolean! + helloWorld(name: String): String! @deprecated(reason: "this is a test schema") } -"Example interface implementation where value is a float" -type SecondInterfaceImplementation implements BasicInterface { - "Custom field float value" - floatValue: Float! - "Unique identifier of the second implementation" - id: Int! - "Name of the second implementation" - name: String! -} -"Custom enum description" -enum CustomEnum { - "First enum value" - ONE - "Third enum value" - THREE @deprecated(reason : "only goes up to two") - "Second enum value" - TWO -} -"Custom scalar representing UUID" -scalar UUID -"Test input object" -input SimpleArgumentInput { - "Maximum value for test criteria" - max: Float - "Minimum value for test criteria" - min: Float - "New value to be set" - newName: String + +type Mutation { + updateName(name: String!): String! } diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/Application.mustache b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/Application.mustache deleted file mode 100644 index db5161c2fb..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/Application.mustache +++ /dev/null @@ -1,102 +0,0 @@ -package com.example - -import com.example.generated.JUnitQuery -import com.example.generated.enums.CustomEnum -import com.example.generated.inputs.SimpleArgumentInput -import com.example.generated.junitquery.BasicObject2 -import com.example.generated.junitquery.ScalarWrapper -import com.example.generated.junitquery.SecondInterfaceImplementation -{{#optionalInputWrapper}} -{{#useJackson}} -import com.expediagroup.graphql.client.jackson.types.OptionalInput -{{/useJackson}} -{{^useJackson}} -import com.expediagroup.graphql.client.serialization.types.OptionalInput -{{/useJackson}} -{{/optionalInputWrapper}} -{{#ktorClient}} -import com.expediagroup.graphql.client.ktor.GraphQLKtorClient -{{#defaultHeader}} -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.defaultRequest -import io.ktor.client.request.header -import java.net.URL -{{/defaultHeader}} -{{/ktorClient}} -{{^ktorClient}} -import com.expediagroup.graphql.client.spring.GraphQLWebClient -import org.springframework.web.reactive.function.client.WebClient -{{/ktorClient}} -{{#customScalarsEnabled}}import java.util.UUID{{/customScalarsEnabled}} -import kotlinx.coroutines.runBlocking - -fun main() { - {{^defaultHeader}} - val client = GraphQLWebClient(System.getProperty("graphQLEndpoint")) - {{/defaultHeader}} - {{#defaultHeader}} - {{#ktorClient}} - val httpClient = HttpClient(engineFactory = CIO) { - defaultRequest { - header("{{name}}", "{{value}}") - } - } - val client = GraphQLKtorClient( - url = URL(System.getProperty("graphQLEndpoint")), - httpClient = httpClient - ) - {{/ktorClient}} - {{^ktorClient}} - val webClientBuilder = WebClient.builder() - .defaultHeader("{{name}}", "{{value}}") - val client = GraphQLWebClient( - url = System.getProperty("graphQLEndpoint"), - builder = webClientBuilder - ) - {{/ktorClient}} - {{/defaultHeader}} - - {{#optionalInputWrapper}} - val variables = JUnitQuery.Variables(SimpleArgumentInput(newName = OptionalInput.Defined("blah"))) - {{/optionalInputWrapper}} - {{^optionalInputWrapper}} - val variables = JUnitQuery.Variables(SimpleArgumentInput(newName = "blah")) - {{/optionalInputWrapper}} - val query = JUnitQuery(variables) - - runBlocking { - {{^requestHeader}} - val response = client.execute(query) - {{/requestHeader}} - {{#requestHeader}} - val response = client.execute(query) { - header("{{name}}", "{{value}}") - } - {{/requestHeader}} - - val data = response.data - assert(data != null) - val scalarResult = data?.scalarQuery - assert(scalarResult is ScalarWrapper) - assert(scalarResult != null) - assert(scalarResult?.count is Int) - {{#customScalarsEnabled}} - assert(scalarResult?.custom is UUID) - {{/customScalarsEnabled}} - {{^customScalarsEnabled}} - assert(scalarResult?.custom is String) - {{/customScalarsEnabled}} - assert(CustomEnum.ONE == data?.enumQuery) - val interfaceResult = data?.interfaceQuery - assert(interfaceResult is SecondInterfaceImplementation) - val unionResult = data?.unionQuery - assert(unionResult is BasicObject2) - assert(response.errors == null) - assert(response.extensions == null) - } - - {{#ktorClient}} - client.close() - {{/ktorClient}} -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/HelloWorldQuery.mustache b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/HelloWorldQuery.mustache deleted file mode 100644 index 2ef09d2dca..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/HelloWorldQuery.mustache +++ /dev/null @@ -1,21 +0,0 @@ -package com.example - -import com.expediagroup.graphql.server.operations.Query -import org.springframework.stereotype.Component -{{#customScalarsEnabled}} -import java.util.UUID -{{/customScalarsEnabled}} - -@Component -class HelloWorldQuery : Query { - - fun helloWorld(name: String? = null) = if (name != null) { - "Hello, $name!!!" - } else { - "Hello, World!!!" - } - -{{#customScalarsEnabled}} - fun randomUUID(): UUID = UUID.randomUUID() -{{/customScalarsEnabled}} -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/JUnit.mustache b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/JUnit.mustache deleted file mode 100644 index 7d1dccafcf..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/JUnit.mustache +++ /dev/null @@ -1,61 +0,0 @@ -package com.example - -import com.example.generated.JUnitQuery -import com.example.generated.enums.CustomEnum -import com.example.generated.inputs.SimpleArgumentInput -{{#customScalarsEnabled}}import com.example.generated.scalars.UUID{{/customScalarsEnabled}} -import com.example.generated.junitquery.BasicObject2 -import com.example.generated.junitquery.ScalarWrapper -import com.example.generated.junitquery.SecondInterfaceImplementation -import com.expediagroup.graphql.client.spring.GraphQLWebClient -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import java.io.File -import java.nio.file.Paths - -class GraphQLMavenPluginTest { - - @Test - fun `verify client code was generated`() { - val buildDirectory = System.getProperty("buildDirectory") - val path = Paths.get(buildDirectory, "generated", "sources", "graphql", "com", "example", "generated", "JUnitQuery.kt") - assertTrue(path.toFile().exists(), "graphql client was generated") - } - - @Test - fun `verify client code was generated and can execute query`() { - val graphQLEndpoint = System.getProperty("graphQLEndpoint") - val client = GraphQLWebClient(graphQLEndpoint) - - val variables = JUnitQuery.Variables(simpleCriteria = SimpleArgumentInput(newName = "blah")) - val query = JUnitQuery(variables) - - assertDoesNotThrow { - runBlocking { - val response = client.execute(query) - assertTrue(response.errors == null) - val data = response.data - assertNotNull(data) - val scalarResult = data?.scalarQuery - assertTrue(scalarResult is ScalarWrapper) - assertNotNull(scalarResult) - assertTrue(scalarResult?.count is Int) - {{#customScalarsEnabled}} - assertTrue(scalarResult?.custom is UUID) - {{/customScalarsEnabled}} - {{^customScalarsEnabled}} - assertTrue(scalarResult?.custom is String) - {{/customScalarsEnabled}} - assertEquals(CustomEnum.ONE, data?.enumQuery) - val interfaceResult = data?.interfaceQuery - assertTrue(interfaceResult is SecondInterfaceImplementation) - val unionResult = data?.unionQuery - assertTrue(unionResult is BasicObject2) - } - } - } -} diff --git a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/ServerApplication.mustache b/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/ServerApplication.mustache deleted file mode 100644 index c50a765ffd..0000000000 --- a/plugins/graphql-kotlin-gradle-plugin/src/test/resources/templates/ServerApplication.mustache +++ /dev/null @@ -1,22 +0,0 @@ -package com.example - -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.runApplication -{{#customScalarsEnabled}} -import com.example.CustomFederatedHooks -import com.expediagroup.graphql.generator.federation.FederatedSchemaGeneratorHooks -import org.springframework.context.annotation.Bean -{{/customScalarsEnabled}} - -@SpringBootApplication -class Application -{{#customScalarsEnabled}} -{ - @Bean - fun federatedSchemaGeneratorHooks(): FederatedSchemaGeneratorHooks = CustomFederatedHooks() -} -{{/customScalarsEnabled}} - -fun main(args: Array) { - runApplication(*args) -}