@@ -17,6 +17,167 @@ export const createPolicy = (level: RuleInstanceSeverityLevel): SchemaPolicyInpu
1717
1818describe ( 'Schema policy checks' , ( ) => {
1919 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+
20181 it ( 'Federation project with policy with only warnings, should check only the part that was changed' , async ( ) => {
21182 const { tokens, policy } = await prepareProject ( ProjectType . Federation ) ;
22183 await policy . setOrganizationSchemaPolicy (
0 commit comments