From c6ebbc20d36b888806b93f7ba4437a3a416155f5 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 14:24:27 +0300 Subject: [PATCH 1/9] Apollo Router integration --- .github/workflows/tests-integration.yaml | 36 +++++- integration-tests/package.json | 1 + .../tests/apollo-router/apollo-router.test.ts | 108 ++++++++++++++++++ .../apollo-router/apollo-router.test.yml | 4 + integration-tests/vite.config.ts | 1 + package.json | 1 + 6 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 integration-tests/tests/apollo-router/apollo-router.test.ts create mode 100644 integration-tests/tests/apollo-router/apollo-router.test.yml diff --git a/.github/workflows/tests-integration.yaml b/.github/workflows/tests-integration.yaml index bf02ac8ea2d..027151748c9 100644 --- a/.github/workflows/tests-integration.yaml +++ b/.github/workflows/tests-integration.yaml @@ -25,7 +25,7 @@ jobs: strategy: matrix: # Divide integration tests into 3 shards, to run them in parallel. - shardIndex: [1, 2, 3] + shardIndex: [1, 2, 3, 'apollo-router'] env: DOCKER_REGISTRY: ${{ inputs.registry }}/${{ inputs.imageName }}/ @@ -68,10 +68,40 @@ jobs: run: | docker compose -f docker/docker-compose.community.yml -f ./integration-tests/docker-compose.integration.yaml --env-file ./integration-tests/.env ps - - name: run integration tests + ## ---- START ---- Apollo Router specific steps + - if: matrix.shardIndex == 'apollo-router' + name: Install Rust + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1 + with: + toolchain: '1.91.1' + default: true + override: true + + - if: matrix.shardIndex == 'apollo-router' + name: Cache Rust + uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2 + + - if: matrix.shardIndex == 'apollo-router' + name: build apollo router + timeout-minutes: 10 + run: | + cargo build + + - if: matrix.shardIndex == 'apollo-router' + name: run apollo router integration tests + timeout-minutes: 10 + run: | + pnpm test:integration:apollo-router + + ## ---- END ---- Apollo Router specific steps + + - if: matrix.shardIndex != 'apollo-router' + name: run integration tests timeout-minutes: 10 run: | - VITEST_MAX_THREADS=${{ steps.cpu-cores.outputs.count }} pnpm --filter integration-tests test:integration --shard=${{ matrix.shardIndex }}/3 + pnpm test:integration --shard=${{ matrix.shardIndex }}/3 + env: + VITEST_MAX_THREADS: ${{ steps.cpu-cores.outputs.count }} - name: log dump if: ${{ failure() }} diff --git a/integration-tests/package.json b/integration-tests/package.json index 0f201abdd10..8f5175a9819 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -7,6 +7,7 @@ "prepare:env": "cd ../ && pnpm build:libraries && pnpm build:services", "start": "./local.sh", "test:integration": "vitest", + "test:integration:apollo-router": "TEST_APOLLO_ROUTER=1 vitest tests/apollo-router", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/integration-tests/tests/apollo-router/apollo-router.test.ts b/integration-tests/tests/apollo-router/apollo-router.test.ts new file mode 100644 index 00000000000..6ae99d5b685 --- /dev/null +++ b/integration-tests/tests/apollo-router/apollo-router.test.ts @@ -0,0 +1,108 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { ProjectType } from 'testkit/gql/graphql'; +import { initSeed } from 'testkit/seed'; +import { getServiceHost } from 'testkit/utils'; +import { execa } from '@esm2cjs/execa'; + +describe('Apollo Router Integration', () => { + const getBaseEndpoint = () => + getServiceHost('server', 8082).then(v => `http://${v}/artifacts/v1/`); + it('fetches the supergraph and sends usage reports', async () => { + const endpointBaseUrl = await getBaseEndpoint(); + const { createOrg } = await initSeed().createOwner(); + const { createProject } = await createOrg(); + const { createTargetAccessToken, createCdnAccess, target, waitForOperationsCollected } = + await createProject(ProjectType.Federation); + const writeToken = await createTargetAccessToken({}); + + // Publish Schema + const publishSchemaResult = await writeToken + .publishSchema({ + author: 'Arda', + commit: 'abc123', + sdl: /* GraphQL */ ` + type Query { + me: User + } + type User { + id: ID! + name: String! + } + `, + service: 'users', + url: 'https://federation-demo.theguild.workers.dev/users', + }) + .then(r => r.expectNoGraphQLErrors()); + + expect(publishSchemaResult.schemaPublish.__typename).toBe('SchemaPublishSuccess'); + const cdnAccessResult = await createCdnAccess(); + + const usageAddress = await getServiceHost('usage', 8081); + + const routerBinPath = join(__dirname, '../../../target/debug/router'); + if (!existsSync(routerBinPath)) { + throw new Error( + `Apollo Router binary not found at path: ${routerBinPath}, make sure to build it first with 'cargo build'`, + ); + } + const routerConfigPath = join(__dirname, 'apollo-router.test.yml'); + const routerProc = execa(routerBinPath, ['--dev', '--config', routerConfigPath], { + all: true, + env: { + HIVE_CDN_ENDPOINT: endpointBaseUrl + target.id, + HIVE_CDN_KEY: cdnAccessResult.secretAccessToken, + HIVE_ENDPOINT: `http://${usageAddress}`, + HIVE_TOKEN: writeToken.secret, + HIVE_TARGET_ID: target.id, + }, + }); + await new Promise((resolve, reject) => { + if (!routerProc.all) { + return reject(new Error('No stdout from Apollo Router process')); + } + let log = ''; + routerProc.all.on('data', data => { + log += data.toString(); + if (log.includes('GraphQL endpoint exposed at')) { + resolve(true); + } + }); + }); + + try { + const url = 'http://localhost:4000/'; + const response = await fetch(url, { + method: 'POST', + headers: { + accept: 'application/json', + 'content-type': 'application/json', + }, + body: JSON.stringify({ + query: ` + query TestQuery { + me { + id + name + } + } + `, + }), + }); + + expect(response.status).toBe(200); + const result = await response.json(); + expect(result).toEqual({ + data: { + me: { + id: '1', + name: 'Ada Lovelace', + }, + }, + }); + await waitForOperationsCollected(1); + } finally { + routerProc.cancel(); + } + }); +}); diff --git a/integration-tests/tests/apollo-router/apollo-router.test.yml b/integration-tests/tests/apollo-router/apollo-router.test.yml new file mode 100644 index 00000000000..a82654bd2c9 --- /dev/null +++ b/integration-tests/tests/apollo-router/apollo-router.test.yml @@ -0,0 +1,4 @@ +supergraph: + listen: 0.0.0.0:4000 +plugins: + hive.usage: {} diff --git a/integration-tests/vite.config.ts b/integration-tests/vite.config.ts index d14e9b16b73..737f092f327 100644 --- a/integration-tests/vite.config.ts +++ b/integration-tests/vite.config.ts @@ -29,5 +29,6 @@ export default defineConfig({ }, setupFiles, testTimeout: 90_000, + exclude: process.env.TEST_APOLLO_ROUTER ? [] : ['tests/apollo-router/**'], }, }); diff --git a/package.json b/package.json index 830e9de13c8..7f10c66cf3a 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "test:e2e:local": "CYPRESS_BASE_URL=http://localhost:3000 RUN_AGAINST_LOCAL_SERVICES=1 cypress open --browser chrome", "test:e2e:open": "CYPRESS_BASE_URL=$HIVE_APP_BASE_URL cypress open", "test:integration": "cd integration-tests && pnpm test:integration", + "test:integration:apollo-router": "cd integration-tests && pnpm test:integration:apollo-router", "typecheck": "pnpm run -r --filter '!hive' typecheck", "upload-sourcemaps": "./scripts/upload-sourcemaps.sh", "workspace": "pnpm run --filter $1 $2" From 96604afd25b9edddd03dadca536349707220a0d7 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 16:18:05 +0300 Subject: [PATCH 2/9] install protoc --- .github/workflows/tests-integration.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests-integration.yaml b/.github/workflows/tests-integration.yaml index 027151748c9..acaeeac5078 100644 --- a/.github/workflows/tests-integration.yaml +++ b/.github/workflows/tests-integration.yaml @@ -69,6 +69,13 @@ jobs: docker compose -f docker/docker-compose.community.yml -f ./integration-tests/docker-compose.integration.yaml --env-file ./integration-tests/.env ps ## ---- START ---- Apollo Router specific steps + + - if: matrix.shardIndex == 'apollo-router' + name: Install Protoc + uses: arduino/setup-protoc@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - if: matrix.shardIndex == 'apollo-router' name: Install Rust uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1 From 92767b868dabab6de1824690732a3a08b8c22c45 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 16:20:00 +0300 Subject: [PATCH 3/9] Go --- .../tests/apollo-router/apollo-router.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/integration-tests/tests/apollo-router/apollo-router.test.ts b/integration-tests/tests/apollo-router/apollo-router.test.ts index 6ae99d5b685..3d98c083e90 100644 --- a/integration-tests/tests/apollo-router/apollo-router.test.ts +++ b/integration-tests/tests/apollo-router/apollo-router.test.ts @@ -58,11 +58,17 @@ describe('Apollo Router Integration', () => { }, }); await new Promise((resolve, reject) => { - if (!routerProc.all) { + routerProc.catch(err => { + if (!err.isCanceled) { + reject(err); + } + }); + const routerProcOut = routerProc.all; + if (!routerProcOut) { return reject(new Error('No stdout from Apollo Router process')); } let log = ''; - routerProc.all.on('data', data => { + routerProcOut.on('data', data => { log += data.toString(); if (log.includes('GraphQL endpoint exposed at')) { resolve(true); From 58ca8b2d5bac18e598c9a3ff8e5ea4678563b947 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 16:52:52 +0300 Subject: [PATCH 4/9] Write config file --- .../tests/apollo-router/apollo-router.test.ts | 26 +++++++++++++++++-- .../apollo-router/apollo-router.test.yml | 4 --- 2 files changed, 24 insertions(+), 6 deletions(-) delete mode 100644 integration-tests/tests/apollo-router/apollo-router.test.yml diff --git a/integration-tests/tests/apollo-router/apollo-router.test.ts b/integration-tests/tests/apollo-router/apollo-router.test.ts index 3d98c083e90..1e8f98fccf3 100644 --- a/integration-tests/tests/apollo-router/apollo-router.test.ts +++ b/integration-tests/tests/apollo-router/apollo-router.test.ts @@ -1,4 +1,6 @@ -import { existsSync } from 'node:fs'; +import { existsSync, rmSync, writeFileSync } from 'node:fs'; +import { createServer } from 'node:http'; +import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { ProjectType } from 'testkit/gql/graphql'; import { initSeed } from 'testkit/seed'; @@ -8,7 +10,19 @@ import { execa } from '@esm2cjs/execa'; describe('Apollo Router Integration', () => { const getBaseEndpoint = () => getServiceHost('server', 8082).then(v => `http://${v}/artifacts/v1/`); + const getAvailablePort = () => + new Promise(resolve => { + const server = createServer(); + server.listen(0, () => { + const address = server.address(); + if (address && typeof address === 'object') { + const port = address.port; + server.close(() => resolve(port)); + } + }); + }); it('fetches the supergraph and sends usage reports', async () => { + const routerConfigPath = join(tmpdir(), `apollo-router-config-${Date.now()}.yaml`); const endpointBaseUrl = await getBaseEndpoint(); const { createOrg } = await initSeed().createOwner(); const { createProject } = await createOrg(); @@ -46,7 +60,14 @@ describe('Apollo Router Integration', () => { `Apollo Router binary not found at path: ${routerBinPath}, make sure to build it first with 'cargo build'`, ); } - const routerConfigPath = join(__dirname, 'apollo-router.test.yml'); + const routerPort = await getAvailablePort(); + const routerConfigContent = ` +supergraph: + listen: 0.0.0.0:${routerPort} +plugins: + hive.usage: {} +`.trim(); + writeFileSync(routerConfigPath, routerConfigContent, 'utf-8'); const routerProc = execa(routerBinPath, ['--dev', '--config', routerConfigPath], { all: true, env: { @@ -109,6 +130,7 @@ describe('Apollo Router Integration', () => { await waitForOperationsCollected(1); } finally { routerProc.cancel(); + rmSync(routerConfigPath); } }); }); diff --git a/integration-tests/tests/apollo-router/apollo-router.test.yml b/integration-tests/tests/apollo-router/apollo-router.test.yml deleted file mode 100644 index a82654bd2c9..00000000000 --- a/integration-tests/tests/apollo-router/apollo-router.test.yml +++ /dev/null @@ -1,4 +0,0 @@ -supergraph: - listen: 0.0.0.0:4000 -plugins: - hive.usage: {} From e99f1de93c146c6af8771254b5eb63a20d4d5a6e Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 17:14:08 +0300 Subject: [PATCH 5/9] Oops --- integration-tests/tests/apollo-router/apollo-router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/tests/apollo-router/apollo-router.test.ts b/integration-tests/tests/apollo-router/apollo-router.test.ts index 1e8f98fccf3..980d26a9c18 100644 --- a/integration-tests/tests/apollo-router/apollo-router.test.ts +++ b/integration-tests/tests/apollo-router/apollo-router.test.ts @@ -98,7 +98,7 @@ plugins: }); try { - const url = 'http://localhost:4000/'; + const url = `http://localhost:${routerPort}/`; const response = await fetch(url, { method: 'POST', headers: { From b862824b7c344fb0393ea5cd056c1ebcee23ad79 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 18:49:18 +0300 Subject: [PATCH 6/9] Increase timeout for Apollo Router integration tests --- .github/workflows/tests-integration.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests-integration.yaml b/.github/workflows/tests-integration.yaml index acaeeac5078..3a77d428658 100644 --- a/.github/workflows/tests-integration.yaml +++ b/.github/workflows/tests-integration.yaml @@ -90,13 +90,12 @@ jobs: - if: matrix.shardIndex == 'apollo-router' name: build apollo router - timeout-minutes: 10 run: | cargo build - if: matrix.shardIndex == 'apollo-router' name: run apollo router integration tests - timeout-minutes: 10 + timeout-minutes: 30 run: | pnpm test:integration:apollo-router @@ -104,7 +103,7 @@ jobs: - if: matrix.shardIndex != 'apollo-router' name: run integration tests - timeout-minutes: 10 + timeout-minutes: 30 run: | pnpm test:integration --shard=${{ matrix.shardIndex }}/3 env: From 899c02180c318e53a17e2009c5abfee51821aa91 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 18:51:00 +0300 Subject: [PATCH 7/9] Reduce integration tests timeout and update command --- .github/workflows/tests-integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-integration.yaml b/.github/workflows/tests-integration.yaml index 3a77d428658..89fa606a496 100644 --- a/.github/workflows/tests-integration.yaml +++ b/.github/workflows/tests-integration.yaml @@ -103,9 +103,9 @@ jobs: - if: matrix.shardIndex != 'apollo-router' name: run integration tests - timeout-minutes: 30 + timeout-minutes: 10 run: | - pnpm test:integration --shard=${{ matrix.shardIndex }}/3 + pnpm --filter integration-tests test:integration --shard=${{ matrix.shardIndex }}/3 env: VITEST_MAX_THREADS: ${{ steps.cpu-cores.outputs.count }} From 6a0d8fbb5477031bb011109a8b49a07a37868d59 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 20:53:30 +0300 Subject: [PATCH 8/9] Update tests-integration.yaml --- .github/workflows/tests-integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-integration.yaml b/.github/workflows/tests-integration.yaml index 89fa606a496..3a77d428658 100644 --- a/.github/workflows/tests-integration.yaml +++ b/.github/workflows/tests-integration.yaml @@ -103,9 +103,9 @@ jobs: - if: matrix.shardIndex != 'apollo-router' name: run integration tests - timeout-minutes: 10 + timeout-minutes: 30 run: | - pnpm --filter integration-tests test:integration --shard=${{ matrix.shardIndex }}/3 + pnpm test:integration --shard=${{ matrix.shardIndex }}/3 env: VITEST_MAX_THREADS: ${{ steps.cpu-cores.outputs.count }} From 160a3d988329923ed31eae6549cbd5068289814a Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 9 Dec 2025 22:24:56 +0300 Subject: [PATCH 9/9] Lets go --- integration-tests/vite.config.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/integration-tests/vite.config.ts b/integration-tests/vite.config.ts index 737f092f327..857f7412891 100644 --- a/integration-tests/vite.config.ts +++ b/integration-tests/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vitest/config'; +import { defaultExclude, defineConfig } from 'vitest/config'; const setupFiles = ['../scripts/serializer.ts', './expect.ts']; @@ -29,6 +29,8 @@ export default defineConfig({ }, setupFiles, testTimeout: 90_000, - exclude: process.env.TEST_APOLLO_ROUTER ? [] : ['tests/apollo-router/**'], + exclude: process.env.TEST_APOLLO_ROUTER + ? defaultExclude + : [...defaultExclude, 'tests/apollo-router/**'], }, });