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.
- package/CHANGELOG.md +25 -0
- package/LICENSE +15 -0
- package/README.md +144 -135
- package/bin/index.js +92 -89
- package/lib/generator.js +3 -2
- package/lib/modules/app-setup.js +49 -12
- package/lib/modules/caching-setup.js +1 -1
- package/lib/modules/config-files.js +32 -3
- package/lib/modules/database-setup.js +2 -2
- package/lib/modules/kafka-setup.js +8 -8
- package/lib/prompts.js +16 -1
- package/package.json +14 -2
- package/templates/clean-architecture/ts/src/index.ts.ejs +1 -2
- package/templates/common/.cursorrules.ejs +1 -1
- package/templates/common/.env.example.ejs +1 -1
- package/templates/common/.gitlab-ci.yml.ejs +55 -4
- package/templates/common/Dockerfile +12 -2
- package/templates/common/Jenkinsfile.ejs +32 -21
- package/templates/common/README.md.ejs +19 -9
- package/templates/common/SECURITY.md +20 -0
- package/templates/common/_github/workflows/{ci.yml → ci.yml.ejs} +13 -7
- package/templates/common/_github/workflows/security.yml.ejs +36 -0
- package/templates/common/_husky/pre-commit +4 -0
- package/templates/common/caching/js/redisClient.spec.js.ejs +0 -2
- package/templates/common/docker-compose.yml.ejs +13 -20
- package/templates/common/ecosystem.config.js.ejs +1 -1
- package/templates/common/jest.config.js.ejs +1 -0
- package/templates/common/jest.e2e.config.js.ejs +8 -0
- package/templates/common/kafka/js/config/kafka.js +2 -1
- package/templates/common/kafka/js/config/kafka.spec.js.ejs +6 -0
- package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +6 -0
- package/templates/common/kafka/ts/config/kafka.ts +2 -1
- package/templates/common/package.json.ejs +14 -9
- package/templates/common/prompts/add-feature.md.ejs +1 -1
- package/templates/common/prompts/project-context.md.ejs +1 -1
- package/templates/common/scripts/run-e2e.js.ejs +63 -0
- package/templates/common/sonar-project.properties.ejs +27 -0
- package/templates/common/src/tests/e2e/e2e.users.test.js.ejs +49 -0
- package/templates/common/src/tests/e2e/e2e.users.test.ts.ejs +49 -0
- 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
|
|
20
|
+
- name: Use Node.js 22.x
|
|
25
21
|
uses: actions/setup-node@v4
|
|
26
22
|
with:
|
|
27
|
-
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
|
|
@@ -14,7 +14,7 @@ services:
|
|
|
14
14
|
<%_ } -%>
|
|
15
15
|
<%_ if (communication === 'Kafka') { -%>
|
|
16
16
|
environment:
|
|
17
|
-
- KAFKA_BROKER=kafka:
|
|
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-
|
|
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:
|
|
144
|
-
depends_on:
|
|
145
|
-
- zookeeper
|
|
135
|
+
image: bitnamilegacy/kafka:3.4.1-debian-12-r39
|
|
146
136
|
ports:
|
|
147
|
-
- "${
|
|
137
|
+
- "${KAFKA_EXTERNAL_PORT:-9093}:9093"
|
|
148
138
|
environment:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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:
|
|
@@ -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
|
+
};
|
|
@@ -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();
|
|
@@ -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.
|
|
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
|
-
<%
|
|
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"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 >
|
|
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 >
|
|
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();
|