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