From 9ad2334466580a0078e5c16ec6efc917752cf0d8 Mon Sep 17 00:00:00 2001 From: mag123c Date: Thu, 14 Aug 2025 22:14:29 +0900 Subject: [PATCH] fix(sample): update gql federation samples to use production-ready --- .../README.md | 47 +++++- .../gateway/.gitignore | 5 +- .../gateway/generate-supergraph.ts | 114 +++++++++++++++ .../gateway/package.json | 4 + .../gateway/src/app.module.ts | 25 +++- .../gateway/supergraph.yaml | 10 ++ .../gateway/test/app.e2e-spec.ts | 74 ++++++++++ .../README.md | 54 ++++++- .../gateway/.gitignore | 6 +- .../gateway/generate-supergraph.ts | 137 ++++++++++++++++++ .../gateway/generate-typings.ts | 11 ++ .../gateway/package.json | 5 + .../gateway/src/app.module.ts | 25 +++- .../gateway/supergraph.yaml | 10 ++ .../gateway/test/app.e2e-spec.ts | 85 +++++++++++ 15 files changed, 592 insertions(+), 20 deletions(-) create mode 100644 sample/31-graphql-federation-code-first/gateway/generate-supergraph.ts create mode 100644 sample/31-graphql-federation-code-first/gateway/supergraph.yaml create mode 100644 sample/31-graphql-federation-code-first/gateway/test/app.e2e-spec.ts create mode 100644 sample/32-graphql-federation-schema-first/gateway/generate-supergraph.ts create mode 100644 sample/32-graphql-federation-schema-first/gateway/generate-typings.ts create mode 100644 sample/32-graphql-federation-schema-first/gateway/supergraph.yaml create mode 100644 sample/32-graphql-federation-schema-first/gateway/test/app.e2e-spec.ts diff --git a/sample/31-graphql-federation-code-first/README.md b/sample/31-graphql-federation-code-first/README.md index 2743338c4cb..bbc72be0d59 100644 --- a/sample/31-graphql-federation-code-first/README.md +++ b/sample/31-graphql-federation-code-first/README.md @@ -1,12 +1,25 @@ # GraphQL Federation - Code First -A simple example of GraphQL Federation using Code First approach. +A production-ready example of GraphQL Federation using Code First approach. + +## Installation + +Install dependencies for all applications: + +```sh +cd users-application && npm install +cd ../posts-application && npm install +cd ../gateway && npm install +``` ## Execution -Make sure to start the two sub-graph applications first, then the gateway. Otherwise the gateway won't be able to fetch schemas from the sub-graphs. +### Development Mode + +For development, the gateway will automatically generate a supergraph schema on startup: ```sh +# Start sub-graph applications first cd users-application && npm run start ``` @@ -15,9 +28,39 @@ cd posts-application && npm run start ``` ```sh +# The gateway will auto-generate supergraph.graphql on startup cd gateway && npm run start ``` +### Production Mode + +For production, generate the supergraph schema beforehand: + +```sh +# 1. Ensure sub-graphs are running +cd users-application && npm run start +cd ../posts-application && npm run start + +# 2. Generate supergraph schema using Rover CLI (requires @apollo/rover installation) +cd ../gateway && npm run generate:supergraph:rover + +# 3. Start the gateway with the static supergraph +npm run start:prod +``` + +## Supergraph Schema Generation + +The gateway provides two methods for generating the supergraph schema: + +1. **Local Generation (Development)**: `npm run generate:supergraph` + - Generates a simplified supergraph for local development + - Does not require Rover CLI + +2. **Rover CLI Generation (Production)**: `npm run generate:supergraph:rover` + - Uses Apollo Rover to compose the actual supergraph from running sub-graphs + - Requires Rover CLI: `npm install -g @apollo/rover` + - Recommended for production deployments + ## Access the graph You can reach the gateway under `http://localhost:3001/graphql` diff --git a/sample/31-graphql-federation-code-first/gateway/.gitignore b/sample/31-graphql-federation-code-first/gateway/.gitignore index c16ef026a31..443a69165b4 100644 --- a/sample/31-graphql-federation-code-first/gateway/.gitignore +++ b/sample/31-graphql-federation-code-first/gateway/.gitignore @@ -31,4 +31,7 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +# Generated files +supergraph.graphql \ No newline at end of file diff --git a/sample/31-graphql-federation-code-first/gateway/generate-supergraph.ts b/sample/31-graphql-federation-code-first/gateway/generate-supergraph.ts new file mode 100644 index 00000000000..2c978566351 --- /dev/null +++ b/sample/31-graphql-federation-code-first/gateway/generate-supergraph.ts @@ -0,0 +1,114 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; +import { readFileSync, writeFileSync } from 'fs'; +import { join } from 'path'; + +const execAsync = promisify(exec); + +async function generateSupergraph() { + try { + // Check if Rover CLI is installed + try { + await execAsync('rover --version'); + } catch (error) { + console.error('Rover CLI is not installed. Please install it first:'); + console.error('npm install -g @apollo/rover'); + process.exit(1); + } + + // Generate supergraph schema + console.log('Generating supergraph schema...'); + const { stdout, stderr } = await execAsync( + 'rover supergraph compose --config ./supergraph.yaml', + ); + + if (stderr && !stderr.includes('WARN')) { + console.error('Error generating supergraph:', stderr); + process.exit(1); + } + + // Write the supergraph schema to a file + writeFileSync(join(__dirname, 'supergraph.graphql'), stdout); + console.log('Supergraph schema generated successfully!'); + console.log('Output written to: supergraph.graphql'); + } catch (error) { + console.error('Failed to generate supergraph:', error); + process.exit(1); + } +} + +// For local development without Rover +async function generateSupergraphLocal() { + console.log('Generating local supergraph schema for development...'); + + // This is a simplified supergraph for local development + // In production, use Rover CLI to generate the actual supergraph + const supergraphSchema = ` +schema + @link(url: "https://specs.apollo.dev/federation/v2.3") + @link(url: "https://specs.apollo.dev/link/v1.0") +{ + query: Query +} + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @key(fields: String!, resolvable: Boolean = true) on OBJECT | INTERFACE + +directive @requires(fields: String!) on FIELD_DEFINITION + +directive @provides(fields: String!) on FIELD_DEFINITION + +directive @external on OBJECT | FIELD_DEFINITION + +directive @shareable on OBJECT | FIELD_DEFINITION + +directive @extends on OBJECT | INTERFACE + +scalar link__Import + +enum link__Purpose { + SECURITY + EXECUTION +} + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} + +union _Entity = User | Post + +scalar _Any + +type _Service { + sdl: String! +} + +type User @key(fields: "id") { + id: ID! + name: String! + posts: [Post!]! +} + +type Post @key(fields: "id") { + id: ID! + title: String! + body: String! + authorId: ID! + user: User! +} +`; + + writeFileSync(join(__dirname, 'supergraph.graphql'), supergraphSchema); + console.log('Local supergraph schema generated successfully!'); +} + +// Check command line arguments +const useRover = process.argv.includes('--rover'); + +if (useRover) { + generateSupergraph(); +} else { + generateSupergraphLocal(); +} diff --git a/sample/31-graphql-federation-code-first/gateway/package.json b/sample/31-graphql-federation-code-first/gateway/package.json index 7f42db6d5d5..c5720e188a5 100644 --- a/sample/31-graphql-federation-code-first/gateway/package.json +++ b/sample/31-graphql-federation-code-first/gateway/package.json @@ -11,6 +11,10 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", + "generate:supergraph": "ts-node generate-supergraph.ts", + "generate:supergraph:rover": "ts-node generate-supergraph.ts --rover", + "prestart": "npm run generate:supergraph", + "prestart:dev": "npm run generate:supergraph", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/sample/31-graphql-federation-code-first/gateway/src/app.module.ts b/sample/31-graphql-federation-code-first/gateway/src/app.module.ts index c4900e92347..ddfb133c2a3 100644 --- a/sample/31-graphql-federation-code-first/gateway/src/app.module.ts +++ b/sample/31-graphql-federation-code-first/gateway/src/app.module.ts @@ -1,19 +1,30 @@ -import { IntrospectAndCompose } from '@apollo/gateway'; import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +// For development, you can use IntrospectAndCompose (not recommended for production) +// import { IntrospectAndCompose } from '@apollo/gateway'; + +const supergraphSdl = readFileSync( + join(__dirname, '..', 'supergraph.graphql'), + 'utf-8', +).trim(); @Module({ imports: [ GraphQLModule.forRoot({ driver: ApolloGatewayDriver, gateway: { - supergraphSdl: new IntrospectAndCompose({ - subgraphs: [ - { name: 'users', url: 'http://localhost:3002/graphql' }, - { name: 'posts', url: 'http://localhost:3003/graphql' }, - ], - }), + supergraphSdl, + // For development only - not recommended for production + // supergraphSdl: new IntrospectAndCompose({ + // subgraphs: [ + // { name: 'users', url: 'http://localhost:3002/graphql' }, + // { name: 'posts', url: 'http://localhost:3003/graphql' }, + // ], + // }), }, }), ], diff --git a/sample/31-graphql-federation-code-first/gateway/supergraph.yaml b/sample/31-graphql-federation-code-first/gateway/supergraph.yaml new file mode 100644 index 00000000000..10c277defe2 --- /dev/null +++ b/sample/31-graphql-federation-code-first/gateway/supergraph.yaml @@ -0,0 +1,10 @@ +federation_version: =2.3.2 +subgraphs: + users: + routing_url: http://localhost:3002/graphql + schema: + subgraph_url: http://localhost:3002/graphql + posts: + routing_url: http://localhost:3003/graphql + schema: + subgraph_url: http://localhost:3003/graphql \ No newline at end of file diff --git a/sample/31-graphql-federation-code-first/gateway/test/app.e2e-spec.ts b/sample/31-graphql-federation-code-first/gateway/test/app.e2e-spec.ts new file mode 100644 index 00000000000..07e34e075c4 --- /dev/null +++ b/sample/31-graphql-federation-code-first/gateway/test/app.e2e-spec.ts @@ -0,0 +1,74 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from '../src/app.module'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +describe('GraphQL Federation Gateway (Code First)', () => { + let app: INestApplication; + + beforeAll(async () => { + // Ensure supergraph.graphql exists + const supergraphPath = join(__dirname, '..', 'supergraph.graphql'); + if (!existsSync(supergraphPath)) { + // Generate a default supergraph for testing + require('../generate-supergraph'); + } + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should load gateway with static supergraph', () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query { + _service { + sdl + } + } + `, + }) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.data._service).toBeDefined(); + expect(res.body.data._service.sdl).toContain('type User'); + expect(res.body.data._service.sdl).toContain('type Post'); + }); + }); + + it('should not use IntrospectAndCompose in production', () => { + const appModulePath = join(__dirname, '..', 'src', 'app.module.ts'); + const fs = require('fs'); + const appModuleContent = fs.readFileSync(appModulePath, 'utf-8'); + + // Check that IntrospectAndCompose is commented out or not used + const hasActiveIntrospect = + appModuleContent.includes('new IntrospectAndCompose') && + !appModuleContent.includes('// supergraphSdl: new IntrospectAndCompose'); + + expect(hasActiveIntrospect).toBe(false); + }); + + it('should read supergraph from file system', () => { + const appModulePath = join(__dirname, '..', 'src', 'app.module.ts'); + const fs = require('fs'); + const appModuleContent = fs.readFileSync(appModulePath, 'utf-8'); + + // Check that the module reads from a file + expect(appModuleContent).toContain('readFileSync'); + expect(appModuleContent).toContain('supergraph.graphql'); + }); +}); diff --git a/sample/32-graphql-federation-schema-first/README.md b/sample/32-graphql-federation-schema-first/README.md index 7a0f47a9d98..b0d95e90f38 100644 --- a/sample/32-graphql-federation-schema-first/README.md +++ b/sample/32-graphql-federation-schema-first/README.md @@ -1,12 +1,25 @@ # GraphQL Federation - Schema First -A simple example of GraphQL Federation using Schema First approach. +A production-ready example of GraphQL Federation using Schema First approach. + +## Installation + +Install dependencies for all applications: + +```sh +cd users-application && npm install +cd ../posts-application && npm install +cd ../gateway && npm install +``` ## Execution -Make sure to start the two sub-graph applications first, then the gateway. Otherwise the gateway won't be able to fetch schemas from the sub-graphs. +### Development Mode + +For development, the gateway will automatically generate a supergraph schema from local schema files: ```sh +# Start sub-graph applications first cd users-application && npm run start ``` @@ -15,9 +28,46 @@ cd posts-application && npm run start ``` ```sh +# The gateway will auto-generate supergraph.graphql and TypeScript types on startup cd gateway && npm run start ``` +### Production Mode + +For production, generate the supergraph schema and types beforehand: + +```sh +# 1. Ensure sub-graphs are running (for Rover CLI method) +cd users-application && npm run start +cd ../posts-application && npm run start + +# 2. Generate supergraph schema using Rover CLI (requires @apollo/rover installation) +cd ../gateway && npm run generate:supergraph:rover + +# 3. Generate TypeScript types from supergraph +npm run generate:typings + +# 4. Start the gateway with the static supergraph +npm run start:prod +``` + +## Schema Generation + +The gateway provides multiple generation scripts: + +1. **Supergraph Generation (Local)**: `npm run generate:supergraph` + - Composes supergraph from local `.graphql` files + - Suitable for development when sub-graphs share the same repository + +2. **Supergraph Generation (Rover)**: `npm run generate:supergraph:rover` + - Uses Apollo Rover to compose supergraph from running sub-graphs + - Requires Rover CLI: `npm install -g @apollo/rover` + - Recommended for production deployments + +3. **TypeScript Types Generation**: `npm run generate:typings` + - Generates TypeScript interfaces from the supergraph schema + - Provides type safety for resolvers and GraphQL operations + ## Access the graph You can reach the gateway under `http://localhost:3002/graphql` diff --git a/sample/32-graphql-federation-schema-first/gateway/.gitignore b/sample/32-graphql-federation-schema-first/gateway/.gitignore index c16ef026a31..2cea0b0a1d8 100644 --- a/sample/32-graphql-federation-schema-first/gateway/.gitignore +++ b/sample/32-graphql-federation-schema-first/gateway/.gitignore @@ -31,4 +31,8 @@ lerna-debug.log* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +# Generated files +supergraph.graphql +src/graphql.schema.ts \ No newline at end of file diff --git a/sample/32-graphql-federation-schema-first/gateway/generate-supergraph.ts b/sample/32-graphql-federation-schema-first/gateway/generate-supergraph.ts new file mode 100644 index 00000000000..537fc8024bd --- /dev/null +++ b/sample/32-graphql-federation-schema-first/gateway/generate-supergraph.ts @@ -0,0 +1,137 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { join } from 'path'; + +const execAsync = promisify(exec); + +async function generateSupergraph() { + try { + // Check if Rover CLI is installed + try { + await execAsync('rover --version'); + } catch (error) { + console.error('Rover CLI is not installed. Please install it first:'); + console.error('npm install -g @apollo/rover'); + process.exit(1); + } + + // Generate supergraph schema + console.log('Generating supergraph schema...'); + const { stdout, stderr } = await execAsync( + 'rover supergraph compose --config ./supergraph.yaml', + ); + + if (stderr && !stderr.includes('WARN')) { + console.error('Error generating supergraph:', stderr); + process.exit(1); + } + + // Write the supergraph schema to a file + writeFileSync(join(__dirname, 'supergraph.graphql'), stdout); + console.log('Supergraph schema generated successfully!'); + console.log('Output written to: supergraph.graphql'); + } catch (error) { + console.error('Failed to generate supergraph:', error); + process.exit(1); + } +} + +// For local development - compose schemas from files +async function generateSupergraphLocal() { + console.log('Generating local supergraph schema from schema files...'); + + const usersSchemaPath = join( + __dirname, + '..', + 'users-application', + 'src', + 'users', + 'users.graphql', + ); + const postsSchemaPath = join( + __dirname, + '..', + 'posts-application', + 'src', + 'posts', + 'posts.graphql', + ); + + if (!existsSync(usersSchemaPath) || !existsSync(postsSchemaPath)) { + console.error( + 'Schema files not found. Make sure both sub-graph applications have their schema files.', + ); + process.exit(1); + } + + const usersSchema = readFileSync(usersSchemaPath, 'utf-8'); + const postsSchema = readFileSync(postsSchemaPath, 'utf-8'); + + // Combine schemas for local development + // This is a simplified approach - in production, use Rover CLI + const supergraphSchema = ` +schema + @link(url: "https://specs.apollo.dev/federation/v2.3") + @link(url: "https://specs.apollo.dev/link/v1.0") +{ + query: Query +} + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @key(fields: String!, resolvable: Boolean = true) on OBJECT | INTERFACE + +directive @requires(fields: String!) on FIELD_DEFINITION + +directive @provides(fields: String!) on FIELD_DEFINITION + +directive @external on OBJECT | FIELD_DEFINITION + +directive @shareable on OBJECT | FIELD_DEFINITION + +directive @extends on OBJECT | INTERFACE + +scalar link__Import + +enum link__Purpose { + SECURITY + EXECUTION +} + +type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! + getUser(id: ID!): User + getPosts: [Post] + findPost(id: ID!): Post +} + +union _Entity = User | Post + +scalar _Any + +type _Service { + sdl: String! +} + +${usersSchema.replace(/^extend type Query[\s\S]*?}$/m, '')} + +${postsSchema.replace(/^extend type Query[\s\S]*?}$/m, '').replace(/^extend type User[\s\S]*?}$/m, '')} +`; + + writeFileSync(join(__dirname, 'supergraph.graphql'), supergraphSchema); + console.log('Local supergraph schema generated successfully!'); + console.log( + 'Note: For production, use Rover CLI with actual running sub-graphs.', + ); +} + +// Check command line arguments +const useRover = process.argv.includes('--rover'); + +if (useRover) { + generateSupergraph(); +} else { + generateSupergraphLocal(); +} diff --git a/sample/32-graphql-federation-schema-first/gateway/generate-typings.ts b/sample/32-graphql-federation-schema-first/gateway/generate-typings.ts new file mode 100644 index 00000000000..8c9f7249670 --- /dev/null +++ b/sample/32-graphql-federation-schema-first/gateway/generate-typings.ts @@ -0,0 +1,11 @@ +import { GraphQLDefinitionsFactory } from '@nestjs/graphql'; +import { join } from 'path'; + +const definitionsFactory = new GraphQLDefinitionsFactory(); +definitionsFactory.generate({ + typePaths: ['./supergraph.graphql'], + path: join(process.cwd(), 'src/graphql.schema.ts'), + outputAs: 'interface', + skipResolverArgs: true, + federation: true, +}); diff --git a/sample/32-graphql-federation-schema-first/gateway/package.json b/sample/32-graphql-federation-schema-first/gateway/package.json index a9e0a335044..c88decdee8d 100644 --- a/sample/32-graphql-federation-schema-first/gateway/package.json +++ b/sample/32-graphql-federation-schema-first/gateway/package.json @@ -13,6 +13,11 @@ "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", + "generate:supergraph": "ts-node generate-supergraph.ts", + "generate:supergraph:rover": "ts-node generate-supergraph.ts --rover", + "generate:typings": "ts-node generate-typings.ts", + "prestart": "npm run generate:supergraph && npm run generate:typings", + "prestart:dev": "npm run generate:supergraph && npm run generate:typings", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/sample/32-graphql-federation-schema-first/gateway/src/app.module.ts b/sample/32-graphql-federation-schema-first/gateway/src/app.module.ts index 0ee8b303dde..7f6825f8cb0 100644 --- a/sample/32-graphql-federation-schema-first/gateway/src/app.module.ts +++ b/sample/32-graphql-federation-schema-first/gateway/src/app.module.ts @@ -1,19 +1,30 @@ -import { IntrospectAndCompose } from '@apollo/gateway'; import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +// For development, you can use IntrospectAndCompose (not recommended for production) +// import { IntrospectAndCompose } from '@apollo/gateway'; + +const supergraphSdl = readFileSync( + join(__dirname, '..', 'supergraph.graphql'), + 'utf-8', +).trim(); @Module({ imports: [ GraphQLModule.forRoot({ driver: ApolloGatewayDriver, gateway: { - supergraphSdl: new IntrospectAndCompose({ - subgraphs: [ - { name: 'users', url: 'http://localhost:3000/graphql' }, - { name: 'posts', url: 'http://localhost:3001/graphql' }, - ], - }), + supergraphSdl, + // For development only - not recommended for production + // supergraphSdl: new IntrospectAndCompose({ + // subgraphs: [ + // { name: 'users', url: 'http://localhost:3000/graphql' }, + // { name: 'posts', url: 'http://localhost:3001/graphql' }, + // ], + // }), }, }), ], diff --git a/sample/32-graphql-federation-schema-first/gateway/supergraph.yaml b/sample/32-graphql-federation-schema-first/gateway/supergraph.yaml new file mode 100644 index 00000000000..a683a037757 --- /dev/null +++ b/sample/32-graphql-federation-schema-first/gateway/supergraph.yaml @@ -0,0 +1,10 @@ +federation_version: =2.3.2 +subgraphs: + users: + routing_url: http://localhost:3000/graphql + schema: + file: ../users-application/src/users/users.graphql + posts: + routing_url: http://localhost:3001/graphql + schema: + file: ../posts-application/src/posts/posts.graphql \ No newline at end of file diff --git a/sample/32-graphql-federation-schema-first/gateway/test/app.e2e-spec.ts b/sample/32-graphql-federation-schema-first/gateway/test/app.e2e-spec.ts new file mode 100644 index 00000000000..4d04d4ccfda --- /dev/null +++ b/sample/32-graphql-federation-schema-first/gateway/test/app.e2e-spec.ts @@ -0,0 +1,85 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from '../src/app.module'; +import { existsSync } from 'fs'; +import { join } from 'path'; + +describe('GraphQL Federation Gateway (Schema First)', () => { + let app: INestApplication; + + beforeAll(async () => { + // Ensure supergraph.graphql exists + const supergraphPath = join(__dirname, '..', 'supergraph.graphql'); + if (!existsSync(supergraphPath)) { + // Generate a default supergraph for testing + require('../generate-supergraph'); + } + + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + it('should load gateway with static supergraph', () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + query: ` + query { + _service { + sdl + } + } + `, + }) + .expect(200) + .expect((res) => { + expect(res.body.data).toBeDefined(); + expect(res.body.data._service).toBeDefined(); + expect(res.body.data._service.sdl).toContain('type User'); + expect(res.body.data._service.sdl).toContain('type Post'); + }); + }); + + it('should not use IntrospectAndCompose in production', () => { + const appModulePath = join(__dirname, '..', 'src', 'app.module.ts'); + const fs = require('fs'); + const appModuleContent = fs.readFileSync(appModulePath, 'utf-8'); + + // Check that IntrospectAndCompose is commented out or not used + const hasActiveIntrospect = + appModuleContent.includes('new IntrospectAndCompose') && + !appModuleContent.includes('// supergraphSdl: new IntrospectAndCompose'); + + expect(hasActiveIntrospect).toBe(false); + }); + + it('should read supergraph from file system', () => { + const appModulePath = join(__dirname, '..', 'src', 'app.module.ts'); + const fs = require('fs'); + const appModuleContent = fs.readFileSync(appModulePath, 'utf-8'); + + // Check that the module reads from a file + expect(appModuleContent).toContain('readFileSync'); + expect(appModuleContent).toContain('supergraph.graphql'); + }); + + it('should have schema generation scripts configured', () => { + const packageJsonPath = join(__dirname, '..', 'package.json'); + const fs = require('fs'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + + // Check for required scripts + expect(packageJson.scripts['generate:supergraph']).toBeDefined(); + expect(packageJson.scripts['generate:supergraph:rover']).toBeDefined(); + expect(packageJson.scripts['generate:typings']).toBeDefined(); + }); +});