@apollo/gateway 2.0.0-alpha.1 → 2.0.0-alpha.2
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 +2 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -17
- package/dist/config.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 +4 -4
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -28
- package/dist/index.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +4 -3
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.js +7 -3
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/operationContext.js +0 -1
- package/dist/operationContext.js.map +1 -1
- package/dist/utilities/array.js +1 -1
- package/dist/utilities/array.js.map +1 -1
- package/package.json +8 -9
- package/src/__generated__/graphqlTypes.ts +13 -11
- package/src/__tests__/buildQueryPlan.test.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +572 -76
- package/src/__tests__/execution-utils.ts +2 -4
- package/src/__tests__/gateway/composedSdl.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +2 -0
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +8 -4
- package/src/__tests__/gateway/opentelemetry.test.ts +1 -0
- package/src/__tests__/gateway/queryPlanCache.test.ts +3 -0
- package/src/__tests__/integration/aliases.test.ts +1 -0
- package/src/__tests__/integration/configuration.test.ts +3 -21
- package/src/__tests__/integration/logger.test.ts +1 -1
- package/src/__tests__/integration/networkRequests.test.ts +53 -28
- package/src/__tests__/integration/nockMocks.ts +33 -5
- package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +2 -2
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +36 -6
- package/src/config.ts +8 -43
- package/src/core/__tests__/core.test.ts +6 -6
- package/src/datasources/types.ts +1 -1
- package/src/executeQueryPlan.ts +4 -7
- package/src/index.ts +22 -50
- package/src/loadServicesFromRemoteEndpoint.ts +1 -1
- package/src/loadSupergraphSdlFromStorage.ts +7 -4
- package/src/make-fetch-happen.d.ts +1 -1
- package/src/operationContext.ts +2 -2
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +1 -1
- package/src/utilities/array.ts +1 -1
- 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/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/legacyLoadServicesFromStorage.ts +0 -170
|
@@ -30,11 +30,11 @@ describe('executeQueryPlan', () => {
|
|
|
30
30
|
[serviceName: string]: LocalGraphQLDataSource;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const parseOp = (operation: string, operationSchema?: Schema): Operation => {
|
|
34
34
|
return parseOperation((operationSchema ?? schema), operation);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
const buildPlan = (operation: string | Operation, operationQueryPlanner?: QueryPlanner, operationSchema?: Schema): QueryPlan => {
|
|
38
38
|
const op = typeof operation === 'string' ? parseOp(operation, operationSchema): operation;
|
|
39
39
|
return (operationQueryPlanner ?? queryPlanner).buildQueryPlan(op);
|
|
40
40
|
}
|
|
@@ -88,6 +88,7 @@ describe('executeQueryPlan', () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
function buildRequestContext(): GraphQLRequestContext {
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
91
92
|
// @ts-ignore
|
|
92
93
|
return {
|
|
93
94
|
cache: undefined as any,
|
|
@@ -1116,102 +1117,409 @@ describe('executeQueryPlan', () => {
|
|
|
1116
1117
|
});
|
|
1117
1118
|
});
|
|
1118
1119
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1120
|
+
describe('reusing root types', () => {
|
|
1121
|
+
it('can query other subgraphs when the Query type is the type of a field', async () => {
|
|
1122
|
+
const s1 = gql`
|
|
1123
|
+
type Query {
|
|
1124
|
+
getA: A
|
|
1125
|
+
}
|
|
1124
1126
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1127
|
+
type A {
|
|
1128
|
+
q: Query
|
|
1129
|
+
}
|
|
1130
|
+
`;
|
|
1129
1131
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1132
|
+
const s2 = gql`
|
|
1133
|
+
type Query {
|
|
1134
|
+
one: Int
|
|
1135
|
+
}
|
|
1136
|
+
`;
|
|
1135
1137
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1138
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
|
|
1139
|
+
{ name: 'S1', typeDefs: s1 },
|
|
1140
|
+
{ name: 'S2', typeDefs: s2 }
|
|
1141
|
+
]);
|
|
1140
1142
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
};
|
|
1143
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1144
|
+
Query: {
|
|
1145
|
+
getA() {
|
|
1146
|
+
return {
|
|
1147
|
+
getA: {}
|
|
1148
|
+
};
|
|
1149
|
+
},
|
|
1149
1150
|
},
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1151
|
+
A: {
|
|
1152
|
+
q() {
|
|
1153
|
+
return Object.create(null);
|
|
1154
|
+
}
|
|
1154
1155
|
}
|
|
1155
|
-
}
|
|
1156
|
-
});
|
|
1156
|
+
});
|
|
1157
1157
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1158
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1159
|
+
Query: {
|
|
1160
|
+
one() {
|
|
1161
|
+
return 1;
|
|
1162
|
+
},
|
|
1162
1163
|
},
|
|
1163
|
-
}
|
|
1164
|
-
});
|
|
1164
|
+
});
|
|
1165
1165
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1166
|
+
const operation = parseOp(`
|
|
1167
|
+
query {
|
|
1168
|
+
getA {
|
|
1169
|
+
q {
|
|
1170
|
+
one
|
|
1171
|
+
}
|
|
1171
1172
|
}
|
|
1172
1173
|
}
|
|
1173
|
-
|
|
1174
|
-
`, schema);
|
|
1174
|
+
`, schema);
|
|
1175
1175
|
|
|
1176
|
-
|
|
1176
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
1177
1177
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1178
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1179
|
+
QueryPlan {
|
|
1180
|
+
Sequence {
|
|
1181
|
+
Fetch(service: "S1") {
|
|
1182
|
+
{
|
|
1183
|
+
getA {
|
|
1184
|
+
q {
|
|
1185
|
+
__typename
|
|
1186
|
+
}
|
|
1186
1187
|
}
|
|
1187
1188
|
}
|
|
1189
|
+
},
|
|
1190
|
+
Flatten(path: "getA.q") {
|
|
1191
|
+
Fetch(service: "S2") {
|
|
1192
|
+
{
|
|
1193
|
+
... on Query {
|
|
1194
|
+
one
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
}
|
|
1201
|
+
`);
|
|
1202
|
+
|
|
1203
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1204
|
+
|
|
1205
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1206
|
+
Object {
|
|
1207
|
+
"getA": Object {
|
|
1208
|
+
"q": Object {
|
|
1209
|
+
"one": 1,
|
|
1210
|
+
},
|
|
1211
|
+
},
|
|
1212
|
+
}
|
|
1213
|
+
`);
|
|
1214
|
+
})
|
|
1215
|
+
|
|
1216
|
+
it('can query other subgraphs when the Query type is the type of a field after a mutation', async () => {
|
|
1217
|
+
const s1 = gql`
|
|
1218
|
+
type Query {
|
|
1219
|
+
one: Int
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
type Mutation {
|
|
1223
|
+
mutateSomething: Query
|
|
1224
|
+
}
|
|
1225
|
+
`;
|
|
1226
|
+
|
|
1227
|
+
const s2 = gql`
|
|
1228
|
+
type Query {
|
|
1229
|
+
two: Int
|
|
1230
|
+
}
|
|
1231
|
+
`;
|
|
1232
|
+
|
|
1233
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
|
|
1234
|
+
{ name: 'S1', typeDefs: s1 },
|
|
1235
|
+
{ name: 'S2', typeDefs: s2 }
|
|
1236
|
+
]);
|
|
1237
|
+
|
|
1238
|
+
let hasMutated = false;
|
|
1239
|
+
|
|
1240
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1241
|
+
Query: {
|
|
1242
|
+
one() {
|
|
1243
|
+
return 1;
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
Mutation: {
|
|
1247
|
+
mutateSomething() {
|
|
1248
|
+
hasMutated = true;
|
|
1249
|
+
return {};
|
|
1250
|
+
},
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1255
|
+
Query: {
|
|
1256
|
+
two() {
|
|
1257
|
+
return 2;
|
|
1258
|
+
},
|
|
1259
|
+
},
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
const operation = parseOp(`
|
|
1263
|
+
mutation {
|
|
1264
|
+
mutateSomething {
|
|
1265
|
+
one
|
|
1266
|
+
two
|
|
1188
1267
|
}
|
|
1268
|
+
}
|
|
1269
|
+
`, schema);
|
|
1270
|
+
|
|
1271
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
1272
|
+
|
|
1273
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1274
|
+
QueryPlan {
|
|
1275
|
+
Sequence {
|
|
1276
|
+
Fetch(service: "S1") {
|
|
1277
|
+
{
|
|
1278
|
+
mutateSomething {
|
|
1279
|
+
__typename
|
|
1280
|
+
one
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
Flatten(path: "mutateSomething") {
|
|
1285
|
+
Fetch(service: "S2") {
|
|
1286
|
+
{
|
|
1287
|
+
... on Query {
|
|
1288
|
+
two
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
},
|
|
1293
|
+
},
|
|
1294
|
+
}
|
|
1295
|
+
`);
|
|
1296
|
+
|
|
1297
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1298
|
+
|
|
1299
|
+
expect(hasMutated).toBeTruthy();
|
|
1300
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1301
|
+
Object {
|
|
1302
|
+
"mutateSomething": Object {
|
|
1303
|
+
"one": 1,
|
|
1304
|
+
"two": 2,
|
|
1305
|
+
},
|
|
1306
|
+
}
|
|
1307
|
+
`);
|
|
1308
|
+
})
|
|
1309
|
+
|
|
1310
|
+
it('can mutate other subgraphs when the Mutation type is the type of a field', async () => {
|
|
1311
|
+
const s1 = gql`
|
|
1312
|
+
type Query {
|
|
1313
|
+
getA: A
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
type Mutation {
|
|
1317
|
+
mutateOne: Int
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
type A {
|
|
1321
|
+
m: Mutation
|
|
1322
|
+
}
|
|
1323
|
+
`;
|
|
1324
|
+
|
|
1325
|
+
const s2 = gql`
|
|
1326
|
+
type Mutation {
|
|
1327
|
+
mutateTwo: Int
|
|
1328
|
+
}
|
|
1329
|
+
`;
|
|
1330
|
+
|
|
1331
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
|
|
1332
|
+
{ name: 'S1', typeDefs: s1 },
|
|
1333
|
+
{ name: 'S2', typeDefs: s2 }
|
|
1334
|
+
]);
|
|
1335
|
+
|
|
1336
|
+
let mutateOneCalled = false;
|
|
1337
|
+
let mutateTwoCalled = false;
|
|
1338
|
+
|
|
1339
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1340
|
+
Query: {
|
|
1341
|
+
getA() {
|
|
1342
|
+
return {
|
|
1343
|
+
getA: {}
|
|
1344
|
+
};
|
|
1189
1345
|
},
|
|
1190
|
-
|
|
1191
|
-
|
|
1346
|
+
},
|
|
1347
|
+
A: {
|
|
1348
|
+
m() {
|
|
1349
|
+
return Object.create(null);
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
Mutation: {
|
|
1353
|
+
mutateOne() {
|
|
1354
|
+
mutateOneCalled = true;
|
|
1355
|
+
return 1;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1361
|
+
Mutation: {
|
|
1362
|
+
mutateTwo() {
|
|
1363
|
+
mutateTwoCalled = true;
|
|
1364
|
+
return 2;
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
const operation = parseOp(`
|
|
1370
|
+
query {
|
|
1371
|
+
getA {
|
|
1372
|
+
m {
|
|
1373
|
+
mutateOne
|
|
1374
|
+
mutateTwo
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
`, schema);
|
|
1379
|
+
|
|
1380
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
1381
|
+
|
|
1382
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1383
|
+
QueryPlan {
|
|
1384
|
+
Sequence {
|
|
1385
|
+
Fetch(service: "S1") {
|
|
1192
1386
|
{
|
|
1193
|
-
|
|
1194
|
-
|
|
1387
|
+
getA {
|
|
1388
|
+
m {
|
|
1389
|
+
__typename
|
|
1390
|
+
mutateOne
|
|
1391
|
+
}
|
|
1195
1392
|
}
|
|
1196
1393
|
}
|
|
1197
1394
|
},
|
|
1395
|
+
Flatten(path: "getA.m") {
|
|
1396
|
+
Fetch(service: "S2") {
|
|
1397
|
+
{
|
|
1398
|
+
... on Mutation {
|
|
1399
|
+
mutateTwo
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
},
|
|
1403
|
+
},
|
|
1198
1404
|
},
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
`);
|
|
1202
|
-
|
|
1203
|
-
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1405
|
+
}
|
|
1406
|
+
`);
|
|
1204
1407
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1408
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1409
|
+
expect(mutateOneCalled).toBeTruthy();
|
|
1410
|
+
expect(mutateTwoCalled).toBeTruthy();
|
|
1411
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1412
|
+
Object {
|
|
1413
|
+
"getA": Object {
|
|
1414
|
+
"m": Object {
|
|
1415
|
+
"mutateOne": 1,
|
|
1416
|
+
"mutateTwo": 2,
|
|
1417
|
+
},
|
|
1210
1418
|
},
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1419
|
+
}
|
|
1213
1420
|
`);
|
|
1214
|
-
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
it('can mutate other subgraphs when the Mutation type is the type of a field after a mutation', async () => {
|
|
1424
|
+
const s1 = gql`
|
|
1425
|
+
type Query {
|
|
1426
|
+
one: Int
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
type Mutation {
|
|
1430
|
+
mutateSomething: Mutation
|
|
1431
|
+
}
|
|
1432
|
+
`;
|
|
1433
|
+
|
|
1434
|
+
const s2 = gql`
|
|
1435
|
+
type Mutation {
|
|
1436
|
+
mutateTwo: Int
|
|
1437
|
+
}
|
|
1438
|
+
`;
|
|
1439
|
+
|
|
1440
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([
|
|
1441
|
+
{ name: 'S1', typeDefs: s1 },
|
|
1442
|
+
{ name: 'S2', typeDefs: s2 }
|
|
1443
|
+
]);
|
|
1444
|
+
|
|
1445
|
+
let somethingMutationCount = 0;
|
|
1446
|
+
let hasMutatedTwo = false;
|
|
1447
|
+
|
|
1448
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1449
|
+
Query: {
|
|
1450
|
+
one() {
|
|
1451
|
+
return 1;
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
Mutation: {
|
|
1455
|
+
mutateSomething() {
|
|
1456
|
+
++somethingMutationCount;
|
|
1457
|
+
return {};
|
|
1458
|
+
},
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
addResolversToSchema(serviceMap['S2'].schema, {
|
|
1463
|
+
Mutation: {
|
|
1464
|
+
mutateTwo() {
|
|
1465
|
+
hasMutatedTwo = true;
|
|
1466
|
+
return 2;
|
|
1467
|
+
},
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
const operation = parseOp(`
|
|
1472
|
+
mutation {
|
|
1473
|
+
mutateSomething {
|
|
1474
|
+
mutateSomething {
|
|
1475
|
+
mutateTwo
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
`, schema);
|
|
1480
|
+
|
|
1481
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
1482
|
+
|
|
1483
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1484
|
+
QueryPlan {
|
|
1485
|
+
Sequence {
|
|
1486
|
+
Fetch(service: "S1") {
|
|
1487
|
+
{
|
|
1488
|
+
mutateSomething {
|
|
1489
|
+
mutateSomething {
|
|
1490
|
+
__typename
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
Flatten(path: "mutateSomething.mutateSomething") {
|
|
1496
|
+
Fetch(service: "S2") {
|
|
1497
|
+
{
|
|
1498
|
+
... on Mutation {
|
|
1499
|
+
mutateTwo
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
},
|
|
1503
|
+
},
|
|
1504
|
+
},
|
|
1505
|
+
}
|
|
1506
|
+
`);
|
|
1507
|
+
|
|
1508
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1509
|
+
|
|
1510
|
+
expect(somethingMutationCount).toBe(2);
|
|
1511
|
+
expect(hasMutatedTwo).toBeTruthy();
|
|
1512
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
1513
|
+
Object {
|
|
1514
|
+
"mutateSomething": Object {
|
|
1515
|
+
"mutateSomething": Object {
|
|
1516
|
+
"mutateTwo": 2,
|
|
1517
|
+
},
|
|
1518
|
+
},
|
|
1519
|
+
}
|
|
1520
|
+
`);
|
|
1521
|
+
})
|
|
1522
|
+
});
|
|
1215
1523
|
|
|
1216
1524
|
describe('interfaces on interfaces', () => {
|
|
1217
1525
|
it('can execute queries on an interface only implemented by other interfaces', async () => {
|
|
@@ -1309,7 +1617,7 @@ describe('executeQueryPlan', () => {
|
|
|
1309
1617
|
},
|
|
1310
1618
|
});
|
|
1311
1619
|
|
|
1312
|
-
|
|
1620
|
+
const operation = parseOp(`
|
|
1313
1621
|
query {
|
|
1314
1622
|
allValues {
|
|
1315
1623
|
a
|
|
@@ -1323,7 +1631,7 @@ describe('executeQueryPlan', () => {
|
|
|
1323
1631
|
}
|
|
1324
1632
|
`, schema);
|
|
1325
1633
|
|
|
1326
|
-
|
|
1634
|
+
const queryPlan = buildPlan(operation, queryPlanner);
|
|
1327
1635
|
|
|
1328
1636
|
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1329
1637
|
QueryPlan {
|
|
@@ -1378,7 +1686,7 @@ describe('executeQueryPlan', () => {
|
|
|
1378
1686
|
}
|
|
1379
1687
|
`);
|
|
1380
1688
|
|
|
1381
|
-
|
|
1689
|
+
const response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
1382
1690
|
|
|
1383
1691
|
expect(response.data).toMatchInlineSnapshot(`
|
|
1384
1692
|
Object {
|
|
@@ -1642,4 +1950,192 @@ describe('executeQueryPlan', () => {
|
|
|
1642
1950
|
`);
|
|
1643
1951
|
});
|
|
1644
1952
|
});
|
|
1953
|
+
|
|
1954
|
+
test('do not send subgraphs an interface they do not know', async () => {
|
|
1955
|
+
// This test validates that the issue described on https://github.com/apollographql/federation/issues/817 is fixed.
|
|
1956
|
+
const s1 = {
|
|
1957
|
+
name: 'S1',
|
|
1958
|
+
typeDefs: gql`
|
|
1959
|
+
type Query {
|
|
1960
|
+
myField: MyInterface
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
interface MyInterface {
|
|
1964
|
+
name: String
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
type MyTypeA implements MyInterface {
|
|
1968
|
+
name: String
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
type MyTypeB implements MyInterface {
|
|
1972
|
+
name: String
|
|
1973
|
+
}
|
|
1974
|
+
`
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
const s2 = {
|
|
1978
|
+
name: 'S2',
|
|
1979
|
+
typeDefs: gql`
|
|
1980
|
+
interface MyInterface {
|
|
1981
|
+
name: String
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
type MyTypeC implements MyInterface {
|
|
1985
|
+
name: String
|
|
1986
|
+
}
|
|
1987
|
+
`
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
1991
|
+
|
|
1992
|
+
addResolversToSchema(serviceMap['S1'].schema, {
|
|
1993
|
+
Query: {
|
|
1994
|
+
myField() {
|
|
1995
|
+
return { __typename: 'MyTypeA', name: "foo" };
|
|
1996
|
+
},
|
|
1997
|
+
},
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
// First, we just query the field without conditions.
|
|
2001
|
+
// Note that there is no reason to type-explode this: clearly, `myField` will never return a `MyTypeC` since
|
|
2002
|
+
// it's resolved by S1 which doesn't know that type, but that doesn't impact the plan.
|
|
2003
|
+
let operation = parseOp(`
|
|
2004
|
+
query {
|
|
2005
|
+
myField {
|
|
2006
|
+
name
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
`, schema);
|
|
2010
|
+
let queryPlan = buildPlan(operation, queryPlanner);
|
|
2011
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2012
|
+
QueryPlan {
|
|
2013
|
+
Fetch(service: "S1") {
|
|
2014
|
+
{
|
|
2015
|
+
myField {
|
|
2016
|
+
__typename
|
|
2017
|
+
name
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
}
|
|
2022
|
+
`);
|
|
2023
|
+
let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2024
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2025
|
+
Object {
|
|
2026
|
+
"myField": Object {
|
|
2027
|
+
"name": "foo",
|
|
2028
|
+
},
|
|
2029
|
+
}
|
|
2030
|
+
`);
|
|
2031
|
+
|
|
2032
|
+
// Now forcing the query planning to notice that `MyTypeC` can never happen and making
|
|
2033
|
+
// sure it doesn't ask it from S1, which doesn't know it.
|
|
2034
|
+
operation = parseOp(`
|
|
2035
|
+
query {
|
|
2036
|
+
myField {
|
|
2037
|
+
... on MyTypeA {
|
|
2038
|
+
name
|
|
2039
|
+
}
|
|
2040
|
+
... on MyTypeC {
|
|
2041
|
+
name
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
`, schema);
|
|
2046
|
+
queryPlan = buildPlan(operation, queryPlanner);
|
|
2047
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2048
|
+
QueryPlan {
|
|
2049
|
+
Fetch(service: "S1") {
|
|
2050
|
+
{
|
|
2051
|
+
myField {
|
|
2052
|
+
__typename
|
|
2053
|
+
... on MyTypeA {
|
|
2054
|
+
name
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
},
|
|
2059
|
+
}
|
|
2060
|
+
`);
|
|
2061
|
+
|
|
2062
|
+
response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2063
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2064
|
+
Object {
|
|
2065
|
+
"myField": Object {
|
|
2066
|
+
"name": "foo",
|
|
2067
|
+
},
|
|
2068
|
+
}
|
|
2069
|
+
`);
|
|
2070
|
+
|
|
2071
|
+
|
|
2072
|
+
// Testing only getting name for `MyTypeB`, which is known by S1, but not returned
|
|
2073
|
+
// by `myField` in practice (so the result is "empty").
|
|
2074
|
+
operation = parseOp(`
|
|
2075
|
+
query {
|
|
2076
|
+
myField {
|
|
2077
|
+
... on MyTypeB {
|
|
2078
|
+
name
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
`, schema);
|
|
2083
|
+
|
|
2084
|
+
queryPlan = buildPlan(operation, queryPlanner);
|
|
2085
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2086
|
+
QueryPlan {
|
|
2087
|
+
Fetch(service: "S1") {
|
|
2088
|
+
{
|
|
2089
|
+
myField {
|
|
2090
|
+
__typename
|
|
2091
|
+
... on MyTypeB {
|
|
2092
|
+
name
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
}
|
|
2098
|
+
`);
|
|
2099
|
+
response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2100
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2101
|
+
Object {
|
|
2102
|
+
"myField": Object {},
|
|
2103
|
+
}
|
|
2104
|
+
`);
|
|
2105
|
+
|
|
2106
|
+
operation = parseOp(`
|
|
2107
|
+
query {
|
|
2108
|
+
myField {
|
|
2109
|
+
... on MyTypeC {
|
|
2110
|
+
name
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
`, schema);
|
|
2115
|
+
|
|
2116
|
+
// Lastly, same with only getting name for `MyTypeC`. It isn't known by S1 so the condition should not
|
|
2117
|
+
// be included in the query, but we should still query `myField` to know if it resolve to "something"
|
|
2118
|
+
// (and all we know it can't be a `MyTypeC`) or to `null`. In particular, the end response should be
|
|
2119
|
+
// the same than in the previous example with `MyTypeB` since from the end-use POV, this is the same
|
|
2120
|
+
// example.
|
|
2121
|
+
queryPlan = buildPlan(operation, queryPlanner);
|
|
2122
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
2123
|
+
QueryPlan {
|
|
2124
|
+
Fetch(service: "S1") {
|
|
2125
|
+
{
|
|
2126
|
+
myField {
|
|
2127
|
+
__typename
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
},
|
|
2131
|
+
}
|
|
2132
|
+
`);
|
|
2133
|
+
|
|
2134
|
+
response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
2135
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
2136
|
+
Object {
|
|
2137
|
+
"myField": Object {},
|
|
2138
|
+
}
|
|
2139
|
+
`);
|
|
2140
|
+
});
|
|
1645
2141
|
});
|