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,170 @@
1
+ <% if (communication !== 'GraphQL') { -%>
2
+ const HTTP_STATUS = require('@/utils/httpCodes');
3
+ <% } -%>
4
+ const { getUsers, createUser } = require('@/controllers/userController');
5
+ const User = require('@/models/User');
6
+ <%_ if (caching === 'Redis') { -%>
7
+ const cacheService = require('@/config/redisClient');
8
+ <%_ } else if (caching === 'Memory Cache') { -%>
9
+ const cacheService = require('@/config/memoryCache');
10
+ <%_ } -%>
11
+
12
+ // Mock dependencies
13
+ jest.mock('@/models/User');
14
+ <%_ if (caching === 'Redis') { -%>
15
+ jest.mock('@/config/redisClient', () => ({
16
+ getOrSet: jest.fn(),
17
+ del: jest.fn()
18
+ }));
19
+ <%_ } else if (caching === 'Memory Cache') { -%>
20
+ jest.mock('@/config/memoryCache', () => ({
21
+ getOrSet: jest.fn(),
22
+ del: jest.fn()
23
+ }));
24
+ <%_ } -%>
25
+ jest.mock('@/utils/logger');
26
+
27
+ describe('UserController', () => {
28
+ <% if (communication !== 'GraphQL') { -%>
29
+ let mockRequest;
30
+ let mockResponse;
31
+ let mockNext;
32
+ <% } -%>
33
+
34
+ beforeEach(() => {
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
+ afterEach(() => {
46
+ jest.clearAllMocks();
47
+ });
48
+
49
+ describe('getUsers', () => {
50
+ it('should return successfully (Happy Path)', async () => {
51
+ // Arrange
52
+ const usersMock = [{ id: '1', name: 'Test', email: 'test@example.com' }];
53
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
54
+ cacheService.getOrSet.mockResolvedValue(usersMock);
55
+ <%_ } else { -%>
56
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
57
+ User.find.mockResolvedValue(usersMock);
58
+ <%_ } else { -%>
59
+ User.findAll.mockResolvedValue(usersMock);
60
+ <%_ } -%>
61
+ <%_ } -%>
62
+
63
+ // Act
64
+ <% if (communication === 'GraphQL') { -%>
65
+ const result = await getUsers();
66
+
67
+ // Assert
68
+ expect(result).toEqual(usersMock);
69
+ <% } else { -%>
70
+ await getUsers(mockRequest, mockResponse, mockNext);
71
+
72
+ // Assert
73
+ expect(mockResponse.json).toHaveBeenCalledWith(usersMock);
74
+ <% } -%>
75
+ });
76
+
77
+ it('should handle errors correctly (Error Handling)', async () => {
78
+ // Arrange
79
+ const error = new Error('Database Error');
80
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
81
+ cacheService.getOrSet.mockRejectedValue(error);
82
+ <%_ } else { -%>
83
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
84
+ User.find.mockRejectedValue(error);
85
+ <%_ } else { -%>
86
+ User.findAll.mockRejectedValue(error);
87
+ <%_ } -%>
88
+ <%_ } -%>
89
+
90
+ // Act & Assert
91
+ <% if (communication === 'GraphQL') { -%>
92
+ await expect(getUsers()).rejects.toThrow(error);
93
+ <% } else { -%>
94
+ await getUsers(mockRequest, mockResponse, mockNext);
95
+ expect(mockNext).toHaveBeenCalledWith(error);
96
+ <% } -%>
97
+ });
98
+ });
99
+
100
+ describe('createUser', () => {
101
+ it('should successfully create a new user (Happy Path)', async () => {
102
+ // Arrange
103
+ const payload = { name: 'Alice', email: 'alice@example.com' };
104
+ <% if (communication === 'GraphQL') { -%>
105
+ const dataArg = payload;
106
+ <% } else { -%>
107
+ mockRequest.body = payload;
108
+ <% } -%>
109
+
110
+ const expectedUser = { id: '1', ...payload };
111
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
112
+ User.create.mockResolvedValue(expectedUser);
113
+ <%_ if (database === 'None') { -%>User.mockData = [expectedUser];<%_ } -%>
114
+ <%_ } else { -%>
115
+ User.create.mockResolvedValue(expectedUser);
116
+ <%_ } -%>
117
+
118
+ // Act
119
+ <% if (communication === 'GraphQL') { -%>
120
+ const result = await createUser(dataArg);
121
+
122
+ // Assert
123
+ <%_ if (database === 'None') { -%>
124
+ expect(result.name).toBe(payload.name);
125
+ expect(result.email).toBe(payload.email);
126
+ expect(User.mockData.length).toBe(1);
127
+ <%_ } else { -%>
128
+ expect(result).toEqual(expectedUser);
129
+ expect(User.create).toHaveBeenCalledWith(payload);
130
+ <%_ } -%>
131
+ <% } else { -%>
132
+ await createUser(mockRequest, mockResponse, mockNext);
133
+
134
+ // Assert
135
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
136
+ <%_ if (database === 'None') { -%>
137
+ expect(mockResponse.json).toHaveBeenCalled();
138
+ expect(User.mockData.length).toBe(1);
139
+ <%_ } else { -%>
140
+ expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
141
+ expect(User.create).toHaveBeenCalledWith(payload);
142
+ <%_ } -%>
143
+ <% } -%>
144
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
145
+ expect(cacheService.del).toHaveBeenCalledWith('users:all');
146
+ <%_ } -%>
147
+ });
148
+
149
+ it('should handle errors when creation fails (Error Handling)', async () => {
150
+ // Arrange
151
+ const error = new Error('Creation Error');
152
+ const payload = { name: 'Bob', email: 'bob@example.com' };
153
+ <% if (communication === 'GraphQL') { -%>
154
+ const dataArg = payload;
155
+ <% } else { -%>
156
+ mockRequest.body = payload;
157
+ <% } -%>
158
+
159
+ User.create.mockRejectedValue(error);
160
+
161
+ // Act & Assert
162
+ <% if (communication === 'GraphQL') { -%>
163
+ await expect(createUser(dataArg)).rejects.toThrow(error);
164
+ <% } else { -%>
165
+ await createUser(mockRequest, mockResponse, mockNext);
166
+ expect(mockNext).toHaveBeenCalledWith(error);
167
+ <% } -%>
168
+ });
169
+ });
170
+ });
@@ -1,5 +1,5 @@
1
1
  const { ApiError } = require('./ApiError');
2
- const { HTTP_STATUS } = require('../utils/httpCodes');
2
+ const HTTP_STATUS = require('../utils/httpCodes');
3
3
 
4
4
  class BadRequestError extends ApiError {
5
5
  constructor(message = 'Bad request') {
@@ -0,0 +1,21 @@
1
+ const { BadRequestError } = require('@/errors/BadRequestError');
2
+ const { ApiError } = require('@/errors/ApiError');
3
+ const HTTP_STATUS = require('@/utils/httpCodes');
4
+
5
+ describe('BadRequestError', () => {
6
+ it('should extend ApiError', () => {
7
+ const error = new BadRequestError();
8
+ expect(error).toBeInstanceOf(ApiError);
9
+ });
10
+
11
+ it('should have default message "Bad request"', () => {
12
+ const error = new BadRequestError();
13
+ expect(error.message).toBe('Bad request');
14
+ expect(error.statusCode).toBe(HTTP_STATUS.BAD_REQUEST);
15
+ });
16
+
17
+ it('should accept a custom message', () => {
18
+ const error = new BadRequestError('Custom bad request');
19
+ expect(error.message).toBe('Custom bad request');
20
+ });
21
+ });
@@ -1,5 +1,5 @@
1
1
  const { ApiError } = require('./ApiError');
2
- const { HTTP_STATUS } = require('../utils/httpCodes');
2
+ const HTTP_STATUS = require('../utils/httpCodes');
3
3
 
4
4
  class NotFoundError extends ApiError {
5
5
  constructor(message = 'Resource not found') {
@@ -0,0 +1,21 @@
1
+ const { NotFoundError } = require('@/errors/NotFoundError');
2
+ const { ApiError } = require('@/errors/ApiError');
3
+ const HTTP_STATUS = require('@/utils/httpCodes');
4
+
5
+ describe('NotFoundError', () => {
6
+ it('should extend ApiError', () => {
7
+ const error = new NotFoundError();
8
+ expect(error).toBeInstanceOf(ApiError);
9
+ });
10
+
11
+ it('should have default message "Resource not found"', () => {
12
+ const error = new NotFoundError();
13
+ expect(error.message).toBe('Resource not found');
14
+ expect(error.statusCode).toBe(HTTP_STATUS.NOT_FOUND);
15
+ });
16
+
17
+ it('should accept a custom message', () => {
18
+ const error = new NotFoundError('User not found');
19
+ expect(error.message).toBe('User not found');
20
+ });
21
+ });
@@ -0,0 +1,29 @@
1
+ const { gqlContext } = require('@/graphql/context');
2
+ const { resolvers } = require('@/graphql/resolvers');
3
+ const { typeDefs } = require('@/graphql/typeDefs');
4
+
5
+ describe('GraphQL Context', () => {
6
+ it('should exercise GraphQL index entry points', () => {
7
+ expect(resolvers).toBeDefined();
8
+ expect(typeDefs).toBeDefined();
9
+ });
10
+ it('should return context with token when authorization header is present', async () => {
11
+ const mockRequest = {
12
+ headers: {
13
+ authorization: 'Bearer token123',
14
+ },
15
+ };
16
+
17
+ const context = await gqlContext({ req: mockRequest });
18
+ expect(context).toEqual({ token: 'Bearer token123' });
19
+ });
20
+
21
+ it('should return context with empty token when authorization header is missing', async () => {
22
+ const mockRequest = {
23
+ headers: {},
24
+ };
25
+
26
+ const context = await gqlContext({ req: mockRequest });
27
+ expect(context).toEqual({ token: '' });
28
+ });
29
+ });
@@ -0,0 +1,47 @@
1
+ const { userResolvers } = require('@/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('@/controllers/userController', () => ({
7
+ getUsers: (...args) => mockGetUsers(...args),
8
+ createUser: (...args) => mockCreateUser(...args)
9
+ }));
10
+
11
+ describe('User Resolvers', () => {
12
+ afterEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ describe('Query.getAllUsers', () => {
17
+ it('should return all users', async () => {
18
+ const result = await userResolvers.Query.getAllUsers();
19
+ expect(result).toEqual([{ id: '1', name: 'John Doe', email: 'john@example.com' }]);
20
+ expect(mockGetUsers).toHaveBeenCalledTimes(1);
21
+ });
22
+ });
23
+
24
+ describe('Mutation.createUser', () => {
25
+ it('should create and return a new user', async () => {
26
+ const result = await userResolvers.Mutation.createUser(null, { name: 'Jane', email: 'jane@example.com' });
27
+ expect(result).toEqual({ id: '1', name: 'Jane', email: 'jane@example.com' });
28
+ expect(mockCreateUser).toHaveBeenCalledWith({ name: 'Jane', email: 'jane@example.com' });
29
+ expect(mockCreateUser).toHaveBeenCalledTimes(1);
30
+ });
31
+ });
32
+ <%_ if (database === 'MongoDB') { -%>
33
+ describe('User.id', () => {
34
+ it('should return parent.id if available', () => {
35
+ const parent = { id: '123' };
36
+ const result = userResolvers.User.id(parent);
37
+ expect(result).toBe('123');
38
+ });
39
+
40
+ it('should fallback to parent._id if id is not available', () => {
41
+ const parent = { _id: '456' };
42
+ const result = userResolvers.User.id(parent);
43
+ expect(result).toBe('456');
44
+ });
45
+ });
46
+ <%_ } -%>
47
+ });
@@ -1,6 +1,7 @@
1
1
  const express = require('express');
2
2
  const cors = require('cors');
3
- <%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('./routes/api');<%_ } -%>
3
+ <%_ if (communication === 'REST APIs') { -%>const apiRoutes = require('./routes/api');<%_ } %>
4
+ const healthRoutes = require('./routes/healthRoute');
4
5
  <%_ if (communication === 'Kafka') { -%>const { connectKafka, sendMessage } = require('./services/kafkaService');<%_ } -%>
5
6
  <%_ if (communication === 'GraphQL') { -%>
6
7
  const { ApolloServer } = require('@apollo/server');
@@ -21,7 +22,7 @@ const app = express();
21
22
  const PORT = env.PORT;
22
23
  const logger = require('./utils/logger');
23
24
  const morgan = require('morgan');
24
- const { errorMiddleware } = require('./utils/error.middleware');
25
+ const { errorMiddleware } = require('./utils/errorMiddleware');
25
26
 
26
27
  app.use(cors());
27
28
  app.use(express.json());
@@ -48,15 +49,13 @@ app.get('/', (req, res) => {
48
49
  });
49
50
  });
50
51
  <% } -%>
51
- app.get('/health', (req, res) => {
52
- res.json({ status: 'UP' });
53
- });
52
+ app.use('/health', healthRoutes);
54
53
 
55
54
  // Start Server Logic
56
55
  const startServer = async () => {
57
56
  <%_ if (communication === 'GraphQL') { -%>
58
57
  // GraphQL Setup
59
- const server = new ApolloServer({
58
+ const apolloServer = new ApolloServer({
60
59
  typeDefs,
61
60
  resolvers,
62
61
  plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })],
@@ -80,11 +79,11 @@ const startServer = async () => {
80
79
  return formattedError;
81
80
  },
82
81
  });
83
- await server.start();
84
- app.use('/graphql', expressMiddleware(server, { context: gqlContext }));
82
+ await apolloServer.start();
83
+ app.use('/graphql', expressMiddleware(apolloServer, { context: gqlContext }));
85
84
  <%_ } -%>
86
85
  app.use(errorMiddleware);
87
- app.listen(PORT, () => {
86
+ const server = app.listen(PORT, () => {
88
87
  logger.info(`Server running on port ${PORT}`);
89
88
  <%_ if (communication === 'Kafka') { -%>
90
89
  connectKafka().then(() => {
@@ -95,6 +94,9 @@ const startServer = async () => {
95
94
  });
96
95
  <%_ } -%>
97
96
  });
97
+
98
+ const setupGracefulShutdown = require('./utils/gracefulShutdown');
99
+ setupGracefulShutdown(server);
98
100
  };
99
101
 
100
102
  <%_ if (database !== 'None') { -%>
@@ -0,0 +1,36 @@
1
+ const request = require('supertest');
2
+ const express = require('express');
3
+ const router = require('@/routes/api');
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('@/controllers/userController', () => ({
9
+ getUsers: (...args) => mockGetUsers(...args),
10
+ createUser: (...args) => mockCreateUser(...args)
11
+ }));
12
+
13
+ describe('ApiRoutes', () => {
14
+ let app;
15
+
16
+ beforeEach(() => {
17
+ app = express();
18
+ app.use(express.json());
19
+ app.use('/api', router);
20
+ });
21
+
22
+ it('GET /api/users should call controller.getUsers', async () => {
23
+ await request(app)
24
+ .get('/api/users');
25
+
26
+ expect(mockGetUsers).toHaveBeenCalledTimes(1);
27
+ });
28
+
29
+ it('POST /api/users should call controller.createUser', async () => {
30
+ await request(app)
31
+ .post('/api/users')
32
+ .send({ name: 'Test', email: 'test@example.com' });
33
+
34
+ expect(mockCreateUser).toHaveBeenCalledTimes(1);
35
+ });
36
+ });
@@ -0,0 +1,63 @@
1
+ jest.mock('winston-daily-rotate-file');
2
+ jest.mock('winston', () => {
3
+ const mockLogger = {
4
+ add: jest.fn(),
5
+ info: jest.fn(),
6
+ error: jest.fn(),
7
+ warn: jest.fn()
8
+ };
9
+ const format = {
10
+ combine: jest.fn(),
11
+ timestamp: jest.fn(),
12
+ json: jest.fn(),
13
+ simple: jest.fn()
14
+ };
15
+ const transports = {
16
+ Console: jest.fn(),
17
+ DailyRotateFile: jest.fn()
18
+ };
19
+ return {
20
+ format,
21
+ transports,
22
+ createLogger: jest.fn().mockReturnValue(mockLogger)
23
+ };
24
+ });
25
+
26
+ <% if (architecture === 'MVC') { -%>
27
+ const logger = require('@/utils/logger');
28
+ <% } else { -%>
29
+ const logger = require('@/infrastructure/log/logger');
30
+ <% } -%>
31
+
32
+ describe('Logger', () => {
33
+ it('should export a logger instance', () => {
34
+ expect(logger).toBeDefined();
35
+ });
36
+
37
+ it('should have info method', () => {
38
+ expect(typeof logger.info).toBe('function');
39
+ });
40
+
41
+ it('should have error method', () => {
42
+ expect(typeof logger.error).toBe('function');
43
+ });
44
+
45
+ it('should call info', () => {
46
+ logger.info('test message');
47
+ expect(logger.info).toHaveBeenCalledWith('test message');
48
+ });
49
+
50
+ it('should call error', () => {
51
+ logger.error('test error');
52
+ expect(logger.error).toHaveBeenCalledWith('test error');
53
+ });
54
+
55
+ it('should use JSON format in production environment', () => {
56
+ const winston = require('winston');
57
+ jest.resetModules();
58
+ process.env.NODE_ENV = 'production';
59
+ require('@/utils/logger');
60
+ expect(winston.format.json).toHaveBeenCalled();
61
+ process.env.NODE_ENV = 'test';
62
+ });
63
+ });
@@ -0,0 +1,185 @@
1
+ <% if (communication !== 'GraphQL') { -%>
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { HTTP_STATUS } from '@/utils/httpCodes';
4
+ <% } -%>
5
+ import { UserController } from '@/controllers/userController';
6
+ import User from '@/models/User';
7
+ <%_ if (caching === 'Redis') { -%>
8
+ import cacheService from '@/config/redisClient';
9
+ <%_ } else if (caching === 'Memory Cache') { -%>
10
+ import cacheService from '@/config/memoryCache';
11
+ <%_ } -%>
12
+
13
+ // Mock dependencies
14
+ jest.mock('@/models/User');
15
+ <%_ if (caching === 'Redis') { -%>
16
+ jest.mock('@/config/redisClient', () => ({
17
+ getOrSet: jest.fn(),
18
+ del: jest.fn()
19
+ }));
20
+ <%_ } else if (caching === 'Memory Cache') { -%>
21
+ jest.mock('@/config/memoryCache', () => ({
22
+ getOrSet: jest.fn(),
23
+ del: jest.fn()
24
+ }));
25
+ <%_ } -%>
26
+ jest.mock('@/utils/logger');
27
+
28
+ describe('UserController', () => {
29
+ let userController: UserController;
30
+ <% if (communication !== 'GraphQL') { -%>
31
+ let mockRequest: Partial<Request>;
32
+ let mockResponse: Partial<Response>;
33
+ let mockNext: NextFunction;
34
+ <% } -%>
35
+
36
+ beforeEach(() => {
37
+ userController = new UserController();
38
+ <% if (communication !== 'GraphQL') { -%>
39
+ mockRequest = {};
40
+ mockResponse = {
41
+ json: jest.fn(),
42
+ status: jest.fn().mockReturnThis(),
43
+ };
44
+ mockNext = jest.fn();
45
+ <% } -%>
46
+ });
47
+
48
+ afterEach(() => {
49
+ jest.clearAllMocks();
50
+ });
51
+
52
+ describe('getUsers', () => {
53
+ it('should return successfully (Happy Path)', async () => {
54
+ // Arrange
55
+ const usersMock = [{ id: '1', name: 'Test', email: 'test@example.com' }];
56
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
57
+ (cacheService.getOrSet as jest.Mock).mockResolvedValue(usersMock);
58
+ <%_ } else { -%>
59
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
60
+ (User.find as jest.Mock).mockResolvedValue(usersMock);
61
+ <%_ } else { -%>
62
+ (User.findAll as jest.Mock).mockResolvedValue(usersMock);
63
+ <%_ } -%>
64
+ <%_ } -%>
65
+
66
+ // Act
67
+ <% if (communication === 'GraphQL') { -%>
68
+ const result = await userController.getUsers();
69
+
70
+ // Assert
71
+ expect(result).toEqual(usersMock);
72
+ <% } else { -%>
73
+ await userController.getUsers(mockRequest as Request, mockResponse as Response, mockNext);
74
+
75
+ // Assert
76
+ expect(mockResponse.json).toHaveBeenCalledWith(usersMock);
77
+ <% } -%>
78
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
79
+ expect(cacheService.getOrSet).toHaveBeenCalled();
80
+ <%_ } else { -%>
81
+ <%_ if (database === 'MongoDB') { -%>
82
+ expect(User.find).toHaveBeenCalled();
83
+ <%_ } else if (database === 'None') { -%>
84
+ // No DB mock logic
85
+ <%_ } else { -%>
86
+ expect(User.findAll).toHaveBeenCalled();
87
+ <%_ } -%>
88
+ <%_ } -%>
89
+ });
90
+
91
+ it('should handle errors correctly (Error Handling)', async () => {
92
+ // Arrange
93
+ const error = new Error('Database Error');
94
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
95
+ (cacheService.getOrSet as jest.Mock).mockRejectedValue(error);
96
+ <%_ } else { -%>
97
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
98
+ (User.find as jest.Mock).mockRejectedValue(error);
99
+ <%_ } else { -%>
100
+ (User.findAll as jest.Mock).mockRejectedValue(error);
101
+ <%_ } -%>
102
+ <%_ } -%>
103
+
104
+ // Act & Assert
105
+ <% if (communication === 'GraphQL') { -%>
106
+ await expect(userController.getUsers()).rejects.toThrow(error);
107
+ <% } else { -%>
108
+ await userController.getUsers(mockRequest as Request, mockResponse as Response, mockNext);
109
+ expect(mockNext).toHaveBeenCalledWith(error);
110
+ <% } -%>
111
+ });
112
+ });
113
+
114
+ describe('createUser', () => {
115
+ it('should successfully create a new user (Happy Path)', async () => {
116
+ // Arrange
117
+ const payload = { name: 'Alice', email: 'alice@example.com' };
118
+ <% if (communication === 'GraphQL') { -%>
119
+ const dataArg = payload;
120
+ <% } else { -%>
121
+ mockRequest.body = payload;
122
+ <% } -%>
123
+
124
+ <%_ if (database === 'MongoDB' || database === 'None') { -%>
125
+ const expectedUser = { id: '1', ...payload };
126
+ (User.create as jest.Mock).mockResolvedValue(expectedUser);
127
+ <%_ if (database === 'None') { -%>User.mockData = [expectedUser];<%_ } -%>
128
+ <%_ } else { -%>
129
+ const expectedUser = { id: '1', ...payload };
130
+ (User.create as jest.Mock).mockResolvedValue(expectedUser);
131
+ <%_ } -%>
132
+
133
+ // Act
134
+ <% if (communication === 'GraphQL') { -%>
135
+ const result = await userController.createUser(dataArg);
136
+
137
+ // Assert
138
+ <%_ if (database === 'None') { -%>
139
+ expect(result.name).toBe(payload.name);
140
+ expect(result.email).toBe(payload.email);
141
+ expect(User.mockData.length).toBe(1);
142
+ <%_ } else { -%>
143
+ expect(result).toEqual(expectedUser);
144
+ expect(User.create).toHaveBeenCalledWith(payload);
145
+ <%_ } -%>
146
+ <% } else { -%>
147
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
148
+
149
+ // Assert
150
+ expect(mockResponse.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
151
+ <%_ if (database === 'None') { -%>
152
+ expect(mockResponse.json).toHaveBeenCalled();
153
+ expect(User.mockData.length).toBe(1);
154
+ <%_ } else { -%>
155
+ expect(mockResponse.json).toHaveBeenCalledWith(expectedUser);
156
+ expect(User.create).toHaveBeenCalledWith(payload);
157
+ <%_ } -%>
158
+ <% } -%>
159
+ <%_ if (caching === 'Redis' || caching === 'Memory Cache') { -%>
160
+ expect(cacheService.del).toHaveBeenCalledWith('users:all');
161
+ <%_ } -%>
162
+ });
163
+
164
+ it('should handle errors when creation fails (Error Handling)', async () => {
165
+ // Arrange
166
+ const error = new Error('Creation Error');
167
+ const payload = { name: 'Bob', email: 'bob@example.com' };
168
+ <% if (communication === 'GraphQL') { -%>
169
+ const dataArg = payload;
170
+ <% } else { -%>
171
+ mockRequest.body = payload;
172
+ <% } -%>
173
+
174
+ (User.create as jest.Mock).mockRejectedValue(error);
175
+
176
+ // Act & Assert
177
+ <% if (communication === 'GraphQL') { -%>
178
+ await expect(userController.createUser(dataArg)).rejects.toThrow(error);
179
+ <% } else { -%>
180
+ await userController.createUser(mockRequest as Request, mockResponse as Response, mockNext);
181
+ expect(mockNext).toHaveBeenCalledWith(error);
182
+ <% } -%>
183
+ });
184
+ });
185
+ });