From 0e4b20ee3ec0a317ad15cbd31ace481292c29f08 Mon Sep 17 00:00:00 2001 From: Patrick Strawderman <$(git --no-pager log --format=format:'%ae' -n 1)> Date: Mon, 8 Jan 2024 09:20:26 -0800 Subject: [PATCH 1/2] Fix various deprecation and compiler warnings --- .../datafetcher/ConcurrentDataFetcher.java | 4 +- ...vcConfigurationPropertiesValidationTest.kt | 12 +++--- ...hCustomGraphQLPathAndServletContextTest.kt | 4 +- ...etConfigurationPropertiesValidationTest.kt | 2 +- .../internal/VirtualThreadTaskExecutor.java | 1 + .../VirtualThreadTaskExecutor.java | 1 + .../DefaultDgsFederationResolver.kt | 39 ++++++++++--------- .../dgs/DefaultDgsFederationResolverTest.kt | 4 +- .../graphql/dgs/internal/InputArgumentTest.kt | 2 +- 9 files changed, 36 insertions(+), 33 deletions(-) diff --git a/graphql-dgs-example-shared/src/main/java/com/netflix/graphql/dgs/example/shared/datafetcher/ConcurrentDataFetcher.java b/graphql-dgs-example-shared/src/main/java/com/netflix/graphql/dgs/example/shared/datafetcher/ConcurrentDataFetcher.java index ac54b6ceb..c0eb0530e 100644 --- a/graphql-dgs-example-shared/src/main/java/com/netflix/graphql/dgs/example/shared/datafetcher/ConcurrentDataFetcher.java +++ b/graphql-dgs-example-shared/src/main/java/com/netflix/graphql/dgs/example/shared/datafetcher/ConcurrentDataFetcher.java @@ -39,7 +39,7 @@ public CompletableFuture concurrent1() { } System.out.println("Done concurrent thing 1"); - return new Long(System.currentTimeMillis()).intValue(); + return Long.valueOf(System.currentTimeMillis()).intValue(); }); System.out.println("Exit concurrent1"); @@ -59,7 +59,7 @@ public CompletableFuture concurrent2() { } System.out.println("Done concurrent thing 2"); - return new Long(System.currentTimeMillis()).intValue(); + return Long.valueOf(System.currentTimeMillis()).intValue(); }); System.out.println("Exit concurrent2"); diff --git a/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/DgsWebMvcConfigurationPropertiesValidationTest.kt b/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/DgsWebMvcConfigurationPropertiesValidationTest.kt index 94a82c865..1383b2d82 100644 --- a/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/DgsWebMvcConfigurationPropertiesValidationTest.kt +++ b/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/DgsWebMvcConfigurationPropertiesValidationTest.kt @@ -36,7 +36,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.path: /fooql/") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.path must start with '/' and not end with '/'") } } @@ -45,7 +45,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.path: fooql") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.path must start with '/' and not end with '/'") } } @@ -63,7 +63,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.graphiql.path: /fooql/") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.graphiql.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.graphiql.path must start with '/' and not end with '/'") } } @@ -72,7 +72,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.graphiql.path: fooql") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.graphiql.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.graphiql.path must start with '/' and not end with '/'") } } @@ -90,7 +90,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.schema-json.path: /fooql/") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.schema-json.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.schema-json.path must start with '/' and not end with '/'") } } @@ -99,7 +99,7 @@ class DgsWebMvcConfigurationPropertiesValidationTest { context .withPropertyValues("dgs.graphql.schema-json.path: fooql") .run { ctx -> - assertThat(ctx).hasFailed().failure.getRootCause().hasMessageContaining("dgs.graphql.schema-json.path must start with '/' and not end with '/'") + assertThat(ctx).hasFailed().failure.rootCause().hasMessageContaining("dgs.graphql.schema-json.path must start with '/' and not end with '/'") } } diff --git a/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest.kt b/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest.kt index fcf3b6528..665ff26af 100644 --- a/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest.kt +++ b/graphql-dgs-spring-webmvc-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/webmvc/autoconfigure/GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest.kt @@ -68,7 +68,7 @@ class GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest( absPathWithoutContextPath, String::class.java ) - assertThat(graphqlResponse.statusCodeValue).isEqualTo(HttpStatus.NOT_FOUND.value()) + assertThat(graphqlResponse.statusCode).isEqualTo(HttpStatus.NOT_FOUND) // graphql is available with context path in uri (400 expected as we don't sent proper request) val absPathWithContextPath = "$rootUri/zuzu" @@ -76,7 +76,7 @@ class GraphiQLPathConfigWithCustomGraphQLPathAndServletContextTest( absPathWithContextPath, String::class.java ) - assertThat(graphqlResponse.statusCodeValue).isEqualTo(HttpStatus.BAD_REQUEST.value()) + assertThat(graphqlResponse.statusCode).isEqualTo(HttpStatus.BAD_REQUEST) val graphiqlResponse = testRestTemplate.getForEntity( "/graphiql", diff --git a/graphql-dgs-subscriptions-websockets-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/subscriptions/websockets/autoconfigure/DgsWebSocketConfigurationPropertiesValidationTest.kt b/graphql-dgs-subscriptions-websockets-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/subscriptions/websockets/autoconfigure/DgsWebSocketConfigurationPropertiesValidationTest.kt index 313d688cf..e17407f70 100644 --- a/graphql-dgs-subscriptions-websockets-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/subscriptions/websockets/autoconfigure/DgsWebSocketConfigurationPropertiesValidationTest.kt +++ b/graphql-dgs-subscriptions-websockets-autoconfigure/src/test/kotlin/com/netflix/graphql/dgs/subscriptions/websockets/autoconfigure/DgsWebSocketConfigurationPropertiesValidationTest.kt @@ -47,7 +47,7 @@ class DgsWebSocketConfigurationPropertiesValidationTest { .withPropertyValues("dgs.graphql.websocket.path: /pws/") .run { ctx -> Assertions.assertThat(ctx).hasFailed() - .failure.rootCause.hasMessageContaining("dgs.graphql.websocket.path must start with '/' and not end with '/'") + .failure.rootCause().hasMessageContaining("dgs.graphql.websocket.path must start with '/' and not end with '/'") } } diff --git a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/internal/VirtualThreadTaskExecutor.java b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/internal/VirtualThreadTaskExecutor.java index f52d57e1b..ff3c7645b 100644 --- a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/internal/VirtualThreadTaskExecutor.java +++ b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/internal/VirtualThreadTaskExecutor.java @@ -36,6 +36,7 @@ public void execute(@NotNull Runnable task) { } @Override + @Deprecated public void execute(@NotNull Runnable task, long startTimeout) { throw new UnsupportedOperationException("VirtualThreadTaskExecutor is only supported on JDK 21+"); } diff --git a/graphql-dgs/src/main/java21/com.netflix.graphql.dgs.internal.VirtualThreadTaskExecutor/VirtualThreadTaskExecutor.java b/graphql-dgs/src/main/java21/com.netflix.graphql.dgs.internal.VirtualThreadTaskExecutor/VirtualThreadTaskExecutor.java index bcd2f5824..1d986c4d1 100644 --- a/graphql-dgs/src/main/java21/com.netflix.graphql.dgs.internal.VirtualThreadTaskExecutor/VirtualThreadTaskExecutor.java +++ b/graphql-dgs/src/main/java21/com.netflix.graphql.dgs.internal.VirtualThreadTaskExecutor/VirtualThreadTaskExecutor.java @@ -42,6 +42,7 @@ public void execute(@NotNull Runnable task) { } @Override + @Deprecated public void execute(@NotNull Runnable task, long startTimeout) { var future = new FutureTask<>(task, null); execute(future); diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/federation/DefaultDgsFederationResolver.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/federation/DefaultDgsFederationResolver.kt index fbef54d11..044e8f876 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/federation/DefaultDgsFederationResolver.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/federation/DefaultDgsFederationResolver.kt @@ -42,11 +42,11 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import reactor.core.publisher.Mono import java.lang.reflect.InvocationTargetException -import java.util.* +import java.util.Locale +import java.util.Optional import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionException import java.util.concurrent.CompletionStage -import java.util.stream.Collectors @DgsComponent open class DefaultDgsFederationResolver() : @@ -68,7 +68,6 @@ open class DefaultDgsFederationResolver() : /** * Used when the DefaultDgsFederationResolver is extended. */ - @Suppress("JoinDeclarationAndAssignment") @Autowired lateinit var entityFetcherRegistry: EntityFetcherRegistry @@ -82,23 +81,25 @@ open class DefaultDgsFederationResolver() : } private fun valuesWithMappedScalars(graphQLContext: GraphQLContext, values: Map, scalarMappings: Map, Coercing<*, *>>, currentPath: MutableList): Map { - return values.entries.stream() - .map { - currentPath.add(it.key) - - val newValue = if (scalarMappings[currentPath] != null) { - scalarMappings[currentPath]!!.parseValue(it.value, graphQLContext, Locale.getDefault()) - } else if (it.value is Map<*, *>) { - valuesWithMappedScalars(graphQLContext, it.value as Map, scalarMappings, currentPath) - } else { - it.value - } + return values.mapValues { (key, value) -> + currentPath += key + + val converter = scalarMappings[currentPath] + + val newValue = if (converter != null) { + converter.parseValue(value, graphQLContext, Locale.getDefault()) + } else if (value is Map<*, *>) { + @Suppress("UNCHECKED_CAST") + value as Map + valuesWithMappedScalars(graphQLContext, value, scalarMappings, currentPath) + } else { + value + } - currentPath.removeLast() + currentPath.removeLast() - Pair(it.key, newValue) - } - .collect(Collectors.toMap({ it.first }, { it.second!! })) + newValue!! + } } private fun dgsEntityFetchers(env: DataFetchingEnvironment): CompletableFuture>> { @@ -128,7 +129,7 @@ open class DefaultDgsFederationResolver() : } if (result == null) { - logger.error("@DgsEntityFetcher returned null for type: $typename") + logger.error("@DgsEntityFetcher returned null for type: {}", typename) CompletableFuture.completedFuture(null) } diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DefaultDgsFederationResolverTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DefaultDgsFederationResolverTest.kt index ec65d50d9..54935534b 100644 --- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DefaultDgsFederationResolverTest.kt +++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DefaultDgsFederationResolverTest.kt @@ -634,7 +634,7 @@ class DefaultDgsFederationResolverTest { // Define a mock movie entity fetcher that throws an EntityNotFoundException for movieId 1 val movieEntityFetcher = object { @DgsEntityFetcher(name = "Movie") - fun movieEntityFetcher(values: Map, dfe: DgsDataFetchingEnvironment?): Movie { + fun movieEntityFetcher(values: Map): Movie { if (values["movieId"] == "1") { throw DgsEntityNotFoundException("No entity found for movieId 1") } @@ -645,7 +645,7 @@ class DefaultDgsFederationResolverTest { // Define a mock show entity fetcher that throws an EntityNotFoundException for showId 2 val showEntityFetcher = object { @DgsEntityFetcher(name = "Show") - fun showEntityFetcher(values: Map, dfe: DgsDataFetchingEnvironment?): Show { + fun showEntityFetcher(values: Map): Show { if (values["showId"] == "2") { throw DgsEntityNotFoundException("No entity found for showId 2") } diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/InputArgumentTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/InputArgumentTest.kt index f769e96f9..786179dd3 100644 --- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/InputArgumentTest.kt +++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/InputArgumentTest.kt @@ -626,7 +626,7 @@ internal class InputArgumentTest { @DgsComponent class Fetcher { @DgsQuery(field = "numbers") - fun numbers(@InputArgument("list") listOptional: Optional>, dfe: DataFetchingEnvironment): String { + fun numbers(@InputArgument("list") listOptional: Optional>): String { assertThat(listOptional).isNotEmpty assertThat(listOptional.get()).containsExactlyElementsOf(listOf(1, 2, 3)) return "Numbers are ${listOptional.map{ it.joinToString(", ") }.orElse("na")}" From 9f561d4602582a36d141d1c4fbdea7b4e3488eba Mon Sep 17 00:00:00 2001 From: Patrick Strawderman <$(git --no-pager log --format=format:'%ae' -n 1)> Date: Tue, 2 Jan 2024 09:59:47 -0800 Subject: [PATCH 2/2] Fix handling of UnknownOperationException When GraphQL.executeAsync throws UnknownOperationException, or any exception implementing GraphQLError, set the status to BAD_REQUEST / 400 in BaseDgsQueryExecutor. Fixes #1751. --- .../dgs/internal/BaseDgsQueryExecutor.kt | 73 +++++++++++++------ .../dgs/internal/DefaultDgsQueryExecutor.kt | 2 +- .../internal/DefaultDgsQueryExecutorTest.kt | 71 +++++++++++------- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/BaseDgsQueryExecutor.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/BaseDgsQueryExecutor.kt index f7b7b0394..3ab61cf96 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/BaseDgsQueryExecutor.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/BaseDgsQueryExecutor.kt @@ -29,24 +29,24 @@ import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider import com.netflix.graphql.dgs.DgsExecutionResult import com.netflix.graphql.dgs.context.DgsContext import com.netflix.graphql.dgs.exceptions.DgsBadRequestException +import com.netflix.graphql.types.errors.TypedGraphQLError import graphql.ExecutionInput import graphql.ExecutionResult import graphql.GraphQL -import graphql.GraphQLContext import graphql.GraphQLError import graphql.execution.ExecutionIdProvider import graphql.execution.ExecutionStrategy import graphql.execution.instrumentation.Instrumentation import graphql.execution.preparsed.PreparsedDocumentProvider import graphql.schema.GraphQLSchema -import org.dataloader.registries.ScheduledDataLoaderRegistry import org.intellij.lang.annotations.Language import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.util.StringUtils -import java.util.* +import java.util.Optional import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletionException object BaseDgsQueryExecutor { @@ -104,30 +104,61 @@ object BaseDgsQueryExecutor { idProvider.ifPresent { graphQLBuilder.executionIdProvider(it) } val graphQL: GraphQL = graphQLBuilder.build() - val graphQLContextFuture = CompletableFuture() - val dataLoaderRegistry = dataLoaderProvider.buildRegistryWithContextSupplier { graphQLContextFuture.get() } + + lateinit var executionInput: ExecutionInput + val dataLoaderRegistry = dataLoaderProvider.buildRegistryWithContextSupplier { executionInput.graphQLContext } + + @Suppress("DEPRECATION") + executionInput = ExecutionInput.newExecutionInput() + .query(query) + .operationName(operationName) + .variables(inputVariables) + .dataLoaderRegistry(dataLoaderRegistry) + .context(dgsContext) + .graphQLContext(dgsContext) + .extensions(extensions.orEmpty()) + .build() return try { - @Suppress("DEPRECATION") - val executionInput = ExecutionInput.newExecutionInput() - .query(query) - .operationName(operationName) - .variables(inputVariables) - .dataLoaderRegistry(dataLoaderRegistry) - .context(dgsContext) - .graphQLContext(dgsContext) - .extensions(extensions.orEmpty()) - .build() - graphQLContextFuture.complete(executionInput.graphQLContext) - graphQL.executeAsync(executionInput).whenComplete { _, _ -> - if (dataLoaderRegistry is ScheduledDataLoaderRegistry) { - dataLoaderRegistry.close() + val future = graphQL.executeAsync(executionInput) + + if (dataLoaderRegistry is AutoCloseable) { + future.whenComplete { _, _ -> dataLoaderRegistry.close() } + } + + future.exceptionally { exc -> + val cause = if (exc is CompletionException) { + exc.cause + } else { + exc + } + if (cause is GraphQLError) { + DgsExecutionResult + .builder() + .status(HttpStatus.BAD_REQUEST) + .executionResult(ExecutionResult.newExecutionResult().addError(cause).build()) + .build() + } else { + DgsExecutionResult + .builder() + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .executionResult( + ExecutionResult.newExecutionResult().addError( + TypedGraphQLError.newInternalErrorBuilder().build() + ).build() + ) + .build() } } } catch (e: Exception) { logger.error("Encountered an exception while handling query {}", query, e) - val errors: List = if (e is GraphQLError) listOf(e) else emptyList() - CompletableFuture.completedFuture(ExecutionResult.newExecutionResult().errors(errors).build()) + val executionResult = ExecutionResult.newExecutionResult() + if (e is GraphQLError) { + executionResult.addError(e) + } else { + executionResult.addError(TypedGraphQLError.newInternalErrorBuilder().build()) + } + CompletableFuture.completedFuture(executionResult.build()) } } } diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutor.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutor.kt index 940b5a256..88acbc793 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutor.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutor.kt @@ -101,7 +101,7 @@ class DefaultDgsQueryExecutor( if (result.errors.size > 0) { val nullValueError = result.errors.find { it is NonNullableFieldWasNullError } if (nullValueError != null) { - logger.error(nullValueError.message) + logger.error("{}", nullValueError.message) } } diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutorTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutorTest.kt index 7fd98a52a..be01e3d78 100644 --- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutorTest.kt +++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/DefaultDgsQueryExecutorTest.kt @@ -33,11 +33,14 @@ import com.netflix.graphql.dgs.internal.method.MethodDataFetcherFactory import graphql.InvalidSyntaxError import graphql.execution.AsyncExecutionStrategy import graphql.execution.AsyncSerialExecutionStrategy +import graphql.execution.UnknownOperationException import graphql.execution.instrumentation.SimplePerformantInstrumentation import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.InstanceOfAssertFactory import org.dataloader.DataLoaderRegistry import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -45,6 +48,7 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import org.springframework.context.ApplicationContext import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus import java.time.LocalDateTime import java.util.Optional import java.util.function.Supplier @@ -53,6 +57,10 @@ import java.util.function.Supplier @ExtendWith(MockKExtension::class) internal class DefaultDgsQueryExecutorTest { + companion object { + private val DGS_RESULT = InstanceOfAssertFactory(DgsExecutionResult::class.java, Assertions::assertThat) + } + @MockK lateinit var applicationContextMock: ApplicationContext @@ -63,35 +71,35 @@ internal class DefaultDgsQueryExecutorTest { @BeforeEach fun createExecutor() { - val fetcher = object : Any() { + val fetcher = object { @DgsData(parentType = "Query", field = "hello") fun hello(): String { return "hi!" } } - val numbersFetcher = object : Any() { + val numbersFetcher = object { @DgsData(parentType = "Query", field = "numbers") fun hello(): List { return listOf(1, 2, 3) } } - val moviesFetcher = object : Any() { + val moviesFetcher = object { @DgsData(parentType = "Query", field = "movies") fun movies(): List { return listOf(Movie("Extraction", LocalDateTime.MIN), Movie("Da 5 Bloods", LocalDateTime.MAX)) } } - val fetcherWithError = object : Any() { + val fetcherWithError = object { @DgsData(parentType = "Query", field = "withError") fun withError(): String { throw RuntimeException("Broken!") } } - val echoFetcher = object : Any() { + val echoFetcher = object { @DgsData(parentType = "Query", field = "echo") fun echo(@InputArgument("message") message: String): String { return message @@ -99,21 +107,13 @@ internal class DefaultDgsQueryExecutorTest { } every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf( - Pair( - "helloFetcher", - fetcher - ), - Pair("numbersFetcher", numbersFetcher), - Pair("moviesFetcher", moviesFetcher), - Pair("withErrorFetcher", fetcherWithError), - Pair("echoFetcher", echoFetcher) - ) - every { applicationContextMock.getBeansWithAnnotation(DgsScalar::class.java) } returns mapOf( - Pair( - "DateTimeScalar", - LocalDateTimeScalar() - ) + "helloFetcher" to fetcher, + "numbersFetcher" to numbersFetcher, + "moviesFetcher" to moviesFetcher, + "withErrorFetcher" to fetcherWithError, + "echoFetcher" to echoFetcher ) + every { applicationContextMock.getBeansWithAnnotation(DgsScalar::class.java) } returns mapOf("DateTimeScalar" to LocalDateTimeScalar()) every { applicationContextMock.getBeansWithAnnotation(DgsDirective::class.java) } returns emptyMap() every { dgsDataLoaderProvider.buildRegistryWithContextSupplier(any>()) } returns DataLoaderRegistry() @@ -186,13 +186,11 @@ internal class DefaultDgsQueryExecutorTest { .message ).isEqualTo(DgsBadRequestException.NULL_OR_EMPTY_QUERY_EXCEPTION.message) - val springResponse = (result as DgsExecutionResult).toSpringResponse() - - assertThat( - springResponse - .statusCode - .value() - ).isEqualTo(400) + assertThat(result) + .asInstanceOf(DGS_RESULT) + .extracting { it.toSpringResponse() } + .extracting { it.statusCode } + .isEqualTo(HttpStatus.BAD_REQUEST) } @Test @@ -200,10 +198,29 @@ internal class DefaultDgsQueryExecutorTest { val result = dgsQueryExecutor.execute("a") assertThat(result) .isNotNull - .extracting { it.errors.first() } + .extracting { it.errors } + .asList() + .singleElement() .isInstanceOf(InvalidSyntaxError::class.java) } + @Test + fun `Invalid operation returns a GraphQL Error with type UnknownOperationException`() { + val result = dgsQueryExecutor.execute("""{ movies { title } }""", emptyMap(), "foo") + assertThat(result) + .isNotNull + .extracting { it.errors } + .asList() + .singleElement() + .isInstanceOf(UnknownOperationException::class.java) + + assertThat(result) + .asInstanceOf(DGS_RESULT) + .extracting { it.toSpringResponse() } + .extracting { it.statusCode } + .isEqualTo(HttpStatus.BAD_REQUEST) + } + @Test fun extractJsonWithString() { val helloResult = dgsQueryExecutor.executeAndExtractJsonPath(