Skip to content

Commit bfc2a7c

Browse files
committed
fixes and improves field visibility
1 parent 9035f2d commit bfc2a7c

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import graphql.schema.GraphQLInputObjectType
99
import graphql.schema.GraphQLObjectType
1010
import graphql.schema.GraphQLSchema
1111
import graphql.schema.TypeResolver
12+
import graphql.schema.idl.SchemaPrinter
1213
import spock.lang.Specification
1314

1415
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition
@@ -23,6 +24,20 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
2324
return directives.find({ directive -> directive.name == "private" }) == null
2425
})
2526

27+
/**
28+
* Helper method to validate that a schema is valid by printing it and re-parsing it.
29+
* This ensures the transformation always produces a valid schema.
30+
*/
31+
void assertSchemaIsValid(GraphQLSchema schema) {
32+
def printer = new SchemaPrinter(SchemaPrinter.Options.defaultOptions()
33+
.includeDirectives(true)
34+
.includeScalarTypes(true))
35+
def printedSchema = printer.print(schema)
36+
// Parse the printed schema to verify it's valid
37+
def reparsedSchema = TestUtil.schema(printedSchema)
38+
assert reparsedSchema != null: "Re-parsed schema should not be null"
39+
}
40+
2641
def "can remove a private field"() {
2742
given:
2843
GraphQLSchema schema = TestUtil.schema("""
@@ -49,6 +64,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
4964
then:
5065
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
5166
restrictedSchema.getType("BillingStatus") == null
67+
assertSchemaIsValid(restrictedSchema)
5268
}
5369

5470
def "can remove a type associated with a private field"() {
@@ -84,6 +100,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
84100
then:
85101
restrictedSchema.getType("BillingStatus") == null
86102
restrictedSchema.getType("SuperSecretCustomerData") == null
103+
assertSchemaIsValid(restrictedSchema)
87104
}
88105

89106
def "removes concrete types referenced only by interface"() {
@@ -122,6 +139,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
122139
and: "After the private field is removed, they become unreachable and are removed"
123140
restrictedSchema.getType("BillingStatus") == null
124141
restrictedSchema.getType("SuperSecretCustomerData") == null
142+
assertSchemaIsValid(restrictedSchema)
125143
}
126144

127145
def "interface and its implementations that have both private and public reference is retained"() {
@@ -160,6 +178,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
160178
then:
161179
restrictedSchema.getType("SuperSecretCustomerData") != null
162180
restrictedSchema.getType("BillingStatus") != null
181+
assertSchemaIsValid(restrictedSchema)
163182
}
164183

165184

@@ -207,6 +226,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
207226
restrictedSchema.getType("X") != null
208227
restrictedSchema.getType("Foo") != null
209228
restrictedSchema.getType("X2") != null
229+
assertSchemaIsValid(restrictedSchema)
210230
}
211231

212232
def "union types"() {
@@ -246,6 +266,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
246266
restrictedSchema.getType("FooOrBar") == null
247267
restrictedSchema.getType("Bar") == null
248268
restrictedSchema.getType("Foo") != null
269+
assertSchemaIsValid(restrictedSchema)
249270
}
250271

251272
def "union type with reference by private interface removed"() {
@@ -293,6 +314,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
293314
restrictedSchema.getType("Baz") == null
294315
restrictedSchema.getType("Bing") == null
295316
restrictedSchema.getType("FooOrBar") == null
317+
assertSchemaIsValid(restrictedSchema)
296318
}
297319

298320

@@ -329,6 +351,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
329351
then:
330352
restrictedSchema.getType("BillingStatus") != null
331353
restrictedSchema.getType("SuperSecretCustomerData") != null
354+
assertSchemaIsValid(restrictedSchema)
332355
}
333356

334357
def "leaves interface types referenced only by concrete types"() {
@@ -400,6 +423,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
400423
then:
401424
restrictedSchema.getType("BillingStatus") == null
402425
restrictedSchema.getType("SuperSecretCustomerData") == null
426+
assertSchemaIsValid(restrictedSchema)
403427
}
404428

405429
def "leaves interface type if has private and public reference"() {
@@ -442,6 +466,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
442466
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
443467
restrictedSchema.getType("BillingStatus") == null
444468
restrictedSchema.getType("SuperSecretCustomerData") != null
469+
assertSchemaIsValid(restrictedSchema)
445470
}
446471

447472
def "leaves concrete type if has public and private"() {
@@ -478,6 +503,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
478503
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
479504
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("publicBillingStatus") != null
480505
restrictedSchema.getType("BillingStatus") != null
506+
assertSchemaIsValid(restrictedSchema)
481507
}
482508

483509
def "removes interface type if only private reference with multiple interfaces"() {
@@ -524,6 +550,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
524550
restrictedSchema.getType("SuperSecretCustomerData") == null
525551
restrictedSchema.getType("Billable") == null
526552
restrictedSchema.getType("PublicView") != null
553+
assertSchemaIsValid(restrictedSchema)
527554
}
528555

529556
def "primitive types are retained"() {
@@ -546,6 +573,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
546573
then:
547574
restrictedSchema.getType("String") != null
548575
restrictedSchema.getType("Boolean") != null
576+
assertSchemaIsValid(restrictedSchema)
549577
}
550578

551579
def "root types with different names are supported"() {
@@ -577,6 +605,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
577605

578606
then:
579607
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
608+
assertSchemaIsValid(restrictedSchema)
580609
}
581610

582611
def "fields and types are removed from subscriptions and mutations"() {
@@ -620,6 +649,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
620649
then:
621650
restrictedSchema.getType("Foo") == null
622651
restrictedSchema.getType("Bar") == null
652+
assertSchemaIsValid(restrictedSchema)
623653
}
624654

625655
def "type with both private and public transitive references is retained"() {
@@ -653,6 +683,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
653683
then:
654684
(restrictedSchema.getType("Foo") as GraphQLObjectType).getFieldDefinition("baz") == null
655685
restrictedSchema.getType("Baz") != null
686+
assertSchemaIsValid(restrictedSchema)
656687
}
657688

658689
def "type with multiple private parent references is removed"() {
@@ -688,6 +719,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
688719
(restrictedSchema.getType("Foo") as GraphQLObjectType).getFieldDefinition("baz") == null
689720
(restrictedSchema.getType("Bar") as GraphQLObjectType).getFieldDefinition("baz") == null
690721
restrictedSchema.getType("Baz") == null
722+
assertSchemaIsValid(restrictedSchema)
691723
}
692724

693725
def "type with multiple private grandparent references is removed"() {
@@ -722,6 +754,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
722754
restrictedSchema.getType("Foo") == null
723755
restrictedSchema.getType("Bar") == null
724756
restrictedSchema.getType("Baz") == null
757+
assertSchemaIsValid(restrictedSchema)
725758
}
726759

727760
def "type with circular reference can be traversed"() {
@@ -746,6 +779,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
746779
then:
747780
(restrictedSchema.getType("Foo") as GraphQLObjectType).getFieldDefinition("foo2") == null
748781
restrictedSchema.getType("Foo") != null
782+
assertSchemaIsValid(restrictedSchema)
749783
}
750784

751785
def "input types can have private fields"() {
@@ -784,6 +818,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
784818
(restrictedSchema.getType("FooInput") as GraphQLInputObjectType).getFieldDefinition("foo") == null
785819
restrictedSchema.getType("FooInput") != null
786820
restrictedSchema.getType("BarInput") == null
821+
assertSchemaIsValid(restrictedSchema)
787822
}
788823

789824
def "enum types can be removed"() {
@@ -831,6 +866,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
831866
restrictedSchema.getType("FooInput") != null
832867
restrictedSchema.getType("BarEnum") == null
833868
restrictedSchema.getType("FooEnum") == null
869+
assertSchemaIsValid(restrictedSchema)
834870
}
835871

836872
def "unreferenced types can have fields removed, and the referenced types must be removed as well if they are not used"() {
@@ -868,6 +904,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
868904

869905
and: "Bing is also an additional type not reachable from roots, so it is preserved"
870906
restrictedSchema.getType("Bing") != null
907+
assertSchemaIsValid(restrictedSchema)
871908
}
872909

873910
def "unreferenced types can have fields removed, and referenced type must not be removed if used elsewhere in the connected graph"() {
@@ -906,6 +943,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
906943

907944
and: "since Bing is used in the connected graph, it MUST not be removed"
908945
restrictedSchema.getType("Bing") != null
946+
assertSchemaIsValid(restrictedSchema)
909947
}
910948

911949
def "unreferenced types can have fields removed, and referenced type must not be removed if used elsewhere"() {
@@ -945,6 +983,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
945983
and: "since Bing is used elsewhere (Bar.foo), it SHOULD not be removed"
946984
restrictedSchema.getType("Bing") != null
947985
}
986+
948987
def "use type references - private field declared with interface type removes both concrete and interface"() {
949988
given:
950989
def query = newObject()
@@ -1041,6 +1080,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
10411080
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
10421081
restrictedSchema.getType("BillingStatus") == null
10431082
restrictedSchema.getType("SuperSecretCustomerData") == null
1083+
assertSchemaIsValid(restrictedSchema)
10441084
}
10451085

10461086
def "use type references - unreferenced types are removed"() {
@@ -1074,6 +1114,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
10741114
then:
10751115
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
10761116
restrictedSchema.getType("BillingStatus") == null
1117+
assertSchemaIsValid(restrictedSchema)
10771118
}
10781119

10791120
def "before and after transformation hooks are run"() {
@@ -1148,6 +1189,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
11481189
then:
11491190
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
11501191
restrictedSchema.getType("BillingStatus") != null
1192+
assertSchemaIsValid(restrictedSchema)
11511193
}
11521194

11531195
def "handles types that become visible via types reachable by interface that implements interface"() {
@@ -1193,6 +1235,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
11931235
then:
11941236
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
11951237
restrictedSchema.getType("BillingStatus") != null
1238+
assertSchemaIsValid(restrictedSchema)
11961239
}
11971240

11981241
def "can remove a field with a directive containing enum argument"() {
@@ -1225,6 +1268,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
12251268
then:
12261269
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
12271270
restrictedSchema.getType("BillingStatus") == null
1271+
assertSchemaIsValid(restrictedSchema)
12281272
}
12291273

12301274
def "can remove a field with a directive containing type argument"() {
@@ -1256,7 +1300,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
12561300
then:
12571301
(restrictedSchema.getType("Account") as GraphQLObjectType).getFieldDefinition("billingStatus") == null
12581302
restrictedSchema.getType("BillingStatus") == null
1259-
1303+
assertSchemaIsValid(restrictedSchema)
12601304
}
12611305

12621306
def "remove all fields from a type which is referenced via additional types"() {
@@ -1288,6 +1332,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
12881332
GraphQLSchema restrictedSchema = visibilitySchemaTransformation.apply(patchedSchema)
12891333
then:
12901334
(restrictedSchema.getType("Foo") as GraphQLObjectType).getFieldDefinition("toDelete") == null
1335+
assertSchemaIsValid(restrictedSchema)
12911336
}
12921337

12931338
def "remove field from a type which is referenced via additional types and an additional not reachable child is deleted"() {
@@ -1429,6 +1474,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
14291474
// Rental should only have id field (customer is private)
14301475
(restrictedSchema.getType("Rental") as GraphQLObjectType).getFieldDefinition("id") != null
14311476
(restrictedSchema.getType("Rental") as GraphQLObjectType).getFieldDefinition("customer") == null
1477+
assertSchemaIsValid(restrictedSchema)
14321478
}
14331479

14341480
def "originally unused type subgraph is fully preserved with private field removal"() {
@@ -1489,6 +1535,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
14891535

14901536
and: "PrivateOnlyType is also an additional type not reachable from roots, so it is preserved"
14911537
restrictedSchema.getType("PrivateOnlyType") != null
1538+
assertSchemaIsValid(restrictedSchema)
14921539
}
14931540

14941541
def "multiple originally unused type subgraphs are all preserved"() {
@@ -1536,6 +1583,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
15361583
and: "Second unused subgraph is fully preserved"
15371584
restrictedSchema.getType("UnusedB") != null
15381585
restrictedSchema.getType("UnusedBChild") != null
1586+
assertSchemaIsValid(restrictedSchema)
15391587
}
15401588

15411589
def "findRootUnusedTypes considers interface implementations as reachable from roots"() {
@@ -1576,6 +1624,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
15761624

15771625
and: "TrulyUnused is an additional type not reachable from roots, so it is preserved as root unused type"
15781626
restrictedSchema.getType("TrulyUnused") != null
1627+
assertSchemaIsValid(restrictedSchema)
15791628
}
15801629

15811630
def "findRootUnusedTypes ignores special introspection types starting with underscore"() {
@@ -1613,6 +1662,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
16131662
and: "Special introspection types starting with _ should be preserved"
16141663
restrictedSchema.getType("_AppliedDirective") != null
16151664
restrictedSchema.getType("_DirectiveArgument") != null
1665+
assertSchemaIsValid(restrictedSchema)
16161666
}
16171667

16181668
def "custom scalar types are removed when only referenced by private fields"() {
@@ -1643,6 +1693,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
16431693

16441694
and: "SecretToken scalar is only used by private field, so it should be removed"
16451695
restrictedSchema.getType("SecretToken") == null
1696+
assertSchemaIsValid(restrictedSchema)
16461697
}
16471698

16481699
def "originally unused enum types are preserved"() {
@@ -1674,6 +1725,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
16741725

16751726
and: "UnusedEnum is an additional type not reachable from roots, so it is preserved"
16761727
restrictedSchema.getType("UnusedEnum") != null
1728+
assertSchemaIsValid(restrictedSchema)
16771729
}
16781730

16791731
def "originally unused scalar types are preserved"() {
@@ -1697,6 +1749,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
16971749

16981750
and: "UnusedScalar is an additional type not reachable from roots, so it is preserved"
16991751
restrictedSchema.getType("UnusedScalar") != null
1752+
assertSchemaIsValid(restrictedSchema)
17001753
}
17011754

17021755
def "enum and scalar types only reachable via private fields are removed"() {
@@ -1731,6 +1784,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
17311784
and: "SecretScalar and SecretEnum are only reachable via private fields, so they should be removed"
17321785
restrictedSchema.getType("SecretScalar") == null
17331786
restrictedSchema.getType("SecretEnum") == null
1787+
assertSchemaIsValid(restrictedSchema)
17341788
}
17351789

17361790
def "input object type only reachable via private field is removed along with nested inputs"() {
@@ -1766,6 +1820,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
17661820
and: "SecretInput and NestedSecretInput should be removed as they're only reachable via private field"
17671821
restrictedSchema.getType("SecretInput") == null
17681822
restrictedSchema.getType("NestedSecretInput") == null
1823+
assertSchemaIsValid(restrictedSchema)
17691824
}
17701825

17711826
def "nested input types are removed when parent input field is private"() {
@@ -1807,6 +1862,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
18071862
and: "SecretDataInput and DeepSecretInput should be removed as they're only reachable via private field"
18081863
restrictedSchema.getType("SecretDataInput") == null
18091864
restrictedSchema.getType("DeepSecretInput") == null
1865+
assertSchemaIsValid(restrictedSchema)
18101866
}
18111867

18121868
def "originally unused input types are preserved"() {
@@ -1836,6 +1892,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
18361892

18371893
and: "UnusedInput is an additional type not reachable from roots, so it is preserved"
18381894
restrictedSchema.getType("UnusedInput") != null
1895+
assertSchemaIsValid(restrictedSchema)
18391896
}
18401897

18411898
def "input types only reachable via private input fields are removed"() {
@@ -1871,6 +1928,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
18711928

18721929
and: "PrivateRefInput should be removed as it's only reachable via private input field"
18731930
restrictedSchema.getType("PrivateRefInput") == null
1931+
assertSchemaIsValid(restrictedSchema)
18741932
}
18751933

18761934
def "input type used by both public and private fields is preserved"() {
@@ -1903,6 +1961,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
19031961

19041962
and: "SharedInput should be preserved because it's still used by publicAction"
19051963
restrictedSchema.getType("SharedInput") != null
1964+
assertSchemaIsValid(restrictedSchema)
19061965
}
19071966

19081967
def "input field with nested input referencing enum and scalar"() {
@@ -1948,6 +2007,7 @@ class FieldVisibilitySchemaTransformationTest extends Specification {
19482007
restrictedSchema.getType("SecretConfigInput") == null
19492008
restrictedSchema.getType("SecretToken") == null
19502009
restrictedSchema.getType("SecretLevel") == null
2010+
assertSchemaIsValid(restrictedSchema)
19512011
}
19522012

19532013
}

0 commit comments

Comments
 (0)