nodejs-quickstart-structure 1.16.2 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +15 -0
  3. package/README.md +144 -135
  4. package/bin/index.js +92 -89
  5. package/lib/generator.js +3 -2
  6. package/lib/modules/app-setup.js +49 -12
  7. package/lib/modules/caching-setup.js +1 -1
  8. package/lib/modules/config-files.js +32 -3
  9. package/lib/modules/database-setup.js +2 -2
  10. package/lib/modules/kafka-setup.js +8 -8
  11. package/lib/prompts.js +16 -1
  12. package/package.json +14 -2
  13. package/templates/clean-architecture/ts/src/index.ts.ejs +1 -2
  14. package/templates/common/.cursorrules.ejs +1 -1
  15. package/templates/common/.env.example.ejs +1 -1
  16. package/templates/common/.gitlab-ci.yml.ejs +55 -4
  17. package/templates/common/Dockerfile +12 -2
  18. package/templates/common/Jenkinsfile.ejs +32 -21
  19. package/templates/common/README.md.ejs +19 -9
  20. package/templates/common/SECURITY.md +20 -0
  21. package/templates/common/_github/workflows/{ci.yml → ci.yml.ejs} +13 -7
  22. package/templates/common/_github/workflows/security.yml.ejs +36 -0
  23. package/templates/common/_husky/pre-commit +4 -0
  24. package/templates/common/caching/js/redisClient.spec.js.ejs +0 -2
  25. package/templates/common/docker-compose.yml.ejs +13 -20
  26. package/templates/common/ecosystem.config.js.ejs +1 -1
  27. package/templates/common/jest.config.js.ejs +1 -0
  28. package/templates/common/jest.e2e.config.js.ejs +8 -0
  29. package/templates/common/kafka/js/config/kafka.js +2 -1
  30. package/templates/common/kafka/js/config/kafka.spec.js.ejs +6 -0
  31. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +6 -0
  32. package/templates/common/kafka/ts/config/kafka.ts +2 -1
  33. package/templates/common/package.json.ejs +14 -9
  34. package/templates/common/prompts/add-feature.md.ejs +1 -1
  35. package/templates/common/prompts/project-context.md.ejs +1 -1
  36. package/templates/common/scripts/run-e2e.js.ejs +63 -0
  37. package/templates/common/sonar-project.properties.ejs +27 -0
  38. package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +49 -0
  39. package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +49 -0
  40. package/templates/mvc/js/src/index.js.ejs +1 -1
@@ -0,0 +1,20 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the latest `main` branch is supported for security updates.
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | 1.0.x | :white_check_mark: |
10
+ | < 1.0 | :x: |
11
+
12
+ ## Reporting a Vulnerability
13
+
14
+ We take the security of this project seriously. If you believe you have found a security vulnerability, please report it following these steps:
15
+
16
+ 1. **Do not open a public issue.**
17
+ 2. Send an email to the project maintainers (see `package.json`).
18
+ 3. Provide a detailed description of the vulnerability, including steps to reproduce.
19
+
20
+ We will acknowledge your report within 48 hours and work on a fix as soon as possible.
@@ -14,17 +14,13 @@ jobs:
14
14
 
15
15
  runs-on: ubuntu-latest
16
16
 
17
- strategy:
18
- matrix:
19
- node-version: [20.x, 22.x]
20
-
21
17
  steps:
22
18
  - uses: actions/checkout@v4
23
19
 
24
- - name: Use Node.js ${{ matrix.node-version }}
20
+ - name: Use Node.js 22.x
25
21
  uses: actions/setup-node@v4
26
22
  with:
27
- node-version: ${{ matrix.node-version }}
23
+ node-version: 22.x
28
24
  cache: 'npm'
29
25
 
30
26
  - name: Install Dependencies
@@ -33,8 +29,18 @@ jobs:
33
29
  - name: Lint Code
34
30
  run: npm run lint
35
31
 
36
- - name: Run Tests
32
+ - name: Run Unit Tests
37
33
  run: npm run test:coverage
38
34
 
35
+ - name: Run E2E Tests
36
+ run: npm run test:e2e
37
+
39
38
  - name: Build
40
39
  run: npm run build --if-present
40
+ <% if (includeSecurity) { %>
41
+ - name: SonarQube Scan
42
+ uses: SonarSource/sonarqube-scan-action@master
43
+ env:
44
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
45
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
46
+ <% } %>
@@ -0,0 +1,36 @@
1
+ name: Enterprise Security Scan
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+ schedule:
9
+ - cron: '0 0 * * 1' # Weekly scan
10
+
11
+ jobs:
12
+ node-security:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Run Snyk to check for vulnerabilities
18
+ uses: snyk/actions/node@master
19
+ env:
20
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
21
+ with:
22
+ args: --severity-threshold=high
23
+
24
+ container-security:
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ - name: Build Docker image
29
+ run: docker build -t <%= projectName %>:latest .
30
+ - name: Run Snyk to check Docker image for vulnerabilities
31
+ uses: snyk/actions/docker@master
32
+ env:
33
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
34
+ with:
35
+ image: <%= projectName %>:latest
36
+ args: --file=Dockerfile --severity-threshold=high --skip-unused-projects
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ npx lint-staged
@@ -1,5 +1,3 @@
1
- const Redis = require('ioredis');
2
-
3
1
  jest.mock('ioredis', () => {
4
2
  const mRedis = jest.fn(() => ({
5
3
  on: jest.fn(),
@@ -14,7 +14,7 @@ services:
14
14
  <%_ } -%>
15
15
  <%_ if (communication === 'Kafka') { -%>
16
16
  environment:
17
- - KAFKA_BROKER=kafka:29092
17
+ - KAFKA_BROKER=kafka:9092
18
18
  - KAFKAJS_NO_PARTITIONER_WARNING=1
19
19
  - PORT=3000
20
20
  <%_ if (caching === 'Redis') { -%>
@@ -99,7 +99,7 @@ services:
99
99
  - mongodb_data:/data/db
100
100
 
101
101
  mongo-migrate:
102
- image: node:22-alpine
102
+ image: node:22-slim
103
103
  working_dir: /app
104
104
  volumes:
105
105
  - .:/app
@@ -131,27 +131,20 @@ services:
131
131
  - db
132
132
  <%_ } -%>
133
133
  <%_ if (communication === 'Kafka') { -%>
134
- zookeeper:
135
- image: confluentinc/cp-zookeeper:7.4.0
136
- environment:
137
- ZOOKEEPER_CLIENT_PORT: 2181
138
- ZOOKEEPER_TICK_TIME: 2000
139
- ports:
140
- - "${ZOOKEEPER_PORT:-2181}:2181"
141
-
142
134
  kafka:
143
- image: confluentinc/cp-kafka:7.4.0
144
- depends_on:
145
- - zookeeper
135
+ image: bitnamilegacy/kafka:3.4.1-debian-12-r39
146
136
  ports:
147
- - "${KAFKA_PORT:-9092}:9092"
137
+ - "${KAFKA_EXTERNAL_PORT:-9093}:9093"
148
138
  environment:
149
- KAFKA_BROKER_ID: 1
150
- KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
151
- KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
152
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
153
- KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
154
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
139
+ - KAFKA_ENABLE_KRAFT=yes
140
+ - KAFKA_CFG_PROCESS_ROLES=broker,controller
141
+ - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=1@localhost:9094
142
+ - KAFKA_CFG_NODE_ID=1
143
+ - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:${KAFKA_PORT:-9093}
144
+ - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:9093,CONTROLLER://:9094
145
+ - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
146
+ - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
147
+ - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
155
148
  <%_ } -%>
156
149
  <%_ if (caching === 'Redis') { -%>
157
150
  redis:
@@ -15,7 +15,7 @@ module.exports = {
15
15
  REDIS_PASSWORD: "",
16
16
  <%_ } -%>
17
17
  <%_ if (communication === 'Kafka') { -%>
18
- KAFKA_BROKER: "127.0.0.1:9092",
18
+ KAFKA_BROKER: "127.0.0.1:9093",
19
19
  KAFKAJS_NO_PARTITIONER_WARNING: 1,
20
20
  <%_ } -%>
21
21
  <%_ if (database !== 'None') { -%>
@@ -3,6 +3,7 @@ module.exports = {
3
3
  coverageDirectory: 'coverage',
4
4
  collectCoverageFrom: ['src/**/*.{js,ts}'],
5
5
  testMatch: ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'],
6
+ testPathIgnorePatterns: ['/node_modules/', '/tests/e2e/'],
6
7
  <% if (language === 'TypeScript') { %>preset: 'ts-jest',<% } %>
7
8
  moduleNameMapper: {
8
9
  '^@/(.*)$': '<rootDir>/src/$1',
@@ -0,0 +1,8 @@
1
+ <%_ if (language === 'TypeScript') { _%>/* eslint-disable @typescript-eslint/no-require-imports */<%_ } _%>
2
+ module.exports = {
3
+ ...require('./jest.config'),
4
+ testMatch: ['<rootDir>/tests/e2e/**/*.test.ts', '<rootDir>/tests/e2e/**/*.test.js'],
5
+ testPathIgnorePatterns: ['/node_modules/'],
6
+ testTimeout: 30000,
7
+ clearMocks: true
8
+ };
@@ -1,8 +1,9 @@
1
1
  const { Kafka } = require('kafkajs');
2
+ const { env } = require('./env');
2
3
 
3
4
  const kafka = new Kafka({
4
5
  clientId: 'nodejs-service',
5
- brokers: [process.env.KAFKA_BROKER || 'localhost:9092']
6
+ brokers: [env.KAFKA_BROKER]
6
7
  });
7
8
 
8
9
  module.exports = { kafka };
@@ -9,6 +9,12 @@ jest.mock('kafkajs', () => {
9
9
  };
10
10
  });
11
11
 
12
+ jest.mock('<% if (architecture === "MVC") { %>@/config/env<% } else { %>@/infrastructure/config/env<% } %>', () => ({
13
+ env: {
14
+ KAFKA_BROKER: 'localhost:9092'
15
+ }
16
+ }));
17
+
12
18
  describe('Kafka Configuration', () => {
13
19
  beforeEach(() => {
14
20
  jest.clearAllMocks();
@@ -9,6 +9,12 @@ jest.mock('kafkajs', () => {
9
9
  };
10
10
  });
11
11
 
12
+ jest.mock('@/config/env', () => ({
13
+ env: {
14
+ KAFKA_BROKER: 'localhost:9092'
15
+ }
16
+ }));
17
+
12
18
  describe('Kafka Configuration', () => {
13
19
  beforeEach(() => {
14
20
  jest.clearAllMocks();
@@ -1,6 +1,7 @@
1
1
  import { Kafka } from 'kafkajs';
2
+ import { env } from '@/config/env';
2
3
 
3
4
  export const kafka = new Kafka({
4
5
  clientId: 'nodejs-service',
5
- brokers: [process.env.KAFKA_BROKER || 'localhost:9092']
6
+ brokers: [env.KAFKA_BROKER]
6
7
  });
@@ -11,12 +11,16 @@
11
11
  "lint": "eslint .",
12
12
  "lint:fix": "eslint . --fix",
13
13
  "format": "prettier --write .",
14
- "prepare": "node -e \"try { require('child_process').execSync('husky install'); } catch (e) { console.log('Not a git repository, skipping husky install'); }\"",
14
+ "prepare": "node -e \"if (require('fs').existsSync('.git')) { try { require('child_process').execSync('husky install', { stdio: 'inherit' }); } catch (e) { console.error('Husky installation failed:', e.message); } }\"",
15
15
  <% if (database === 'MongoDB') { %> "migrate": "migrate-mongo up",
16
16
  <% } -%>
17
17
  "test": "jest",
18
18
  "test:watch": "jest --watch",
19
- "test:coverage": "jest --coverage"
19
+ "test:coverage": "jest --coverage",
20
+ "test:e2e:run": "jest --config ./jest.e2e.config.js --passWithNoTests",
21
+ "test:e2e": "node scripts/run-e2e.js"<% if (includeSecurity) { %>,
22
+ "security:check": "npm audit && npm run snyk:test",
23
+ "snyk:test": "snyk test"<% } %>
20
24
  },
21
25
  "dependencies": {
22
26
  "express": "^4.18.2",
@@ -62,15 +66,13 @@
62
66
  "@types/express": "^4.17.21",
63
67
  "@types/cors": "^2.8.17",
64
68
  "@types/hpp": "^0.2.3",
65
- <% if (caching === 'Redis') { %> "@types/ioredis": "^5.0.0",
66
- <% } -%>
67
69
  <% if (caching === 'Memory Cache') { %> "@types/node-cache": "^4.2.5",
68
70
  <% } -%>
69
71
  <% if (database === 'PostgreSQL') { %> "@types/pg": "^8.10.9",
70
72
  <% } -%>
71
- <%_ if (database === 'MySQL' || database === 'PostgreSQL') { -%>
73
+ <% if (database === 'MySQL' || database === 'PostgreSQL') { -%>
72
74
  "@types/sequelize": "^4.28.19",
73
- <%_ } -%>
75
+ <% } -%>
74
76
  "@types/morgan": "^1.9.9",
75
77
  "rimraf": "^6.0.1"<% if ((viewEngine && viewEngine !== 'None') || communication === 'REST APIs' || communication === 'Kafka') { %>,
76
78
  "cpx2": "^8.0.0"<% } %><% } %>,
@@ -82,19 +84,22 @@
82
84
  "eslint-plugin-import-x": "^4.6.1",
83
85
  "eslint-import-resolver-typescript": "^3.7.0",
84
86
  "husky": "^8.0.3",
85
- "lint-staged": "^15.4.3"<% if (language === 'TypeScript') { %>,
87
+ "lint-staged": "^15.4.3",
88
+ "snyk": "^1.1295.0"<% if (language === 'TypeScript') { %>,
86
89
  "typescript-eslint": "^8.24.1",<%_ if (communication === 'REST APIs' || communication === 'Kafka') { %>
87
90
  "@types/swagger-ui-express": "^4.1.6",
88
91
  "@types/yamljs": "^0.2.34",<%_ } %>
89
92
  "jest": "^29.7.0",
90
93
  "ts-jest": "^29.2.5",
91
94
  "@types/jest": "^29.5.14",
92
- "supertest": "6.3.3",
95
+ "wait-on": "^7.2.0",
96
+ "supertest": "^7.1.3",
93
97
  "tsconfig-paths": "^4.2.0",
94
98
  "tsc-alias": "^1.8.10",
95
99
  "@types/supertest": "^6.0.2"<% } else { %>,
96
100
  "jest": "^29.7.0",
97
- "supertest": "6.3.3"<% } %>
101
+ "wait-on": "^7.2.0",
102
+ "supertest": "^7.1.3"<% } %>
98
103
  },
99
104
  "lint-staged": {
100
105
  "*.{js,ts}": [
@@ -21,6 +21,6 @@ Please provide the code implementation following these steps:
21
21
  3. **Controller**: Implement the business logic and request handling.
22
22
  4. **Route**: Create the API endpoints and wire them to the controller.
23
23
  <% } -%>
24
- 6. **Testing**: Write comprehensive Jest unit tests covering the "Happy Path" and "Edge Cases/Errors" (AAA pattern). Remember, our coverage requirement is > 70%!
24
+ 6. **Testing**: Write comprehensive Jest unit tests covering the "Happy Path" and "Edge Cases/Errors" (AAA pattern). Remember, our coverage requirement is > 80%!
25
25
 
26
26
  Please provide the plan first so I can review it before we write the code.
@@ -32,7 +32,7 @@ We use the MVC (Model-View-Controller) pattern.
32
32
  <% } -%>
33
33
 
34
34
  ## Core Standards
35
- 1. **Testing**: We enforce > 70% coverage. Tests use Jest and the AAA (Arrange, Act, Assert) pattern.
35
+ 1. **Testing**: We enforce > 80% coverage. Tests use Jest and the AAA (Arrange, Act, Assert) pattern.
36
36
  2. **Error Handling**: We use centralized custom errors (e.g., `ApiError`) and global error middleware. Status codes come from standard constants, not hardcoded numbers.
37
37
  3. **Paths & Naming**:
38
38
  <% if (language === 'TypeScript') { -%>
@@ -0,0 +1,63 @@
1
+ /* eslint-disable */
2
+ const { execSync } = require('child_process');
3
+ const path = require('path');
4
+
5
+ // Set a specific port for E2E tests to avoid collisions with local development
6
+ process.env.PORT = '3001';
7
+ const TEST_PORT = process.env.PORT;
8
+
9
+ const execute = (command) => {
10
+ console.log(`\n> ${command}`);
11
+ // Run commands from the project root instead of the scripts folder
12
+ execSync(command, { stdio: 'inherit', cwd: path.resolve(__dirname, '../') });
13
+ };
14
+
15
+ let composeCmd = 'docker-compose';
16
+ try {
17
+ execSync('docker compose version', { stdio: 'ignore' });
18
+ composeCmd = 'docker compose';
19
+ } catch (e) {
20
+ // fallback to docker-compose
21
+ }
22
+
23
+ let currentProcessStartedDocker = false;
24
+
25
+ try {
26
+ let isAlreadyUp = false;
27
+ try {
28
+ // Silently check if the endpoint is already live (1.5-second timeout)
29
+ execSync(`npx wait-on http-get://127.0.0.1:${TEST_PORT}/health -t 1500`, {
30
+ stdio: 'ignore',
31
+ cwd: path.resolve(__dirname, '../')
32
+ });
33
+ isAlreadyUp = true;
34
+ } catch (e) {
35
+ isAlreadyUp = false;
36
+ }
37
+
38
+ if (isAlreadyUp) {
39
+ console.log('Infrastructure is already running! Skipping Docker spin-up...');
40
+ } else {
41
+ console.log(`Starting Docker Compose infrastructure using '${composeCmd}'...`);
42
+ execute(`${composeCmd} up -d --build`);
43
+ currentProcessStartedDocker = true;
44
+
45
+ console.log('Waiting for application healthcheck to turn green (120s timeout)...');
46
+ // Using wait-on to poll the universal /health endpoint injected into all architectures
47
+ execute(`npx wait-on http-get://127.0.0.1:${TEST_PORT}/health -t 120000`);
48
+ console.log('Infrastructure is healthy!');
49
+ }
50
+
51
+ console.log('Running E2E tests...');
52
+ execute('npm run test:e2e:run');
53
+ } catch (error) {
54
+ console.error('E2E tests failed or infrastructure did not boot in time.');
55
+ process.exitCode = 1;
56
+ } finally {
57
+ if (currentProcessStartedDocker) {
58
+ console.log('Tearing down isolated Docker Compose infrastructure...');
59
+ execute(`${composeCmd} down`);
60
+ } else {
61
+ console.log('Leaving preexisting infrastructure running.');
62
+ }
63
+ }
@@ -0,0 +1,27 @@
1
+ # SonarCloud/SonarQube Project Configuration
2
+ # See https://docs.sonarqube.org/latest/analysis/analysis-parameters/
3
+
4
+ sonar.projectKey=your-org-key_<%= projectName %>
5
+ sonar.projectName=<%= projectName %>
6
+ sonar.organization=your-org-key
7
+ sonar.projectVersion=1.0.0
8
+
9
+ # Path to the source directories
10
+ sonar.sources=src
11
+ # Path to the test directories
12
+ sonar.tests=tests
13
+
14
+ # Language specific settings
15
+ sonar.javascript.environments=node
16
+ sonar.typescript.tsconfigPath=tsconfig.json
17
+ sonar.javascript.lcov.reportPaths=coverage/lcov.info
18
+
19
+ # Exclusions
20
+ sonar.exclusions=node_modules/**, dist/**, coverage/**, tests/**
21
+
22
+ # Quality Gates
23
+ sonar.qualitygate.wait=true
24
+ sonar.qualitygate.timeout=300
25
+
26
+ # Security Hotspots
27
+ sonar.security.reportPaths=snyk-report.json
@@ -0,0 +1,49 @@
1
+ const request = require('supertest');
2
+
3
+ const SERVER_URL = process.env.TEST_URL || `http://127.0.0.1:${process.env.PORT || 3001}`;
4
+
5
+ describe('E2E User Tests', () => {
6
+ // Global setup and teardown hooks can be added here
7
+ // typically for database seeding or external authentication checks prior to E2E.
8
+ const uniqueEmail = `test_${Date.now()}@example.com`;
9
+
10
+ <%_ if (communication === 'GraphQL') { _%>
11
+ it('should create a user and verify flow via GraphQL', async () => {
12
+ const query = `
13
+ mutation {
14
+ createUser(name: "Test User", email: "${uniqueEmail}") {
15
+ id
16
+ name
17
+ email
18
+ }
19
+ }
20
+ `;
21
+ const response = await request(SERVER_URL)
22
+ .post('/graphql')
23
+ .send({ query });
24
+
25
+ expect(response.statusCode).toBe(200);
26
+ });
27
+ <%_ } else if (communication === 'Kafka') { _%>
28
+ it('should trigger Kafka event for user creation', async () => {
29
+ const response = await request(SERVER_URL)
30
+ .post('/api/users')
31
+ .send({ name: 'Test User', email: uniqueEmail });
32
+
33
+ // Assuming the API returns 201 or 404 (if no REST endpoint is exposed in Kafka skeleton)
34
+ expect([201, 202, 404]).toContain(response.statusCode);
35
+
36
+ // Wait for Kafka to process...
37
+ await new Promise(resolve => setTimeout(resolve, 1000));
38
+ });
39
+ <%_ } else { _%>
40
+ it('should create a user successfully via REST', async () => {
41
+ const response = await request(SERVER_URL)
42
+ .post('/api/users')
43
+ .send({ name: 'Test User', email: uniqueEmail });
44
+
45
+ // E2E Tests must have strict and deterministic assertions
46
+ expect(response.statusCode).toBe(201);
47
+ });
48
+ <%_ } _%>
49
+ });
@@ -0,0 +1,49 @@
1
+ import request from 'supertest';
2
+
3
+ const SERVER_URL = process.env.TEST_URL || `http://127.0.0.1:${process.env.PORT || 3001}`;
4
+
5
+ describe('E2E User Tests', () => {
6
+ // Global setup and teardown hooks can be added here
7
+ // typically for database seeding or external authentication checks prior to E2E.
8
+ const uniqueEmail = `test_${Date.now()}@example.com`;
9
+
10
+ <%_ if (communication === 'GraphQL') { _%>
11
+ it('should create a user and verify flow via GraphQL', async () => {
12
+ const query = `
13
+ mutation {
14
+ createUser(name: "Test User", email: "${uniqueEmail}") {
15
+ id
16
+ name
17
+ email
18
+ }
19
+ }
20
+ `;
21
+ const response = await request(SERVER_URL)
22
+ .post('/graphql')
23
+ .send({ query });
24
+
25
+ expect(response.statusCode).toBe(200);
26
+ });
27
+ <%_ } else if (communication === 'Kafka') { _%>
28
+ it('should trigger Kafka event for user creation', async () => {
29
+ const response = await request(SERVER_URL)
30
+ .post('/api/users')
31
+ .send({ name: 'Test User', email: uniqueEmail });
32
+
33
+ // Assuming the API returns 201 or 404 (if no REST endpoint is exposed in Kafka skeleton)
34
+ expect([201, 202, 404]).toContain(response.statusCode);
35
+
36
+ // Wait for Kafka to process...
37
+ await new Promise(resolve => setTimeout(resolve, 1000));
38
+ });
39
+ <%_ } else { _%>
40
+ it('should create a user successfully via REST', async () => {
41
+ const response = await request(SERVER_URL)
42
+ .post('/api/users')
43
+ .send({ name: 'Test User', email: uniqueEmail });
44
+
45
+ // E2E Tests must have strict and deterministic assertions
46
+ expect(response.statusCode).toBe(201);
47
+ });
48
+ <%_ } _%>
49
+ });
@@ -1,3 +1,4 @@
1
+ const { env } = require('./config/env');
1
2
  const express = require('express');
2
3
  const cors = require('cors');
3
4
  <%_ if (communication === 'REST APIs' || communication === 'Kafka') { -%>const apiRoutes = require('./routes/api');<%_ } %>
@@ -16,7 +17,6 @@ const { gqlContext } = require('./graphql/context');
16
17
  const swaggerUi = require('swagger-ui-express');
17
18
  const swaggerSpecs = require('./config/swagger');
18
19
  <%_ } -%>
19
- const { env } = require('./config/env');
20
20
  const setupGracefulShutdown = require('./utils/gracefulShutdown');
21
21
 
22
22
  const app = express();