From f709ff60f5d4f00b1433d10c18326174566c5f0b Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Fri, 13 Mar 2026 10:55:18 +1100 Subject: [PATCH 1/4] Add definition -> coordinate factory method --- .../NadelSchemaMemberCoordinatesFactory.kt | 393 +++++++++++++----- ...NadelSchemaMemberCoordinatesFactoryTest.kt | 103 ++--- 2 files changed, 339 insertions(+), 157 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactory.kt b/lib/src/main/java/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactory.kt index 2a854b6eb..743d23b06 100644 --- a/lib/src/main/java/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactory.kt +++ b/lib/src/main/java/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactory.kt @@ -1,5 +1,12 @@ package graphql.nadel.definition.coordinates +import graphql.language.Document +import graphql.language.NamedNode +import graphql.nadel.engine.util.AnySDLDefinition +import graphql.nadel.engine.util.unwrapAll +import graphql.nadel.schema.NadelSchemaDefinitionTraverser +import graphql.nadel.schema.NadelSchemaDefinitionTraverserElement +import graphql.nadel.schema.NadelSchemaDefinitionTraverserVisitor import graphql.nadel.schema.NadelSchemaTraverser import graphql.nadel.schema.NadelSchemaTraverserElement import graphql.nadel.schema.NadelSchemaTraverserVisitor @@ -53,6 +60,26 @@ class NadelSchemaMemberCoordinatesFactory { ) } + fun create( + schema: Document, + ): Set { + val definitions = schema.definitions + .asSequence() + .filterIsInstance() + + // There can be multiple definitions per name, but in this scenario we don't care + val definitionByName = definitions + .associateBy { + (it as NamedNode<*>).name + } + + val roots = definitions + .mapNotNull(NadelSchemaDefinitionTraverserElement::from) + .toList() + + return createImpl(roots, definitionByName) + } + private fun createImpl( roots: List, ): Set { @@ -61,117 +88,265 @@ class NadelSchemaMemberCoordinatesFactory { NadelSchemaTraverser() .traverse( roots, - object : NadelSchemaTraverserVisitor { - override fun visitGraphQLArgument( - element: NadelSchemaTraverserElement.Argument, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLUnionType( - element: NadelSchemaTraverserElement.UnionType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLUnionMemberType( - element: NadelSchemaTraverserElement.UnionMemberType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLInterfaceType( - element: NadelSchemaTraverserElement.InterfaceType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLEnumType( - element: NadelSchemaTraverserElement.EnumType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLEnumValueDefinition( - element: NadelSchemaTraverserElement.EnumValueDefinition, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLFieldDefinition( - element: NadelSchemaTraverserElement.FieldDefinition, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLInputObjectField( - element: NadelSchemaTraverserElement.InputObjectField, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLInputObjectType( - element: NadelSchemaTraverserElement.InputObjectType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLObjectType( - element: NadelSchemaTraverserElement.ObjectType, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLScalarType( - element: NadelSchemaTraverserElement.ScalarType, - ): Boolean { - // Ignore built in scalars - if (ScalarInfo.isGraphqlSpecifiedScalar(element.node.name)) { - return false - } - - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLDirective( - element: NadelSchemaTraverserElement.Directive, - ): Boolean { - // Ignore built in directives - if (DirectiveInfo.isGraphqlSpecifiedDirective(element.node.name)) { - return false - } - - coordinates.add(element.coordinates()) - return true - } - - override fun visitGraphQLAppliedDirective( - element: NadelSchemaTraverserElement.AppliedDirective, - ): Boolean { - coordinates.add(element.coordinates()) - return false // Don't traverse argument further - } - - override fun visitGraphQLAppliedDirectiveArgument( - element: NadelSchemaTraverserElement.AppliedDirectiveArgument, - ): Boolean { - coordinates.add(element.coordinates()) - return true - } - } + NadelSchemaCoordinateCollectorTraverserVisitor(coordinates), ) return coordinates } + + private fun createImpl( + roots: List, + definitionByName: Map, + ): Set { + val coordinates = mutableSetOf() + + NadelSchemaDefinitionTraverser() + .traverse( + roots, + NadelSchemaDefinitionCoordinateCollectorTraverserVisitor(coordinates, definitionByName), + ) + + return coordinates + } +} + +internal class NadelSchemaCoordinateCollectorTraverserVisitor( + private val coordinates: MutableCollection, +) : NadelSchemaTraverserVisitor { + override fun visitGraphQLArgument( + element: NadelSchemaTraverserElement.Argument, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLUnionType( + element: NadelSchemaTraverserElement.UnionType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLUnionMemberType( + element: NadelSchemaTraverserElement.UnionMemberType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInterfaceType( + element: NadelSchemaTraverserElement.InterfaceType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLEnumType( + element: NadelSchemaTraverserElement.EnumType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLEnumValueDefinition( + element: NadelSchemaTraverserElement.EnumValueDefinition, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLFieldDefinition( + element: NadelSchemaTraverserElement.FieldDefinition, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInputObjectField( + element: NadelSchemaTraverserElement.InputObjectField, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInputObjectType( + element: NadelSchemaTraverserElement.InputObjectType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLObjectType( + element: NadelSchemaTraverserElement.ObjectType, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLScalarType( + element: NadelSchemaTraverserElement.ScalarType, + ): Boolean { + // Ignore built in scalars + if (ScalarInfo.isGraphqlSpecifiedScalar(element.node.name)) { + return false + } + + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLDirective( + element: NadelSchemaTraverserElement.Directive, + ): Boolean { + // Ignore built in directives + if (DirectiveInfo.isGraphqlSpecifiedDirective(element.node.name)) { + return false + } + + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLAppliedDirective( + element: NadelSchemaTraverserElement.AppliedDirective, + ): Boolean { + coordinates.add(element.coordinates()) + return false // Don't traverse argument further + } + + override fun visitGraphQLAppliedDirectiveArgument( + element: NadelSchemaTraverserElement.AppliedDirectiveArgument, + ): Boolean { + coordinates.add(element.coordinates()) + return true + } +} + +internal class NadelSchemaDefinitionCoordinateCollectorTraverserVisitor( + private val coordinates: MutableCollection, + private val definitionByName: Map, +) : NadelSchemaDefinitionTraverserVisitor { + override fun visitGraphQLAppliedDirective(element: NadelSchemaDefinitionTraverserElement.AppliedDirective): Boolean { + coordinates.add(element.coordinates()) + return false // Don't traverse argument further + } + + override fun visitGraphQLAppliedDirectiveArgument(element: NadelSchemaDefinitionTraverserElement.AppliedDirectiveArgument): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLArgument(element: NadelSchemaDefinitionTraverserElement.Argument): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLDirective(element: NadelSchemaDefinitionTraverserElement.Directive): Boolean { + // Ignore built in directives + if (DirectiveInfo.isGraphqlSpecifiedDirective(element.node.name)) { + return false + } + + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLEnumType(element: NadelSchemaDefinitionTraverserElement.EnumType): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLEnumValueDefinition(element: NadelSchemaDefinitionTraverserElement.EnumValueDefinition): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLFieldDefinition(element: NadelSchemaDefinitionTraverserElement.FieldDefinition): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInputObjectField(element: NadelSchemaDefinitionTraverserElement.InputObjectField): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInputObjectType(element: NadelSchemaDefinitionTraverserElement.InputObjectType): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLInterfaceType(element: NadelSchemaDefinitionTraverserElement.InterfaceType): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLObjectType(element: NadelSchemaDefinitionTraverserElement.ObjectType): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLScalarType(element: NadelSchemaDefinitionTraverserElement.ScalarType): Boolean { + // Ignore built in scalars + if (ScalarInfo.isGraphqlSpecifiedScalar(element.node.name)) { + return false + } + + coordinates.add(element.coordinates()) + return true + } + + override fun visitGraphQLUnionType(element: NadelSchemaDefinitionTraverserElement.UnionType): Boolean { + coordinates.add(element.coordinates()) + return true + } + + override fun visitTypeReference(element: NadelSchemaDefinitionTraverserElement.TypeReference): Boolean { + // Resolve definition then traverse + val typeName = element.node.unwrapAll().name + + // Can't resolve built in scalars + if (ScalarInfo.isGraphqlSpecifiedScalar(typeName) || DirectiveInfo.isGraphqlSpecifiedDirective(typeName)) { + return false + } + + val definition = definitionByName[typeName] ?: throw NullPointerException(typeName) + val resolvedElement = NadelSchemaDefinitionTraverserElement.from(definition) ?: return false + return visitElement(resolvedElement) + } + + private fun visitElement(element: NadelSchemaDefinitionTraverserElement): Boolean { + return when (element) { + is NadelSchemaDefinitionTraverserElement.AppliedDirective -> + visitGraphQLAppliedDirective(element) + is NadelSchemaDefinitionTraverserElement.AppliedDirectiveArgument -> + visitGraphQLAppliedDirectiveArgument(element) + is NadelSchemaDefinitionTraverserElement.DirectiveArgument -> + visitGraphQLArgument(element) + is NadelSchemaDefinitionTraverserElement.FieldArgument -> + visitGraphQLArgument(element) + is NadelSchemaDefinitionTraverserElement.EnumType -> + visitGraphQLEnumType(element) + is NadelSchemaDefinitionTraverserElement.EnumValueDefinition -> + visitGraphQLEnumValueDefinition(element) + is NadelSchemaDefinitionTraverserElement.FieldDefinition -> + visitGraphQLFieldDefinition(element) + is NadelSchemaDefinitionTraverserElement.InputObjectField -> + visitGraphQLInputObjectField(element) + is NadelSchemaDefinitionTraverserElement.InputObjectType -> + visitGraphQLInputObjectType(element) + is NadelSchemaDefinitionTraverserElement.InterfaceType -> + visitGraphQLInterfaceType(element) + is NadelSchemaDefinitionTraverserElement.ObjectType -> + visitGraphQLObjectType(element) + is NadelSchemaDefinitionTraverserElement.ScalarType -> + visitGraphQLScalarType(element) + is NadelSchemaDefinitionTraverserElement.UnionType -> + visitGraphQLUnionType(element) + is NadelSchemaDefinitionTraverserElement.Directive -> + visitGraphQLDirective(element) + is NadelSchemaDefinitionTraverserElement.TypeReference -> + visitTypeReference(element) + } + } } diff --git a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt index 01171a686..0f87a7674 100644 --- a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt @@ -1,10 +1,25 @@ package graphql.nadel.definition.coordinates +import graphql.parser.Parser import graphql.schema.idl.SchemaGenerator import org.junit.jupiter.api.Test import kotlin.test.assertTrue -class NadelSchemaMemberCoordinatesFactoryTest { +abstract class NadelSchemaMemberCoordinatesFactoryTest { + abstract fun extractCoordinates(schema: String): Set + + class GraphQLSchemaExtractorTest : NadelSchemaMemberCoordinatesFactoryTest() { + override fun extractCoordinates(schema: String): Set { + return NadelSchemaMemberCoordinatesFactory().create(SchemaGenerator.createdMockedSchema(schema)) + } + } + + class DocumentDefinitionExtractorTest : NadelSchemaMemberCoordinatesFactoryTest() { + override fun extractCoordinates(schema: String): Set { + return NadelSchemaMemberCoordinatesFactory().create(Parser().parseDocument(schema)) + } + } + @Test fun `generates schema map`() { // language=GraphQL @@ -175,29 +190,25 @@ class NadelSchemaMemberCoordinatesFactoryTest { ) // When - val coordinates = NadelSchemaMemberCoordinatesFactory().create(SchemaGenerator.createdMockedSchema(schema)) + val coordinates = extractCoordinates(schema) // Then for (expected in expectedSet) { assertTrue(coordinates.contains(expected)) } - for (actual in coordinates) { assertTrue(expectedSet.contains(actual)) } - assertTrue(coordinates.size == expectedSet.size) } @Test fun `generates coordinates for minimal schema with single query field`() { - val schema = SchemaGenerator.createdMockedSchema( - """ - type Query { - hello: String - } - """.trimIndent() - ) + val schema = """ + type Query { + hello: String + } + """.trimIndent() val expectedSet = setOf( NadelObjectCoordinates("Query"), @@ -205,7 +216,7 @@ class NadelSchemaMemberCoordinatesFactoryTest { ) // When - val coordinates = NadelSchemaMemberCoordinatesFactory().create(schema) + val coordinates = extractCoordinates(schema) // Then for (expected in expectedSet) { @@ -219,22 +230,20 @@ class NadelSchemaMemberCoordinatesFactoryTest { @Test fun `generates coordinates for schema with union type`() { - val schema = SchemaGenerator.createdMockedSchema( - """ - type Query { - result: SearchResult - } - union SearchResult = User | Product - type User { - id: ID! - name: String - } - type Product { - id: ID! - title: String - } - """.trimIndent() - ) + val schema = """ + type Query { + result: SearchResult + } + union SearchResult = User | Product + type User { + id: ID! + name: String + } + type Product { + id: ID! + title: String + } + """.trimIndent() val expectedSet = setOf( NadelObjectCoordinates("Query"), @@ -249,7 +258,7 @@ class NadelSchemaMemberCoordinatesFactoryTest { ) // When - val coordinates = NadelSchemaMemberCoordinatesFactory().create(schema) + val coordinates = extractCoordinates(schema) // Then for (expected in expectedSet) { @@ -263,24 +272,22 @@ class NadelSchemaMemberCoordinatesFactoryTest { @Test fun `generates coordinates for schema with input type and mutation`() { - val schema = SchemaGenerator.createdMockedSchema( - """ - type Query { - user(id: ID!): User - } - type Mutation { - createUser(input: CreateUserInput!): User - } - input CreateUserInput { - name: String! - email: String - } - type User { - id: ID! - name: String - } - """.trimIndent() - ) + val schema = """ + type Query { + user(id: ID!): User + } + type Mutation { + createUser(input: CreateUserInput!): User + } + input CreateUserInput { + name: String! + email: String + } + type User { + id: ID! + name: String + } + """.trimIndent() val expectedSet = setOf( NadelObjectCoordinates("Query"), @@ -298,7 +305,7 @@ class NadelSchemaMemberCoordinatesFactoryTest { ) // When - val coordinates = NadelSchemaMemberCoordinatesFactory().create(schema) + val coordinates = extractCoordinates(schema) // Then for (expected in expectedSet) { From 976c6168959d4c59d6fe01288aaf5f0f077fb254 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Fri, 13 Mar 2026 11:06:20 +1100 Subject: [PATCH 2/4] Update test --- ...NadelSchemaMemberCoordinatesFactoryTest.kt | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt index 0f87a7674..71702af43 100644 --- a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt @@ -3,11 +3,26 @@ package graphql.nadel.definition.coordinates import graphql.parser.Parser import graphql.schema.idl.SchemaGenerator import org.junit.jupiter.api.Test +import kotlin.collections.contains import kotlin.test.assertTrue abstract class NadelSchemaMemberCoordinatesFactoryTest { abstract fun extractCoordinates(schema: String): Set + fun runTest(schema: String, expectedSet: Set) { + // When + val coordinates = extractCoordinates(schema) + + // Then + for (expected in expectedSet) { + assertTrue(coordinates.contains(expected)) + } + for (actual in coordinates) { + assertTrue(expectedSet.contains(actual)) + } + assertTrue(coordinates.size == expectedSet.size) + } + class GraphQLSchemaExtractorTest : NadelSchemaMemberCoordinatesFactoryTest() { override fun extractCoordinates(schema: String): Set { return NadelSchemaMemberCoordinatesFactory().create(SchemaGenerator.createdMockedSchema(schema)) @@ -18,6 +33,31 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { override fun extractCoordinates(schema: String): Set { return NadelSchemaMemberCoordinatesFactory().create(Parser().parseDocument(schema)) } + + @Test + fun `can generate coordinates for semantically invalid schema`() { + val schema = """ + type Query { + search: Searchable + } + + interface Searchable + + type Article implements Searchable { + body: String + } + """.trimIndent() + + val expectedSet = setOf( + NadelInterfaceCoordinates("Searchable"), + NadelObjectCoordinates("Article"), + NadelObjectCoordinates("Article").field("body"), + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("search"), + ) + + runTest(schema, expectedSet) + } } @Test @@ -189,17 +229,7 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { NadelUnionCoordinates("UserUnion"), ) - // When - val coordinates = extractCoordinates(schema) - - // Then - for (expected in expectedSet) { - assertTrue(coordinates.contains(expected)) - } - for (actual in coordinates) { - assertTrue(expectedSet.contains(actual)) - } - assertTrue(coordinates.size == expectedSet.size) + runTest(schema, expectedSet) } @Test @@ -215,17 +245,7 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { NadelObjectCoordinates("Query").field("hello"), ) - // When - val coordinates = extractCoordinates(schema) - - // Then - for (expected in expectedSet) { - assertTrue(coordinates.contains(expected)) - } - for (actual in coordinates) { - assertTrue(expectedSet.contains(actual)) - } - assertTrue(coordinates.size == expectedSet.size) + runTest(schema, expectedSet) } @Test @@ -257,17 +277,7 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { NadelObjectCoordinates("Product").field("title"), ) - // When - val coordinates = extractCoordinates(schema) - - // Then - for (expected in expectedSet) { - assertTrue(coordinates.contains(expected)) - } - for (actual in coordinates) { - assertTrue(expectedSet.contains(actual)) - } - assertTrue(coordinates.size == expectedSet.size) + runTest(schema, expectedSet) } @Test @@ -304,16 +314,6 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { NadelObjectCoordinates("User").field("name"), ) - // When - val coordinates = extractCoordinates(schema) - - // Then - for (expected in expectedSet) { - assertTrue(coordinates.contains(expected)) - } - for (actual in coordinates) { - assertTrue(expectedSet.contains(actual)) - } - assertTrue(coordinates.size == expectedSet.size) + runTest(schema, expectedSet) } } From 56f1e3dc81d95c8252a8fb71cd97a275e18a0584 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Fri, 13 Mar 2026 11:15:52 +1100 Subject: [PATCH 3/4] Add more tests --- ...NadelSchemaMemberCoordinatesFactoryTest.kt | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt index 71702af43..dcaa476c8 100644 --- a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt @@ -58,6 +58,168 @@ abstract class NadelSchemaMemberCoordinatesFactoryTest { runTest(schema, expectedSet) } + + @Test + fun `can generate coordinates for schema with duplicate type definitions`() { + val schema = """ + type Query { + user: User + } + + type User { + id: ID! + name: String + } + + type User { + id: ID! + email: String + } + """.trimIndent() + + val expectedSet = setOf( + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("user"), + NadelObjectCoordinates("User"), + NadelObjectCoordinates("User").field("id"), + NadelObjectCoordinates("User").field("name"), + NadelObjectCoordinates("User").field("email"), + ) + + runTest(schema, expectedSet) + } + + @Test + fun `can generate coordinates for schema with interface implementing another interface without fields`() { + val schema = """ + type Query { + entity: Entity + } + + interface Node { + id: ID! + } + + interface Entity implements Node + + type Product implements Entity { + id: ID! + name: String + } + """.trimIndent() + + val expectedSet = setOf( + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("entity"), + NadelInterfaceCoordinates("Node"), + NadelInterfaceCoordinates("Node").field("id"), + NadelInterfaceCoordinates("Entity"), + NadelObjectCoordinates("Product"), + NadelObjectCoordinates("Product").field("id"), + NadelObjectCoordinates("Product").field("name"), + ) + + runTest(schema, expectedSet) + } + + @Test + fun `can generate coordinates for schema with enum with no values`() { + val schema = """ + type Query { + status: Status + } + + enum Status + + type Placeholder { + value: String + } + """.trimIndent() + + val expectedSet = setOf( + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("status"), + NadelEnumCoordinates("Status"), + NadelObjectCoordinates("Placeholder"), + NadelObjectCoordinates("Placeholder").field("value"), + ) + + runTest(schema, expectedSet) + } + + @Test + fun `can generate coordinates for schema with type missing interface field`() { + val schema = """ + type Query { + node: Node + } + + interface Node { + id: ID! + name: String! + } + + type User implements Node { + id: ID! + } + + type Product { + id: ID! + } + """.trimIndent() + + val expectedSet = setOf( + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("node"), + NadelInterfaceCoordinates("Node"), + NadelInterfaceCoordinates("Node").field("id"), + NadelInterfaceCoordinates("Node").field("name"), + NadelObjectCoordinates("User"), + NadelObjectCoordinates("User").field("id"), + NadelObjectCoordinates("Product"), + NadelObjectCoordinates("Product").field("id"), + ) + + runTest(schema, expectedSet) + } + + @Test + fun `can generate coordinates for schema with interface field with incompatible nullability`() { + val schema = """ + type Query { + node: Node + } + + interface Node { + id: ID! + name: String! + } + + type User implements Node { + id: ID! + name: String + } + + type Product { + id: ID! + } + """.trimIndent() + + val expectedSet = setOf( + NadelObjectCoordinates("Query"), + NadelObjectCoordinates("Query").field("node"), + NadelInterfaceCoordinates("Node"), + NadelInterfaceCoordinates("Node").field("id"), + NadelInterfaceCoordinates("Node").field("name"), + NadelObjectCoordinates("User"), + NadelObjectCoordinates("User").field("id"), + NadelObjectCoordinates("User").field("name"), + NadelObjectCoordinates("Product"), + NadelObjectCoordinates("Product").field("id"), + ) + + runTest(schema, expectedSet) + } } @Test From b9e91c9233ec0ba014d9e4cb3be11477dfc2499d Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Fri, 13 Mar 2026 11:19:07 +1100 Subject: [PATCH 4/4] Remove unused --- .../coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt index dcaa476c8..afea16a97 100644 --- a/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/definition/coordinates/NadelSchemaMemberCoordinatesFactoryTest.kt @@ -3,7 +3,6 @@ package graphql.nadel.definition.coordinates import graphql.parser.Parser import graphql.schema.idl.SchemaGenerator import org.junit.jupiter.api.Test -import kotlin.collections.contains import kotlin.test.assertTrue abstract class NadelSchemaMemberCoordinatesFactoryTest {