@@ -17,6 +17,167 @@ export const createPolicy = (level: RuleInstanceSeverityLevel): SchemaPolicyInpu
17
17
18
18
describe ( 'Schema policy checks' , ( ) => {
19
19
describe ( 'model: composite' , ( ) => {
20
+ it ( 'no-unreachable-types issue: directives are not scanned/marked as unused' , async ( ) => {
21
+ const policyObject : SchemaPolicyInput = {
22
+ rules : [
23
+ {
24
+ ruleId : 'no-unreachable-types' ,
25
+ severity : RuleInstanceSeverityLevel . Error ,
26
+ } ,
27
+ ] ,
28
+ } ;
29
+
30
+ const { tokens, policy } = await prepareProject ( ProjectType . Federation ) ;
31
+ await policy . setOrganizationSchemaPolicy ( policyObject , true ) ;
32
+ const cli = createCLI ( tokens . registry ) ;
33
+
34
+ await cli . publish ( {
35
+ sdl : /* GraphQL */ `
36
+ type Query {
37
+ a: String!
38
+ }
39
+ ` ,
40
+ serviceName : 'a' ,
41
+ serviceUrl : 'https://api.com/a' ,
42
+ expect : 'latest-composable' ,
43
+ } ) ;
44
+
45
+ await cli . publish ( {
46
+ sdl : /* GraphQL */ `
47
+ type Query {
48
+ b: String!
49
+ }
50
+ ` ,
51
+ serviceName : 'b' ,
52
+ serviceUrl : 'https://api.com/b' ,
53
+ expect : 'latest-composable' ,
54
+ } ) ;
55
+
56
+ // In this example, the policy checks sees the "hasRole" directive in the schema
57
+ // because we are using composeDirective.
58
+ const rawMessage = await cli . check ( {
59
+ sdl : /* GraphQL */ `
60
+ extend schema
61
+ @link(url: "https://specs.apollo.dev/link/v1.0")
62
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective"])
63
+ @link(url: "https://myspecs.dev/myDirective/v1.0", import: ["@hasRole"])
64
+ @composeDirective(name: "@hasRole")
65
+
66
+ scalar Unused
67
+
68
+ scalar Used
69
+
70
+ scalar UsedInInput
71
+
72
+ directive @hasRole(role: Role!) on QUERY | MUTATION | FIELD_DEFINITION
73
+
74
+ enum Role {
75
+ admin
76
+ user
77
+ }
78
+
79
+ enum Permission {
80
+ read
81
+ write
82
+ create
83
+ delete
84
+ }
85
+
86
+ type Query {
87
+ userRole(roleID: Int!): UserRole! @hasRole(role: admin)
88
+ scalar(input: UsedInInput!): Used
89
+ }
90
+
91
+ type UserRole {
92
+ id: ID!
93
+ name: String!
94
+ }
95
+ ` ,
96
+ serviceName : 'c' ,
97
+ expect : 'rejected' ,
98
+ } ) ;
99
+ const message = stripAnsi ( rawMessage ) ;
100
+
101
+ expect ( message ) . toContain ( `Detected 2 errors` ) ;
102
+ expect ( message . split ( '\n' ) . slice ( 1 ) ) . toEqual ( [
103
+ '✖ Detected 2 errors' ,
104
+ '' ,
105
+ ' - Scalar type `Unused` is unreachable. (source: policy-no-unreachable-types)' ,
106
+ ' - Enum type `Permission` is unreachable. (source: policy-no-unreachable-types)' ,
107
+ '' ,
108
+ 'ℹ Detected 9 changes' ,
109
+ '' ,
110
+ ' Safe changes:' ,
111
+ ' - Type Permission was added' ,
112
+ ' - Type Role was added' ,
113
+ ' - Type Unused was added' ,
114
+ ' - Type Used was added' ,
115
+ ' - Type UsedInInput was added' ,
116
+ ' - Type UserRole was added' ,
117
+ ' - Field scalar was added to object type Query' ,
118
+ ' - Field userRole was added to object type Query' ,
119
+ ' - Directive hasRole was added' ,
120
+ '' ,
121
+ 'View full report:' ,
122
+ expect . any ( String ) ,
123
+ '' ,
124
+ ] ) ;
125
+
126
+ // But in this one, we are not using composeDirective, so the final compose directive
127
+ // is not visible by the policy checker, and the policy checker will not detect it.
128
+ // This is why it's being reported an unused, and also other related inputs/types.
129
+ const rawMessage2 = await cli . check ( {
130
+ sdl : /* GraphQL */ `
131
+ scalar Unused
132
+
133
+ scalar Used
134
+
135
+ scalar UsedInInput
136
+
137
+ directive @hasRole(role: Role!) on QUERY | MUTATION | FIELD_DEFINITION
138
+
139
+ enum Role {
140
+ admin
141
+ user
142
+ }
143
+
144
+ enum Permission {
145
+ read
146
+ write
147
+ create
148
+ delete
149
+ }
150
+
151
+ type Query {
152
+ userRole(roleID: Int!): UserRole! @hasRole(role: admin)
153
+ scalar(input: UsedInInput!): Used
154
+ }
155
+
156
+ type UserRole {
157
+ id: ID!
158
+ name: String!
159
+ }
160
+ ` ,
161
+ serviceName : 'c' ,
162
+ expect : 'rejected' ,
163
+ } ) ;
164
+ const message2 = stripAnsi ( rawMessage2 ) ;
165
+
166
+ expect ( message2 ) . toContain ( `Detected 4 errors` ) ;
167
+ expect ( message2 ) . toContain (
168
+ `Scalar type \`Unused\` is unreachable. (source: policy-no-unreachable-types)` ,
169
+ ) ;
170
+ expect ( message2 ) . toContain (
171
+ `Directive \`hasRole\` is unreachable. (source: policy-no-unreachable-types)` ,
172
+ ) ;
173
+ expect ( message2 ) . toContain (
174
+ `Enum type \`Role\` is unreachable. (source: policy-no-unreachable-types)` ,
175
+ ) ;
176
+ expect ( message2 ) . toContain (
177
+ `Enum type \`Permission\` is unreachable. (source: policy-no-unreachable-types)` ,
178
+ ) ;
179
+ } ) ;
180
+
20
181
it ( 'Federation project with policy with only warnings, should check only the part that was changed' , async ( ) => {
21
182
const { tokens, policy } = await prepareProject ( ProjectType . Federation ) ;
22
183
await policy . setOrganizationSchemaPolicy (
0 commit comments