Skip to content

Commit

Permalink
[plugin] expose timeout config for downloadSDL/introspectSchema tasks (
Browse files Browse the repository at this point in the history
…#775)

* [plugin] expose timeout configuration for downloadSDL/introspectSchema tasks

Expose new read/connect timeout configuration for downloadSDL and introspectSchema tasks (and corresponding MOJOs). Example configuration:

```kotlin
graphql {
  client {
    endpoint = "http://localhost:8080/graphql"
    packageName = "com.example.generated"

    timeout {
        // Connect timeout in milliseconds
        connect = 5_000
        // Read timeout in milliseconds
        read = 15_000
    }
  }
}
```

```xml
<plugin>
    <groupId>com.expediagroup</groupId>
    <artifactId>graphql-kotlin-maven-plugin</artifactId>
    <version>${graphql-kotlin.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>introspect-schema</goal>
            </goals>
            <configuration>
                <endpoint>http://localhost:8080/graphql</endpoint>
                <!-- optional configuration below -->
                <timeoutConfiguration>
                    <!-- timeout values in milliseconds -->
                    <connect>5000</connect>
                    <read>15000</read>
                </timeoutConfiguration>
            </configuration>
        </execution>
    </executions>
</plugin>
```

Resolves: #745

* update timeout values for test

It looks like GH Actions sometimes take a bit longer to execute so bumping up the response delay from 1s to 10s.

* disable parallel maven integration tests

* enable streaming mvn logs to std out

* unique integration test maven project names
  • Loading branch information
dariuszkuc authored Jun 29, 2020
1 parent 078b21c commit 490709f
Show file tree
Hide file tree
Showing 27 changed files with 703 additions and 94 deletions.
18 changes: 18 additions & 0 deletions docs/plugins/gradle-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ graphql {
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
// List of query files to be processed.
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
// Timeout configuration
timeout {
// Connect timeout in milliseconds
connect = 5_000
// Read timeout in milliseconds
read = 15_000
}
}
}
```
Expand All @@ -68,6 +75,7 @@ and could be used as an alternative to `graphqlIntrospectSchema` to generate inp
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**Command line property is**: `endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request. |
| `timeoutConfig` | TimeoutConfig | | Timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|

### graphqlGenerateClient

Expand Down Expand Up @@ -121,6 +129,7 @@ should be used to generate input for the subsequent `graphqlGenerateClient` task
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**Command line property is**: `endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
| `timeoutConfig` | TimeoutConfig | | Timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|

## Examples

Expand Down Expand Up @@ -307,6 +316,8 @@ the GraphQL client code based on the provided query.

```kotlin
// build.gradle.kts
import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
import com.expediagroup.graphql.plugin.gradle.graphql

graphql {
Expand All @@ -318,6 +329,10 @@ graphql {
headers["X-Custom-Header"] = "My-Custom-Header"
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
timeout {
connect = 10_000
read = 30_000
}
}
}
```
Expand All @@ -326,12 +341,15 @@ Above configuration is equivalent to the following

```kotlin
// build.gradle.kts
import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask

val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) {
endpoint.set("http://localhost:8080/sdl")
headers.put("X-Custom-Header", "My-Custom-Header")
timeoutConfig.set(TimeoutConfig(connect = 10_000, read = 30_000))
}
val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) {
packageName.set("com.example.generated")
Expand Down
37 changes: 34 additions & 3 deletions docs/plugins/maven-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ goal provides limited functionality by itself and instead should be used to gene
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**User property is**: `graphql.endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request.
| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default values are:** connect timeout = 5000, read timeout = 15000.<br/> |

**Parameter Details**

* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds.

```xml
<timeoutConfiguration>
<!-- timeout values in milliseconds -->
<connect>1000</connect>
<read>30000</read>
</timeoutConfiguration>
```

### generate-client

Expand Down Expand Up @@ -124,6 +137,19 @@ should be used to generate input for the subsequent `generate-client` goal.
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**User property is**: `graphql.endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration(in milliseconds) to execute introspection query before we cancel the request.<br/>**Default values are:** connect timeout = 5000, read timeout = 15000.<br/> |

**Parameter Details**

* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds.

```xml
<timeoutConfiguration>
<!-- timeout values in milliseconds -->
<connect>1000</connect>
<read>30000</read>
</timeoutConfiguration>
```

## Examples

Expand Down Expand Up @@ -374,9 +400,6 @@ the GraphQL client code based on the provided query.
<schemaFile>${project.build.directory}/schema.graphql</schemaFile>
<!-- optional configuration below -->
<allowDeprecatedFields>true</allowDeprecatedFields>
<headers>
<X-Custom-Header>My-Custom-Header</X-Custom-Header>
</headers>
<converters>
<!-- custom scalar UUID type -->
<UUID>
Expand All @@ -387,6 +410,14 @@ the GraphQL client code based on the provided query.
<converter>com.example.UUIDScalarConverter</converter>
</UUID>
</converters>
<headers>
<X-Custom-Header>My-Custom-Header</X-Custom-Header>
</headers>
<timeoutConfiguration>
<!-- timeout values in milliseconds -->
<connect>1000</connect>
<read>30000</read>
</timeoutConfiguration>
<queryFiles>
<queryFile>${project.basedir}/src/main/resources/queries/MyQuery.graphql</queryFile>
</queryFiles>
Expand Down
8 changes: 8 additions & 0 deletions plugins/graphql-kotlin-gradle-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ graphql {
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
// List of query files to be processed.
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
timeout {
// Connect timeout in milliseconds
connect = 5_000
// Read timeout in milliseconds
read = 15_000
}
}
}
```
Expand All @@ -61,6 +67,7 @@ and could be used as an alternative to `graphqlIntrospectSchema` to generate inp
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**Command line property is**: `endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request. |
| `timeoutConfig` | TimeoutConfig | | Optional timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|

### graphqlGenerateClient

Expand Down Expand Up @@ -114,6 +121,7 @@ should be used to generate input for the subsequent `graphqlGenerateClient` task
| -------- | ---- | -------- | ----------- |
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**Command line property is**: `endpoint`. |
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
| `timeoutConfig` | TimeoutConfig | | Optional timeout configuration(in milliseconds) to execute introspection query before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|

## Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ class GraphQLGradlePlugin : Plugin<Project> {
val introspectSchemaTask = project.tasks.named(INTROSPECT_SCHEMA_TASK_NAME, GraphQLIntrospectSchemaTask::class.java).get()
introspectSchemaTask.endpoint.convention(project.provider { extension.clientExtension.endpoint })
introspectSchemaTask.headers.convention(project.provider { extension.clientExtension.headers })
introspectSchemaTask.timeoutConfig.convention(project.provider { extension.clientExtension.timeoutConfig })
generateClientTask.dependsOn(introspectSchemaTask.path)
generateClientTask.schemaFile.convention(introspectSchemaTask.outputFile)
} else if (extension.clientExtension.sdlEndpoint != null) {
val downloadSDLTask = project.tasks.named(DOWNLOAD_SDL_TASK_NAME, GraphQLDownloadSDLTask::class.java).get()
downloadSDLTask.endpoint.convention(project.provider { extension.clientExtension.sdlEndpoint })
downloadSDLTask.headers.convention(project.provider { extension.clientExtension.headers })
downloadSDLTask.timeoutConfig.convention(project.provider { extension.clientExtension.timeoutConfig })
generateClientTask.dependsOn(downloadSDLTask.path)
generateClientTask.schemaFile.convention(downloadSDLTask.outputFile)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.expediagroup.graphql.plugin.gradle

import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
import java.io.File

Expand Down Expand Up @@ -54,4 +55,11 @@ open class GraphQLPluginClientExtension {
var converters: MutableMap<String, ScalarConverterMapping> = mutableMapOf()
/** List of query files to be processed. */
var queryFiles: MutableList<File> = mutableListOf()

/** Connect and read timeout configuration for executing introspection query/download schema */
internal val timeoutConfig: TimeoutConfig = TimeoutConfig()

fun timeout(config: TimeoutConfig.() -> Unit = {}) {
timeoutConfig.apply(config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.expediagroup.graphql.plugin.gradle.tasks

import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.downloadSchema
import kotlinx.coroutines.runBlocking
import org.gradle.api.DefaultTask
Expand Down Expand Up @@ -49,6 +50,13 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
@Input
val headers: MapProperty<String, Any> = project.objects.mapProperty(String::class.java, Any::class.java)

/**
* Timeout configuration that specifies maximum amount of time (in milliseconds) to connect and download schema from SDL endpoint before we cancel the request.
* Defaults to Ktor CIO engine defaults (5 seconds for connect timeout and 15 seconds for read timeout).
*/
@Input
val timeoutConfig: Property<TimeoutConfig> = project.objects.property(TimeoutConfig::class.java)

@OutputFile
val outputFile: Provider<RegularFile> = project.layout.buildDirectory.file("schema.graphql")

Expand All @@ -57,6 +65,7 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
description = "Download schema in SDL format from target endpoint."

headers.convention(emptyMap())
timeoutConfig.convention(TimeoutConfig())
}

/**
Expand All @@ -67,7 +76,7 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
fun downloadSDLAction() {
logger.debug("starting download SDL task against ${endpoint.get()}")
runBlocking {
val schema = downloadSchema(endpoint = endpoint.get(), httpHeaders = headers.get())
val schema = downloadSchema(endpoint = endpoint.get(), httpHeaders = headers.get(), timeoutConfig = timeoutConfig.get())
val outputFile = outputFile.get().asFile
outputFile.writeText(schema)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.expediagroup.graphql.plugin.gradle.tasks

import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.introspectSchema
import kotlinx.coroutines.runBlocking
import org.gradle.api.DefaultTask
Expand Down Expand Up @@ -49,6 +50,13 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
@Input
val headers: MapProperty<String, Any> = project.objects.mapProperty(String::class.java, Any::class.java)

/**
* Timeout configuration that specifies maximum amount of time (in milliseconds) to connect and execute introspection query before we cancel the request.
* Defaults to Ktor CIO engine defaults (5 seconds for connect timeout and 15 seconds for read timeout).
*/
@Input
val timeoutConfig: Property<TimeoutConfig> = project.objects.property(TimeoutConfig::class.java)

@OutputFile
val outputFile: Provider<RegularFile> = project.layout.buildDirectory.file("schema.graphql")

Expand All @@ -57,6 +65,7 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
description = "Run introspection query against target GraphQL endpoint and save schema locally."

headers.convention(emptyMap())
timeoutConfig.convention(TimeoutConfig())
}

/**
Expand All @@ -67,7 +76,7 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
fun introspectSchemaAction() {
logger.debug("starting introspection task against ${endpoint.get()}")
runBlocking {
val schema = introspectSchema(endpoint = endpoint.get(), httpHeaders = headers.get())
val schema = introspectSchema(endpoint = endpoint.get(), httpHeaders = headers.get(), timeoutConfig = timeoutConfig.get())
outputFile.get().asFile.writeText(schema)
}
logger.debug("successfully created GraphQL schema from introspection result")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,29 @@ class GraphQLDownloadSDLTaskIT : GraphQLGradlePluginAbstractIT() {
assertEquals(TaskOutcome.SUCCESS, result.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome)
assertTrue(File(tempDir.toFile(), "build/schema.graphql").exists())
}

@Test
fun `apply the gradle plugin and execute downloadSDL with timeout`(@TempDir tempDir: Path) {
val testProjectDirectory = tempDir.toFile()
WireMock.reset()
WireMock.stubFor(stubSdlEndpoint(delay = 10_000))

val buildFileContents =
"""
val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) {
endpoint.set("${wireMockServer.baseUrl()}/sdl")
timeoutConfig.set(TimeoutConfig(connect = 100, read = 100))
}
""".trimIndent()
testProjectDirectory.generateBuildFile(buildFileContents)

val result = GradleRunner.create()
.withProjectDir(testProjectDirectory)
.withPluginClasspath()
.withArguments(DOWNLOAD_SDL_TASK_NAME)
.buildAndFail()

assertEquals(TaskOutcome.FAILED, result.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome)
assertTrue(result.output.contains("Timed out waiting for 100 ms", ignoreCase = true))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,23 @@ abstract class GraphQLGradlePluginAbstractIT {
WireMock.stubFor(stubGraphQLResponse())
}

fun stubSdlEndpoint(): MappingBuilder = WireMock.get("/sdl")
.withResponse(content = testSchema, contentType = "text/plain")
fun stubSdlEndpoint(delay: Int? = null): MappingBuilder = WireMock.get("/sdl")
.withResponse(content = testSchema, contentType = "text/plain", delay = delay)

fun stubIntrospectionResult(): MappingBuilder = WireMock.post("/graphql")
fun stubIntrospectionResult(delay: Int? = null): MappingBuilder = WireMock.post("/graphql")
.withRequestBody(ContainsPattern("IntrospectionQuery"))
.withResponse(content = introspectionResult)
.withResponse(content = introspectionResult, delay = delay)

fun stubGraphQLResponse(): MappingBuilder = WireMock.post("/graphql")
fun stubGraphQLResponse(delay: Int? = null): MappingBuilder = WireMock.post("/graphql")
.withRequestBody(ContainsPattern("JUnitQuery"))
.withResponse(content = testResponse)
.withResponse(content = testResponse, delay = delay)

private fun MappingBuilder.withResponse(content: String, contentType: String = "application/json") = this.willReturn(
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 {
Expand All @@ -82,6 +83,7 @@ abstract class GraphQLGradlePluginAbstractIT {
val buildFileContents =
"""
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import com.expediagroup.graphql.plugin.config.TimeoutConfig
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
import com.expediagroup.graphql.plugin.gradle.graphql
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,29 @@ class GraphQLIntrospectSchemaTaskIT : GraphQLGradlePluginAbstractIT() {
assertEquals(TaskOutcome.SUCCESS, result.task(":$INTROSPECT_SCHEMA_TASK_NAME")?.outcome)
assertTrue(File(testProjectDirectory, "build/schema.graphql").exists())
}

@Test
fun `apply the gradle plugin and execute introspectSchema with timeout`(@TempDir tempDir: Path) {
val testProjectDirectory = tempDir.toFile()
WireMock.reset()
WireMock.stubFor(stubIntrospectionResult(delay = 10_000))

val buildFileContents =
"""
val graphqlIntrospectSchema by tasks.getting(GraphQLIntrospectSchemaTask::class) {
endpoint.set("${wireMockServer.baseUrl()}/graphql")
timeoutConfig.set(TimeoutConfig(connect = 100, read = 100))
}
""".trimIndent()
testProjectDirectory.generateBuildFile(buildFileContents)

val result = GradleRunner.create()
.withProjectDir(testProjectDirectory)
.withPluginClasspath()
.withArguments(INTROSPECT_SCHEMA_TASK_NAME)
.buildAndFail()

assertEquals(TaskOutcome.FAILED, result.task(":$INTROSPECT_SCHEMA_TASK_NAME")?.outcome)
assertTrue(result.output.contains("Timed out waiting for 100 ms", ignoreCase = true))
}
}
Loading

0 comments on commit 490709f

Please sign in to comment.