nodejs-quickstart-structure 1.12.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 (104) hide show
  1. package/CHANGELOG.md +26 -3
  2. package/README.md +5 -3
  3. package/lib/generator.js +17 -3
  4. package/lib/modules/app-setup.js +167 -47
  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 +79 -13
  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/infrastructure/webserver/server.js.ejs +20 -9
  18. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +8 -4
  19. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +102 -0
  20. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
  21. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +49 -0
  22. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
  23. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
  24. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
  25. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  26. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +1 -1
  27. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  28. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +1 -1
  29. package/templates/clean-architecture/ts/src/index.ts.ejs +15 -11
  30. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +64 -0
  31. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +85 -0
  32. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +2 -3
  33. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +166 -0
  34. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  35. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  36. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  37. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  38. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  39. package/templates/clean-architecture/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +1 -2
  40. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  41. package/templates/common/caching/js/redisClient.js.ejs +4 -0
  42. package/templates/common/caching/js/redisClient.spec.js.ejs +149 -0
  43. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  44. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  45. package/templates/common/caching/ts/redisClient.ts.ejs +4 -0
  46. package/templates/common/database/js/database.spec.js.ejs +56 -0
  47. package/templates/common/database/js/models/User.js.ejs +22 -0
  48. package/templates/common/database/js/models/User.spec.js.ejs +84 -0
  49. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  50. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  51. package/templates/common/database/ts/models/User.spec.ts.ejs +84 -0
  52. package/templates/common/database/ts/models/User.ts.ejs +26 -0
  53. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  54. package/templates/common/eslint.config.mjs.ejs +11 -2
  55. package/templates/common/health/js/healthRoute.js.ejs +44 -0
  56. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  57. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  58. package/templates/common/health/ts/healthRoute.ts.ejs +43 -0
  59. package/templates/common/jest.config.js.ejs +19 -5
  60. package/templates/common/kafka/js/config/kafka.spec.js.ejs +21 -0
  61. package/templates/common/kafka/js/services/kafkaService.js.ejs +13 -4
  62. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +60 -0
  63. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +21 -0
  64. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +61 -0
  65. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +6 -1
  66. package/templates/common/package.json.ejs +0 -3
  67. package/templates/common/shutdown/js/gracefulShutdown.js.ejs +61 -0
  68. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +160 -0
  69. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +158 -0
  70. package/templates/common/shutdown/ts/gracefulShutdown.ts.ejs +58 -0
  71. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  72. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  73. package/templates/common/tsconfig.json +1 -1
  74. package/templates/mvc/js/src/controllers/userController.js.ejs +4 -31
  75. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +170 -0
  76. package/templates/mvc/js/src/errors/BadRequestError.js +1 -1
  77. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  78. package/templates/mvc/js/src/errors/NotFoundError.js +1 -1
  79. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  80. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  81. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +47 -0
  82. package/templates/mvc/js/src/index.js.ejs +11 -9
  83. package/templates/mvc/js/src/routes/api.spec.js.ejs +36 -0
  84. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  85. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +185 -0
  86. package/templates/mvc/ts/src/controllers/userController.ts.ejs +4 -31
  87. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  88. package/templates/mvc/ts/src/errors/BadRequestError.ts +1 -1
  89. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  90. package/templates/mvc/ts/src/errors/NotFoundError.ts +1 -1
  91. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  92. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  93. package/templates/mvc/ts/src/index.ts.ejs +13 -9
  94. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +40 -0
  95. package/templates/mvc/ts/src/utils/{error.middleware.ts.ejs → errorMiddleware.ts.ejs} +1 -2
  96. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +64 -0
  97. package/docs/demo.gif +0 -0
  98. package/docs/generateCase.md +0 -265
  99. package/docs/generatorFlow.md +0 -233
  100. package/docs/releaseNoteRule.md +0 -42
  101. package/docs/ruleDevelop.md +0 -30
  102. package/templates/common/tests/health.test.ts.ejs +0 -14
  103. /package/templates/clean-architecture/js/src/infrastructure/webserver/{middlewares/error.middleware.js → middleware/errorMiddleware.js} +0 -0
  104. /package/templates/mvc/js/src/utils/{error.middleware.js → errorMiddleware.js} +0 -0
@@ -0,0 +1,166 @@
1
+ <% if (communication !== 'GraphQL') { -%>
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { HTTP_STATUS } from '@/utils/httpCodes';
4
+ <% } -%>
5
+ import { UserController } from '@/interfaces/controllers/userController';
6
+ import CreateUser from '@/usecases/createUser';
7
+ import GetAllUsers from '@/usecases/getAllUsers';
8
+
9
+ // Mock dependencies
10
+ jest.mock('@/infrastructure/repositories/UserRepository');
11
+ jest.mock('@/usecases/createUser');
12
+ jest.mock('@/usecases/getAllUsers');
13
+ jest.mock('@/infrastructure/log/logger');
14
+
15
+ describe('UserController (Clean Architecture)', () => {
16
+ let userController: UserController;
17
+ let mockCreateUserUseCase: jest.Mocked<CreateUser>;
18
+ let mockGetAllUsersUseCase: jest.Mocked<GetAllUsers>;
19
+ <% if (communication !== 'GraphQL') { -%>
20
+ let mockRequest: Partial<Request>;
21
+ let mockResponse: Partial<Response>;
22
+ let mockNext: NextFunction;
23
+ <% } -%>
24
+
25
+ beforeEach(() => {
26
+ // Clear all mocks
27
+ jest.clearAllMocks();
28
+
29
+ userController = new UserController();
30
+
31
+ // Retrieve the mocked instances created inside UserController constructor
32
+ mockCreateUserUseCase = (CreateUser as jest.Mock).mock.instances[0] as jest.Mocked<CreateUser>;
33
+ mockGetAllUsersUseCase = (GetAllUsers as jest.Mock).mock.instances[0] as jest.Mocked<GetAllUsers>;
34
+
35
+ <% if (communication !== 'GraphQL') { -%>
36
+ mockRequest = {};
37
+ mockResponse = {
38
+ json: jest.fn(),
39
+ status: jest.fn().mockReturnThis(),
40
+ };
41
+ mockNext = jest.fn();
42
+ <% } -%>
43
+ });
44
+
45
+ describe('getUsers', () => {
46
+ it('should return successfully (Happy Path)', async () => {
47
+ // Arrange
48
+ const usersMock = [{ id: '1', name: 'Test', email: 'test@example.com' }];
49
+ mockGetAllUsersUseCase.execute.mockResolvedValue(usersMock);
50
+
51
+ // Act
52
+ <% if (communication === 'GraphQL') { -%>
53
+ const result = await userController.getUsers();
54
+
55
+ // Assert
56
+ expect(result).toEqual(usersMock);
57
+ <% } else { -%>
58
+ await userController.getUsers(mockRequest as Request, mockResponse as Response, mockNext);
59
+
60
+ // Assert
61
+ expect(mockResponse.json).toHaveBeenCalledWith(usersMock);
62
+ <% } -%>
63
+ expect(mockGetAllUsersUseCase.execute).toHaveBeenCalled();
64
+ });
65
+
66
+ it('should handle errors correctly (Error Handling)', async () => {
67
+ // Arrange
68
+ const error = new Error('UseCase Error');
69
+ mockGetAllUsersUseCase.execute.mockRejectedValue(error);
70
+
71
+ // Act & Assert
72
+ <% if (communication === 'GraphQL') { -%>
73
+ await expect(userController.getUsers()).rejects.toThrow(error);
74
+ <% } else { -%>
75
+ await userController.getUsers(mockRequest as Request, mockResponse as Response, mockNext);
76
+ expect(mockNext).toHaveBeenCalledWith(error);
77
+ <% } -%>
78
+ });
79
+
80
+ it('should handle non-Error objects in catch block', async () => {
81
+ // Arrange
82
+ const error = 'String Error';
83
+ mockGetAllUsersUseCase.execute.mockRejectedValue(error);
84
+
85
+ // Act & Assert
86
+ <% if (communication === 'GraphQL') { -%>
87
+ await expect(userController.getUsers()).rejects.toEqual(error);
88
+ <% } else { -%>
89
+ await userController.getUsers(mockRequest as Request, mockResponse as Response, mockNext);
90
+ expect(mockNext).toHaveBeenCalledWith(error);
91
+ <% } -%>
92
+ });
93
+ });
94
+
95
+ describe('createUser', () => {
96
+ it('should successfully create a new user (Happy Path)', async () => {
97
+ // Arrange
98
+ const payload = { name: 'Alice', email: 'alice@example.com' };
99
+ <% if (communication === 'GraphQL') { -%>
100
+ const dataArg = payload;
101
+ <% } else { -%>
102
+ mockRequest.body = payload;
103
+ <% } -%>
104
+ const expectedUser = { id: '1', ...payload };
105
+
106
+ mockCreateUserUseCase.execute.mockResolvedValue(expectedUser);
107
+
108
+ // Act
109
+ <% if (communication === 'GraphQL') { -%>
110
+ const result = await userController.createUser(dataArg);
111
+
112
+ // Assert
113
+ expect(result).toEqual(expectedUser);
114
+ <% } else { -%>
115
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
116
+
117
+ // Assert
118
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
119
+ expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
120
+ <% } -%>
121
+ expect(mockCreateUserUseCase.execute).toHaveBeenCalledWith(payload.name, payload.email);
122
+ });
123
+
124
+ it('should handle errors when creation fails (Error Handling)', async () => {
125
+ // Arrange
126
+ const error = new Error('Creation Error');
127
+ const payload = { name: 'Bob', email: 'bob@example.com' };
128
+ <% if (communication === 'GraphQL') { -%>
129
+ const dataArg = payload;
130
+ <% } else { -%>
131
+ mockRequest.body = payload;
132
+ <% } -%>
133
+
134
+ mockCreateUserUseCase.execute.mockRejectedValue(error);
135
+
136
+ // Act & Assert
137
+ <% if (communication === 'GraphQL') { -%>
138
+ await expect(userController.createUser(dataArg)).rejects.toThrow(error);
139
+ <% } else { -%>
140
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
141
+ expect(mockNext).toHaveBeenCalledWith(error);
142
+ <% } -%>
143
+ });
144
+
145
+ it('should handle non-Error objects in catch block when creation fails', async () => {
146
+ // Arrange
147
+ const error = 'Creation String Error';
148
+ const payload = { name: 'Bob', email: 'bob@example.com' };
149
+ <% if (communication === 'GraphQL') { -%>
150
+ const dataArg = payload;
151
+ <% } else { -%>
152
+ mockRequest.body = payload;
153
+ <% } -%>
154
+
155
+ mockCreateUserUseCase.execute.mockRejectedValue(error);
156
+
157
+ // Act & Assert
158
+ <% if (communication === 'GraphQL') { -%>
159
+ await expect(userController.createUser(dataArg)).rejects.toEqual(error);
160
+ <% } else { -%>
161
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
162
+ expect(mockNext).toHaveBeenCalledWith(error);
163
+ <% } -%>
164
+ });
165
+ });
166
+ });
@@ -0,0 +1,32 @@
1
+ import { gqlContext } from '@/interfaces/graphql/context';
2
+ import { Request } from 'express';
3
+ import { resolvers } from '@/interfaces/graphql/resolvers';
4
+ import { typeDefs } from '@/interfaces/graphql/typeDefs';
5
+
6
+ jest.mock('@/infrastructure/repositories/UserRepository');
7
+
8
+ describe('GraphQL Context', () => {
9
+ it('should exercise GraphQL index entry points', () => {
10
+ expect(resolvers).toBeDefined();
11
+ expect(typeDefs).toBeDefined();
12
+ });
13
+ it('should return context with token when authorization header is present', async () => {
14
+ const mockRequest = {
15
+ headers: {
16
+ authorization: 'Bearer token123',
17
+ },
18
+ } as Request;
19
+
20
+ const context = await gqlContext({ req: mockRequest });
21
+ expect(context.token).toBe('Bearer token123');
22
+ });
23
+
24
+ it('should return context with empty token when authorization header is missing', async () => {
25
+ const mockRequest = {
26
+ headers: {},
27
+ } as Request;
28
+
29
+ const context = await gqlContext({ req: mockRequest });
30
+ expect(context.token).toBe('');
31
+ });
32
+ });
@@ -0,0 +1,51 @@
1
+ import { userResolvers } from '@/interfaces/graphql/resolvers/user.resolvers';
2
+
3
+ const mockGetUsers = jest.fn().mockResolvedValue([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
4
+ const mockCreateUser = jest.fn().mockResolvedValue({ id: '1', name: 'Jane', email: 'jane@example.com' });
5
+
6
+ jest.mock('@/interfaces/controllers/userController', () => {
7
+ return {
8
+ UserController: jest.fn().mockImplementation(() => ({
9
+ getUsers: (...args: unknown[]) => mockGetUsers(...args),
10
+ createUser: (...args: unknown[]) => mockCreateUser(...args)
11
+ }))
12
+ };
13
+ });
14
+
15
+ describe('User Resolvers', () => {
16
+ afterEach(() => {
17
+ jest.clearAllMocks();
18
+ });
19
+
20
+ describe('Query.getAllUsers', () => {
21
+ it('should return all users', async () => {
22
+ const result = await userResolvers.Query.getAllUsers();
23
+ expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
24
+ expect(mockGetUsers).toHaveBeenCalledTimes(1);
25
+ });
26
+ });
27
+
28
+ describe('Mutation.createUser', () => {
29
+ it('should create and return a new user', async () => {
30
+ const result = await userResolvers.Mutation.createUser(null, { name: 'Jane', email: 'jane@example.com' });
31
+ expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
32
+ expect(mockCreateUser).toHaveBeenCalledWith({ name: 'Jane', email: 'jane@example.com' });
33
+ expect(mockCreateUser).toHaveBeenCalledTimes(1);
34
+ });
35
+ });
36
+ <%_ if (database === 'MongoDB') { -%>
37
+ describe('User.id', () => {
38
+ it('should return parent.id if available', () => {
39
+ const parent = { id: '123' };
40
+ const result = userResolvers.User.id(parent as { id?: string; _id?: unknown });
41
+ expect(result).toBe('123');
42
+ });
43
+
44
+ it('should fallback to parent._id if id is not available', () => {
45
+ const parent = { _id: '456' };
46
+ const result = userResolvers.User.id(parent as { id?: string; _id?: unknown });
47
+ expect(result).toBe('456');
48
+ });
49
+ });
50
+ <%_ } -%>
51
+ });
@@ -0,0 +1,40 @@
1
+ import request from 'supertest';
2
+ import express, { Express } from 'express';
3
+ import router from '@/interfaces/routes/userRoutes';
4
+
5
+ const mockGetUsers = jest.fn().mockImplementation((req, res) => res.status(200).json([{ id: '1', name: 'John Doe' }]));
6
+ const mockCreateUser = jest.fn().mockImplementation((req, res) => res.status(201).json({ id: '1', name: 'Test' }));
7
+
8
+ jest.mock('@/interfaces/controllers/userController', () => {
9
+ return {
10
+ UserController: jest.fn().mockImplementation(() => ({
11
+ getUsers: (...args: unknown[]) => mockGetUsers(...args),
12
+ createUser: (...args: unknown[]) => mockCreateUser(...args)
13
+ }))
14
+ };
15
+ });
16
+
17
+ describe('UserRoutes', () => {
18
+ let app: Express;
19
+
20
+ beforeEach(() => {
21
+ app = express();
22
+ app.use(express.json());
23
+ app.use('/users', router);
24
+ });
25
+
26
+ it('POST /users should call controller.createUser', async () => {
27
+ await request(app)
28
+ .post('/users')
29
+ .send({ name: 'Test', email: 'test@example.com' });
30
+
31
+ expect(mockCreateUser).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ it('GET /users should call controller.getUsers', async () => {
35
+ await request(app)
36
+ .get('/users');
37
+
38
+ expect(mockGetUsers).toHaveBeenCalledTimes(1);
39
+ });
40
+ });
@@ -0,0 +1,51 @@
1
+ import CreateUser from '@/usecases/createUser';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ <%_ if (caching !== 'None') { -%>
4
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
5
+ <%_ } -%>
6
+
7
+ jest.mock('@/infrastructure/repositories/UserRepository');
8
+ <%_ if (caching !== 'None') { -%>
9
+ jest.mock('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>', () => ({
10
+ get: jest.fn(),
11
+ set: jest.fn(),
12
+ del: jest.fn()
13
+ }));
14
+ <%_ } -%>
15
+
16
+ describe('CreateUser UseCase', () => {
17
+ let createUser: CreateUser;
18
+ let mockUserRepository: jest.Mocked<UserRepository>;
19
+
20
+ beforeEach(() => {
21
+ mockUserRepository = new UserRepository() as jest.Mocked<UserRepository>;
22
+ createUser = new CreateUser(mockUserRepository);
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ it('should create and save a new user', async () => {
27
+ const name = 'Test User';
28
+ const email = 'test@example.com';
29
+ const expectedResult = { id: 1, name, email };
30
+
31
+ mockUserRepository.save.mockResolvedValue(expectedResult as any);
32
+
33
+ const result = await createUser.execute(name, email);
34
+
35
+ expect(mockUserRepository.save).toHaveBeenCalledTimes(1);
36
+ const savedUser = mockUserRepository.save.mock.calls[0][0];
37
+ expect(savedUser.name).toBe(name);
38
+ expect(savedUser.email).toBe(email);
39
+ expect(result).toEqual(expectedResult);
40
+ <%_ if (caching !== 'None') { -%>
41
+ expect(cacheService.del).toHaveBeenCalledWith('users:all');
42
+ <%_ } %>
43
+ });
44
+
45
+ it('should throw an error if repository fails', async () => {
46
+ const error = new Error('Database error');
47
+ mockUserRepository.save.mockRejectedValue(error);
48
+
49
+ await expect(createUser.execute('Test', 'test@test.com')).rejects.toThrow(error);
50
+ });
51
+ });
@@ -0,0 +1,63 @@
1
+ import GetAllUsers from '@/usecases/getAllUsers';
2
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
3
+ <%_ if (caching !== 'None') { -%>
4
+ import cacheService from '<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>';
5
+ <%_ } -%>
6
+
7
+ jest.mock('@/infrastructure/repositories/UserRepository');
8
+ <%_ if (caching !== 'None') { -%>
9
+ jest.mock('<% if (caching === "Redis") { %>@/infrastructure/caching/redisClient<% } else { %>@/infrastructure/caching/memoryCache<% } %>', () => ({
10
+ get: jest.fn(),
11
+ set: jest.fn(),
12
+ del: jest.fn()
13
+ }));
14
+ <%_ } -%>
15
+
16
+ describe('GetAllUsers UseCase', () => {
17
+ let getAllUsers: GetAllUsers;
18
+ let mockUserRepository: jest.Mocked<UserRepository>;
19
+
20
+ beforeEach(() => {
21
+ mockUserRepository = new UserRepository() as jest.Mocked<UserRepository>;
22
+ getAllUsers = new GetAllUsers(mockUserRepository);
23
+ jest.clearAllMocks();
24
+ });
25
+
26
+ it('should retrieve all users', async () => {
27
+ const expectedUsers = [{ id: 1, name: 'Alice', email: 'alice@example.com' }];
28
+ mockUserRepository.getUsers.mockResolvedValue(expectedUsers as any);
29
+ <%_ if (caching !== 'None') { -%>
30
+ (cacheService.get as jest.Mock).mockResolvedValue(null);
31
+ <%_ } %>
32
+
33
+ const result = await getAllUsers.execute();
34
+
35
+ expect(mockUserRepository.getUsers).toHaveBeenCalledTimes(1);
36
+ expect(result).toEqual(expectedUsers);
37
+ <%_ if (caching !== 'None') { -%>
38
+ expect(cacheService.set).toHaveBeenCalled();
39
+ <%_ } %>
40
+ });
41
+
42
+ <%_ if (caching !== 'None') { -%>
43
+ it('should return from cache if available', async () => {
44
+ const cachedUsers = [{ id: 1, name: 'Cached', email: 'cached@example.com' }];
45
+ (cacheService.get as jest.Mock).mockResolvedValue(cachedUsers);
46
+
47
+ const result = await getAllUsers.execute();
48
+
49
+ expect(mockUserRepository.getUsers).not.toHaveBeenCalled();
50
+ expect(result).toEqual(cachedUsers);
51
+ });
52
+ <%_ } -%>
53
+
54
+ it('should throw an error if repository fails', async () => {
55
+ const error = new Error('Database error');
56
+ mockUserRepository.getUsers.mockRejectedValue(error);
57
+ <%_ if (caching !== 'None') { -%>
58
+ (cacheService.get as jest.Mock).mockResolvedValue(null);
59
+ <%_ } -%>
60
+
61
+ await expect(getAllUsers.execute()).rejects.toThrow(error);
62
+ });
63
+ });
@@ -3,8 +3,7 @@ import logger from '@/infrastructure/log/logger';
3
3
  import { ApiError } from '@/errors/ApiError';
4
4
  import { HTTP_STATUS } from '@/utils/httpCodes';
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
- export const errorMiddleware = (err: Error, req: Request, res: Response, next: unknown) => {
6
+ export const errorMiddleware = (err: Error, req: Request, res: Response, _next: unknown) => {
8
7
  let error = err;
9
8
 
10
9
  if (!(error instanceof ApiError)) {
@@ -0,0 +1,101 @@
1
+ jest.mock('node-cache', () => {
2
+ return jest.fn().mockImplementation(() => ({
3
+ get: jest.fn(),
4
+ set: jest.fn(),
5
+ del: jest.fn(),
6
+ }));
7
+ });
8
+
9
+ jest.mock('<%- loggerPath %>', () => ({
10
+ info: jest.fn(),
11
+ error: jest.fn(),
12
+ }));
13
+
14
+ describe('Memory Cache Client', () => {
15
+ let memoryCacheService;
16
+
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ jest.resetModules();
20
+ memoryCacheService = require('<% if (architecture === "MVC") { %>@/config/memoryCache<% } else { %>@/infrastructure/caching/memoryCache<% } %>');
21
+ });
22
+
23
+ it('should get data from memory cache', async () => {
24
+ const mockData = { test: 'data' };
25
+ memoryCacheService.cache.get.mockReturnValue(JSON.stringify(mockData));
26
+
27
+ const data = await memoryCacheService.get('test-key');
28
+ expect(data).toEqual(mockData);
29
+ expect(memoryCacheService.cache.get).toHaveBeenCalledWith('test-key');
30
+ });
31
+
32
+ it('should return null when key is not found', async () => {
33
+ memoryCacheService.cache.get.mockReturnValue(undefined);
34
+ const data = await memoryCacheService.get('non-existent');
35
+ expect(data).toBeNull();
36
+ });
37
+
38
+ it('should return data as is if not a string', async () => {
39
+ const mockData = { test: 'data' };
40
+ memoryCacheService.cache.get.mockReturnValue(mockData);
41
+ const data = await memoryCacheService.get('binary-key');
42
+ expect(data).toEqual(mockData);
43
+ });
44
+
45
+ it('should handle errors in get', async () => {
46
+ memoryCacheService.cache.get.mockImplementation(() => { throw new Error('Cache Error'); });
47
+ const data = await memoryCacheService.get('error-key');
48
+ expect(data).toBeNull();
49
+ });
50
+
51
+ it('should set data to memory cache without TTL', async () => {
52
+ const mockData = { test: 'data' };
53
+ await memoryCacheService.set('test-key', mockData);
54
+ expect(memoryCacheService.cache.set).toHaveBeenCalledWith('test-key', JSON.stringify(mockData));
55
+ });
56
+
57
+ it('should handle errors in set', async () => {
58
+ memoryCacheService.cache.set.mockImplementation(() => { throw new Error('Cache Error'); });
59
+ await memoryCacheService.set('test-key', 'value');
60
+ });
61
+
62
+ it('should handle errors in del', async () => {
63
+ memoryCacheService.cache.del.mockImplementation(() => { throw new Error('Cache Error'); });
64
+ await memoryCacheService.del('test-key');
65
+ });
66
+
67
+ it('should use getOrSet and call fetcher if not cached', async () => {
68
+ memoryCacheService.get = jest.fn().mockResolvedValue(null);
69
+ memoryCacheService.set = jest.fn();
70
+ const fetcher = jest.fn().mockResolvedValue({ new: 'data' });
71
+
72
+ const result = await memoryCacheService.getOrSet('new-key', fetcher);
73
+
74
+ expect(result).toEqual({ new: 'data' });
75
+ expect(fetcher).toHaveBeenCalled();
76
+ // Result should be cached
77
+ expect(memoryCacheService.set).toHaveBeenCalledWith('new-key', { new: 'data' }, 3600);
78
+ });
79
+
80
+ it('should return cached data in getOrSet', async () => {
81
+ const cachedData = { cached: 'data' };
82
+ memoryCacheService.get = jest.fn().mockResolvedValue(cachedData);
83
+ const fetcher = jest.fn();
84
+
85
+ const result = await memoryCacheService.getOrSet('cached-key', fetcher);
86
+
87
+ expect(result).toEqual(cachedData);
88
+ expect(fetcher).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('should handle falsy data from fetcher in getOrSet', async () => {
92
+ memoryCacheService.get = jest.fn().mockResolvedValue(null);
93
+ memoryCacheService.set = jest.fn();
94
+ const fetcher = jest.fn().mockResolvedValue(null);
95
+
96
+ const result = await memoryCacheService.getOrSet('empty-key', fetcher);
97
+
98
+ expect(result).toBeNull();
99
+ expect(memoryCacheService.set).not.toHaveBeenCalled();
100
+ });
101
+ });
@@ -66,6 +66,10 @@ class RedisService {
66
66
  if (data) await this.set(key, data, ttl);
67
67
  return data;
68
68
  }
69
+
70
+ async quit() {
71
+ return await this.client.quit();
72
+ }
69
73
  }
70
74
 
71
75
  module.exports = RedisService.getInstance();
@@ -0,0 +1,149 @@
1
+ const Redis = require('ioredis');
2
+
3
+ jest.mock('ioredis', () => {
4
+ const mRedis = jest.fn(() => ({
5
+ on: jest.fn(),
6
+ get: jest.fn(),
7
+ set: jest.fn(),
8
+ del: jest.fn(),
9
+ quit: jest.fn().mockResolvedValue('OK'),
10
+ }));
11
+ return mRedis;
12
+ });
13
+
14
+ jest.mock('dotenv', () => ({
15
+ config: jest.fn()
16
+ }));
17
+
18
+ const mockLogger = {
19
+ info: jest.fn(),
20
+ error: jest.fn(),
21
+ };
22
+
23
+ jest.mock('<%- loggerPath %>', () => mockLogger);
24
+
25
+ describe('Redis Service', () => {
26
+ let RedisService;
27
+ let logger;
28
+
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ jest.resetModules();
32
+
33
+ // Clean environment
34
+ const envVars = ['REDIS_HOST', 'REDIS_PORT', 'REDIS_PASSWORD'];
35
+ envVars.forEach(v => delete process.env[v]);
36
+
37
+ logger = require('<%- loggerPath %>');
38
+ RedisService = require('<%= redisClientPath %>');
39
+ });
40
+
41
+ it('should handle redis events', () => {
42
+ // Find the event handlers
43
+ const handlers = {};
44
+ RedisService.client.on.mock.calls.forEach(([event, handler]) => {
45
+ handlers[event] = handler;
46
+ });
47
+
48
+ if (handlers['connect']) {
49
+ handlers['connect']();
50
+ expect(logger.info).toHaveBeenCalledWith('Redis connected');
51
+ }
52
+
53
+ if (handlers['error']) {
54
+ handlers['error'](new Error('Test Error'));
55
+ expect(logger.error).toHaveBeenCalledWith('Redis error:', expect.any(Error));
56
+ }
57
+ });
58
+
59
+ it('should get data from redis', async () => {
60
+ const mockData = { test: 'data' };
61
+ RedisService.client.get.mockResolvedValue(JSON.stringify(mockData));
62
+
63
+ const data = await RedisService.get('test-key');
64
+ expect(data).toEqual(mockData);
65
+ expect(RedisService.client.get).toHaveBeenCalledWith('test-key');
66
+ });
67
+
68
+ it('should return null when key not found in redis', async () => {
69
+ RedisService.client.get.mockResolvedValue(null);
70
+ const data = await RedisService.get('non-existent');
71
+ expect(data).toBeNull();
72
+ });
73
+
74
+ it('should handle errors in get', async () => {
75
+ RedisService.client.get.mockRejectedValue(new Error('Redis Error'));
76
+ const result = await RedisService.get('test-key');
77
+ expect(result).toBeNull();
78
+ expect(logger.error).toHaveBeenCalled();
79
+ });
80
+
81
+ it('should set data to redis without TTL', async () => {
82
+ const mockData = { test: 'data' };
83
+ await RedisService.set('test-key', mockData);
84
+ expect(RedisService.client.set).toHaveBeenCalledWith('test-key', JSON.stringify(mockData));
85
+ });
86
+
87
+ it('should set data to redis with TTL', async () => {
88
+ const mockData = { test: 'data' };
89
+ await RedisService.set('test-key', mockData, 3600);
90
+ expect(RedisService.client.set).toHaveBeenCalledWith('test-key', JSON.stringify(mockData), 'EX', 3600);
91
+ });
92
+
93
+ it('should handle errors in set', async () => {
94
+ RedisService.client.set.mockRejectedValue(new Error('Redis Error'));
95
+ await RedisService.set('test-key', 'value');
96
+ expect(logger.error).toHaveBeenCalled();
97
+ });
98
+
99
+ it('should delete data from redis', async () => {
100
+ await RedisService.del('test-key');
101
+ expect(RedisService.client.del).toHaveBeenCalledWith('test-key');
102
+ });
103
+
104
+ it('should handle errors in del', async () => {
105
+ RedisService.client.del.mockRejectedValue(new Error('Redis Error'));
106
+ await RedisService.del('test-key');
107
+ expect(logger.error).toHaveBeenCalled();
108
+ });
109
+
110
+ it('should use getOrSet and call fetcher if not cached', async () => {
111
+ RedisService.get = jest.fn().mockResolvedValue(null);
112
+ RedisService.set = jest.fn();
113
+ const fetcher = jest.fn().mockResolvedValue({ new: 'data' });
114
+
115
+ const result = await RedisService.getOrSet('new-key', fetcher);
116
+
117
+ expect(result).toEqual({ new: 'data' });
118
+ expect(fetcher).toHaveBeenCalled();
119
+ expect(RedisService.set).toHaveBeenCalledWith('new-key', { new: 'data' }, 3600);
120
+ });
121
+
122
+ it('should use getOrSet and return cached data', async () => {
123
+ const cachedData = { cached: 'data' };
124
+ RedisService.get = jest.fn().mockResolvedValue(cachedData);
125
+ const fetcher = jest.fn();
126
+
127
+ const result = await RedisService.getOrSet('cached-key', fetcher);
128
+
129
+ expect(result).toEqual(cachedData);
130
+ expect(fetcher).not.toHaveBeenCalled();
131
+ });
132
+
133
+ it('should handle falsy data from fetcher in getOrSet', async () => {
134
+ RedisService.get = jest.fn().mockResolvedValue(null);
135
+ RedisService.set = jest.fn();
136
+ const fetcher = jest.fn().mockResolvedValue(null);
137
+
138
+ const result = await RedisService.getOrSet('empty-key', fetcher);
139
+
140
+ expect(result).toBeNull();
141
+ expect(RedisService.set).not.toHaveBeenCalled();
142
+ });
143
+
144
+ it('should quit the redis client', async () => {
145
+ const result = await RedisService.quit();
146
+ expect(result).toBe('OK');
147
+ expect(RedisService.client.quit).toHaveBeenCalled();
148
+ });
149
+ });