@apollo/gateway 2.3.4 → 2.3.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.
@@ -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
+ });
@@ -4587,6 +4587,514 @@ describe('executeQueryPlan', () => {
4587
4587
  }
4588
4588
  `);
4589
4589
  });
4590
+
4591
+ test('handles querying @interfaceObject from a specific implementation', async () => {
4592
+ const s1 = {
4593
+ name: 's1',
4594
+ typeDefs: gql`
4595
+ extend schema
4596
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
4597
+
4598
+ type Query {
4599
+ ts: [T!]!
4600
+ }
4601
+
4602
+ interface I {
4603
+ id: ID!
4604
+ }
4605
+
4606
+ type T implements I @key(fields: "id") {
4607
+ id: ID!
4608
+ }
4609
+ `,
4610
+ resolvers: {
4611
+ Query: {
4612
+ ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
4613
+ },
4614
+ }
4615
+ }
4616
+
4617
+ const s2 = {
4618
+ name: 's2',
4619
+ typeDefs: gql`
4620
+ extend schema
4621
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject", "@external", "@requires"])
4622
+
4623
+ type I @key(fields: "id") @interfaceObject {
4624
+ id: ID!
4625
+ v: String
4626
+ }
4627
+ `,
4628
+ resolvers: {
4629
+ I: {
4630
+ __resolveReference(ref: any) {
4631
+ return {
4632
+ ...ref,
4633
+ v: `id=${ref.id}`
4634
+ };
4635
+ },
4636
+ }
4637
+ }
4638
+ }
4639
+
4640
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
4641
+
4642
+ let operation = parseOp(`
4643
+ {
4644
+ ts {
4645
+ v
4646
+ }
4647
+ }
4648
+ `, schema);
4649
+
4650
+ let queryPlan = buildPlan(operation, queryPlanner);
4651
+ const expectedPlan = `
4652
+ QueryPlan {
4653
+ Sequence {
4654
+ Fetch(service: "s1") {
4655
+ {
4656
+ ts {
4657
+ __typename
4658
+ id
4659
+ }
4660
+ }
4661
+ },
4662
+ Flatten(path: "ts.@") {
4663
+ Fetch(service: "s2") {
4664
+ {
4665
+ ... on T {
4666
+ __typename
4667
+ id
4668
+ }
4669
+ } =>
4670
+ {
4671
+ ... on I {
4672
+ v
4673
+ }
4674
+ }
4675
+ },
4676
+ },
4677
+ },
4678
+ }
4679
+ `;
4680
+ expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
4681
+
4682
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
4683
+ expect(response.data).toMatchInlineSnapshot(`
4684
+ Object {
4685
+ "ts": Array [
4686
+ Object {
4687
+ "v": "id=2",
4688
+ },
4689
+ Object {
4690
+ "v": "id=4",
4691
+ },
4692
+ Object {
4693
+ "v": "id=1",
4694
+ },
4695
+ ],
4696
+ }
4697
+ `);
4698
+ });
4699
+
4700
+ test('handles querying @interfaceObject from a specific implementation (even when the subgraph does not have the corresponding interface)', async () => {
4701
+ const s1 = {
4702
+ name: 's1',
4703
+ typeDefs: gql`
4704
+ extend schema
4705
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
4706
+
4707
+ type Query {
4708
+ ts: [T!]!
4709
+ }
4710
+
4711
+ type T @key(fields: "id", resolvable: false) {
4712
+ id: ID!
4713
+ }
4714
+ `,
4715
+ resolvers: {
4716
+ Query: {
4717
+ ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
4718
+ },
4719
+ }
4720
+ }
4721
+
4722
+ const s2 = {
4723
+ name: 's2',
4724
+ typeDefs: gql`
4725
+ extend schema
4726
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject"])
4727
+
4728
+ interface I @key(fields: "id") {
4729
+ id: ID!
4730
+ required: String
4731
+ }
4732
+
4733
+ type T implements I @key(fields: "id") {
4734
+ id: ID!
4735
+ required: String
4736
+ }
4737
+
4738
+ `,
4739
+ resolvers: {
4740
+ I: {
4741
+ __resolveReference(ref: any) {
4742
+ return [
4743
+ { id: '1', __typename: "T", required: "r1" },
4744
+ { id: '2', __typename: "T", required: "r2" },
4745
+ { id: '3', __typename: "T", required: "r3" },
4746
+ { id: '4', __typename: "T", required: "r4" },
4747
+ ].find(({id}) => id === ref.id);
4748
+ },
4749
+ },
4750
+ }
4751
+ }
4752
+
4753
+ const s3 = {
4754
+ name: 's3',
4755
+ typeDefs: gql`
4756
+ extend schema
4757
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject", "@external", "@requires"])
4758
+
4759
+ type I @key(fields: "id") @interfaceObject {
4760
+ id: ID!
4761
+ required: String @external
4762
+ v: String @requires(fields: "required")
4763
+ }
4764
+ `,
4765
+ resolvers: {
4766
+ I: {
4767
+ __resolveReference(ref: any) {
4768
+ return {
4769
+ ...ref,
4770
+ v: `req=${ref.required}`
4771
+ };
4772
+ },
4773
+ }
4774
+ }
4775
+ }
4776
+
4777
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2, s3 ]);
4778
+
4779
+ let operation = parseOp(`
4780
+ {
4781
+ ts {
4782
+ v
4783
+ }
4784
+ }
4785
+ `, schema);
4786
+
4787
+ let queryPlan = buildPlan(operation, queryPlanner);
4788
+ const expectedPlan = `
4789
+ QueryPlan {
4790
+ Sequence {
4791
+ Fetch(service: "s1") {
4792
+ {
4793
+ ts {
4794
+ __typename
4795
+ id
4796
+ }
4797
+ }
4798
+ },
4799
+ Flatten(path: "ts.@") {
4800
+ Fetch(service: "s2") {
4801
+ {
4802
+ ... on I {
4803
+ __typename
4804
+ id
4805
+ }
4806
+ } =>
4807
+ {
4808
+ ... on I {
4809
+ __typename
4810
+ required
4811
+ }
4812
+ }
4813
+ },
4814
+ },
4815
+ Flatten(path: "ts.@") {
4816
+ Fetch(service: "s3") {
4817
+ {
4818
+ ... on I {
4819
+ __typename
4820
+ required
4821
+ id
4822
+ }
4823
+ } =>
4824
+ {
4825
+ ... on I {
4826
+ v
4827
+ }
4828
+ }
4829
+ },
4830
+ },
4831
+ },
4832
+ }
4833
+ `;
4834
+ expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
4835
+
4836
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
4837
+ expect(response.data).toMatchInlineSnapshot(`
4838
+ Object {
4839
+ "ts": Array [
4840
+ Object {
4841
+ "v": "req=r2",
4842
+ },
4843
+ Object {
4844
+ "v": "req=r4",
4845
+ },
4846
+ Object {
4847
+ "v": "req=r1",
4848
+ },
4849
+ ],
4850
+ }
4851
+ `);
4852
+ });
4853
+
4854
+ test('handles @requires on @interfaceObject that applies to only one of the queried implementation', async () => {
4855
+ // The case this test is that where the @interfaceObject in s2 has a @requires, but the query we send requests the field on which
4856
+ // there is this @require only for one of the implementation type, which it request another field with no require for another implementation.
4857
+ // And we're making sure the requirements only get queried for T1, the first type.
4858
+ const s1 = {
4859
+ name: 's1',
4860
+ typeDefs: gql`
4861
+ extend schema
4862
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
4863
+
4864
+ type Query {
4865
+ is: [I!]!
4866
+ }
4867
+
4868
+ interface I @key(fields: "id") {
4869
+ id: ID!
4870
+ name: String
4871
+ req: Req
4872
+ }
4873
+
4874
+ type T1 implements I @key(fields: "id") {
4875
+ id: ID!
4876
+ name: String
4877
+ req: Req
4878
+ }
4879
+
4880
+ type T2 implements I @key(fields: "id") {
4881
+ id: ID!
4882
+ name: String
4883
+ req: Req
4884
+ }
4885
+
4886
+ type Req {
4887
+ id: ID!
4888
+ }
4889
+ `,
4890
+ resolvers: {
4891
+ Query: {
4892
+ is: () => [
4893
+ { __typename: 'T1', id: '2', name: 'e2', req: { id: 'r1'} },
4894
+ { __typename: 'T2', id: '4', name: 'e4', req: { id: 'r2'} },
4895
+ { __typename: 'T1', id: '1', name: 'e1', req: { id: 'r3'} }
4896
+ ]
4897
+ },
4898
+ }
4899
+ }
4900
+
4901
+ const s2 = {
4902
+ name: 's2',
4903
+ typeDefs: gql`
4904
+ extend schema
4905
+ @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject", "@external", "@requires"])
4906
+
4907
+ type I @key(fields: "id") @interfaceObject {
4908
+ id: ID!
4909
+ req: Req @external
4910
+ v: String! @requires(fields: "req { id }")
4911
+ }
4912
+
4913
+ type Req {
4914
+ id: ID! @external
4915
+ }
4916
+ `,
4917
+ resolvers: {
4918
+ I: {
4919
+ __resolveReference(ref: any) {
4920
+ return {
4921
+ ...ref,
4922
+ v: `req=${ref.req.id}`
4923
+ };
4924
+ },
4925
+ }
4926
+ }
4927
+ }
4928
+
4929
+ const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
4930
+
4931
+ let operation = parseOp(`
4932
+ {
4933
+ is {
4934
+ ... on T1 {
4935
+ v
4936
+ }
4937
+ ... on T2 {
4938
+ name
4939
+ }
4940
+ }
4941
+ }
4942
+ `, schema);
4943
+
4944
+ let queryPlan = buildPlan(operation, queryPlanner);
4945
+ expect(queryPlan).toMatchInlineSnapshot(`
4946
+ QueryPlan {
4947
+ Sequence {
4948
+ Fetch(service: "s1") {
4949
+ {
4950
+ is {
4951
+ __typename
4952
+ ... on T1 {
4953
+ __typename
4954
+ id
4955
+ req {
4956
+ id
4957
+ }
4958
+ }
4959
+ ... on T2 {
4960
+ name
4961
+ }
4962
+ }
4963
+ }
4964
+ },
4965
+ Flatten(path: "is.@") {
4966
+ Fetch(service: "s2") {
4967
+ {
4968
+ ... on T1 {
4969
+ __typename
4970
+ id
4971
+ }
4972
+ ... on I {
4973
+ __typename
4974
+ req {
4975
+ id
4976
+ }
4977
+ }
4978
+ } =>
4979
+ {
4980
+ ... on I {
4981
+ v
4982
+ }
4983
+ }
4984
+ },
4985
+ },
4986
+ },
4987
+ }
4988
+ `);
4989
+
4990
+ let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
4991
+ expect(response.errors).toBeUndefined();
4992
+ expect(response.data).toMatchInlineSnapshot(`
4993
+ Object {
4994
+ "is": Array [
4995
+ Object {
4996
+ "v": "req=r1",
4997
+ },
4998
+ Object {
4999
+ "name": "e4",
5000
+ },
5001
+ Object {
5002
+ "v": "req=r3",
5003
+ },
5004
+ ],
5005
+ }
5006
+ `);
5007
+
5008
+ // Sanity checking that if we ask for `v` (the field with @requires), then everything still works.
5009
+ operation = parseOp(`
5010
+ {
5011
+ is {
5012
+ ... on T1 {
5013
+ v
5014
+ }
5015
+ ... on T2 {
5016
+ v
5017
+ name
5018
+ }
5019
+ }
5020
+ }
5021
+ `, schema);
5022
+
5023
+ global.console = require('console');
5024
+ queryPlan = buildPlan(operation, queryPlanner);
5025
+ expect(queryPlan).toMatchInlineSnapshot(`
5026
+ QueryPlan {
5027
+ Sequence {
5028
+ Fetch(service: "s1") {
5029
+ {
5030
+ is {
5031
+ __typename
5032
+ ... on T1 {
5033
+ __typename
5034
+ id
5035
+ req {
5036
+ id
5037
+ }
5038
+ }
5039
+ ... on T2 {
5040
+ __typename
5041
+ id
5042
+ req {
5043
+ id
5044
+ }
5045
+ name
5046
+ }
5047
+ }
5048
+ }
5049
+ },
5050
+ Flatten(path: "is.@") {
5051
+ Fetch(service: "s2") {
5052
+ {
5053
+ ... on T1 {
5054
+ __typename
5055
+ id
5056
+ }
5057
+ ... on I {
5058
+ __typename
5059
+ req {
5060
+ id
5061
+ }
5062
+ }
5063
+ ... on T2 {
5064
+ __typename
5065
+ id
5066
+ }
5067
+ } =>
5068
+ {
5069
+ ... on I {
5070
+ v
5071
+ }
5072
+ }
5073
+ },
5074
+ },
5075
+ },
5076
+ }
5077
+ `);
5078
+
5079
+ response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
5080
+ expect(response.errors).toBeUndefined();
5081
+ expect(response.data).toMatchInlineSnapshot(`
5082
+ Object {
5083
+ "is": Array [
5084
+ Object {
5085
+ "v": "req=r1",
5086
+ },
5087
+ Object {
5088
+ "name": "e4",
5089
+ "v": "req=r2",
5090
+ },
5091
+ Object {
5092
+ "v": "req=r3",
5093
+ },
5094
+ ],
5095
+ }
5096
+ `);
5097
+ });
4590
5098
  });
4591
5099
 
4592
5100
  describe('fields with conflicting types needing aliasing', () => {