@apollo/gateway 2.3.4 → 2.4.0-alpha.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.
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +38 -12
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/executeQueryPlan.conditions.test.ts +1488 -0
- package/src/__tests__/executeQueryPlan.test.ts +28 -16
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/buildService.test.ts +17 -13
- package/src/__tests__/gateway/endToEnd.test.ts +91 -94
- package/src/__tests__/gateway/queryPlanCache.test.ts +18 -19
- package/src/__tests__/gateway/queryPlannerConfig.test.ts +101 -0
- package/src/__tests__/gateway/reporting.test.ts +23 -45
- package/src/__tests__/gateway/supergraphSdl.test.ts +5 -3
- package/src/__tests__/gateway/testUtils.ts +89 -0
- package/src/__tests__/integration/aliases.test.ts +5 -5
- package/src/__tests__/integration/boolean.test.ts +9 -9
- package/src/__tests__/integration/managed.test.ts +10 -9
- package/src/executeQueryPlan.ts +58 -18
- package/src/index.ts +10 -5
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from 'apollo-federation-integration-testsuite';
|
|
17
17
|
import { QueryPlan, QueryPlanner } from '@apollo/query-planner';
|
|
18
18
|
import { ApolloGateway } from '..';
|
|
19
|
-
import {
|
|
19
|
+
import { ApolloServer } from '@apollo/server';
|
|
20
20
|
import { getFederatedTestingSchema } from './execution-utils';
|
|
21
21
|
import { Schema, Operation, parseOperation, buildSchemaFromAST, arrayEquals } from '@apollo/federation-internals';
|
|
22
22
|
import {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
GraphQLResolverMap,
|
|
25
25
|
} from '@apollo/subgraph/src/schema-helper';
|
|
26
26
|
import {GatewayExecutionResult, GatewayGraphQLRequestContext} from '@apollo/server-gateway-interface';
|
|
27
|
+
import { unwrapSingleResultKind } from './gateway/testUtils';
|
|
27
28
|
|
|
28
29
|
expect.addSnapshotSerializer(astSerializer);
|
|
29
30
|
expect.addSnapshotSerializer(queryPlanSerializer);
|
|
@@ -1325,13 +1326,12 @@ describe('executeQueryPlan', () => {
|
|
|
1325
1326
|
// But note that this is only one possible initialization path for the
|
|
1326
1327
|
// gateway, and with the current duplication of logic we'd actually need
|
|
1327
1328
|
// to test other scenarios (like loading from supergraph SDL) separately.
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1329
|
+
const server = new ApolloServer({
|
|
1330
|
+
gateway: new ApolloGateway({
|
|
1331
|
+
supergraphSdl: print(superGraphWithInaccessible),
|
|
1332
|
+
}),
|
|
1330
1333
|
});
|
|
1331
|
-
|
|
1332
|
-
const { schema, executor } = await gateway.load();
|
|
1333
|
-
|
|
1334
|
-
const server = new ApolloServer({ schema, executor });
|
|
1334
|
+
await server.start();
|
|
1335
1335
|
|
|
1336
1336
|
const query = `#graphql
|
|
1337
1337
|
query {
|
|
@@ -1349,10 +1349,22 @@ describe('executeQueryPlan', () => {
|
|
|
1349
1349
|
query,
|
|
1350
1350
|
});
|
|
1351
1351
|
|
|
1352
|
-
|
|
1353
|
-
expect(
|
|
1352
|
+
const { errors, data } = unwrapSingleResultKind(response);
|
|
1353
|
+
expect(data).toBeUndefined();
|
|
1354
|
+
expect(errors).toMatchInlineSnapshot(`
|
|
1354
1355
|
Array [
|
|
1355
|
-
|
|
1356
|
+
Object {
|
|
1357
|
+
"extensions": Object {
|
|
1358
|
+
"code": "GRAPHQL_VALIDATION_FAILED",
|
|
1359
|
+
},
|
|
1360
|
+
"locations": Array [
|
|
1361
|
+
Object {
|
|
1362
|
+
"column": 15,
|
|
1363
|
+
"line": 7,
|
|
1364
|
+
},
|
|
1365
|
+
],
|
|
1366
|
+
"message": "Cannot query field \\"ssn\\" on type \\"User\\".",
|
|
1367
|
+
},
|
|
1356
1368
|
]
|
|
1357
1369
|
`);
|
|
1358
1370
|
});
|
|
@@ -3817,7 +3829,7 @@ describe('executeQueryPlan', () => {
|
|
|
3817
3829
|
name: 'S1',
|
|
3818
3830
|
typeDefs: gql`
|
|
3819
3831
|
extend schema
|
|
3820
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
3832
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
|
|
3821
3833
|
|
|
3822
3834
|
type Query {
|
|
3823
3835
|
iFromS1: I
|
|
@@ -3863,7 +3875,7 @@ describe('executeQueryPlan', () => {
|
|
|
3863
3875
|
name: 'S2',
|
|
3864
3876
|
typeDefs: gql`
|
|
3865
3877
|
extend schema
|
|
3866
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
3878
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
|
|
3867
3879
|
|
|
3868
3880
|
type Query {
|
|
3869
3881
|
iFromS2: I
|
|
@@ -4207,7 +4219,7 @@ describe('executeQueryPlan', () => {
|
|
|
4207
4219
|
// any specific extra resolving.
|
|
4208
4220
|
const tester = defineSchema({});
|
|
4209
4221
|
|
|
4210
|
-
|
|
4222
|
+
const { plan, response } = await tester(`
|
|
4211
4223
|
query {
|
|
4212
4224
|
iFromS2 {
|
|
4213
4225
|
y
|
|
@@ -4242,7 +4254,7 @@ describe('executeQueryPlan', () => {
|
|
|
4242
4254
|
s1: { iResolveReferenceExtra: (id: string) => ({ __typename: id === 'idA' ? 'A' : 'B' }), },
|
|
4243
4255
|
});
|
|
4244
4256
|
|
|
4245
|
-
|
|
4257
|
+
const { plan, response } = await tester(`
|
|
4246
4258
|
query {
|
|
4247
4259
|
iFromS2 {
|
|
4248
4260
|
... on B {
|
|
@@ -4329,7 +4341,7 @@ describe('executeQueryPlan', () => {
|
|
|
4329
4341
|
name: 'products',
|
|
4330
4342
|
typeDefs: gql`
|
|
4331
4343
|
extend schema
|
|
4332
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
4344
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
|
|
4333
4345
|
|
|
4334
4346
|
type Query {
|
|
4335
4347
|
products: [Product!]!
|
|
@@ -4378,7 +4390,7 @@ describe('executeQueryPlan', () => {
|
|
|
4378
4390
|
name: 'reviews',
|
|
4379
4391
|
typeDefs: gql`
|
|
4380
4392
|
extend schema
|
|
4381
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
4393
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
|
|
4382
4394
|
|
|
4383
4395
|
type Query {
|
|
4384
4396
|
allReviewedProducts: [Product!]!
|
|
@@ -17,7 +17,7 @@ import { queryPlanSerializer, astSerializer } from 'apollo-federation-integratio
|
|
|
17
17
|
import gql from 'graphql-tag';
|
|
18
18
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
19
19
|
import { composeServices } from '@apollo/composition';
|
|
20
|
-
import { buildSchema, operationFromDocument, ServiceDefinition } from '@apollo/federation-internals';
|
|
20
|
+
import { buildSchema, Operation, operationFromDocument, ServiceDefinition } from '@apollo/federation-internals';
|
|
21
21
|
import { GatewayExecutionResult, GatewayGraphQLRequest } from '@apollo/server-gateway-interface';
|
|
22
22
|
|
|
23
23
|
const prettyFormat = require('pretty-format');
|
|
@@ -39,7 +39,7 @@ export async function execute(
|
|
|
39
39
|
request: GatewayGraphQLRequest,
|
|
40
40
|
services: ServiceDefinitionModule[] = fixtures,
|
|
41
41
|
logger: Logger = console,
|
|
42
|
-
): Promise<GatewayExecutionResult & { queryPlan: QueryPlan }> {
|
|
42
|
+
): Promise<GatewayExecutionResult & { queryPlan: QueryPlan, operation: Operation }> {
|
|
43
43
|
const serviceMap = Object.fromEntries(
|
|
44
44
|
services.map(({ name, typeDefs, resolvers }) => {
|
|
45
45
|
return [
|
|
@@ -79,7 +79,7 @@ export async function execute(
|
|
|
79
79
|
apiSchema,
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
return { ...result, queryPlan };
|
|
82
|
+
return { ...result, queryPlan, operation };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export function buildLocalService(modules: GraphQLSchemaModule[]) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import nock from 'nock';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ApolloServer } from '@apollo/server';
|
|
4
4
|
|
|
5
5
|
import { RemoteGraphQLDataSource } from '../../datasources/RemoteGraphQLDataSource';
|
|
6
6
|
import { ApolloGateway, SERVICE_DEFINITION_QUERY } from '../../';
|
|
7
7
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
8
8
|
import { GraphQLDataSourceRequestKind } from '../../datasources/types';
|
|
9
9
|
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
10
|
+
import { unwrapSingleResultKind } from '../gateway/testUtils';
|
|
10
11
|
|
|
11
12
|
beforeEach(nockBeforeEach);
|
|
12
13
|
afterEach(nockAfterEach);
|
|
@@ -62,15 +63,10 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
|
|
|
62
63
|
},
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
const { schema, executor } = await gateway.load();
|
|
66
|
-
|
|
67
66
|
const server = new ApolloServer({
|
|
68
|
-
|
|
69
|
-
executor,
|
|
70
|
-
context: () => ({
|
|
71
|
-
userId: '1234',
|
|
72
|
-
}),
|
|
67
|
+
gateway,
|
|
73
68
|
});
|
|
69
|
+
await server.start();
|
|
74
70
|
|
|
75
71
|
const query = `#graphql
|
|
76
72
|
{
|
|
@@ -97,12 +93,20 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
|
|
|
97
93
|
replyHeaders,
|
|
98
94
|
);
|
|
99
95
|
|
|
100
|
-
const result = await server.executeOperation(
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
const result = await server.executeOperation(
|
|
97
|
+
{
|
|
98
|
+
query,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
contextValue: {
|
|
102
|
+
userId: '1234',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
);
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
expect(
|
|
107
|
+
const { data, errors } = unwrapSingleResultKind(result);
|
|
108
|
+
expect(errors).toBeUndefined();
|
|
109
|
+
expect(data).toEqual({
|
|
106
110
|
me: { username: '@jbaxleyiii' },
|
|
107
111
|
});
|
|
108
112
|
});
|
|
@@ -1,68 +1,49 @@
|
|
|
1
|
-
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
2
|
-
import { ApolloServer } from 'apollo-server';
|
|
3
|
-
import fetch, { Response } from 'node-fetch';
|
|
4
|
-
import { ApolloGateway } from '../..';
|
|
5
1
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
6
|
-
import {
|
|
7
|
-
import { GraphQLSchemaModule } from '@apollo/subgraph/src/schema-helper';
|
|
8
|
-
import { buildSchema, ObjectType, ServiceDefinition } from '@apollo/federation-internals';
|
|
2
|
+
import { buildSchema, ObjectType } from '@apollo/federation-internals';
|
|
9
3
|
import gql from 'graphql-tag';
|
|
10
4
|
import { printSchema } from 'graphql';
|
|
5
|
+
import { startSubgraphsAndGateway, Services } from './testUtils'
|
|
6
|
+
import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';
|
|
7
|
+
import { QueryPlan } from '@apollo/query-planner';
|
|
8
|
+
import { createHash } from '@apollo/utils.createhash';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const server = new ApolloServer({
|
|
15
|
-
schema,
|
|
16
|
-
// Manually installing the inline trace plugin means it doesn't log a message.
|
|
17
|
-
plugins: [ApolloServerPluginInlineTrace()],
|
|
18
|
-
});
|
|
19
|
-
const { url } = await server.listen({ port: 0 });
|
|
20
|
-
return { url, server };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let backendServers: ApolloServer[];
|
|
24
|
-
let gateway: ApolloGateway;
|
|
25
|
-
let gatewayServer: ApolloServer;
|
|
26
|
-
let gatewayUrl: string;
|
|
27
|
-
|
|
28
|
-
async function startServicesAndGateway(servicesDefs: ServiceDefinition[]) {
|
|
29
|
-
backendServers = [];
|
|
30
|
-
const serviceList = [];
|
|
31
|
-
for (const serviceDef of servicesDefs) {
|
|
32
|
-
const { server, url } = await startFederatedServer([serviceDef]);
|
|
33
|
-
backendServers.push(server);
|
|
34
|
-
serviceList.push({ name: serviceDef.name, url });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
gateway = new ApolloGateway({ serviceList });
|
|
38
|
-
gatewayServer = new ApolloServer({
|
|
39
|
-
gateway,
|
|
40
|
-
});
|
|
41
|
-
({ url: gatewayUrl } = await gatewayServer.listen({ port: 0 }));
|
|
10
|
+
function approximateObjectSize<T>(obj: T): number {
|
|
11
|
+
return Buffer.byteLength(JSON.stringify(obj), 'utf8');
|
|
42
12
|
}
|
|
43
13
|
|
|
44
|
-
|
|
45
|
-
return fetch(gatewayUrl, {
|
|
46
|
-
method: 'POST',
|
|
47
|
-
headers: {
|
|
48
|
-
'Content-Type': 'application/json',
|
|
49
|
-
},
|
|
50
|
-
body: JSON.stringify({ query }),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
14
|
+
let services: Services;
|
|
53
15
|
|
|
54
16
|
afterEach(async () => {
|
|
55
|
-
|
|
56
|
-
await
|
|
57
|
-
}
|
|
58
|
-
if (gatewayServer) {
|
|
59
|
-
await gatewayServer.stop();
|
|
17
|
+
if (services) {
|
|
18
|
+
await services.stop();
|
|
60
19
|
}
|
|
61
20
|
});
|
|
62
21
|
|
|
22
|
+
|
|
63
23
|
describe('caching', () => {
|
|
24
|
+
const cache = new InMemoryLRUCache<QueryPlan>({maxSize: Math.pow(2, 20) * (30), sizeCalculation: approximateObjectSize});
|
|
64
25
|
beforeEach(async () => {
|
|
65
|
-
await
|
|
26
|
+
services = await startSubgraphsAndGateway(fixtures, { gatewayConfig: { queryPlannerConfig: { cache } } });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it(`cached query plan`, async () => {
|
|
30
|
+
const query = `
|
|
31
|
+
query {
|
|
32
|
+
me {
|
|
33
|
+
name {
|
|
34
|
+
first
|
|
35
|
+
last
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
topProducts {
|
|
39
|
+
name
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
await services.queryGateway(query);
|
|
45
|
+
const queryHash:string = createHash('sha256').update(query).digest('hex');
|
|
46
|
+
expect(await cache.get(queryHash)).toBeTruthy();
|
|
66
47
|
});
|
|
67
48
|
|
|
68
49
|
it(`cache control`, async () => {
|
|
@@ -80,7 +61,7 @@ describe('caching', () => {
|
|
|
80
61
|
}
|
|
81
62
|
`;
|
|
82
63
|
|
|
83
|
-
const response = await queryGateway(query);
|
|
64
|
+
const response = await services.queryGateway(query);
|
|
84
65
|
const result = await response.json();
|
|
85
66
|
expect(result).toMatchInlineSnapshot(`
|
|
86
67
|
Object {
|
|
@@ -134,7 +115,7 @@ describe('caching', () => {
|
|
|
134
115
|
}
|
|
135
116
|
`;
|
|
136
117
|
|
|
137
|
-
const response = await queryGateway(query);
|
|
118
|
+
const response = await services.queryGateway(query);
|
|
138
119
|
const result = await response.json();
|
|
139
120
|
expect(result).toMatchInlineSnapshot(`
|
|
140
121
|
Object {
|
|
@@ -167,7 +148,7 @@ describe('caching', () => {
|
|
|
167
148
|
},
|
|
168
149
|
}
|
|
169
150
|
`);
|
|
170
|
-
expect(response.headers.get('cache-control')).toBe(
|
|
151
|
+
expect(response.headers.get('cache-control')).toBe('no-store');
|
|
171
152
|
});
|
|
172
153
|
});
|
|
173
154
|
|
|
@@ -185,8 +166,8 @@ describe('end-to-end features', () => {
|
|
|
185
166
|
typeDefs: gql`
|
|
186
167
|
extend schema
|
|
187
168
|
@link(
|
|
188
|
-
url: "https://specs.apollo.dev/federation/v2.0"
|
|
189
|
-
import: [
|
|
169
|
+
url: "https://specs.apollo.dev/federation/v2.0"
|
|
170
|
+
import: ["@key", { name: "@tag", as: "@federationTag" }]
|
|
190
171
|
)
|
|
191
172
|
|
|
192
173
|
type Query {
|
|
@@ -202,10 +183,10 @@ describe('end-to-end features', () => {
|
|
|
202
183
|
Query: {
|
|
203
184
|
t: () => ({
|
|
204
185
|
k: 42,
|
|
205
|
-
x: 1
|
|
186
|
+
x: 1,
|
|
206
187
|
}),
|
|
207
|
-
}
|
|
208
|
-
}
|
|
188
|
+
},
|
|
189
|
+
},
|
|
209
190
|
};
|
|
210
191
|
|
|
211
192
|
const subgraphB = {
|
|
@@ -214,8 +195,8 @@ describe('end-to-end features', () => {
|
|
|
214
195
|
typeDefs: gql`
|
|
215
196
|
extend schema
|
|
216
197
|
@link(
|
|
217
|
-
url: "https://specs.apollo.dev/federation/v2.0"
|
|
218
|
-
import: [
|
|
198
|
+
url: "https://specs.apollo.dev/federation/v2.0"
|
|
199
|
+
import: ["@key", { name: "@tag", as: "@federationTag" }]
|
|
219
200
|
)
|
|
220
201
|
|
|
221
202
|
type T @key(fields: "k") {
|
|
@@ -226,13 +207,13 @@ describe('end-to-end features', () => {
|
|
|
226
207
|
resolvers: {
|
|
227
208
|
T: {
|
|
228
209
|
__resolveReference: ({ k }: { k: string }) => {
|
|
229
|
-
return k === '42' ?
|
|
210
|
+
return k === '42' ? { y: 2 } : undefined;
|
|
230
211
|
},
|
|
231
|
-
}
|
|
232
|
-
}
|
|
212
|
+
},
|
|
213
|
+
},
|
|
233
214
|
};
|
|
234
215
|
|
|
235
|
-
await
|
|
216
|
+
services = await startSubgraphsAndGateway([subgraphA, subgraphB]);
|
|
236
217
|
|
|
237
218
|
const query = `
|
|
238
219
|
{
|
|
@@ -243,7 +224,7 @@ describe('end-to-end features', () => {
|
|
|
243
224
|
}
|
|
244
225
|
`;
|
|
245
226
|
|
|
246
|
-
const response = await queryGateway(query);
|
|
227
|
+
const response = await services.queryGateway(query);
|
|
247
228
|
const result = await response.json();
|
|
248
229
|
expect(result).toMatchInlineSnapshot(`
|
|
249
230
|
Object {
|
|
@@ -256,12 +237,16 @@ describe('end-to-end features', () => {
|
|
|
256
237
|
}
|
|
257
238
|
`);
|
|
258
239
|
|
|
259
|
-
const supergraphSdl = gateway.__testing().supergraphSdl;
|
|
240
|
+
const supergraphSdl = services.gateway.__testing().supergraphSdl;
|
|
260
241
|
expect(supergraphSdl).toBeDefined();
|
|
261
242
|
const supergraph = buildSchema(supergraphSdl!);
|
|
262
243
|
const typeT = supergraph.type('T') as ObjectType;
|
|
263
|
-
expect(
|
|
264
|
-
|
|
244
|
+
expect(
|
|
245
|
+
typeT.field('x')?.appliedDirectivesOf('federationTag').toString(),
|
|
246
|
+
).toStrictEqual('@federationTag(name: "Important")');
|
|
247
|
+
expect(
|
|
248
|
+
typeT.field('y')?.appliedDirectivesOf('federationTag').toString(),
|
|
249
|
+
).toStrictEqual('@federationTag(name: "Less Important")');
|
|
265
250
|
});
|
|
266
251
|
|
|
267
252
|
it('handles fed1 schema', async () => {
|
|
@@ -282,10 +267,10 @@ describe('end-to-end features', () => {
|
|
|
282
267
|
Query: {
|
|
283
268
|
t: () => ({
|
|
284
269
|
k: 42,
|
|
285
|
-
x: 1
|
|
270
|
+
x: 1,
|
|
286
271
|
}),
|
|
287
|
-
}
|
|
288
|
-
}
|
|
272
|
+
},
|
|
273
|
+
},
|
|
289
274
|
};
|
|
290
275
|
|
|
291
276
|
const subgraphB = {
|
|
@@ -300,13 +285,13 @@ describe('end-to-end features', () => {
|
|
|
300
285
|
resolvers: {
|
|
301
286
|
T: {
|
|
302
287
|
__resolveReference: ({ k }: { k: string }) => {
|
|
303
|
-
return k === '42' ?
|
|
288
|
+
return k === '42' ? { y: 2 } : undefined;
|
|
304
289
|
},
|
|
305
|
-
}
|
|
306
|
-
}
|
|
290
|
+
},
|
|
291
|
+
},
|
|
307
292
|
};
|
|
308
293
|
|
|
309
|
-
await
|
|
294
|
+
services = await startSubgraphsAndGateway([subgraphA, subgraphB]);
|
|
310
295
|
|
|
311
296
|
const query = `
|
|
312
297
|
{
|
|
@@ -317,7 +302,7 @@ describe('end-to-end features', () => {
|
|
|
317
302
|
}
|
|
318
303
|
`;
|
|
319
304
|
|
|
320
|
-
const response = await queryGateway(query);
|
|
305
|
+
const response = await services.queryGateway(query);
|
|
321
306
|
const result = await response.json();
|
|
322
307
|
expect(result).toMatchInlineSnapshot(`
|
|
323
308
|
Object {
|
|
@@ -338,8 +323,8 @@ describe('end-to-end features', () => {
|
|
|
338
323
|
typeDefs: gql`
|
|
339
324
|
extend schema
|
|
340
325
|
@link(
|
|
341
|
-
url: "https://specs.apollo.dev/federation/v2.0"
|
|
342
|
-
import: [
|
|
326
|
+
url: "https://specs.apollo.dev/federation/v2.0"
|
|
327
|
+
import: ["@key", "@shareable", "@inaccessible"]
|
|
343
328
|
)
|
|
344
329
|
|
|
345
330
|
type Query {
|
|
@@ -369,9 +354,9 @@ describe('end-to-end features', () => {
|
|
|
369
354
|
}),
|
|
370
355
|
f: (_: any, args: any) => {
|
|
371
356
|
return args.e === 'FOO' ? 0 : 1;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
375
360
|
};
|
|
376
361
|
|
|
377
362
|
const subgraphB = {
|
|
@@ -380,8 +365,8 @@ describe('end-to-end features', () => {
|
|
|
380
365
|
typeDefs: gql`
|
|
381
366
|
extend schema
|
|
382
367
|
@link(
|
|
383
|
-
url: "https://specs.apollo.dev/federation/v2.0"
|
|
384
|
-
import: [
|
|
368
|
+
url: "https://specs.apollo.dev/federation/v2.0"
|
|
369
|
+
import: ["@key", "@shareable", "@inaccessible"]
|
|
385
370
|
)
|
|
386
371
|
|
|
387
372
|
type T @key(fields: "k") {
|
|
@@ -393,13 +378,13 @@ describe('end-to-end features', () => {
|
|
|
393
378
|
resolvers: {
|
|
394
379
|
T: {
|
|
395
380
|
__resolveReference: ({ k }: { k: string }) => {
|
|
396
|
-
return k === '42' ?
|
|
381
|
+
return k === '42' ? { c: 'foo', d: 'bar' } : undefined;
|
|
397
382
|
},
|
|
398
|
-
}
|
|
399
|
-
}
|
|
383
|
+
},
|
|
384
|
+
},
|
|
400
385
|
};
|
|
401
386
|
|
|
402
|
-
await
|
|
387
|
+
services = await startSubgraphsAndGateway([subgraphA, subgraphB]);
|
|
403
388
|
|
|
404
389
|
const q1 = `
|
|
405
390
|
{
|
|
@@ -411,7 +396,7 @@ describe('end-to-end features', () => {
|
|
|
411
396
|
}
|
|
412
397
|
`;
|
|
413
398
|
|
|
414
|
-
const resp1 = await queryGateway(q1);
|
|
399
|
+
const resp1 = await services.queryGateway(q1);
|
|
415
400
|
const res1 = await resp1.json();
|
|
416
401
|
expect(res1).toMatchInlineSnapshot(`
|
|
417
402
|
Object {
|
|
@@ -426,7 +411,7 @@ describe('end-to-end features', () => {
|
|
|
426
411
|
`);
|
|
427
412
|
|
|
428
413
|
// Make sure the exposed API doesn't have any @inaccessible elements.
|
|
429
|
-
expect(printSchema(gateway.schema!)).toMatchInlineSnapshot(`
|
|
414
|
+
expect(printSchema(services.gateway.schema!)).toMatchInlineSnapshot(`
|
|
430
415
|
"enum E {
|
|
431
416
|
FOO
|
|
432
417
|
}
|
|
@@ -449,7 +434,7 @@ describe('end-to-end features', () => {
|
|
|
449
434
|
f(e: BAR)
|
|
450
435
|
}
|
|
451
436
|
`;
|
|
452
|
-
const resp2 = await queryGateway(q2);
|
|
437
|
+
const resp2 = await services.queryGateway(q2);
|
|
453
438
|
const res2 = await resp2.json();
|
|
454
439
|
expect(res2).toMatchInlineSnapshot(`
|
|
455
440
|
Object {
|
|
@@ -458,6 +443,12 @@ describe('end-to-end features', () => {
|
|
|
458
443
|
"extensions": Object {
|
|
459
444
|
"code": "GRAPHQL_VALIDATION_FAILED",
|
|
460
445
|
},
|
|
446
|
+
"locations": Array [
|
|
447
|
+
Object {
|
|
448
|
+
"column": 14,
|
|
449
|
+
"line": 3,
|
|
450
|
+
},
|
|
451
|
+
],
|
|
461
452
|
"message": "Value \\"BAR\\" does not exist in \\"E\\" enum.",
|
|
462
453
|
},
|
|
463
454
|
],
|
|
@@ -471,7 +462,7 @@ describe('end-to-end features', () => {
|
|
|
471
462
|
}
|
|
472
463
|
}
|
|
473
464
|
`;
|
|
474
|
-
const resp3 = await queryGateway(q3);
|
|
465
|
+
const resp3 = await services.queryGateway(q3);
|
|
475
466
|
const res3 = await resp3.json();
|
|
476
467
|
expect(res3).toMatchInlineSnapshot(`
|
|
477
468
|
Object {
|
|
@@ -480,10 +471,16 @@ describe('end-to-end features', () => {
|
|
|
480
471
|
"extensions": Object {
|
|
481
472
|
"code": "GRAPHQL_VALIDATION_FAILED",
|
|
482
473
|
},
|
|
474
|
+
"locations": Array [
|
|
475
|
+
Object {
|
|
476
|
+
"column": 11,
|
|
477
|
+
"line": 4,
|
|
478
|
+
},
|
|
479
|
+
],
|
|
483
480
|
"message": "Cannot query field \\"a\\" on type \\"T\\". Did you mean \\"b\\", \\"d\\", or \\"k\\"?",
|
|
484
481
|
},
|
|
485
482
|
],
|
|
486
483
|
}
|
|
487
484
|
`);
|
|
488
485
|
});
|
|
489
|
-
})
|
|
486
|
+
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import gql from 'graphql-tag';
|
|
2
|
-
import {
|
|
2
|
+
import { ApolloServer } from '@apollo/server';
|
|
3
3
|
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
4
4
|
|
|
5
5
|
import { LocalGraphQLDataSource } from '../../datasources/LocalGraphQLDataSource';
|
|
6
6
|
import { ApolloGateway } from '../../';
|
|
7
7
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
8
8
|
import { QueryPlanner } from '@apollo/query-planner';
|
|
9
|
+
import { unwrapSingleResultKind } from '../gateway/testUtils';
|
|
9
10
|
|
|
10
11
|
it('caches the query plan for a request', async () => {
|
|
11
12
|
const buildQueryPlanSpy = jest.spyOn(QueryPlanner.prototype, 'buildQueryPlan');
|
|
@@ -23,9 +24,8 @@ it('caches the query plan for a request', async () => {
|
|
|
23
24
|
},
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const server = new ApolloServer({ schema, executor });
|
|
27
|
+
const server = new ApolloServer({ gateway });
|
|
28
|
+
await server.start();
|
|
29
29
|
|
|
30
30
|
const upc = '1';
|
|
31
31
|
|
|
@@ -42,7 +42,8 @@ it('caches the query plan for a request', async () => {
|
|
|
42
42
|
variables: { upc },
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
const { data: result1Data } = unwrapSingleResultKind(result);
|
|
46
|
+
expect(result1Data).toEqual({
|
|
46
47
|
product: {
|
|
47
48
|
name: 'Table',
|
|
48
49
|
},
|
|
@@ -53,7 +54,7 @@ it('caches the query plan for a request', async () => {
|
|
|
53
54
|
variables: { upc },
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
expect(
|
|
57
|
+
expect(unwrapSingleResultKind(secondResult).data).toEqual(result1Data);
|
|
57
58
|
expect(buildQueryPlanSpy).toHaveBeenCalledTimes(1);
|
|
58
59
|
});
|
|
59
60
|
|
|
@@ -85,24 +86,23 @@ it('supports multiple operations and operationName', async () => {
|
|
|
85
86
|
},
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
const server = new ApolloServer({ schema, executor });
|
|
89
|
+
const server = new ApolloServer({ gateway });
|
|
90
|
+
await server.start();
|
|
91
91
|
|
|
92
|
-
const
|
|
92
|
+
const userResult = await server.executeOperation({
|
|
93
93
|
query,
|
|
94
94
|
operationName: 'GetUser',
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
const
|
|
97
|
+
const reviewsResult = await server.executeOperation({
|
|
98
98
|
query,
|
|
99
99
|
operationName: 'GetReviews',
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
expect(
|
|
102
|
+
expect(unwrapSingleResultKind(userResult).data).toEqual({
|
|
103
103
|
me: { username: '@ada' },
|
|
104
104
|
});
|
|
105
|
-
expect(
|
|
105
|
+
expect(unwrapSingleResultKind(reviewsResult).data).toEqual({
|
|
106
106
|
topReviews: [
|
|
107
107
|
{ body: 'Love it!' },
|
|
108
108
|
{ body: 'Too expensive.' },
|
|
@@ -194,9 +194,8 @@ it('does not corrupt cached queryplan data across requests', async () => {
|
|
|
194
194
|
},
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
const server = new ApolloServer({ schema, executor });
|
|
197
|
+
const server = new ApolloServer({ gateway });
|
|
198
|
+
await server.start();
|
|
200
199
|
|
|
201
200
|
const query1 = `#graphql
|
|
202
201
|
query UserFavoriteColor {
|
|
@@ -225,8 +224,8 @@ it('does not corrupt cached queryplan data across requests', async () => {
|
|
|
225
224
|
query: query1,
|
|
226
225
|
});
|
|
227
226
|
|
|
228
|
-
expect(result1.errors).toEqual(undefined);
|
|
229
|
-
expect(result2.errors).toEqual(undefined);
|
|
230
|
-
expect(result3.errors).toEqual(undefined);
|
|
227
|
+
expect(unwrapSingleResultKind(result1).errors).toEqual(undefined);
|
|
228
|
+
expect(unwrapSingleResultKind(result2).errors).toEqual(undefined);
|
|
229
|
+
expect(unwrapSingleResultKind(result3).errors).toEqual(undefined);
|
|
231
230
|
expect(result1).toEqual(result3);
|
|
232
231
|
});
|