@apollo/gateway 2.0.0-alpha.0 → 2.0.0-alpha.4

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 (154) hide show
  1. package/README.md +1 -1
  2. package/dist/__generated__/graphqlTypes.d.ts +13 -11
  3. package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
  4. package/dist/__generated__/graphqlTypes.js.map +1 -1
  5. package/dist/config.d.ts +45 -24
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +30 -31
  8. package/dist/config.js.map +1 -1
  9. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  10. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  11. package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
  12. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  13. package/dist/datasources/types.d.ts +1 -1
  14. package/dist/datasources/types.d.ts.map +1 -1
  15. package/dist/executeQueryPlan.d.ts.map +1 -1
  16. package/dist/executeQueryPlan.js +6 -6
  17. package/dist/executeQueryPlan.js.map +1 -1
  18. package/dist/index.d.ts +36 -23
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +197 -297
  21. package/dist/index.js.map +1 -1
  22. package/dist/operationContext.js +0 -1
  23. package/dist/operationContext.js.map +1 -1
  24. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  25. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  26. package/dist/schema-helper/addResolversToSchema.js +62 -0
  27. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  28. package/dist/schema-helper/error.d.ts +6 -0
  29. package/dist/schema-helper/error.d.ts.map +1 -0
  30. package/dist/schema-helper/error.js +14 -0
  31. package/dist/schema-helper/error.js.map +1 -0
  32. package/dist/schema-helper/index.d.ts +4 -0
  33. package/dist/schema-helper/index.d.ts.map +1 -0
  34. package/dist/schema-helper/index.js +16 -0
  35. package/dist/schema-helper/index.js.map +1 -0
  36. package/dist/schema-helper/resolverMap.d.ts +16 -0
  37. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  38. package/dist/schema-helper/resolverMap.js +3 -0
  39. package/dist/schema-helper/resolverMap.js.map +1 -0
  40. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  41. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  42. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  43. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  44. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  45. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  46. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  47. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  48. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  49. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  50. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  51. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  52. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  53. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  54. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  55. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  56. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
  57. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  58. package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
  59. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  60. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +21 -0
  61. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  62. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +41 -10
  63. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  64. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
  65. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  66. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
  67. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  68. package/dist/supergraphManagers/index.d.ts +5 -0
  69. package/dist/supergraphManagers/index.d.ts.map +1 -0
  70. package/dist/supergraphManagers/index.js +12 -0
  71. package/dist/supergraphManagers/index.js.map +1 -0
  72. package/dist/utilities/array.js +1 -1
  73. package/dist/utilities/array.js.map +1 -1
  74. package/dist/utilities/createHash.d.ts +2 -0
  75. package/dist/utilities/createHash.d.ts.map +1 -0
  76. package/dist/utilities/createHash.js +15 -0
  77. package/dist/utilities/createHash.js.map +1 -0
  78. package/dist/utilities/isNodeLike.d.ts +3 -0
  79. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  80. package/dist/utilities/isNodeLike.js +8 -0
  81. package/dist/utilities/isNodeLike.js.map +1 -0
  82. package/package.json +9 -9
  83. package/src/__generated__/graphqlTypes.ts +13 -11
  84. package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
  85. package/src/__tests__/buildQueryPlan.test.ts +1 -1
  86. package/src/__tests__/executeQueryPlan.test.ts +1171 -77
  87. package/src/__tests__/execution-utils.ts +5 -7
  88. package/src/__tests__/gateway/buildService.test.ts +3 -3
  89. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  90. package/src/__tests__/gateway/executor.test.ts +3 -1
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -121
  92. package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
  94. package/src/__tests__/gateway/reporting.test.ts +42 -13
  95. package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
  96. package/src/__tests__/integration/aliases.test.ts +9 -3
  97. package/src/__tests__/integration/configuration.test.ts +140 -21
  98. package/src/__tests__/integration/logger.test.ts +2 -2
  99. package/src/__tests__/integration/networkRequests.test.ts +126 -149
  100. package/src/__tests__/integration/nockMocks.ts +57 -16
  101. package/src/__tests__/nockAssertions.ts +20 -0
  102. package/src/config.ts +153 -77
  103. package/src/core/__tests__/core.test.ts +6 -6
  104. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  105. package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
  106. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  107. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
  108. package/src/datasources/types.ts +1 -1
  109. package/src/executeQueryPlan.ts +18 -9
  110. package/src/index.ts +323 -481
  111. package/src/make-fetch-happen.d.ts +1 -1
  112. package/src/operationContext.ts +2 -2
  113. package/src/schema-helper/addResolversToSchema.ts +83 -0
  114. package/src/schema-helper/error.ts +11 -0
  115. package/src/schema-helper/index.ts +3 -0
  116. package/src/schema-helper/resolverMap.ts +23 -0
  117. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  118. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +7 -7
  119. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  120. package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
  121. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +7 -7
  122. package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
  123. package/src/supergraphManagers/LocalCompose/index.ts +79 -0
  124. package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +343 -0
  125. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  126. package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
  127. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +63 -14
  128. package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
  129. package/src/supergraphManagers/index.ts +4 -0
  130. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +13 -10
  131. package/src/utilities/array.ts +1 -1
  132. package/src/utilities/createHash.ts +10 -0
  133. package/src/utilities/isNodeLike.ts +11 -0
  134. package/CHANGELOG.md +0 -452
  135. package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
  136. package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
  137. package/dist/legacyLoadServicesFromStorage.js +0 -62
  138. package/dist/legacyLoadServicesFromStorage.js.map +0 -1
  139. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  140. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  141. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  142. package/dist/loadSupergraphSdlFromStorage.d.ts +0 -12
  143. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  144. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  145. package/dist/outOfBandReporter.d.ts +0 -15
  146. package/dist/outOfBandReporter.d.ts.map +0 -1
  147. package/dist/outOfBandReporter.js +0 -88
  148. package/dist/outOfBandReporter.js.map +0 -1
  149. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
  150. package/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
  151. package/src/__tests__/integration/legacyNockMocks.ts +0 -113
  152. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -664
  153. package/src/legacyLoadServicesFromStorage.ts +0 -170
  154. 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,26 +11,25 @@ import {
12
11
  mockSupergraphSdlRequestSuccess,
13
12
  mockSupergraphSdlRequest,
14
13
  mockApolloConfig,
15
- mockCloudConfigUrl,
14
+ mockCloudConfigUrl1,
15
+ mockSupergraphSdlRequestIfAfter,
16
+ mockSupergraphSdlRequestSuccessIfAfter,
16
17
  } from './nockMocks';
17
18
  import {
18
19
  accounts,
19
20
  books,
20
21
  documents,
22
+ Fixture,
21
23
  fixturesWithUpdate,
22
24
  inventory,
23
25
  product,
24
26
  reviews,
25
27
  } from 'apollo-federation-integration-testsuite';
26
28
  import { getTestingSupergraphSdl } from '../execution-utils';
29
+ import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
30
+ import resolvable from '@josephg/resolvable';
27
31
 
28
- export interface MockService {
29
- name: string;
30
- url: string;
31
- typeDefs: DocumentNode;
32
- }
33
-
34
- const simpleService: MockService = {
32
+ const simpleService: Fixture = {
35
33
  name: 'accounts',
36
34
  url: 'http://localhost:4001',
37
35
  typeDefs: gql`
@@ -60,7 +58,7 @@ let gateway: ApolloGateway | null = null;
60
58
  let cleanUp: (() => void) | null = null;
61
59
 
62
60
  beforeEach(() => {
63
- if (!nock.isActive()) nock.activate();
61
+ nockBeforeEach();
64
62
 
65
63
  const warn = jest.fn();
66
64
  const debug = jest.fn();
@@ -76,9 +74,8 @@ beforeEach(() => {
76
74
  });
77
75
 
78
76
  afterEach(async () => {
79
- expect(nock.isDone()).toBeTruthy();
80
- nock.cleanAll();
81
- nock.restore();
77
+ nockAfterEach();
78
+
82
79
  if (gateway) {
83
80
  await gateway.stop();
84
81
  gateway = null;
@@ -98,13 +95,12 @@ it('Queries remote endpoints for their SDLs', async () => {
98
95
  expect(gateway.schema!.getType('User')!.description).toBe('This is my User');
99
96
  });
100
97
 
101
- // TODO(trevor:cloudconfig): Remove all usages of the experimental config option
102
98
  it('Fetches Supergraph SDL from remote storage', async () => {
103
99
  mockSupergraphSdlRequestSuccess();
104
100
 
105
101
  gateway = new ApolloGateway({
106
102
  logger,
107
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
103
+ uplinkEndpoints: [mockCloudConfigUrl1],
108
104
  });
109
105
 
110
106
  await gateway.load(mockApolloConfig);
@@ -112,10 +108,9 @@ it('Fetches Supergraph SDL from remote storage', async () => {
112
108
  expect(gateway.schema?.getType('User')).toBeTruthy();
113
109
  });
114
110
 
115
- // TODO(trevor:cloudconfig): This test should evolve to demonstrate overriding the default in the future
116
111
  it('Fetches Supergraph SDL from remote storage using a configured env variable', async () => {
117
112
  cleanUp = mockedEnv({
118
- APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: mockCloudConfigUrl,
113
+ APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: mockCloudConfigUrl1,
119
114
  });
120
115
  mockSupergraphSdlRequestSuccess();
121
116
 
@@ -130,32 +125,47 @@ it('Fetches Supergraph SDL from remote storage using a configured env variable',
130
125
 
131
126
  it('Updates Supergraph SDL from remote storage', async () => {
132
127
  mockSupergraphSdlRequestSuccess();
133
- mockSupergraphSdlRequestSuccess(getTestingSupergraphSdl(fixturesWithUpdate), 'updatedId-5678');
128
+ mockSupergraphSdlRequestSuccessIfAfter(
129
+ 'originalId-1234',
130
+ 'updatedId-5678',
131
+ getTestingSupergraphSdl(fixturesWithUpdate),
132
+ );
134
133
 
135
134
  // This test is only interested in the second time the gateway notifies of an
136
135
  // update, since the first happens on load.
137
- let secondUpdateResolve: Function;
138
- const secondUpdate = new Promise((res) => (secondUpdateResolve = res));
139
- const schemaChangeCallback = jest
140
- .fn()
141
- .mockImplementationOnce(() => {})
142
- .mockImplementationOnce(() => {
143
- secondUpdateResolve();
144
- });
136
+ const secondUpdate = resolvable();
145
137
 
146
138
  gateway = new ApolloGateway({
147
139
  logger,
148
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
140
+ uplinkEndpoints: [mockCloudConfigUrl1],
149
141
  });
150
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
151
- gateway.experimental_pollInterval = 100;
152
- gateway.onSchemaChange(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
+ );
153
155
 
154
156
  await gateway.load(mockApolloConfig);
155
- expect(gateway['compositionId']).toMatchInlineSnapshot(`"originalId-1234"`);
156
157
 
157
158
  await secondUpdate;
158
- 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();
159
169
  });
160
170
 
161
171
  describe('Supergraph SDL update failures', () => {
@@ -164,7 +174,8 @@ describe('Supergraph SDL update failures', () => {
164
174
 
165
175
  gateway = new ApolloGateway({
166
176
  logger,
167
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
177
+ uplinkEndpoints: [mockCloudConfigUrl1],
178
+ uplinkMaxRetries: 0,
168
179
  });
169
180
 
170
181
  await expect(
@@ -183,32 +194,32 @@ describe('Supergraph SDL update failures', () => {
183
194
 
184
195
  it('Handles arbitrary fetch failures (non 200 response)', async () => {
185
196
  mockSupergraphSdlRequestSuccess();
186
- mockSupergraphSdlRequest().reply(500);
197
+ mockSupergraphSdlRequestIfAfter('originalId-1234').reply(500);
187
198
 
188
199
  // Spy on logger.error so we can just await once it's been called
189
- let errorLogged: Function;
190
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
191
- logger.error = jest.fn(() => errorLogged());
200
+ const errorLoggedPromise = resolvable();
201
+ logger.error = jest.fn(() => errorLoggedPromise.resolve());
192
202
 
193
203
  gateway = new ApolloGateway({
194
204
  logger,
195
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
205
+ uplinkEndpoints: [mockCloudConfigUrl1],
206
+ uplinkMaxRetries: 0,
196
207
  });
197
208
 
198
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
199
- gateway.experimental_pollInterval = 100;
209
+ // for testing purposes, a short pollInterval is ideal so we'll override here
210
+ gateway['pollIntervalInMs'] = 100;
200
211
 
201
212
  await gateway.load(mockApolloConfig);
202
213
  await errorLoggedPromise;
203
214
 
204
215
  expect(logger.error).toHaveBeenCalledWith(
205
- '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',
206
217
  );
207
218
  });
208
219
 
209
220
  it('Handles GraphQL errors', async () => {
210
221
  mockSupergraphSdlRequestSuccess();
211
- mockSupergraphSdlRequest().reply(200, {
222
+ mockSupergraphSdlRequest('originalId-1234').reply(200, {
212
223
  errors: [
213
224
  {
214
225
  message: 'Cannot query field "fail" on type "Query".',
@@ -219,30 +230,28 @@ describe('Supergraph SDL update failures', () => {
219
230
  });
220
231
 
221
232
  // Spy on logger.error so we can just await once it's been called
222
- let errorLogged: Function;
223
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
224
- logger.error = jest.fn(() => errorLogged());
233
+ const errorLoggedPromise = resolvable();
234
+ logger.error = jest.fn(() => errorLoggedPromise.resolve());
225
235
 
226
236
  gateway = new ApolloGateway({
227
237
  logger,
228
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
238
+ uplinkEndpoints: [mockCloudConfigUrl1],
239
+ uplinkMaxRetries: 0,
229
240
  });
230
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
231
- gateway.experimental_pollInterval = 100;
241
+ // for testing purposes, a short pollInterval is ideal so we'll override here
242
+ gateway['pollIntervalInMs'] = 100;
232
243
 
233
244
  await gateway.load(mockApolloConfig);
234
245
  await errorLoggedPromise;
235
246
 
236
247
  expect(logger.error).toHaveBeenCalledWith(
237
- 'An error occurred while fetching your schema from Apollo: ' +
238
- '\n' +
239
- '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".`,
240
249
  );
241
250
  });
242
251
 
243
252
  it("Doesn't update and logs on receiving unparseable Supergraph SDL", async () => {
244
253
  mockSupergraphSdlRequestSuccess();
245
- mockSupergraphSdlRequest().reply(
254
+ mockSupergraphSdlRequestIfAfter('originalId-1234').reply(
246
255
  200,
247
256
  JSON.stringify({
248
257
  data: {
@@ -256,22 +265,21 @@ describe('Supergraph SDL update failures', () => {
256
265
  );
257
266
 
258
267
  // Spy on logger.error so we can just await once it's been called
259
- let errorLogged: Function;
260
- const errorLoggedPromise = new Promise((r) => (errorLogged = r));
261
- logger.error = jest.fn(() => errorLogged());
268
+ const errorLoggedPromise = resolvable();
269
+ logger.error = jest.fn(() => errorLoggedPromise.resolve());
262
270
 
263
271
  gateway = new ApolloGateway({
264
272
  logger,
265
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
273
+ uplinkEndpoints: [mockCloudConfigUrl1],
266
274
  });
267
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
268
- gateway.experimental_pollInterval = 100;
275
+ // for testing purposes, a short pollInterval is ideal so we'll override here
276
+ gateway['pollIntervalInMs'] = 100;
269
277
 
270
278
  await gateway.load(mockApolloConfig);
271
279
  await errorLoggedPromise;
272
280
 
273
281
  expect(logger.error).toHaveBeenCalledWith(
274
- 'Syntax Error: Unexpected Name "Syntax".',
282
+ 'UplinkFetcher failed to update supergraph with the following error: Syntax Error: Unexpected Name "Syntax".',
275
283
  );
276
284
  expect(gateway.schema).toBeTruthy();
277
285
  });
@@ -292,10 +300,10 @@ describe('Supergraph SDL update failures', () => {
292
300
 
293
301
  gateway = new ApolloGateway({
294
302
  logger,
295
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
303
+ uplinkEndpoints: [mockCloudConfigUrl1],
296
304
  });
297
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
298
- gateway.experimental_pollInterval = 100;
305
+ // for testing purposes, a short pollInterval is ideal so we'll override here
306
+ gateway['pollIntervalInMs'] = 100;
299
307
 
300
308
  await expect(
301
309
  gateway.load(mockApolloConfig),
@@ -314,28 +322,29 @@ describe('Supergraph SDL update failures', () => {
314
322
  it('Rollsback to a previous schema when triggered', async () => {
315
323
  // Init
316
324
  mockSupergraphSdlRequestSuccess();
317
- mockSupergraphSdlRequestSuccess(getTestingSupergraphSdl(fixturesWithUpdate), 'updatedId-5678');
318
- mockSupergraphSdlRequestSuccess();
325
+ mockSupergraphSdlRequestSuccessIfAfter(
326
+ 'originalId-1234',
327
+ 'updatedId-5678',
328
+ getTestingSupergraphSdl(fixturesWithUpdate),
329
+ );
330
+ mockSupergraphSdlRequestSuccessIfAfter('updatedId-5678');
319
331
 
320
- let firstResolve: Function;
321
- let secondResolve: Function;
322
- let thirdResolve: Function;
323
- const firstSchemaChangeBlocker = new Promise((res) => (firstResolve = res));
324
- const secondSchemaChangeBlocker = new Promise((res) => (secondResolve = res));
325
- const thirdSchemaChangeBlocker = new Promise((res) => (thirdResolve = res));
332
+ const firstSchemaChangeBlocker = resolvable();
333
+ const secondSchemaChangeBlocker = resolvable();
334
+ const thirdSchemaChangeBlocker = resolvable();
326
335
 
327
336
  const onChange = jest
328
337
  .fn()
329
- .mockImplementationOnce(() => firstResolve())
330
- .mockImplementationOnce(() => secondResolve())
331
- .mockImplementationOnce(() => thirdResolve());
338
+ .mockImplementationOnce(() => firstSchemaChangeBlocker.resolve())
339
+ .mockImplementationOnce(() => secondSchemaChangeBlocker.resolve())
340
+ .mockImplementationOnce(() => thirdSchemaChangeBlocker.resolve());
332
341
 
333
342
  gateway = new ApolloGateway({
334
343
  logger,
335
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
344
+ uplinkEndpoints: [mockCloudConfigUrl1],
336
345
  });
337
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
338
- gateway.experimental_pollInterval = 100;
346
+ // for testing purposes, a short pollInterval is ideal so we'll override here
347
+ gateway['pollIntervalInMs'] = 100;
339
348
 
340
349
  gateway.onSchemaChange(onChange);
341
350
  await gateway.load(mockApolloConfig);
@@ -386,15 +395,15 @@ describe('Downstream service health checks', () => {
386
395
  // [accounts] Account -> A @key selects id, but Account.id could not be found"
387
396
  // `);
388
397
  // Instead we'll just use the regular snapshot matcher...
398
+ let err;
389
399
  try {
390
400
  await gateway.load(mockApolloConfig);
391
401
  } catch (e) {
392
- var err = e;
402
+ err = e;
393
403
  }
394
404
 
395
- // TODO: smell that we should be awaiting something else
396
405
  expect(err.message).toMatchInlineSnapshot(`
397
- "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:
398
407
  [accounts]: 500: Internal Server Error"
399
408
  `);
400
409
 
@@ -416,10 +425,10 @@ describe('Downstream service health checks', () => {
416
425
  gateway = new ApolloGateway({
417
426
  serviceHealthCheck: true,
418
427
  logger,
419
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
428
+ uplinkEndpoints: [mockCloudConfigUrl1],
420
429
  });
421
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
422
- gateway.experimental_pollInterval = 100;
430
+ // for testing purposes, a short pollInterval is ideal so we'll override here
431
+ gateway['pollIntervalInMs'] = 100;
423
432
 
424
433
  await gateway.load(mockApolloConfig);
425
434
  await gateway.stop();
@@ -439,7 +448,7 @@ describe('Downstream service health checks', () => {
439
448
  gateway = new ApolloGateway({
440
449
  serviceHealthCheck: true,
441
450
  logger,
442
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
451
+ uplinkEndpoints: [mockCloudConfigUrl1],
443
452
  });
444
453
 
445
454
  // This is the ideal, but our version of Jest has a bug with printing error snapshots.
@@ -450,15 +459,16 @@ describe('Downstream service health checks', () => {
450
459
  // [accounts] Account -> A @key selects id, but Account.id could not be found"
451
460
  // `);
452
461
  // Instead we'll just use the regular snapshot matcher...
462
+ let err;
453
463
  try {
454
464
  await gateway.load(mockApolloConfig);
455
465
  } catch (e) {
456
- var err = e;
466
+ err = e;
457
467
  }
458
468
 
459
469
  // TODO: smell that we should be awaiting something else
460
470
  expect(err.message).toMatchInlineSnapshot(`
461
- "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:
462
472
  [accounts]: 500: Internal Server Error"
463
473
  `);
464
474
 
@@ -480,28 +490,27 @@ describe('Downstream service health checks', () => {
480
490
  mockAllServicesHealthCheckSuccess();
481
491
 
482
492
  // Update
483
- mockSupergraphSdlRequestSuccess(
484
- getTestingSupergraphSdl(fixturesWithUpdate),
485
- 'updatedId-5678',
493
+ mockSupergraphSdlRequestSuccessIfAfter(
494
+ 'originalId-1234',
495
+ 'updatedId-5678',
496
+ getTestingSupergraphSdl(fixturesWithUpdate),
486
497
  );
487
498
  mockAllServicesHealthCheckSuccess();
488
499
 
489
- let resolve1: Function;
490
- let resolve2: Function;
491
- const schemaChangeBlocker1 = new Promise((res) => (resolve1 = res));
492
- const schemaChangeBlocker2 = new Promise((res) => (resolve2 = res));
500
+ const schemaChangeBlocker1 = resolvable();
501
+ const schemaChangeBlocker2 = resolvable();
493
502
  const onChange = jest
494
503
  .fn()
495
- .mockImplementationOnce(() => resolve1())
496
- .mockImplementationOnce(() => resolve2());
504
+ .mockImplementationOnce(() => schemaChangeBlocker1.resolve())
505
+ .mockImplementationOnce(() => schemaChangeBlocker2.resolve());
497
506
 
498
507
  gateway = new ApolloGateway({
499
508
  serviceHealthCheck: true,
500
509
  logger,
501
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
510
+ uplinkEndpoints: [mockCloudConfigUrl1],
502
511
  });
503
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
504
- gateway.experimental_pollInterval = 100;
512
+ // for testing purposes, a short pollInterval is ideal so we'll override here
513
+ gateway['pollIntervalInMs'] = 100;
505
514
 
506
515
  gateway.onSchemaChange(onChange);
507
516
  await gateway.load(mockApolloConfig);
@@ -519,13 +528,18 @@ describe('Downstream service health checks', () => {
519
528
  });
520
529
 
521
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
+
522
535
  mockSupergraphSdlRequestSuccess();
523
536
  mockAllServicesHealthCheckSuccess();
524
537
 
525
538
  // Update (with one health check failure)
526
- mockSupergraphSdlRequestSuccess(
539
+ mockSupergraphSdlRequestSuccessIfAfter(
540
+ 'originalId-1234',
541
+ 'updatedId-5678',
527
542
  getTestingSupergraphSdl(fixturesWithUpdate),
528
- 'updatedId-5678',
529
543
  );
530
544
  mockServiceHealthCheck(accounts).reply(500);
531
545
  mockServiceHealthCheckSuccess(books);
@@ -534,57 +548,16 @@ describe('Downstream service health checks', () => {
534
548
  mockServiceHealthCheckSuccess(reviews);
535
549
  mockServiceHealthCheckSuccess(documents);
536
550
 
537
- let resolve: Function;
538
- const schemaChangeBlocker = new Promise((res) => (resolve = res));
539
-
540
551
  gateway = new ApolloGateway({
541
552
  serviceHealthCheck: true,
542
553
  logger,
543
- schemaConfigDeliveryEndpoint: mockCloudConfigUrl,
554
+ uplinkEndpoints: [mockCloudConfigUrl1],
544
555
  });
545
- // @ts-ignore for testing purposes, a short pollInterval is ideal so we'll override here
546
- gateway.experimental_pollInterval = 100;
547
-
548
- // @ts-ignore for testing purposes, we'll call the original `updateSchema`
549
- // function from our mock. The first call should mimic original behavior,
550
- // but the second call needs to handle the PromiseRejection. Typically for tests
551
- // like these we would leverage the `gateway.onSchemaChange` callback to drive
552
- // the test, but in this case, that callback isn't triggered when the update
553
- // fails (as expected) so we get creative with the second mock as seen below.
554
- const original = gateway.updateSchema;
555
- const mockUpdateSchema = jest
556
- .fn()
557
- .mockImplementationOnce(async () => {
558
- await original.apply(gateway);
559
- })
560
- .mockImplementationOnce(async () => {
561
- // mock the first poll and handle the error which would otherwise be caught
562
- // and logged from within the `pollServices` class method
563
-
564
- // This is the ideal, but our version of Jest has a bug with printing error snapshots.
565
- // See: https://github.com/facebook/jest/pull/10217 (fixed in v26.2.0)
566
- // expect(original.apply(gateway)).rejects.toThrowErrorMatchingInlineSnapshot(`
567
- // 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:
568
- // [accounts]: 500: Internal Server Error"
569
- // `);
570
- // Instead we'll just use the regular snapshot matcher...
571
- try {
572
- await original.apply(gateway);
573
- } catch (e) {
574
- var err = e;
575
- }
576
-
577
- expect(err.message).toMatchInlineSnapshot(`
578
- "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:
579
- [accounts]: 500: Internal Server Error"
580
- `);
581
- // finally resolve the promise which drives this test
582
- resolve();
583
- });
584
-
585
- // @ts-ignore for testing purposes, replace the `updateSchema`
586
- // function on the gateway with our mock
587
- 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());
588
561
 
589
562
  // load the gateway as usual
590
563
  await gateway.load(mockApolloConfig);
@@ -593,11 +566,15 @@ describe('Downstream service health checks', () => {
593
566
  expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
594
567
  expect(getRootQueryFields(gateway.schema)).not.toContain('review');
595
568
 
596
- 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
+ );
597
573
 
598
574
  // At this point, the mock update should have been called but the schema
599
575
  // should still be the original.
600
- expect(mockUpdateSchema).toHaveBeenCalledTimes(2);
576
+ expect(updateSpy).toHaveBeenCalledTimes(1);
577
+
601
578
  expect(getRootQueryFields(gateway.schema)).toContain('topReviews');
602
579
  expect(getRootQueryFields(gateway.schema)).not.toContain('review');
603
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,27 +71,42 @@ 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 mockSupergraphSdlRequest() {
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,
78
91
  apiKey: apiKey,
92
+ ifAfterId: ifAfter,
79
93
  },
80
94
  });
81
95
  }
82
96
 
83
- export function mockSupergraphSdlRequestSuccess(
84
- supergraphSdl = getTestingSupergraphSdl(),
85
- id = 'originalId-1234',
97
+ export function mockSupergraphSdlRequest(ifAfter: string | null = null, url: string = 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(),
86
105
  ) {
87
- return mockSupergraphSdlRequest().reply(
106
+ if (supergraphSdl == null) {
107
+ supergraphSdl = getTestingSupergraphSdl();
108
+ }
109
+ return mockSupergraphSdlRequestIfAfter(ifAfter).reply(
88
110
  200,
89
111
  JSON.stringify({
90
112
  data: {
@@ -98,6 +120,25 @@ export function mockSupergraphSdlRequestSuccess(
98
120
  );
99
121
  }
100
122
 
123
+ export function mockSupergraphSdlRequestIfAfterUnchanged(
124
+ ifAfter: string | null = null,
125
+ ) {
126
+ return mockSupergraphSdlRequestIfAfter(ifAfter).reply(
127
+ 200,
128
+ JSON.stringify({
129
+ data: {
130
+ routerConfig: {
131
+ __typename: 'Unchanged',
132
+ },
133
+ },
134
+ }),
135
+ );
136
+ }
137
+
138
+ export function mockSupergraphSdlRequestSuccess() {
139
+ return mockSupergraphSdlRequestSuccessIfAfter(null);
140
+ }
141
+
101
142
  export function mockOutOfBandReportRequest() {
102
143
  return gatewayNock(mockOutOfBandReporterUrl).post('/', () => true);
103
144
  }