Skip to content

Commit 49dca7e

Browse files
committed
Test to showcase bug
1 parent bfd8789 commit 49dca7e

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

src/test/groovy/graphql/schema/transform/FieldVisibilitySchemaTransformationTest.groovy

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.schema.transform
22

3+
import graphql.AssertException
34
import graphql.Scalars
45
import graphql.TestUtil
56
import graphql.schema.GraphQLAppliedDirective
@@ -1346,5 +1347,126 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
13461347
(restrictedSchema.getType("Input") as GraphQLInputObjectType).getFieldDefinition("toDelete") == null
13471348
}
13481349

1350+
/**
1351+
* This test reproduces the issue 4133 bug pattern with FieldVisibilitySchemaTransformation.
1352+
*
1353+
* The problem occurs when:
1354+
* 1. A field is marked @private and gets deleted
1355+
* 2. The traversal doesn't continue to children of deleted nodes
1356+
* 3. Types only reachable through deleted fields are not visited
1357+
* 4. Those unvisited types may have circular references using GraphQLTypeReference
1358+
* 5. During schema rebuild, stale object references point to types with unresolvable TypeReferences
1359+
*
1360+
* Schema structure:
1361+
* - Query.rental @private -> Rental (not visited because parent field deleted)
1362+
* - Query.customer -> Customer (visited)
1363+
* - Customer.rental -> TypeReference("Rental") (placeholder, doesn't cause Rental to be visited)
1364+
* - Rental.customer -> Customer (actual object reference to ORIGINAL unmodified Customer)
1365+
* - Customer.payment @private -> Payment (deleted, Payment not visited)
1366+
* - Payment.inventory -> TypeReference("Inventory")
1367+
* - Inventory not in schema -> ERROR during type reference resolution
1368+
*
1369+
* WORKAROUND: Add all types to additionalTypes before applying the transformation.
1370+
* This ensures all types are visited during the transformation.
1371+
*/
1372+
def "issue 4133 - circular references with private fields - demonstrates the bug"() {
1373+
given:
1374+
GraphQLSchema schema = TestUtil.schema("""
1375+
1376+
directive @private on FIELD_DEFINITION
1377+
1378+
type Query {
1379+
rental: Rental @private
1380+
customer: Customer
1381+
}
1382+
1383+
type Customer {
1384+
rental: Rental
1385+
payment: Payment @private
1386+
}
1387+
1388+
type Rental {
1389+
id: ID
1390+
customer: Customer @private
1391+
}
1392+
1393+
type Payment {
1394+
inventory: Inventory @private
1395+
}
1396+
1397+
type Inventory {
1398+
payment: Payment @private
1399+
}
1400+
""")
1401+
1402+
when:
1403+
visibilitySchemaTransformation.apply(schema)
1404+
1405+
then:
1406+
// This demonstrates the bug - without the workaround, we get "type not found" error
1407+
def e = thrown(AssertException)
1408+
e.message.contains("not found in schema")
1409+
}
1410+
1411+
/**
1412+
* This test shows the workaround for issue 4133 - add all types to additionalTypes
1413+
* before applying the transformation to ensure all types are visited.
1414+
*/
1415+
def "issue 4133 - circular references with private fields - workaround with additionalTypes"() {
1416+
given:
1417+
GraphQLSchema schema = TestUtil.schema("""
1418+
1419+
directive @private on FIELD_DEFINITION
1420+
1421+
type Query {
1422+
rental: Rental @private
1423+
customer: Customer
1424+
}
1425+
1426+
type Customer {
1427+
rental: Rental
1428+
payment: Payment @private
1429+
}
1430+
1431+
type Rental {
1432+
id: ID
1433+
customer: Customer @private
1434+
}
1435+
1436+
type Payment {
1437+
inventory: Inventory @private
1438+
}
1439+
1440+
type Inventory {
1441+
payment: Payment @private
1442+
}
1443+
""")
1444+
1445+
// WORKAROUND: Add all types to additionalTypes to ensure they are all visited
1446+
def patchedSchema = schema.transform { builder ->
1447+
schema.typeMap.each { entry ->
1448+
def type = entry.value
1449+
if (type != schema.queryType && type != schema.mutationType && type != schema.subscriptionType) {
1450+
builder.additionalType(type)
1451+
}
1452+
}
1453+
}
1454+
1455+
when:
1456+
GraphQLSchema restrictedSchema = visibilitySchemaTransformation.apply(patchedSchema)
1457+
1458+
then:
1459+
// Query should only have customer field (rental is private)
1460+
(restrictedSchema.getType("Query") as GraphQLObjectType).getFieldDefinition("rental") == null
1461+
(restrictedSchema.getType("Query") as GraphQLObjectType).getFieldDefinition("customer") != null
1462+
1463+
// Customer should only have rental field (payment is private)
1464+
(restrictedSchema.getType("Customer") as GraphQLObjectType).getFieldDefinition("rental") != null
1465+
(restrictedSchema.getType("Customer") as GraphQLObjectType).getFieldDefinition("payment") == null
1466+
1467+
// Rental should only have id field (customer is private)
1468+
(restrictedSchema.getType("Rental") as GraphQLObjectType).getFieldDefinition("id") != null
1469+
(restrictedSchema.getType("Rental") as GraphQLObjectType).getFieldDefinition("customer") == null
1470+
}
13491471

13501472
}

0 commit comments

Comments
 (0)