@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.
- package/README.md +1 -1
- package/dist/__generated__/graphqlTypes.d.ts +13 -11
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/config.d.ts +45 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -31
- 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/datasources/types.d.ts +1 -1
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +6 -6
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +36 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -297
- package/dist/index.js.map +1 -1
- package/dist/operationContext.js +0 -1
- package/dist/operationContext.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 +32 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +21 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +41 -10
- 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 +5 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +12 -0
- package/dist/supergraphManagers/index.js.map +1 -0
- package/dist/utilities/array.js +1 -1
- package/dist/utilities/array.js.map +1 -1
- 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 -9
- package/src/__generated__/graphqlTypes.ts +13 -11
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/buildQueryPlan.test.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +1171 -77
- package/src/__tests__/execution-utils.ts +5 -7
- 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 +3 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -121
- package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
- package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
- 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 -3
- package/src/__tests__/integration/configuration.test.ts +140 -21
- package/src/__tests__/integration/logger.test.ts +2 -2
- package/src/__tests__/integration/networkRequests.test.ts +126 -149
- package/src/__tests__/integration/nockMocks.ts +57 -16
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +153 -77
- package/src/core/__tests__/core.test.ts +6 -6
- 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/datasources/types.ts +1 -1
- package/src/executeQueryPlan.ts +18 -9
- package/src/index.ts +323 -481
- package/src/make-fetch-happen.d.ts +1 -1
- package/src/operationContext.ts +2 -2
- 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 +7 -7
- 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} +7 -7
- package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
- package/src/supergraphManagers/LocalCompose/index.ts +79 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +343 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +63 -14
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
- package/src/supergraphManagers/index.ts +4 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +13 -10
- package/src/utilities/array.ts +1 -1
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/CHANGELOG.md +0 -452
- package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
- package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
- package/dist/legacyLoadServicesFromStorage.js +0 -62
- package/dist/legacyLoadServicesFromStorage.js.map +0 -1
- 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 -12
- 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__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -664
- package/src/legacyLoadServicesFromStorage.ts +0 -170
- 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,26 +11,25 @@ import {
|
|
|
12
11
|
mockSupergraphSdlRequestSuccess,
|
|
13
12
|
mockSupergraphSdlRequest,
|
|
14
13
|
mockApolloConfig,
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
149
141
|
});
|
|
150
|
-
//
|
|
151
|
-
gateway
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
205
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
206
|
+
uplinkMaxRetries: 0,
|
|
196
207
|
});
|
|
197
208
|
|
|
198
|
-
//
|
|
199
|
-
gateway
|
|
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
|
-
|
|
223
|
-
|
|
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
|
-
|
|
238
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
239
|
+
uplinkMaxRetries: 0,
|
|
229
240
|
});
|
|
230
|
-
//
|
|
231
|
-
gateway
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
273
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
266
274
|
});
|
|
267
|
-
//
|
|
268
|
-
gateway
|
|
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
|
-
|
|
303
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
296
304
|
});
|
|
297
|
-
//
|
|
298
|
-
gateway
|
|
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
|
-
|
|
318
|
-
|
|
325
|
+
mockSupergraphSdlRequestSuccessIfAfter(
|
|
326
|
+
'originalId-1234',
|
|
327
|
+
'updatedId-5678',
|
|
328
|
+
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
329
|
+
);
|
|
330
|
+
mockSupergraphSdlRequestSuccessIfAfter('updatedId-5678');
|
|
319
331
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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(() =>
|
|
330
|
-
.mockImplementationOnce(() =>
|
|
331
|
-
.mockImplementationOnce(() =>
|
|
338
|
+
.mockImplementationOnce(() => firstSchemaChangeBlocker.resolve())
|
|
339
|
+
.mockImplementationOnce(() => secondSchemaChangeBlocker.resolve())
|
|
340
|
+
.mockImplementationOnce(() => thirdSchemaChangeBlocker.resolve());
|
|
332
341
|
|
|
333
342
|
gateway = new ApolloGateway({
|
|
334
343
|
logger,
|
|
335
|
-
|
|
344
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
336
345
|
});
|
|
337
|
-
//
|
|
338
|
-
gateway
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
428
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
420
429
|
});
|
|
421
|
-
//
|
|
422
|
-
gateway
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
493
|
+
mockSupergraphSdlRequestSuccessIfAfter(
|
|
494
|
+
'originalId-1234',
|
|
495
|
+
'updatedId-5678',
|
|
496
|
+
getTestingSupergraphSdl(fixturesWithUpdate),
|
|
486
497
|
);
|
|
487
498
|
mockAllServicesHealthCheckSuccess();
|
|
488
499
|
|
|
489
|
-
|
|
490
|
-
|
|
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(() =>
|
|
496
|
-
.mockImplementationOnce(() =>
|
|
504
|
+
.mockImplementationOnce(() => schemaChangeBlocker1.resolve())
|
|
505
|
+
.mockImplementationOnce(() => schemaChangeBlocker2.resolve());
|
|
497
506
|
|
|
498
507
|
gateway = new ApolloGateway({
|
|
499
508
|
serviceHealthCheck: true,
|
|
500
509
|
logger,
|
|
501
|
-
|
|
510
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
502
511
|
});
|
|
503
|
-
//
|
|
504
|
-
gateway
|
|
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
|
-
|
|
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
|
-
|
|
554
|
+
uplinkEndpoints: [mockCloudConfigUrl1],
|
|
544
555
|
});
|
|
545
|
-
//
|
|
546
|
-
gateway
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
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(
|
|
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 }:
|
|
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,27 +71,42 @@ 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
|
|
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,
|
|
78
91
|
apiKey: apiKey,
|
|
92
|
+
ifAfterId: ifAfter,
|
|
79
93
|
},
|
|
80
94
|
});
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
}
|