@apollo/gateway 2.4.5 → 2.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__generated__/graphqlTypes.d.ts +19 -1
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js +1 -0
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/package.json +4 -4
- package/src/__generated__/graphqlTypes.ts +33 -2
- package/src/__mocks__/tsconfig.json +0 -7
- package/src/__tests__/.gitkeep +0 -0
- package/src/__tests__/CucumberREADME.md +0 -96
- package/src/__tests__/build-query-plan.feature +0 -1471
- package/src/__tests__/buildQueryPlan.test.ts +0 -1225
- package/src/__tests__/executeQueryPlan.conditions.test.ts +0 -1488
- package/src/__tests__/executeQueryPlan.introspection.test.ts +0 -140
- package/src/__tests__/executeQueryPlan.test.ts +0 -6140
- package/src/__tests__/execution-utils.ts +0 -124
- package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +0 -195
- package/src/__tests__/gateway/buildService.test.ts +0 -249
- package/src/__tests__/gateway/endToEnd.test.ts +0 -486
- package/src/__tests__/gateway/executor.test.ts +0 -96
- package/src/__tests__/gateway/extensions.test.ts +0 -37
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +0 -239
- package/src/__tests__/gateway/opentelemetry.test.ts +0 -123
- package/src/__tests__/gateway/queryPlanCache.test.ts +0 -231
- package/src/__tests__/gateway/queryPlannerConfig.test.ts +0 -101
- package/src/__tests__/gateway/reporting.test.ts +0 -616
- package/src/__tests__/gateway/supergraphSdl.test.ts +0 -396
- package/src/__tests__/gateway/testUtils.ts +0 -89
- package/src/__tests__/integration/abstract-types.test.ts +0 -1861
- package/src/__tests__/integration/aliases.test.ts +0 -180
- package/src/__tests__/integration/boolean.test.ts +0 -279
- package/src/__tests__/integration/complex-key.test.ts +0 -197
- package/src/__tests__/integration/configuration.test.ts +0 -404
- package/src/__tests__/integration/custom-directives.test.ts +0 -174
- package/src/__tests__/integration/execution-style.test.ts +0 -35
- package/src/__tests__/integration/fragments.test.ts +0 -237
- package/src/__tests__/integration/list-key.test.ts +0 -128
- package/src/__tests__/integration/logger.test.ts +0 -122
- package/src/__tests__/integration/managed.test.ts +0 -319
- package/src/__tests__/integration/merge-arrays.test.ts +0 -34
- package/src/__tests__/integration/multiple-key.test.ts +0 -327
- package/src/__tests__/integration/mutations.test.ts +0 -287
- package/src/__tests__/integration/networkRequests.test.ts +0 -542
- package/src/__tests__/integration/nockMocks.ts +0 -157
- package/src/__tests__/integration/provides.test.ts +0 -77
- package/src/__tests__/integration/requires.test.ts +0 -359
- package/src/__tests__/integration/scope.test.ts +0 -557
- package/src/__tests__/integration/single-service.test.ts +0 -119
- package/src/__tests__/integration/unions.test.ts +0 -79
- package/src/__tests__/integration/value-types.test.ts +0 -382
- package/src/__tests__/integration/variables.test.ts +0 -120
- package/src/__tests__/nockAssertions.ts +0 -20
- package/src/__tests__/queryPlanCucumber.test.ts +0 -55
- package/src/__tests__/resultShaping.test.ts +0 -605
- package/src/__tests__/testSetup.ts +0 -1
- package/src/__tests__/tsconfig.json +0 -8
- package/src/core/__tests__/core.test.ts +0 -412
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +0 -51
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +0 -574
- package/src/schema-helper/__tests__/addExtensions.test.ts +0 -70
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -364
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/loadServicesFromRemoteEndpoint.test.ts +0 -40
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +0 -65
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -511
- package/src/utilities/__tests__/deepMerge.test.ts +0 -77
|
@@ -1,1488 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import { getFederatedTestingSchema, ServiceDefinitionModule } from './execution-utils';
|
|
3
|
-
import {
|
|
4
|
-
astSerializer,
|
|
5
|
-
queryPlanSerializer,
|
|
6
|
-
} from 'apollo-federation-integration-testsuite';
|
|
7
|
-
import { Operation, parseOperation, Schema } from '@apollo/federation-internals';
|
|
8
|
-
import { QueryPlan } from '@apollo/query-planner';
|
|
9
|
-
import { LocalGraphQLDataSource } from '../datasources';
|
|
10
|
-
import { GatewayExecutionResult, GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
|
|
11
|
-
import { buildOperationContext } from '../operationContext';
|
|
12
|
-
import { executeQueryPlan } from '../executeQueryPlan';
|
|
13
|
-
|
|
14
|
-
expect.addSnapshotSerializer(astSerializer);
|
|
15
|
-
expect.addSnapshotSerializer(queryPlanSerializer);
|
|
16
|
-
|
|
17
|
-
describe('Execution tests for @include/@skip', () => {
|
|
18
|
-
function buildRequestContext(variables: Record<string, any>): GatewayGraphQLRequestContext {
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
return {
|
|
22
|
-
cache: undefined as any,
|
|
23
|
-
context: {},
|
|
24
|
-
request: {
|
|
25
|
-
variables,
|
|
26
|
-
},
|
|
27
|
-
metrics: {},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let s2Queries: {id : number}[] = [];
|
|
32
|
-
/**
|
|
33
|
-
* Simple subgraph schemas reused by a number of tests. This declares a simple interface `T` with 2 implems `T1` and `T2`.
|
|
34
|
-
* There is a simple operation that returns a list of 3 simple objects:
|
|
35
|
-
* 1. { __typename: 'T1', id: 1, a1: 10, b1: 100 },
|
|
36
|
-
* 2. { __typename: 'T2', id: 2, a2: 20, b2: 200 },
|
|
37
|
-
* 3. { __typename: 'T1', id: 3, a1: 30, b1: 300 },
|
|
38
|
-
* The `b1` and `b2` fields are provided by the 2nd subgraph, the rest by the 1st one.
|
|
39
|
-
*
|
|
40
|
-
* Additionally, everytime the 2nd subgraph is asked to resolve an entity, we collect it in `s2Queries`. This allows tests to
|
|
41
|
-
* validate when condition should not applied that the conditioned fetch is indeed not queried.
|
|
42
|
-
*/
|
|
43
|
-
const simpleInterfaceSchemas: ServiceDefinitionModule[] = [
|
|
44
|
-
{
|
|
45
|
-
name: 'S1',
|
|
46
|
-
typeDefs: gql`
|
|
47
|
-
type Query {
|
|
48
|
-
t: [T]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface T {
|
|
52
|
-
id: ID!
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type T1 implements T @key(fields: "id") {
|
|
56
|
-
id: ID!
|
|
57
|
-
a1: Int
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type T2 implements T @key(fields: "id") {
|
|
61
|
-
id: ID!
|
|
62
|
-
a2: Int
|
|
63
|
-
}
|
|
64
|
-
`,
|
|
65
|
-
resolvers: {
|
|
66
|
-
Query: {
|
|
67
|
-
t() {
|
|
68
|
-
return [
|
|
69
|
-
{ __typename: 'T1', id: 1, a1: 10 },
|
|
70
|
-
{ __typename: 'T2', id: 2, a2: 20 },
|
|
71
|
-
{ __typename: 'T1', id: 3, a1: 30 },
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: 'S2',
|
|
79
|
-
typeDefs: gql`
|
|
80
|
-
type T1 @key(fields: "id") {
|
|
81
|
-
id: ID!
|
|
82
|
-
b1: Int
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
type T2 @key(fields: "id") {
|
|
86
|
-
id: ID!
|
|
87
|
-
b2: Int
|
|
88
|
-
}
|
|
89
|
-
`,
|
|
90
|
-
resolvers: {
|
|
91
|
-
T1: {
|
|
92
|
-
__resolveReference(ref: { id: number }) {
|
|
93
|
-
s2Queries.push(ref);
|
|
94
|
-
return { ...ref, b1: 100 * ref.id };
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
T2: {
|
|
98
|
-
__resolveReference(ref: { id: number }) {
|
|
99
|
-
s2Queries.push(ref);
|
|
100
|
-
return { ...ref, b2: 100 * ref.id };
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
];
|
|
106
|
-
|
|
107
|
-
async function executePlan(
|
|
108
|
-
queryPlan: QueryPlan,
|
|
109
|
-
operation: Operation,
|
|
110
|
-
schema: Schema,
|
|
111
|
-
serviceMap: { [serviceName: string]: LocalGraphQLDataSource },
|
|
112
|
-
variables: Record<string, any> = {},
|
|
113
|
-
): Promise<GatewayExecutionResult> {
|
|
114
|
-
const apiSchema = schema.toAPISchema();
|
|
115
|
-
const operationContext = buildOperationContext({
|
|
116
|
-
schema: apiSchema.toGraphQLJSSchema(),
|
|
117
|
-
operationDocument: gql`${operation.toString()}`,
|
|
118
|
-
});
|
|
119
|
-
return executeQueryPlan(
|
|
120
|
-
queryPlan,
|
|
121
|
-
serviceMap,
|
|
122
|
-
buildRequestContext(variables),
|
|
123
|
-
operationContext,
|
|
124
|
-
schema.toGraphQLJSSchema(),
|
|
125
|
-
apiSchema,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
describe('Constant conditions optimisation', () => {
|
|
130
|
-
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema(simpleInterfaceSchemas);
|
|
131
|
-
|
|
132
|
-
it('top-level @include never included', async () => {
|
|
133
|
-
const operation = parseOperation(schema, `
|
|
134
|
-
{
|
|
135
|
-
t @include(if: false) {
|
|
136
|
-
id
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
`);
|
|
140
|
-
|
|
141
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
142
|
-
expect(queryPlan).toMatchInlineSnapshot(`QueryPlan {}`);
|
|
143
|
-
|
|
144
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
145
|
-
expect(response.errors).toBeUndefined();
|
|
146
|
-
expect(response.data).toMatchInlineSnapshot(`Object {}`);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('top-level @skip always skipped', async () => {
|
|
150
|
-
const operation = parseOperation(schema, `
|
|
151
|
-
{
|
|
152
|
-
t @skip(if: true) {
|
|
153
|
-
id
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
`);
|
|
157
|
-
|
|
158
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
159
|
-
expect(queryPlan).toMatchInlineSnapshot(`QueryPlan {}`);
|
|
160
|
-
|
|
161
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
162
|
-
expect(response.errors).toBeUndefined();
|
|
163
|
-
expect(response.data).toMatchInlineSnapshot(`Object {}`);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('top-level @include always included', async () => {
|
|
167
|
-
const operation = parseOperation(schema, `
|
|
168
|
-
{
|
|
169
|
-
t @include(if: true) {
|
|
170
|
-
id
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
`);
|
|
174
|
-
|
|
175
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
176
|
-
// Note that due to how we handle constant conditions, those don't get removed in the fetch nodes (which only matter when things are
|
|
177
|
-
// included with a constant; if they are skipped with a constant, then the fetch is not include so ...). This feels like a very minor
|
|
178
|
-
// point so leaving it be for now: constant conditions are a bit of a smell in the first place, so unsure we need to go above and
|
|
179
|
-
// beyond to optimise them.
|
|
180
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
181
|
-
QueryPlan {
|
|
182
|
-
Fetch(service: "S1") {
|
|
183
|
-
{
|
|
184
|
-
t @include(if: true) {
|
|
185
|
-
__typename
|
|
186
|
-
id
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
`);
|
|
192
|
-
|
|
193
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
194
|
-
expect(response.errors).toBeUndefined();
|
|
195
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
196
|
-
Object {
|
|
197
|
-
"t": Array [
|
|
198
|
-
Object {
|
|
199
|
-
"id": "1",
|
|
200
|
-
},
|
|
201
|
-
Object {
|
|
202
|
-
"id": "2",
|
|
203
|
-
},
|
|
204
|
-
Object {
|
|
205
|
-
"id": "3",
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
}
|
|
209
|
-
`);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('top-level @skip always included', async () => {
|
|
213
|
-
const operation = parseOperation(schema, `
|
|
214
|
-
{
|
|
215
|
-
t @skip(if: false) {
|
|
216
|
-
id
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
`);
|
|
220
|
-
|
|
221
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
222
|
-
// Same as for @include: the @skip within the fetch is not cleared, but that's harmless.
|
|
223
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
224
|
-
QueryPlan {
|
|
225
|
-
Fetch(service: "S1") {
|
|
226
|
-
{
|
|
227
|
-
t @skip(if: false) {
|
|
228
|
-
__typename
|
|
229
|
-
id
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
}
|
|
234
|
-
`);
|
|
235
|
-
|
|
236
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
237
|
-
expect(response.errors).toBeUndefined();
|
|
238
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
239
|
-
Object {
|
|
240
|
-
"t": Array [
|
|
241
|
-
Object {
|
|
242
|
-
"id": "1",
|
|
243
|
-
},
|
|
244
|
-
Object {
|
|
245
|
-
"id": "2",
|
|
246
|
-
},
|
|
247
|
-
Object {
|
|
248
|
-
"id": "3",
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
}
|
|
252
|
-
`);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('Non top-level @include never included', async () => {
|
|
256
|
-
const operation = parseOperation(schema, `
|
|
257
|
-
{
|
|
258
|
-
t {
|
|
259
|
-
... on T1 {
|
|
260
|
-
a1
|
|
261
|
-
b1 @include(if: false)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
`);
|
|
266
|
-
|
|
267
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
268
|
-
// Importantly, we only bother querying S1
|
|
269
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
270
|
-
QueryPlan {
|
|
271
|
-
Fetch(service: "S1") {
|
|
272
|
-
{
|
|
273
|
-
t {
|
|
274
|
-
__typename
|
|
275
|
-
... on T1 {
|
|
276
|
-
__typename
|
|
277
|
-
id
|
|
278
|
-
a1
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
}
|
|
284
|
-
`);
|
|
285
|
-
|
|
286
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
287
|
-
expect(response.errors).toBeUndefined();
|
|
288
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
289
|
-
Object {
|
|
290
|
-
"t": Array [
|
|
291
|
-
Object {
|
|
292
|
-
"a1": 10,
|
|
293
|
-
},
|
|
294
|
-
Object {},
|
|
295
|
-
Object {
|
|
296
|
-
"a1": 30,
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
}
|
|
300
|
-
`);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('Non top-level @skip always skipped', async () => {
|
|
304
|
-
const operation = parseOperation(schema, `
|
|
305
|
-
{
|
|
306
|
-
t {
|
|
307
|
-
... on T1 {
|
|
308
|
-
a1
|
|
309
|
-
b1 @skip(if: true)
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
`);
|
|
314
|
-
|
|
315
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
316
|
-
// Importantly, we only bother querying S1
|
|
317
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
318
|
-
QueryPlan {
|
|
319
|
-
Fetch(service: "S1") {
|
|
320
|
-
{
|
|
321
|
-
t {
|
|
322
|
-
__typename
|
|
323
|
-
... on T1 {
|
|
324
|
-
__typename
|
|
325
|
-
id
|
|
326
|
-
a1
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
}
|
|
332
|
-
`);
|
|
333
|
-
|
|
334
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
335
|
-
expect(response.errors).toBeUndefined();
|
|
336
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
337
|
-
Object {
|
|
338
|
-
"t": Array [
|
|
339
|
-
Object {
|
|
340
|
-
"a1": 10,
|
|
341
|
-
},
|
|
342
|
-
Object {},
|
|
343
|
-
Object {
|
|
344
|
-
"a1": 30,
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
}
|
|
348
|
-
`);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('Non top-level @include always included', async () => {
|
|
352
|
-
const operation = parseOperation(schema, `
|
|
353
|
-
{
|
|
354
|
-
t {
|
|
355
|
-
... on T1 {
|
|
356
|
-
a1
|
|
357
|
-
b1 @include(if: true)
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
`);
|
|
362
|
-
|
|
363
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
364
|
-
// Again, constantly included is the one case that still show up in the fetch, but that's harmless. The point here is
|
|
365
|
-
// that we don't bother with a ConditionNode.
|
|
366
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
367
|
-
QueryPlan {
|
|
368
|
-
Sequence {
|
|
369
|
-
Fetch(service: "S1") {
|
|
370
|
-
{
|
|
371
|
-
t {
|
|
372
|
-
__typename
|
|
373
|
-
... on T1 {
|
|
374
|
-
__typename
|
|
375
|
-
id
|
|
376
|
-
a1
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
Flatten(path: "t.@") {
|
|
382
|
-
Fetch(service: "S2") {
|
|
383
|
-
{
|
|
384
|
-
... on T1 {
|
|
385
|
-
__typename
|
|
386
|
-
id
|
|
387
|
-
}
|
|
388
|
-
} =>
|
|
389
|
-
{
|
|
390
|
-
... on T1 {
|
|
391
|
-
b1 @include(if: true)
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
}
|
|
398
|
-
`);
|
|
399
|
-
|
|
400
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
401
|
-
expect(response.errors).toBeUndefined();
|
|
402
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
403
|
-
Object {
|
|
404
|
-
"t": Array [
|
|
405
|
-
Object {
|
|
406
|
-
"a1": 10,
|
|
407
|
-
"b1": 100,
|
|
408
|
-
},
|
|
409
|
-
Object {},
|
|
410
|
-
Object {
|
|
411
|
-
"a1": 30,
|
|
412
|
-
"b1": 300,
|
|
413
|
-
},
|
|
414
|
-
],
|
|
415
|
-
}
|
|
416
|
-
`);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it('Non top-level @skip always included', async () => {
|
|
420
|
-
const operation = parseOperation(schema, `
|
|
421
|
-
{
|
|
422
|
-
t {
|
|
423
|
-
... on T1 {
|
|
424
|
-
a1
|
|
425
|
-
b1 @skip(if: false)
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
`);
|
|
430
|
-
|
|
431
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
432
|
-
// Again, constantly included is the one case that still show up in the fetch, but that's harmless. The point here is
|
|
433
|
-
// that we don't bother with a ConditionNode.
|
|
434
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
435
|
-
QueryPlan {
|
|
436
|
-
Sequence {
|
|
437
|
-
Fetch(service: "S1") {
|
|
438
|
-
{
|
|
439
|
-
t {
|
|
440
|
-
__typename
|
|
441
|
-
... on T1 {
|
|
442
|
-
__typename
|
|
443
|
-
id
|
|
444
|
-
a1
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
Flatten(path: "t.@") {
|
|
450
|
-
Fetch(service: "S2") {
|
|
451
|
-
{
|
|
452
|
-
... on T1 {
|
|
453
|
-
__typename
|
|
454
|
-
id
|
|
455
|
-
}
|
|
456
|
-
} =>
|
|
457
|
-
{
|
|
458
|
-
... on T1 {
|
|
459
|
-
b1 @skip(if: false)
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
}
|
|
466
|
-
`);
|
|
467
|
-
|
|
468
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
469
|
-
expect(response.errors).toBeUndefined();
|
|
470
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
471
|
-
Object {
|
|
472
|
-
"t": Array [
|
|
473
|
-
Object {
|
|
474
|
-
"a1": 10,
|
|
475
|
-
"b1": 100,
|
|
476
|
-
},
|
|
477
|
-
Object {},
|
|
478
|
-
Object {
|
|
479
|
-
"a1": 30,
|
|
480
|
-
"b1": 300,
|
|
481
|
-
},
|
|
482
|
-
],
|
|
483
|
-
}
|
|
484
|
-
`);
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
describe('Simple variable conditions handling', () => {
|
|
489
|
-
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema(simpleInterfaceSchemas);
|
|
490
|
-
|
|
491
|
-
it('handles default values for condition variables', async () => {
|
|
492
|
-
const operation = parseOperation(schema, `
|
|
493
|
-
query ($if: Boolean! = false){
|
|
494
|
-
t {
|
|
495
|
-
id
|
|
496
|
-
... on T1 @include(if: $if) {
|
|
497
|
-
b1
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
`);
|
|
502
|
-
|
|
503
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
504
|
-
// We validate that the condition has been extracted.
|
|
505
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
506
|
-
QueryPlan {
|
|
507
|
-
Sequence {
|
|
508
|
-
Fetch(service: "S1") {
|
|
509
|
-
{
|
|
510
|
-
t {
|
|
511
|
-
__typename
|
|
512
|
-
id
|
|
513
|
-
... on T1 @include(if: $if) {
|
|
514
|
-
__typename
|
|
515
|
-
id
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
},
|
|
520
|
-
Include(if: $if) {
|
|
521
|
-
Flatten(path: "t.@") {
|
|
522
|
-
Fetch(service: "S2") {
|
|
523
|
-
{
|
|
524
|
-
... on T1 {
|
|
525
|
-
__typename
|
|
526
|
-
id
|
|
527
|
-
}
|
|
528
|
-
} =>
|
|
529
|
-
{
|
|
530
|
-
... on T1 {
|
|
531
|
-
b1
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
},
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
|
-
},
|
|
538
|
-
}
|
|
539
|
-
`);
|
|
540
|
-
|
|
541
|
-
s2Queries = [];
|
|
542
|
-
// No variables: the default (not included) should be used.
|
|
543
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
544
|
-
expect(response.errors).toBeUndefined();
|
|
545
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
546
|
-
Object {
|
|
547
|
-
"t": Array [
|
|
548
|
-
Object {
|
|
549
|
-
"id": "1",
|
|
550
|
-
},
|
|
551
|
-
Object {
|
|
552
|
-
"id": "2",
|
|
553
|
-
},
|
|
554
|
-
Object {
|
|
555
|
-
"id": "3",
|
|
556
|
-
},
|
|
557
|
-
],
|
|
558
|
-
}
|
|
559
|
-
`);
|
|
560
|
-
expect(s2Queries).toHaveLength(0);
|
|
561
|
-
|
|
562
|
-
s2Queries = [];
|
|
563
|
-
// Checks that the overriding of the default does work.
|
|
564
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if: true });
|
|
565
|
-
expect(response.errors).toBeUndefined();
|
|
566
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
567
|
-
Object {
|
|
568
|
-
"t": Array [
|
|
569
|
-
Object {
|
|
570
|
-
"b1": 100,
|
|
571
|
-
"id": "1",
|
|
572
|
-
},
|
|
573
|
-
Object {
|
|
574
|
-
"id": "2",
|
|
575
|
-
},
|
|
576
|
-
Object {
|
|
577
|
-
"b1": 300,
|
|
578
|
-
"id": "3",
|
|
579
|
-
},
|
|
580
|
-
],
|
|
581
|
-
}
|
|
582
|
-
`);
|
|
583
|
-
expect(s2Queries).toHaveLength(2);
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
it('handles condition on named fragments spread', async () => {
|
|
587
|
-
const operation = parseOperation(schema, `
|
|
588
|
-
query ($if: Boolean!){
|
|
589
|
-
t {
|
|
590
|
-
id
|
|
591
|
-
... GetB1 @include(if: $if)
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
fragment GetB1 on T1 {
|
|
596
|
-
b1
|
|
597
|
-
}
|
|
598
|
-
`);
|
|
599
|
-
|
|
600
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
601
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
602
|
-
QueryPlan {
|
|
603
|
-
Sequence {
|
|
604
|
-
Fetch(service: "S1") {
|
|
605
|
-
{
|
|
606
|
-
t {
|
|
607
|
-
__typename
|
|
608
|
-
id
|
|
609
|
-
... on T1 @include(if: $if) {
|
|
610
|
-
__typename
|
|
611
|
-
id
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
Include(if: $if) {
|
|
617
|
-
Flatten(path: "t.@") {
|
|
618
|
-
Fetch(service: "S2") {
|
|
619
|
-
{
|
|
620
|
-
... on T1 {
|
|
621
|
-
__typename
|
|
622
|
-
id
|
|
623
|
-
}
|
|
624
|
-
} =>
|
|
625
|
-
{
|
|
626
|
-
... on T1 {
|
|
627
|
-
b1
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
},
|
|
631
|
-
}
|
|
632
|
-
},
|
|
633
|
-
},
|
|
634
|
-
}
|
|
635
|
-
`);
|
|
636
|
-
|
|
637
|
-
s2Queries = [];
|
|
638
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if: true });
|
|
639
|
-
expect(response.errors).toBeUndefined();
|
|
640
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
641
|
-
Object {
|
|
642
|
-
"t": Array [
|
|
643
|
-
Object {
|
|
644
|
-
"b1": 100,
|
|
645
|
-
"id": "1",
|
|
646
|
-
},
|
|
647
|
-
Object {
|
|
648
|
-
"id": "2",
|
|
649
|
-
},
|
|
650
|
-
Object {
|
|
651
|
-
"b1": 300,
|
|
652
|
-
"id": "3",
|
|
653
|
-
},
|
|
654
|
-
],
|
|
655
|
-
}
|
|
656
|
-
`);
|
|
657
|
-
expect(s2Queries).toHaveLength(2);
|
|
658
|
-
|
|
659
|
-
s2Queries = [];
|
|
660
|
-
// Checks that the overriding of the default does work.
|
|
661
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if: false });
|
|
662
|
-
expect(response.errors).toBeUndefined();
|
|
663
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
664
|
-
Object {
|
|
665
|
-
"t": Array [
|
|
666
|
-
Object {
|
|
667
|
-
"id": "1",
|
|
668
|
-
},
|
|
669
|
-
Object {
|
|
670
|
-
"id": "2",
|
|
671
|
-
},
|
|
672
|
-
Object {
|
|
673
|
-
"id": "3",
|
|
674
|
-
},
|
|
675
|
-
],
|
|
676
|
-
}
|
|
677
|
-
`);
|
|
678
|
-
expect(s2Queries).toHaveLength(0);
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
it('handles condition inside named fragments', async () => {
|
|
682
|
-
const operation = parseOperation(schema, `
|
|
683
|
-
query ($if: Boolean!){
|
|
684
|
-
t {
|
|
685
|
-
id
|
|
686
|
-
... OtherGetB1
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
fragment OtherGetB1 on T1 {
|
|
691
|
-
b1 @include(if: $if)
|
|
692
|
-
}
|
|
693
|
-
`);
|
|
694
|
-
|
|
695
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
696
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
697
|
-
QueryPlan {
|
|
698
|
-
Sequence {
|
|
699
|
-
Fetch(service: "S1") {
|
|
700
|
-
{
|
|
701
|
-
t {
|
|
702
|
-
__typename
|
|
703
|
-
id
|
|
704
|
-
... on T1 {
|
|
705
|
-
__typename
|
|
706
|
-
id
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
},
|
|
711
|
-
Include(if: $if) {
|
|
712
|
-
Flatten(path: "t.@") {
|
|
713
|
-
Fetch(service: "S2") {
|
|
714
|
-
{
|
|
715
|
-
... on T1 {
|
|
716
|
-
__typename
|
|
717
|
-
id
|
|
718
|
-
}
|
|
719
|
-
} =>
|
|
720
|
-
{
|
|
721
|
-
... on T1 {
|
|
722
|
-
b1
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
|
-
},
|
|
729
|
-
}
|
|
730
|
-
`);
|
|
731
|
-
|
|
732
|
-
s2Queries = [];
|
|
733
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if: true });
|
|
734
|
-
expect(response.errors).toBeUndefined();
|
|
735
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
736
|
-
Object {
|
|
737
|
-
"t": Array [
|
|
738
|
-
Object {
|
|
739
|
-
"b1": 100,
|
|
740
|
-
"id": "1",
|
|
741
|
-
},
|
|
742
|
-
Object {
|
|
743
|
-
"id": "2",
|
|
744
|
-
},
|
|
745
|
-
Object {
|
|
746
|
-
"b1": 300,
|
|
747
|
-
"id": "3",
|
|
748
|
-
},
|
|
749
|
-
],
|
|
750
|
-
}
|
|
751
|
-
`);
|
|
752
|
-
expect(s2Queries).toHaveLength(2);
|
|
753
|
-
|
|
754
|
-
s2Queries = [];
|
|
755
|
-
// Checks that the overriding of the default does work.
|
|
756
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if: false });
|
|
757
|
-
expect(response.errors).toBeUndefined();
|
|
758
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
759
|
-
Object {
|
|
760
|
-
"t": Array [
|
|
761
|
-
Object {
|
|
762
|
-
"id": "1",
|
|
763
|
-
},
|
|
764
|
-
Object {
|
|
765
|
-
"id": "2",
|
|
766
|
-
},
|
|
767
|
-
Object {
|
|
768
|
-
"id": "3",
|
|
769
|
-
},
|
|
770
|
-
],
|
|
771
|
-
}
|
|
772
|
-
`);
|
|
773
|
-
expect(s2Queries).toHaveLength(0);
|
|
774
|
-
});
|
|
775
|
-
})
|
|
776
|
-
|
|
777
|
-
describe('Fetches with multiple top-level conditioned types', () => {
|
|
778
|
-
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema(simpleInterfaceSchemas);
|
|
779
|
-
|
|
780
|
-
it('creates a condition node when a condition covers a whole fetch', async () => {
|
|
781
|
-
const operation = parseOperation(schema, `
|
|
782
|
-
query ($if: Boolean!){
|
|
783
|
-
t {
|
|
784
|
-
id
|
|
785
|
-
... on T1 @include(if: $if) {
|
|
786
|
-
b1
|
|
787
|
-
}
|
|
788
|
-
... on T2 @include(if: $if) {
|
|
789
|
-
b2
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
`);
|
|
794
|
-
|
|
795
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
796
|
-
// We validate that the condition has been extracted.
|
|
797
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
798
|
-
QueryPlan {
|
|
799
|
-
Sequence {
|
|
800
|
-
Fetch(service: "S1") {
|
|
801
|
-
{
|
|
802
|
-
t {
|
|
803
|
-
__typename
|
|
804
|
-
id
|
|
805
|
-
... on T1 @include(if: $if) {
|
|
806
|
-
__typename
|
|
807
|
-
id
|
|
808
|
-
}
|
|
809
|
-
... on T2 @include(if: $if) {
|
|
810
|
-
__typename
|
|
811
|
-
id
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
},
|
|
816
|
-
Include(if: $if) {
|
|
817
|
-
Flatten(path: "t.@") {
|
|
818
|
-
Fetch(service: "S2") {
|
|
819
|
-
{
|
|
820
|
-
... on T1 {
|
|
821
|
-
__typename
|
|
822
|
-
id
|
|
823
|
-
}
|
|
824
|
-
... on T2 {
|
|
825
|
-
__typename
|
|
826
|
-
id
|
|
827
|
-
}
|
|
828
|
-
} =>
|
|
829
|
-
{
|
|
830
|
-
... on T1 {
|
|
831
|
-
b1
|
|
832
|
-
}
|
|
833
|
-
... on T2 {
|
|
834
|
-
b2
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
},
|
|
841
|
-
}
|
|
842
|
-
`);
|
|
843
|
-
|
|
844
|
-
s2Queries = [];
|
|
845
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if: true });
|
|
846
|
-
expect(response.errors).toBeUndefined();
|
|
847
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
848
|
-
Object {
|
|
849
|
-
"t": Array [
|
|
850
|
-
Object {
|
|
851
|
-
"b1": 100,
|
|
852
|
-
"id": "1",
|
|
853
|
-
},
|
|
854
|
-
Object {
|
|
855
|
-
"b2": 200,
|
|
856
|
-
"id": "2",
|
|
857
|
-
},
|
|
858
|
-
Object {
|
|
859
|
-
"b1": 300,
|
|
860
|
-
"id": "3",
|
|
861
|
-
},
|
|
862
|
-
],
|
|
863
|
-
}
|
|
864
|
-
`);
|
|
865
|
-
// Since we include the fields, S2 should have been asked to resolve the `b` field of our 3 entities.
|
|
866
|
-
expect(s2Queries).toHaveLength(3);
|
|
867
|
-
|
|
868
|
-
s2Queries = [];
|
|
869
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if: false });
|
|
870
|
-
expect(response.errors).toBeUndefined();
|
|
871
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
872
|
-
Object {
|
|
873
|
-
"t": Array [
|
|
874
|
-
Object {
|
|
875
|
-
"id": "1",
|
|
876
|
-
},
|
|
877
|
-
Object {
|
|
878
|
-
"id": "2",
|
|
879
|
-
},
|
|
880
|
-
Object {
|
|
881
|
-
"id": "3",
|
|
882
|
-
},
|
|
883
|
-
],
|
|
884
|
-
}
|
|
885
|
-
`);
|
|
886
|
-
// But make sure we indeed do not query S2 if we don't need to.
|
|
887
|
-
expect(s2Queries).toHaveLength(0);
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
it('does _not_ creates a condition node when no single condition covers the whole fetch', async () => {
|
|
891
|
-
const operation = parseOperation(schema, `
|
|
892
|
-
query ($if1: Boolean!, $if2: Boolean!){
|
|
893
|
-
t {
|
|
894
|
-
id
|
|
895
|
-
... on T1 @include(if: $if1) {
|
|
896
|
-
b1
|
|
897
|
-
}
|
|
898
|
-
... on T2 @include(if: $if2) {
|
|
899
|
-
b2
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
`);
|
|
904
|
-
|
|
905
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
906
|
-
// We validate that the condition has been extracted.
|
|
907
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
908
|
-
QueryPlan {
|
|
909
|
-
Sequence {
|
|
910
|
-
Fetch(service: "S1") {
|
|
911
|
-
{
|
|
912
|
-
t {
|
|
913
|
-
__typename
|
|
914
|
-
id
|
|
915
|
-
... on T1 @include(if: $if1) {
|
|
916
|
-
__typename
|
|
917
|
-
id
|
|
918
|
-
}
|
|
919
|
-
... on T2 @include(if: $if2) {
|
|
920
|
-
__typename
|
|
921
|
-
id
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
},
|
|
926
|
-
Flatten(path: "t.@") {
|
|
927
|
-
Fetch(service: "S2") {
|
|
928
|
-
{
|
|
929
|
-
... on T1 {
|
|
930
|
-
__typename
|
|
931
|
-
id
|
|
932
|
-
}
|
|
933
|
-
... on T2 {
|
|
934
|
-
__typename
|
|
935
|
-
id
|
|
936
|
-
}
|
|
937
|
-
} =>
|
|
938
|
-
{
|
|
939
|
-
... on T1 @include(if: $if1) {
|
|
940
|
-
b1
|
|
941
|
-
}
|
|
942
|
-
... on T2 @include(if: $if2) {
|
|
943
|
-
b2
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
},
|
|
947
|
-
},
|
|
948
|
-
},
|
|
949
|
-
}
|
|
950
|
-
`);
|
|
951
|
-
|
|
952
|
-
s2Queries = [];
|
|
953
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: true, if2: true });
|
|
954
|
-
expect(response.errors).toBeUndefined();
|
|
955
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
956
|
-
Object {
|
|
957
|
-
"t": Array [
|
|
958
|
-
Object {
|
|
959
|
-
"b1": 100,
|
|
960
|
-
"id": "1",
|
|
961
|
-
},
|
|
962
|
-
Object {
|
|
963
|
-
"b2": 200,
|
|
964
|
-
"id": "2",
|
|
965
|
-
},
|
|
966
|
-
Object {
|
|
967
|
-
"b1": 300,
|
|
968
|
-
"id": "3",
|
|
969
|
-
},
|
|
970
|
-
],
|
|
971
|
-
}
|
|
972
|
-
`);
|
|
973
|
-
// Since we include the fields, S2 should have been asked to resolve the `b` field of our 3 entities.
|
|
974
|
-
expect(s2Queries).toHaveLength(3);
|
|
975
|
-
|
|
976
|
-
s2Queries = [];
|
|
977
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: false, if2: false });
|
|
978
|
-
expect(response.errors).toBeUndefined();
|
|
979
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
980
|
-
Object {
|
|
981
|
-
"t": Array [
|
|
982
|
-
Object {
|
|
983
|
-
"id": "1",
|
|
984
|
-
},
|
|
985
|
-
Object {
|
|
986
|
-
"id": "2",
|
|
987
|
-
},
|
|
988
|
-
Object {
|
|
989
|
-
"id": "3",
|
|
990
|
-
},
|
|
991
|
-
],
|
|
992
|
-
}
|
|
993
|
-
`);
|
|
994
|
-
// TODO: It's unfortunate, but we currently do query S2 in that case. We could fix this by including the
|
|
995
|
-
// condition in the inputs: even if the gateway/router don't have a `ConditionNode` to shortcut things
|
|
996
|
-
// directly, as long as it handle the conditions when extracting the inputs, it would end up with 0
|
|
997
|
-
// inputs in this case, and would not send a query, which technically would be a tiny bit slower than
|
|
998
|
-
// having a `ConditionNode`, but tons better than sending a useless fetch.
|
|
999
|
-
expect(s2Queries).toHaveLength(3);
|
|
1000
|
-
|
|
1001
|
-
s2Queries = [];
|
|
1002
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: false, if2: true });
|
|
1003
|
-
expect(response.errors).toBeUndefined();
|
|
1004
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1005
|
-
Object {
|
|
1006
|
-
"t": Array [
|
|
1007
|
-
Object {
|
|
1008
|
-
"id": "1",
|
|
1009
|
-
},
|
|
1010
|
-
Object {
|
|
1011
|
-
"b2": 200,
|
|
1012
|
-
"id": "2",
|
|
1013
|
-
},
|
|
1014
|
-
Object {
|
|
1015
|
-
"id": "3",
|
|
1016
|
-
},
|
|
1017
|
-
],
|
|
1018
|
-
}
|
|
1019
|
-
`);
|
|
1020
|
-
// TODO: Similar to the previous case, we could (should) here only send the query for the single
|
|
1021
|
-
// entity that is included, but we currently instead send everything.
|
|
1022
|
-
expect(s2Queries).toHaveLength(3);
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
it('handles "nested" conditions', async () => {
|
|
1026
|
-
// Shows that as long as the condition matches, even "nested" @include gets handled as a condition node.
|
|
1027
|
-
const operation = parseOperation(schema, `
|
|
1028
|
-
query ($if1: Boolean!, $if2: Boolean!){
|
|
1029
|
-
t {
|
|
1030
|
-
id
|
|
1031
|
-
... on T1 @include(if: $if1) {
|
|
1032
|
-
b1 @include(if: $if2)
|
|
1033
|
-
}
|
|
1034
|
-
... on T2 @include(if: $if1) {
|
|
1035
|
-
b2 @include(if: $if2)
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
`);
|
|
1040
|
-
|
|
1041
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1042
|
-
// We validate that the condition has been extracted.
|
|
1043
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1044
|
-
QueryPlan {
|
|
1045
|
-
Sequence {
|
|
1046
|
-
Fetch(service: "S1") {
|
|
1047
|
-
{
|
|
1048
|
-
t {
|
|
1049
|
-
__typename
|
|
1050
|
-
id
|
|
1051
|
-
... on T1 @include(if: $if1) {
|
|
1052
|
-
__typename
|
|
1053
|
-
id
|
|
1054
|
-
}
|
|
1055
|
-
... on T2 @include(if: $if1) {
|
|
1056
|
-
__typename
|
|
1057
|
-
id
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
},
|
|
1062
|
-
Include(if: $if2) {
|
|
1063
|
-
Include(if: $if1) {
|
|
1064
|
-
Flatten(path: "t.@") {
|
|
1065
|
-
Fetch(service: "S2") {
|
|
1066
|
-
{
|
|
1067
|
-
... on T1 {
|
|
1068
|
-
__typename
|
|
1069
|
-
id
|
|
1070
|
-
}
|
|
1071
|
-
... on T2 {
|
|
1072
|
-
__typename
|
|
1073
|
-
id
|
|
1074
|
-
}
|
|
1075
|
-
} =>
|
|
1076
|
-
{
|
|
1077
|
-
... on T1 {
|
|
1078
|
-
b1
|
|
1079
|
-
}
|
|
1080
|
-
... on T2 {
|
|
1081
|
-
b2
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
},
|
|
1088
|
-
},
|
|
1089
|
-
}
|
|
1090
|
-
`);
|
|
1091
|
-
|
|
1092
|
-
s2Queries = [];
|
|
1093
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: true, if2: true });
|
|
1094
|
-
expect(response.errors).toBeUndefined();
|
|
1095
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1096
|
-
Object {
|
|
1097
|
-
"t": Array [
|
|
1098
|
-
Object {
|
|
1099
|
-
"b1": 100,
|
|
1100
|
-
"id": "1",
|
|
1101
|
-
},
|
|
1102
|
-
Object {
|
|
1103
|
-
"b2": 200,
|
|
1104
|
-
"id": "2",
|
|
1105
|
-
},
|
|
1106
|
-
Object {
|
|
1107
|
-
"b1": 300,
|
|
1108
|
-
"id": "3",
|
|
1109
|
-
},
|
|
1110
|
-
],
|
|
1111
|
-
}
|
|
1112
|
-
`);
|
|
1113
|
-
// Since we include the fields, S2 should have been asked to resolve the `b` field of our 3 entities.
|
|
1114
|
-
expect(s2Queries).toHaveLength(3);
|
|
1115
|
-
|
|
1116
|
-
s2Queries = [];
|
|
1117
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: false, if2: true });
|
|
1118
|
-
expect(response.errors).toBeUndefined();
|
|
1119
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1120
|
-
Object {
|
|
1121
|
-
"t": Array [
|
|
1122
|
-
Object {
|
|
1123
|
-
"id": "1",
|
|
1124
|
-
},
|
|
1125
|
-
Object {
|
|
1126
|
-
"id": "2",
|
|
1127
|
-
},
|
|
1128
|
-
Object {
|
|
1129
|
-
"id": "3",
|
|
1130
|
-
},
|
|
1131
|
-
],
|
|
1132
|
-
}
|
|
1133
|
-
`);
|
|
1134
|
-
// If any one of the 2 condition is false, then we shouldn't query S2 at all (even if the other is true).
|
|
1135
|
-
expect(s2Queries).toHaveLength(0);
|
|
1136
|
-
|
|
1137
|
-
s2Queries = [];
|
|
1138
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: true, if2: false });
|
|
1139
|
-
expect(response.errors).toBeUndefined();
|
|
1140
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1141
|
-
Object {
|
|
1142
|
-
"t": Array [
|
|
1143
|
-
Object {
|
|
1144
|
-
"id": "1",
|
|
1145
|
-
},
|
|
1146
|
-
Object {
|
|
1147
|
-
"id": "2",
|
|
1148
|
-
},
|
|
1149
|
-
Object {
|
|
1150
|
-
"id": "3",
|
|
1151
|
-
},
|
|
1152
|
-
],
|
|
1153
|
-
}
|
|
1154
|
-
`);
|
|
1155
|
-
expect(s2Queries).toHaveLength(0);
|
|
1156
|
-
});
|
|
1157
|
-
|
|
1158
|
-
describe('same element having both @skip and @include', () => {
|
|
1159
|
-
it('with constant conditions, neither excluding', async () => {
|
|
1160
|
-
const operation = parseOperation(schema, `
|
|
1161
|
-
{
|
|
1162
|
-
t {
|
|
1163
|
-
id
|
|
1164
|
-
... on T1 @include(if: true) @skip(if: false) {
|
|
1165
|
-
b1
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
`);
|
|
1170
|
-
|
|
1171
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1172
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1173
|
-
QueryPlan {
|
|
1174
|
-
Sequence {
|
|
1175
|
-
Fetch(service: "S1") {
|
|
1176
|
-
{
|
|
1177
|
-
t {
|
|
1178
|
-
__typename
|
|
1179
|
-
id
|
|
1180
|
-
... on T1 @include(if: true) @skip(if: false) {
|
|
1181
|
-
__typename
|
|
1182
|
-
id
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
},
|
|
1187
|
-
Flatten(path: "t.@") {
|
|
1188
|
-
Fetch(service: "S2") {
|
|
1189
|
-
{
|
|
1190
|
-
... on T1 {
|
|
1191
|
-
... on T1 {
|
|
1192
|
-
__typename
|
|
1193
|
-
id
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
} =>
|
|
1197
|
-
{
|
|
1198
|
-
... on T1 @include(if: true) {
|
|
1199
|
-
... on T1 @skip(if: false) {
|
|
1200
|
-
b1
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
},
|
|
1205
|
-
},
|
|
1206
|
-
},
|
|
1207
|
-
}
|
|
1208
|
-
`);
|
|
1209
|
-
|
|
1210
|
-
s2Queries = [];
|
|
1211
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
1212
|
-
expect(response.errors).toBeUndefined();
|
|
1213
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1214
|
-
Object {
|
|
1215
|
-
"t": Array [
|
|
1216
|
-
Object {
|
|
1217
|
-
"b1": 100,
|
|
1218
|
-
"id": "1",
|
|
1219
|
-
},
|
|
1220
|
-
Object {
|
|
1221
|
-
"id": "2",
|
|
1222
|
-
},
|
|
1223
|
-
Object {
|
|
1224
|
-
"b1": 300,
|
|
1225
|
-
"id": "3",
|
|
1226
|
-
},
|
|
1227
|
-
],
|
|
1228
|
-
}
|
|
1229
|
-
`);
|
|
1230
|
-
expect(s2Queries).toHaveLength(2);
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
it('with constant conditions, both excluding', async () => {
|
|
1234
|
-
const operation = parseOperation(schema, `
|
|
1235
|
-
{
|
|
1236
|
-
t {
|
|
1237
|
-
id
|
|
1238
|
-
... on T1 @include(if: false) @skip(if: true) {
|
|
1239
|
-
b1
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
`);
|
|
1244
|
-
|
|
1245
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1246
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1247
|
-
QueryPlan {
|
|
1248
|
-
Fetch(service: "S1") {
|
|
1249
|
-
{
|
|
1250
|
-
t {
|
|
1251
|
-
__typename
|
|
1252
|
-
id
|
|
1253
|
-
... on T1 @include(if: false) @skip(if: true) {
|
|
1254
|
-
__typename
|
|
1255
|
-
id
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
},
|
|
1260
|
-
}
|
|
1261
|
-
`);
|
|
1262
|
-
|
|
1263
|
-
s2Queries = [];
|
|
1264
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
1265
|
-
expect(response.errors).toBeUndefined();
|
|
1266
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1267
|
-
Object {
|
|
1268
|
-
"t": Array [
|
|
1269
|
-
Object {
|
|
1270
|
-
"id": "1",
|
|
1271
|
-
},
|
|
1272
|
-
Object {
|
|
1273
|
-
"id": "2",
|
|
1274
|
-
},
|
|
1275
|
-
Object {
|
|
1276
|
-
"id": "3",
|
|
1277
|
-
},
|
|
1278
|
-
],
|
|
1279
|
-
}
|
|
1280
|
-
`);
|
|
1281
|
-
expect(s2Queries).toHaveLength(0);
|
|
1282
|
-
});
|
|
1283
|
-
|
|
1284
|
-
it('with constant conditions, first excluding', async () => {
|
|
1285
|
-
const operation = parseOperation(schema, `
|
|
1286
|
-
{
|
|
1287
|
-
t {
|
|
1288
|
-
id
|
|
1289
|
-
... on T1 @include(if: false) @skip(if: false) {
|
|
1290
|
-
b1
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
`);
|
|
1295
|
-
|
|
1296
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1297
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1298
|
-
QueryPlan {
|
|
1299
|
-
Fetch(service: "S1") {
|
|
1300
|
-
{
|
|
1301
|
-
t {
|
|
1302
|
-
__typename
|
|
1303
|
-
id
|
|
1304
|
-
... on T1 @include(if: false) @skip(if: false) {
|
|
1305
|
-
__typename
|
|
1306
|
-
id
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
},
|
|
1311
|
-
}
|
|
1312
|
-
`);
|
|
1313
|
-
|
|
1314
|
-
s2Queries = [];
|
|
1315
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
1316
|
-
expect(response.errors).toBeUndefined();
|
|
1317
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1318
|
-
Object {
|
|
1319
|
-
"t": Array [
|
|
1320
|
-
Object {
|
|
1321
|
-
"id": "1",
|
|
1322
|
-
},
|
|
1323
|
-
Object {
|
|
1324
|
-
"id": "2",
|
|
1325
|
-
},
|
|
1326
|
-
Object {
|
|
1327
|
-
"id": "3",
|
|
1328
|
-
},
|
|
1329
|
-
],
|
|
1330
|
-
}
|
|
1331
|
-
`);
|
|
1332
|
-
expect(s2Queries).toHaveLength(0);
|
|
1333
|
-
});
|
|
1334
|
-
|
|
1335
|
-
it('with constant conditions, second excluding', async () => {
|
|
1336
|
-
const operation = parseOperation(schema, `
|
|
1337
|
-
{
|
|
1338
|
-
t {
|
|
1339
|
-
id
|
|
1340
|
-
... on T1 @include(if: true) @skip(if: true) {
|
|
1341
|
-
b1
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
`);
|
|
1346
|
-
|
|
1347
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1348
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1349
|
-
QueryPlan {
|
|
1350
|
-
Fetch(service: "S1") {
|
|
1351
|
-
{
|
|
1352
|
-
t {
|
|
1353
|
-
__typename
|
|
1354
|
-
id
|
|
1355
|
-
... on T1 @include(if: true) @skip(if: true) {
|
|
1356
|
-
__typename
|
|
1357
|
-
id
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
},
|
|
1362
|
-
}
|
|
1363
|
-
`);
|
|
1364
|
-
|
|
1365
|
-
s2Queries = [];
|
|
1366
|
-
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
1367
|
-
expect(response.errors).toBeUndefined();
|
|
1368
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1369
|
-
Object {
|
|
1370
|
-
"t": Array [
|
|
1371
|
-
Object {
|
|
1372
|
-
"id": "1",
|
|
1373
|
-
},
|
|
1374
|
-
Object {
|
|
1375
|
-
"id": "2",
|
|
1376
|
-
},
|
|
1377
|
-
Object {
|
|
1378
|
-
"id": "3",
|
|
1379
|
-
},
|
|
1380
|
-
],
|
|
1381
|
-
}
|
|
1382
|
-
`);
|
|
1383
|
-
expect(s2Queries).toHaveLength(0);
|
|
1384
|
-
});
|
|
1385
|
-
|
|
1386
|
-
it('with variable conditions', async () => {
|
|
1387
|
-
const operation = parseOperation(schema, `
|
|
1388
|
-
query ($if1: Boolean!, $if2: Boolean!) {
|
|
1389
|
-
t {
|
|
1390
|
-
id
|
|
1391
|
-
... on T1 @include(if: $if1) @skip(if: $if2) {
|
|
1392
|
-
b1
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
`);
|
|
1397
|
-
|
|
1398
|
-
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
1399
|
-
// Ensures both @skip and @include have condition nodes.
|
|
1400
|
-
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1401
|
-
QueryPlan {
|
|
1402
|
-
Sequence {
|
|
1403
|
-
Fetch(service: "S1") {
|
|
1404
|
-
{
|
|
1405
|
-
t {
|
|
1406
|
-
__typename
|
|
1407
|
-
id
|
|
1408
|
-
... on T1 @include(if: $if1) @skip(if: $if2) {
|
|
1409
|
-
__typename
|
|
1410
|
-
id
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
},
|
|
1415
|
-
Skip(if: $if2) {
|
|
1416
|
-
Include(if: $if1) {
|
|
1417
|
-
Flatten(path: "t.@") {
|
|
1418
|
-
Fetch(service: "S2") {
|
|
1419
|
-
{
|
|
1420
|
-
... on T1 {
|
|
1421
|
-
... on T1 {
|
|
1422
|
-
__typename
|
|
1423
|
-
id
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
} =>
|
|
1427
|
-
{
|
|
1428
|
-
... on T1 {
|
|
1429
|
-
... on T1 {
|
|
1430
|
-
b1
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
},
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
},
|
|
1438
|
-
},
|
|
1439
|
-
}
|
|
1440
|
-
`);
|
|
1441
|
-
|
|
1442
|
-
s2Queries = [];
|
|
1443
|
-
// With data included by both conditions
|
|
1444
|
-
let response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: true, if2: false });
|
|
1445
|
-
expect(response.errors).toBeUndefined();
|
|
1446
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1447
|
-
Object {
|
|
1448
|
-
"t": Array [
|
|
1449
|
-
Object {
|
|
1450
|
-
"b1": 100,
|
|
1451
|
-
"id": "1",
|
|
1452
|
-
},
|
|
1453
|
-
Object {
|
|
1454
|
-
"id": "2",
|
|
1455
|
-
},
|
|
1456
|
-
Object {
|
|
1457
|
-
"b1": 300,
|
|
1458
|
-
"id": "3",
|
|
1459
|
-
},
|
|
1460
|
-
],
|
|
1461
|
-
}
|
|
1462
|
-
`);
|
|
1463
|
-
expect(s2Queries).toHaveLength(2);
|
|
1464
|
-
|
|
1465
|
-
s2Queries = [];
|
|
1466
|
-
// With data excluded by one condition
|
|
1467
|
-
response = await executePlan(queryPlan, operation, schema, serviceMap, { if1: true, if2: true });
|
|
1468
|
-
expect(response.errors).toBeUndefined();
|
|
1469
|
-
expect(response.data).toMatchInlineSnapshot(`
|
|
1470
|
-
Object {
|
|
1471
|
-
"t": Array [
|
|
1472
|
-
Object {
|
|
1473
|
-
"id": "1",
|
|
1474
|
-
},
|
|
1475
|
-
Object {
|
|
1476
|
-
"id": "2",
|
|
1477
|
-
},
|
|
1478
|
-
Object {
|
|
1479
|
-
"id": "3",
|
|
1480
|
-
},
|
|
1481
|
-
],
|
|
1482
|
-
}
|
|
1483
|
-
`);
|
|
1484
|
-
expect(s2Queries).toHaveLength(0);
|
|
1485
|
-
});
|
|
1486
|
-
})
|
|
1487
|
-
})
|
|
1488
|
-
});
|