@apollo/gateway 2.4.0 → 2.4.2

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,514 @@ 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
+ });
4866
+
4867
+ test('handles @requires on @interfaceObject that applies to only one of the queried implementation', async () => {
4868
+ // The case this test is that where the @interfaceObject in s2 has a @requires, but the query we send requests the field on which
4869
+ // there is this @require only for one of the implementation type, which it request another field with no require for another implementation.
4870
+ // And we're making sure the requirements only get queried for T1, the first type.
4871
+ const s1 = {
4872
+ name: 's1',
4873
+ typeDefs: gql`
4874
+ extend schema
4875
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
4876
+
4877
+ type Query {
4878
+ is: [I!]!
4879
+ }
4880
+
4881
+ interface I @key(fields: "id") {
4882
+ id: ID!
4883
+ name: String
4884
+ req: Req
4885
+ }
4886
+
4887
+ type T1 implements I @key(fields: "id") {
4888
+ id: ID!
4889
+ name: String
4890
+ req: Req
4891
+ }
4892
+
4893
+ type T2 implements I @key(fields: "id") {
4894
+ id: ID!
4895
+ name: String
4896
+ req: Req
4897
+ }
4898
+
4899
+ type Req {
4900
+ id: ID!
4901
+ }
4902
+ `,
4903
+ resolvers: {
4904
+ Query: {
4905
+ is: () => [
4906
+ { __typename: 'T1', id: '2', name: 'e2', req: { id: 'r1'} },
4907
+ { __typename: 'T2', id: '4', name: 'e4', req: { id: 'r2'} },
4908
+ { __typename: 'T1', id: '1', name: 'e1', req: { id: 'r3'} }
4909
+ ]
4910
+ },
4911
+ }
4912
+ }
4913
+
4914
+ const s2 = {
4915
+ name: 's2',
4916
+ typeDefs: gql`
4917
+ extend schema
4918
+ @link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
4919
+
4920
+ type I @key(fields: "id") @interfaceObject {
4921
+ id: ID!
4922
+ req: Req @external
4923
+ v: String! @requires(fields: "req { id }")
4924
+ }
4925
+
4926
+ type Req {
4927
+ id: ID! @external
4928
+ }
4929
+ `,
4930
+ resolvers: {
4931
+ I: {
4932
+ __resolveReference(ref: any) {
4933
+ return {
4934
+ ...ref,
4935
+ v: `req=${ref.req.id}`
4936
+ };
4937
+ },
4938
+ }
4939
+ }
4940
+ }
4941
+
4942
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
4943
+
4944
+ let operation = parseOp(`
4945
+ {
4946
+ is {
4947
+ ... on T1 {
4948
+ v
4949
+ }
4950
+ ... on T2 {
4951
+ name
4952
+ }
4953
+ }
4954
+ }
4955
+ `, schema);
4956
+
4957
+ let queryPlan = buildPlan(operation, queryPlanner);
4958
+ expect(queryPlan).toMatchInlineSnapshot(`
4959
+ QueryPlan {
4960
+ Sequence {
4961
+ Fetch(service: "s1") {
4962
+ {
4963
+ is {
4964
+ __typename
4965
+ ... on T1 {
4966
+ __typename
4967
+ id
4968
+ req {
4969
+ id
4970
+ }
4971
+ }
4972
+ ... on T2 {
4973
+ name
4974
+ }
4975
+ }
4976
+ }
4977
+ },
4978
+ Flatten(path: "is.@") {
4979
+ Fetch(service: "s2") {
4980
+ {
4981
+ ... on T1 {
4982
+ __typename
4983
+ id
4984
+ }
4985
+ ... on I {
4986
+ __typename
4987
+ req {
4988
+ id
4989
+ }
4990
+ }
4991
+ } =>
4992
+ {
4993
+ ... on I {
4994
+ v
4995
+ }
4996
+ }
4997
+ },
4998
+ },
4999
+ },
5000
+ }
5001
+ `);
5002
+
5003
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
5004
+ expect(response.errors).toBeUndefined();
5005
+ expect(response.data).toMatchInlineSnapshot(`
5006
+ Object {
5007
+ "is": Array [
5008
+ Object {
5009
+ "v": "req=r1",
5010
+ },
5011
+ Object {
5012
+ "name": "e4",
5013
+ },
5014
+ Object {
5015
+ "v": "req=r3",
5016
+ },
5017
+ ],
5018
+ }
5019
+ `);
5020
+
5021
+ // Sanity checking that if we ask for `v` (the field with @requires), then everything still works.
5022
+ operation = parseOp(`
5023
+ {
5024
+ is {
5025
+ ... on T1 {
5026
+ v
5027
+ }
5028
+ ... on T2 {
5029
+ v
5030
+ name
5031
+ }
5032
+ }
5033
+ }
5034
+ `, schema);
5035
+
5036
+ global.console = require('console');
5037
+ queryPlan = buildPlan(operation, queryPlanner);
5038
+ expect(queryPlan).toMatchInlineSnapshot(`
5039
+ QueryPlan {
5040
+ Sequence {
5041
+ Fetch(service: "s1") {
5042
+ {
5043
+ is {
5044
+ __typename
5045
+ ... on T1 {
5046
+ __typename
5047
+ id
5048
+ req {
5049
+ id
5050
+ }
5051
+ }
5052
+ ... on T2 {
5053
+ __typename
5054
+ id
5055
+ req {
5056
+ id
5057
+ }
5058
+ name
5059
+ }
5060
+ }
5061
+ }
5062
+ },
5063
+ Flatten(path: "is.@") {
5064
+ Fetch(service: "s2") {
5065
+ {
5066
+ ... on T1 {
5067
+ __typename
5068
+ id
5069
+ }
5070
+ ... on I {
5071
+ __typename
5072
+ req {
5073
+ id
5074
+ }
5075
+ }
5076
+ ... on T2 {
5077
+ __typename
5078
+ id
5079
+ }
5080
+ } =>
5081
+ {
5082
+ ... on I {
5083
+ v
5084
+ }
5085
+ }
5086
+ },
5087
+ },
5088
+ },
5089
+ }
5090
+ `);
5091
+
5092
+ response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
5093
+ expect(response.errors).toBeUndefined();
5094
+ expect(response.data).toMatchInlineSnapshot(`
5095
+ Object {
5096
+ "is": Array [
5097
+ Object {
5098
+ "v": "req=r1",
5099
+ },
5100
+ Object {
5101
+ "name": "e4",
5102
+ "v": "req=r2",
5103
+ },
5104
+ Object {
5105
+ "v": "req=r3",
5106
+ },
5107
+ ],
5108
+ }
5109
+ `);
5110
+ });
4603
5111
  });
4604
5112
 
4605
5113
  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