@apollo/gateway 0.45.1 → 0.46.0-alpha.0
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/CHANGELOG.md +21 -0
- package/dist/config.d.ts +42 -16
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -18
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +35 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -308
- package/dist/index.js.map +1 -1
- 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/{loadSupergraphSdlFromStorage.d.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts} +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/{outOfBandReporter.d.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts} +0 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/{outOfBandReporter.js → supergraphManagers/UplinkFetcher/outOfBandReporter.js} +2 -2
- 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/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 +6 -4
- package/src/__tests__/execution-utils.ts +2 -2
- package/src/__tests__/gateway/buildService.test.ts +2 -2
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -99
- 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 +4 -6
- package/src/__tests__/gateway/supergraphSdl.test.ts +390 -0
- package/src/__tests__/integration/aliases.test.ts +9 -3
- package/src/__tests__/integration/configuration.test.ts +109 -12
- package/src/__tests__/integration/logger.test.ts +1 -1
- package/src/__tests__/integration/networkRequests.test.ts +81 -118
- package/src/__tests__/integration/nockMocks.ts +15 -8
- package/src/config.ts +149 -40
- package/src/index.ts +314 -485
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
- package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +163 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
- package/src/supergraphManagers/LegacyFetcher/index.ts +229 -0
- package/src/supergraphManagers/LocalCompose/index.ts +83 -0
- package/src/{__tests__ → supergraphManagers/UplinkFetcher/__tests__}/loadSupergraphSdlFromStorage.test.ts +4 -4
- 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} +3 -3
- package/src/{outOfBandReporter.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.ts} +2 -2
- package/src/supergraphManagers/index.ts +4 -0
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
|
@@ -6,14 +6,20 @@ import { LocalGraphQLDataSource } from '../../datasources/LocalGraphQLDataSource
|
|
|
6
6
|
import { ApolloGateway } from '../../';
|
|
7
7
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
8
8
|
import { QueryPlanner } from '@apollo/query-planner';
|
|
9
|
+
|
|
9
10
|
it('caches the query plan for a request', async () => {
|
|
10
11
|
const buildQueryPlanSpy = jest.spyOn(QueryPlanner.prototype, 'buildQueryPlan');
|
|
11
12
|
|
|
13
|
+
const localDataSources = Object.fromEntries(
|
|
14
|
+
fixtures.map((f) => [
|
|
15
|
+
f.name,
|
|
16
|
+
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
17
|
+
]),
|
|
18
|
+
);
|
|
12
19
|
const gateway = new ApolloGateway({
|
|
13
20
|
localServiceList: fixtures,
|
|
14
|
-
buildService
|
|
15
|
-
|
|
16
|
-
return new LocalGraphQLDataSource(buildSubgraphSchema([service]));
|
|
21
|
+
buildService(service) {
|
|
22
|
+
return localDataSources[service.name];
|
|
17
23
|
},
|
|
18
24
|
});
|
|
19
25
|
|
|
@@ -65,11 +71,17 @@ it('supports multiple operations and operationName', async () => {
|
|
|
65
71
|
}
|
|
66
72
|
`;
|
|
67
73
|
|
|
74
|
+
const localDataSources = Object.fromEntries(
|
|
75
|
+
fixtures.map((f) => [
|
|
76
|
+
f.name,
|
|
77
|
+
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
78
|
+
]),
|
|
79
|
+
);
|
|
80
|
+
|
|
68
81
|
const gateway = new ApolloGateway({
|
|
69
82
|
localServiceList: fixtures,
|
|
70
|
-
buildService
|
|
71
|
-
|
|
72
|
-
return new LocalGraphQLDataSource(buildSubgraphSchema([service]));
|
|
83
|
+
buildService(service) {
|
|
84
|
+
return localDataSources[service.name];
|
|
73
85
|
},
|
|
74
86
|
});
|
|
75
87
|
|
|
@@ -170,11 +182,15 @@ it('does not corrupt cached queryplan data across requests', async () => {
|
|
|
170
182
|
},
|
|
171
183
|
};
|
|
172
184
|
|
|
185
|
+
const dataSources: Record<string, LocalGraphQLDataSource> = {
|
|
186
|
+
a: new LocalGraphQLDataSource(buildSubgraphSchema(serviceA)),
|
|
187
|
+
b: new LocalGraphQLDataSource(buildSubgraphSchema(serviceB)),
|
|
188
|
+
};
|
|
189
|
+
|
|
173
190
|
const gateway = new ApolloGateway({
|
|
174
191
|
localServiceList: [serviceA, serviceB],
|
|
175
|
-
buildService
|
|
176
|
-
|
|
177
|
-
return new LocalGraphQLDataSource(buildSubgraphSchema([service]));
|
|
192
|
+
buildService(service) {
|
|
193
|
+
return dataSources[service.name];
|
|
178
194
|
},
|
|
179
195
|
});
|
|
180
196
|
|
|
@@ -13,6 +13,7 @@ import { Plugin, Config, Refs } from 'pretty-format';
|
|
|
13
13
|
import { Report, Trace } from 'apollo-reporting-protobuf';
|
|
14
14
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
15
15
|
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
16
|
+
import resolvable, { Resolvable } from '@josephg/resolvable';
|
|
16
17
|
|
|
17
18
|
// Normalize specific fields that change often (eg timestamps) to static values,
|
|
18
19
|
// to make snapshot testing viable. (If these helpers are more generally
|
|
@@ -89,19 +90,16 @@ describe('reporting', () => {
|
|
|
89
90
|
let backendServers: ApolloServer[];
|
|
90
91
|
let gatewayServer: ApolloServer;
|
|
91
92
|
let gatewayUrl: string;
|
|
92
|
-
let reportPromise:
|
|
93
|
+
let reportPromise: Resolvable<any>;
|
|
93
94
|
|
|
94
95
|
beforeEach(async () => {
|
|
95
|
-
|
|
96
|
-
reportPromise = new Promise<any>((resolve) => {
|
|
97
|
-
reportResolver = resolve;
|
|
98
|
-
});
|
|
96
|
+
reportPromise = resolvable();
|
|
99
97
|
|
|
100
98
|
nockBeforeEach();
|
|
101
99
|
nock('https://usage-reporting.api.apollographql.com')
|
|
102
100
|
.post('/api/ingress/traces')
|
|
103
101
|
.reply(200, (_: any, requestBody: string) => {
|
|
104
|
-
|
|
102
|
+
reportPromise.resolve(requestBody);
|
|
105
103
|
return 'ok';
|
|
106
104
|
});
|
|
107
105
|
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApolloGateway,
|
|
3
|
+
SubgraphHealthCheckFunction,
|
|
4
|
+
SupergraphSdlUpdateFunction,
|
|
5
|
+
} from '@apollo/gateway';
|
|
6
|
+
import { fixturesWithUpdate } from 'apollo-federation-integration-testsuite';
|
|
7
|
+
import { createHash } from 'apollo-graphql/lib/utilities/createHash';
|
|
8
|
+
import { ApolloServer } from 'apollo-server';
|
|
9
|
+
import { Logger } from 'apollo-server-types';
|
|
10
|
+
import { fetch } from '../../__mocks__/apollo-server-env';
|
|
11
|
+
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
12
|
+
import { mockAllServicesHealthCheckSuccess } from '../integration/nockMocks';
|
|
13
|
+
import resolvable from '@josephg/resolvable';
|
|
14
|
+
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
15
|
+
|
|
16
|
+
async function getSupergraphSdlGatewayServer() {
|
|
17
|
+
const server = new ApolloServer({
|
|
18
|
+
gateway: new ApolloGateway({
|
|
19
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await server.listen({ port: 0 });
|
|
24
|
+
return server;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let logger: Logger;
|
|
28
|
+
let gateway: ApolloGateway | null;
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
nockBeforeEach();
|
|
31
|
+
|
|
32
|
+
logger = {
|
|
33
|
+
debug: jest.fn(),
|
|
34
|
+
info: jest.fn(),
|
|
35
|
+
warn: jest.fn(),
|
|
36
|
+
error: jest.fn(),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
nockAfterEach();
|
|
42
|
+
|
|
43
|
+
if (gateway) {
|
|
44
|
+
await gateway.stop();
|
|
45
|
+
gateway = null;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Using supergraphSdl static configuration', () => {
|
|
50
|
+
it('successfully starts and serves requests to the proper services', async () => {
|
|
51
|
+
const server = await getSupergraphSdlGatewayServer();
|
|
52
|
+
|
|
53
|
+
fetch.mockJSONResponseOnce({
|
|
54
|
+
data: { me: { username: '@jbaxleyiii' } },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const result = await server.executeOperation({
|
|
58
|
+
query: '{ me { username } }',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(result.data).toMatchInlineSnapshot(`
|
|
62
|
+
Object {
|
|
63
|
+
"me": Object {
|
|
64
|
+
"username": "@jbaxleyiii",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
const [url, request] = fetch.mock.calls[0];
|
|
70
|
+
expect(url).toEqual('https://accounts.api.com');
|
|
71
|
+
expect(request?.body).toEqual(
|
|
72
|
+
JSON.stringify({ query: '{me{username}}', variables: {} }),
|
|
73
|
+
);
|
|
74
|
+
await server.stop();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('Using supergraphSdl dynamic configuration', () => {
|
|
79
|
+
it('calls the user provided function after `gateway.load()` is called', async () => {
|
|
80
|
+
const callbackSpy = jest.fn(async () => ({
|
|
81
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
gateway = new ApolloGateway({
|
|
85
|
+
supergraphSdl: callbackSpy,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(callbackSpy).not.toHaveBeenCalled();
|
|
89
|
+
await gateway.load();
|
|
90
|
+
expect(callbackSpy).toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('starts and remains in `initialized` state until `supergraphSdl` Promise resolves', async () => {
|
|
94
|
+
const promiseGuaranteeingWeAreInTheCallback = resolvable();
|
|
95
|
+
const promiseGuaranteeingWeStayInTheCallback = resolvable();
|
|
96
|
+
|
|
97
|
+
gateway = new ApolloGateway({
|
|
98
|
+
async supergraphSdl() {
|
|
99
|
+
promiseGuaranteeingWeAreInTheCallback.resolve();
|
|
100
|
+
await promiseGuaranteeingWeStayInTheCallback;
|
|
101
|
+
return {
|
|
102
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(gateway.__testing().state.phase).toEqual('initialized');
|
|
108
|
+
|
|
109
|
+
const gatewayLoaded = gateway.load();
|
|
110
|
+
await promiseGuaranteeingWeAreInTheCallback;
|
|
111
|
+
expect(gateway.__testing().state.phase).toEqual('initialized');
|
|
112
|
+
|
|
113
|
+
promiseGuaranteeingWeStayInTheCallback.resolve();
|
|
114
|
+
await gatewayLoaded;
|
|
115
|
+
expect(gateway.__testing().state.phase).toEqual('loaded');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('moves from `initialized` to `loaded` state after calling `load()` and after user Promise resolves', async () => {
|
|
119
|
+
const userPromise = resolvable<{ supergraphSdl: string }>();
|
|
120
|
+
|
|
121
|
+
gateway = new ApolloGateway({
|
|
122
|
+
async supergraphSdl() {
|
|
123
|
+
return userPromise;
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const loadPromise = gateway.load();
|
|
128
|
+
expect(gateway.__testing().state.phase).toEqual('initialized');
|
|
129
|
+
|
|
130
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
131
|
+
const expectedCompositionId = createHash('sha256')
|
|
132
|
+
.update(supergraphSdl)
|
|
133
|
+
.digest('hex');
|
|
134
|
+
userPromise.resolve({ supergraphSdl });
|
|
135
|
+
|
|
136
|
+
await loadPromise;
|
|
137
|
+
const { state, compositionId } = gateway.__testing();
|
|
138
|
+
expect(state.phase).toEqual('loaded');
|
|
139
|
+
expect(compositionId).toEqual(expectedCompositionId);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('updates its supergraph after user calls update function', async () => {
|
|
143
|
+
const userPromise = resolvable<{ supergraphSdl: string }>();
|
|
144
|
+
|
|
145
|
+
let userUpdateFn: SupergraphSdlUpdateFunction;
|
|
146
|
+
gateway = new ApolloGateway({
|
|
147
|
+
async supergraphSdl({ update }) {
|
|
148
|
+
userUpdateFn = update;
|
|
149
|
+
return userPromise;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
154
|
+
const expectedId = createHash('sha256').update(supergraphSdl).digest('hex');
|
|
155
|
+
userPromise.resolve({ supergraphSdl: getTestingSupergraphSdl() });
|
|
156
|
+
await gateway.load();
|
|
157
|
+
expect(gateway.__testing().compositionId).toEqual(expectedId);
|
|
158
|
+
|
|
159
|
+
const updatedSupergraphSdl = getTestingSupergraphSdl(fixturesWithUpdate);
|
|
160
|
+
const expectedUpdatedId = createHash('sha256')
|
|
161
|
+
.update(updatedSupergraphSdl)
|
|
162
|
+
.digest('hex');
|
|
163
|
+
|
|
164
|
+
userUpdateFn!(updatedSupergraphSdl);
|
|
165
|
+
expect(gateway.__testing().compositionId).toEqual(expectedUpdatedId);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('calls user-provided `cleanup` function when stopped', async () => {
|
|
169
|
+
const cleanup = jest.fn(() => Promise.resolve());
|
|
170
|
+
gateway = new ApolloGateway({
|
|
171
|
+
async supergraphSdl() {
|
|
172
|
+
return {
|
|
173
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
174
|
+
cleanup,
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await gateway.load();
|
|
180
|
+
const { state, compositionId } = gateway.__testing();
|
|
181
|
+
expect(state.phase).toEqual('loaded');
|
|
182
|
+
expect(compositionId).toEqual(
|
|
183
|
+
'562c22b3382b56b1651944a96e89a361fe847b9b32660eae5ecbd12adc20bf8b',
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await gateway.stop();
|
|
187
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('performs a successful health check on subgraphs', async () => {
|
|
191
|
+
mockAllServicesHealthCheckSuccess();
|
|
192
|
+
|
|
193
|
+
let healthCheckCallback: SubgraphHealthCheckFunction;
|
|
194
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
195
|
+
gateway = new ApolloGateway({
|
|
196
|
+
async supergraphSdl({ healthCheck }) {
|
|
197
|
+
healthCheckCallback = healthCheck;
|
|
198
|
+
return {
|
|
199
|
+
supergraphSdl,
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await gateway.load();
|
|
205
|
+
const { state, compositionId } = gateway.__testing();
|
|
206
|
+
expect(state.phase).toEqual('loaded');
|
|
207
|
+
expect(compositionId).toEqual(
|
|
208
|
+
'562c22b3382b56b1651944a96e89a361fe847b9b32660eae5ecbd12adc20bf8b',
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
await expect(healthCheckCallback!(supergraphSdl)).resolves.toBeUndefined();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('calls `initialize` on an object provided to `supergraphSdl`', async () => {
|
|
215
|
+
const MockSdlUpdatingClass = {
|
|
216
|
+
initialize() {
|
|
217
|
+
return Promise.resolve({
|
|
218
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const initializeSpy = jest.spyOn(MockSdlUpdatingClass, 'initialize');
|
|
223
|
+
|
|
224
|
+
gateway = new ApolloGateway({
|
|
225
|
+
supergraphSdl: MockSdlUpdatingClass,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(initializeSpy).not.toHaveBeenCalled();
|
|
229
|
+
await gateway.load();
|
|
230
|
+
expect(initializeSpy).toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('errors', () => {
|
|
234
|
+
it('fails to load if `SupergraphManager` throws on initialization', async () => {
|
|
235
|
+
const failureMessage = 'Error from supergraphSdl function';
|
|
236
|
+
gateway = new ApolloGateway({
|
|
237
|
+
async supergraphSdl() {
|
|
238
|
+
throw new Error(failureMessage);
|
|
239
|
+
},
|
|
240
|
+
logger,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await expect(gateway.load()).rejects.toThrowError(failureMessage);
|
|
244
|
+
|
|
245
|
+
expect(gateway.__testing().state.phase).toEqual('failed to load');
|
|
246
|
+
// we don't want the `afterEach` to call `gateway.stop()` in this case
|
|
247
|
+
// since it would throw an error due to the gateway's failed to load state
|
|
248
|
+
gateway = null;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('gracefully handles Promise rejections from user `cleanup` function', async () => {
|
|
252
|
+
const rejectionMessage = 'thrown from cleanup function';
|
|
253
|
+
const cleanup = jest.fn(() => Promise.reject(rejectionMessage));
|
|
254
|
+
gateway = new ApolloGateway({
|
|
255
|
+
async supergraphSdl() {
|
|
256
|
+
return {
|
|
257
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
258
|
+
cleanup,
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
logger,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await gateway.load();
|
|
265
|
+
await expect(gateway.stop()).resolves.toBeUndefined();
|
|
266
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
267
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
268
|
+
'Error occured while calling user provided `cleanup` function: ' +
|
|
269
|
+
rejectionMessage,
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('throws an error when `healthCheck` rejects', async () => {
|
|
274
|
+
// no mocks, so nock will reject
|
|
275
|
+
let healthCheckCallback: SubgraphHealthCheckFunction;
|
|
276
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
277
|
+
gateway = new ApolloGateway({
|
|
278
|
+
async supergraphSdl({ healthCheck }) {
|
|
279
|
+
healthCheckCallback = healthCheck;
|
|
280
|
+
return {
|
|
281
|
+
supergraphSdl,
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await gateway.load();
|
|
287
|
+
const { state, compositionId } = gateway.__testing();
|
|
288
|
+
expect(state.phase).toEqual('loaded');
|
|
289
|
+
expect(compositionId).toEqual(
|
|
290
|
+
'562c22b3382b56b1651944a96e89a361fe847b9b32660eae5ecbd12adc20bf8b',
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
await expect(healthCheckCallback!(supergraphSdl)).rejects.toThrowError(
|
|
294
|
+
/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/,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('throws an error when `update` is called after gateway fails to load', async () => {
|
|
299
|
+
let updateCallback: SupergraphSdlUpdateFunction;
|
|
300
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
301
|
+
gateway = new ApolloGateway({
|
|
302
|
+
async supergraphSdl({ update }) {
|
|
303
|
+
updateCallback = update;
|
|
304
|
+
return {
|
|
305
|
+
supergraphSdl: 'invalid SDL',
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
await gateway.load();
|
|
312
|
+
} catch {}
|
|
313
|
+
|
|
314
|
+
expect(() =>
|
|
315
|
+
updateCallback!(supergraphSdl),
|
|
316
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
317
|
+
`"Can't call \`update\` callback after gateway failed to load."`,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// gateway failed to load, so we don't want the `afterEach` to call `gateway.stop()`
|
|
321
|
+
gateway = null;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('throws an error when `update` is called while an update is in progress', async () => {
|
|
325
|
+
let updateCallback: SupergraphSdlUpdateFunction;
|
|
326
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
327
|
+
gateway = new ApolloGateway({
|
|
328
|
+
async supergraphSdl({ update }) {
|
|
329
|
+
updateCallback = update;
|
|
330
|
+
return {
|
|
331
|
+
supergraphSdl,
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
experimental_didUpdateSupergraph() {
|
|
335
|
+
updateCallback(getTestingSupergraphSdl(fixturesWithUpdate));
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await expect(gateway.load()).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
340
|
+
`"Can't call \`update\` callback while supergraph update is in progress."`,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// gateway failed to load, so we don't want the `afterEach` to call `gateway.stop()`
|
|
344
|
+
gateway = null;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('throws an error when `update` is called after gateway is stopped', async () => {
|
|
348
|
+
let updateCallback: SupergraphSdlUpdateFunction;
|
|
349
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
350
|
+
gateway = new ApolloGateway({
|
|
351
|
+
async supergraphSdl({ update }) {
|
|
352
|
+
updateCallback = update;
|
|
353
|
+
return {
|
|
354
|
+
supergraphSdl,
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await gateway.load();
|
|
360
|
+
await gateway.stop();
|
|
361
|
+
|
|
362
|
+
expect(() =>
|
|
363
|
+
updateCallback!(getTestingSupergraphSdl(fixturesWithUpdate)),
|
|
364
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
365
|
+
`"Can't call \`update\` callback after gateway has been stopped."`,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('throws an error when `update` is called with an invalid supergraph', async () => {
|
|
370
|
+
let updateCallback: SupergraphSdlUpdateFunction;
|
|
371
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
372
|
+
gateway = new ApolloGateway({
|
|
373
|
+
async supergraphSdl({ update }) {
|
|
374
|
+
updateCallback = update;
|
|
375
|
+
return {
|
|
376
|
+
supergraphSdl,
|
|
377
|
+
};
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
await gateway.load();
|
|
382
|
+
|
|
383
|
+
expect(() =>
|
|
384
|
+
updateCallback!('invalid SDL'),
|
|
385
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
386
|
+
`"Syntax Error: Unexpected Name \\"invalid\\"."`,
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
});
|
|
@@ -141,11 +141,17 @@ it('supports aliases of nested fields on subservices', async () => {
|
|
|
141
141
|
|
|
142
142
|
// TODO after we remove GraphQLExtensions from ApolloServer, this can go away
|
|
143
143
|
it('supports aliases when using ApolloServer', async () => {
|
|
144
|
+
const localDataSources = Object.fromEntries(
|
|
145
|
+
fixtures.map((f) => [
|
|
146
|
+
f.name,
|
|
147
|
+
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
148
|
+
]),
|
|
149
|
+
);
|
|
150
|
+
|
|
144
151
|
const gateway = new ApolloGateway({
|
|
145
152
|
localServiceList: fixtures,
|
|
146
|
-
buildService
|
|
147
|
-
|
|
148
|
-
return new LocalGraphQLDataSource(buildSubgraphSchema([service]));
|
|
153
|
+
buildService(service) {
|
|
154
|
+
return localDataSources[service.name];
|
|
149
155
|
},
|
|
150
156
|
});
|
|
151
157
|
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
mockCloudConfigUrl3,
|
|
13
13
|
} from './nockMocks';
|
|
14
14
|
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
15
|
-
import {
|
|
15
|
+
import { fixtures, Fixture } from 'apollo-federation-integration-testsuite';
|
|
16
16
|
|
|
17
17
|
let logger: Logger;
|
|
18
18
|
|
|
19
|
-
const service:
|
|
19
|
+
const service: Fixture = {
|
|
20
20
|
name: 'accounts',
|
|
21
21
|
url: 'http://localhost:4001',
|
|
22
22
|
typeDefs: gql`
|
|
@@ -326,14 +326,17 @@ describe('gateway config / env behavior', () => {
|
|
|
326
326
|
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: 'env-config',
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
const config = {
|
|
330
330
|
logger,
|
|
331
331
|
uplinkEndpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2, mockCloudConfigUrl3],
|
|
332
|
-
}
|
|
332
|
+
};
|
|
333
|
+
gateway = new ApolloGateway(config);
|
|
333
334
|
|
|
334
|
-
expect(gateway['
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
expect(gateway['getUplinkEndpoints'](config)).toEqual([
|
|
336
|
+
mockCloudConfigUrl1,
|
|
337
|
+
mockCloudConfigUrl2,
|
|
338
|
+
mockCloudConfigUrl3,
|
|
339
|
+
]);
|
|
337
340
|
|
|
338
341
|
gateway = null;
|
|
339
342
|
});
|
|
@@ -345,16 +348,110 @@ describe('gateway config / env behavior', () => {
|
|
|
345
348
|
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: 'env-config',
|
|
346
349
|
});
|
|
347
350
|
|
|
348
|
-
|
|
351
|
+
const config = {
|
|
349
352
|
logger,
|
|
350
353
|
schemaConfigDeliveryEndpoint: 'code-config',
|
|
351
|
-
}
|
|
354
|
+
};
|
|
355
|
+
gateway = new ApolloGateway(config);
|
|
352
356
|
|
|
353
|
-
expect(gateway['
|
|
354
|
-
['code-config'],
|
|
355
|
-
);
|
|
357
|
+
expect(gateway['getUplinkEndpoints'](config)).toEqual(['code-config']);
|
|
356
358
|
|
|
357
359
|
gateway = null;
|
|
358
360
|
});
|
|
359
361
|
});
|
|
360
362
|
});
|
|
363
|
+
|
|
364
|
+
describe('deprecation warnings', () => {
|
|
365
|
+
it('warns with `experimental_updateSupergraphSdl` option set', async () => {
|
|
366
|
+
const gateway = new ApolloGateway({
|
|
367
|
+
async experimental_updateSupergraphSdl() {
|
|
368
|
+
return {
|
|
369
|
+
id: 'supergraph',
|
|
370
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
logger,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await gateway.load();
|
|
377
|
+
|
|
378
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
379
|
+
'The `experimental_updateSupergraphSdl` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the function form of the `supergraphSdl` configuration option.',
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
await gateway.stop();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('warns with `experimental_updateServiceDefinitions` option set', async () => {
|
|
386
|
+
const gateway = new ApolloGateway({
|
|
387
|
+
async experimental_updateServiceDefinitions() {
|
|
388
|
+
return {
|
|
389
|
+
isNewSchema: false,
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
logger,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
await gateway.load();
|
|
397
|
+
// gateway will throw since we're not providing an actual service list, disregard
|
|
398
|
+
} catch {}
|
|
399
|
+
|
|
400
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
401
|
+
'The `experimental_updateServiceDefinitions` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the function form of the `supergraphSdl` configuration option.',
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('warns with `serviceList` option set', async () => {
|
|
406
|
+
const gateway = new ApolloGateway({
|
|
407
|
+
serviceList: [{ name: 'accounts', url: 'http://localhost:4001' }],
|
|
408
|
+
logger,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
await gateway.load();
|
|
413
|
+
// gateway will throw since we haven't mocked these requests, unimportant for this test
|
|
414
|
+
} catch {}
|
|
415
|
+
|
|
416
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
417
|
+
'The `serviceList` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to its replacement `IntrospectAndCompose`. More information on `IntrospectAndCompose` can be found in the documentation.',
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('warns with `localServiceList` option set', async () => {
|
|
422
|
+
const gateway = new ApolloGateway({
|
|
423
|
+
localServiceList: fixtures,
|
|
424
|
+
logger,
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
await gateway.load();
|
|
428
|
+
|
|
429
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
430
|
+
'The `localServiceList` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the `LocalCompose` supergraph manager exported by `@apollo/gateway`.',
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
await gateway.stop();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('warns with `schemaConfigDeliveryEndpoint` option set', async () => {
|
|
437
|
+
new ApolloGateway({
|
|
438
|
+
schemaConfigDeliveryEndpoint: 'test',
|
|
439
|
+
logger,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
443
|
+
'The `schemaConfigDeliveryEndpoint` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent (array form) `uplinkEndpoints` configuration option.',
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('warns with `experimental_pollInterval` option set', async () => {
|
|
448
|
+
new ApolloGateway({
|
|
449
|
+
experimental_pollInterval: 10000,
|
|
450
|
+
logger,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
expect(logger.warn).toHaveBeenCalledWith(
|
|
454
|
+
'The `experimental_pollInterval` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent `pollIntervalInMs` configuration option.',
|
|
455
|
+
);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
@@ -10,7 +10,7 @@ import * as log4js from "log4js";
|
|
|
10
10
|
|
|
11
11
|
const LOWEST_LOG_LEVEL = "debug";
|
|
12
12
|
|
|
13
|
-
const KNOWN_DEBUG_MESSAGE = "
|
|
13
|
+
const KNOWN_DEBUG_MESSAGE = "Gateway successfully initialized (but not yet loaded)";
|
|
14
14
|
|
|
15
15
|
async function triggerKnownDebugMessage(logger: Logger) {
|
|
16
16
|
// Trigger a known error.
|