@apollo/gateway 2.4.5 → 2.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__generated__/graphqlTypes.d.ts +19 -1
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js +1 -0
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/executeQueryPlan.js +1 -1
- package/dist/executeQueryPlan.js.map +1 -1
- package/package.json +4 -4
- package/src/__generated__/graphqlTypes.ts +33 -2
- package/src/executeQueryPlan.ts +1 -1
- package/src/__mocks__/tsconfig.json +0 -7
- package/src/__tests__/.gitkeep +0 -0
- package/src/__tests__/CucumberREADME.md +0 -96
- package/src/__tests__/build-query-plan.feature +0 -1471
- package/src/__tests__/buildQueryPlan.test.ts +0 -1225
- package/src/__tests__/executeQueryPlan.conditions.test.ts +0 -1488
- package/src/__tests__/executeQueryPlan.introspection.test.ts +0 -140
- package/src/__tests__/executeQueryPlan.test.ts +0 -6140
- package/src/__tests__/execution-utils.ts +0 -124
- package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +0 -195
- package/src/__tests__/gateway/buildService.test.ts +0 -249
- package/src/__tests__/gateway/endToEnd.test.ts +0 -486
- package/src/__tests__/gateway/executor.test.ts +0 -96
- package/src/__tests__/gateway/extensions.test.ts +0 -37
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +0 -239
- package/src/__tests__/gateway/opentelemetry.test.ts +0 -123
- package/src/__tests__/gateway/queryPlanCache.test.ts +0 -231
- package/src/__tests__/gateway/queryPlannerConfig.test.ts +0 -101
- package/src/__tests__/gateway/reporting.test.ts +0 -616
- package/src/__tests__/gateway/supergraphSdl.test.ts +0 -396
- package/src/__tests__/gateway/testUtils.ts +0 -89
- package/src/__tests__/integration/abstract-types.test.ts +0 -1861
- package/src/__tests__/integration/aliases.test.ts +0 -180
- package/src/__tests__/integration/boolean.test.ts +0 -279
- package/src/__tests__/integration/complex-key.test.ts +0 -197
- package/src/__tests__/integration/configuration.test.ts +0 -404
- package/src/__tests__/integration/custom-directives.test.ts +0 -174
- package/src/__tests__/integration/execution-style.test.ts +0 -35
- package/src/__tests__/integration/fragments.test.ts +0 -237
- package/src/__tests__/integration/list-key.test.ts +0 -128
- package/src/__tests__/integration/logger.test.ts +0 -122
- package/src/__tests__/integration/managed.test.ts +0 -319
- package/src/__tests__/integration/merge-arrays.test.ts +0 -34
- package/src/__tests__/integration/multiple-key.test.ts +0 -327
- package/src/__tests__/integration/mutations.test.ts +0 -287
- package/src/__tests__/integration/networkRequests.test.ts +0 -542
- package/src/__tests__/integration/nockMocks.ts +0 -157
- package/src/__tests__/integration/provides.test.ts +0 -77
- package/src/__tests__/integration/requires.test.ts +0 -359
- package/src/__tests__/integration/scope.test.ts +0 -557
- package/src/__tests__/integration/single-service.test.ts +0 -119
- package/src/__tests__/integration/unions.test.ts +0 -79
- package/src/__tests__/integration/value-types.test.ts +0 -382
- package/src/__tests__/integration/variables.test.ts +0 -120
- package/src/__tests__/nockAssertions.ts +0 -20
- package/src/__tests__/queryPlanCucumber.test.ts +0 -55
- package/src/__tests__/resultShaping.test.ts +0 -605
- package/src/__tests__/testSetup.ts +0 -1
- package/src/__tests__/tsconfig.json +0 -8
- package/src/core/__tests__/core.test.ts +0 -412
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +0 -51
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +0 -574
- package/src/schema-helper/__tests__/addExtensions.test.ts +0 -70
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +0 -364
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/loadServicesFromRemoteEndpoint.test.ts +0 -40
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +0 -65
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -511
- package/src/utilities/__tests__/deepMerge.test.ts +0 -77
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import { ApolloGateway } from '../..';
|
|
3
|
-
import {
|
|
4
|
-
DynamicGatewayConfig,
|
|
5
|
-
Experimental_DidResolveQueryPlanCallback,
|
|
6
|
-
Experimental_UpdateServiceDefinitions,
|
|
7
|
-
ServiceDefinitionUpdate,
|
|
8
|
-
} from '../../config';
|
|
9
|
-
import {
|
|
10
|
-
product,
|
|
11
|
-
reviews,
|
|
12
|
-
inventory,
|
|
13
|
-
accounts,
|
|
14
|
-
books,
|
|
15
|
-
documents,
|
|
16
|
-
fixtures,
|
|
17
|
-
fixturesWithUpdate,
|
|
18
|
-
} from 'apollo-federation-integration-testsuite';
|
|
19
|
-
import { createHash } from '@apollo/utils.createhash';
|
|
20
|
-
import type { Logger } from '@apollo/utils.logger';
|
|
21
|
-
import resolvable from '@josephg/resolvable';
|
|
22
|
-
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
23
|
-
|
|
24
|
-
// The order of this was specified to preserve existing test coverage. Typically
|
|
25
|
-
// we would just import and use the `fixtures` array.
|
|
26
|
-
const serviceDefinitions = [
|
|
27
|
-
product,
|
|
28
|
-
reviews,
|
|
29
|
-
inventory,
|
|
30
|
-
accounts,
|
|
31
|
-
books,
|
|
32
|
-
documents,
|
|
33
|
-
].map((s, i) => ({
|
|
34
|
-
name: s.name,
|
|
35
|
-
typeDefs: s.typeDefs,
|
|
36
|
-
url: `http://localhost:${i}`,
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
let logger: Logger;
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
const warn = jest.fn();
|
|
43
|
-
const debug = jest.fn();
|
|
44
|
-
const error = jest.fn();
|
|
45
|
-
const info = jest.fn();
|
|
46
|
-
|
|
47
|
-
logger = {
|
|
48
|
-
warn,
|
|
49
|
-
debug,
|
|
50
|
-
error,
|
|
51
|
-
info,
|
|
52
|
-
};
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('lifecycle hooks', () => {
|
|
56
|
-
it('uses updateServiceDefinitions override', async () => {
|
|
57
|
-
const experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions =
|
|
58
|
-
jest.fn(async () => {
|
|
59
|
-
return { serviceDefinitions, isNewSchema: true };
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const gateway = new ApolloGateway({
|
|
63
|
-
serviceList: serviceDefinitions,
|
|
64
|
-
experimental_updateServiceDefinitions,
|
|
65
|
-
logger,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await gateway.load();
|
|
69
|
-
|
|
70
|
-
expect(experimental_updateServiceDefinitions).toBeCalled();
|
|
71
|
-
expect(gateway.schema!.getType('Furniture')).toBeDefined();
|
|
72
|
-
await gateway.stop();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('calls experimental_didUpdateSupergraph on schema update', async () => {
|
|
76
|
-
const compositionMetadata = {
|
|
77
|
-
formatVersion: 1,
|
|
78
|
-
id: 'abc',
|
|
79
|
-
implementingServiceLocations: [],
|
|
80
|
-
schemaHash: 'hash1',
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const mockUpdate = jest
|
|
84
|
-
.fn<Promise<ServiceDefinitionUpdate>, [config: DynamicGatewayConfig]>()
|
|
85
|
-
.mockImplementationOnce(async () => {
|
|
86
|
-
return {
|
|
87
|
-
serviceDefinitions: fixtures,
|
|
88
|
-
isNewSchema: true,
|
|
89
|
-
compositionMetadata: {
|
|
90
|
-
...compositionMetadata,
|
|
91
|
-
id: '123',
|
|
92
|
-
schemaHash: 'hash2',
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
})
|
|
96
|
-
// We want to return a different composition across two ticks, so we mock it
|
|
97
|
-
// slightly differently
|
|
98
|
-
.mockImplementationOnce(async () => {
|
|
99
|
-
return {
|
|
100
|
-
serviceDefinitions: fixturesWithUpdate,
|
|
101
|
-
isNewSchema: true,
|
|
102
|
-
compositionMetadata,
|
|
103
|
-
};
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const mockDidUpdate = jest.fn();
|
|
107
|
-
|
|
108
|
-
const gateway = new ApolloGateway({
|
|
109
|
-
experimental_updateServiceDefinitions: mockUpdate,
|
|
110
|
-
experimental_didUpdateSupergraph: mockDidUpdate,
|
|
111
|
-
logger,
|
|
112
|
-
});
|
|
113
|
-
// for testing purposes, a short pollInterval is ideal so we'll override here
|
|
114
|
-
gateway['pollIntervalInMs'] = 100;
|
|
115
|
-
|
|
116
|
-
const schemaChangeBlocker1 = resolvable();
|
|
117
|
-
const schemaChangeBlocker2 = resolvable();
|
|
118
|
-
|
|
119
|
-
gateway.onSchemaLoadOrUpdate(
|
|
120
|
-
jest
|
|
121
|
-
.fn()
|
|
122
|
-
.mockImplementationOnce(() => schemaChangeBlocker1.resolve())
|
|
123
|
-
.mockImplementationOnce(() => schemaChangeBlocker2.resolve()),
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
await gateway.load();
|
|
127
|
-
|
|
128
|
-
await schemaChangeBlocker1;
|
|
129
|
-
|
|
130
|
-
expect(mockUpdate).toBeCalledTimes(1);
|
|
131
|
-
expect(mockDidUpdate).toBeCalledTimes(1);
|
|
132
|
-
|
|
133
|
-
await schemaChangeBlocker2;
|
|
134
|
-
|
|
135
|
-
expect(mockUpdate).toBeCalledTimes(2);
|
|
136
|
-
expect(mockDidUpdate).toBeCalledTimes(2);
|
|
137
|
-
|
|
138
|
-
const [firstCall, secondCall] = mockDidUpdate.mock.calls;
|
|
139
|
-
|
|
140
|
-
// Note that we've composing our usual test fixtures here
|
|
141
|
-
const expectedFirstId = createHash('sha256').update(getTestingSupergraphSdl()).digest('hex');
|
|
142
|
-
expect(firstCall[0]!.compositionId).toEqual(expectedFirstId);
|
|
143
|
-
// first call should have no second "previous" argument
|
|
144
|
-
expect(firstCall[1]).toBeUndefined();
|
|
145
|
-
|
|
146
|
-
// Note that this assertion is a tad fragile in that every time we modify
|
|
147
|
-
// the supergraph (even just formatting differences), this ID will change
|
|
148
|
-
// and this test will have to updated.
|
|
149
|
-
expect(secondCall[0]!.compositionId).toEqual(
|
|
150
|
-
'4644102bb30ec7e254fac2577f78137bbab156cb965973ae481f309120738ff6',
|
|
151
|
-
);
|
|
152
|
-
// second call should have previous info in the second arg
|
|
153
|
-
expect(secondCall[1]!.compositionId).toEqual(expectedFirstId);
|
|
154
|
-
|
|
155
|
-
await gateway.stop();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('uses default service definition updater', async () => {
|
|
159
|
-
const gateway = new ApolloGateway({
|
|
160
|
-
localServiceList: serviceDefinitions,
|
|
161
|
-
logger,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const { schema } = await gateway.load();
|
|
165
|
-
|
|
166
|
-
// spying on gateway.loadServiceDefinitions wasn't working, so this also
|
|
167
|
-
// should test functionality. If there's no overwriting service definition
|
|
168
|
-
// updater, it has to use the default. If there's a valid schema, then
|
|
169
|
-
// the loader had to have been called.
|
|
170
|
-
expect(schema.getType('User')).toBeDefined();
|
|
171
|
-
|
|
172
|
-
await gateway.stop();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('warns when polling on the default fetcher', async () => {
|
|
176
|
-
new ApolloGateway({
|
|
177
|
-
serviceList: serviceDefinitions,
|
|
178
|
-
pollIntervalInMs: 10,
|
|
179
|
-
logger,
|
|
180
|
-
});
|
|
181
|
-
expect(logger.warn).toHaveBeenCalledWith(
|
|
182
|
-
'Polling running services is dangerous and not recommended in production. Polling should only be used against a registry. If you are polling running services, use with caution.',
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('registers schema change callbacks when pollIntervalInMs is set for unmanaged configs', async () => {
|
|
187
|
-
const experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions =
|
|
188
|
-
jest.fn(async (_config) => {
|
|
189
|
-
return { serviceDefinitions, isNewSchema: true };
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const gateway = new ApolloGateway({
|
|
193
|
-
serviceList: [{ name: 'book', url: 'http://localhost:32542' }],
|
|
194
|
-
experimental_updateServiceDefinitions,
|
|
195
|
-
pollIntervalInMs: 100,
|
|
196
|
-
logger,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const schemaChangeBlocker = resolvable();
|
|
200
|
-
const schemaChangeCallback = jest.fn(() => schemaChangeBlocker.resolve());
|
|
201
|
-
|
|
202
|
-
gateway.onSchemaLoadOrUpdate(schemaChangeCallback);
|
|
203
|
-
await gateway.load();
|
|
204
|
-
|
|
205
|
-
await schemaChangeBlocker;
|
|
206
|
-
|
|
207
|
-
expect(schemaChangeCallback).toBeCalledTimes(1);
|
|
208
|
-
await gateway.stop();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('calls experimental_didResolveQueryPlan when executor is called', async () => {
|
|
212
|
-
const experimental_didResolveQueryPlan: Experimental_DidResolveQueryPlanCallback =
|
|
213
|
-
jest.fn();
|
|
214
|
-
|
|
215
|
-
const gateway = new ApolloGateway({
|
|
216
|
-
localServiceList: [books],
|
|
217
|
-
experimental_didResolveQueryPlan,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const { executor } = await gateway.load();
|
|
221
|
-
|
|
222
|
-
const source = `#graphql
|
|
223
|
-
{ book(isbn: "0262510871") { year } }
|
|
224
|
-
`;
|
|
225
|
-
|
|
226
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
227
|
-
// @ts-ignore
|
|
228
|
-
await executor({
|
|
229
|
-
source,
|
|
230
|
-
document: gql(source),
|
|
231
|
-
request: {},
|
|
232
|
-
queryHash: 'hashed',
|
|
233
|
-
context: {},
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
expect(experimental_didResolveQueryPlan).toBeCalled();
|
|
237
|
-
await gateway.stop();
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import {ApolloGateway, LocalGraphQLDataSource} from '../../';
|
|
3
|
-
import {fixtures, spanSerializer} from 'apollo-federation-integration-testsuite';
|
|
4
|
-
import {InMemorySpanExporter, SimpleSpanProcessor} from '@opentelemetry/tracing'
|
|
5
|
-
import {NodeTracerProvider} from '@opentelemetry/node';
|
|
6
|
-
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
7
|
-
|
|
8
|
-
expect.addSnapshotSerializer(spanSerializer);
|
|
9
|
-
|
|
10
|
-
const inMemorySpans = new InMemorySpanExporter();
|
|
11
|
-
const tracerProvider = new NodeTracerProvider();
|
|
12
|
-
tracerProvider.addSpanProcessor(new SimpleSpanProcessor(inMemorySpans));
|
|
13
|
-
tracerProvider.register();
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
inMemorySpans.reset();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('opentelemetry', () => {
|
|
20
|
-
async function execute(executor: any, source: string, variables: any, operationName: string) {
|
|
21
|
-
await executor({
|
|
22
|
-
source,
|
|
23
|
-
document: gql(source),
|
|
24
|
-
request: {
|
|
25
|
-
variables,
|
|
26
|
-
},
|
|
27
|
-
operationName,
|
|
28
|
-
queryHash: 'hashed',
|
|
29
|
-
context: null,
|
|
30
|
-
cache: {} as any,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('with local data', () =>
|
|
35
|
-
{
|
|
36
|
-
async function gateway() {
|
|
37
|
-
const localDataSources = Object.fromEntries(
|
|
38
|
-
fixtures.map((f) => [
|
|
39
|
-
f.name,
|
|
40
|
-
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
41
|
-
]),
|
|
42
|
-
);
|
|
43
|
-
const gateway = new ApolloGateway({
|
|
44
|
-
localServiceList: fixtures,
|
|
45
|
-
buildService(service) {
|
|
46
|
-
return localDataSources[service.name];
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const {executor} = await gateway.load();
|
|
51
|
-
return executor;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
it('receives spans on success', async () => {
|
|
55
|
-
const executor = await gateway();
|
|
56
|
-
|
|
57
|
-
const source = `#graphql
|
|
58
|
-
query GetProduct($upc: String!) {
|
|
59
|
-
product(upc: $upc) {
|
|
60
|
-
name
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
`;
|
|
64
|
-
|
|
65
|
-
await execute(executor, source, {upc: '1'}, 'GetProduct');
|
|
66
|
-
expect(inMemorySpans.getFinishedSpans()).toMatchSnapshot();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('receives spans on validation failure', async () => {
|
|
70
|
-
const executor = await gateway();
|
|
71
|
-
const source = `#graphql
|
|
72
|
-
query InvalidVariables($first: Int!) {
|
|
73
|
-
topReviews(first: $first) {
|
|
74
|
-
body
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
|
-
await execute(executor, source, { upc: '1' }, 'InvalidVariables');
|
|
80
|
-
expect(inMemorySpans.getFinishedSpans()).toMatchSnapshot();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('receives spans on plan failure', async () => {
|
|
84
|
-
const executor = await gateway();
|
|
85
|
-
const source = `#graphql
|
|
86
|
-
subscription GetProduct($upc: String!) {
|
|
87
|
-
product(upc: $upc) {
|
|
88
|
-
name
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
await execute(executor, source, {upc: '1'}, 'GetProduct');
|
|
95
|
-
}
|
|
96
|
-
catch(err) {}
|
|
97
|
-
expect(inMemorySpans.getFinishedSpans()).toMatchSnapshot();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
it('receives spans on fetch failure', async () => {
|
|
103
|
-
const gateway = new ApolloGateway({
|
|
104
|
-
localServiceList: fixtures,
|
|
105
|
-
fetcher: () => {
|
|
106
|
-
throw Error('Nooo');
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const { executor } = await gateway.load();
|
|
111
|
-
|
|
112
|
-
const source = `#graphql
|
|
113
|
-
query GetProduct($upc: String!) {
|
|
114
|
-
product(upc: $upc) {
|
|
115
|
-
name
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
`;
|
|
119
|
-
|
|
120
|
-
await execute(executor, source, {upc: '1'}, 'GetProduct');
|
|
121
|
-
expect(inMemorySpans.getFinishedSpans()).toMatchSnapshot();
|
|
122
|
-
});
|
|
123
|
-
});
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import { ApolloServer } from '@apollo/server';
|
|
3
|
-
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
4
|
-
|
|
5
|
-
import { LocalGraphQLDataSource } from '../../datasources/LocalGraphQLDataSource';
|
|
6
|
-
import { ApolloGateway } from '../../';
|
|
7
|
-
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
8
|
-
import { QueryPlanner } from '@apollo/query-planner';
|
|
9
|
-
import { unwrapSingleResultKind } from '../gateway/testUtils';
|
|
10
|
-
|
|
11
|
-
it('caches the query plan for a request', async () => {
|
|
12
|
-
const buildQueryPlanSpy = jest.spyOn(QueryPlanner.prototype, 'buildQueryPlan');
|
|
13
|
-
|
|
14
|
-
const localDataSources = Object.fromEntries(
|
|
15
|
-
fixtures.map((f) => [
|
|
16
|
-
f.name,
|
|
17
|
-
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
18
|
-
]),
|
|
19
|
-
);
|
|
20
|
-
const gateway = new ApolloGateway({
|
|
21
|
-
localServiceList: fixtures,
|
|
22
|
-
buildService(service) {
|
|
23
|
-
return localDataSources[service.name];
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const server = new ApolloServer({ gateway });
|
|
28
|
-
await server.start();
|
|
29
|
-
|
|
30
|
-
const upc = '1';
|
|
31
|
-
|
|
32
|
-
const query = `#graphql
|
|
33
|
-
query GetProduct($upc: String!) {
|
|
34
|
-
product(upc: $upc) {
|
|
35
|
-
name
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
`;
|
|
39
|
-
|
|
40
|
-
const result = await server.executeOperation({
|
|
41
|
-
query,
|
|
42
|
-
variables: { upc },
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const { data: result1Data } = unwrapSingleResultKind(result);
|
|
46
|
-
expect(result1Data).toEqual({
|
|
47
|
-
product: {
|
|
48
|
-
name: 'Table',
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const secondResult = await server.executeOperation({
|
|
53
|
-
query,
|
|
54
|
-
variables: { upc },
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
expect(unwrapSingleResultKind(secondResult).data).toEqual(result1Data);
|
|
58
|
-
expect(buildQueryPlanSpy).toHaveBeenCalledTimes(1);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('supports multiple operations and operationName', async () => {
|
|
62
|
-
const query = `#graphql
|
|
63
|
-
query GetUser {
|
|
64
|
-
me {
|
|
65
|
-
username
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
query GetReviews {
|
|
69
|
-
topReviews {
|
|
70
|
-
body
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
`;
|
|
74
|
-
|
|
75
|
-
const localDataSources = Object.fromEntries(
|
|
76
|
-
fixtures.map((f) => [
|
|
77
|
-
f.name,
|
|
78
|
-
new LocalGraphQLDataSource(buildSubgraphSchema(f)),
|
|
79
|
-
]),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const gateway = new ApolloGateway({
|
|
83
|
-
localServiceList: fixtures,
|
|
84
|
-
buildService(service) {
|
|
85
|
-
return localDataSources[service.name];
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const server = new ApolloServer({ gateway });
|
|
90
|
-
await server.start();
|
|
91
|
-
|
|
92
|
-
const userResult = await server.executeOperation({
|
|
93
|
-
query,
|
|
94
|
-
operationName: 'GetUser',
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const reviewsResult = await server.executeOperation({
|
|
98
|
-
query,
|
|
99
|
-
operationName: 'GetReviews',
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(unwrapSingleResultKind(userResult).data).toEqual({
|
|
103
|
-
me: { username: '@ada' },
|
|
104
|
-
});
|
|
105
|
-
expect(unwrapSingleResultKind(reviewsResult).data).toEqual({
|
|
106
|
-
topReviews: [
|
|
107
|
-
{ body: 'Love it!' },
|
|
108
|
-
{ body: 'Too expensive.' },
|
|
109
|
-
{ body: 'Could be better.' },
|
|
110
|
-
{ body: 'Prefer something else.' },
|
|
111
|
-
{ body: 'Wish I had read this before.' },
|
|
112
|
-
],
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('does not corrupt cached queryplan data across requests', async () => {
|
|
117
|
-
const serviceA = {
|
|
118
|
-
name: 'a',
|
|
119
|
-
typeDefs: gql`
|
|
120
|
-
type Query {
|
|
121
|
-
user: User
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
type User @key(fields: "id") {
|
|
125
|
-
id: ID!
|
|
126
|
-
preferences: Preferences
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
type Preferences {
|
|
130
|
-
favorites: Things
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
type Things {
|
|
134
|
-
color: String
|
|
135
|
-
animal: String
|
|
136
|
-
}
|
|
137
|
-
`,
|
|
138
|
-
resolvers: {
|
|
139
|
-
Query: {
|
|
140
|
-
user() {
|
|
141
|
-
return {
|
|
142
|
-
id: '1',
|
|
143
|
-
preferences: {
|
|
144
|
-
favorites: { color: 'limegreen', animal: 'platypus' },
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const serviceB = {
|
|
153
|
-
name: 'b',
|
|
154
|
-
typeDefs: gql`
|
|
155
|
-
extend type User @key(fields: "id") {
|
|
156
|
-
id: ID! @external
|
|
157
|
-
preferences: Preferences @external
|
|
158
|
-
favoriteColor: String
|
|
159
|
-
@requires(fields: "preferences { favorites { color } }")
|
|
160
|
-
favoriteAnimal: String
|
|
161
|
-
@requires(fields: "preferences { favorites { animal } }")
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
extend type Preferences {
|
|
165
|
-
favorites: Things @external
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
extend type Things {
|
|
169
|
-
color: String @external
|
|
170
|
-
animal: String @external
|
|
171
|
-
}
|
|
172
|
-
`,
|
|
173
|
-
resolvers: {
|
|
174
|
-
User: {
|
|
175
|
-
favoriteColor(user: any) {
|
|
176
|
-
return user.preferences.favorites.color;
|
|
177
|
-
},
|
|
178
|
-
favoriteAnimal(user: any) {
|
|
179
|
-
return user.preferences.favorites.animal;
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const dataSources: Record<string, LocalGraphQLDataSource> = {
|
|
186
|
-
a: new LocalGraphQLDataSource(buildSubgraphSchema(serviceA)),
|
|
187
|
-
b: new LocalGraphQLDataSource(buildSubgraphSchema(serviceB)),
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const gateway = new ApolloGateway({
|
|
191
|
-
localServiceList: [serviceA, serviceB],
|
|
192
|
-
buildService(service) {
|
|
193
|
-
return dataSources[service.name];
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const server = new ApolloServer({ gateway });
|
|
198
|
-
await server.start();
|
|
199
|
-
|
|
200
|
-
const query1 = `#graphql
|
|
201
|
-
query UserFavoriteColor {
|
|
202
|
-
user {
|
|
203
|
-
favoriteColor
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
`;
|
|
207
|
-
|
|
208
|
-
const query2 = `#graphql
|
|
209
|
-
query UserFavorites {
|
|
210
|
-
user {
|
|
211
|
-
favoriteColor
|
|
212
|
-
favoriteAnimal
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
`;
|
|
216
|
-
|
|
217
|
-
const result1 = await server.executeOperation({
|
|
218
|
-
query: query1,
|
|
219
|
-
});
|
|
220
|
-
const result2 = await server.executeOperation({
|
|
221
|
-
query: query2,
|
|
222
|
-
});
|
|
223
|
-
const result3 = await server.executeOperation({
|
|
224
|
-
query: query1,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
expect(unwrapSingleResultKind(result1).errors).toEqual(undefined);
|
|
228
|
-
expect(unwrapSingleResultKind(result2).errors).toEqual(undefined);
|
|
229
|
-
expect(unwrapSingleResultKind(result3).errors).toEqual(undefined);
|
|
230
|
-
expect(result1).toEqual(result3);
|
|
231
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import gql from 'graphql-tag';
|
|
2
|
-
import { startSubgraphsAndGateway, Services } from './testUtils'
|
|
3
|
-
|
|
4
|
-
let services: Services;
|
|
5
|
-
|
|
6
|
-
afterEach(async () => {
|
|
7
|
-
if (services) {
|
|
8
|
-
await services.stop();
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe('`debug.bypassPlannerForSingleSubgraph` config', () => {
|
|
13
|
-
const subgraph = {
|
|
14
|
-
name: 'A',
|
|
15
|
-
url: 'https://A',
|
|
16
|
-
typeDefs: gql`
|
|
17
|
-
type Query {
|
|
18
|
-
a: A
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type A {
|
|
22
|
-
b: B
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type B {
|
|
26
|
-
x: Int
|
|
27
|
-
y: String
|
|
28
|
-
}
|
|
29
|
-
`,
|
|
30
|
-
resolvers: {
|
|
31
|
-
Query: {
|
|
32
|
-
a: () => ({
|
|
33
|
-
b: {
|
|
34
|
-
x: 1,
|
|
35
|
-
y: 'foo',
|
|
36
|
-
}
|
|
37
|
-
}),
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const query = `
|
|
43
|
-
{
|
|
44
|
-
a {
|
|
45
|
-
b {
|
|
46
|
-
x
|
|
47
|
-
y
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const expectedResult = `
|
|
54
|
-
Object {
|
|
55
|
-
"data": Object {
|
|
56
|
-
"a": Object {
|
|
57
|
-
"b": Object {
|
|
58
|
-
"x": 1,
|
|
59
|
-
"y": "foo",
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
}
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
it('is disabled by default', async () => {
|
|
67
|
-
services = await startSubgraphsAndGateway([subgraph]);
|
|
68
|
-
|
|
69
|
-
const response = await services.queryGateway(query);
|
|
70
|
-
const result = await response.json();
|
|
71
|
-
expect(result).toMatchInlineSnapshot(expectedResult);
|
|
72
|
-
|
|
73
|
-
const queryPlanner = services.gateway.__testing().queryPlanner!;
|
|
74
|
-
// If the query planner is genuinely used, we shoud have evaluated 1 plan.
|
|
75
|
-
expect(queryPlanner.lastGeneratedPlanStatistics()?.evaluatedPlanCount).toBe(1);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('works when enabled', async () => {
|
|
79
|
-
services = await startSubgraphsAndGateway(
|
|
80
|
-
[subgraph],
|
|
81
|
-
{
|
|
82
|
-
gatewayConfig: {
|
|
83
|
-
queryPlannerConfig: {
|
|
84
|
-
debug: {
|
|
85
|
-
bypassPlannerForSingleSubgraph: true,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const response = await services.queryGateway(query);
|
|
93
|
-
const result = await response.json();
|
|
94
|
-
expect(result).toMatchInlineSnapshot(expectedResult);
|
|
95
|
-
|
|
96
|
-
const queryPlanner = services.gateway.__testing().queryPlanner!;
|
|
97
|
-
// The `bypassPlannerForSingleSubgraph` doesn't evaluate anything. It's use is the only case where `evaluatedPlanCount` can be 0.
|
|
98
|
-
expect(queryPlanner.lastGeneratedPlanStatistics()?.evaluatedPlanCount).toBe(0);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|