nodejs-quickstart-structure 1.13.0 → 1.14.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 (93) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -3
  3. package/lib/generator.js +17 -3
  4. package/lib/modules/app-setup.js +111 -19
  5. package/lib/modules/caching-setup.js +13 -0
  6. package/lib/modules/config-files.js +25 -62
  7. package/lib/modules/database-setup.js +35 -30
  8. package/lib/modules/kafka-setup.js +78 -10
  9. package/package.json +1 -2
  10. package/templates/clean-architecture/js/src/errors/BadRequestError.js +1 -1
  11. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  12. package/templates/clean-architecture/js/src/errors/NotFoundError.js +1 -1
  13. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  14. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
  15. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +2 -3
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +81 -0
  17. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +8 -4
  18. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +102 -0
  19. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
  20. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +49 -0
  21. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
  22. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
  23. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
  24. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  25. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +1 -1
  26. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  27. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +1 -1
  28. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +64 -0
  29. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +85 -0
  30. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +2 -3
  31. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +166 -0
  32. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  33. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  34. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  35. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  36. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  37. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  38. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  39. package/templates/common/caching/js/redisClient.spec.js.ejs +149 -0
  40. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  41. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  42. package/templates/common/database/js/database.spec.js.ejs +56 -0
  43. package/templates/common/database/js/models/User.js.ejs +22 -0
  44. package/templates/common/database/js/models/User.spec.js.ejs +84 -0
  45. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  46. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  47. package/templates/common/database/ts/models/User.spec.ts.ejs +84 -0
  48. package/templates/common/database/ts/models/User.ts.ejs +26 -0
  49. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  50. package/templates/common/eslint.config.mjs.ejs +11 -2
  51. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  52. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  53. package/templates/common/jest.config.js.ejs +19 -5
  54. package/templates/common/kafka/js/config/kafka.spec.js.ejs +21 -0
  55. package/templates/common/kafka/js/services/kafkaService.js.ejs +9 -5
  56. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +60 -0
  57. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +21 -0
  58. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +61 -0
  59. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +1 -1
  60. package/templates/common/package.json.ejs +0 -3
  61. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +160 -0
  62. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +158 -0
  63. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  64. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  65. package/templates/common/tsconfig.json +1 -1
  66. package/templates/mvc/js/src/controllers/userController.js.ejs +4 -31
  67. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +170 -0
  68. package/templates/mvc/js/src/errors/BadRequestError.js +1 -1
  69. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  70. package/templates/mvc/js/src/errors/NotFoundError.js +1 -1
  71. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  72. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  73. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +47 -0
  74. package/templates/mvc/js/src/index.js.ejs +1 -1
  75. package/templates/mvc/js/src/routes/api.spec.js.ejs +36 -0
  76. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  77. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +185 -0
  78. package/templates/mvc/ts/src/controllers/userController.ts.ejs +4 -31
  79. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  80. package/templates/mvc/ts/src/errors/BadRequestError.ts +1 -1
  81. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  82. package/templates/mvc/ts/src/errors/NotFoundError.ts +1 -1
  83. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  84. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  85. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +40 -0
  86. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  87. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +64 -0
  88. package/docs/demo.gif +0 -0
  89. package/docs/generateCase.md +0 -265
  90. package/docs/generatorFlow.md +0 -233
  91. package/docs/releaseNoteRule.md +0 -42
  92. package/docs/ruleDevelop.md +0 -30
  93. package/templates/common/tests/health.test.ts.ejs +0 -24
@@ -0,0 +1,84 @@
1
+ <% if (database === 'MongoDB') { _%>
2
+ const mongoose = require('mongoose');
3
+
4
+ jest.mock('mongoose', () => {
5
+ const mSchema = jest.fn();
6
+ const mModel = {
7
+ find: jest.fn(),
8
+ create: jest.fn(),
9
+ };
10
+ return {
11
+ Schema: mSchema,
12
+ model: jest.fn().mockReturnValue(mModel),
13
+ };
14
+ });
15
+ <% } else if (database === 'None') { _%>
16
+ // No external dependencies to mock for None DB
17
+ <% } else { _%>
18
+ const { Model } = require('sequelize');
19
+
20
+ jest.mock('sequelize', () => {
21
+ const mDataTypes = {
22
+ INTEGER: 'INTEGER',
23
+ STRING: 'STRING',
24
+ };
25
+ const mModel = class {
26
+ static init = jest.fn();
27
+ static findAll = jest.fn();
28
+ static create = jest.fn();
29
+ };
30
+ return { DataTypes: mDataTypes, Model: mModel };
31
+ });
32
+
33
+ <% if (architecture === 'MVC') { _%>
34
+ jest.mock('@/config/database', () => ({}));
35
+ <% } else { _%>
36
+ jest.mock('@/infrastructure/database/database', () => ({}));
37
+ <% } _%>
38
+ <% } _%>
39
+
40
+ describe('User Model', () => {
41
+ beforeEach(() => {
42
+ jest.clearAllMocks();
43
+ });
44
+
45
+ it('should be defined and initialized', () => {
46
+ const User = require('<% if (architecture === "MVC") { %>@/models/User<% } else { %>@/infrastructure/database/models/User<% } %>');
47
+ expect(User).toBeDefined();
48
+ <% if (database === 'MongoDB') { %>
49
+ expect(mongoose.model).toHaveBeenCalled();
50
+ <% } else if (database === 'None') { %>
51
+ // Test mockData is initialized
52
+ expect(User.mockData).toEqual([]);
53
+ <% } else { %>
54
+ // Sequelize init is called on the class
55
+ expect(Model.init).toHaveBeenCalled();
56
+ <% } %>
57
+ });
58
+
59
+ it('should handle model operations', async () => {
60
+ const User = require('<% if (architecture === "MVC") { %>@/models/User<% } else { %>@/infrastructure/database/models/User<% } %>');
61
+ const data = { name: 'Test', email: 'test@example.com' };
62
+
63
+ <% if (database === 'MongoDB') { %>
64
+ User.create.mockResolvedValue({ id: '1', ...data });
65
+ User.find.mockResolvedValue([{ id: '1', ...data }]);
66
+
67
+ const user = await User.create(data);
68
+ expect(user.name).toBe(data.name);
69
+ expect(await User.find()).toBeDefined();
70
+ <% } else if (database === 'None') { %>
71
+ const user = await User.create(data);
72
+ expect(user.name).toBe(data.name);
73
+ expect(await User.find()).toBeDefined();
74
+ expect(await User.findAll()).toBeDefined();
75
+ <% } else { %>
76
+ User.create.mockResolvedValue({ id: 1, ...data });
77
+ User.findAll.mockResolvedValue([{ id: 1, ...data }]);
78
+
79
+ const user = await User.create(data);
80
+ expect(user.name).toBe(data.name);
81
+ expect(await User.findAll()).toBeDefined();
82
+ <% } %>
83
+ });
84
+ });
@@ -0,0 +1,43 @@
1
+ jest.mock('mongoose', () => ({
2
+ connect: jest.fn().mockResolvedValue(true),
3
+ }));
4
+
5
+ const mockLogger = {
6
+ info: jest.fn(),
7
+ error: jest.fn(),
8
+ };
9
+
10
+ jest.mock('<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>', () => mockLogger);
11
+
12
+ describe('Mongoose Configuration', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ jest.resetModules();
16
+ });
17
+
18
+ it('should call mongoose.connect with correct parameters', async () => {
19
+ const connectDB = require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>');
20
+ const mongoose = require('mongoose');
21
+ await connectDB();
22
+ expect(mongoose.connect).toHaveBeenCalledWith(
23
+ expect.stringContaining('mongodb://'),
24
+ expect.any(Object)
25
+ );
26
+ });
27
+
28
+ it('should handle connection failure and retry', async () => {
29
+ const connectDB = require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>');
30
+ const mongoose = require('mongoose');
31
+ mongoose.connect
32
+ .mockRejectedValueOnce(new Error('Connection failed'))
33
+ .mockResolvedValueOnce(true);
34
+
35
+ const timeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(cb => cb());
36
+
37
+ await connectDB();
38
+
39
+ expect(mockLogger.error).toHaveBeenCalled();
40
+ expect(mongoose.connect).toHaveBeenCalledTimes(2);
41
+ timeoutSpy.mockRestore();
42
+ });
43
+ });
@@ -0,0 +1,56 @@
1
+ jest.mock('sequelize', () => {
2
+ const mSequelize = jest.fn(() => ({
3
+ authenticate: jest.fn().mockResolvedValue(true),
4
+ define: jest.fn(),
5
+ }));
6
+ return { Sequelize: mSequelize };
7
+ });
8
+
9
+ jest.mock('dotenv', () => ({
10
+ config: jest.fn()
11
+ }));
12
+
13
+ describe('Database Configuration', () => {
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ jest.resetModules();
17
+
18
+ // Clean environment
19
+ const envVars = ['DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT'];
20
+ envVars.forEach(v => delete process.env[v]);
21
+ });
22
+
23
+ it('should initialize Sequelize with environment variables', () => {
24
+ const { Sequelize: SequelizeMock } = require('sequelize');
25
+ process.env.DB_NAME = 'testdb';
26
+ process.env.DB_USER = 'testuser';
27
+ process.env.DB_PASSWORD = 'testpassword';
28
+ process.env.DB_HOST = 'localhost';
29
+ process.env.DB_PORT = '5432';
30
+
31
+ require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>');
32
+
33
+ expect(SequelizeMock).toHaveBeenLastCalledWith(
34
+ 'testdb',
35
+ 'testuser',
36
+ 'testpassword',
37
+ expect.objectContaining({
38
+ host: 'localhost',
39
+ port: 5432
40
+ })
41
+ );
42
+ });
43
+
44
+ it('should initialize Sequelize with default values when env vars are missing', () => {
45
+ const { Sequelize: SequelizeMock } = require('sequelize');
46
+ delete process.env.DB_NAME;
47
+ delete process.env.DB_USER;
48
+ delete process.env.DB_PASSWORD;
49
+ delete process.env.DB_HOST;
50
+ delete process.env.DB_PORT;
51
+
52
+ require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>');
53
+
54
+ expect(SequelizeMock).toHaveBeenCalledTimes(1);
55
+ });
56
+ });
@@ -0,0 +1,84 @@
1
+ <% if (database === 'MongoDB') { _%>
2
+ import mongoose from 'mongoose';
3
+
4
+ jest.mock('mongoose', () => {
5
+ const mSchema = jest.fn();
6
+ const mModel = {
7
+ find: jest.fn(),
8
+ create: jest.fn(),
9
+ };
10
+ return {
11
+ Schema: mSchema,
12
+ model: jest.fn().mockReturnValue(mModel),
13
+ };
14
+ });
15
+ <% } else if (database === 'None') { _%>
16
+ // No external dependencies to mock for None DB
17
+ <% } else { _%>
18
+ import { Model } from 'sequelize';
19
+
20
+ jest.mock('sequelize', () => {
21
+ const mDataTypes = {
22
+ INTEGER: 'INTEGER',
23
+ STRING: 'STRING',
24
+ };
25
+ const mModel = class {
26
+ static init = jest.fn();
27
+ static findAll = jest.fn();
28
+ static create = jest.fn();
29
+ };
30
+ return { DataTypes: mDataTypes, Model: mModel };
31
+ });
32
+
33
+ <% if (architecture === 'MVC') { _%>
34
+ jest.mock('@/config/database', () => ({}));
35
+ <% } else { _%>
36
+ jest.mock('@/infrastructure/database/database', () => ({}));
37
+ <% } _%>
38
+ <% } _%>
39
+
40
+ describe('User Model', () => {
41
+ beforeEach(() => {
42
+ jest.clearAllMocks();
43
+ });
44
+
45
+ it('should be defined and initialized', () => {
46
+ const User = require('<% if (architecture === "MVC") { %>@/models/User<% } else { %>@/infrastructure/database/models/User<% } %>').default;
47
+ expect(User).toBeDefined();
48
+ <% if (database === 'MongoDB') { %>
49
+ expect(mongoose.model).toHaveBeenCalled();
50
+ <% } else if (database === 'None') { %>
51
+ // Test mockData is initialized
52
+ expect(User.mockData).toEqual([]);
53
+ <% } else { %>
54
+ // Sequelize init is called on the class
55
+ expect(Model.init).toHaveBeenCalled();
56
+ <% } %>
57
+ });
58
+
59
+ it('should handle model operations', async () => {
60
+ const User = require('<% if (architecture === "MVC") { %>@/models/User<% } else { %>@/infrastructure/database/models/User<% } %>').default;
61
+ const data = { name: 'Test', email: 'test@example.com' };
62
+
63
+ <% if (database === 'MongoDB') { %>
64
+ (User.create as jest.Mock).mockResolvedValue({ id: '1', ...data });
65
+ (User.find as jest.Mock).mockResolvedValue([{ id: '1', ...data }]);
66
+
67
+ const user = await User.create(data);
68
+ expect(user.name).toBe(data.name);
69
+ expect(await User.find()).toBeDefined();
70
+ <% } else if (database === 'None') { %>
71
+ const user = await User.create(data);
72
+ expect(user.name).toBe(data.name);
73
+ expect(await User.find()).toBeDefined();
74
+ expect(await User.findAll()).toBeDefined();
75
+ <% } else { %>
76
+ (User.create as jest.Mock).mockResolvedValue({ id: 1, ...data });
77
+ (User.findAll as jest.Mock).mockResolvedValue([{ id: 1, ...data }]);
78
+
79
+ const user = await User.create(data);
80
+ expect(user.name).toBe(data.name);
81
+ expect(await User.findAll()).toBeDefined();
82
+ <% } %>
83
+ });
84
+ });
@@ -1,3 +1,28 @@
1
+ <% if (database === 'None') { -%>
2
+ export interface User {
3
+ id: string;
4
+ name: string;
5
+ email: string;
6
+ }
7
+
8
+ export default class UserModel {
9
+ static mockData: User[] = [];
10
+
11
+ static async find() {
12
+ return this.mockData;
13
+ }
14
+
15
+ static async findAll() {
16
+ return this.mockData;
17
+ }
18
+
19
+ static async create(data: Omit<User, 'id'>) {
20
+ const newUser = { id: String(this.mockData.length + 1), ...data };
21
+ this.mockData.push(newUser);
22
+ return newUser as User;
23
+ }
24
+ }
25
+ <% } else { -%>
1
26
  import { DataTypes, Model } from 'sequelize';
2
27
  <% if (architecture === 'MVC') { %>import sequelize from '@/config/database';<% } else { %>import sequelize from '@/infrastructure/database/database';<% } %>
3
28
 
@@ -32,3 +57,4 @@ User.init(
32
57
  );
33
58
 
34
59
  export default User;
60
+ <% } -%>
@@ -0,0 +1,42 @@
1
+ jest.mock('mongoose', () => ({
2
+ connect: jest.fn().mockResolvedValue(true),
3
+ }));
4
+
5
+ const logger = {
6
+ info: jest.fn(),
7
+ error: jest.fn(),
8
+ };
9
+
10
+ jest.mock('<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>', () => logger);
11
+
12
+ describe('Mongoose Configuration', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ jest.resetModules();
16
+ });
17
+
18
+ it('should call mongoose.connect with correct parameters', async () => {
19
+ const connectDB = require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>').default;
20
+ const mongoose = require('mongoose');
21
+ await connectDB();
22
+ expect(mongoose.connect).toHaveBeenCalledWith(
23
+ expect.stringContaining('mongodb://')
24
+ );
25
+ });
26
+
27
+ it('should handle connection failure and retry', async () => {
28
+ const connectDB = require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>').default;
29
+ const mongoose = require('mongoose');
30
+ (mongoose.connect as jest.Mock)
31
+ .mockRejectedValueOnce(new Error('Connection failed'))
32
+ .mockResolvedValueOnce(true);
33
+
34
+ const timeoutSpy = jest.spyOn(global, 'setTimeout').mockImplementation(cb => { (cb as any)(); return {} as any; });
35
+
36
+ await connectDB();
37
+
38
+ expect(logger.error).toHaveBeenCalled();
39
+ expect(mongoose.connect).toHaveBeenCalledTimes(2);
40
+ timeoutSpy.mockRestore();
41
+ });
42
+ });
@@ -17,8 +17,17 @@ export default tseslint.config(
17
17
  rules: {
18
18
  "no-console": "warn",
19
19
  "no-unused-vars": "off",
20
- "@typescript-eslint/no-unused-vars": "warn"
21
- }
20
+ "@typescript-eslint/no-unused-vars": "warn",
21
+ "@typescript-eslint/no-require-imports": "error",
22
+ },
23
+ },
24
+ {
25
+ files: ["**/*.test.ts", "**/*.spec.ts", "tests/**/*.ts"],
26
+ rules: {
27
+ "@typescript-eslint/no-require-imports": "off",
28
+ "@typescript-eslint/no-unused-vars": "warn",
29
+ "@typescript-eslint/no-explicit-any": "off",
30
+ },
22
31
  }
23
32
  );<% } else { %>
24
33
  export default [
@@ -0,0 +1,70 @@
1
+ const request = require('supertest');
2
+ const express = require('express');
3
+ <% if (architecture === 'MVC') { -%>
4
+ const healthRoute = require('@/routes/healthRoute');
5
+ <% } else { -%>
6
+ const healthRoute = require('@/interfaces/routes/healthRoute');
7
+ <% } -%>
8
+ const HTTP_STATUS = require('@/utils/httpCodes');
9
+
10
+ <%_ if (database === 'MongoDB') { -%>
11
+ jest.mock('mongoose', () => {
12
+ return {
13
+ connection: {
14
+ readyState: 1,
15
+ db: {
16
+ admin: jest.fn().mockReturnValue({
17
+ ping: jest.fn().mockResolvedValue(true)
18
+ })
19
+ }
20
+ }
21
+ };
22
+ });
23
+ <%_ } else if (database !== 'None') { -%>
24
+ jest.mock('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>', () => {
25
+ return {
26
+ authenticate: jest.fn()
27
+ };
28
+ });
29
+ <%_ } -%>
30
+
31
+ describe('Health Route', () => {
32
+ let app;
33
+
34
+ beforeEach(() => {
35
+ app = express();
36
+ app.use('/health', healthRoute);
37
+ jest.clearAllMocks();
38
+ });
39
+
40
+ it('should return 200 OK with UP status', async () => {
41
+ const res = await request(app).get('/health');
42
+ expect(res.status).toBe(HTTP_STATUS.OK);
43
+ expect(res.body.status).toBe('UP');
44
+ expect(res.body.database).toBe('<% if (database === "None") { %>None<% } else { %>connected<% } %>');
45
+ });
46
+
47
+ <%_ if (database === 'MongoDB') { -%>
48
+ it('should handle database ping failure and return 500', async () => {
49
+ const mongoose = require('mongoose');
50
+ mongoose.connection.db.admin.mockReturnValueOnce({
51
+ ping: jest.fn().mockRejectedValueOnce(new Error('DB Error'))
52
+ });
53
+
54
+ const res = await request(app).get('/health');
55
+ expect(res.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
56
+ expect(res.body.status).toBe('DOWN');
57
+ expect(res.body.database).toBe('error');
58
+ });
59
+ <%_ } else if (database !== 'None') { -%>
60
+ it('should handle database authentication failure and return 500', async () => {
61
+ const sequelize = require('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>');
62
+ sequelize.authenticate.mockRejectedValueOnce(new Error('DB Error'));
63
+
64
+ const res = await request(app).get('/health');
65
+ expect(res.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
66
+ expect(res.body.status).toBe('DOWN');
67
+ expect(res.body.database).toBe('error');
68
+ });
69
+ <%_ } -%>
70
+ });
@@ -0,0 +1,76 @@
1
+ import request from 'supertest';
2
+ import express from 'express';
3
+ <% if (architecture === 'MVC') { -%>
4
+ import healthRoute from '@/routes/healthRoute';
5
+ <% } else { -%>
6
+ import healthRoute from '@/interfaces/routes/healthRoute';
7
+ <% } -%>
8
+ import { HTTP_STATUS } from '@/utils/httpCodes';
9
+ <%_ if (database === 'MongoDB') { -%>
10
+ import mongoose from 'mongoose';
11
+ <%_ } else if (database !== 'None') { -%>
12
+ import sequelize from '<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>';
13
+ <%_ } -%>
14
+
15
+ <%_ if (database === 'MongoDB') { -%>
16
+ jest.mock('mongoose', () => {
17
+ return {
18
+ connection: {
19
+ readyState: 1,
20
+ db: {
21
+ admin: jest.fn().mockReturnValue({
22
+ ping: jest.fn().mockResolvedValue(true)
23
+ })
24
+ }
25
+ }
26
+ };
27
+ });
28
+ <%_ } else if (database !== 'None') { -%>
29
+ jest.mock('<% if (architecture === "MVC") { %>@/config/database<% } else { %>@/infrastructure/database/database<% } %>', () => {
30
+ return {
31
+ __esModule: true,
32
+ default: {
33
+ authenticate: jest.fn()
34
+ }
35
+ };
36
+ });
37
+ <%_ } -%>
38
+
39
+ describe('Health Route', () => {
40
+ let app: express.Express;
41
+
42
+ beforeEach(() => {
43
+ app = express();
44
+ app.use('/health', healthRoute);
45
+ jest.clearAllMocks();
46
+ });
47
+
48
+ it('should return 200 OK with UP status', async () => {
49
+ const res = await request(app).get('/health');
50
+ expect(res.status).toBe(HTTP_STATUS.OK);
51
+ expect(res.body.status).toBe('UP');
52
+ expect(res.body.database).toBe('<% if (database === "None") { %>None<% } else { %>connected<% } %>');
53
+ });
54
+
55
+ <%_ if (database === 'MongoDB') { -%>
56
+ it('should handle database ping failure and return 500', async () => {
57
+ ((mongoose.connection.db as any).admin as jest.Mock).mockReturnValueOnce({
58
+ ping: jest.fn().mockRejectedValueOnce(new Error('DB Error'))
59
+ });
60
+
61
+ const res = await request(app).get('/health');
62
+ expect(res.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
63
+ expect(res.body.status).toBe('DOWN');
64
+ expect(res.body.database).toBe('error');
65
+ });
66
+ <%_ } else if (database !== 'None') { -%>
67
+ it('should handle database authentication failure and return 500', async () => {
68
+ (sequelize.authenticate as jest.Mock).mockRejectedValueOnce(new Error('DB Error'));
69
+
70
+ const res = await request(app).get('/health');
71
+ expect(res.status).toBe(HTTP_STATUS.INTERNAL_SERVER_ERROR);
72
+ expect(res.body.status).toBe('DOWN');
73
+ expect(res.body.database).toBe('error');
74
+ });
75
+ <%_ } -%>
76
+ });
@@ -2,13 +2,27 @@ module.exports = {
2
2
  testEnvironment: 'node',
3
3
  coverageDirectory: 'coverage',
4
4
  collectCoverageFrom: ['src/**/*.{js,ts}'],
5
- testMatch: ['**/*.test.ts', '**/*.test.js'],
6
- <% if (language === 'TypeScript') { %>preset: 'ts-jest',
5
+ testMatch: ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'],
6
+ <% if (language === 'TypeScript') { %>preset: 'ts-jest',<% } %>
7
7
  moduleNameMapper: {
8
8
  '^@/(.*)$': '<rootDir>/src/$1',
9
- },<% } %>
9
+ },
10
10
  coveragePathIgnorePatterns: [
11
11
  "/node_modules/",
12
- "/dist/"
13
- ]
12
+ "/dist/",
13
+ "src/index",
14
+ "src/app",
15
+ "src/config/env",
16
+ "src/config/swagger",
17
+ "src/infrastructure/webserver/swagger",
18
+ "src/infrastructure/webserver/server"
19
+ ],
20
+ coverageThreshold: {
21
+ global: {
22
+ branches: 65,
23
+ functions: 70,
24
+ lines: 70,
25
+ statements: 70
26
+ }
27
+ }
14
28
  };
@@ -0,0 +1,21 @@
1
+ const { Kafka } = require('kafkajs');
2
+
3
+ jest.mock('kafkajs', () => {
4
+ return {
5
+ Kafka: jest.fn().mockImplementation(() => ({
6
+ producer: jest.fn(),
7
+ consumer: jest.fn(),
8
+ })),
9
+ };
10
+ });
11
+
12
+ describe('Kafka Configuration', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+
17
+ it('should initialize Kafka', () => {
18
+ require('<% if (architecture === "MVC") { %>@/config/kafka<% } else { %>@/infrastructure/config/kafka<% } %>');
19
+ expect(Kafka).toHaveBeenCalled();
20
+ });
21
+ });
@@ -1,16 +1,19 @@
1
1
  const { kafka } = require('../config/kafka');
2
2
  const logger = require('<%= loggerPath %>');
3
3
 
4
- const producer = kafka.producer();
5
- const consumer = kafka.consumer({ groupId: 'test-group' });
4
+ let producer = null;
5
+ let consumer = null;
6
6
 
7
7
  const connectKafka = async () => {
8
+ if (!producer) producer = kafka.producer();
9
+ if (!consumer) consumer = kafka.consumer({ groupId: 'test-group' });
10
+
8
11
  await producer.connect();
9
12
  await consumer.connect();
10
13
  await consumer.subscribe({ topic: 'test-topic', fromBeginning: true });
11
14
 
12
15
  await consumer.run({
13
- eachMessage: async ({ topic, partition, message }) => {
16
+ eachMessage: async ({ _topic, _partition, message }) => {
14
17
  logger.info({
15
18
  value: message.value.toString(),
16
19
  });
@@ -19,6 +22,7 @@ const connectKafka = async () => {
19
22
  };
20
23
 
21
24
  const sendMessage = async (topic, message) => {
25
+ if (!producer) producer = kafka.producer();
22
26
  await producer.send({
23
27
  topic,
24
28
  messages: [
@@ -28,8 +32,8 @@ const sendMessage = async (topic, message) => {
28
32
  };
29
33
 
30
34
  const disconnectKafka = async () => {
31
- await producer.disconnect();
32
- await consumer.disconnect();
35
+ if (producer) await producer.disconnect();
36
+ if (consumer) await consumer.disconnect();
33
37
  };
34
38
 
35
39
  module.exports = { connectKafka, sendMessage, disconnectKafka };
@@ -0,0 +1,60 @@
1
+ let kafka;
2
+ let connectKafka, sendMessage, disconnectKafka;
3
+
4
+ jest.mock('<%= configPath %>', () => ({
5
+ kafka: {
6
+ producer: jest.fn().mockReturnValue({
7
+ connect: jest.fn().mockResolvedValue(undefined),
8
+ send: jest.fn().mockResolvedValue(undefined),
9
+ disconnect: jest.fn().mockResolvedValue(undefined),
10
+ }),
11
+ consumer: jest.fn().mockReturnValue({
12
+ connect: jest.fn().mockResolvedValue(undefined),
13
+ subscribe: jest.fn().mockResolvedValue(undefined),
14
+ run: jest.fn().mockResolvedValue(undefined),
15
+ disconnect: jest.fn().mockResolvedValue(undefined),
16
+ }),
17
+ },
18
+ }));
19
+
20
+ jest.mock('<%= loggerPath %>');
21
+
22
+ describe('Kafka Client', () => {
23
+ beforeEach(async () => {
24
+ jest.resetModules();
25
+ jest.clearAllMocks();
26
+ kafka = require('<%= configPath %>').kafka;
27
+ ({ connectKafka, sendMessage, disconnectKafka } = require('<%= servicePath %>'));
28
+ await connectKafka();
29
+ });
30
+
31
+ it('should connect producer and consumer', async () => {
32
+ await connectKafka();
33
+ const producer = kafka.producer.mock.results[0].value;
34
+ const consumer = kafka.consumer.mock.results[0].value;
35
+
36
+ expect(producer.connect).toHaveBeenCalled();
37
+ expect(consumer.connect).toHaveBeenCalled();
38
+ });
39
+
40
+ it('should send a message', async () => {
41
+ const topic = 'test-topic';
42
+ const message = 'test-message';
43
+ await sendMessage(topic, message);
44
+ const producer = kafka.producer.mock.results[0].value;
45
+
46
+ expect(producer.send).toHaveBeenCalledWith({
47
+ topic,
48
+ messages: [{ value: message }],
49
+ });
50
+ });
51
+
52
+ it('should disconnect Kafka', async () => {
53
+ await disconnectKafka();
54
+ const producer = kafka.producer.mock.results[0].value;
55
+ const consumer = kafka.consumer.mock.results[0].value;
56
+
57
+ expect(producer.disconnect).toHaveBeenCalled();
58
+ expect(consumer.disconnect).toHaveBeenCalled();
59
+ });
60
+ });