@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.
Files changed (130) hide show
  1. package/dist/config.d.ts +43 -15
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +28 -18
  4. package/dist/config.js.map +1 -1
  5. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  6. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  7. package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
  8. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  9. package/dist/executeQueryPlan.d.ts.map +1 -1
  10. package/dist/executeQueryPlan.js +2 -2
  11. package/dist/executeQueryPlan.js.map +1 -1
  12. package/dist/index.d.ts +37 -22
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +199 -283
  15. package/dist/index.js.map +1 -1
  16. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  17. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  18. package/dist/schema-helper/addResolversToSchema.js +62 -0
  19. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  20. package/dist/schema-helper/error.d.ts +6 -0
  21. package/dist/schema-helper/error.d.ts.map +1 -0
  22. package/dist/schema-helper/error.js +14 -0
  23. package/dist/schema-helper/error.js.map +1 -0
  24. package/dist/schema-helper/index.d.ts +4 -0
  25. package/dist/schema-helper/index.d.ts.map +1 -0
  26. package/dist/schema-helper/index.js +16 -0
  27. package/dist/schema-helper/index.js.map +1 -0
  28. package/dist/schema-helper/resolverMap.d.ts +16 -0
  29. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  30. package/dist/schema-helper/resolverMap.js +3 -0
  31. package/dist/schema-helper/resolverMap.js.map +1 -0
  32. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  33. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  34. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  35. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  36. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  37. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  38. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  39. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  40. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  41. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  42. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  43. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  44. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  45. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  46. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  47. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  48. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +33 -0
  49. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  50. package/dist/supergraphManagers/UplinkFetcher/index.js +98 -0
  51. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  52. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +25 -0
  53. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  54. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +40 -15
  55. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  56. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
  57. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  58. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
  59. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  60. package/dist/supergraphManagers/index.d.ts +6 -0
  61. package/dist/supergraphManagers/index.d.ts.map +1 -0
  62. package/dist/supergraphManagers/index.js +14 -0
  63. package/dist/supergraphManagers/index.js.map +1 -0
  64. package/dist/utilities/createHash.d.ts +2 -0
  65. package/dist/utilities/createHash.d.ts.map +1 -0
  66. package/dist/utilities/createHash.js +15 -0
  67. package/dist/utilities/createHash.js.map +1 -0
  68. package/dist/utilities/isNodeLike.d.ts +3 -0
  69. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  70. package/dist/utilities/isNodeLike.js +8 -0
  71. package/dist/utilities/isNodeLike.js.map +1 -0
  72. package/package.json +9 -7
  73. package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
  74. package/src/__tests__/build-query-plan.feature +52 -0
  75. package/src/__tests__/executeQueryPlan.test.ts +599 -1
  76. package/src/__tests__/execution-utils.ts +3 -3
  77. package/src/__tests__/gateway/buildService.test.ts +3 -3
  78. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  79. package/src/__tests__/gateway/executor.test.ts +1 -1
  80. package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -125
  81. package/src/__tests__/gateway/opentelemetry.test.ts +8 -4
  82. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -12
  83. package/src/__tests__/gateway/reporting.test.ts +42 -13
  84. package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
  85. package/src/__tests__/integration/aliases.test.ts +9 -4
  86. package/src/__tests__/integration/configuration.test.ts +146 -9
  87. package/src/__tests__/integration/logger.test.ts +1 -1
  88. package/src/__tests__/integration/networkRequests.test.ts +99 -147
  89. package/src/__tests__/integration/nockMocks.ts +30 -16
  90. package/src/__tests__/nockAssertions.ts +20 -0
  91. package/src/config.ts +149 -38
  92. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  93. package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
  94. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  95. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
  96. package/src/executeQueryPlan.ts +14 -2
  97. package/src/index.ts +325 -452
  98. package/src/schema-helper/addResolversToSchema.ts +83 -0
  99. package/src/schema-helper/error.ts +11 -0
  100. package/src/schema-helper/index.ts +3 -0
  101. package/src/schema-helper/resolverMap.ts +23 -0
  102. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  103. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
  104. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  105. package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
  106. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
  107. package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
  108. package/src/supergraphManagers/LocalCompose/index.ts +79 -0
  109. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +397 -0
  110. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  111. package/src/supergraphManagers/UplinkFetcher/index.ts +130 -0
  112. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +68 -17
  113. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
  114. package/src/supergraphManagers/index.ts +5 -0
  115. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +12 -9
  116. package/src/utilities/createHash.ts +10 -0
  117. package/src/utilities/isNodeLike.ts +11 -0
  118. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  119. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  120. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  121. package/dist/loadSupergraphSdlFromStorage.d.ts +0 -13
  122. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  123. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  124. package/dist/outOfBandReporter.d.ts +0 -15
  125. package/dist/outOfBandReporter.d.ts.map +0 -1
  126. package/dist/outOfBandReporter.js +0 -88
  127. package/dist/outOfBandReporter.js.map +0 -1
  128. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
  129. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -694
  130. package/src/outOfBandReporter.ts +0 -128
@@ -1,6 +1,5 @@
1
- import nock from 'nock';
2
1
  import gql from 'graphql-tag';
3
- import { DocumentNode, GraphQLObjectType, GraphQLSchema } from 'graphql';
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
- mockCloudConfigUrl,
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
- type GenericFunction = (...args: unknown[]) => unknown;
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
- if (!nock.isActive()) nock.activate();
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
- expect(nock.isDone()).toBeTruthy();
83
- nock.cleanAll();
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
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: mockCloudConfigUrl,
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
- let secondUpdateResolve: GenericFunction;
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
140
+ uplinkEndpoints: [mockCloudConfigUrl1],
154
141
  });
155
- // eslint-disable-next-line
156
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
157
- gateway.experimental_pollInterval = 100;
158
- gateway.onSchemaLoadOrUpdate(schemaChangeCallback);
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
- expect(gateway['compositionId']).toMatchInlineSnapshot(`"updatedId-5678"`);
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
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
- let errorLogged: GenericFunction;
196
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
205
+ uplinkEndpoints: [mockCloudConfigUrl1],
206
+ uplinkMaxRetries: 0,
202
207
  });
203
208
 
204
- // eslint-disable-next-line
205
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- let errorLogged: GenericFunction;
230
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
238
+ uplinkEndpoints: [mockCloudConfigUrl1],
239
+ uplinkMaxRetries: 0,
236
240
  });
237
- // eslint-disable-next-line
238
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- 'An error occurred while fetching your schema from Apollo: ' +
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
- let errorLogged: GenericFunction;
268
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
273
+ uplinkEndpoints: [mockCloudConfigUrl1],
274
274
  });
275
- // eslint-disable-next-line
276
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
303
+ uplinkEndpoints: [mockCloudConfigUrl1],
305
304
  });
306
- // eslint-disable-next-line
307
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- let firstResolve: GenericFunction;
335
- let secondResolve: GenericFunction;
336
- let thirdResolve: GenericFunction;
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(() => firstResolve())
344
- .mockImplementationOnce(() => secondResolve())
345
- .mockImplementationOnce(() => thirdResolve());
338
+ .mockImplementationOnce(() => firstSchemaChangeBlocker.resolve())
339
+ .mockImplementationOnce(() => secondSchemaChangeBlocker.resolve())
340
+ .mockImplementationOnce(() => thirdSchemaChangeBlocker.resolve());
346
341
 
347
342
  gateway = new ApolloGateway({
348
343
  logger,
349
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
344
+ uplinkEndpoints: [mockCloudConfigUrl1],
350
345
  });
351
- // eslint-disable-next-line
352
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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 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:
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
428
+ uplinkEndpoints: [mockCloudConfigUrl1],
436
429
  });
437
- // eslint-disable-next-line
438
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
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 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:
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
- let resolve1: GenericFunction;
509
- let resolve2: GenericFunction;
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(() => resolve1())
515
- .mockImplementationOnce(() => resolve2());
504
+ .mockImplementationOnce(() => schemaChangeBlocker1.resolve())
505
+ .mockImplementationOnce(() => schemaChangeBlocker2.resolve());
516
506
 
517
507
  gateway = new ApolloGateway({
518
508
  serviceHealthCheck: true,
519
509
  logger,
520
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
510
+ uplinkEndpoints: [mockCloudConfigUrl1],
521
511
  });
522
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
523
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
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
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
554
+ uplinkEndpoints: [mockCloudConfigUrl1],
565
555
  });
566
- // eslint-disable-next-line
567
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
568
- gateway.experimental_pollInterval = 100;
569
-
570
- // eslint-disable-next-line
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 schemaChangeBlocker;
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(mockUpdateSchema).toHaveBeenCalledTimes(2);
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 }: MockService) {
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: MockService) {
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 mockServiceHealthCheck({ url }: MockService) {
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: MockService) {
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 mockCloudConfigUrl =
68
- 'https://example.cloud-config-url.com/cloudconfig/';
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(mockCloudConfigUrl).post('/', {
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
- ifAfter: string | null = null,
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
+ };