nodejs-quickstart-structure 1.13.0 → 1.15.1

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 (103) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +4 -3
  3. package/bin/index.js +84 -80
  4. package/lib/generator.js +28 -4
  5. package/lib/modules/app-setup.js +111 -19
  6. package/lib/modules/caching-setup.js +13 -0
  7. package/lib/modules/config-files.js +50 -62
  8. package/lib/modules/database-setup.js +35 -30
  9. package/lib/modules/kafka-setup.js +78 -10
  10. package/package.json +8 -4
  11. package/templates/clean-architecture/js/src/errors/BadRequestError.js +1 -1
  12. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  13. package/templates/clean-architecture/js/src/errors/NotFoundError.js +1 -1
  14. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  15. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +2 -3
  17. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +81 -0
  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/infrastructure/log/logger.spec.ts.ejs +64 -0
  30. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +85 -0
  31. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +2 -3
  32. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +166 -0
  33. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  34. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  35. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  36. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  37. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  38. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  39. package/templates/common/.cursorrules.ejs +60 -0
  40. package/templates/common/.dockerignore +2 -0
  41. package/templates/common/.gitlab-ci.yml.ejs +5 -5
  42. package/templates/common/Jenkinsfile.ejs +1 -1
  43. package/templates/common/README.md.ejs +11 -1
  44. package/templates/common/_github/workflows/ci.yml +7 -4
  45. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  46. package/templates/common/caching/js/redisClient.spec.js.ejs +149 -0
  47. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  48. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  49. package/templates/common/database/js/database.spec.js.ejs +56 -0
  50. package/templates/common/database/js/models/User.js.ejs +22 -0
  51. package/templates/common/database/js/models/User.spec.js.ejs +84 -0
  52. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  53. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  54. package/templates/common/database/ts/models/User.spec.ts.ejs +84 -0
  55. package/templates/common/database/ts/models/User.ts.ejs +26 -0
  56. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  57. package/templates/common/eslint.config.mjs.ejs +11 -2
  58. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  59. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  60. package/templates/common/jest.config.js.ejs +19 -5
  61. package/templates/common/kafka/js/config/kafka.spec.js.ejs +21 -0
  62. package/templates/common/kafka/js/services/kafkaService.js.ejs +9 -5
  63. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +60 -0
  64. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +21 -0
  65. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +61 -0
  66. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +1 -1
  67. package/templates/common/package.json.ejs +0 -3
  68. package/templates/common/prompts/add-feature.md.ejs +26 -0
  69. package/templates/common/prompts/project-context.md.ejs +43 -0
  70. package/templates/common/prompts/troubleshoot.md.ejs +28 -0
  71. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +160 -0
  72. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +158 -0
  73. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  74. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  75. package/templates/common/tsconfig.json +1 -1
  76. package/templates/mvc/js/src/controllers/userController.js.ejs +4 -31
  77. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +170 -0
  78. package/templates/mvc/js/src/errors/BadRequestError.js +1 -1
  79. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  80. package/templates/mvc/js/src/errors/NotFoundError.js +1 -1
  81. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  82. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  83. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +47 -0
  84. package/templates/mvc/js/src/index.js.ejs +1 -1
  85. package/templates/mvc/js/src/routes/api.spec.js.ejs +36 -0
  86. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  87. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +185 -0
  88. package/templates/mvc/ts/src/controllers/userController.ts.ejs +4 -31
  89. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  90. package/templates/mvc/ts/src/errors/BadRequestError.ts +1 -1
  91. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  92. package/templates/mvc/ts/src/errors/NotFoundError.ts +1 -1
  93. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  94. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  95. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +40 -0
  96. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  97. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +64 -0
  98. package/docs/demo.gif +0 -0
  99. package/docs/generateCase.md +0 -265
  100. package/docs/generatorFlow.md +0 -233
  101. package/docs/releaseNoteRule.md +0 -42
  102. package/docs/ruleDevelop.md +0 -30
  103. package/templates/common/tests/health.test.ts.ejs +0 -24
@@ -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,60 @@
1
+ # Cursor AI Coding Rules for <%= projectName %>
2
+
3
+ ## Project Context
4
+ You are an expert working on **<%= projectName %>**.
5
+ - **Project Goal**: [Replace this with your business logic, e.g., E-commerce API]
6
+ - **Language**: <%= language %>
7
+ - **Architecture**: <%= architecture %>
8
+ - **Database**: <%= database %>
9
+ - **Communication**: <%= communication %>
10
+
11
+ ## Excluded Files/Folders
12
+ When indexing or searching the workspace, ignore the following paths to prevent context pollution:
13
+ - `node_modules/`
14
+ - `dist/`
15
+ - `build/`
16
+ - `coverage/`
17
+ - `.git/`
18
+
19
+ ## Strict Rules
20
+
21
+ ### 1. Testing First
22
+ - Every new service or controller method MUST have a test file in `tests/`.
23
+ - **Coverage Gate**: Aim for > 70% coverage (Statement/Line/Function/Branch).
24
+ - **Format**: Use Jest with the AAA (Arrange, Act, Assert) pattern.
25
+ - **Isolation**: Mock external dependencies (DB, Redis, etc.) using `jest.mock()`.
26
+
27
+ ### 2. Error Handling
28
+ - Do NOT use generic `Error`.
29
+ - Use custom classes from `src/errors/` (e.g., `ApiError`, `NotFoundError`, `BadRequestError`).
30
+ <% if (language === 'TypeScript') { -%>
31
+ - Use `HTTP_STATUS` constants from `@/utils/httpCodes` for status codes.
32
+ <% } else { -%>
33
+ - Use `HTTP_STATUS` constants from `../utils/httpCodes.js` for status codes.
34
+ <% } -%>
35
+
36
+ ### 3. File Naming & Style
37
+ - **Controllers**: camelCase (e.g., `userController.<% if (language === 'TypeScript') { %>ts<% } else { %>js<% } %>`).
38
+ - **Services**: camelCase (e.g., `userService.<% if (language === 'TypeScript') { %>ts<% } else { %>js<% } %>`).
39
+ - **Routes**: camelCase (e.g., `userRoutes.<% if (language === 'TypeScript') { %>ts<% } else { %>js<% } %>`).
40
+ <% if (language === 'TypeScript') { -%>
41
+ - **Imports**: Use path aliases (e.g., `@/services/...`) instead of relative paths.
42
+ - **Typing**: Ensure strong typing for interfaces and DTOs. Do not use `any` unless absolutely necessary.
43
+ <% } else { -%>
44
+ - **Imports**: Use relative paths as dictated by the directory structure.
45
+ <% } -%>
46
+
47
+ ### 4. Architecture Standards
48
+ <% if (architecture === 'Clean Architecture') { -%>
49
+ - Enforce strict separation of concerns:
50
+ - `domain`: Entities and enterprise business rules.
51
+ - `usecases`: Application business rules.
52
+ - `interfaces`: Controllers and Routes.
53
+ - `infrastructure`: Frameworks, Database, Caching, and Web Server.
54
+ - Dependencies point inward toward the `domain`.
55
+ <% } else { -%>
56
+ - Enforce MVC standards:
57
+ - `models`: Data layer.
58
+ - `controllers`: Request handlers and business logic.
59
+ - `routes`: Define endpoints routing to controllers.
60
+ <% } -%>
@@ -8,3 +8,5 @@ README.md
8
8
  docker-compose.yml
9
9
  test_results.log
10
10
  flyway/sql
11
+ .cursorrules
12
+ prompts
@@ -12,24 +12,24 @@ cache:
12
12
 
13
13
  install_dependencies:
14
14
  stage: .pre
15
- image: node:18-alpine
15
+ image: node:22-alpine
16
16
  script:
17
17
  - npm ci
18
18
 
19
19
  lint_code:
20
20
  stage: lint
21
- image: node:18-alpine
21
+ image: node:22-alpine
22
22
  script:
23
23
  - npm run lint
24
24
 
25
25
  run_tests:
26
26
  stage: test
27
- image: node:18-alpine
27
+ image: node:22-alpine
28
28
  script:
29
- - npm test
29
+ - npm run test:coverage
30
30
 
31
31
  build_app:
32
32
  stage: build
33
- image: node:18-alpine
33
+ image: node:22-alpine
34
34
  script:
35
35
  - npm run build --if-present
@@ -21,7 +21,7 @@ pipeline {
21
21
 
22
22
  stage('Test') {
23
23
  steps {
24
- sh 'npm test'
24
+ sh 'npm run test:coverage'
25
25
  }
26
26
  }
27
27
 
@@ -217,4 +217,14 @@ docker-compose down
217
217
  - **Helmet**: Sets secure HTTP headers.
218
218
  - **CORS**: Configured for cross-origin requests.
219
219
  - **Rate Limiting**: Protects against DDoS / Brute-force.
220
- - **HPP**: Prevents HTTP Parameter Pollution attacks.
220
+ - **HPP**: Prevents HTTP Parameter Pollution attacks.
221
+
222
+
223
+ ## 🤖 AI-Native Development
224
+
225
+ This project is "AI-Ready" out of the box. We have pre-configured industry-leading AI context files to bridge the gap between "Generated Code" and "AI-Assisted Development."
226
+
227
+ - **Magic Defaults**: We've automatically tailored your AI context to focus on **<%= projectName %>** and its specific architectural stack (<%= architecture %>, <%= database %>, etc.).
228
+ - **Use Cursor?** We've configured **`.cursorrules`** at the root. It enforces project standards (70% coverage, MVC/Clean) directly within the editor.
229
+ - *Pro-tip*: You can customize the `Project Goal` placeholder in `.cursorrules` to help the AI understand your specific business logic!
230
+ - **Use ChatGPT/Gemini/Claude?** Check the **`prompts/`** directory. It contains highly-specialized Agent Skill templates. You can copy-paste these into any LLM to give it a "Senior Developer" understanding of your codebase immediately.
@@ -1,5 +1,8 @@
1
1
  name: Node.js CI
2
2
 
3
+ env:
4
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
5
+
3
6
  on:
4
7
  push:
5
8
  branches: [ "main" ]
@@ -13,13 +16,13 @@ jobs:
13
16
 
14
17
  strategy:
15
18
  matrix:
16
- node-version: [18.x, 20.x]
19
+ node-version: [20.x, 22.x]
17
20
 
18
21
  steps:
19
- - uses: actions/checkout@v3
22
+ - uses: actions/checkout@v4
20
23
 
21
24
  - name: Use Node.js ${{ matrix.node-version }}
22
- uses: actions/setup-node@v3
25
+ uses: actions/setup-node@v4
23
26
  with:
24
27
  node-version: ${{ matrix.node-version }}
25
28
  cache: 'npm'
@@ -31,7 +34,7 @@ jobs:
31
34
  run: npm run lint
32
35
 
33
36
  - name: Run Tests
34
- run: npm test
37
+ run: npm run test:coverage
35
38
 
36
39
  - name: Build
37
40
  run: npm run build --if-present