diff --git a/e2e/self-hosting-hive/gateway.config.ts b/e2e/self-hosting-hive/gateway.config.ts new file mode 100644 index 00000000..4fbc2d30 --- /dev/null +++ b/e2e/self-hosting-hive/gateway.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from '@graphql-hive/gateway'; +import { Opts } from '@internal/testing'; + +const opts = Opts(process.argv); +const selfHostingPort = opts.getServicePort('selfHostingHive'); + +export const gatewayConfig = defineConfig({ + reporting: { + type: 'hive', + token: 'secret', + agent: { + maxRetries: 1, + maxSize: 1, + timeout: 200, + }, + selfHosting: { + applicationUrl: `http://localhost:${selfHostingPort}`, + graphqlEndpoint: `http://localhost:${selfHostingPort}/graphql`, + usageEndpoint: `http://localhost:${selfHostingPort}/usage`, + }, + }, +}); diff --git a/e2e/self-hosting-hive/package.json b/e2e/self-hosting-hive/package.json new file mode 100644 index 00000000..70e03825 --- /dev/null +++ b/e2e/self-hosting-hive/package.json @@ -0,0 +1,8 @@ +{ + "name": "@e2e/self-hosting-hive", + "private": true, + "dependencies": { + "@graphql-hive/gateway": "workspace:*", + "graphql": "16.9.0" + } +} diff --git a/e2e/self-hosting-hive/self-hosting-hive.e2e.ts b/e2e/self-hosting-hive/self-hosting-hive.e2e.ts new file mode 100644 index 00000000..077a9191 --- /dev/null +++ b/e2e/self-hosting-hive/self-hosting-hive.e2e.ts @@ -0,0 +1,24 @@ +import { setTimeout } from 'node:timers/promises'; +import { createExampleSetup, createTenv } from '@internal/e2e'; +import { describe, expect, it } from 'vitest'; + +describe('Self Hosting Hive', () => { + const { gateway, service } = createTenv(__dirname); + const { supergraph, query, result } = createExampleSetup(__dirname); + it('usage', async () => { + const selfHostingHive = await service('selfHostingHive'); + await using gw = await gateway({ + supergraph: await supergraph(), + services: [selfHostingHive], + }); + await expect( + gw.execute({ + query, + }), + ).resolves.toEqual(result); + await setTimeout(300); + const incomingData = selfHostingHive.getStd('out'); + // Check if `/usage` endpoint receives the POST request + expect(incomingData).toContain('POST /usage'); + }); +}); diff --git a/e2e/self-hosting-hive/services/selfHostingHive.ts b/e2e/self-hosting-hive/services/selfHostingHive.ts new file mode 100644 index 00000000..4aa0acee --- /dev/null +++ b/e2e/self-hosting-hive/services/selfHostingHive.ts @@ -0,0 +1,23 @@ +import { createServer } from 'http'; +import { Opts } from '@internal/testing'; + +const opts = Opts(process.argv); +const selfHostingPort = opts.getServicePort('selfHostingHive'); + +// Echo server + +createServer((req, res) => { + process.stdout.write(`${req.method} ${req.url}\n`); + res.writeHead(200, req.headers); + req.on('data', (chunk) => { + process.stdout.write(chunk); + res.write(chunk); + }); + req.on('end', () => { + res.end(); + }); +}).listen(selfHostingPort, () => { + process.stderr.write( + `Echo server listening on http://localhost:${selfHostingPort}\n`, + ); +}); diff --git a/internal/e2e/src/tenv.ts b/internal/e2e/src/tenv.ts index 425f9b08..582738e9 100644 --- a/internal/e2e/src/tenv.ts +++ b/internal/e2e/src/tenv.ts @@ -10,8 +10,7 @@ import { RemoteGraphQLDataSource, type ServiceEndpointDefinition, } from '@apollo/gateway'; -import { createDeferred } from '@graphql-tools/delegate'; -import { fakePromise } from '@graphql-tools/utils'; +import { createDeferred, fakePromise } from '@graphql-tools/utils'; import { boolEnv, createOpt, @@ -147,6 +146,7 @@ export interface ServeOptions extends ProcOptions { /** "docker" specific options. */ docker?: Partial>; }; + services?: Service[]; } export interface Gateway extends Server { @@ -358,6 +358,7 @@ export function createTenv(cwd: string): Tenv { env, runner, args = [], + services, } = opts || {}; let proc: Proc, @@ -471,6 +472,9 @@ export function createTenv(cwd: string): Tenv { createPortOpt(port), ...(supergraph ? ['supergraph', supergraph] : []), ...(subgraph ? ['subgraph', subgraph] : []), + ...(services?.map(({ name, port }) => + createServicePortOpt(name, port), + ) || []), ...args, ], volumes, @@ -485,6 +489,9 @@ export function createTenv(cwd: string): Tenv { path.resolve(__project, 'packages', 'gateway', 'src', 'bin.ts'), ...(supergraph ? ['supergraph', supergraph] : []), ...(subgraph ? ['subgraph', subgraph] : []), + ...(services?.map(({ name, port }) => + createServicePortOpt(name, port), + ) || []), ...args, createPortOpt(port), ); @@ -497,6 +504,9 @@ export function createTenv(cwd: string): Tenv { path.resolve(__project, 'packages', 'gateway', 'src', 'bin.ts'), ...(supergraph ? ['supergraph', supergraph] : []), ...(subgraph ? ['subgraph', subgraph] : []), + ...(services?.map(({ name, port }) => + createServicePortOpt(name, port), + ) || []), ...args, createPortOpt(port), ); @@ -986,7 +996,9 @@ function spawn( // process ended _and_ the stdio streams have been closed if (code) { exitDeferred.reject( - new Error(`Exit code ${code}\n${trimError(stdboth)}`), + new Error( + `Exit code ${code} from ${cmd} ${args.join(' ')}\n${trimError(stdboth)}`, + ), ); } else { exitDeferred.resolve(); diff --git a/packages/gateway/src/cli.ts b/packages/gateway/src/cli.ts index e6eaa52a..c956675e 100644 --- a/packages/gateway/src/cli.ts +++ b/packages/gateway/src/cli.ts @@ -324,8 +324,7 @@ export async function run(userCtx: Partial) { }; const { binName, productDescription, version } = ctx; - cli = cli.name(binName).description(productDescription); - cli.version(version); + cli = cli.name(binName).description(productDescription).version(version); if (cluster.worker?.id) { ctx.log = ctx.log.child(`Worker #${cluster.worker.id}`); diff --git a/packages/gateway/src/commands/supergraph.ts b/packages/gateway/src/commands/supergraph.ts index c3334fae..71cbbd1a 100644 --- a/packages/gateway/src/commands/supergraph.ts +++ b/packages/gateway/src/commands/supergraph.ts @@ -249,7 +249,9 @@ export const addCommand: AddCommand = (ctx, cli) => process.exit(1); } return runSupergraph(ctx, config); - }); + }) + .allowUnknownOption(process.env.NODE_ENV === 'test') + .allowExcessArguments(process.env.NODE_ENV === 'test'); export type SupergraphConfig = GatewayConfigSupergraph & GatewayCLIConfig; diff --git a/yarn.lock b/yarn.lock index 96f2436e..e274c064 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2547,6 +2547,15 @@ __metadata: languageName: unknown linkType: soft +"@e2e/self-hosting-hive@workspace:e2e/self-hosting-hive": + version: 0.0.0-use.local + resolution: "@e2e/self-hosting-hive@workspace:e2e/self-hosting-hive" + dependencies: + "@graphql-hive/gateway": "workspace:*" + graphql: "npm:16.9.0" + languageName: unknown + linkType: soft + "@e2e/subscriptions-cancellation@workspace:e2e/subscriptions-cancellation": version: 0.0.0-use.local resolution: "@e2e/subscriptions-cancellation@workspace:e2e/subscriptions-cancellation"