@apollo/gateway 2.4.5 → 2.4.7
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/__generated__/graphqlTypes.d.ts +19 -1
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js +1 -0
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/executeQueryPlan.js +1 -1
- package/dist/executeQueryPlan.js.map +1 -1
- package/package.json +4 -4
- package/src/__generated__/graphqlTypes.ts +33 -2
- package/src/executeQueryPlan.ts +1 -1
- package/src/__mocks__/tsconfig.json +0 -7
- package/src/__tests__/.gitkeep +0 -0
- package/src/__tests__/CucumberREADME.md +0 -96
- package/src/__tests__/build-query-plan.feature +0 -1471
- package/src/__tests__/buildQueryPlan.test.ts +0 -1225
- package/src/__tests__/executeQueryPlan.conditions.test.ts +0 -1488
- package/src/__tests__/executeQueryPlan.introspection.test.ts +0 -140
- package/src/__tests__/executeQueryPlan.test.ts +0 -6140
- package/src/__tests__/execution-utils.ts +0 -124
- package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +0 -195
- package/src/__tests__/gateway/buildService.test.ts +0 -249
- package/src/__tests__/gateway/endToEnd.test.ts +0 -486
- package/src/__tests__/gateway/executor.test.ts +0 -96
- package/src/__tests__/gateway/extensions.test.ts +0 -37
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +0 -239
- package/src/__tests__/gateway/opentelemetry.test.ts +0 -123
- package/src/__tests__/gateway/queryPlanCache.test.ts +0 -231
- package/src/__tests__/gateway/queryPlannerConfig.test.ts +0 -101
- package/src/__tests__/gateway/reporting.test.ts +0 -616
- package/src/__tests__/gateway/supergraphSdl.test.ts +0 -396
- package/src/__tests__/gateway/testUtils.ts +0 -89
- package/src/__tests__/integration/abstract-types.test.ts +0 -1861
- package/src/__tests__/integration/aliases.test.ts +0 -180
- package/src/__tests__/integration/boolean.test.ts +0 -279
- package/src/__tests__/integration/complex-key.test.ts +0 -197
- package/src/__tests__/integration/configuration.test.ts +0 -404
- package/src/__tests__/integration/custom-directives.test.ts +0 -174
- package/src/__tests__/integration/execution-style.test.ts +0 -35
- package/src/__tests__/integration/fragments.test.ts +0 -237
- package/src/__tests__/integration/list-key.test.ts +0 -128
- package/src/__tests__/integration/logger.test.ts +0 -122
- package/src/__tests__/integration/managed.test.ts +0 -319
- package/src/__tests__/integration/merge-arrays.test.ts +0 -34
- package/src/__tests__/integration/multiple-key.test.ts +0 -327
- package/src/__tests__/integration/mutations.test.ts +0 -287
- package/src/__tests__/integration/networkRequests.test.ts +0 -542
- package/src/__tests__/integration/nockMocks.ts +0 -157
- package/src/__tests__/integration/provides.test.ts +0 -77
- package/src/__tests__/integration/requires.test.ts +0 -359
- package/src/__tests__/integration/scope.test.ts +0 -557
- package/src/__tests__/integration/single-service.test.ts +0 -119
- package/src/__tests__/integration/unions.test.ts +0 -79
- package/src/__tests__/integration/value-types.test.ts +0 -382
- package/src/__tests__/integration/variables.test.ts +0 -120
- package/src/__tests__/nockAssertions.ts +0 -20
- package/src/__tests__/queryPlanCucumber.test.ts +0 -55
- package/src/__tests__/resultShaping.test.ts +0 -605
- package/src/__tests__/testSetup.ts +0 -1
- package/src/__tests__/tsconfig.json +0 -8
- package/src/core/__tests__/core.test.ts +0 -412
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +0 -51
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +0 -574
- package/src/schema-helper/__tests__/addExtensions.test.ts +0 -70
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -364
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/loadServicesFromRemoteEndpoint.test.ts +0 -40
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +0 -65
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -511
- package/src/utilities/__tests__/deepMerge.test.ts +0 -77
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import { GraphQLObjectType, GraphQLSchema } from 'graphql';
|
|
3
|
-
import mockedEnv from 'mocked-env';
|
|
4
|
-
import type { Logger } from '@apollo/utils.logger';
|
|
5
|
-
import { ApolloGateway, UplinkSupergraphManager } from '../..';
|
|
6
|
-
import {
|
|
7
|
-
mockSdlQuerySuccess,
|
|
8
|
-
mockServiceHealthCheckSuccess,
|
|
9
|
-
mockAllServicesHealthCheckSuccess,
|
|
10
|
-
mockServiceHealthCheck,
|
|
11
|
-
mockSupergraphSdlRequestSuccess,
|
|
12
|
-
mockSupergraphSdlRequest,
|
|
13
|
-
mockApolloConfig,
|
|
14
|
-
mockCloudConfigUrl1,
|
|
15
|
-
mockSupergraphSdlRequestIfAfter,
|
|
16
|
-
mockSupergraphSdlRequestSuccessIfAfter,
|
|
17
|
-
} from './nockMocks';
|
|
18
|
-
import {
|
|
19
|
-
accounts,
|
|
20
|
-
books,
|
|
21
|
-
documents,
|
|
22
|
-
Fixture,
|
|
23
|
-
fixturesWithUpdate,
|
|
24
|
-
inventory,
|
|
25
|
-
product,
|
|
26
|
-
reviews,
|
|
27
|
-
} from 'apollo-federation-integration-testsuite';
|
|
28
|
-
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
29
|
-
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
30
|
-
import resolvable from '@josephg/resolvable';
|
|
31
|
-
|
|
32
|
-
const simpleService: Fixture = {
|
|
33
|
-
name: 'accounts',
|
|
34
|
-
url: 'http://localhost:4001',
|
|
35
|
-
typeDefs: gql`
|
|
36
|
-
extend type Query {
|
|
37
|
-
me: User
|
|
38
|
-
everyone: [User]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
"This is my User"
|
|
42
|
-
type User @key(fields: "id") {
|
|
43
|
-
id: ID!
|
|
44
|
-
name: String
|
|
45
|
-
username: String
|
|
46
|
-
}
|
|
47
|
-
`,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function getRootQueryFields(schema?: GraphQLSchema): string[] {
|
|
51
|
-
return Object.keys(
|
|
52
|
-
(schema?.getType('Query') as GraphQLObjectType).getFields(),
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let logger: Logger;
|
|
57
|
-
let gateway: ApolloGateway | null = null;
|
|
58
|
-
let cleanUp: (() => void) | null = null;
|
|
59
|
-
let originalMinPollInterval = UplinkSupergraphManager.MIN_POLL_INTERVAL_MS;
|
|
60
|
-
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
// Set the min poll interval artificially low so we're not waiting during tests
|
|
63
|
-
// @ts-ignore
|
|
64
|
-
UplinkSupergraphManager['MIN_POLL_INTERVAL_MS'] = 100;
|
|
65
|
-
nockBeforeEach();
|
|
66
|
-
|
|
67
|
-
const warn = jest.fn();
|
|
68
|
-
const debug = jest.fn();
|
|
69
|
-
const error = jest.fn();
|
|
70
|
-
const info = jest.fn();
|
|
71
|
-
|
|
72
|
-
logger = {
|
|
73
|
-
warn,
|
|
74
|
-
debug,
|
|
75
|
-
error,
|
|
76
|
-
info,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
afterEach(async () => {
|
|
81
|
-
// @ts-ignore
|
|
82
|
-
UplinkSupergraphManager['MIN_POLL_INTERVAL_MS'] = originalMinPollInterval;
|
|
83
|
-
if (gateway) {
|
|
84
|
-
await gateway.stop();
|
|
85
|
-
gateway = null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
nockAfterEach();
|
|
89
|
-
|
|
90
|
-
if (cleanUp) {
|
|
91
|
-
cleanUp();
|
|
92
|
-
cleanUp = null;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('Queries remote endpoints for their SDLs', async () => {
|
|
97
|
-
mockSdlQuerySuccess(simpleService);
|
|
98
|
-
|
|
99
|
-
gateway = new ApolloGateway({ serviceList: [simpleService], logger });
|
|
100
|
-
await gateway.load();
|
|
101
|
-
expect(gateway.schema!.getType('User')!.description).toBe('This is my User');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('Fetches Supergraph SDL from remote storage', async () => {
|
|
105
|
-
mockSupergraphSdlRequestSuccess();
|
|
106
|
-
|
|
107
|
-
gateway = new ApolloGateway({
|
|
108
|
-
logger,
|
|
109
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
await gateway.load(mockApolloConfig);
|
|
113
|
-
await gateway.stop();
|
|
114
|
-
expect(gateway.schema?.getType('User')).toBeTruthy();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('Fetches Supergraph SDL from remote storage using a configured env variable', async () => {
|
|
118
|
-
cleanUp = mockedEnv({
|
|
119
|
-
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: mockCloudConfigUrl1,
|
|
120
|
-
});
|
|
121
|
-
mockSupergraphSdlRequestSuccess();
|
|
122
|
-
|
|
123
|
-
gateway = new ApolloGateway({
|
|
124
|
-
logger,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
await gateway.load(mockApolloConfig);
|
|
128
|
-
await gateway.stop();
|
|
129
|
-
expect(gateway.schema?.getType('User')).toBeTruthy();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('Updates Supergraph SDL from remote storage', async () => {
|
|
133
|
-
mockSupergraphSdlRequestSuccess();
|
|
134
|
-
mockSupergraphSdlRequestSuccessIfAfter(
|
|
135
|
-
'originalId-1234',
|
|
136
|
-
'updatedId-5678',
|
|
137
|
-
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// This test is only interested in the second time the gateway notifies of an
|
|
141
|
-
// update, since the first happens on load.
|
|
142
|
-
const secondUpdate = resolvable();
|
|
143
|
-
|
|
144
|
-
gateway = new ApolloGateway({
|
|
145
|
-
logger,
|
|
146
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const schemas: GraphQLSchema[] = [];
|
|
150
|
-
gateway.onSchemaLoadOrUpdate(({ apiSchema }) => {
|
|
151
|
-
schemas.push(apiSchema);
|
|
152
|
-
});
|
|
153
|
-
gateway.onSchemaLoadOrUpdate(
|
|
154
|
-
jest
|
|
155
|
-
.fn()
|
|
156
|
-
.mockImplementationOnce(() => {})
|
|
157
|
-
.mockImplementationOnce(() => secondUpdate.resolve()),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
await gateway.load(mockApolloConfig);
|
|
161
|
-
|
|
162
|
-
await secondUpdate;
|
|
163
|
-
|
|
164
|
-
// First schema has no 'review' field on the 'Query' type
|
|
165
|
-
expect(
|
|
166
|
-
(schemas[0].getType('Query') as GraphQLObjectType).getFields()['review'],
|
|
167
|
-
).toBeFalsy();
|
|
168
|
-
|
|
169
|
-
// Updated schema adds 'review' field on the 'Query' type
|
|
170
|
-
expect(
|
|
171
|
-
(schemas[1].getType('Query') as GraphQLObjectType).getFields()['review'],
|
|
172
|
-
).toBeTruthy();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe('Supergraph SDL update failures', () => {
|
|
176
|
-
it('Gateway throws on initial load failure', async () => {
|
|
177
|
-
mockSupergraphSdlRequest().reply(401);
|
|
178
|
-
|
|
179
|
-
gateway = new ApolloGateway({
|
|
180
|
-
logger,
|
|
181
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
182
|
-
uplinkMaxRetries: 0,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
await expect(
|
|
186
|
-
gateway.load(mockApolloConfig),
|
|
187
|
-
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
188
|
-
`"An error occurred while fetching your schema from Apollo: 401 Unauthorized"`,
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('Handles arbitrary fetch failures (non 200 response)', async () => {
|
|
193
|
-
mockSupergraphSdlRequestSuccess();
|
|
194
|
-
mockSupergraphSdlRequestIfAfter('originalId-1234').reply(500);
|
|
195
|
-
|
|
196
|
-
// Spy on logger.error so we can just await once it's been called
|
|
197
|
-
const errorLoggedPromise = resolvable();
|
|
198
|
-
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
199
|
-
|
|
200
|
-
gateway = new ApolloGateway({
|
|
201
|
-
logger,
|
|
202
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
203
|
-
uplinkMaxRetries: 0,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await gateway.load(mockApolloConfig);
|
|
207
|
-
await errorLoggedPromise;
|
|
208
|
-
|
|
209
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
210
|
-
'UplinkSupergraphManager failed to update supergraph with the following error: An error occurred while fetching your schema from Apollo: 500 Internal Server Error',
|
|
211
|
-
);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('Handles GraphQL errors', async () => {
|
|
215
|
-
mockSupergraphSdlRequestSuccess();
|
|
216
|
-
mockSupergraphSdlRequest('originalId-1234').reply(200, {
|
|
217
|
-
errors: [
|
|
218
|
-
{
|
|
219
|
-
message: 'Cannot query field "fail" on type "Query".',
|
|
220
|
-
locations: [{ line: 1, column: 3 }],
|
|
221
|
-
extensions: { code: 'GRAPHQL_VALIDATION_FAILED' },
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Spy on logger.error so we can just await once it's been called
|
|
227
|
-
const errorLoggedPromise = resolvable();
|
|
228
|
-
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
229
|
-
|
|
230
|
-
gateway = new ApolloGateway({
|
|
231
|
-
logger,
|
|
232
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
233
|
-
uplinkMaxRetries: 0,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
await gateway.load(mockApolloConfig);
|
|
237
|
-
await errorLoggedPromise;
|
|
238
|
-
|
|
239
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
240
|
-
`UplinkSupergraphManager failed to update supergraph with the following error: An error occurred while fetching your schema from Apollo: \nCannot query field "fail" on type "Query".`,
|
|
241
|
-
);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("Doesn't update and logs on receiving unparseable Supergraph SDL", async () => {
|
|
245
|
-
mockSupergraphSdlRequestSuccess();
|
|
246
|
-
mockSupergraphSdlRequestIfAfter('originalId-1234').reply(
|
|
247
|
-
200,
|
|
248
|
-
JSON.stringify({
|
|
249
|
-
data: {
|
|
250
|
-
routerConfig: {
|
|
251
|
-
__typename: 'RouterConfigResult',
|
|
252
|
-
id: 'failure',
|
|
253
|
-
supergraphSdl: 'Syntax Error - invalid SDL',
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
}),
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
// Spy on logger.error so we can just await once it's been called
|
|
260
|
-
const errorLoggedPromise = resolvable();
|
|
261
|
-
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
262
|
-
|
|
263
|
-
gateway = new ApolloGateway({
|
|
264
|
-
logger,
|
|
265
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
await gateway.load(mockApolloConfig);
|
|
269
|
-
await errorLoggedPromise;
|
|
270
|
-
|
|
271
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
272
|
-
'UplinkSupergraphManager failed to update supergraph with the following error: Syntax Error: Unexpected Name "Syntax".',
|
|
273
|
-
);
|
|
274
|
-
expect(gateway.schema).toBeTruthy();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('Throws on initial load when receiving unparseable Supergraph SDL', async () => {
|
|
278
|
-
mockSupergraphSdlRequest().reply(
|
|
279
|
-
200,
|
|
280
|
-
JSON.stringify({
|
|
281
|
-
data: {
|
|
282
|
-
routerConfig: {
|
|
283
|
-
__typename: 'RouterConfigResult',
|
|
284
|
-
id: 'failure',
|
|
285
|
-
supergraphSdl: 'Syntax Error - invalid SDL',
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
}),
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
gateway = new ApolloGateway({
|
|
292
|
-
logger,
|
|
293
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
await expect(
|
|
297
|
-
gateway.load(mockApolloConfig),
|
|
298
|
-
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
299
|
-
`"Syntax Error: Unexpected Name \\"Syntax\\"."`,
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
expect(gateway['state'].phase).toEqual('failed to load');
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('Rollsback to a previous schema when triggered', async () => {
|
|
307
|
-
// Init
|
|
308
|
-
mockSupergraphSdlRequestSuccess();
|
|
309
|
-
mockSupergraphSdlRequestSuccessIfAfter(
|
|
310
|
-
'originalId-1234',
|
|
311
|
-
'updatedId-5678',
|
|
312
|
-
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
313
|
-
);
|
|
314
|
-
mockSupergraphSdlRequestSuccessIfAfter('updatedId-5678');
|
|
315
|
-
|
|
316
|
-
const firstSchemaChangeBlocker = resolvable();
|
|
317
|
-
const secondSchemaChangeBlocker = resolvable();
|
|
318
|
-
const thirdSchemaChangeBlocker = resolvable();
|
|
319
|
-
|
|
320
|
-
const onChange = jest
|
|
321
|
-
.fn()
|
|
322
|
-
.mockImplementationOnce(() => firstSchemaChangeBlocker.resolve())
|
|
323
|
-
.mockImplementationOnce(() => secondSchemaChangeBlocker.resolve())
|
|
324
|
-
.mockImplementationOnce(() => thirdSchemaChangeBlocker.resolve());
|
|
325
|
-
|
|
326
|
-
gateway = new ApolloGateway({
|
|
327
|
-
logger,
|
|
328
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
gateway.onSchemaChange(onChange);
|
|
332
|
-
await gateway.load(mockApolloConfig);
|
|
333
|
-
|
|
334
|
-
await firstSchemaChangeBlocker;
|
|
335
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
336
|
-
|
|
337
|
-
await secondSchemaChangeBlocker;
|
|
338
|
-
expect(onChange).toHaveBeenCalledTimes(2);
|
|
339
|
-
|
|
340
|
-
await thirdSchemaChangeBlocker;
|
|
341
|
-
expect(onChange).toHaveBeenCalledTimes(3);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
describe('Downstream service health checks', () => {
|
|
345
|
-
describe('Unmanaged mode', () => {
|
|
346
|
-
it(`Performs health checks to downstream services on load`, async () => {
|
|
347
|
-
mockSdlQuerySuccess(simpleService);
|
|
348
|
-
mockServiceHealthCheckSuccess(simpleService);
|
|
349
|
-
|
|
350
|
-
gateway = new ApolloGateway({
|
|
351
|
-
logger,
|
|
352
|
-
serviceList: [simpleService],
|
|
353
|
-
serviceHealthCheck: true,
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
await gateway.load();
|
|
357
|
-
expect(gateway.schema!.getType('User')!.description).toBe(
|
|
358
|
-
'This is my User',
|
|
359
|
-
);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it(`Rejects on initial load when health check fails`, async () => {
|
|
363
|
-
mockSdlQuerySuccess(simpleService);
|
|
364
|
-
mockServiceHealthCheck(simpleService).reply(500);
|
|
365
|
-
|
|
366
|
-
gateway = new ApolloGateway({
|
|
367
|
-
serviceList: [simpleService],
|
|
368
|
-
serviceHealthCheck: true,
|
|
369
|
-
logger,
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// This is the ideal, but our version of Jest has a bug with printing error snapshots.
|
|
373
|
-
// See: https://github.com/facebook/jest/pull/10217 (fixed in v26.2.0)
|
|
374
|
-
// expect(gateway.load(mockApolloConfig)).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
375
|
-
// "A valid schema couldn't be composed. The following composition errors were found:
|
|
376
|
-
// [accounts] User -> A @key selects id, but User.id could not be found
|
|
377
|
-
// [accounts] Account -> A @key selects id, but Account.id could not be found"
|
|
378
|
-
// `);
|
|
379
|
-
// Instead we'll just use the regular snapshot matcher...
|
|
380
|
-
let err;
|
|
381
|
-
try {
|
|
382
|
-
await gateway.load(mockApolloConfig);
|
|
383
|
-
} catch (e) {
|
|
384
|
-
err = e;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
expect(err.message).toMatchInlineSnapshot(`
|
|
388
|
-
"The gateway subgraphs health check failed. Updating to the provided \`supergraphSdl\` will likely result in future request failures to subgraphs. The following error occurred during the health check:
|
|
389
|
-
[accounts]: 500: Internal Server Error"
|
|
390
|
-
`);
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
describe('Managed mode', () => {
|
|
395
|
-
it('Performs health checks to downstream services on load', async () => {
|
|
396
|
-
mockSupergraphSdlRequestSuccess();
|
|
397
|
-
mockAllServicesHealthCheckSuccess();
|
|
398
|
-
|
|
399
|
-
gateway = new ApolloGateway({
|
|
400
|
-
serviceHealthCheck: true,
|
|
401
|
-
logger,
|
|
402
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
await gateway.load(mockApolloConfig);
|
|
406
|
-
await gateway.stop();
|
|
407
|
-
|
|
408
|
-
expect(gateway.schema!.getType('User')!).toBeTruthy();
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('Rejects on initial load when health check fails', async () => {
|
|
412
|
-
mockSupergraphSdlRequestSuccess();
|
|
413
|
-
mockServiceHealthCheck(accounts).reply(500);
|
|
414
|
-
mockServiceHealthCheckSuccess(books);
|
|
415
|
-
mockServiceHealthCheckSuccess(inventory);
|
|
416
|
-
mockServiceHealthCheckSuccess(product);
|
|
417
|
-
mockServiceHealthCheckSuccess(reviews);
|
|
418
|
-
mockServiceHealthCheckSuccess(documents);
|
|
419
|
-
|
|
420
|
-
gateway = new ApolloGateway({
|
|
421
|
-
serviceHealthCheck: true,
|
|
422
|
-
logger,
|
|
423
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
// This is the ideal, but our version of Jest has a bug with printing error snapshots.
|
|
427
|
-
// See: https://github.com/facebook/jest/pull/10217 (fixed in v26.2.0)
|
|
428
|
-
// expect(gateway.load(mockApolloConfig)).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
429
|
-
// "A valid schema couldn't be composed. The following composition errors were found:
|
|
430
|
-
// [accounts] User -> A @key selects id, but User.id could not be found
|
|
431
|
-
// [accounts] Account -> A @key selects id, but Account.id could not be found"
|
|
432
|
-
// `);
|
|
433
|
-
// Instead we'll just use the regular snapshot matcher...
|
|
434
|
-
let err;
|
|
435
|
-
try {
|
|
436
|
-
await gateway.load(mockApolloConfig);
|
|
437
|
-
} catch (e) {
|
|
438
|
-
err = e;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// TODO: smell that we should be awaiting something else
|
|
442
|
-
expect(err.message).toMatchInlineSnapshot(`
|
|
443
|
-
"The gateway subgraphs health check failed. Updating to the provided \`supergraphSdl\` will likely result in future request failures to subgraphs. The following error occurred during the health check:
|
|
444
|
-
[accounts]: 500: Internal Server Error"
|
|
445
|
-
`);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// This test has been flaky for a long time, and fails consistently after changes
|
|
449
|
-
// introduced by https://github.com/apollographql/apollo-server/pull/4277.
|
|
450
|
-
// I've decided to skip this test for now with hopes that we can one day
|
|
451
|
-
// determine the root cause and test this behavior in a reliable manner.
|
|
452
|
-
it('Rolls over to new schema when health check succeeds', async () => {
|
|
453
|
-
mockSupergraphSdlRequestSuccess();
|
|
454
|
-
mockAllServicesHealthCheckSuccess();
|
|
455
|
-
|
|
456
|
-
// Update
|
|
457
|
-
mockSupergraphSdlRequestSuccessIfAfter(
|
|
458
|
-
'originalId-1234',
|
|
459
|
-
'updatedId-5678',
|
|
460
|
-
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
461
|
-
);
|
|
462
|
-
mockAllServicesHealthCheckSuccess();
|
|
463
|
-
|
|
464
|
-
const schemaChangeBlocker1 = resolvable();
|
|
465
|
-
const schemaChangeBlocker2 = resolvable();
|
|
466
|
-
const onChange = jest
|
|
467
|
-
.fn()
|
|
468
|
-
.mockImplementationOnce(() => schemaChangeBlocker1.resolve())
|
|
469
|
-
.mockImplementationOnce(() => schemaChangeBlocker2.resolve());
|
|
470
|
-
|
|
471
|
-
gateway = new ApolloGateway({
|
|
472
|
-
serviceHealthCheck: true,
|
|
473
|
-
logger,
|
|
474
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
gateway.onSchemaChange(onChange);
|
|
478
|
-
await gateway.load(mockApolloConfig);
|
|
479
|
-
|
|
480
|
-
// Basic testing schema doesn't contain a `review` field on `Query` type
|
|
481
|
-
await schemaChangeBlocker1;
|
|
482
|
-
expect(getRootQueryFields(gateway.schema)).not.toContain('review');
|
|
483
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
484
|
-
|
|
485
|
-
// "Updated" testing schema adds a `review` field on `Query` type
|
|
486
|
-
await schemaChangeBlocker2;
|
|
487
|
-
expect(getRootQueryFields(gateway.schema)).toContain('review');
|
|
488
|
-
|
|
489
|
-
expect(onChange).toHaveBeenCalledTimes(2);
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it('Preserves original schema when health check fails', async () => {
|
|
493
|
-
const errorLoggedPromise = resolvable();
|
|
494
|
-
const errorSpy = jest.fn(() => errorLoggedPromise.resolve());
|
|
495
|
-
logger.error = errorSpy;
|
|
496
|
-
|
|
497
|
-
mockSupergraphSdlRequestSuccess();
|
|
498
|
-
mockAllServicesHealthCheckSuccess();
|
|
499
|
-
|
|
500
|
-
// Update (with one health check failure)
|
|
501
|
-
mockSupergraphSdlRequestSuccessIfAfter(
|
|
502
|
-
'originalId-1234',
|
|
503
|
-
'updatedId-5678',
|
|
504
|
-
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
505
|
-
);
|
|
506
|
-
mockServiceHealthCheck(accounts).reply(500);
|
|
507
|
-
mockServiceHealthCheckSuccess(books);
|
|
508
|
-
mockServiceHealthCheckSuccess(inventory);
|
|
509
|
-
mockServiceHealthCheckSuccess(product);
|
|
510
|
-
mockServiceHealthCheckSuccess(reviews);
|
|
511
|
-
mockServiceHealthCheckSuccess(documents);
|
|
512
|
-
|
|
513
|
-
gateway = new ApolloGateway({
|
|
514
|
-
serviceHealthCheck: true,
|
|
515
|
-
logger,
|
|
516
|
-
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
const updateSpy = jest.fn();
|
|
520
|
-
gateway.onSchemaLoadOrUpdate(() => updateSpy());
|
|
521
|
-
|
|
522
|
-
// load the gateway as usual
|
|
523
|
-
await gateway.load(mockApolloConfig);
|
|
524
|
-
|
|
525
|
-
// Validate we have the original schema
|
|
526
|
-
expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
|
|
527
|
-
expect(getRootQueryFields(gateway.schema)).not.toContain('review');
|
|
528
|
-
|
|
529
|
-
await errorLoggedPromise;
|
|
530
|
-
expect(logger.error).toHaveBeenCalledWith(
|
|
531
|
-
`UplinkSupergraphManager failed to update supergraph with the following error: The gateway subgraphs health check failed. Updating to the provided \`supergraphSdl\` will likely result in future request failures to subgraphs. The following error occurred during the health check:\n[accounts]: 500: Internal Server Error`,
|
|
532
|
-
);
|
|
533
|
-
|
|
534
|
-
// At this point, the mock update should have been called but the schema
|
|
535
|
-
// should still be the original.
|
|
536
|
-
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
537
|
-
|
|
538
|
-
expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
|
|
539
|
-
expect(getRootQueryFields(gateway.schema)).not.toContain('review');
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import nock from 'nock';
|
|
2
|
-
import { HEALTH_CHECK_QUERY, SERVICE_DEFINITION_QUERY } from '../..';
|
|
3
|
-
import { SUPERGRAPH_SDL_QUERY } from '../../supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage';
|
|
4
|
-
import { getTestingSupergraphSdl } from '../../__tests__/execution-utils';
|
|
5
|
-
import { print } from 'graphql';
|
|
6
|
-
import { Fixture, fixtures as testingFixtures } from 'apollo-federation-integration-testsuite';
|
|
7
|
-
|
|
8
|
-
export const graphRef = 'federated-service@current';
|
|
9
|
-
export const apiKey = 'service:federated-service:DD71EBbGmsuh-6suUVDwnA';
|
|
10
|
-
const apiKeyHash = 'dd55a79d467976346d229a7b12b673ce';
|
|
11
|
-
|
|
12
|
-
export const mockApolloConfig = {
|
|
13
|
-
apollo: {
|
|
14
|
-
key: apiKey,
|
|
15
|
-
keyHash: apiKeyHash,
|
|
16
|
-
graphRef,
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Service mocks
|
|
21
|
-
function mockSdlQuery({ url }: Fixture) {
|
|
22
|
-
return nock(url).post('/', {
|
|
23
|
-
query: SERVICE_DEFINITION_QUERY,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function mockSdlQuerySuccess(service: Fixture) {
|
|
28
|
-
return mockSdlQuery(service).reply(200, {
|
|
29
|
-
data: { _service: { sdl: print(service.typeDefs) } },
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function mockAllServicesSdlQuerySuccess(
|
|
34
|
-
fixtures: Fixture[] = testingFixtures,
|
|
35
|
-
) {
|
|
36
|
-
return fixtures.map((fixture) => mockSdlQuerySuccess(fixture));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function mockServiceHealthCheck({ url }: Fixture) {
|
|
40
|
-
return nock(url).post('/', {
|
|
41
|
-
query: HEALTH_CHECK_QUERY,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function mockServiceHealthCheckSuccess(service: Fixture) {
|
|
46
|
-
return mockServiceHealthCheck(service).reply(200, {
|
|
47
|
-
data: { __typename: 'Query' },
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function mockAllServicesHealthCheckSuccess(
|
|
52
|
-
fixtures: Fixture[] = testingFixtures,
|
|
53
|
-
) {
|
|
54
|
-
return fixtures.map((fixture) =>
|
|
55
|
-
mockServiceHealthCheck(fixture).reply(200, {
|
|
56
|
-
data: { __typename: 'Query' },
|
|
57
|
-
}),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Supergraph SDL fetching mocks
|
|
62
|
-
function gatewayNock(url: Parameters<typeof nock>[0]): nock.Scope {
|
|
63
|
-
const { name, version } = require('../../../package.json');
|
|
64
|
-
return nock(url, {
|
|
65
|
-
reqheaders: {
|
|
66
|
-
'apollographql-client-name': name,
|
|
67
|
-
'apollographql-client-version': version,
|
|
68
|
-
'user-agent': `${name}/${version}`,
|
|
69
|
-
'content-type': 'application/json',
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const mockCloudConfigUrl1 =
|
|
75
|
-
'https://example1.cloud-config-url.com/cloudconfig/';
|
|
76
|
-
|
|
77
|
-
export const mockCloudConfigUrl2 =
|
|
78
|
-
'https://example2.cloud-config-url.com/cloudconfig/';
|
|
79
|
-
|
|
80
|
-
export const mockCloudConfigUrl3 =
|
|
81
|
-
'https://example3.cloud-config-url.com/cloudconfig/';
|
|
82
|
-
|
|
83
|
-
export const mockOutOfBandReporterUrl =
|
|
84
|
-
'https://example.outofbandreporter.com/monitoring/';
|
|
85
|
-
|
|
86
|
-
export function mockSupergraphSdlRequestIfAfter(ifAfter: string | null, url: string | RegExp = mockCloudConfigUrl1) {
|
|
87
|
-
return gatewayNock(url).post('/', {
|
|
88
|
-
query: SUPERGRAPH_SDL_QUERY,
|
|
89
|
-
variables: {
|
|
90
|
-
ref: graphRef,
|
|
91
|
-
apiKey: apiKey,
|
|
92
|
-
ifAfterId: ifAfter,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function mockSupergraphSdlRequest(ifAfter: string | null = null, url: string | RegExp = mockCloudConfigUrl1) {
|
|
98
|
-
return mockSupergraphSdlRequestIfAfter(ifAfter, url);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function mockSupergraphSdlRequestSuccessIfAfter(
|
|
102
|
-
ifAfter: string | null = null,
|
|
103
|
-
id: string = 'originalId-1234',
|
|
104
|
-
supergraphSdl: string = getTestingSupergraphSdl(),
|
|
105
|
-
url: string | RegExp = mockCloudConfigUrl1,
|
|
106
|
-
) {
|
|
107
|
-
if (supergraphSdl == null) {
|
|
108
|
-
supergraphSdl = getTestingSupergraphSdl();
|
|
109
|
-
}
|
|
110
|
-
return mockSupergraphSdlRequestIfAfter(ifAfter, url).reply(
|
|
111
|
-
200,
|
|
112
|
-
JSON.stringify({
|
|
113
|
-
data: {
|
|
114
|
-
routerConfig: {
|
|
115
|
-
__typename: 'RouterConfigResult',
|
|
116
|
-
id,
|
|
117
|
-
supergraphSdl,
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
}),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function mockSupergraphSdlRequestIfAfterUnchanged(
|
|
125
|
-
ifAfter: string | null = null,
|
|
126
|
-
url: string = mockCloudConfigUrl1,
|
|
127
|
-
) {
|
|
128
|
-
return mockSupergraphSdlRequestIfAfter(ifAfter, url).reply(
|
|
129
|
-
200,
|
|
130
|
-
JSON.stringify({
|
|
131
|
-
data: {
|
|
132
|
-
routerConfig: {
|
|
133
|
-
__typename: 'Unchanged',
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
}),
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function mockSupergraphSdlRequestSuccess({supergraphSdl = getTestingSupergraphSdl(), url = mockCloudConfigUrl1}: {supergraphSdl?: string, url?: string | RegExp} = {}) {
|
|
141
|
-
return mockSupergraphSdlRequestSuccessIfAfter(null, undefined, supergraphSdl, url);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function mockOutOfBandReportRequest() {
|
|
145
|
-
return gatewayNock(mockOutOfBandReporterUrl).post('/', () => true);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function mockOutOfBandReportRequestSuccess() {
|
|
149
|
-
return mockOutOfBandReportRequest().reply(
|
|
150
|
-
200,
|
|
151
|
-
JSON.stringify({
|
|
152
|
-
data: {
|
|
153
|
-
reportError: true
|
|
154
|
-
},
|
|
155
|
-
}),
|
|
156
|
-
);
|
|
157
|
-
}
|