nodejs-quickstart-structure 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -3
  3. package/lib/generator.js +17 -3
  4. package/lib/modules/app-setup.js +111 -19
  5. package/lib/modules/caching-setup.js +13 -0
  6. package/lib/modules/config-files.js +25 -62
  7. package/lib/modules/database-setup.js +35 -30
  8. package/lib/modules/kafka-setup.js +78 -10
  9. package/package.json +1 -2
  10. package/templates/clean-architecture/js/src/errors/BadRequestError.js +1 -1
  11. package/templates/clean-architecture/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  12. package/templates/clean-architecture/js/src/errors/NotFoundError.js +1 -1
  13. package/templates/clean-architecture/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  14. package/templates/clean-architecture/js/src/infrastructure/log/logger.spec.js.ejs +63 -0
  15. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.js.ejs +2 -3
  16. package/templates/clean-architecture/js/src/infrastructure/repositories/UserRepository.spec.js.ejs +81 -0
  17. package/templates/clean-architecture/js/src/interfaces/controllers/userController.js.ejs +8 -4
  18. package/templates/clean-architecture/js/src/interfaces/controllers/userController.spec.js.ejs +102 -0
  19. package/templates/clean-architecture/js/src/interfaces/graphql/context.spec.js.ejs +31 -0
  20. package/templates/clean-architecture/js/src/interfaces/graphql/resolvers/user.resolvers.spec.js.ejs +49 -0
  21. package/templates/clean-architecture/js/src/interfaces/routes/api.spec.js.ejs +38 -0
  22. package/templates/clean-architecture/js/src/usecases/CreateUser.spec.js.ejs +51 -0
  23. package/templates/clean-architecture/js/src/usecases/GetAllUsers.spec.js.ejs +61 -0
  24. package/templates/clean-architecture/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  25. package/templates/clean-architecture/ts/src/errors/BadRequestError.ts +1 -1
  26. package/templates/clean-architecture/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  27. package/templates/clean-architecture/ts/src/errors/NotFoundError.ts +1 -1
  28. package/templates/clean-architecture/ts/src/infrastructure/log/logger.spec.ts.ejs +64 -0
  29. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.spec.ts.ejs +85 -0
  30. package/templates/clean-architecture/ts/src/infrastructure/repositories/UserRepository.ts.ejs +2 -3
  31. package/templates/clean-architecture/ts/src/interfaces/controllers/userController.spec.ts.ejs +166 -0
  32. package/templates/clean-architecture/ts/src/interfaces/graphql/context.spec.ts.ejs +32 -0
  33. package/templates/clean-architecture/ts/src/interfaces/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  34. package/templates/clean-architecture/ts/src/interfaces/routes/userRoutes.spec.ts.ejs +40 -0
  35. package/templates/clean-architecture/ts/src/usecases/createUser.spec.ts.ejs +51 -0
  36. package/templates/clean-architecture/ts/src/usecases/getAllUsers.spec.ts.ejs +63 -0
  37. package/templates/clean-architecture/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  38. package/templates/common/caching/js/memoryCache.spec.js.ejs +101 -0
  39. package/templates/common/caching/js/redisClient.spec.js.ejs +149 -0
  40. package/templates/common/caching/ts/memoryCache.spec.ts.ejs +102 -0
  41. package/templates/common/caching/ts/redisClient.spec.ts.ejs +157 -0
  42. package/templates/common/database/js/database.spec.js.ejs +56 -0
  43. package/templates/common/database/js/models/User.js.ejs +22 -0
  44. package/templates/common/database/js/models/User.spec.js.ejs +84 -0
  45. package/templates/common/database/js/mongoose.spec.js.ejs +43 -0
  46. package/templates/common/database/ts/database.spec.ts.ejs +56 -0
  47. package/templates/common/database/ts/models/User.spec.ts.ejs +84 -0
  48. package/templates/common/database/ts/models/User.ts.ejs +26 -0
  49. package/templates/common/database/ts/mongoose.spec.ts.ejs +42 -0
  50. package/templates/common/eslint.config.mjs.ejs +11 -2
  51. package/templates/common/health/js/healthRoute.spec.js.ejs +70 -0
  52. package/templates/common/health/ts/healthRoute.spec.ts.ejs +76 -0
  53. package/templates/common/jest.config.js.ejs +19 -5
  54. package/templates/common/kafka/js/config/kafka.spec.js.ejs +21 -0
  55. package/templates/common/kafka/js/services/kafkaService.js.ejs +9 -5
  56. package/templates/common/kafka/js/services/kafkaService.spec.js.ejs +60 -0
  57. package/templates/common/kafka/ts/config/kafka.spec.ts.ejs +21 -0
  58. package/templates/common/kafka/ts/services/kafkaService.spec.ts.ejs +61 -0
  59. package/templates/common/kafka/ts/services/kafkaService.ts.ejs +1 -1
  60. package/templates/common/package.json.ejs +0 -3
  61. package/templates/common/shutdown/js/gracefulShutdown.spec.js.ejs +160 -0
  62. package/templates/common/shutdown/ts/gracefulShutdown.spec.ts.ejs +158 -0
  63. package/templates/common/src/utils/errorMiddleware.spec.js.ejs +79 -0
  64. package/templates/common/src/utils/errorMiddleware.spec.ts.ejs +94 -0
  65. package/templates/common/tsconfig.json +1 -1
  66. package/templates/mvc/js/src/controllers/userController.js.ejs +4 -31
  67. package/templates/mvc/js/src/controllers/userController.spec.js.ejs +170 -0
  68. package/templates/mvc/js/src/errors/BadRequestError.js +1 -1
  69. package/templates/mvc/js/src/errors/BadRequestError.spec.js.ejs +21 -0
  70. package/templates/mvc/js/src/errors/NotFoundError.js +1 -1
  71. package/templates/mvc/js/src/errors/NotFoundError.spec.js.ejs +21 -0
  72. package/templates/mvc/js/src/graphql/context.spec.js.ejs +29 -0
  73. package/templates/mvc/js/src/graphql/resolvers/user.resolvers.spec.js.ejs +47 -0
  74. package/templates/mvc/js/src/index.js.ejs +1 -1
  75. package/templates/mvc/js/src/routes/api.spec.js.ejs +36 -0
  76. package/templates/mvc/js/src/utils/logger.spec.js.ejs +63 -0
  77. package/templates/mvc/ts/src/controllers/userController.spec.ts.ejs +185 -0
  78. package/templates/mvc/ts/src/controllers/userController.ts.ejs +4 -31
  79. package/templates/mvc/ts/src/errors/BadRequestError.spec.ts.ejs +21 -0
  80. package/templates/mvc/ts/src/errors/BadRequestError.ts +1 -1
  81. package/templates/mvc/ts/src/errors/NotFoundError.spec.ts.ejs +21 -0
  82. package/templates/mvc/ts/src/errors/NotFoundError.ts +1 -1
  83. package/templates/mvc/ts/src/graphql/context.spec.ts.ejs +30 -0
  84. package/templates/mvc/ts/src/graphql/resolvers/user.resolvers.spec.ts.ejs +51 -0
  85. package/templates/mvc/ts/src/routes/api.spec.ts.ejs +40 -0
  86. package/templates/mvc/ts/src/utils/errorMiddleware.ts.ejs +1 -2
  87. package/templates/mvc/ts/src/utils/logger.spec.ts.ejs +64 -0
  88. package/docs/demo.gif +0 -0
  89. package/docs/generateCase.md +0 -265
  90. package/docs/generatorFlow.md +0 -233
  91. package/docs/releaseNoteRule.md +0 -42
  92. package/docs/ruleDevelop.md +0 -30
  93. package/templates/common/tests/health.test.ts.ejs +0 -24
@@ -0,0 +1,61 @@
1
+ const GetAllUsers = require('@/usecases/GetAllUsers');
2
+ const UserRepository = require('@/infrastructure/repositories/UserRepository');
3
+ <%_ if (caching !== 'None') { -%>
4
+ const cacheService = require('<% 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;
18
+ let mockUserRepository;
19
+
20
+ beforeEach(() => {
21
+ mockUserRepository = new 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);
29
+ <%_ if (caching !== 'None') { -%>
30
+ cacheService.get.mockResolvedValue(null); // Cache miss
31
+ <%_ } -%>
32
+ const result = await getAllUsers.execute();
33
+
34
+ expect(mockUserRepository.getUsers).toHaveBeenCalledTimes(1);
35
+ expect(result).toEqual(expectedUsers);
36
+ <%_ if (caching !== 'None') { -%>
37
+ expect(cacheService.set).toHaveBeenCalled();
38
+ <%_ } -%>
39
+ });
40
+
41
+ <%_ if (caching !== 'None') { -%>
42
+ it('should return from cache if available', async () => {
43
+ const cachedUsers = [{ id: 1, name: 'Cached', email: 'cached@example.com' }];
44
+ cacheService.get.mockResolvedValue(cachedUsers);
45
+
46
+ const result = await getAllUsers.execute();
47
+
48
+ expect(mockUserRepository.getUsers).not.toHaveBeenCalled();
49
+ expect(result).toEqual(cachedUsers);
50
+ });
51
+ <%_ } -%>
52
+
53
+ it('should throw an error if repository fails', async () => {
54
+ const error = new Error('Database error');
55
+ mockUserRepository.getUsers.mockRejectedValue(error);
56
+ <%_ if (caching !== 'None') { -%>
57
+ cacheService.get.mockResolvedValue(null); // Cache miss
58
+ <%_ } -%>
59
+ await expect(getAllUsers.execute()).rejects.toThrow(error);
60
+ });
61
+ });
@@ -0,0 +1,21 @@
1
+ import { BadRequestError } from '@/errors/BadRequestError';
2
+ import { ApiError } from '@/errors/ApiError';
3
+ import { HTTP_STATUS } from '@/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,4 +1,4 @@
1
- import { ApiError } from './ApiError';
1
+ import { ApiError } from '@/errors/ApiError';
2
2
  import { HTTP_STATUS } from '@/utils/httpCodes';
3
3
 
4
4
  export class BadRequestError extends ApiError {
@@ -0,0 +1,21 @@
1
+ import { NotFoundError } from '@/errors/NotFoundError';
2
+ import { ApiError } from '@/errors/ApiError';
3
+ import { HTTP_STATUS } from '@/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
+ });
@@ -1,4 +1,4 @@
1
- import { ApiError } from './ApiError';
1
+ import { ApiError } from '@/errors/ApiError';
2
2
  import { HTTP_STATUS } from '@/utils/httpCodes';
3
3
 
4
4
  export class NotFoundError extends ApiError {
@@ -0,0 +1,64 @@
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
+ import logger from '@/utils/logger';
28
+ <% } else { -%>
29
+ import logger from '@/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
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
60
+ require('<% if (architecture === "MVC") { %>@/utils/logger<% } else { %>@/infrastructure/log/logger<% } %>');
61
+ expect(winston.format.json).toHaveBeenCalled();
62
+ process.env.NODE_ENV = 'test';
63
+ });
64
+ });
@@ -0,0 +1,85 @@
1
+ import { UserRepository } from '@/infrastructure/repositories/UserRepository';
2
+ import UserModel from '@/infrastructure/database/models/User';
3
+
4
+ // Mock DB Model Database Layer
5
+ jest.mock('@/infrastructure/database/models/User');
6
+
7
+ describe('UserRepository', () => {
8
+ let userRepository: UserRepository;
9
+
10
+ beforeEach(() => {
11
+ userRepository = new UserRepository();
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ describe('save', () => {
16
+ it('should save and return a newly created user (Happy Path)', async () => {
17
+ // Arrange
18
+ const payload = { id: '', name: 'TestUser', email: 'test@example.com' };
19
+ <%_ if (database === 'MongoDB') { -%>
20
+ const mockDbRecord = { _id: { toString: () => '1' }, name: 'TestUser', email: 'test@example.com' };
21
+ (UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
22
+ <%_ } else if (database === 'None') { -%>
23
+ (UserModel.create as jest.Mock).mockResolvedValue(payload);
24
+ <%_ } else { -%>
25
+ const mockDbRecord = { id: '1', name: 'TestUser', email: 'test@example.com' };
26
+ (UserModel.create as jest.Mock).mockResolvedValue(mockDbRecord);
27
+ <%_ } -%>
28
+
29
+ // Act
30
+ const result = await userRepository.save(payload);
31
+
32
+ // Assert
33
+ <%_ if (database === 'None') { -%>
34
+ expect(result.name).toEqual(payload.name)
35
+ <%_ } else { -%>
36
+ expect(result).toEqual({ id: '1', name: 'TestUser', email: 'test@example.com' });
37
+ expect(UserModel.create).toHaveBeenCalledWith({ name: payload.name, email: payload.email });
38
+ <%_ } -%>
39
+ });
40
+
41
+ it('should throw an error when DB fails explicitly (Edge Case)', async () => {
42
+ <%_ if (database === 'None') { -%>
43
+ // Mocks do not naturally fail
44
+ <%_ } else { -%>
45
+ // Arrange
46
+ const payload = { id: '', name: 'FailUser', email: 'fail@example.com' };
47
+ const error = new Error('DB Connection Refused');
48
+ (UserModel.create as jest.Mock).mockRejectedValue(error);
49
+
50
+ // Act & Assert
51
+ await expect(userRepository.save(payload)).rejects.toThrow(error);
52
+ <%_ } -%>
53
+ });
54
+ });
55
+
56
+ describe('getUsers', () => {
57
+ it('should return a list of mapped UserEntities (Happy Path)', async () => {
58
+ // Arrange
59
+ <%_ if (database === 'MongoDB') { -%>
60
+ const mockDbRecords = [{ _id: { toString: () => '1' }, name: 'User1', email: 'user1@example.com' }];
61
+ (UserModel.find as jest.Mock).mockResolvedValue(mockDbRecords);
62
+ <%_ } else if (database === 'None') { -%>
63
+ const mockData = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
64
+ (UserModel.find as jest.Mock).mockResolvedValue(mockData);
65
+ <%_ } else { -%>
66
+ const mockDbRecords = [{ id: '1', name: 'User1', email: 'user1@example.com' }];
67
+ (UserModel.findAll as jest.Mock).mockResolvedValue(mockDbRecords);
68
+ <%_ } -%>
69
+
70
+ // Act
71
+ const result = await userRepository.getUsers();
72
+
73
+ // Assert
74
+ expect(result).toHaveLength(1);
75
+ expect(result[0]).toEqual({ id: '1', name: 'User1', email: 'user1@example.com' });
76
+ <%_ if (database !== 'None') { -%>
77
+ <%_ if (database === 'MongoDB') { -%>
78
+ expect(UserModel.find).toHaveBeenCalled();
79
+ <%_ } else { -%>
80
+ expect(UserModel.findAll).toHaveBeenCalled();
81
+ <%_ } -%>
82
+ <%_ } -%>
83
+ });
84
+ });
85
+ });
@@ -7,8 +7,7 @@ export class UserRepository {
7
7
  const newUser = await UserModel.create({ name: user.name, email: user.email });
8
8
  return { id: newUser._id.toString(), name: newUser.name, email: newUser.email };
9
9
  <%_ } else if (database === 'None') { -%>
10
- const newUser = { id: String(UserModel.mockData.length + 1), name: user.name, email: user.email };
11
- UserModel.mockData.push(newUser);
10
+ const newUser = await UserModel.create(user);
12
11
  return newUser;
13
12
  <%_ } else { -%>
14
13
  const newUser = await UserModel.create({ name: user.name, email: user.email });
@@ -25,7 +24,7 @@ export class UserRepository {
25
24
  email: user.email
26
25
  }));
27
26
  <%_ } else if (database === 'None') { -%>
28
- return UserModel.mockData;
27
+ return await UserModel.find();
29
28
  <%_ } else { -%>
30
29
  const users = await UserModel.findAll();
31
30
  return users.map(user => ({
@@ -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
+ });