@apollo/gateway 2.4.0 → 2.4.1

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.
@@ -0,0 +1,140 @@
1
+ import gql from 'graphql-tag';
2
+ import { getFederatedTestingSchema, ServiceDefinitionModule } from './execution-utils';
3
+ import { Operation, parseOperation, Schema } from "@apollo/federation-internals";
4
+ import { QueryPlan } from '@apollo/query-planner';
5
+ import { LocalGraphQLDataSource } from '../datasources';
6
+ import { GatewayExecutionResult, GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
7
+ import { buildOperationContext } from '../operationContext';
8
+ import { executeQueryPlan } from '../executeQueryPlan';
9
+
10
+ function buildRequestContext(variables: Record<string, any>): GatewayGraphQLRequestContext {
11
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
12
+ // @ts-ignore
13
+ return {
14
+ cache: undefined as any,
15
+ context: {},
16
+ request: {
17
+ variables,
18
+ },
19
+ metrics: {},
20
+ };
21
+ }
22
+
23
+ async function executePlan(
24
+ queryPlan: QueryPlan,
25
+ operation: Operation,
26
+ schema: Schema,
27
+ serviceMap: { [serviceName: string]: LocalGraphQLDataSource },
28
+ variables: Record<string, any> = {},
29
+ ): Promise<GatewayExecutionResult> {
30
+ const apiSchema = schema.toAPISchema();
31
+ const operationContext = buildOperationContext({
32
+ schema: apiSchema.toGraphQLJSSchema(),
33
+ operationDocument: gql`${operation.toString()}`,
34
+ });
35
+ return executeQueryPlan(
36
+ queryPlan,
37
+ serviceMap,
38
+ buildRequestContext(variables),
39
+ operationContext,
40
+ schema.toGraphQLJSSchema(),
41
+ apiSchema,
42
+ );
43
+ }
44
+
45
+ describe('handling of introspection queries', () => {
46
+ const typeDefs: ServiceDefinitionModule[] = [
47
+ {
48
+ name: 'S1',
49
+ typeDefs: gql`
50
+ type Query {
51
+ t: [T]
52
+ }
53
+
54
+ interface T {
55
+ id: ID!
56
+ }
57
+
58
+ type T1 implements T @key(fields: "id") {
59
+ id: ID!
60
+ a1: Int
61
+ }
62
+
63
+ type T2 implements T @key(fields: "id") {
64
+ id: ID!
65
+ a2: Int
66
+ }
67
+ `,
68
+ },
69
+ ];
70
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema(typeDefs);
71
+
72
+ it('it handles aliases on introspection fields', async () => {
73
+ const operation = parseOperation(schema, `
74
+ {
75
+ myAlias: __type(name: "T1") {
76
+ kind
77
+ name
78
+ }
79
+ }
80
+ `);
81
+
82
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
83
+ const response = await executePlan(queryPlan, operation, schema, serviceMap);
84
+ expect(response.errors).toBeUndefined();
85
+ expect(response.data).toMatchInlineSnapshot(`
86
+ Object {
87
+ "myAlias": Object {
88
+ "kind": "OBJECT",
89
+ "name": "T1",
90
+ },
91
+ }
92
+ `);
93
+ });
94
+
95
+ it('it handles aliases inside introspection fields', async () => {
96
+ const operation = parseOperation(schema, `
97
+ {
98
+ __type(name: "T1") {
99
+ myKind: kind
100
+ name
101
+ }
102
+ }
103
+ `);
104
+
105
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
106
+ const response = await executePlan(queryPlan, operation, schema, serviceMap);
107
+ expect(response.errors).toBeUndefined();
108
+ expect(response.data).toMatchInlineSnapshot(`
109
+ Object {
110
+ "__type": Object {
111
+ "myKind": "OBJECT",
112
+ "name": "T1",
113
+ },
114
+ }
115
+ `);
116
+ });
117
+
118
+ it('it handles variables passed to introspection fields', async () => {
119
+ const operation = parseOperation(schema, `
120
+ query ($name: String!) {
121
+ __type(name: $name) {
122
+ kind
123
+ name
124
+ }
125
+ }
126
+ `);
127
+
128
+ const queryPlan = queryPlanner.buildQueryPlan(operation);
129
+ const response = await executePlan(queryPlan, operation, schema, serviceMap, { name: "T1" });
130
+ expect(response.errors).toBeUndefined();
131
+ expect(response.data).toMatchInlineSnapshot(`
132
+ Object {
133
+ "__type": Object {
134
+ "kind": "OBJECT",
135
+ "name": "T1",
136
+ },
137
+ }
138
+ `);
139
+ });
140
+ });
@@ -3830,7 +3830,7 @@ describe('executeQueryPlan', () => {
3830
3830
  name: 'S1',
3831
3831
  typeDefs: gql`
3832
3832
  extend schema
3833
- @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
3833
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
3834
3834
 
3835
3835
  type Query {
3836
3836
  iFromS1: I
@@ -3876,7 +3876,7 @@ describe('executeQueryPlan', () => {
3876
3876
  name: 'S2',
3877
3877
  typeDefs: gql`
3878
3878
  extend schema
3879
- @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
3879
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject"])
3880
3880
 
3881
3881
  type Query {
3882
3882
  iFromS2: I
@@ -4342,7 +4342,7 @@ describe('executeQueryPlan', () => {
4342
4342
  name: 'products',
4343
4343
  typeDefs: gql`
4344
4344
  extend schema
4345
- @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
4345
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
4346
4346
 
4347
4347
  type Query {
4348
4348
  products: [Product!]!
@@ -4391,7 +4391,7 @@ describe('executeQueryPlan', () => {
4391
4391
  name: 'reviews',
4392
4392
  typeDefs: gql`
4393
4393
  extend schema
4394
- @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
4394
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject"])
4395
4395
 
4396
4396
  type Query {
4397
4397
  allReviewedProducts: [Product!]!
@@ -4600,6 +4600,269 @@ describe('executeQueryPlan', () => {
4600
4600
  }
4601
4601
  `);
4602
4602
  });
4603
+
4604
+ test('handles querying @interfaceObject from a specific implementation', async () => {
4605
+ const s1 = {
4606
+ name: 's1',
4607
+ typeDefs: gql`
4608
+ extend schema
4609
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
4610
+
4611
+ type Query {
4612
+ ts: [T!]!
4613
+ }
4614
+
4615
+ interface I {
4616
+ id: ID!
4617
+ }
4618
+
4619
+ type T implements I @key(fields: "id") {
4620
+ id: ID!
4621
+ }
4622
+ `,
4623
+ resolvers: {
4624
+ Query: {
4625
+ ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
4626
+ },
4627
+ }
4628
+ }
4629
+
4630
+ const s2 = {
4631
+ name: 's2',
4632
+ typeDefs: gql`
4633
+ extend schema
4634
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
4635
+
4636
+ type I @key(fields: "id") @interfaceObject {
4637
+ id: ID!
4638
+ v: String
4639
+ }
4640
+ `,
4641
+ resolvers: {
4642
+ I: {
4643
+ __resolveReference(ref: any) {
4644
+ return {
4645
+ ...ref,
4646
+ v: `id=${ref.id}`
4647
+ };
4648
+ },
4649
+ }
4650
+ }
4651
+ }
4652
+
4653
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
4654
+
4655
+ let operation = parseOp(`
4656
+ {
4657
+ ts {
4658
+ v
4659
+ }
4660
+ }
4661
+ `, schema);
4662
+
4663
+ let queryPlan = buildPlan(operation, queryPlanner);
4664
+ const expectedPlan = `
4665
+ QueryPlan {
4666
+ Sequence {
4667
+ Fetch(service: "s1") {
4668
+ {
4669
+ ts {
4670
+ __typename
4671
+ id
4672
+ }
4673
+ }
4674
+ },
4675
+ Flatten(path: "ts.@") {
4676
+ Fetch(service: "s2") {
4677
+ {
4678
+ ... on T {
4679
+ __typename
4680
+ id
4681
+ }
4682
+ } =>
4683
+ {
4684
+ ... on I {
4685
+ v
4686
+ }
4687
+ }
4688
+ },
4689
+ },
4690
+ },
4691
+ }
4692
+ `;
4693
+ expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
4694
+
4695
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
4696
+ expect(response.data).toMatchInlineSnapshot(`
4697
+ Object {
4698
+ "ts": Array [
4699
+ Object {
4700
+ "v": "id=2",
4701
+ },
4702
+ Object {
4703
+ "v": "id=4",
4704
+ },
4705
+ Object {
4706
+ "v": "id=1",
4707
+ },
4708
+ ],
4709
+ }
4710
+ `);
4711
+ });
4712
+
4713
+ test('handles querying @interfaceObject from a specific implementation (even when the subgraph does not have the corresponding interface)', async () => {
4714
+ const s1 = {
4715
+ name: 's1',
4716
+ typeDefs: gql`
4717
+ extend schema
4718
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
4719
+
4720
+ type Query {
4721
+ ts: [T!]!
4722
+ }
4723
+
4724
+ type T @key(fields: "id", resolvable: false) {
4725
+ id: ID!
4726
+ }
4727
+ `,
4728
+ resolvers: {
4729
+ Query: {
4730
+ ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
4731
+ },
4732
+ }
4733
+ }
4734
+
4735
+ const s2 = {
4736
+ name: 's2',
4737
+ typeDefs: gql`
4738
+ extend schema
4739
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
4740
+
4741
+ interface I @key(fields: "id") {
4742
+ id: ID!
4743
+ required: String
4744
+ }
4745
+
4746
+ type T implements I @key(fields: "id") {
4747
+ id: ID!
4748
+ required: String
4749
+ }
4750
+
4751
+ `,
4752
+ resolvers: {
4753
+ I: {
4754
+ __resolveReference(ref: any) {
4755
+ return [
4756
+ { id: '1', __typename: "T", required: "r1" },
4757
+ { id: '2', __typename: "T", required: "r2" },
4758
+ { id: '3', __typename: "T", required: "r3" },
4759
+ { id: '4', __typename: "T", required: "r4" },
4760
+ ].find(({id}) => id === ref.id);
4761
+ },
4762
+ },
4763
+ }
4764
+ }
4765
+
4766
+ const s3 = {
4767
+ name: 's3',
4768
+ typeDefs: gql`
4769
+ extend schema
4770
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
4771
+
4772
+ type I @key(fields: "id") @interfaceObject {
4773
+ id: ID!
4774
+ required: String @external
4775
+ v: String @requires(fields: "required")
4776
+ }
4777
+ `,
4778
+ resolvers: {
4779
+ I: {
4780
+ __resolveReference(ref: any) {
4781
+ return {
4782
+ ...ref,
4783
+ v: `req=${ref.required}`
4784
+ };
4785
+ },
4786
+ }
4787
+ }
4788
+ }
4789
+
4790
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2, s3 ]);
4791
+
4792
+ let operation = parseOp(`
4793
+ {
4794
+ ts {
4795
+ v
4796
+ }
4797
+ }
4798
+ `, schema);
4799
+
4800
+ let queryPlan = buildPlan(operation, queryPlanner);
4801
+ const expectedPlan = `
4802
+ QueryPlan {
4803
+ Sequence {
4804
+ Fetch(service: "s1") {
4805
+ {
4806
+ ts {
4807
+ __typename
4808
+ id
4809
+ }
4810
+ }
4811
+ },
4812
+ Flatten(path: "ts.@") {
4813
+ Fetch(service: "s2") {
4814
+ {
4815
+ ... on I {
4816
+ __typename
4817
+ id
4818
+ }
4819
+ } =>
4820
+ {
4821
+ ... on I {
4822
+ __typename
4823
+ required
4824
+ }
4825
+ }
4826
+ },
4827
+ },
4828
+ Flatten(path: "ts.@") {
4829
+ Fetch(service: "s3") {
4830
+ {
4831
+ ... on I {
4832
+ __typename
4833
+ required
4834
+ id
4835
+ }
4836
+ } =>
4837
+ {
4838
+ ... on I {
4839
+ v
4840
+ }
4841
+ }
4842
+ },
4843
+ },
4844
+ },
4845
+ }
4846
+ `;
4847
+ expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
4848
+
4849
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
4850
+ expect(response.data).toMatchInlineSnapshot(`
4851
+ Object {
4852
+ "ts": Array [
4853
+ Object {
4854
+ "v": "req=r2",
4855
+ },
4856
+ Object {
4857
+ "v": "req=r4",
4858
+ },
4859
+ Object {
4860
+ "v": "req=r1",
4861
+ },
4862
+ ],
4863
+ }
4864
+ `);
4865
+ });
4603
4866
  });
4604
4867
 
4605
4868
  describe('fields with conflicting types needing aliasing', () => {
@@ -88,7 +88,7 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
88
88
  .reply(
89
89
  200,
90
90
  {
91
- data: { me: { username: '@jbaxleyiii' } },
91
+ data: { me: { username: '@apollo-user' } },
92
92
  },
93
93
  replyHeaders,
94
94
  );
@@ -107,7 +107,7 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
107
107
  const { data, errors } = unwrapSingleResultKind(result);
108
108
  expect(errors).toBeUndefined();
109
109
  expect(data).toEqual({
110
- me: { username: '@jbaxleyiii' },
110
+ me: { username: '@apollo-user' },
111
111
  });
112
112
  });
113
113
 
@@ -62,7 +62,7 @@ describe('Using supergraphSdl static configuration', () => {
62
62
 
63
63
  nock(accounts.url)
64
64
  .post('/', { query: '{me{username}}', variables: {} })
65
- .reply(200, { data: { me: { username: '@jbaxleyiii' } } });
65
+ .reply(200, { data: { me: { username: '@apollo-user' } } });
66
66
 
67
67
 
68
68
  const result = await server.executeOperation({
@@ -72,7 +72,7 @@ describe('Using supergraphSdl static configuration', () => {
72
72
  expect(unwrapSingleResultKind(result).data).toMatchInlineSnapshot(`
73
73
  Object {
74
74
  "me": Object {
75
- "username": "@jbaxleyiii",
75
+ "username": "@apollo-user",
76
76
  },
77
77
  }
78
78
  `);
@@ -6,8 +6,8 @@ expect.addSnapshotSerializer(astSerializer);
6
6
  expect.addSnapshotSerializer(queryPlanSerializer);
7
7
 
8
8
  const users = [
9
- { id: '1', name: 'Trevor Scheer', organizationId: '1', __typename: 'User' },
10
- { id: '1', name: 'Trevor Scheer', organizationId: '2', __typename: 'User' },
9
+ { id: '1', name: 'Trevor', organizationId: '1', __typename: 'User' },
10
+ { id: '1', name: 'Trevor', organizationId: '2', __typename: 'User' },
11
11
  { id: '2', name: 'James Baxley', organizationId: '1', __typename: 'User' },
12
12
  { id: '2', name: 'James Baxley', organizationId: '3', __typename: 'User' },
13
13
  ];
@@ -162,7 +162,7 @@ it('works fetches data correctly with complex / nested @key fields', async () =>
162
162
  reviews: [
163
163
  {
164
164
  author: {
165
- name: 'Trevor Scheer',
165
+ name: 'Trevor',
166
166
  organization: {
167
167
  name: 'Apollo',
168
168
  },
@@ -170,7 +170,7 @@ it('works fetches data correctly with complex / nested @key fields', async () =>
170
170
  },
171
171
  {
172
172
  author: {
173
- name: 'Trevor Scheer',
173
+ name: 'Trevor',
174
174
  organization: {
175
175
  name: 'Wayfair',
176
176
  },
@@ -6,7 +6,7 @@ expect.addSnapshotSerializer(astSerializer);
6
6
  expect.addSnapshotSerializer(queryPlanSerializer);
7
7
 
8
8
  const users = [
9
- { id: ['1', '1'], name: 'Trevor Scheer', __typename: 'User' },
9
+ { id: ['1', '1'], name: 'Trevor', __typename: 'User' },
10
10
  { id: ['2', '2'], name: 'James Baxley', __typename: 'User' },
11
11
  ];
12
12
 
@@ -89,7 +89,7 @@ it('fetches data correctly list type @key fields', async () => {
89
89
 
90
90
  expect(data).toEqual({
91
91
  reviews: [
92
- { body: 'Good', author: { name: 'Trevor Scheer' } },
92
+ { body: 'Good', author: { name: 'Trevor' } },
93
93
  { body: 'Bad', author: { name: 'James Baxley' } },
94
94
  ],
95
95
  });
@@ -7,7 +7,7 @@ expect.addSnapshotSerializer(queryPlanSerializer);
7
7
 
8
8
  const users = [
9
9
  { ssn: '111-11-1111', name: 'Trevor', id: '10', __typename: 'User' },
10
- { ssn: '222-22-2222', name: 'Scheer', id: '20', __typename: 'User' },
10
+ { ssn: '222-22-2222', name: 'Joel', id: '20', __typename: 'User' },
11
11
  { ssn: '333-33-3333', name: 'James', id: '30', __typename: 'User' },
12
12
  { ssn: '444-44-4444', name: 'Baxley', id: '40', __typename: 'User' },
13
13
  ];
@@ -148,7 +148,7 @@ it('fetches data correctly with multiple @key fields', async () => {
148
148
  body: 'B',
149
149
  author: {
150
150
  risk: 0.2,
151
- name: 'Scheer',
151
+ name: 'Joel',
152
152
  },
153
153
  },
154
154
  {
@@ -218,13 +218,13 @@ it('collapses nested requires with user-defined fragments', async () => {
218
218
  {
219
219
  user {
220
220
  __typename
221
- id
222
221
  preferences {
223
222
  favorites {
224
- animal
225
223
  color
224
+ animal
226
225
  }
227
226
  }
227
+ id
228
228
  }
229
229
  }
230
230
  },
@@ -18,7 +18,7 @@ const accounts = {
18
18
  `,
19
19
  resolvers: {
20
20
  Query: {
21
- me: () => ({ id: 1, name: 'Martijn' }),
21
+ me: () => ({ id: 1, name: 'Me' }),
22
22
  },
23
23
  },
24
24
  };
@@ -98,14 +98,7 @@ describe('value types', () => {
98
98
  reviews {
99
99
  metadata {
100
100
  __typename
101
- ... on KeyValue {
102
- key
103
- value
104
- }
105
- ... on Error {
106
- code
107
- message
108
- }
101
+ ...Metadata
109
102
  }
110
103
  }
111
104
  }
@@ -113,18 +106,22 @@ describe('value types', () => {
113
106
  reviews {
114
107
  metadata {
115
108
  __typename
116
- ... on KeyValue {
117
- key
118
- value
119
- }
120
- ... on Error {
121
- code
122
- message
123
- }
109
+ ...Metadata
124
110
  }
125
111
  }
126
112
  }
127
113
  }
114
+
115
+ fragment Metadata on MetadataOrError {
116
+ ... on KeyValue {
117
+ key
118
+ value
119
+ }
120
+ ... on Error {
121
+ code
122
+ message
123
+ }
124
+ }
128
125
  },
129
126
  },
130
127
  Flatten(path: "topProducts.@") {