diff --git a/gateway/src/main/java/com/bakdata/quick/gateway/GraphQLSchemaGenerator.java b/gateway/src/main/java/com/bakdata/quick/gateway/GraphQLSchemaGenerator.java index e2a1ca3e..66a40b86 100644 --- a/gateway/src/main/java/com/bakdata/quick/gateway/GraphQLSchemaGenerator.java +++ b/gateway/src/main/java/com/bakdata/quick/gateway/GraphQLSchemaGenerator.java @@ -67,7 +67,7 @@ public GraphQLSchemaGenerator(final List directiveWirings, this.directiveWirings = directiveWirings; this.quickGraphQLTypes = quickGraphQLTypes; this.postProcessings = postProcessings; - this.customScalars = List.of(ExtendedScalars.GraphQLLong); + this.customScalars = List.of(ExtendedScalars.GraphQLLong, ExtendedScalars.DateTime); } // TODO: remove. Currently exists only for old tests diff --git a/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLQueryExecutionTest.java b/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLQueryExecutionTest.java index 4ac2e42e..39b3abd3 100644 --- a/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLQueryExecutionTest.java +++ b/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLQueryExecutionTest.java @@ -16,6 +16,11 @@ package com.bakdata.quick.gateway; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; +import static java.time.temporal.ChronoField.HOUR_OF_DAY; +import static java.time.temporal.ChronoField.MINUTE_OF_HOUR; +import static java.time.temporal.ChronoField.NANO_OF_SECOND; +import static java.time.temporal.ChronoField.SECOND_OF_MINUTE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,6 +46,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; import java.util.Collections; import java.util.List; import java.util.Map; @@ -50,7 +58,8 @@ import org.junit.jupiter.api.TestInfo; class GraphQLQueryExecutionTest { - private static final TypeReference> OBJECT_TYPE_REFERENCE = new TypeReference<>() {}; + private static final TypeReference> OBJECT_TYPE_REFERENCE = new TypeReference<>() { + }; private static final Path workingDirectory = Path.of("src", "test", "resources", "schema", "execution"); private final TopicRegistryClient registryClient = new TestTopicRegistryClient(); private final ObjectMapper mapper = new ObjectMapper(); @@ -310,6 +319,43 @@ void shouldThrowErrorForNonNullableField() throws IOException { }); } + @Test + void shouldExecuteQueryWithDateTime(final TestInfo testInfo) throws IOException { + final String name = testInfo.getTestMethod().orElseThrow().getName(); + final Path schemaPath = workingDirectory.resolve(name + ".graphql"); + final Path queryPath = workingDirectory.resolve(name + "Query.graphql"); + + final TestClientSupplier testClientSupplier = new TestClientSupplier(); + final GraphQL graphQL = this.getGraphQL(schemaPath, testClientSupplier); + + final ZonedDateTime zonedNow = ZonedDateTime.now(); + final DataFetcherClient dataFetcherClient = testClientSupplier.getClient("metadata-topic"); + final Metadata meta = Metadata.builder().id("test").createdAt(zonedNow).source("s1").build(); + when(dataFetcherClient.fetchResult("test")).thenAnswer(invocation -> meta); + + final ExecutionResult executionResult = graphQL.execute(Files.readString(queryPath)); + final Map> data = executionResult.getData(); + assertThat(data.get("findMetadata")) + .containsEntry("createdAt", zonedNow.format(getCustomDateTimeFormatter())); + + } + + // Taken from: {@link graphql.scalars.datetime.DateTimeScalar} + private static DateTimeFormatter getCustomDateTimeFormatter() { + return new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendLiteral('T') + .appendValue(HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(MINUTE_OF_HOUR, 2) + .appendLiteral(':') + .appendValue(SECOND_OF_MINUTE, 2) + .appendFraction(NANO_OF_SECOND, 3, 3, true) + .appendOffset("+HH:MM", "Z") + .toFormatter(); + } + private GraphQL getGraphQL(final Path schemaPath, final ClientSupplier clientSupplier) throws IOException { final KafkaConfig kafkaConfig = new KafkaConfig("dummy", "dummy"); @@ -398,4 +444,12 @@ private static class UserRequest { int timestamp; int requests; } + + @Value + @Builder + private static class Metadata { + String id; + ZonedDateTime createdAt; + String source; + } } diff --git a/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLSchemaGeneratorTest.java b/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLSchemaGeneratorTest.java index 582226d8..7f666a6a 100644 --- a/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLSchemaGeneratorTest.java +++ b/gateway/src/test/java/com/bakdata/quick/gateway/GraphQLSchemaGeneratorTest.java @@ -32,6 +32,7 @@ import com.bakdata.quick.gateway.fetcher.subscription.MultiSubscriptionFetcher; import com.bakdata.quick.gateway.fetcher.subscription.SubscriptionFetcher; import graphql.Scalars; +import graphql.scalars.ExtendedScalars; import graphql.schema.DataFetcher; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLFieldDefinition; @@ -511,6 +512,23 @@ void shouldNotCovertIfReturnTypeOfRangeQueryIsNotList(final TestInfo testInfo) t this.assertQuickDirectiveExceptionMessage(testInfo, "The return type of range queries should be a list."); } + @Test + void shouldConvertSchemaWithDateTime(final TestInfo testInfo) throws IOException { + final Path schemaPath = workingDirectory.resolve(testInfo.getTestMethod().orElseThrow().getName() + ".graphql"); + final GraphQLSchema schema = this.generator.create(Files.readString(schemaPath)); + + assertThat(schema.getTypeMap()) + .containsKeys("Purchase", "Product", "Metadata", "Query"); + + final GraphQLFieldDefinition fieldDefinition = + GraphQLTestUtil.getFieldDefinition("Metadata", "createdAt", schema); + assertThat(fieldDefinition) + .isNotNull() + .extracting(GraphQLFieldDefinition::getType) + .isInstanceOf(GraphQLScalarType.class) + .isExactlyInstanceOf(ExtendedScalars.DateTime.getClass()); + } + private void registerTopics() { this.registryClient.register( "purchase-topic", diff --git a/gateway/src/test/resources/schema/conversion/shouldConvertSchemaWithDateTime.graphql b/gateway/src/test/resources/schema/conversion/shouldConvertSchemaWithDateTime.graphql new file mode 100644 index 00000000..7979c5ac --- /dev/null +++ b/gateway/src/test/resources/schema/conversion/shouldConvertSchemaWithDateTime.graphql @@ -0,0 +1,22 @@ +type Query { + findPurchases: [Purchase] @topic(name: "purchase-topic") +} + +type Purchase { + purchaseId: ID!, + productId: ID!, + userId: ID!, + amount: Int, + product: Product @topic(name: "product-topic", keyField: "productId") +} + +type Product { + productId: ID!, + name: String, + metadata: Metadata +} + +type Metadata { + createdAt: DateTime, + source: String +} diff --git a/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTime.graphql b/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTime.graphql new file mode 100644 index 00000000..12b1a277 --- /dev/null +++ b/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTime.graphql @@ -0,0 +1,9 @@ +type Query { + findMetadata(id: String): Metadata @topic(name: "metadata-topic", keyArgument: "id") +} + +type Metadata { + id: String! + createdAt: DateTime!, + source: String +} diff --git a/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTimeQuery.graphql b/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTimeQuery.graphql new file mode 100644 index 00000000..c12b4651 --- /dev/null +++ b/gateway/src/test/resources/schema/execution/shouldExecuteQueryWithDateTimeQuery.graphql @@ -0,0 +1,5 @@ +{ + findMetadata(id: "test") { + createdAt + } +}