Skip to content

Commit f2058b6

Browse files
ardatanenisdenjo
authored andcommitted
Add Federation Compatibility Test Suite (#120)
1 parent eb80080 commit f2058b6

File tree

9 files changed

+341
-104
lines changed

9 files changed

+341
-104
lines changed

.github/workflows/check.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ on:
77
branches:
88
- main
99
pull_request:
10-
branches:
11-
- main
1210

1311
jobs:
1412
format:

.github/workflows/test.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ on:
55
branches:
66
- main
77
pull_request:
8-
branches:
9-
- main
108

119
jobs:
1210
unit:

.yarn/releases/yarn-4.5.1.cjs

100755100644
File mode changed.

.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ yarnPath: .yarn/releases/yarn-4.5.1.cjs # TODO: corepack does not work in github
22
nodeLinker: node-modules
33
npmPublishRegistry: https://registry.npmjs.org
44
npmAuthToken: ${NPM_TOKEN:-}
5+
checksumBehavior: ignore

jest.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ const rootDir = __dirname;
99
const tsconfigPath = resolve(rootDir, 'tsconfig.json');
1010
const tsconfigContents = readFileSync(tsconfigPath, 'utf8');
1111
const tsconfig = JSON5.parse(tsconfigContents);
12+
const ESM_PACKAGES = ['graphql-federation-gateway-audit'];
1213

1314
export default {
1415
testEnvironment: 'node',
1516
rootDir,
1617
restoreMocks: true,
1718
reporters: ['default'],
18-
verbose: isCI,
1919
modulePathIgnorePatterns: ['dist'],
2020
collectCoverage: false,
2121
cacheDirectory: resolve(rootDir, `${isCI ? '' : 'node_modules/'}.cache/jest`),
@@ -31,5 +31,6 @@ export default {
3131
useESM: true,
3232
}),
3333
},
34+
transformIgnorePatterns: [`node_modules/(?!(${ESM_PACKAGES.join('|')})/)`],
3435
testMatch: ['**/*.(test|spec).ts'],
3536
} satisfies Config;

packages/federation/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@apollo/subgraph": "^2.5.4",
5858
"@types/lodash.pick": "4.4.9",
5959
"graphql": "^16.9.0",
60+
"graphql-federation-gateway-audit": "the-guild-org/graphql-federation-gateway-audit#expose-server-logic",
6061
"pkgroll": "2.5.1"
6162
},
6263
"sideEffects": false,
Lines changed: 72 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { existsSync, readdirSync, readFileSync } from 'fs';
1+
import { readdirSync, readFileSync } from 'fs';
22
import { join } from 'path';
3-
import { ApolloGateway } from '@apollo/gateway';
43
import { normalizedExecutor } from '@graphql-tools/executor';
54
import {
65
ExecutionResult,
@@ -9,6 +8,7 @@ import {
98
MapperKind,
109
mapSchema,
1110
} from '@graphql-tools/utils';
11+
import { assertSingleExecutionValue } from '@internal/testing';
1212
import {
1313
buildSchema,
1414
getNamedType,
@@ -19,86 +19,76 @@ import {
1919
printSchema,
2020
validate,
2121
} from 'graphql';
22-
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
22+
import { createRouter } from 'graphql-federation-gateway-audit';
23+
import { beforeAll, describe, expect, it } from 'vitest';
2324
import { getStitchedSchemaFromSupergraphSdl } from '../src/supergraph';
2425

2526
describe('Federation Compatibility', () => {
26-
if (!existsSync(join(__dirname, 'fixtures', 'federation-compatibility'))) {
27-
console.warn('Make sure you fetched the fixtures from the API first');
28-
it.skip('skipping tests', () => {});
29-
return;
30-
}
31-
const fixturesDir = join(__dirname, 'fixtures', 'federation-compatibility');
32-
readdirSync(fixturesDir).forEach((supergraphName) => {
33-
const supergraphFixturesDir = join(fixturesDir, supergraphName);
34-
const supergraphSdlPath = join(supergraphFixturesDir, 'supergraph.graphql');
35-
if (!existsSync(supergraphSdlPath)) {
36-
return;
27+
const auditRouter = createRouter();
28+
const supergraphList = readdirSync(
29+
join(
30+
__dirname,
31+
'../../../node_modules/graphql-federation-gateway-audit/src/test-suites',
32+
),
33+
);
34+
const supergraphSdlMap = new Map<string, string>();
35+
const supergraphTestMap = new Map<string, any>();
36+
beforeAll(async () => {
37+
const supergraphPathListRes = await auditRouter.fetch(
38+
'http://localhost/supergraphs',
39+
);
40+
if (!supergraphPathListRes.ok) {
41+
const error = await supergraphPathListRes.text();
42+
throw new Error(`Failed to fetch supergraph list: ${error}`);
43+
}
44+
const supergraphPathList = await supergraphPathListRes.json();
45+
for (const supergraphPath of supergraphPathList) {
46+
if (supergraphPath) {
47+
const supergraphRes = await auditRouter.fetch(supergraphPath);
48+
const supergraphPathParts = supergraphPath.split('/');
49+
const supergraphName =
50+
supergraphPathParts[supergraphPathParts.length - 2];
51+
const supergraphSdl = await supergraphRes.text();
52+
supergraphSdlMap.set(supergraphName, supergraphSdl);
53+
const testsPath = supergraphPath.replace('/supergraph', '/tests');
54+
const testsRes = await auditRouter.fetch(testsPath);
55+
const testsContent = await testsRes.json();
56+
supergraphTestMap.set(supergraphName, testsContent);
57+
}
3758
}
59+
});
60+
61+
for (const supergraphName of supergraphList) {
3862
describe(supergraphName, () => {
39-
const supergraphSdl = readFileSync(supergraphSdlPath, 'utf-8');
4063
let stitchedSchema: GraphQLSchema;
41-
// let apolloExecutor: Executor;
42-
// let apolloSubgraphCalls: Record<string, number> = {};
43-
let stitchingSubgraphCalls: Record<string, number> = {};
44-
let apolloGW: ApolloGateway;
45-
beforeAll(async () => {
64+
let supergraphSdl: string;
65+
const testFile = readFileSync(
66+
join(
67+
__dirname,
68+
'../../../node_modules/graphql-federation-gateway-audit/src/test-suites',
69+
supergraphName,
70+
'test.ts',
71+
),
72+
'utf-8',
73+
);
74+
let tests: { query: string; expected: any }[] = new Array<{
75+
query: string;
76+
expected: any;
77+
}>(testFile.match(/createTest\(/g)?.length ?? 0).fill({
78+
query: '',
79+
expected: {},
80+
});
81+
beforeAll(() => {
82+
supergraphSdl = supergraphSdlMap.get(supergraphName)!;
83+
tests = supergraphTestMap.get(supergraphName)!;
4684
stitchedSchema = getStitchedSchemaFromSupergraphSdl({
4785
supergraphSdl,
48-
onSubschemaConfig(subschemaConfig) {
49-
const actualExecutor = subschemaConfig.executor;
50-
subschemaConfig.executor = function tracedExecutor(execReq) {
51-
stitchingSubgraphCalls[subschemaConfig.name.toLowerCase()] =
52-
(stitchingSubgraphCalls[subschemaConfig.name] || 0) + 1;
53-
return actualExecutor(execReq);
54-
};
86+
httpExecutorOpts: {
87+
fetch: auditRouter.fetch,
5588
},
5689
batch: true,
5790
});
58-
/* apolloGW = new ApolloGateway({
59-
supergraphSdl,
60-
buildService({ name, url }) {
61-
const subgraphName = name;
62-
const actualService = new RemoteGraphQLDataSource({ url });
63-
return {
64-
process(options) {
65-
apolloSubgraphCalls[subgraphName.toLowerCase()] =
66-
(apolloSubgraphCalls[subgraphName.toLowerCase()] || 0) + 1;
67-
return actualService.process(options);
68-
},
69-
};
70-
},
71-
});
72-
const loadedGw = await apolloGW.load();
73-
apolloExecutor = function apolloExecutor(execReq) {
74-
const operationAST = getOperationAST(execReq.document, execReq.operationName);
75-
if (!operationAST) {
76-
throw new Error('Operation not found');
77-
}
78-
const printedDoc = print(execReq.document);
79-
return loadedGw.executor({
80-
request: {},
81-
logger: console,
82-
schema: loadedGw.schema,
83-
schemaHash: 'hash' as any,
84-
context: execReq.context as any,
85-
cache: new Map() as any,
86-
queryHash: 'hash' as any,
87-
document: execReq.document,
88-
source: printedDoc,
89-
operationName: execReq.operationName || null,
90-
operation: operationAST,
91-
metrics: {},
92-
overallCachePolicy: {},
93-
}) as ExecutionResult;
94-
}; */
95-
});
96-
afterAll(async () => {
97-
await apolloGW?.stop?.();
9891
});
99-
const tests: { query: string; expected: any }[] = JSON.parse(
100-
readFileSync(join(supergraphFixturesDir, 'tests.json'), 'utf-8'),
101-
);
10292
it('generates the expected schema', () => {
10393
const inputSchema = buildSchema(supergraphSdl, {
10494
noLocation: true,
@@ -153,26 +143,28 @@ describe('Federation Compatibility', () => {
153143
printSchema(sortedInputSchema).trim(),
154144
);
155145
});
156-
tests.forEach((test, i) => {
146+
tests.forEach((_, i) => {
157147
describe(`test-query-${i}`, () => {
158-
let result: ExecutionResult;
159-
beforeAll(async () => {
160-
// apolloSubgraphCalls = {};
161-
stitchingSubgraphCalls = {};
148+
it('gives the correct result', async () => {
149+
const test = tests[i];
150+
if (!test) {
151+
throw new Error(`Test ${i} not found`);
152+
}
162153
const document = parse(test.query, { noLocation: true });
163154
const validationErrors = validate(stitchedSchema, document);
155+
let result: ExecutionResult;
164156
if (validationErrors.length > 0) {
165157
result = {
166158
errors: validationErrors,
167159
};
168160
} else {
169-
result = (await normalizedExecutor({
161+
const execRes = await normalizedExecutor({
170162
schema: stitchedSchema,
171163
document,
172-
})) as ExecutionResult;
164+
});
165+
assertSingleExecutionValue(execRes);
166+
result = execRes;
173167
}
174-
});
175-
it('gives the correct result', () => {
176168
const received = {
177169
data: result.data ?? null,
178170
errors: !!result.errors?.length,
@@ -195,5 +187,5 @@ describe('Federation Compatibility', () => {
195187
});
196188
});
197189
});
198-
});
190+
}
199191
});

vitest.projects.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,4 @@ export default defineWorkspace([
3131
testTimeout,
3232
},
3333
},
34-
{
35-
extends: './vitest.config.ts',
36-
test: {
37-
name: 'e2e:bench',
38-
hookTimeout: testTimeout,
39-
testTimeout,
40-
benchmark: {
41-
include: ['e2e/**/*.bench.ts'],
42-
},
43-
},
44-
},
4534
]);

0 commit comments

Comments
 (0)