@apollo/gateway 2.0.0-alpha.2 → 2.0.0-alpha.3
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/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.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/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +1 -1
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +13 -5
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.js +34 -7
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/outOfBandReporter.d.ts +10 -12
- package/dist/outOfBandReporter.d.ts.map +1 -1
- package/dist/outOfBandReporter.js +70 -73
- package/dist/outOfBandReporter.js.map +1 -1
- package/package.json +4 -4
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/executeQueryPlan.test.ts +598 -0
- package/src/__tests__/gateway/buildService.test.ts +1 -1
- package/src/__tests__/gateway/composedSdl.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +1 -1
- package/src/__tests__/gateway/reporting.test.ts +8 -5
- package/src/__tests__/integration/configuration.test.ts +44 -4
- package/src/__tests__/integration/networkRequests.test.ts +21 -19
- package/src/__tests__/integration/nockMocks.ts +12 -6
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +101 -452
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +3 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/executeQueryPlan.ts +11 -1
- package/src/index.ts +26 -12
- package/src/loadSupergraphSdlFromStorage.ts +54 -8
- package/src/outOfBandReporter.ts +87 -89
|
@@ -2138,4 +2138,602 @@ describe('executeQueryPlan', () => {
|
|
|
2138
2138
|
}
|
|
2139
2139
|
`);
|
|
2140
2140
|
});
|
|
2141
|
+
|
|
2142
|
+
describe('@requires', () => {
|
|
2143
|
+
test('handles null in required field correctly (with nullable fields)', async () => {
|
|
2144
|
+
const s1_data = [
|
|
2145
|
+
{ id: 0, f1: "foo" },
|
|
2146
|
+
{ id: 1, f1: null },
|
|
2147
|
+
{ id: 2, f1: "bar" },
|
|
2148
|
+
];
|
|
2149
|
+
|
|
2150
|
+
const s1 = {
|
|
2151
|
+
name: 'S1',
|
|
2152
|
+
typeDefs: gql`
|
|
2153
|
+
type T1 @key(fields: "id") {
|
|
2154
|
+
id: Int!
|
|
2155
|
+
f1: String
|
|
2156
|
+
}
|
|
2157
|
+
`,
|
|
2158
|
+
resolvers: {
|
|
2159
|
+
T1: {
|
|
2160
|
+
__resolveReference(ref: {id: number}) {
|
|
2161
|
+
return s1_data[ref.id];
|
|
2162
|
+
},
|
|
2163
|
+
},
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
const s2 = {
|
|
2168
|
+
name: 'S2',
|
|
2169
|
+
typeDefs: gql`
|
|
2170
|
+
type Query {
|
|
2171
|
+
getT1s: [T1]
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
type T1 @key(fields: "id") {
|
|
2175
|
+
id: Int!
|
|
2176
|
+
f1: String @external
|
|
2177
|
+
f2: T2 @requires(fields: "f1")
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
type T2 {
|
|
2181
|
+
a: String
|
|
2182
|
+
}
|
|
2183
|
+
`,
|
|
2184
|
+
resolvers: {
|
|
2185
|
+
Query: {
|
|
2186
|
+
getT1s() {
|
|
2187
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2188
|
+
},
|
|
2189
|
+
},
|
|
2190
|
+
T1: {
|
|
2191
|
+
__resolveReference(ref: { id: number }) {
|
|
2192
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2193
|
+
return ref;
|
|
2194
|
+
},
|
|
2195
|
+
f2(o: { f1: string }) {
|
|
2196
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2203
|
+
|
|
2204
|
+
const operation = parseOp(`
|
|
2205
|
+
query {
|
|
2206
|
+
getT1s {
|
|
2207
|
+
id
|
|
2208
|
+
f1
|
|
2209
|
+
f2 {
|
|
2210
|
+
a
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
`, schema);
|
|
2215
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
2216
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2217
|
+
QueryPlan {
|
|
2218
|
+
Sequence {
|
|
2219
|
+
Fetch(service: "S2") {
|
|
2220
|
+
{
|
|
2221
|
+
getT1s {
|
|
2222
|
+
__typename
|
|
2223
|
+
id
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
Flatten(path: "getT1s.@") {
|
|
2228
|
+
Fetch(service: "S1") {
|
|
2229
|
+
{
|
|
2230
|
+
... on T1 {
|
|
2231
|
+
__typename
|
|
2232
|
+
id
|
|
2233
|
+
}
|
|
2234
|
+
} =>
|
|
2235
|
+
{
|
|
2236
|
+
... on T1 {
|
|
2237
|
+
f1
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
},
|
|
2241
|
+
},
|
|
2242
|
+
Flatten(path: "getT1s.@") {
|
|
2243
|
+
Fetch(service: "S2") {
|
|
2244
|
+
{
|
|
2245
|
+
... on T1 {
|
|
2246
|
+
__typename
|
|
2247
|
+
f1
|
|
2248
|
+
id
|
|
2249
|
+
}
|
|
2250
|
+
} =>
|
|
2251
|
+
{
|
|
2252
|
+
... on T1 {
|
|
2253
|
+
f2 {
|
|
2254
|
+
a
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
2259
|
+
},
|
|
2260
|
+
},
|
|
2261
|
+
}
|
|
2262
|
+
`);
|
|
2263
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2264
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2265
|
+
Object {
|
|
2266
|
+
"getT1s": Array [
|
|
2267
|
+
Object {
|
|
2268
|
+
"f1": "foo",
|
|
2269
|
+
"f2": Object {
|
|
2270
|
+
"a": "t1:foo",
|
|
2271
|
+
},
|
|
2272
|
+
"id": 0,
|
|
2273
|
+
},
|
|
2274
|
+
Object {
|
|
2275
|
+
"f1": null,
|
|
2276
|
+
"f2": null,
|
|
2277
|
+
"id": 1,
|
|
2278
|
+
},
|
|
2279
|
+
Object {
|
|
2280
|
+
"f1": "bar",
|
|
2281
|
+
"f2": Object {
|
|
2282
|
+
"a": "t1:bar",
|
|
2283
|
+
},
|
|
2284
|
+
"id": 2,
|
|
2285
|
+
},
|
|
2286
|
+
],
|
|
2287
|
+
}
|
|
2288
|
+
`);
|
|
2289
|
+
expect(response.errors).toBeUndefined();
|
|
2290
|
+
});
|
|
2291
|
+
|
|
2292
|
+
test('handles null in required field correctly (with @require field non-nullable)', async () => {
|
|
2293
|
+
const s1_data = [
|
|
2294
|
+
{ id: 0, f1: "foo" },
|
|
2295
|
+
{ id: 1, f1: null },
|
|
2296
|
+
{ id: 2, f1: "bar" },
|
|
2297
|
+
];
|
|
2298
|
+
|
|
2299
|
+
const s1 = {
|
|
2300
|
+
name: 'S1',
|
|
2301
|
+
typeDefs: gql`
|
|
2302
|
+
type T1 @key(fields: "id") {
|
|
2303
|
+
id: Int!
|
|
2304
|
+
f1: String
|
|
2305
|
+
}
|
|
2306
|
+
`,
|
|
2307
|
+
resolvers: {
|
|
2308
|
+
T1: {
|
|
2309
|
+
__resolveReference(ref: { id: number }) {
|
|
2310
|
+
return s1_data[ref.id];
|
|
2311
|
+
},
|
|
2312
|
+
},
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
const s2 = {
|
|
2317
|
+
name: 'S2',
|
|
2318
|
+
typeDefs: gql`
|
|
2319
|
+
type Query {
|
|
2320
|
+
getT1s: [T1]
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
type T1 @key(fields: "id") {
|
|
2324
|
+
id: Int!
|
|
2325
|
+
f1: String @external
|
|
2326
|
+
f2: T2! @requires(fields: "f1")
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
type T2 {
|
|
2330
|
+
a: String
|
|
2331
|
+
}
|
|
2332
|
+
`,
|
|
2333
|
+
resolvers: {
|
|
2334
|
+
Query: {
|
|
2335
|
+
getT1s() {
|
|
2336
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2337
|
+
},
|
|
2338
|
+
},
|
|
2339
|
+
T1: {
|
|
2340
|
+
__resolveReference(ref: { id: number }) {
|
|
2341
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2342
|
+
return ref;
|
|
2343
|
+
},
|
|
2344
|
+
f2(o: { f1: string }) {
|
|
2345
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2352
|
+
|
|
2353
|
+
const operation = parseOp(`
|
|
2354
|
+
query {
|
|
2355
|
+
getT1s {
|
|
2356
|
+
id
|
|
2357
|
+
f1
|
|
2358
|
+
f2 {
|
|
2359
|
+
a
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
`, schema);
|
|
2364
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
2365
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2366
|
+
QueryPlan {
|
|
2367
|
+
Sequence {
|
|
2368
|
+
Fetch(service: "S2") {
|
|
2369
|
+
{
|
|
2370
|
+
getT1s {
|
|
2371
|
+
__typename
|
|
2372
|
+
id
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
},
|
|
2376
|
+
Flatten(path: "getT1s.@") {
|
|
2377
|
+
Fetch(service: "S1") {
|
|
2378
|
+
{
|
|
2379
|
+
... on T1 {
|
|
2380
|
+
__typename
|
|
2381
|
+
id
|
|
2382
|
+
}
|
|
2383
|
+
} =>
|
|
2384
|
+
{
|
|
2385
|
+
... on T1 {
|
|
2386
|
+
f1
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
},
|
|
2390
|
+
},
|
|
2391
|
+
Flatten(path: "getT1s.@") {
|
|
2392
|
+
Fetch(service: "S2") {
|
|
2393
|
+
{
|
|
2394
|
+
... on T1 {
|
|
2395
|
+
__typename
|
|
2396
|
+
f1
|
|
2397
|
+
id
|
|
2398
|
+
}
|
|
2399
|
+
} =>
|
|
2400
|
+
{
|
|
2401
|
+
... on T1 {
|
|
2402
|
+
f2 {
|
|
2403
|
+
a
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
},
|
|
2408
|
+
},
|
|
2409
|
+
},
|
|
2410
|
+
}
|
|
2411
|
+
`);
|
|
2412
|
+
|
|
2413
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2414
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
2415
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2416
|
+
Object {
|
|
2417
|
+
"getT1s": Array [
|
|
2418
|
+
Object {
|
|
2419
|
+
"f1": "foo",
|
|
2420
|
+
"f2": Object {
|
|
2421
|
+
"a": "t1:foo",
|
|
2422
|
+
},
|
|
2423
|
+
"id": 0,
|
|
2424
|
+
},
|
|
2425
|
+
null,
|
|
2426
|
+
Object {
|
|
2427
|
+
"f1": "bar",
|
|
2428
|
+
"f2": Object {
|
|
2429
|
+
"a": "t1:bar",
|
|
2430
|
+
},
|
|
2431
|
+
"id": 2,
|
|
2432
|
+
},
|
|
2433
|
+
],
|
|
2434
|
+
}
|
|
2435
|
+
`);
|
|
2436
|
+
|
|
2437
|
+
// We returning `null` for f2 which isn't nullable, so it bubbled up and we should have an error
|
|
2438
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f2.']);
|
|
2439
|
+
});
|
|
2440
|
+
|
|
2441
|
+
test('handles null in required field correctly (with non-nullable required field)', async () => {
|
|
2442
|
+
const s1 = {
|
|
2443
|
+
name: 'S1',
|
|
2444
|
+
typeDefs: gql`
|
|
2445
|
+
type T1 @key(fields: "id") {
|
|
2446
|
+
id: Int!
|
|
2447
|
+
f1: String!
|
|
2448
|
+
}
|
|
2449
|
+
`,
|
|
2450
|
+
resolvers: {
|
|
2451
|
+
T1: {
|
|
2452
|
+
__resolveReference(ref: { id: number}) {
|
|
2453
|
+
return s1_data[ref.id];
|
|
2454
|
+
},
|
|
2455
|
+
},
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
const s2 = {
|
|
2460
|
+
name: 'S2',
|
|
2461
|
+
typeDefs: gql`
|
|
2462
|
+
type Query {
|
|
2463
|
+
getT1s: [T1]
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
type T1 @key(fields: "id") {
|
|
2467
|
+
id: Int!
|
|
2468
|
+
f1: String! @external
|
|
2469
|
+
f2: T2 @requires(fields: "f1")
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
type T2 {
|
|
2473
|
+
a: String
|
|
2474
|
+
}
|
|
2475
|
+
`,
|
|
2476
|
+
resolvers: {
|
|
2477
|
+
Query: {
|
|
2478
|
+
getT1s() {
|
|
2479
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2480
|
+
},
|
|
2481
|
+
},
|
|
2482
|
+
T1: {
|
|
2483
|
+
__resolveReference(ref: { id: number }) {
|
|
2484
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2485
|
+
return ref;
|
|
2486
|
+
},
|
|
2487
|
+
f2(o: { f1: string }) {
|
|
2488
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2495
|
+
|
|
2496
|
+
const s1_data = [
|
|
2497
|
+
{ id: 0, f1: "foo" },
|
|
2498
|
+
{ id: 1, f1: null },
|
|
2499
|
+
{ id: 2, f1: "bar" },
|
|
2500
|
+
];
|
|
2501
|
+
|
|
2502
|
+
const operation = parseOp(`
|
|
2503
|
+
query {
|
|
2504
|
+
getT1s {
|
|
2505
|
+
id
|
|
2506
|
+
f1
|
|
2507
|
+
f2 {
|
|
2508
|
+
a
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
`, schema);
|
|
2513
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
2514
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2515
|
+
QueryPlan {
|
|
2516
|
+
Sequence {
|
|
2517
|
+
Fetch(service: "S2") {
|
|
2518
|
+
{
|
|
2519
|
+
getT1s {
|
|
2520
|
+
__typename
|
|
2521
|
+
id
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2525
|
+
Flatten(path: "getT1s.@") {
|
|
2526
|
+
Fetch(service: "S1") {
|
|
2527
|
+
{
|
|
2528
|
+
... on T1 {
|
|
2529
|
+
__typename
|
|
2530
|
+
id
|
|
2531
|
+
}
|
|
2532
|
+
} =>
|
|
2533
|
+
{
|
|
2534
|
+
... on T1 {
|
|
2535
|
+
f1
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
},
|
|
2539
|
+
},
|
|
2540
|
+
Flatten(path: "getT1s.@") {
|
|
2541
|
+
Fetch(service: "S2") {
|
|
2542
|
+
{
|
|
2543
|
+
... on T1 {
|
|
2544
|
+
__typename
|
|
2545
|
+
f1
|
|
2546
|
+
id
|
|
2547
|
+
}
|
|
2548
|
+
} =>
|
|
2549
|
+
{
|
|
2550
|
+
... on T1 {
|
|
2551
|
+
f2 {
|
|
2552
|
+
a
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
},
|
|
2557
|
+
},
|
|
2558
|
+
},
|
|
2559
|
+
}
|
|
2560
|
+
`);
|
|
2561
|
+
|
|
2562
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2563
|
+
// `null` should bubble up since `f2` is now non-nullable. But we should still get the `id: 0` response.
|
|
2564
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2565
|
+
Object {
|
|
2566
|
+
"getT1s": Array [
|
|
2567
|
+
Object {
|
|
2568
|
+
"f1": "foo",
|
|
2569
|
+
"f2": Object {
|
|
2570
|
+
"a": "t1:foo",
|
|
2571
|
+
},
|
|
2572
|
+
"id": 0,
|
|
2573
|
+
},
|
|
2574
|
+
null,
|
|
2575
|
+
Object {
|
|
2576
|
+
"f1": "bar",
|
|
2577
|
+
"f2": Object {
|
|
2578
|
+
"a": "t1:bar",
|
|
2579
|
+
},
|
|
2580
|
+
"id": 2,
|
|
2581
|
+
},
|
|
2582
|
+
],
|
|
2583
|
+
}
|
|
2584
|
+
`);
|
|
2585
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['Cannot return null for non-nullable field T1.f1.']);
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
test('handles errors in required field correctly (with nullable fields)', async () => {
|
|
2589
|
+
const s1 = {
|
|
2590
|
+
name: 'S1',
|
|
2591
|
+
typeDefs: gql`
|
|
2592
|
+
type T1 @key(fields: "id") {
|
|
2593
|
+
id: Int!
|
|
2594
|
+
f1: String
|
|
2595
|
+
}
|
|
2596
|
+
`,
|
|
2597
|
+
resolvers: {
|
|
2598
|
+
T1: {
|
|
2599
|
+
__resolveReference(ref: { id: number }) {
|
|
2600
|
+
return ref;
|
|
2601
|
+
},
|
|
2602
|
+
f1(o: { id: number }) {
|
|
2603
|
+
switch (o.id) {
|
|
2604
|
+
case 0: return "foo";
|
|
2605
|
+
case 1: return [ "invalid" ]; // This will effectively throw
|
|
2606
|
+
case 2: return "bar";
|
|
2607
|
+
default: throw new Error('Not handled');
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
},
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
const s2 = {
|
|
2615
|
+
name: 'S2',
|
|
2616
|
+
typeDefs: gql`
|
|
2617
|
+
type Query {
|
|
2618
|
+
getT1s: [T1]
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
type T1 @key(fields: "id") {
|
|
2622
|
+
id: Int!
|
|
2623
|
+
f1: String @external
|
|
2624
|
+
f2: T2 @requires(fields: "f1")
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
type T2 {
|
|
2628
|
+
a: String
|
|
2629
|
+
}
|
|
2630
|
+
`,
|
|
2631
|
+
resolvers: {
|
|
2632
|
+
Query: {
|
|
2633
|
+
getT1s() {
|
|
2634
|
+
return [{id: 0}, {id: 1}, {id: 2}];
|
|
2635
|
+
},
|
|
2636
|
+
},
|
|
2637
|
+
T1: {
|
|
2638
|
+
__resolveReference(ref: { id: number }) {
|
|
2639
|
+
// the ref has already the id and f1 is a require is triggered, and we resolve f2 below
|
|
2640
|
+
return ref;
|
|
2641
|
+
},
|
|
2642
|
+
f2(o: { f1: string }) {
|
|
2643
|
+
return o.f1 === null ? null : { a: `t1:${o.f1}` };
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
2650
|
+
|
|
2651
|
+
const operation = parseOp(`
|
|
2652
|
+
query {
|
|
2653
|
+
getT1s {
|
|
2654
|
+
id
|
|
2655
|
+
f1
|
|
2656
|
+
f2 {
|
|
2657
|
+
a
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
`, schema);
|
|
2662
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
2663
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2664
|
+
QueryPlan {
|
|
2665
|
+
Sequence {
|
|
2666
|
+
Fetch(service: "S2") {
|
|
2667
|
+
{
|
|
2668
|
+
getT1s {
|
|
2669
|
+
__typename
|
|
2670
|
+
id
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
},
|
|
2674
|
+
Flatten(path: "getT1s.@") {
|
|
2675
|
+
Fetch(service: "S1") {
|
|
2676
|
+
{
|
|
2677
|
+
... on T1 {
|
|
2678
|
+
__typename
|
|
2679
|
+
id
|
|
2680
|
+
}
|
|
2681
|
+
} =>
|
|
2682
|
+
{
|
|
2683
|
+
... on T1 {
|
|
2684
|
+
f1
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
},
|
|
2689
|
+
Flatten(path: "getT1s.@") {
|
|
2690
|
+
Fetch(service: "S2") {
|
|
2691
|
+
{
|
|
2692
|
+
... on T1 {
|
|
2693
|
+
__typename
|
|
2694
|
+
f1
|
|
2695
|
+
id
|
|
2696
|
+
}
|
|
2697
|
+
} =>
|
|
2698
|
+
{
|
|
2699
|
+
... on T1 {
|
|
2700
|
+
f2 {
|
|
2701
|
+
a
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
},
|
|
2706
|
+
},
|
|
2707
|
+
},
|
|
2708
|
+
}
|
|
2709
|
+
`);
|
|
2710
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2711
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2712
|
+
Object {
|
|
2713
|
+
"getT1s": Array [
|
|
2714
|
+
Object {
|
|
2715
|
+
"f1": "foo",
|
|
2716
|
+
"f2": Object {
|
|
2717
|
+
"a": "t1:foo",
|
|
2718
|
+
},
|
|
2719
|
+
"id": 0,
|
|
2720
|
+
},
|
|
2721
|
+
Object {
|
|
2722
|
+
"f1": null,
|
|
2723
|
+
"f2": null,
|
|
2724
|
+
"id": 1,
|
|
2725
|
+
},
|
|
2726
|
+
Object {
|
|
2727
|
+
"f1": "bar",
|
|
2728
|
+
"f2": Object {
|
|
2729
|
+
"a": "t1:bar",
|
|
2730
|
+
},
|
|
2731
|
+
"id": 2,
|
|
2732
|
+
},
|
|
2733
|
+
],
|
|
2734
|
+
}
|
|
2735
|
+
`);
|
|
2736
|
+
expect(response.errors?.map((e) => e.message)).toStrictEqual(['String cannot represent value: ["invalid"]']);
|
|
2737
|
+
});
|
|
2738
|
+
});
|
|
2141
2739
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetch } from '../../__mocks__/
|
|
1
|
+
import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
2
2
|
import { ApolloServerBase as ApolloServer } from 'apollo-server-core';
|
|
3
3
|
|
|
4
4
|
import { RemoteGraphQLDataSource } from '../../datasources/RemoteGraphQLDataSource';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
1
2
|
import { ApolloGateway } from '@apollo/gateway';
|
|
2
3
|
import { ApolloServer } from 'apollo-server';
|
|
3
|
-
import { fetch } from '../../__mocks__/apollo-server-env';
|
|
4
4
|
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
5
5
|
|
|
6
6
|
async function getSupergraphSdlGatewayServer() {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { fetch } from '../../__mocks__/make-fetch-happen-fetcher';
|
|
1
2
|
import gql from 'graphql-tag';
|
|
2
3
|
import { ApolloGateway } from '../../';
|
|
3
4
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
4
5
|
import { Logger } from 'apollo-server-types';
|
|
5
|
-
import { fetch } from '../../__mocks__/apollo-server-env';
|
|
6
6
|
|
|
7
7
|
let logger: {
|
|
8
8
|
warn: jest.MockedFunction<Logger['warn']>,
|
|
@@ -5,13 +5,15 @@ import gql from 'graphql-tag';
|
|
|
5
5
|
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
6
6
|
import { ApolloServer } from 'apollo-server';
|
|
7
7
|
import { ApolloServerPluginUsageReporting } from 'apollo-server-core';
|
|
8
|
-
import { execute
|
|
9
|
-
import {
|
|
8
|
+
import { execute } from '@apollo/client/link/core';
|
|
9
|
+
import { toPromise } from '@apollo/client/link/utils';
|
|
10
|
+
import { createHttpLink } from '@apollo/client/link/http';
|
|
10
11
|
import fetch from 'node-fetch';
|
|
11
12
|
import { ApolloGateway } from '../..';
|
|
12
13
|
import { Plugin, Config, Refs } from 'pretty-format';
|
|
13
14
|
import { Report, Trace } from 'apollo-reporting-protobuf';
|
|
14
15
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
16
|
+
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
15
17
|
|
|
16
18
|
// Normalize specific fields that change often (eg timestamps) to static values,
|
|
17
19
|
// to make snapshot testing viable. (If these helpers are more generally
|
|
@@ -89,7 +91,6 @@ describe('reporting', () => {
|
|
|
89
91
|
let gatewayServer: ApolloServer;
|
|
90
92
|
let gatewayUrl: string;
|
|
91
93
|
let reportPromise: Promise<any>;
|
|
92
|
-
let nockScope: nock.Scope;
|
|
93
94
|
|
|
94
95
|
beforeEach(async () => {
|
|
95
96
|
let reportResolver: (report: any) => void;
|
|
@@ -97,7 +98,8 @@ describe('reporting', () => {
|
|
|
97
98
|
reportResolver = resolve;
|
|
98
99
|
});
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
nockBeforeEach();
|
|
102
|
+
nock('https://usage-reporting.api.apollographql.com')
|
|
101
103
|
.post('/api/ingress/traces')
|
|
102
104
|
.reply(200, (_: any, requestBody: string) => {
|
|
103
105
|
reportResolver(requestBody);
|
|
@@ -137,7 +139,8 @@ describe('reporting', () => {
|
|
|
137
139
|
if (gatewayServer) {
|
|
138
140
|
await gatewayServer.stop();
|
|
139
141
|
}
|
|
140
|
-
|
|
142
|
+
|
|
143
|
+
nockAfterEach();
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
it(`queries three services`, async () => {
|