Skip to content

Commit 856bf7e

Browse files
authored
Apollo Router Integration Tests (#7394)
1 parent b4df418 commit 856bf7e

File tree

5 files changed

+182
-5
lines changed

5 files changed

+182
-5
lines changed

.github/workflows/tests-integration.yaml

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
strategy:
2626
matrix:
2727
# Divide integration tests into 3 shards, to run them in parallel.
28-
shardIndex: [1, 2, 3]
28+
shardIndex: [1, 2, 3, 'apollo-router']
2929

3030
env:
3131
DOCKER_REGISTRY: ${{ inputs.registry }}/${{ inputs.imageName }}/
@@ -68,10 +68,46 @@ jobs:
6868
run: |
6969
docker compose -f docker/docker-compose.community.yml -f ./integration-tests/docker-compose.integration.yaml --env-file ./integration-tests/.env ps
7070
71-
- name: run integration tests
72-
timeout-minutes: 10
71+
## ---- START ---- Apollo Router specific steps
72+
73+
- if: matrix.shardIndex == 'apollo-router'
74+
name: Install Protoc
75+
uses: arduino/setup-protoc@v3
76+
with:
77+
repo-token: ${{ secrets.GITHUB_TOKEN }}
78+
79+
- if: matrix.shardIndex == 'apollo-router'
80+
name: Install Rust
81+
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1
82+
with:
83+
toolchain: '1.91.1'
84+
default: true
85+
override: true
86+
87+
- if: matrix.shardIndex == 'apollo-router'
88+
name: Cache Rust
89+
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
90+
91+
- if: matrix.shardIndex == 'apollo-router'
92+
name: build apollo router
93+
run: |
94+
cargo build
95+
96+
- if: matrix.shardIndex == 'apollo-router'
97+
name: run apollo router integration tests
98+
timeout-minutes: 30
99+
run: |
100+
pnpm test:integration:apollo-router
101+
102+
## ---- END ---- Apollo Router specific steps
103+
104+
- if: matrix.shardIndex != 'apollo-router'
105+
name: run integration tests
106+
timeout-minutes: 30
73107
run: |
74-
VITEST_MAX_THREADS=${{ steps.cpu-cores.outputs.count }} pnpm --filter integration-tests test:integration --shard=${{ matrix.shardIndex }}/3
108+
pnpm test:integration --shard=${{ matrix.shardIndex }}/3
109+
env:
110+
VITEST_MAX_THREADS: ${{ steps.cpu-cores.outputs.count }}
75111

76112
- name: log dump
77113
if: ${{ failure() }}

integration-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"prepare:env": "cd ../ && pnpm build:libraries && pnpm build:services",
88
"start": "./local.sh",
99
"test:integration": "vitest",
10+
"test:integration:apollo-router": "TEST_APOLLO_ROUTER=1 vitest tests/apollo-router",
1011
"typecheck": "tsc --noEmit"
1112
},
1213
"devDependencies": {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { existsSync, rmSync, writeFileSync } from 'node:fs';
2+
import { createServer } from 'node:http';
3+
import { tmpdir } from 'node:os';
4+
import { join } from 'node:path';
5+
import { ProjectType } from 'testkit/gql/graphql';
6+
import { initSeed } from 'testkit/seed';
7+
import { getServiceHost } from 'testkit/utils';
8+
import { execa } from '@esm2cjs/execa';
9+
10+
describe('Apollo Router Integration', () => {
11+
const getBaseEndpoint = () =>
12+
getServiceHost('server', 8082).then(v => `http://${v}/artifacts/v1/`);
13+
const getAvailablePort = () =>
14+
new Promise<number>(resolve => {
15+
const server = createServer();
16+
server.listen(0, () => {
17+
const address = server.address();
18+
if (address && typeof address === 'object') {
19+
const port = address.port;
20+
server.close(() => resolve(port));
21+
}
22+
});
23+
});
24+
it('fetches the supergraph and sends usage reports', async () => {
25+
const routerConfigPath = join(tmpdir(), `apollo-router-config-${Date.now()}.yaml`);
26+
const endpointBaseUrl = await getBaseEndpoint();
27+
const { createOrg } = await initSeed().createOwner();
28+
const { createProject } = await createOrg();
29+
const { createTargetAccessToken, createCdnAccess, target, waitForOperationsCollected } =
30+
await createProject(ProjectType.Federation);
31+
const writeToken = await createTargetAccessToken({});
32+
33+
// Publish Schema
34+
const publishSchemaResult = await writeToken
35+
.publishSchema({
36+
author: 'Arda',
37+
commit: 'abc123',
38+
sdl: /* GraphQL */ `
39+
type Query {
40+
me: User
41+
}
42+
type User {
43+
id: ID!
44+
name: String!
45+
}
46+
`,
47+
service: 'users',
48+
url: 'https://federation-demo.theguild.workers.dev/users',
49+
})
50+
.then(r => r.expectNoGraphQLErrors());
51+
52+
expect(publishSchemaResult.schemaPublish.__typename).toBe('SchemaPublishSuccess');
53+
const cdnAccessResult = await createCdnAccess();
54+
55+
const usageAddress = await getServiceHost('usage', 8081);
56+
57+
const routerBinPath = join(__dirname, '../../../target/debug/router');
58+
if (!existsSync(routerBinPath)) {
59+
throw new Error(
60+
`Apollo Router binary not found at path: ${routerBinPath}, make sure to build it first with 'cargo build'`,
61+
);
62+
}
63+
const routerPort = await getAvailablePort();
64+
const routerConfigContent = `
65+
supergraph:
66+
listen: 0.0.0.0:${routerPort}
67+
plugins:
68+
hive.usage: {}
69+
`.trim();
70+
writeFileSync(routerConfigPath, routerConfigContent, 'utf-8');
71+
const routerProc = execa(routerBinPath, ['--dev', '--config', routerConfigPath], {
72+
all: true,
73+
env: {
74+
HIVE_CDN_ENDPOINT: endpointBaseUrl + target.id,
75+
HIVE_CDN_KEY: cdnAccessResult.secretAccessToken,
76+
HIVE_ENDPOINT: `http://${usageAddress}`,
77+
HIVE_TOKEN: writeToken.secret,
78+
HIVE_TARGET_ID: target.id,
79+
},
80+
});
81+
await new Promise((resolve, reject) => {
82+
routerProc.catch(err => {
83+
if (!err.isCanceled) {
84+
reject(err);
85+
}
86+
});
87+
const routerProcOut = routerProc.all;
88+
if (!routerProcOut) {
89+
return reject(new Error('No stdout from Apollo Router process'));
90+
}
91+
let log = '';
92+
routerProcOut.on('data', data => {
93+
log += data.toString();
94+
if (log.includes('GraphQL endpoint exposed at')) {
95+
resolve(true);
96+
}
97+
});
98+
});
99+
100+
try {
101+
const url = `http://localhost:${routerPort}/`;
102+
const response = await fetch(url, {
103+
method: 'POST',
104+
headers: {
105+
accept: 'application/json',
106+
'content-type': 'application/json',
107+
},
108+
body: JSON.stringify({
109+
query: `
110+
query TestQuery {
111+
me {
112+
id
113+
name
114+
}
115+
}
116+
`,
117+
}),
118+
});
119+
120+
expect(response.status).toBe(200);
121+
const result = await response.json();
122+
expect(result).toEqual({
123+
data: {
124+
me: {
125+
id: '1',
126+
name: 'Ada Lovelace',
127+
},
128+
},
129+
});
130+
await waitForOperationsCollected(1);
131+
} finally {
132+
routerProc.cancel();
133+
rmSync(routerConfigPath);
134+
}
135+
});
136+
});

integration-tests/vite.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineConfig } from 'vitest/config';
1+
import { defaultExclude, defineConfig } from 'vitest/config';
22

33
const setupFiles = ['../scripts/serializer.ts', './expect.ts'];
44

@@ -29,5 +29,8 @@ export default defineConfig({
2929
},
3030
setupFiles,
3131
testTimeout: 90_000,
32+
exclude: process.env.TEST_APOLLO_ROUTER
33+
? defaultExclude
34+
: [...defaultExclude, 'tests/apollo-router/**'],
3235
},
3336
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"test:e2e:local": "CYPRESS_BASE_URL=http://localhost:3000 RUN_AGAINST_LOCAL_SERVICES=1 cypress open --browser chrome",
5353
"test:e2e:open": "CYPRESS_BASE_URL=$HIVE_APP_BASE_URL cypress open",
5454
"test:integration": "cd integration-tests && pnpm test:integration",
55+
"test:integration:apollo-router": "cd integration-tests && pnpm test:integration:apollo-router",
5556
"typecheck": "pnpm run -r --filter '!hive' typecheck",
5657
"upload-sourcemaps": "./scripts/upload-sourcemaps.sh",
5758
"workspace": "pnpm run --filter $1 $2"

0 commit comments

Comments
 (0)