@apollo/gateway 2.4.5 → 2.4.7

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.
Files changed (67) hide show
  1. package/dist/__generated__/graphqlTypes.d.ts +19 -1
  2. package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
  3. package/dist/__generated__/graphqlTypes.js +1 -0
  4. package/dist/__generated__/graphqlTypes.js.map +1 -1
  5. package/dist/executeQueryPlan.js +1 -1
  6. package/dist/executeQueryPlan.js.map +1 -1
  7. package/package.json +4 -4
  8. package/src/__generated__/graphqlTypes.ts +33 -2
  9. package/src/executeQueryPlan.ts +1 -1
  10. package/src/__mocks__/tsconfig.json +0 -7
  11. package/src/__tests__/.gitkeep +0 -0
  12. package/src/__tests__/CucumberREADME.md +0 -96
  13. package/src/__tests__/build-query-plan.feature +0 -1471
  14. package/src/__tests__/buildQueryPlan.test.ts +0 -1225
  15. package/src/__tests__/executeQueryPlan.conditions.test.ts +0 -1488
  16. package/src/__tests__/executeQueryPlan.introspection.test.ts +0 -140
  17. package/src/__tests__/executeQueryPlan.test.ts +0 -6140
  18. package/src/__tests__/execution-utils.ts +0 -124
  19. package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +0 -195
  20. package/src/__tests__/gateway/buildService.test.ts +0 -249
  21. package/src/__tests__/gateway/endToEnd.test.ts +0 -486
  22. package/src/__tests__/gateway/executor.test.ts +0 -96
  23. package/src/__tests__/gateway/extensions.test.ts +0 -37
  24. package/src/__tests__/gateway/lifecycle-hooks.test.ts +0 -239
  25. package/src/__tests__/gateway/opentelemetry.test.ts +0 -123
  26. package/src/__tests__/gateway/queryPlanCache.test.ts +0 -231
  27. package/src/__tests__/gateway/queryPlannerConfig.test.ts +0 -101
  28. package/src/__tests__/gateway/reporting.test.ts +0 -616
  29. package/src/__tests__/gateway/supergraphSdl.test.ts +0 -396
  30. package/src/__tests__/gateway/testUtils.ts +0 -89
  31. package/src/__tests__/integration/abstract-types.test.ts +0 -1861
  32. package/src/__tests__/integration/aliases.test.ts +0 -180
  33. package/src/__tests__/integration/boolean.test.ts +0 -279
  34. package/src/__tests__/integration/complex-key.test.ts +0 -197
  35. package/src/__tests__/integration/configuration.test.ts +0 -404
  36. package/src/__tests__/integration/custom-directives.test.ts +0 -174
  37. package/src/__tests__/integration/execution-style.test.ts +0 -35
  38. package/src/__tests__/integration/fragments.test.ts +0 -237
  39. package/src/__tests__/integration/list-key.test.ts +0 -128
  40. package/src/__tests__/integration/logger.test.ts +0 -122
  41. package/src/__tests__/integration/managed.test.ts +0 -319
  42. package/src/__tests__/integration/merge-arrays.test.ts +0 -34
  43. package/src/__tests__/integration/multiple-key.test.ts +0 -327
  44. package/src/__tests__/integration/mutations.test.ts +0 -287
  45. package/src/__tests__/integration/networkRequests.test.ts +0 -542
  46. package/src/__tests__/integration/nockMocks.ts +0 -157
  47. package/src/__tests__/integration/provides.test.ts +0 -77
  48. package/src/__tests__/integration/requires.test.ts +0 -359
  49. package/src/__tests__/integration/scope.test.ts +0 -557
  50. package/src/__tests__/integration/single-service.test.ts +0 -119
  51. package/src/__tests__/integration/unions.test.ts +0 -79
  52. package/src/__tests__/integration/value-types.test.ts +0 -382
  53. package/src/__tests__/integration/variables.test.ts +0 -120
  54. package/src/__tests__/nockAssertions.ts +0 -20
  55. package/src/__tests__/queryPlanCucumber.test.ts +0 -55
  56. package/src/__tests__/resultShaping.test.ts +0 -605
  57. package/src/__tests__/testSetup.ts +0 -1
  58. package/src/__tests__/tsconfig.json +0 -8
  59. package/src/core/__tests__/core.test.ts +0 -412
  60. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +0 -51
  61. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +0 -574
  62. package/src/schema-helper/__tests__/addExtensions.test.ts +0 -70
  63. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -364
  64. package/src/supergraphManagers/IntrospectAndCompose/__tests__/loadServicesFromRemoteEndpoint.test.ts +0 -40
  65. package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +0 -65
  66. package/src/supergraphManagers/UplinkSupergraphManager/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -511
  67. 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
- });