@apollo/gateway 2.0.0-alpha.2 → 2.0.0-alpha.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.
- package/dist/config.d.ts +43 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -18
- package/dist/config.js.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +2 -2
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +37 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -283
- package/dist/index.js.map +1 -1
- package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
- package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
- package/dist/schema-helper/addResolversToSchema.js +62 -0
- package/dist/schema-helper/addResolversToSchema.js.map +1 -0
- package/dist/schema-helper/error.d.ts +6 -0
- package/dist/schema-helper/error.d.ts.map +1 -0
- package/dist/schema-helper/error.js +14 -0
- package/dist/schema-helper/error.js.map +1 -0
- package/dist/schema-helper/index.d.ts +4 -0
- package/dist/schema-helper/index.d.ts.map +1 -0
- package/dist/schema-helper/index.js +16 -0
- package/dist/schema-helper/index.js.map +1 -0
- package/dist/schema-helper/resolverMap.d.ts +16 -0
- package/dist/schema-helper/resolverMap.d.ts.map +1 -0
- package/dist/schema-helper/resolverMap.js +3 -0
- package/dist/schema-helper/resolverMap.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
- package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.js +55 -0
- package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +33 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +98 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +25 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +40 -15
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +6 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +14 -0
- package/dist/supergraphManagers/index.js.map +1 -0
- package/dist/utilities/createHash.d.ts +2 -0
- package/dist/utilities/createHash.d.ts.map +1 -0
- package/dist/utilities/createHash.js +15 -0
- package/dist/utilities/createHash.js.map +1 -0
- package/dist/utilities/isNodeLike.d.ts +3 -0
- package/dist/utilities/isNodeLike.d.ts.map +1 -0
- package/dist/utilities/isNodeLike.js +8 -0
- package/dist/utilities/isNodeLike.js.map +1 -0
- package/package.json +9 -7
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/build-query-plan.feature +52 -0
- package/src/__tests__/executeQueryPlan.test.ts +599 -1
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/buildService.test.ts +3 -3
- package/src/__tests__/gateway/endToEnd.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +1 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -125
- package/src/__tests__/gateway/opentelemetry.test.ts +8 -4
- package/src/__tests__/gateway/queryPlanCache.test.ts +25 -12
- package/src/__tests__/gateway/reporting.test.ts +42 -13
- package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
- package/src/__tests__/integration/aliases.test.ts +9 -4
- package/src/__tests__/integration/configuration.test.ts +146 -9
- package/src/__tests__/integration/logger.test.ts +1 -1
- package/src/__tests__/integration/networkRequests.test.ts +99 -147
- package/src/__tests__/integration/nockMocks.ts +30 -16
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +149 -38
- package/src/datasources/LocalGraphQLDataSource.ts +1 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/executeQueryPlan.ts +14 -2
- package/src/index.ts +325 -452
- package/src/schema-helper/addResolversToSchema.ts +83 -0
- package/src/schema-helper/error.ts +11 -0
- package/src/schema-helper/index.ts +3 -0
- package/src/schema-helper/resolverMap.ts +23 -0
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
- package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
- package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
- package/src/supergraphManagers/LocalCompose/index.ts +79 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +397 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +130 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +68 -17
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
- package/src/supergraphManagers/index.ts +5 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +12 -9
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +0 -13
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts +0 -15
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js +0 -88
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -694
- package/src/outOfBandReporter.ts +0 -128
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import nock from 'nock';
|
|
2
1
|
import gql from 'graphql-tag';
|
|
3
|
-
import {
|
|
2
|
+
import { GraphQLObjectType, GraphQLSchema } from 'graphql';
|
|
4
3
|
import mockedEnv from 'mocked-env';
|
|
5
4
|
import { Logger } from 'apollo-server-types';
|
|
6
5
|
import { ApolloGateway } from '../..';
|
|
@@ -12,7 +11,7 @@ import {
|
|
|
12
11
|
mockSupergraphSdlRequestSuccess,
|
|
13
12
|
mockSupergraphSdlRequest,
|
|
14
13
|
mockApolloConfig,
|
|
15
|
-
|
|
14
|
+
mockCloudConfigUrl1,
|
|
16
15
|
mockSupergraphSdlRequestIfAfter,
|
|
17
16
|
mockSupergraphSdlRequestSuccessIfAfter,
|
|
18
17
|
} from './nockMocks';
|
|
@@ -20,21 +19,17 @@ import {
|
|
|
20
19
|
accounts,
|
|
21
20
|
books,
|
|
22
21
|
documents,
|
|
22
|
+
Fixture,
|
|
23
23
|
fixturesWithUpdate,
|
|
24
24
|
inventory,
|
|
25
25
|
product,
|
|
26
26
|
reviews,
|
|
27
27
|
} from 'apollo-federation-integration-testsuite';
|
|
28
28
|
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
29
|
+
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
30
|
+
import resolvable from '@josephg/resolvable';
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
export interface MockService {
|
|
32
|
-
name: string;
|
|
33
|
-
url: string;
|
|
34
|
-
typeDefs: DocumentNode;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const simpleService: MockService = {
|
|
32
|
+
const simpleService: Fixture = {
|
|
38
33
|
name: 'accounts',
|
|
39
34
|
url: 'http://localhost:4001',
|
|
40
35
|
typeDefs: gql`
|
|
@@ -63,7 +58,7 @@ let gateway: ApolloGateway | null = null;
|
|
|
63
58
|
let cleanUp: (() => void) | null = null;
|
|
64
59
|
|
|
65
60
|
beforeEach(() => {
|
|
66
|
-
|
|
61
|
+
nockBeforeEach();
|
|
67
62
|
|
|
68
63
|
const warn = jest.fn();
|
|
69
64
|
const debug = jest.fn();
|
|
@@ -79,9 +74,8 @@ beforeEach(() => {
|
|
|
79
74
|
});
|
|
80
75
|
|
|
81
76
|
afterEach(async () => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
nock.restore();
|
|
77
|
+
nockAfterEach();
|
|
78
|
+
|
|
85
79
|
if (gateway) {
|
|
86
80
|
await gateway.stop();
|
|
87
81
|
gateway = null;
|
|
@@ -106,7 +100,7 @@ it('Fetches Supergraph SDL from remote storage', async () => {
|
|
|
106
100
|
|
|
107
101
|
gateway = new ApolloGateway({
|
|
108
102
|
logger,
|
|
109
|
-
|
|
103
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
110
104
|
});
|
|
111
105
|
|
|
112
106
|
await gateway.load(mockApolloConfig);
|
|
@@ -116,7 +110,7 @@ it('Fetches Supergraph SDL from remote storage', async () => {
|
|
|
116
110
|
|
|
117
111
|
it('Fetches Supergraph SDL from remote storage using a configured env variable', async () => {
|
|
118
112
|
cleanUp = mockedEnv({
|
|
119
|
-
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT:
|
|
113
|
+
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: mockCloudConfigUrl1,
|
|
120
114
|
});
|
|
121
115
|
mockSupergraphSdlRequestSuccess();
|
|
122
116
|
|
|
@@ -139,29 +133,39 @@ it('Updates Supergraph SDL from remote storage', async () => {
|
|
|
139
133
|
|
|
140
134
|
// This test is only interested in the second time the gateway notifies of an
|
|
141
135
|
// update, since the first happens on load.
|
|
142
|
-
|
|
143
|
-
const secondUpdate = new Promise((res) => (secondUpdateResolve = res));
|
|
144
|
-
const schemaChangeCallback = jest
|
|
145
|
-
.fn()
|
|
146
|
-
.mockImplementationOnce(() => undefined)
|
|
147
|
-
.mockImplementationOnce(() => {
|
|
148
|
-
secondUpdateResolve();
|
|
149
|
-
});
|
|
136
|
+
const secondUpdate = resolvable();
|
|
150
137
|
|
|
151
138
|
gateway = new ApolloGateway({
|
|
152
139
|
logger,
|
|
153
|
-
|
|
140
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
154
141
|
});
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
143
|
+
gateway['pollIntervalInMs'] = 100;
|
|
144
|
+
|
|
145
|
+
const schemas: GraphQLSchema[] = [];
|
|
146
|
+
gateway.onSchemaLoadOrUpdate(({ apiSchema }) => {
|
|
147
|
+
schemas.push(apiSchema);
|
|
148
|
+
});
|
|
149
|
+
gateway.onSchemaLoadOrUpdate(
|
|
150
|
+
jest
|
|
151
|
+
.fn()
|
|
152
|
+
.mockImplementationOnce(() => {})
|
|
153
|
+
.mockImplementationOnce(() => secondUpdate.resolve()),
|
|
154
|
+
);
|
|
159
155
|
|
|
160
156
|
await gateway.load(mockApolloConfig);
|
|
161
|
-
expect(gateway['compositionId']).toMatchInlineSnapshot(`"originalId-1234"`);
|
|
162
157
|
|
|
163
158
|
await secondUpdate;
|
|
164
|
-
|
|
159
|
+
|
|
160
|
+
// First schema has no 'review' field on the 'Query' type
|
|
161
|
+
expect(
|
|
162
|
+
(schemas[0].getType('Query') as GraphQLObjectType).getFields()['review'],
|
|
163
|
+
).toBeFalsy();
|
|
164
|
+
|
|
165
|
+
// Updated schema adds 'review' field on the 'Query' type
|
|
166
|
+
expect(
|
|
167
|
+
(schemas[1].getType('Query') as GraphQLObjectType).getFields()['review'],
|
|
168
|
+
).toBeTruthy();
|
|
165
169
|
});
|
|
166
170
|
|
|
167
171
|
describe('Supergraph SDL update failures', () => {
|
|
@@ -170,7 +174,8 @@ describe('Supergraph SDL update failures', () => {
|
|
|
170
174
|
|
|
171
175
|
gateway = new ApolloGateway({
|
|
172
176
|
logger,
|
|
173
|
-
|
|
177
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
178
|
+
uplinkMaxRetries: 0,
|
|
174
179
|
});
|
|
175
180
|
|
|
176
181
|
await expect(
|
|
@@ -192,24 +197,23 @@ describe('Supergraph SDL update failures', () => {
|
|
|
192
197
|
mockSupergraphSdlRequestIfAfter('originalId-1234').reply(500);
|
|
193
198
|
|
|
194
199
|
// Spy on logger.error so we can just await once it's been called
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
logger.error = jest.fn(() => errorLogged());
|
|
200
|
+
const errorLoggedPromise = resolvable();
|
|
201
|
+
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
198
202
|
|
|
199
203
|
gateway = new ApolloGateway({
|
|
200
204
|
logger,
|
|
201
|
-
|
|
205
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
206
|
+
uplinkMaxRetries: 0,
|
|
202
207
|
});
|
|
203
208
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
gateway.experimental_pollInterval = 100;
|
|
209
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
210
|
+
gateway['pollIntervalInMs'] = 100;
|
|
207
211
|
|
|
208
212
|
await gateway.load(mockApolloConfig);
|
|
209
213
|
await errorLoggedPromise;
|
|
210
214
|
|
|
211
215
|
expect(logger.error).toHaveBeenCalledWith(
|
|
212
|
-
'An error occurred while fetching your schema from Apollo: 500 Internal Server Error',
|
|
216
|
+
'UplinkFetcher failed to update supergraph with the following error: An error occurred while fetching your schema from Apollo: 500 Internal Server Error',
|
|
213
217
|
);
|
|
214
218
|
});
|
|
215
219
|
|
|
@@ -226,25 +230,22 @@ describe('Supergraph SDL update failures', () => {
|
|
|
226
230
|
});
|
|
227
231
|
|
|
228
232
|
// Spy on logger.error so we can just await once it's been called
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
logger.error = jest.fn(() => errorLogged());
|
|
233
|
+
const errorLoggedPromise = resolvable();
|
|
234
|
+
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
232
235
|
|
|
233
236
|
gateway = new ApolloGateway({
|
|
234
237
|
logger,
|
|
235
|
-
|
|
238
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
239
|
+
uplinkMaxRetries: 0,
|
|
236
240
|
});
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
gateway.experimental_pollInterval = 100;
|
|
241
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
242
|
+
gateway['pollIntervalInMs'] = 100;
|
|
240
243
|
|
|
241
244
|
await gateway.load(mockApolloConfig);
|
|
242
245
|
await errorLoggedPromise;
|
|
243
246
|
|
|
244
247
|
expect(logger.error).toHaveBeenCalledWith(
|
|
245
|
-
|
|
246
|
-
'\n' +
|
|
247
|
-
'Cannot query field "fail" on type "Query".',
|
|
248
|
+
`UplinkFetcher failed to update supergraph with the following error: An error occurred while fetching your schema from Apollo: \nCannot query field "fail" on type "Query".`,
|
|
248
249
|
);
|
|
249
250
|
});
|
|
250
251
|
|
|
@@ -264,23 +265,21 @@ describe('Supergraph SDL update failures', () => {
|
|
|
264
265
|
);
|
|
265
266
|
|
|
266
267
|
// Spy on logger.error so we can just await once it's been called
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
logger.error = jest.fn(() => errorLogged());
|
|
268
|
+
const errorLoggedPromise = resolvable();
|
|
269
|
+
logger.error = jest.fn(() => errorLoggedPromise.resolve());
|
|
270
270
|
|
|
271
271
|
gateway = new ApolloGateway({
|
|
272
272
|
logger,
|
|
273
|
-
|
|
273
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
274
274
|
});
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
gateway.experimental_pollInterval = 100;
|
|
275
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
276
|
+
gateway['pollIntervalInMs'] = 100;
|
|
278
277
|
|
|
279
278
|
await gateway.load(mockApolloConfig);
|
|
280
279
|
await errorLoggedPromise;
|
|
281
280
|
|
|
282
281
|
expect(logger.error).toHaveBeenCalledWith(
|
|
283
|
-
'Syntax Error: Unexpected Name "Syntax".',
|
|
282
|
+
'UplinkFetcher failed to update supergraph with the following error: Syntax Error: Unexpected Name "Syntax".',
|
|
284
283
|
);
|
|
285
284
|
expect(gateway.schema).toBeTruthy();
|
|
286
285
|
});
|
|
@@ -301,11 +300,10 @@ describe('Supergraph SDL update failures', () => {
|
|
|
301
300
|
|
|
302
301
|
gateway = new ApolloGateway({
|
|
303
302
|
logger,
|
|
304
|
-
|
|
303
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
305
304
|
});
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
gateway.experimental_pollInterval = 100;
|
|
305
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
306
|
+
gateway['pollIntervalInMs'] = 100;
|
|
309
307
|
|
|
310
308
|
await expect(
|
|
311
309
|
gateway.load(mockApolloConfig),
|
|
@@ -331,26 +329,22 @@ it('Rollsback to a previous schema when triggered', async () => {
|
|
|
331
329
|
);
|
|
332
330
|
mockSupergraphSdlRequestSuccessIfAfter('updatedId-5678');
|
|
333
331
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const firstSchemaChangeBlocker = new Promise((res) => (firstResolve = res));
|
|
338
|
-
const secondSchemaChangeBlocker = new Promise((res) => (secondResolve = res));
|
|
339
|
-
const thirdSchemaChangeBlocker = new Promise((res) => (thirdResolve = res));
|
|
332
|
+
const firstSchemaChangeBlocker = resolvable();
|
|
333
|
+
const secondSchemaChangeBlocker = resolvable();
|
|
334
|
+
const thirdSchemaChangeBlocker = resolvable();
|
|
340
335
|
|
|
341
336
|
const onChange = jest
|
|
342
337
|
.fn()
|
|
343
|
-
.mockImplementationOnce(() =>
|
|
344
|
-
.mockImplementationOnce(() =>
|
|
345
|
-
.mockImplementationOnce(() =>
|
|
338
|
+
.mockImplementationOnce(() => firstSchemaChangeBlocker.resolve())
|
|
339
|
+
.mockImplementationOnce(() => secondSchemaChangeBlocker.resolve())
|
|
340
|
+
.mockImplementationOnce(() => thirdSchemaChangeBlocker.resolve());
|
|
346
341
|
|
|
347
342
|
gateway = new ApolloGateway({
|
|
348
343
|
logger,
|
|
349
|
-
|
|
344
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
350
345
|
});
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
gateway.experimental_pollInterval = 100;
|
|
346
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
347
|
+
gateway['pollIntervalInMs'] = 100;
|
|
354
348
|
|
|
355
349
|
gateway.onSchemaChange(onChange);
|
|
356
350
|
await gateway.load(mockApolloConfig);
|
|
@@ -408,9 +402,8 @@ describe('Downstream service health checks', () => {
|
|
|
408
402
|
err = e;
|
|
409
403
|
}
|
|
410
404
|
|
|
411
|
-
// TODO: smell that we should be awaiting something else
|
|
412
405
|
expect(err.message).toMatchInlineSnapshot(`
|
|
413
|
-
"The gateway
|
|
406
|
+
"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:
|
|
414
407
|
[accounts]: 500: Internal Server Error"
|
|
415
408
|
`);
|
|
416
409
|
|
|
@@ -432,11 +425,10 @@ describe('Downstream service health checks', () => {
|
|
|
432
425
|
gateway = new ApolloGateway({
|
|
433
426
|
serviceHealthCheck: true,
|
|
434
427
|
logger,
|
|
435
|
-
|
|
428
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
436
429
|
});
|
|
437
|
-
//
|
|
438
|
-
|
|
439
|
-
gateway.experimental_pollInterval = 100;
|
|
430
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
431
|
+
gateway['pollIntervalInMs'] = 100;
|
|
440
432
|
|
|
441
433
|
await gateway.load(mockApolloConfig);
|
|
442
434
|
await gateway.stop();
|
|
@@ -456,7 +448,7 @@ describe('Downstream service health checks', () => {
|
|
|
456
448
|
gateway = new ApolloGateway({
|
|
457
449
|
serviceHealthCheck: true,
|
|
458
450
|
logger,
|
|
459
|
-
|
|
451
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
460
452
|
});
|
|
461
453
|
|
|
462
454
|
// This is the ideal, but our version of Jest has a bug with printing error snapshots.
|
|
@@ -476,7 +468,7 @@ describe('Downstream service health checks', () => {
|
|
|
476
468
|
|
|
477
469
|
// TODO: smell that we should be awaiting something else
|
|
478
470
|
expect(err.message).toMatchInlineSnapshot(`
|
|
479
|
-
"The gateway
|
|
471
|
+
"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:
|
|
480
472
|
[accounts]: 500: Internal Server Error"
|
|
481
473
|
`);
|
|
482
474
|
|
|
@@ -505,23 +497,20 @@ describe('Downstream service health checks', () => {
|
|
|
505
497
|
);
|
|
506
498
|
mockAllServicesHealthCheckSuccess();
|
|
507
499
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const schemaChangeBlocker1 = new Promise((res) => (resolve1 = res));
|
|
511
|
-
const schemaChangeBlocker2 = new Promise((res) => (resolve2 = res));
|
|
500
|
+
const schemaChangeBlocker1 = resolvable();
|
|
501
|
+
const schemaChangeBlocker2 = resolvable();
|
|
512
502
|
const onChange = jest
|
|
513
503
|
.fn()
|
|
514
|
-
.mockImplementationOnce(() =>
|
|
515
|
-
.mockImplementationOnce(() =>
|
|
504
|
+
.mockImplementationOnce(() => schemaChangeBlocker1.resolve())
|
|
505
|
+
.mockImplementationOnce(() => schemaChangeBlocker2.resolve());
|
|
516
506
|
|
|
517
507
|
gateway = new ApolloGateway({
|
|
518
508
|
serviceHealthCheck: true,
|
|
519
509
|
logger,
|
|
520
|
-
|
|
510
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
521
511
|
});
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
gateway.experimental_pollInterval = 100;
|
|
512
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
513
|
+
gateway['pollIntervalInMs'] = 100;
|
|
525
514
|
|
|
526
515
|
gateway.onSchemaChange(onChange);
|
|
527
516
|
await gateway.load(mockApolloConfig);
|
|
@@ -539,6 +528,10 @@ describe('Downstream service health checks', () => {
|
|
|
539
528
|
});
|
|
540
529
|
|
|
541
530
|
it('Preserves original schema when health check fails', async () => {
|
|
531
|
+
const errorLoggedPromise = resolvable();
|
|
532
|
+
const errorSpy = jest.fn(() => errorLoggedPromise.resolve());
|
|
533
|
+
logger.error = errorSpy;
|
|
534
|
+
|
|
542
535
|
mockSupergraphSdlRequestSuccess();
|
|
543
536
|
mockAllServicesHealthCheckSuccess();
|
|
544
537
|
|
|
@@ -555,61 +548,16 @@ describe('Downstream service health checks', () => {
|
|
|
555
548
|
mockServiceHealthCheckSuccess(reviews);
|
|
556
549
|
mockServiceHealthCheckSuccess(documents);
|
|
557
550
|
|
|
558
|
-
let resolve: GenericFunction;
|
|
559
|
-
const schemaChangeBlocker = new Promise((res) => (resolve = res));
|
|
560
|
-
|
|
561
551
|
gateway = new ApolloGateway({
|
|
562
552
|
serviceHealthCheck: true,
|
|
563
553
|
logger,
|
|
564
|
-
|
|
554
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
565
555
|
});
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
// @ts-ignore for testing purposes, we'll call the original `updateSchema`
|
|
572
|
-
// function from our mock. The first call should mimic original behavior,
|
|
573
|
-
// but the second call needs to handle the PromiseRejection. Typically for tests
|
|
574
|
-
// like these we would leverage the `gateway.onSchemaChange` callback to drive
|
|
575
|
-
// the test, but in this case, that callback isn't triggered when the update
|
|
576
|
-
// fails (as expected) so we get creative with the second mock as seen below.
|
|
577
|
-
const original = gateway.updateSchema;
|
|
578
|
-
const mockUpdateSchema = jest
|
|
579
|
-
.fn()
|
|
580
|
-
.mockImplementationOnce(async () => {
|
|
581
|
-
await original.apply(gateway);
|
|
582
|
-
})
|
|
583
|
-
.mockImplementationOnce(async () => {
|
|
584
|
-
// mock the first poll and handle the error which would otherwise be caught
|
|
585
|
-
// and logged from within the `pollServices` class method
|
|
586
|
-
|
|
587
|
-
// This is the ideal, but our version of Jest has a bug with printing error snapshots.
|
|
588
|
-
// See: https://github.com/facebook/jest/pull/10217 (fixed in v26.2.0)
|
|
589
|
-
// expect(original.apply(gateway)).rejects.toThrowErrorMatchingInlineSnapshot(`
|
|
590
|
-
// The gateway did not update its schema due to failed service health checks. The gateway will continue to operate with the previous schema and reattempt updates. The following error occurred during the health check:
|
|
591
|
-
// [accounts]: 500: Internal Server Error"
|
|
592
|
-
// `);
|
|
593
|
-
// Instead we'll just use the regular snapshot matcher...
|
|
594
|
-
let err;
|
|
595
|
-
try {
|
|
596
|
-
await original.apply(gateway);
|
|
597
|
-
} catch (e) {
|
|
598
|
-
err = e;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
expect(err.message).toMatchInlineSnapshot(`
|
|
602
|
-
"The gateway did not update its schema due to failed service health checks. The gateway will continue to operate with the previous schema and reattempt updates. The following error occurred during the health check:
|
|
603
|
-
[accounts]: 500: Internal Server Error"
|
|
604
|
-
`);
|
|
605
|
-
// finally resolve the promise which drives this test
|
|
606
|
-
resolve();
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
// eslint-disable-next-line
|
|
610
|
-
// @ts-ignore for testing purposes, replace the `updateSchema`
|
|
611
|
-
// function on the gateway with our mock
|
|
612
|
-
gateway.updateSchema = mockUpdateSchema;
|
|
556
|
+
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
557
|
+
gateway['pollIntervalInMs'] = 100;
|
|
558
|
+
|
|
559
|
+
const updateSpy = jest.fn();
|
|
560
|
+
gateway.onSchemaLoadOrUpdate(() => updateSpy());
|
|
613
561
|
|
|
614
562
|
// load the gateway as usual
|
|
615
563
|
await gateway.load(mockApolloConfig);
|
|
@@ -618,11 +566,15 @@ describe('Downstream service health checks', () => {
|
|
|
618
566
|
expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
|
|
619
567
|
expect(getRootQueryFields(gateway.schema)).not.toContain('review');
|
|
620
568
|
|
|
621
|
-
await
|
|
569
|
+
await errorLoggedPromise;
|
|
570
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
571
|
+
`UplinkFetcher 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`,
|
|
572
|
+
);
|
|
622
573
|
|
|
623
574
|
// At this point, the mock update should have been called but the schema
|
|
624
575
|
// should still be the original.
|
|
625
|
-
expect(
|
|
576
|
+
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
577
|
+
|
|
626
578
|
expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
|
|
627
579
|
expect(getRootQueryFields(gateway.schema)).not.toContain('review');
|
|
628
580
|
});
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import nock from 'nock';
|
|
2
|
-
import { MockService } from './networkRequests.test';
|
|
3
2
|
import { HEALTH_CHECK_QUERY, SERVICE_DEFINITION_QUERY } from '../..';
|
|
4
|
-
import { SUPERGRAPH_SDL_QUERY } from '../../loadSupergraphSdlFromStorage';
|
|
3
|
+
import { SUPERGRAPH_SDL_QUERY } from '../../supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage';
|
|
5
4
|
import { getTestingSupergraphSdl } from '../../__tests__/execution-utils';
|
|
6
5
|
import { print } from 'graphql';
|
|
7
|
-
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
6
|
+
import { Fixture, fixtures as testingFixtures } from 'apollo-federation-integration-testsuite';
|
|
8
7
|
|
|
9
8
|
export const graphRef = 'federated-service@current';
|
|
10
9
|
export const apiKey = 'service:federated-service:DD71EBbGmsuh-6suUVDwnA';
|
|
@@ -19,31 +18,39 @@ export const mockApolloConfig = {
|
|
|
19
18
|
};
|
|
20
19
|
|
|
21
20
|
// Service mocks
|
|
22
|
-
function mockSdlQuery({ url }:
|
|
21
|
+
function mockSdlQuery({ url }: Fixture) {
|
|
23
22
|
return nock(url).post('/', {
|
|
24
23
|
query: SERVICE_DEFINITION_QUERY,
|
|
25
24
|
});
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
export function mockSdlQuerySuccess(service:
|
|
27
|
+
export function mockSdlQuerySuccess(service: Fixture) {
|
|
29
28
|
return mockSdlQuery(service).reply(200, {
|
|
30
29
|
data: { _service: { sdl: print(service.typeDefs) } },
|
|
31
30
|
});
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
export function
|
|
33
|
+
export function mockAllServicesSdlQuerySuccess(
|
|
34
|
+
fixtures: Fixture[] = testingFixtures,
|
|
35
|
+
) {
|
|
36
|
+
return fixtures.map((fixture) => mockSdlQuerySuccess(fixture));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function mockServiceHealthCheck({ url }: Fixture) {
|
|
35
40
|
return nock(url).post('/', {
|
|
36
41
|
query: HEALTH_CHECK_QUERY,
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
export function mockServiceHealthCheckSuccess(service:
|
|
45
|
+
export function mockServiceHealthCheckSuccess(service: Fixture) {
|
|
41
46
|
return mockServiceHealthCheck(service).reply(200, {
|
|
42
47
|
data: { __typename: 'Query' },
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
export function mockAllServicesHealthCheckSuccess(
|
|
51
|
+
export function mockAllServicesHealthCheckSuccess(
|
|
52
|
+
fixtures: Fixture[] = testingFixtures,
|
|
53
|
+
) {
|
|
47
54
|
return fixtures.map((fixture) =>
|
|
48
55
|
mockServiceHealthCheck(fixture).reply(200, {
|
|
49
56
|
data: { __typename: 'Query' },
|
|
@@ -64,14 +71,20 @@ function gatewayNock(url: Parameters<typeof nock>[0]): nock.Scope {
|
|
|
64
71
|
});
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
export const
|
|
68
|
-
'https://
|
|
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/';
|
|
69
82
|
|
|
70
83
|
export const mockOutOfBandReporterUrl =
|
|
71
84
|
'https://example.outofbandreporter.com/monitoring/';
|
|
72
85
|
|
|
73
|
-
export function mockSupergraphSdlRequestIfAfter(ifAfter: string | null) {
|
|
74
|
-
return gatewayNock(
|
|
86
|
+
export function mockSupergraphSdlRequestIfAfter(ifAfter: string | null, url: string = mockCloudConfigUrl1) {
|
|
87
|
+
return gatewayNock(url).post('/', {
|
|
75
88
|
query: SUPERGRAPH_SDL_QUERY,
|
|
76
89
|
variables: {
|
|
77
90
|
ref: graphRef,
|
|
@@ -81,8 +94,8 @@ export function mockSupergraphSdlRequestIfAfter(ifAfter: string | null) {
|
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
export function mockSupergraphSdlRequest(ifAfter: string | null = null) {
|
|
85
|
-
return mockSupergraphSdlRequestIfAfter(ifAfter);
|
|
97
|
+
export function mockSupergraphSdlRequest(ifAfter: string | null = null, url: string = mockCloudConfigUrl1) {
|
|
98
|
+
return mockSupergraphSdlRequestIfAfter(ifAfter, url);
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
export function mockSupergraphSdlRequestSuccessIfAfter(
|
|
@@ -108,9 +121,10 @@ export function mockSupergraphSdlRequestSuccessIfAfter(
|
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
export function mockSupergraphSdlRequestIfAfterUnchanged(
|
|
111
|
-
|
|
124
|
+
ifAfter: string | null = null,
|
|
125
|
+
url: string = mockCloudConfigUrl1,
|
|
112
126
|
) {
|
|
113
|
-
return mockSupergraphSdlRequestIfAfter(ifAfter).reply(
|
|
127
|
+
return mockSupergraphSdlRequestIfAfter(ifAfter, url).reply(
|
|
114
128
|
200,
|
|
115
129
|
JSON.stringify({
|
|
116
130
|
data: {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import nock from 'nock';
|
|
2
|
+
|
|
3
|
+
// Ensures an active and clean nock before every test
|
|
4
|
+
export function nockBeforeEach() {
|
|
5
|
+
if (!nock.isActive()) {
|
|
6
|
+
nock.activate();
|
|
7
|
+
}
|
|
8
|
+
// Cleaning _before_ each test ensures that any mocks from a previous test
|
|
9
|
+
// which failed don't affect the current test.
|
|
10
|
+
nock.cleanAll();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Ensures a test is complete (all expected requests were run) and a clean
|
|
14
|
+
// global state after each test.
|
|
15
|
+
export function nockAfterEach() {
|
|
16
|
+
// unmock HTTP interceptor
|
|
17
|
+
nock.restore();
|
|
18
|
+
// effectively nock.isDone() but with more helpful messages in test failures
|
|
19
|
+
expect(nock.activeMocks()).toEqual([]);
|
|
20
|
+
};
|