@tomei/sso 0.33.7 → 0.33.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (182) hide show
  1. package/.commitlintrc.json +22 -22
  2. package/.eslintrc +16 -16
  3. package/.eslintrc.js +35 -35
  4. package/.gitlab-ci.yml +16 -16
  5. package/.husky/commit-msg +15 -15
  6. package/.husky/pre-commit +7 -7
  7. package/.prettierrc +4 -4
  8. package/Jenkinsfile +57 -57
  9. package/README.md +23 -23
  10. package/__tests__/unit/components/group/group.spec.ts +79 -79
  11. package/__tests__/unit/components/group-object-privilege/group-object-privilege.spec.ts +88 -88
  12. package/__tests__/unit/components/group-privilege/group-privilege.spec.ts +68 -68
  13. package/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts +66 -66
  14. package/__tests__/unit/components/group-system-access/group-system-access.spec.ts +83 -83
  15. package/__tests__/unit/components/login-user/l.spec.ts +746 -746
  16. package/__tests__/unit/components/login-user/login.spec.ts +1064 -1064
  17. package/__tests__/unit/components/password-hash/password-hash.service.spec.ts +31 -31
  18. package/__tests__/unit/components/system/system.spec.ts +254 -254
  19. package/__tests__/unit/components/system-privilege/system-privilege.spec.ts +83 -83
  20. package/__tests__/unit/components/user-group/user-group.spec.ts +86 -86
  21. package/__tests__/unit/components/user-object-privilege/user-object-privilege.spec.ts +78 -78
  22. package/__tests__/unit/components/user-privilege/user-privilege.spec.ts +72 -72
  23. package/__tests__/unit/components/user-system-access/user-system-access.spec.ts +89 -89
  24. package/__tests__/unit/redis-client/redis.service.spec.ts +23 -23
  25. package/__tests__/unit/session/session.service.spec.ts +47 -47
  26. package/__tests__/unit/system-privilege/system-privilage.spec.ts +91 -91
  27. package/coverage/clover.xml +1452 -1452
  28. package/coverage/coverage-final.json +47 -47
  29. package/coverage/lcov-report/base.css +224 -224
  30. package/coverage/lcov-report/block-navigation.js +87 -87
  31. package/coverage/lcov-report/components/group/group.repository.ts.html +117 -117
  32. package/coverage/lcov-report/components/group/group.ts.html +327 -327
  33. package/coverage/lcov-report/components/group/index.html +130 -130
  34. package/coverage/lcov-report/components/group-object-privilege/group-object-privilege.repository.ts.html +117 -117
  35. package/coverage/lcov-report/components/group-object-privilege/group-object-privilege.ts.html +321 -321
  36. package/coverage/lcov-report/components/group-object-privilege/index.html +130 -130
  37. package/coverage/lcov-report/components/group-privilege/group-privilege.repository.ts.html +117 -117
  38. package/coverage/lcov-report/components/group-privilege/group-privilege.ts.html +303 -303
  39. package/coverage/lcov-report/components/group-privilege/index.html +130 -130
  40. package/coverage/lcov-report/components/group-reporting-user/group-reporting-user.repository.ts.html +117 -117
  41. package/coverage/lcov-report/components/group-reporting-user/group-reporting-user.ts.html +327 -327
  42. package/coverage/lcov-report/components/group-reporting-user/index.html +130 -130
  43. package/coverage/lcov-report/components/group-system-access/group-system-access.repository.ts.html +117 -117
  44. package/coverage/lcov-report/components/group-system-access/group-system-access.ts.html +309 -309
  45. package/coverage/lcov-report/components/group-system-access/index.html +130 -130
  46. package/coverage/lcov-report/components/login-history/index.html +115 -115
  47. package/coverage/lcov-report/components/login-history/login-history.repository.ts.html +117 -117
  48. package/coverage/lcov-report/components/login-user/index.html +130 -130
  49. package/coverage/lcov-report/components/login-user/login-user.ts.html +5007 -5007
  50. package/coverage/lcov-report/components/login-user/user.repository.ts.html +117 -117
  51. package/coverage/lcov-report/components/password-hash/index.html +115 -115
  52. package/coverage/lcov-report/components/password-hash/password-hash.service.ts.html +126 -126
  53. package/coverage/lcov-report/components/system/index.html +130 -130
  54. package/coverage/lcov-report/components/system/system.repository.ts.html +117 -117
  55. package/coverage/lcov-report/components/system/system.ts.html +909 -909
  56. package/coverage/lcov-report/components/system-privilege/index.html +130 -130
  57. package/coverage/lcov-report/components/system-privilege/system-privilege.repository.ts.html +120 -120
  58. package/coverage/lcov-report/components/system-privilege/system-privilege.ts.html +390 -390
  59. package/coverage/lcov-report/components/user-group/index.html +130 -130
  60. package/coverage/lcov-report/components/user-group/user-group.repository.ts.html +117 -117
  61. package/coverage/lcov-report/components/user-group/user-group.ts.html +354 -354
  62. package/coverage/lcov-report/components/user-object-privilege/index.html +130 -130
  63. package/coverage/lcov-report/components/user-object-privilege/user-object-privilege.repository.ts.html +117 -117
  64. package/coverage/lcov-report/components/user-object-privilege/user-object-privilege.ts.html +312 -312
  65. package/coverage/lcov-report/components/user-privilege/index.html +130 -130
  66. package/coverage/lcov-report/components/user-privilege/user-privilege.repository.ts.html +117 -117
  67. package/coverage/lcov-report/components/user-privilege/user-privilege.ts.html +306 -306
  68. package/coverage/lcov-report/components/user-system-access/index.html +130 -130
  69. package/coverage/lcov-report/components/user-system-access/user-system-access.repository.ts.html +117 -117
  70. package/coverage/lcov-report/components/user-system-access/user-system-access.ts.html +312 -312
  71. package/coverage/lcov-report/enum/group-type.enum.ts.html +108 -108
  72. package/coverage/lcov-report/enum/index.html +160 -160
  73. package/coverage/lcov-report/enum/index.ts.html +93 -93
  74. package/coverage/lcov-report/enum/user-status.enum.ts.html +105 -105
  75. package/coverage/lcov-report/enum/yn.enum.ts.html +96 -96
  76. package/coverage/lcov-report/index.html +370 -370
  77. package/coverage/lcov-report/models/group-object-privilege.entity.ts.html +333 -333
  78. package/coverage/lcov-report/models/group-privilege.entity.ts.html +315 -315
  79. package/coverage/lcov-report/models/group-reporting-user.entity.ts.html +339 -339
  80. package/coverage/lcov-report/models/group-system-access.entity.ts.html +324 -324
  81. package/coverage/lcov-report/models/group.entity.ts.html +435 -435
  82. package/coverage/lcov-report/models/index.html +310 -310
  83. package/coverage/lcov-report/models/login-history.entity.ts.html +252 -252
  84. package/coverage/lcov-report/models/staff.entity.ts.html +411 -411
  85. package/coverage/lcov-report/models/system-privilege.entity.ts.html +354 -354
  86. package/coverage/lcov-report/models/system.entity.ts.html +423 -423
  87. package/coverage/lcov-report/models/user-group.entity.ts.html +354 -354
  88. package/coverage/lcov-report/models/user-object-privilege.entity.ts.html +330 -330
  89. package/coverage/lcov-report/models/user-privilege.entity.ts.html +315 -315
  90. package/coverage/lcov-report/models/user-system-access.entity.ts.html +315 -315
  91. package/coverage/lcov-report/models/user.entity.ts.html +522 -522
  92. package/coverage/lcov-report/prettify.css +1 -1
  93. package/coverage/lcov-report/prettify.js +2 -2
  94. package/coverage/lcov-report/redis-client/index.html +115 -115
  95. package/coverage/lcov-report/redis-client/redis.service.ts.html +240 -240
  96. package/coverage/lcov-report/session/index.html +115 -115
  97. package/coverage/lcov-report/session/session.service.ts.html +246 -246
  98. package/coverage/lcov-report/sorter.js +196 -196
  99. package/coverage/lcov.info +2490 -2490
  100. package/coverage/test-report.xml +128 -128
  101. package/create-sso-user.sql +39 -39
  102. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.d.ts +1 -0
  103. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js +71 -0
  104. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js.map +1 -0
  105. package/dist/__tests__/unit/components/login-user/login-user.spec.d.ts +0 -0
  106. package/dist/__tests__/unit/components/login-user/login-user.spec.js +6 -0
  107. package/dist/__tests__/unit/components/login-user/login-user.spec.js.map +1 -0
  108. package/dist/src/components/group/group.d.ts +27 -0
  109. package/dist/src/components/group/group.js +431 -3
  110. package/dist/src/components/group/group.js.map +1 -1
  111. package/dist/src/components/group-object-privilege/group-object-privilege.d.ts +3 -0
  112. package/dist/src/components/group-object-privilege/group-object-privilege.js +106 -0
  113. package/dist/src/components/group-object-privilege/group-object-privilege.js.map +1 -1
  114. package/dist/src/components/group-object-privilege/group-object-privilege.repository.d.ts +1 -0
  115. package/dist/src/components/group-object-privilege/group-object-privilege.repository.js +22 -0
  116. package/dist/src/components/group-object-privilege/group-object-privilege.repository.js.map +1 -1
  117. package/dist/src/components/group-privilege/group-privilege.d.ts +2 -0
  118. package/dist/src/components/group-privilege/group-privilege.js +10 -0
  119. package/dist/src/components/group-privilege/group-privilege.js.map +1 -1
  120. package/dist/src/components/group-privilege/group-privilege.repository.d.ts +1 -0
  121. package/dist/src/components/group-privilege/group-privilege.repository.js +22 -0
  122. package/dist/src/components/group-privilege/group-privilege.repository.js.map +1 -1
  123. package/dist/src/components/login-user/login-user.d.ts +1 -1
  124. package/dist/src/components/system-privilege/system-privilege.d.ts +7 -0
  125. package/dist/src/components/system-privilege/system-privilege.js +54 -1
  126. package/dist/src/components/system-privilege/system-privilege.js.map +1 -1
  127. package/dist/src/components/user-object-privilege/user-object-privilege.js.map +1 -1
  128. package/dist/src/interfaces/group-object-privilege.interface.d.ts +1 -1
  129. package/dist/src/interfaces/group-privilege.interface.d.ts +1 -1
  130. package/dist/src/interfaces/system-privilege-search.interface.d.ts +5 -0
  131. package/dist/src/interfaces/system-privilege-search.interface.js +3 -0
  132. package/dist/src/interfaces/system-privilege-search.interface.js.map +1 -0
  133. package/dist/src/models/group-object-privilege.entity.js +1 -0
  134. package/dist/src/models/group-object-privilege.entity.js.map +1 -1
  135. package/dist/src/models/group-privilege.entity.js +1 -0
  136. package/dist/src/models/group-privilege.entity.js.map +1 -1
  137. package/dist/src/models/group-reporting-user.entity.js +1 -0
  138. package/dist/src/models/group-reporting-user.entity.js.map +1 -1
  139. package/dist/src/models/user-object-privilege.entity.js +1 -0
  140. package/dist/src/models/user-object-privilege.entity.js.map +1 -1
  141. package/dist/src/models/user-privilege.entity.js +1 -0
  142. package/dist/src/models/user-privilege.entity.js.map +1 -1
  143. package/dist/src/models/user-system-access.entity.js +1 -0
  144. package/dist/src/models/user-system-access.entity.js.map +1 -1
  145. package/dist/tsconfig.tsbuildinfo +1 -1
  146. package/jest.config.js +14 -14
  147. package/migrations/20240314080602-create-user-table.js +108 -108
  148. package/migrations/20240314080603-create-user-group-table.js +85 -85
  149. package/migrations/20240314080604-create-user-user-group-table.js +55 -55
  150. package/migrations/20240314080605-create-login-history-table.js +53 -53
  151. package/migrations/20240527064925-create-system-table.js +78 -78
  152. package/migrations/20240527064926-create-system-privilege-table.js +67 -67
  153. package/migrations/20240527065342-create-group-table.js +89 -89
  154. package/migrations/20240527065633-create-group-reporting-user-table.js +76 -76
  155. package/migrations/20240528011551-create-group-system-access-table.js +72 -72
  156. package/migrations/20240528023018-user-system-access-table.js +75 -75
  157. package/migrations/20240528032229-user-privilege-table.js +75 -75
  158. package/migrations/20240528063003-create-group-privilege-table.js +75 -75
  159. package/migrations/20240528063051-create-group-object-privilege-table.js +84 -84
  160. package/migrations/20240528063107-create-user-object-privilege-table.js +83 -83
  161. package/package.json +89 -89
  162. package/sampledotenv +7 -7
  163. package/sonar-project.properties +22 -22
  164. package/src/components/group/group.ts +1456 -747
  165. package/src/components/group-object-privilege/group-object-privilege.repository.ts +15 -2
  166. package/src/components/group-object-privilege/group-object-privilege.ts +183 -0
  167. package/src/components/group-privilege/group-privilege.repository.ts +12 -2
  168. package/src/components/group-privilege/group-privilege.ts +11 -0
  169. package/src/components/system-privilege/system-privilege.ts +86 -2
  170. package/src/components/user-object-privilege/user-object-privilege.ts +1 -0
  171. package/src/interfaces/group-object-privilege.interface.ts +14 -14
  172. package/src/interfaces/group-privilege.interface.ts +1 -1
  173. package/src/interfaces/system-privilege-search.interface.ts +5 -0
  174. package/src/models/group-object-privilege.entity.ts +1 -0
  175. package/src/models/group-privilege.entity.ts +1 -0
  176. package/src/models/group-reporting-user.entity.ts +1 -0
  177. package/src/models/group-system-access.entity.ts +81 -81
  178. package/src/models/user-object-privilege.entity.ts +1 -0
  179. package/src/models/user-privilege.entity.ts +1 -0
  180. package/src/models/user-system-access.entity.ts +1 -0
  181. package/tsconfig.build.json +5 -5
  182. package/tsconfig.json +22 -22
@@ -1,1065 +1,1065 @@
1
- import { SessionService } from '../../../../src/session/session.service';
2
- import { LoginUser } from '../../../../src/components/login-user/login-user';
3
- import { ApplicationConfig, ComponentConfig } from '@tomei/config';
4
- import { UserRepository } from '../../../../src/components/login-user/user.repository';
5
- import { ClassError } from '@tomei/general';
6
- import { Activity } from '@tomei/activity-history';
7
- import { UserStatus } from '../../../../src/enum/user-status.enum';
8
- import { YN } from '../../../../src/enum/yn.enum';
9
- import SystemPrivilegeModel from '../../../../src/models/system-privilege.entity';
10
- import { GroupSystemAccessRepository } from '../../../../src/components/group-system-access/group-system-access.repository';
11
- import { GroupRepository } from '../../../../src/components/group/group.repository';
12
-
13
- describe('LoginUser', () => {
14
- afterAll(() => {
15
- jest.restoreAllMocks();
16
- });
17
-
18
- describe('init', () => {
19
- let sessionService: any;
20
- let userId: number;
21
- let dbTransaction: any;
22
-
23
- beforeEach(() => {
24
- sessionService = {};
25
- userId = 1;
26
- dbTransaction = null;
27
- });
28
-
29
- it('should initialize LoginUser with valid userId', async () => {
30
- const user = {
31
- UserId: 1,
32
- FullName: 'John Doe',
33
- Email: 'john.doe@example.com',
34
- Password: 'password',
35
- Status: 'active',
36
- DefaultPasswordChangedYN: 'yes',
37
- FirstLoginAt: new Date(),
38
- LastLoginAt: new Date(),
39
- MFAEnabled: 1,
40
- MFAConfig: 'config',
41
- RecoveryEmail: 'john.doe@example.com',
42
- FailedLoginAttemptCount: 0,
43
- LastFailedLoginAt: null,
44
- LastPasswordChangedAt: new Date(),
45
- NeedToChangePasswordYN: 'no',
46
- CreatedById: 1,
47
- CreatedAt: new Date(),
48
- UpdatedById: 1,
49
- UpdatedAt: new Date(),
50
- Staff: {
51
- FullName: 'John Doe',
52
- IdNo: '1234567890',
53
- Mobile: '1234567890',
54
- },
55
- };
56
-
57
- const findOneMock = jest
58
- .spyOn(UserRepository.prototype, 'findOne')
59
- .mockResolvedValueOnce(user as any);
60
-
61
- const result = await LoginUser.init(sessionService, userId, dbTransaction);
62
-
63
- expect(findOneMock).toHaveBeenCalledTimes(1);
64
- expect(findOneMock).toHaveBeenCalledWith({
65
- where: {
66
- UserId: userId,
67
- },
68
- include: [
69
- {
70
- model: expect.anything(),
71
- },
72
- ],
73
- });
74
- expect(result).toBeInstanceOf(LoginUser);
75
- expect(result.UserId).toBe(user.UserId);
76
- expect(result.FullName).toBe(user.FullName);
77
- expect(result.Email).toBe(user.Email);
78
- expect(result.Password).toBe(user.Password);
79
- expect(result.Status).toBe(user.Status);
80
- expect(result.DefaultPasswordChangedYN).toBe(user.DefaultPasswordChangedYN);
81
- expect(result.FirstLoginAt).toBe(user.FirstLoginAt);
82
- expect(result.LastLoginAt).toBe(user.LastLoginAt);
83
- expect(result.MFAEnabled).toBe(user.MFAEnabled);
84
- expect(result.MFAConfig).toBe(user.MFAConfig);
85
- expect(result.RecoveryEmail).toBe(user.RecoveryEmail);
86
- expect(result.FailedLoginAttemptCount).toBe(user.FailedLoginAttemptCount);
87
- expect(result.LastFailedLoginAt).toBe(user.LastFailedLoginAt);
88
- expect(result.LastPasswordChangedAt).toBe(user.LastPasswordChangedAt);
89
- expect(result.NeedToChangePasswordYN).toBe(user.NeedToChangePasswordYN);
90
- expect(result.CreatedById).toBe(user.CreatedById);
91
- expect(result.CreatedAt).toBe(user.CreatedAt);
92
- expect(result.UpdatedById).toBe(user.UpdatedById);
93
- expect(result.UpdatedAt).toBe(user.UpdatedAt);
94
- });
95
-
96
- it('should throw an error when user is not found', async () => {
97
- const findOneMock = jest
98
- .spyOn(UserRepository.prototype, 'findOne')
99
- .mockResolvedValueOnce(null);
100
-
101
- await expect(LoginUser.init(sessionService, userId, dbTransaction)).rejects.toThrow(Error);
102
-
103
- expect(findOneMock).toHaveBeenCalledTimes(1);
104
- expect(findOneMock).toHaveBeenCalledWith({
105
- where: {
106
- UserId: userId,
107
- },
108
- include: [
109
- {
110
- model: expect.anything(),
111
- },
112
- ],
113
- });
114
- });
115
- });
116
-
117
- describe('checkSession', () => {
118
- it('should throw an error if session expired', async () => {
119
- // Arrange
120
- const systemCode = 'EZC';
121
- const sessionId = 'ckymxuh8t000137t011w89zgk';
122
- const userId = '755';
123
- const sessionService = await SessionService.init();
124
- const loginUser = await LoginUser.init(sessionService);
125
-
126
- // Act
127
- const checkSessionPromise = loginUser.checkSession(systemCode, sessionId, userId);
128
-
129
- // Assert
130
- await expect(checkSessionPromise).rejects.toThrow('Session expired.');
131
- });
132
-
133
- it('should refresh the session duration if session is valid', async () => {
134
- // Arrange
135
- const systemCode = 'EZC';
136
- const sessionId = 'ckymxuh8t000137t011w89zgk';
137
- const userId = '755';
138
- const sessionService = await SessionService.init();
139
- const loginUser = await LoginUser.init(sessionService);
140
-
141
- // Mock the _SessionService.retrieveUserSession method
142
- loginUser['_SessionService'].retrieveUserSession = jest.fn().mockResolvedValue({
143
- systemLogins: [
144
- {
145
- code: systemCode,
146
- sessionId: sessionId,
147
- privileges: [],
148
- },
149
- ],
150
- });
151
-
152
- // Mock the _SessionService.refreshDuration method
153
- loginUser['_SessionService'].refreshDuration = jest.fn();
154
-
155
- // Act
156
- const systemLogin = await loginUser.checkSession(systemCode, sessionId, userId);
157
-
158
- // Assert
159
- expect(loginUser['_SessionService'].retrieveUserSession).toHaveBeenCalledWith(userId);
160
- expect(loginUser['_SessionService'].refreshDuration).toHaveBeenCalledWith(userId);
161
- expect(systemLogin).toEqual({
162
- code: systemCode,
163
- sessionId: sessionId,
164
- privileges: [],
165
- });
166
- });
167
- });
168
-
169
- describe('shouldReleaseLock', () => {
170
- const minuteToAutoRelease = 5;
171
- const autoReleaseYN = 'Y';
172
-
173
- beforeEach(() => {
174
- jest
175
- .spyOn(ComponentConfig, 'getComponentConfigValue')
176
- .mockImplementation((componentName: string, configKey: string) => {
177
- if (configKey === 'minuteToAutoRelease') {
178
- return minuteToAutoRelease as any;
179
- }
180
- if (configKey === 'autoReleaseYN') {
181
- return autoReleaseYN as any;
182
- }
183
- });
184
- });
185
-
186
- it('should return true if autoReleaseYN is "Y" and time difference is greater than minuteToAutoRelease', async () => {
187
- // Arrange
188
- const lastFailedLoginAt = new Date();
189
- lastFailedLoginAt.setMinutes(lastFailedLoginAt.getMinutes() - 10); // Set last failed login time to 10 minutes ago
190
- // Act
191
- const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
192
-
193
- // Assert
194
- expect(result).toBe(true);
195
- });
196
-
197
- it('should return false if autoReleaseYN is "Y" and time difference is less than or equal to minuteToAutoRelease', async () => {
198
- // Arrange
199
- const lastFailedLoginAt = new Date();
200
- lastFailedLoginAt.setMinutes(lastFailedLoginAt.getMinutes() - 3); // Set last failed login time to 3 minutes ago
201
-
202
- // Act
203
- const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
204
-
205
- // Assert
206
- expect(result).toBe(false);
207
- });
208
-
209
- it('should return false if autoReleaseYN is "N"', async () => {
210
- // Arrange
211
- const lastFailedLoginAt = new Date();
212
-
213
- // Act
214
- const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
215
-
216
- // Assert
217
- expect(result).toBe(false);
218
- });
219
- });
220
-
221
- describe('releaseLock', () => {
222
- it('should release the lock for a user', async () => {
223
- // Mock the necessary dependencies and setup the test data
224
- const UserId = 1;
225
- const dbTransaction = null;
226
-
227
- const updateMock = jest.spyOn(LoginUser['_Repository'], 'update').mockImplementationOnce(() => Promise.resolve({}) as any);
228
-
229
- // Call the method under test
230
- LoginUser['releaseLock'](UserId, dbTransaction);
231
-
232
- // Verify the expected behavior
233
- expect(updateMock).toHaveBeenCalledTimes(1);
234
- expect(updateMock).toHaveBeenCalledWith(
235
- {
236
- FailedLoginAttemptCount: 0,
237
- Status: 'Active',
238
- },
239
- {
240
- where: {
241
- UserId,
242
- },
243
- transaction: dbTransaction,
244
- }
245
- );
246
- });
247
- });
248
-
249
- describe('checkUserInfoDuplicated', () => {
250
- it('should throw an error if duplicate user info is found', async () => {
251
- // Mock the dependencies and setup the test data
252
- const dbTransaction = null;
253
- const query = {
254
- Email: 'test@example.com',
255
- IdType: 'passport',
256
- IdNo: '123456789',
257
- ContactNo: '1234567890',
258
- };
259
-
260
- // Mock the LoginUser['_Repository'].findAll method to return a user
261
- jest.spyOn(LoginUser['_Repository'], 'findAll').mockResolvedValueOnce([{ id: 1 }] as any);
262
-
263
- // Assert that the method throws an error
264
- await expect(LoginUser['checkUserInfoDuplicated'](dbTransaction, query)).rejects.toThrowError();
265
- });
266
-
267
- it('should not throw an error if duplicate user info is not found', async () => {
268
- // Mock the dependencies and setup the test data
269
- const dbTransaction = null;
270
- const query = {
271
- Email: 'test@example.com',
272
- IdType: 'passport',
273
- IdNo: '123456789',
274
- ContactNo: '1234567890',
275
- };
276
-
277
- // Mock the LoginUser['_Repository'].findAll method to return an empty array
278
- jest.spyOn(LoginUser['_Repository'], 'findAll').mockResolvedValueOnce([]);
279
-
280
- // Assert that the method does not throw an error
281
- await expect(LoginUser['checkUserInfoDuplicated'](dbTransaction, query)).resolves.not.toThrowError();
282
- });
283
- });
284
-
285
- describe('generateDefaultPassword', () => {
286
- const passwordPolicy = {
287
- minLen: 6,
288
- maxLen: 10,
289
- nonAcceptableChar: 'i,l,o',
290
- numOfCapitalLetters: 1,
291
- numOfNumbers: 1,
292
- numOfSpecialChars: 1,
293
- };
294
- beforeEach(() => {
295
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
296
- if (configKey === 'passwordPolicy') {
297
- return passwordPolicy as any;
298
- }
299
- });
300
- });
301
-
302
- it('should generate a default password with the specified length', () => {
303
- const password = LoginUser['generateDefaultPassword']();
304
- expect(password.length).toBeGreaterThanOrEqual(6);
305
- expect(password.length).toBeLessThanOrEqual(10);
306
- });
307
-
308
- it('should generate a default password with at least one capital letter', () => {
309
- const password = LoginUser['generateDefaultPassword']();
310
- expect(/[A-Z]/.test(password)).toBe(true);
311
- });
312
-
313
- it('should generate a default password with at least one number', () => {
314
- const password = LoginUser['generateDefaultPassword']();
315
- expect(/[0-9]/.test(password)).toBe(true);
316
- });
317
-
318
- it('should generate a default password with at least one special character', () => {
319
- const password = LoginUser['generateDefaultPassword']();
320
- expect(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~`]/.test(password)).toBe(true);
321
- });
322
-
323
- it('should generate a default password without any non-acceptable characters', () => {
324
- const password = LoginUser['generateDefaultPassword']();
325
- const nonAcceptableChars = ['i', 'l', 'o'];
326
- expect(nonAcceptableChars.some(char => password.includes(char))).toBe(false);
327
- });
328
- });
329
-
330
- describe('setPassword', () => {
331
- const passwordPolicy = {
332
- minLen: 6,
333
- maxLen: 10,
334
- nonAcceptableChar: 'i,l,o',
335
- numOfCapitalLetters: 1,
336
- numOfNumbers: 1,
337
- numOfSpecialChars: 1,
338
- };
339
- beforeEach(() => {
340
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
341
- if (configKey === 'passwordPolicy') {
342
- return passwordPolicy as any;
343
- }
344
- });
345
- });
346
-
347
- it('should set the password for the user', async () => {
348
- // Arrange
349
- const dbTransaction = null;
350
- const sessionService = await SessionService.init();
351
- const user = await LoginUser.init(sessionService);
352
- const password = 'N3wP@ssw0';
353
-
354
- // Act
355
- const result = await LoginUser['setPassword'](dbTransaction, user, password);
356
-
357
- // Assert
358
- expect(result).toBeInstanceOf(LoginUser);
359
- await expect(LoginUser['setPassword'](dbTransaction, user, password)).resolves.not.toThrowError();
360
- expect(result.Password).toBeDefined();
361
- });
362
-
363
- it('should throw an error if the password does not meet the security requirements', async () => {
364
- // Arrange
365
- const dbTransaction = null;
366
- const sessionService = await SessionService.init();
367
- const user = await LoginUser.init(sessionService);
368
- const password = 'weakpassword';
369
-
370
- // Act & Assert
371
- await expect(LoginUser['setPassword'](dbTransaction, user, password)).rejects.toThrow();
372
- });
373
- });
374
-
375
- describe('create', () => {
376
- let loginUser: LoginUser;
377
- let dbTransaction: any;
378
- let user: LoginUser;
379
- let newUser: any;
380
-
381
- beforeEach(async () => {
382
- const sessionService = await SessionService.init();
383
- loginUser = await LoginUser.init(sessionService);
384
- dbTransaction = null;
385
- newUser = {
386
- UserId: 1,
387
- FullName: 'John Doe',
388
- Email: 'john.doe@example.com',
389
- Password: 'password',
390
- Status: 'Active',
391
- DefaultPasswordChangedYN: 'N',
392
- FirstLoginAt: null,
393
- LastLoginAt: null,
394
- MFAEnabled: null,
395
- MFAConfig: null,
396
- RecoveryEmail: null,
397
- FailedLoginAttemptCount: 0,
398
- LastFailedLoginAt: null,
399
- LastPasswordChangedAt: new Date(),
400
- NeedToChangePasswordYN: 'N',
401
- CreatedById: 1,
402
- CreatedAt: new Date(),
403
- UpdatedById: 1,
404
- UpdatedAt: new Date(),
405
- Staff: {
406
- FullName: 'John Doe',
407
- IdNo: '1234567890',
408
- Mobile: '1234567890',
409
- },
410
- };
411
- user = new (LoginUser as any)(sessionService, null, newUser);
412
- });
413
-
414
- afterEach(() => {
415
- jest.clearAllMocks();
416
- });
417
-
418
- it('should create a new user record', async () => {
419
- const checkPrivilegesMock = jest
420
- .spyOn(loginUser, 'checkPrivileges')
421
- .mockResolvedValueOnce(true);
422
-
423
- const checkUserInfoDuplicatedMock = jest
424
- .spyOn((LoginUser as any), 'checkUserInfoDuplicated')
425
- .mockResolvedValueOnce(undefined);
426
-
427
- const generateDefaultPasswordMock = jest
428
- .spyOn((LoginUser as any), 'generateDefaultPassword')
429
- .mockReturnValueOnce('defaultPassword');
430
-
431
- const setPasswordMock = jest
432
- .spyOn((LoginUser as any), 'setPassword')
433
- .mockResolvedValueOnce(user);
434
-
435
- const createMock = jest
436
- .spyOn((LoginUser as any)['_Repository'], 'create')
437
- .mockResolvedValueOnce({
438
- ...newUser,
439
- get: () => newUser,
440
- });
441
-
442
- const activityCreateMock = jest
443
- .spyOn((Activity as any).prototype, 'create')
444
- .mockResolvedValueOnce(undefined);
445
-
446
-
447
- jest.spyOn(ApplicationConfig, 'getComponentConfigValue').mockImplementation((configKey: string) => {
448
- if (configKey === 'system-code') {
449
- return 'SC';
450
- }
451
- });
452
-
453
-
454
- const result = await LoginUser.create(loginUser, dbTransaction, user);
455
-
456
- expect(checkPrivilegesMock).toHaveBeenCalledTimes(1);
457
- expect(checkPrivilegesMock).toHaveBeenCalledWith(
458
- ApplicationConfig.getComponentConfigValue('system-code'),
459
- 'User - Create'
460
- );
461
-
462
- expect(checkUserInfoDuplicatedMock).toHaveBeenCalledTimes(1);
463
- expect(checkUserInfoDuplicatedMock).toHaveBeenCalledWith(
464
- dbTransaction,
465
- {
466
- Email: user.Email,
467
- IdType: user.IDType,
468
- IdNo: user.IDNo,
469
- ContactNo: user.ContactNo,
470
- }
471
- );
472
-
473
- expect(generateDefaultPasswordMock).toHaveBeenCalledTimes(1);
474
-
475
- expect(setPasswordMock).toHaveBeenCalledTimes(1);
476
- expect(setPasswordMock).toHaveBeenCalledWith(
477
- dbTransaction,
478
- user,
479
- 'defaultPassword'
480
- );
481
-
482
- expect(createMock).toHaveBeenCalledTimes(1);
483
-
484
- const userInfo = {
485
- FullName: user.FullName,
486
- IDNo: user.IDNo,
487
- Email: user.Email,
488
- ContactNo: user.ContactNo,
489
- Password: user.Password,
490
- Status: UserStatus.ACTIVE,
491
- FirstLoginAt: null,
492
- LastLoginAt: null,
493
- MFAEnabled: null,
494
- MFAConfig: null,
495
- RecoveryEmail: null,
496
- FailedLoginAttemptCount: 0,
497
- LastFailedLoginAt: null,
498
- LastPasswordChangedAt: null,
499
- DefaultPasswordChangedYN: YN.No,
500
- NeedToChangePasswordYN: YN.Yes,
501
- CreatedById: loginUser.UserId,
502
- CreatedAt: new Date(),
503
- UpdatedById: loginUser.UserId,
504
- UpdatedAt: new Date(),
505
- UserId: null,
506
- };
507
-
508
- expect(createMock).toHaveBeenCalledWith(
509
- {
510
- Email: userInfo.Email,
511
- Password: userInfo.Password,
512
- Status: userInfo.Status,
513
- DefaultPasswordChangedYN: userInfo.DefaultPasswordChangedYN,
514
- FirstLoginAt: userInfo.FirstLoginAt,
515
- LastLoginAt: userInfo.LastLoginAt,
516
- MFAEnabled: userInfo.MFAEnabled,
517
- MFAConfig: userInfo.MFAConfig,
518
- RecoveryEmail: userInfo.RecoveryEmail,
519
- FailedLoginAttemptCount: userInfo.FailedLoginAttemptCount,
520
- LastFailedLoginAt: userInfo.LastFailedLoginAt,
521
- LastPasswordChangedAt: userInfo.LastPasswordChangedAt,
522
- NeedToChangePasswordYN: userInfo.NeedToChangePasswordYN,
523
- CreatedById: userInfo.CreatedById,
524
- CreatedAt: expect.any(Date),
525
- UpdatedById: userInfo.UpdatedById,
526
- UpdatedAt: expect.any(Date),
527
- },
528
- {
529
- transaction: dbTransaction,
530
- }
531
- );
532
-
533
- expect(activityCreateMock).toHaveBeenCalledTimes(1);
534
-
535
- expect(result).toBeInstanceOf(LoginUser);
536
- expect(result.Email).toBe(userInfo.Email);
537
- expect(result.Password).toBe(userInfo.Password);
538
- expect(result.Status).toBe(userInfo.Status);
539
- expect(result.DefaultPasswordChangedYN).toBe(
540
- userInfo.DefaultPasswordChangedYN
541
- );
542
- expect(result.FirstLoginAt).toBe(null);
543
- expect(result.LastLoginAt).toBe(null);
544
- expect(result.MFAEnabled).toBe(userInfo.MFAEnabled);
545
- expect(result.MFAConfig).toBe(userInfo.MFAConfig);
546
- expect(result.RecoveryEmail).toBe(userInfo.RecoveryEmail);
547
- expect(result.FailedLoginAttemptCount).toBe(
548
- userInfo.FailedLoginAttemptCount
549
- );
550
- expect(result.LastFailedLoginAt).toBe(userInfo.LastFailedLoginAt);
551
- expect(result.LastPasswordChangedAt).toBe(userInfo.LastPasswordChangedAt);
552
- expect(result.NeedToChangePasswordYN).toBe(
553
- userInfo.NeedToChangePasswordYN
554
- );
555
- expect(result.CreatedById).toBe(userInfo.CreatedById);
556
- expect(result.UpdatedById).toBe(userInfo.UpdatedById);
557
- });
558
-
559
- it('should throw an error if user dont have the privilege to create new user', async () => {
560
- jest
561
- .spyOn(loginUser, 'checkPrivileges')
562
- .mockResolvedValueOnce(false);
563
-
564
- await expect(
565
- LoginUser.create(loginUser, dbTransaction, user)
566
- ).rejects.toThrow(ClassError);
567
- });
568
-
569
- it('should throw an error if user email is missing', async () => {
570
- user.Email = undefined;
571
-
572
- jest
573
- .spyOn(loginUser, 'checkPrivileges')
574
- .mockResolvedValueOnce(true);
575
-
576
- await expect(
577
- LoginUser.create(loginUser, dbTransaction, user)
578
- ).rejects.toThrow(ClassError);
579
- });
580
- });
581
-
582
- describe('incrementFailedLoginAttemptCount', () => {
583
- afterAll(() => {
584
- jest.restoreAllMocks();
585
- });
586
-
587
- it('should increment FailedLoginAttemptCount and update user status', async () => {
588
- // Arrange
589
- const sessionService = await SessionService.init();
590
- const loginUser = await LoginUser.init(sessionService);
591
- loginUser['FailedLoginAttemptCount'] = 2;
592
- loginUser['LastFailedLoginAt'] = new Date();
593
- loginUser['Status'] = UserStatus.ACTIVE;
594
-
595
- const dbTransaction = null;
596
-
597
- // Mock the static methods and properties
598
- jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
599
-
600
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
601
- if (configKey === 'maxFailedLoginAttempts') {
602
- return 3;
603
- }
604
- if (configKey === 'autoReleaseYN') {
605
- return 'Y';
606
- }
607
- });
608
-
609
-
610
- // Act
611
- await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
612
-
613
- // Assert
614
- expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
615
- {
616
- FailedLoginAttemptCount: 3,
617
- LastFailedLoginAt: expect.any(Date),
618
- Status: UserStatus.ACTIVE,
619
- },
620
- {
621
- where: {
622
- UserId: loginUser.UserId,
623
- },
624
- transaction: dbTransaction,
625
- }
626
- );
627
- });
628
-
629
- it('should throw an error if maxFailedLoginAttempts or autoReleaseYN is missing', async () => {
630
- // Arrange
631
- const sessionService = await SessionService.init();
632
- const loginUser = await LoginUser.init(sessionService);
633
- loginUser['FailedLoginAttemptCount'] = 2;
634
- loginUser['LastFailedLoginAt'] = new Date();
635
- loginUser['Status'] = UserStatus.ACTIVE;
636
-
637
- const dbTransaction = null;
638
-
639
- // Mock the static methods and properties
640
- jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
641
-
642
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementationOnce((componentName: string, configKey: string) => {
643
- if (configKey === 'maxFailedLoginAttempts') {
644
- return undefined;
645
- }
646
- if (configKey === 'autoReleaseYN') {
647
- return undefined;
648
- }
649
- });
650
-
651
- // Act and Assert
652
- await expect(
653
- loginUser['incrementFailedLoginAttemptCount'](dbTransaction)
654
- ).rejects.toThrow(
655
- new ClassError(
656
- 'LoginUser',
657
- 'LoginUserErrMsg0X',
658
- 'Missing maxFailedLoginAttempts and or autoReleaseYN. Please set in config file.',
659
- ));
660
- });
661
-
662
- it('should lock the user account if the failed login attempts exceed the maximum allowed', async () => {
663
- // Arrange
664
- const sessionService = await SessionService.init();
665
- const loginUser = await LoginUser.init(sessionService);
666
- loginUser['FailedLoginAttemptCount'] = 3;
667
- loginUser['LastFailedLoginAt'] = new Date();
668
- loginUser['Status'] = UserStatus.ACTIVE;
669
-
670
- const dbTransaction = null;
671
-
672
- // Mock the static methods and properties
673
- jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
674
-
675
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
676
- if (configKey === 'maxFailedLoginAttempts') {
677
- return 3;
678
- }
679
- if (configKey === 'autoReleaseYN') {
680
- return 'Y';
681
- }
682
- });
683
-
684
- // Act
685
- try {
686
- await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
687
- expect(false).toBe(true);
688
- } catch (error) {
689
- // Assert
690
- expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
691
- {
692
- FailedLoginAttemptCount: 4,
693
- LastFailedLoginAt: expect.any(Date),
694
- Status: UserStatus.LOCKED,
695
- },
696
- {
697
- where: {
698
- UserId: loginUser.UserId,
699
- },
700
- transaction: dbTransaction,
701
- }
702
- );
703
- expect(error).toBeInstanceOf(ClassError);
704
- expect(error.message).toBe('Your account has been temporarily locked due to too many failed login attempts, please try again later.');
705
- }
706
- });
707
-
708
- it('should permanently lock the user account if the failed login attempts exceed the maximum allowed and autoReleaseYN is N', async () => {
709
- // Arrange
710
- const sessionService = await SessionService.init();
711
- const loginUser = await LoginUser.init(sessionService);
712
- loginUser['FailedLoginAttemptCount'] = 3;
713
- loginUser['LastFailedLoginAt'] = new Date();
714
- loginUser['Status'] = UserStatus.ACTIVE;
715
-
716
- const dbTransaction = null;
717
-
718
- // Mock the static methods and properties
719
- jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
720
-
721
- jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
722
- if (configKey === 'maxFailedLoginAttempts') {
723
- return 3;
724
- }
725
- if (configKey === 'autoReleaseYN') {
726
- return 'N';
727
- }
728
- });
729
-
730
- // Act
731
- try {
732
- await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
733
- expect(false).toBe(true);
734
- } catch (error) {
735
- // Assert
736
- expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
737
- {
738
- FailedLoginAttemptCount: 4,
739
- LastFailedLoginAt: expect.any(Date),
740
- Status: UserStatus.LOCKED,
741
- },
742
- {
743
- where: {
744
- UserId: loginUser.UserId,
745
- },
746
- transaction: dbTransaction,
747
- }
748
- );
749
- expect(error).toBeInstanceOf(ClassError);
750
- expect(error.message).toBe('Your account has been locked due to too many failed login attempts, please contact IT Support for instructions on how to unlock your account');
751
- }
752
- });
753
- });
754
-
755
- describe('combineSystemAccess', () => {
756
- it('should combine user and group system access and remove duplicates', async () => {
757
- // Mock the necessary dependencies
758
- const sessionService = await SessionService.init();
759
- const loginUser = await LoginUser.init(sessionService);
760
- const dbTransaction = null;
761
- const groups = [
762
- { InheritParentSystemAccessYN: true },
763
- { InheritParentSystemAccessYN: false },
764
- ];
765
-
766
- // Mock the necessary repository methods
767
- jest.spyOn((LoginUser as any), 'getInheritedSystemAccess').mockResolvedValueOnce([
768
- { SystemCode: 'system1' },
769
- { SystemCode: 'system2' },
770
- ]);
771
- jest.spyOn((LoginUser as any)['_UserSystemAccessRepo'], 'findAll').mockResolvedValueOnce([
772
- { SystemCode: 'system3' },
773
- ]);
774
-
775
- // Call the method
776
- const result = await LoginUser['combineSystemAccess'](
777
- loginUser,
778
- dbTransaction,
779
- groups
780
- );
781
-
782
- // Assert the result
783
- expect(result).toEqual([
784
- { SystemCode: 'system3' },
785
- { SystemCode: 'system1' },
786
- { SystemCode: 'system2' },
787
- ]);
788
- });
789
- });
790
-
791
- describe('checkPrivileges', () => {
792
- it('should return true if user has the specified privilege', async () => {
793
- // Arrange
794
- const systemCode = 'SS';
795
- const privilegeName = 'Privilege 1';
796
- const sessionService = await SessionService.init();
797
- const loginUser = await LoginUser.init(sessionService);
798
- loginUser.ObjectId = '1';
799
-
800
- // Mock the necessary methods
801
- const rus = jest.spyOn(SessionService.prototype, 'retrieveUserSession').mockResolvedValueOnce({
802
- systemLogins: [
803
- {
804
- id: '1',
805
- sessionId: 'sessionId',
806
- code: systemCode,
807
- privileges: [privilegeName],
808
- },
809
- ],
810
- });
811
- // Act
812
- const hasPrivilege = await loginUser.checkPrivileges(systemCode, privilegeName);
813
-
814
- // Assert
815
- expect(hasPrivilege).toBe(true);
816
- expect(rus).toHaveBeenCalledWith(loginUser.ObjectId);
817
- });
818
-
819
- it('should return false if user does not have the specified privilege', async () => {
820
- // Arrange
821
- const systemCode = 'SS';
822
- const privilegeName = 'Privilege 1';
823
- const sessionService = await SessionService.init();
824
- const loginUser = await LoginUser.init(sessionService);
825
- loginUser.ObjectId = '1';
826
-
827
- // Mock the necessary methods
828
- const rus = jest.spyOn(SessionService.prototype, 'retrieveUserSession').mockResolvedValueOnce({
829
- systemLogins: [
830
- {
831
- id: '1',
832
- sessionId: 'sessionId',
833
- code: systemCode,
834
- privileges: [],
835
- },
836
- ],
837
- });
838
- // Act
839
- const hasPrivilege = await loginUser.checkPrivileges(systemCode, privilegeName);
840
-
841
- // Assert
842
- expect(hasPrivilege).toBe(false);
843
- expect(rus).toHaveBeenCalledWith(loginUser.ObjectId);
844
- });
845
-
846
- it('should throw an error if ObjectId is not set', async () => {
847
- // Arrange
848
- const systemCode = 'SS';
849
- const privilegeName = 'Privilege 1';
850
- const sessionService = await SessionService.init();
851
- const loginUser = await LoginUser.init(sessionService);
852
- loginUser.ObjectId = null;
853
-
854
- // Act & Assert
855
- await expect(loginUser.checkPrivileges(systemCode, privilegeName)).rejects.toThrowError();
856
- });
857
- });
858
-
859
- describe('getObjectPrivileges', () => {
860
- it('should return an array of privileges', async () => {
861
- // Mock the dependencies and setup the test data
862
- const systemCode = 'system1';
863
- const dbTransaction = null;
864
- const sessionService = await SessionService.init();
865
- const loginUser = await LoginUser.init(sessionService);
866
-
867
- // Mock the UserObjectPrivilegeRepo findAll method
868
- const findAllMock = jest
869
- .spyOn(LoginUser['_UserObjectPrivilegeRepo'], 'findAll')
870
- .mockResolvedValue([
871
- {
872
- PrivilegeCode: 'privilege1',
873
- Privilege: {
874
- PrivilegeCode: 'privilege1',
875
- SystemCode: systemCode,
876
- Name: 'privilege1',
877
- },
878
- },
879
- {
880
- PrivilegeCode: 'privilege2',
881
- Privilege: {
882
- PrivilegeCode: 'privilege2',
883
- Name: 'privilege2',
884
- },
885
- },
886
- ] as any);
887
-
888
- // Call the getObjectPrivileges method
889
- const result = await loginUser['getObjectPrivileges'](systemCode, dbTransaction);
890
-
891
- // Assertions
892
- expect(findAllMock).toHaveBeenCalledTimes(1);
893
- expect(findAllMock).toHaveBeenCalledWith({
894
- where: {
895
- UserId: loginUser.UserId,
896
- },
897
- include: {
898
- model: SystemPrivilegeModel,
899
- where: {
900
- SystemCode: systemCode,
901
- Status: 'Active',
902
- },
903
- },
904
- transaction: dbTransaction,
905
- });
906
- expect(result).toEqual(['privilege1', 'privilege2']);
907
- });
908
-
909
- it('should throw an error if an exception occurs', async () => {
910
- // Mock the dependencies and setup the test data
911
- const systemCode = 'system1';
912
- const dbTransaction = null;
913
- const sessionService = await SessionService.init();
914
- const loginUser = await LoginUser.init(sessionService);
915
-
916
- // Mock the UserObjectPrivilegeRepo findAll method to throw an error
917
- jest.spyOn(LoginUser['_UserObjectPrivilegeRepo'], 'findAll').mockRejectedValue(new Error('Database error'));
918
-
919
- // Call the getObjectPrivileges method and expect it to throw an error
920
- await expect(loginUser['getObjectPrivileges'](systemCode, dbTransaction)).rejects.toThrow(Error);
921
- });
922
- });
923
-
924
- describe('getUserPersonalPrivileges', () => {
925
- it('should return an array of privileges', async () => {
926
- // Arrange
927
- const sessionService = await SessionService.init();
928
- const loginUser = await LoginUser.init(sessionService);
929
- const systemCode = 'system1';
930
- const dbTransaction = null;
931
-
932
- // Mock the findAll method of UserPrivilegeRepo
933
- const findAllMock = jest.spyOn(LoginUser['_UserPrivilegeRepo'], 'findAll');
934
- findAllMock.mockResolvedValue([
935
- { Privilege: { Name: 'privilege1' } },
936
- { Privilege: { Name: 'privilege2' } },
937
- ] as any);
938
-
939
- // Act
940
- const privileges = await loginUser['getUserPersonalPrivileges'](
941
- systemCode,
942
- dbTransaction
943
- );
944
-
945
- // Assert
946
- expect(privileges).toEqual(['privilege1', 'privilege2']);
947
- expect(findAllMock).toHaveBeenCalledTimes(1);
948
- expect(findAllMock).toHaveBeenCalledWith({
949
- where: {
950
- UserId: loginUser.UserId,
951
- Status: 'Active',
952
- },
953
- include: {
954
- model: SystemPrivilegeModel,
955
- where: {
956
- SystemCode: systemCode,
957
- Status: 'Active',
958
- },
959
- },
960
- transaction: dbTransaction,
961
- });
962
- });
963
-
964
- it('should throw an error if an error occurs', async () => {
965
- // Arrange
966
- const sessionService = await SessionService.init();
967
- const loginUser = await LoginUser.init(sessionService);
968
- const systemCode = 'system1';
969
- const dbTransaction = null;
970
-
971
- // Mock the findAll method of UserPrivilegeRepo to throw an error
972
- const findAllMock = jest.spyOn(LoginUser['_UserPrivilegeRepo'], 'findAll');
973
- findAllMock.mockRejectedValue(new Error('Database error'));
974
-
975
- // Act and Assert
976
- await expect(
977
- loginUser['getUserPersonalPrivileges'](systemCode, dbTransaction)
978
- ).rejects.toThrow(Error);
979
-
980
- expect(findAllMock).toHaveBeenCalledTimes(1);
981
- expect(findAllMock).toHaveBeenCalledWith({
982
- where: {
983
- UserId: loginUser.UserId,
984
- Status: 'Active',
985
- },
986
- include: {
987
- model: SystemPrivilegeModel,
988
- where: {
989
- SystemCode: systemCode,
990
- Status: 'Active',
991
- },
992
- },
993
- transaction: dbTransaction,
994
- });
995
- });
996
- });
997
-
998
- describe('getInheritedSystemAccess', () => {
999
- it('should return group system access with its parent group system access if applicable', async () => {
1000
- // Mock the necessary dependencies
1001
- const dbTransaction = null;
1002
- const group = {
1003
- GroupCode: 'group1',
1004
- InheritParentPrivilegeYN: 'Y',
1005
- ParentGroupCode: 'parentGroup',
1006
- } as any;
1007
- const parentGroup = {
1008
- GroupCode: 'parentGroup',
1009
- InheritParentPrivilegeYN: 'N',
1010
- ParentGroupCode: null,
1011
- } as any;
1012
-
1013
- const systemAccess = [
1014
- { SystemCode: 'system1', GroupCode: 'group1', System: { SystemCode: 'system1' } },
1015
- { SystemCode: 'system2', GroupCode: 'group1', System: { SystemCode: 'system1' } },
1016
- ] as any;
1017
-
1018
- const parentSystemAccess = [
1019
- { SystemCode: 'system3', GroupCode: 'parentGroup', System: { SystemCode: 'system3' } },
1020
- ] as any;
1021
-
1022
- // Mock the necessary repository methods
1023
- const groupFindByPkMock = jest
1024
- .spyOn(GroupRepository.prototype, 'findByPk')
1025
- .mockResolvedValueOnce(parentGroup as any);
1026
- const systemAccessFindAllMock = jest
1027
- .spyOn(GroupSystemAccessRepository.prototype, 'findAll')
1028
- .mockImplementation((options: any) => {
1029
- if (options.where.GroupCode === group.GroupCode) {
1030
- return Promise.resolve(systemAccess);
1031
- } else if (options.where.GroupCode === parentGroup.GroupCode) {
1032
- return Promise.resolve(parentSystemAccess);
1033
- }
1034
- });
1035
-
1036
- // Call the method
1037
- const result = await LoginUser['getInheritedSystemAccess'](
1038
- dbTransaction,
1039
- group
1040
- );
1041
-
1042
- console.log(result);
1043
- // Assert the result
1044
- expect(result).toEqual([
1045
- {
1046
- SystemCode: 'system1',
1047
- GroupCode: 'group1',
1048
- System: { SystemCode: 'system1' }
1049
- },
1050
- {
1051
- SystemCode: 'system2',
1052
- GroupCode: 'group1',
1053
- System: { SystemCode: 'system1' }
1054
- },
1055
- {
1056
- SystemCode: 'system3',
1057
- GroupCode: 'parentGroup',
1058
- System: { SystemCode: 'system3' }
1059
- }
1060
- ]);
1061
- expect(groupFindByPkMock).toHaveBeenCalledWith(group.ParentGroupCode, dbTransaction);
1062
- expect(systemAccessFindAllMock).toHaveBeenCalledTimes(2);
1063
- })
1064
- });
1
+ import { SessionService } from '../../../../src/session/session.service';
2
+ import { LoginUser } from '../../../../src/components/login-user/login-user';
3
+ import { ApplicationConfig, ComponentConfig } from '@tomei/config';
4
+ import { UserRepository } from '../../../../src/components/login-user/user.repository';
5
+ import { ClassError } from '@tomei/general';
6
+ import { Activity } from '@tomei/activity-history';
7
+ import { UserStatus } from '../../../../src/enum/user-status.enum';
8
+ import { YN } from '../../../../src/enum/yn.enum';
9
+ import SystemPrivilegeModel from '../../../../src/models/system-privilege.entity';
10
+ import { GroupSystemAccessRepository } from '../../../../src/components/group-system-access/group-system-access.repository';
11
+ import { GroupRepository } from '../../../../src/components/group/group.repository';
12
+
13
+ describe('LoginUser', () => {
14
+ afterAll(() => {
15
+ jest.restoreAllMocks();
16
+ });
17
+
18
+ describe('init', () => {
19
+ let sessionService: any;
20
+ let userId: number;
21
+ let dbTransaction: any;
22
+
23
+ beforeEach(() => {
24
+ sessionService = {};
25
+ userId = 1;
26
+ dbTransaction = null;
27
+ });
28
+
29
+ it('should initialize LoginUser with valid userId', async () => {
30
+ const user = {
31
+ UserId: 1,
32
+ FullName: 'John Doe',
33
+ Email: 'john.doe@example.com',
34
+ Password: 'password',
35
+ Status: 'active',
36
+ DefaultPasswordChangedYN: 'yes',
37
+ FirstLoginAt: new Date(),
38
+ LastLoginAt: new Date(),
39
+ MFAEnabled: 1,
40
+ MFAConfig: 'config',
41
+ RecoveryEmail: 'john.doe@example.com',
42
+ FailedLoginAttemptCount: 0,
43
+ LastFailedLoginAt: null,
44
+ LastPasswordChangedAt: new Date(),
45
+ NeedToChangePasswordYN: 'no',
46
+ CreatedById: 1,
47
+ CreatedAt: new Date(),
48
+ UpdatedById: 1,
49
+ UpdatedAt: new Date(),
50
+ Staff: {
51
+ FullName: 'John Doe',
52
+ IdNo: '1234567890',
53
+ Mobile: '1234567890',
54
+ },
55
+ };
56
+
57
+ const findOneMock = jest
58
+ .spyOn(UserRepository.prototype, 'findOne')
59
+ .mockResolvedValueOnce(user as any);
60
+
61
+ const result = await LoginUser.init(sessionService, userId, dbTransaction);
62
+
63
+ expect(findOneMock).toHaveBeenCalledTimes(1);
64
+ expect(findOneMock).toHaveBeenCalledWith({
65
+ where: {
66
+ UserId: userId,
67
+ },
68
+ include: [
69
+ {
70
+ model: expect.anything(),
71
+ },
72
+ ],
73
+ });
74
+ expect(result).toBeInstanceOf(LoginUser);
75
+ expect(result.UserId).toBe(user.UserId);
76
+ expect(result.FullName).toBe(user.FullName);
77
+ expect(result.Email).toBe(user.Email);
78
+ expect(result.Password).toBe(user.Password);
79
+ expect(result.Status).toBe(user.Status);
80
+ expect(result.DefaultPasswordChangedYN).toBe(user.DefaultPasswordChangedYN);
81
+ expect(result.FirstLoginAt).toBe(user.FirstLoginAt);
82
+ expect(result.LastLoginAt).toBe(user.LastLoginAt);
83
+ expect(result.MFAEnabled).toBe(user.MFAEnabled);
84
+ expect(result.MFAConfig).toBe(user.MFAConfig);
85
+ expect(result.RecoveryEmail).toBe(user.RecoveryEmail);
86
+ expect(result.FailedLoginAttemptCount).toBe(user.FailedLoginAttemptCount);
87
+ expect(result.LastFailedLoginAt).toBe(user.LastFailedLoginAt);
88
+ expect(result.LastPasswordChangedAt).toBe(user.LastPasswordChangedAt);
89
+ expect(result.NeedToChangePasswordYN).toBe(user.NeedToChangePasswordYN);
90
+ expect(result.CreatedById).toBe(user.CreatedById);
91
+ expect(result.CreatedAt).toBe(user.CreatedAt);
92
+ expect(result.UpdatedById).toBe(user.UpdatedById);
93
+ expect(result.UpdatedAt).toBe(user.UpdatedAt);
94
+ });
95
+
96
+ it('should throw an error when user is not found', async () => {
97
+ const findOneMock = jest
98
+ .spyOn(UserRepository.prototype, 'findOne')
99
+ .mockResolvedValueOnce(null);
100
+
101
+ await expect(LoginUser.init(sessionService, userId, dbTransaction)).rejects.toThrow(Error);
102
+
103
+ expect(findOneMock).toHaveBeenCalledTimes(1);
104
+ expect(findOneMock).toHaveBeenCalledWith({
105
+ where: {
106
+ UserId: userId,
107
+ },
108
+ include: [
109
+ {
110
+ model: expect.anything(),
111
+ },
112
+ ],
113
+ });
114
+ });
115
+ });
116
+
117
+ describe('checkSession', () => {
118
+ it('should throw an error if session expired', async () => {
119
+ // Arrange
120
+ const systemCode = 'EZC';
121
+ const sessionId = 'ckymxuh8t000137t011w89zgk';
122
+ const userId = '755';
123
+ const sessionService = await SessionService.init();
124
+ const loginUser = await LoginUser.init(sessionService);
125
+
126
+ // Act
127
+ const checkSessionPromise = loginUser.checkSession(systemCode, sessionId, userId);
128
+
129
+ // Assert
130
+ await expect(checkSessionPromise).rejects.toThrow('Session expired.');
131
+ });
132
+
133
+ it('should refresh the session duration if session is valid', async () => {
134
+ // Arrange
135
+ const systemCode = 'EZC';
136
+ const sessionId = 'ckymxuh8t000137t011w89zgk';
137
+ const userId = '755';
138
+ const sessionService = await SessionService.init();
139
+ const loginUser = await LoginUser.init(sessionService);
140
+
141
+ // Mock the _SessionService.retrieveUserSession method
142
+ loginUser['_SessionService'].retrieveUserSession = jest.fn().mockResolvedValue({
143
+ systemLogins: [
144
+ {
145
+ code: systemCode,
146
+ sessionId: sessionId,
147
+ privileges: [],
148
+ },
149
+ ],
150
+ });
151
+
152
+ // Mock the _SessionService.refreshDuration method
153
+ loginUser['_SessionService'].refreshDuration = jest.fn();
154
+
155
+ // Act
156
+ const systemLogin = await loginUser.checkSession(systemCode, sessionId, userId);
157
+
158
+ // Assert
159
+ expect(loginUser['_SessionService'].retrieveUserSession).toHaveBeenCalledWith(userId);
160
+ expect(loginUser['_SessionService'].refreshDuration).toHaveBeenCalledWith(userId);
161
+ expect(systemLogin).toEqual({
162
+ code: systemCode,
163
+ sessionId: sessionId,
164
+ privileges: [],
165
+ });
166
+ });
167
+ });
168
+
169
+ describe('shouldReleaseLock', () => {
170
+ const minuteToAutoRelease = 5;
171
+ const autoReleaseYN = 'Y';
172
+
173
+ beforeEach(() => {
174
+ jest
175
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
176
+ .mockImplementation((componentName: string, configKey: string) => {
177
+ if (configKey === 'minuteToAutoRelease') {
178
+ return minuteToAutoRelease as any;
179
+ }
180
+ if (configKey === 'autoReleaseYN') {
181
+ return autoReleaseYN as any;
182
+ }
183
+ });
184
+ });
185
+
186
+ it('should return true if autoReleaseYN is "Y" and time difference is greater than minuteToAutoRelease', async () => {
187
+ // Arrange
188
+ const lastFailedLoginAt = new Date();
189
+ lastFailedLoginAt.setMinutes(lastFailedLoginAt.getMinutes() - 10); // Set last failed login time to 10 minutes ago
190
+ // Act
191
+ const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
192
+
193
+ // Assert
194
+ expect(result).toBe(true);
195
+ });
196
+
197
+ it('should return false if autoReleaseYN is "Y" and time difference is less than or equal to minuteToAutoRelease', async () => {
198
+ // Arrange
199
+ const lastFailedLoginAt = new Date();
200
+ lastFailedLoginAt.setMinutes(lastFailedLoginAt.getMinutes() - 3); // Set last failed login time to 3 minutes ago
201
+
202
+ // Act
203
+ const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
204
+
205
+ // Assert
206
+ expect(result).toBe(false);
207
+ });
208
+
209
+ it('should return false if autoReleaseYN is "N"', async () => {
210
+ // Arrange
211
+ const lastFailedLoginAt = new Date();
212
+
213
+ // Act
214
+ const result = LoginUser.shouldReleaseLock(lastFailedLoginAt);
215
+
216
+ // Assert
217
+ expect(result).toBe(false);
218
+ });
219
+ });
220
+
221
+ describe('releaseLock', () => {
222
+ it('should release the lock for a user', async () => {
223
+ // Mock the necessary dependencies and setup the test data
224
+ const UserId = 1;
225
+ const dbTransaction = null;
226
+
227
+ const updateMock = jest.spyOn(LoginUser['_Repository'], 'update').mockImplementationOnce(() => Promise.resolve({}) as any);
228
+
229
+ // Call the method under test
230
+ LoginUser['releaseLock'](UserId, dbTransaction);
231
+
232
+ // Verify the expected behavior
233
+ expect(updateMock).toHaveBeenCalledTimes(1);
234
+ expect(updateMock).toHaveBeenCalledWith(
235
+ {
236
+ FailedLoginAttemptCount: 0,
237
+ Status: 'Active',
238
+ },
239
+ {
240
+ where: {
241
+ UserId,
242
+ },
243
+ transaction: dbTransaction,
244
+ }
245
+ );
246
+ });
247
+ });
248
+
249
+ describe('checkUserInfoDuplicated', () => {
250
+ it('should throw an error if duplicate user info is found', async () => {
251
+ // Mock the dependencies and setup the test data
252
+ const dbTransaction = null;
253
+ const query = {
254
+ Email: 'test@example.com',
255
+ IdType: 'passport',
256
+ IdNo: '123456789',
257
+ ContactNo: '1234567890',
258
+ };
259
+
260
+ // Mock the LoginUser['_Repository'].findAll method to return a user
261
+ jest.spyOn(LoginUser['_Repository'], 'findAll').mockResolvedValueOnce([{ id: 1 }] as any);
262
+
263
+ // Assert that the method throws an error
264
+ await expect(LoginUser['checkUserInfoDuplicated'](dbTransaction, query)).rejects.toThrowError();
265
+ });
266
+
267
+ it('should not throw an error if duplicate user info is not found', async () => {
268
+ // Mock the dependencies and setup the test data
269
+ const dbTransaction = null;
270
+ const query = {
271
+ Email: 'test@example.com',
272
+ IdType: 'passport',
273
+ IdNo: '123456789',
274
+ ContactNo: '1234567890',
275
+ };
276
+
277
+ // Mock the LoginUser['_Repository'].findAll method to return an empty array
278
+ jest.spyOn(LoginUser['_Repository'], 'findAll').mockResolvedValueOnce([]);
279
+
280
+ // Assert that the method does not throw an error
281
+ await expect(LoginUser['checkUserInfoDuplicated'](dbTransaction, query)).resolves.not.toThrowError();
282
+ });
283
+ });
284
+
285
+ describe('generateDefaultPassword', () => {
286
+ const passwordPolicy = {
287
+ minLen: 6,
288
+ maxLen: 10,
289
+ nonAcceptableChar: 'i,l,o',
290
+ numOfCapitalLetters: 1,
291
+ numOfNumbers: 1,
292
+ numOfSpecialChars: 1,
293
+ };
294
+ beforeEach(() => {
295
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
296
+ if (configKey === 'passwordPolicy') {
297
+ return passwordPolicy as any;
298
+ }
299
+ });
300
+ });
301
+
302
+ it('should generate a default password with the specified length', () => {
303
+ const password = LoginUser['generateDefaultPassword']();
304
+ expect(password.length).toBeGreaterThanOrEqual(6);
305
+ expect(password.length).toBeLessThanOrEqual(10);
306
+ });
307
+
308
+ it('should generate a default password with at least one capital letter', () => {
309
+ const password = LoginUser['generateDefaultPassword']();
310
+ expect(/[A-Z]/.test(password)).toBe(true);
311
+ });
312
+
313
+ it('should generate a default password with at least one number', () => {
314
+ const password = LoginUser['generateDefaultPassword']();
315
+ expect(/[0-9]/.test(password)).toBe(true);
316
+ });
317
+
318
+ it('should generate a default password with at least one special character', () => {
319
+ const password = LoginUser['generateDefaultPassword']();
320
+ expect(/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?~`]/.test(password)).toBe(true);
321
+ });
322
+
323
+ it('should generate a default password without any non-acceptable characters', () => {
324
+ const password = LoginUser['generateDefaultPassword']();
325
+ const nonAcceptableChars = ['i', 'l', 'o'];
326
+ expect(nonAcceptableChars.some(char => password.includes(char))).toBe(false);
327
+ });
328
+ });
329
+
330
+ describe('setPassword', () => {
331
+ const passwordPolicy = {
332
+ minLen: 6,
333
+ maxLen: 10,
334
+ nonAcceptableChar: 'i,l,o',
335
+ numOfCapitalLetters: 1,
336
+ numOfNumbers: 1,
337
+ numOfSpecialChars: 1,
338
+ };
339
+ beforeEach(() => {
340
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
341
+ if (configKey === 'passwordPolicy') {
342
+ return passwordPolicy as any;
343
+ }
344
+ });
345
+ });
346
+
347
+ it('should set the password for the user', async () => {
348
+ // Arrange
349
+ const dbTransaction = null;
350
+ const sessionService = await SessionService.init();
351
+ const user = await LoginUser.init(sessionService);
352
+ const password = 'N3wP@ssw0';
353
+
354
+ // Act
355
+ const result = await LoginUser['setPassword'](dbTransaction, user, password);
356
+
357
+ // Assert
358
+ expect(result).toBeInstanceOf(LoginUser);
359
+ await expect(LoginUser['setPassword'](dbTransaction, user, password)).resolves.not.toThrowError();
360
+ expect(result.Password).toBeDefined();
361
+ });
362
+
363
+ it('should throw an error if the password does not meet the security requirements', async () => {
364
+ // Arrange
365
+ const dbTransaction = null;
366
+ const sessionService = await SessionService.init();
367
+ const user = await LoginUser.init(sessionService);
368
+ const password = 'weakpassword';
369
+
370
+ // Act & Assert
371
+ await expect(LoginUser['setPassword'](dbTransaction, user, password)).rejects.toThrow();
372
+ });
373
+ });
374
+
375
+ describe('create', () => {
376
+ let loginUser: LoginUser;
377
+ let dbTransaction: any;
378
+ let user: LoginUser;
379
+ let newUser: any;
380
+
381
+ beforeEach(async () => {
382
+ const sessionService = await SessionService.init();
383
+ loginUser = await LoginUser.init(sessionService);
384
+ dbTransaction = null;
385
+ newUser = {
386
+ UserId: 1,
387
+ FullName: 'John Doe',
388
+ Email: 'john.doe@example.com',
389
+ Password: 'password',
390
+ Status: 'Active',
391
+ DefaultPasswordChangedYN: 'N',
392
+ FirstLoginAt: null,
393
+ LastLoginAt: null,
394
+ MFAEnabled: null,
395
+ MFAConfig: null,
396
+ RecoveryEmail: null,
397
+ FailedLoginAttemptCount: 0,
398
+ LastFailedLoginAt: null,
399
+ LastPasswordChangedAt: new Date(),
400
+ NeedToChangePasswordYN: 'N',
401
+ CreatedById: 1,
402
+ CreatedAt: new Date(),
403
+ UpdatedById: 1,
404
+ UpdatedAt: new Date(),
405
+ Staff: {
406
+ FullName: 'John Doe',
407
+ IdNo: '1234567890',
408
+ Mobile: '1234567890',
409
+ },
410
+ };
411
+ user = new (LoginUser as any)(sessionService, null, newUser);
412
+ });
413
+
414
+ afterEach(() => {
415
+ jest.clearAllMocks();
416
+ });
417
+
418
+ it('should create a new user record', async () => {
419
+ const checkPrivilegesMock = jest
420
+ .spyOn(loginUser, 'checkPrivileges')
421
+ .mockResolvedValueOnce(true);
422
+
423
+ const checkUserInfoDuplicatedMock = jest
424
+ .spyOn((LoginUser as any), 'checkUserInfoDuplicated')
425
+ .mockResolvedValueOnce(undefined);
426
+
427
+ const generateDefaultPasswordMock = jest
428
+ .spyOn((LoginUser as any), 'generateDefaultPassword')
429
+ .mockReturnValueOnce('defaultPassword');
430
+
431
+ const setPasswordMock = jest
432
+ .spyOn((LoginUser as any), 'setPassword')
433
+ .mockResolvedValueOnce(user);
434
+
435
+ const createMock = jest
436
+ .spyOn((LoginUser as any)['_Repository'], 'create')
437
+ .mockResolvedValueOnce({
438
+ ...newUser,
439
+ get: () => newUser,
440
+ });
441
+
442
+ const activityCreateMock = jest
443
+ .spyOn((Activity as any).prototype, 'create')
444
+ .mockResolvedValueOnce(undefined);
445
+
446
+
447
+ jest.spyOn(ApplicationConfig, 'getComponentConfigValue').mockImplementation((configKey: string) => {
448
+ if (configKey === 'system-code') {
449
+ return 'SC';
450
+ }
451
+ });
452
+
453
+
454
+ const result = await LoginUser.create(loginUser, dbTransaction, user);
455
+
456
+ expect(checkPrivilegesMock).toHaveBeenCalledTimes(1);
457
+ expect(checkPrivilegesMock).toHaveBeenCalledWith(
458
+ ApplicationConfig.getComponentConfigValue('system-code'),
459
+ 'User - Create'
460
+ );
461
+
462
+ expect(checkUserInfoDuplicatedMock).toHaveBeenCalledTimes(1);
463
+ expect(checkUserInfoDuplicatedMock).toHaveBeenCalledWith(
464
+ dbTransaction,
465
+ {
466
+ Email: user.Email,
467
+ IdType: user.IDType,
468
+ IdNo: user.IDNo,
469
+ ContactNo: user.ContactNo,
470
+ }
471
+ );
472
+
473
+ expect(generateDefaultPasswordMock).toHaveBeenCalledTimes(1);
474
+
475
+ expect(setPasswordMock).toHaveBeenCalledTimes(1);
476
+ expect(setPasswordMock).toHaveBeenCalledWith(
477
+ dbTransaction,
478
+ user,
479
+ 'defaultPassword'
480
+ );
481
+
482
+ expect(createMock).toHaveBeenCalledTimes(1);
483
+
484
+ const userInfo = {
485
+ FullName: user.FullName,
486
+ IDNo: user.IDNo,
487
+ Email: user.Email,
488
+ ContactNo: user.ContactNo,
489
+ Password: user.Password,
490
+ Status: UserStatus.ACTIVE,
491
+ FirstLoginAt: null,
492
+ LastLoginAt: null,
493
+ MFAEnabled: null,
494
+ MFAConfig: null,
495
+ RecoveryEmail: null,
496
+ FailedLoginAttemptCount: 0,
497
+ LastFailedLoginAt: null,
498
+ LastPasswordChangedAt: null,
499
+ DefaultPasswordChangedYN: YN.No,
500
+ NeedToChangePasswordYN: YN.Yes,
501
+ CreatedById: loginUser.UserId,
502
+ CreatedAt: new Date(),
503
+ UpdatedById: loginUser.UserId,
504
+ UpdatedAt: new Date(),
505
+ UserId: null,
506
+ };
507
+
508
+ expect(createMock).toHaveBeenCalledWith(
509
+ {
510
+ Email: userInfo.Email,
511
+ Password: userInfo.Password,
512
+ Status: userInfo.Status,
513
+ DefaultPasswordChangedYN: userInfo.DefaultPasswordChangedYN,
514
+ FirstLoginAt: userInfo.FirstLoginAt,
515
+ LastLoginAt: userInfo.LastLoginAt,
516
+ MFAEnabled: userInfo.MFAEnabled,
517
+ MFAConfig: userInfo.MFAConfig,
518
+ RecoveryEmail: userInfo.RecoveryEmail,
519
+ FailedLoginAttemptCount: userInfo.FailedLoginAttemptCount,
520
+ LastFailedLoginAt: userInfo.LastFailedLoginAt,
521
+ LastPasswordChangedAt: userInfo.LastPasswordChangedAt,
522
+ NeedToChangePasswordYN: userInfo.NeedToChangePasswordYN,
523
+ CreatedById: userInfo.CreatedById,
524
+ CreatedAt: expect.any(Date),
525
+ UpdatedById: userInfo.UpdatedById,
526
+ UpdatedAt: expect.any(Date),
527
+ },
528
+ {
529
+ transaction: dbTransaction,
530
+ }
531
+ );
532
+
533
+ expect(activityCreateMock).toHaveBeenCalledTimes(1);
534
+
535
+ expect(result).toBeInstanceOf(LoginUser);
536
+ expect(result.Email).toBe(userInfo.Email);
537
+ expect(result.Password).toBe(userInfo.Password);
538
+ expect(result.Status).toBe(userInfo.Status);
539
+ expect(result.DefaultPasswordChangedYN).toBe(
540
+ userInfo.DefaultPasswordChangedYN
541
+ );
542
+ expect(result.FirstLoginAt).toBe(null);
543
+ expect(result.LastLoginAt).toBe(null);
544
+ expect(result.MFAEnabled).toBe(userInfo.MFAEnabled);
545
+ expect(result.MFAConfig).toBe(userInfo.MFAConfig);
546
+ expect(result.RecoveryEmail).toBe(userInfo.RecoveryEmail);
547
+ expect(result.FailedLoginAttemptCount).toBe(
548
+ userInfo.FailedLoginAttemptCount
549
+ );
550
+ expect(result.LastFailedLoginAt).toBe(userInfo.LastFailedLoginAt);
551
+ expect(result.LastPasswordChangedAt).toBe(userInfo.LastPasswordChangedAt);
552
+ expect(result.NeedToChangePasswordYN).toBe(
553
+ userInfo.NeedToChangePasswordYN
554
+ );
555
+ expect(result.CreatedById).toBe(userInfo.CreatedById);
556
+ expect(result.UpdatedById).toBe(userInfo.UpdatedById);
557
+ });
558
+
559
+ it('should throw an error if user dont have the privilege to create new user', async () => {
560
+ jest
561
+ .spyOn(loginUser, 'checkPrivileges')
562
+ .mockResolvedValueOnce(false);
563
+
564
+ await expect(
565
+ LoginUser.create(loginUser, dbTransaction, user)
566
+ ).rejects.toThrow(ClassError);
567
+ });
568
+
569
+ it('should throw an error if user email is missing', async () => {
570
+ user.Email = undefined;
571
+
572
+ jest
573
+ .spyOn(loginUser, 'checkPrivileges')
574
+ .mockResolvedValueOnce(true);
575
+
576
+ await expect(
577
+ LoginUser.create(loginUser, dbTransaction, user)
578
+ ).rejects.toThrow(ClassError);
579
+ });
580
+ });
581
+
582
+ describe('incrementFailedLoginAttemptCount', () => {
583
+ afterAll(() => {
584
+ jest.restoreAllMocks();
585
+ });
586
+
587
+ it('should increment FailedLoginAttemptCount and update user status', async () => {
588
+ // Arrange
589
+ const sessionService = await SessionService.init();
590
+ const loginUser = await LoginUser.init(sessionService);
591
+ loginUser['FailedLoginAttemptCount'] = 2;
592
+ loginUser['LastFailedLoginAt'] = new Date();
593
+ loginUser['Status'] = UserStatus.ACTIVE;
594
+
595
+ const dbTransaction = null;
596
+
597
+ // Mock the static methods and properties
598
+ jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
599
+
600
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
601
+ if (configKey === 'maxFailedLoginAttempts') {
602
+ return 3;
603
+ }
604
+ if (configKey === 'autoReleaseYN') {
605
+ return 'Y';
606
+ }
607
+ });
608
+
609
+
610
+ // Act
611
+ await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
612
+
613
+ // Assert
614
+ expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
615
+ {
616
+ FailedLoginAttemptCount: 3,
617
+ LastFailedLoginAt: expect.any(Date),
618
+ Status: UserStatus.ACTIVE,
619
+ },
620
+ {
621
+ where: {
622
+ UserId: loginUser.UserId,
623
+ },
624
+ transaction: dbTransaction,
625
+ }
626
+ );
627
+ });
628
+
629
+ it('should throw an error if maxFailedLoginAttempts or autoReleaseYN is missing', async () => {
630
+ // Arrange
631
+ const sessionService = await SessionService.init();
632
+ const loginUser = await LoginUser.init(sessionService);
633
+ loginUser['FailedLoginAttemptCount'] = 2;
634
+ loginUser['LastFailedLoginAt'] = new Date();
635
+ loginUser['Status'] = UserStatus.ACTIVE;
636
+
637
+ const dbTransaction = null;
638
+
639
+ // Mock the static methods and properties
640
+ jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
641
+
642
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementationOnce((componentName: string, configKey: string) => {
643
+ if (configKey === 'maxFailedLoginAttempts') {
644
+ return undefined;
645
+ }
646
+ if (configKey === 'autoReleaseYN') {
647
+ return undefined;
648
+ }
649
+ });
650
+
651
+ // Act and Assert
652
+ await expect(
653
+ loginUser['incrementFailedLoginAttemptCount'](dbTransaction)
654
+ ).rejects.toThrow(
655
+ new ClassError(
656
+ 'LoginUser',
657
+ 'LoginUserErrMsg0X',
658
+ 'Missing maxFailedLoginAttempts and or autoReleaseYN. Please set in config file.',
659
+ ));
660
+ });
661
+
662
+ it('should lock the user account if the failed login attempts exceed the maximum allowed', async () => {
663
+ // Arrange
664
+ const sessionService = await SessionService.init();
665
+ const loginUser = await LoginUser.init(sessionService);
666
+ loginUser['FailedLoginAttemptCount'] = 3;
667
+ loginUser['LastFailedLoginAt'] = new Date();
668
+ loginUser['Status'] = UserStatus.ACTIVE;
669
+
670
+ const dbTransaction = null;
671
+
672
+ // Mock the static methods and properties
673
+ jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
674
+
675
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
676
+ if (configKey === 'maxFailedLoginAttempts') {
677
+ return 3;
678
+ }
679
+ if (configKey === 'autoReleaseYN') {
680
+ return 'Y';
681
+ }
682
+ });
683
+
684
+ // Act
685
+ try {
686
+ await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
687
+ expect(false).toBe(true);
688
+ } catch (error) {
689
+ // Assert
690
+ expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
691
+ {
692
+ FailedLoginAttemptCount: 4,
693
+ LastFailedLoginAt: expect.any(Date),
694
+ Status: UserStatus.LOCKED,
695
+ },
696
+ {
697
+ where: {
698
+ UserId: loginUser.UserId,
699
+ },
700
+ transaction: dbTransaction,
701
+ }
702
+ );
703
+ expect(error).toBeInstanceOf(ClassError);
704
+ expect(error.message).toBe('Your account has been temporarily locked due to too many failed login attempts, please try again later.');
705
+ }
706
+ });
707
+
708
+ it('should permanently lock the user account if the failed login attempts exceed the maximum allowed and autoReleaseYN is N', async () => {
709
+ // Arrange
710
+ const sessionService = await SessionService.init();
711
+ const loginUser = await LoginUser.init(sessionService);
712
+ loginUser['FailedLoginAttemptCount'] = 3;
713
+ loginUser['LastFailedLoginAt'] = new Date();
714
+ loginUser['Status'] = UserStatus.ACTIVE;
715
+
716
+ const dbTransaction = null;
717
+
718
+ // Mock the static methods and properties
719
+ jest.spyOn((LoginUser as any)['_Repository'], 'update').mockReturnValueOnce(null);
720
+
721
+ jest.spyOn(ComponentConfig, 'getComponentConfigValue').mockImplementation((componentName: string, configKey: string) => {
722
+ if (configKey === 'maxFailedLoginAttempts') {
723
+ return 3;
724
+ }
725
+ if (configKey === 'autoReleaseYN') {
726
+ return 'N';
727
+ }
728
+ });
729
+
730
+ // Act
731
+ try {
732
+ await loginUser['incrementFailedLoginAttemptCount'](dbTransaction);
733
+ expect(false).toBe(true);
734
+ } catch (error) {
735
+ // Assert
736
+ expect(LoginUser['_Repository'].update).toHaveBeenCalledWith(
737
+ {
738
+ FailedLoginAttemptCount: 4,
739
+ LastFailedLoginAt: expect.any(Date),
740
+ Status: UserStatus.LOCKED,
741
+ },
742
+ {
743
+ where: {
744
+ UserId: loginUser.UserId,
745
+ },
746
+ transaction: dbTransaction,
747
+ }
748
+ );
749
+ expect(error).toBeInstanceOf(ClassError);
750
+ expect(error.message).toBe('Your account has been locked due to too many failed login attempts, please contact IT Support for instructions on how to unlock your account');
751
+ }
752
+ });
753
+ });
754
+
755
+ describe('combineSystemAccess', () => {
756
+ it('should combine user and group system access and remove duplicates', async () => {
757
+ // Mock the necessary dependencies
758
+ const sessionService = await SessionService.init();
759
+ const loginUser = await LoginUser.init(sessionService);
760
+ const dbTransaction = null;
761
+ const groups = [
762
+ { InheritParentSystemAccessYN: true },
763
+ { InheritParentSystemAccessYN: false },
764
+ ];
765
+
766
+ // Mock the necessary repository methods
767
+ jest.spyOn((LoginUser as any), 'getInheritedSystemAccess').mockResolvedValueOnce([
768
+ { SystemCode: 'system1' },
769
+ { SystemCode: 'system2' },
770
+ ]);
771
+ jest.spyOn((LoginUser as any)['_UserSystemAccessRepo'], 'findAll').mockResolvedValueOnce([
772
+ { SystemCode: 'system3' },
773
+ ]);
774
+
775
+ // Call the method
776
+ const result = await LoginUser['combineSystemAccess'](
777
+ loginUser,
778
+ dbTransaction,
779
+ groups
780
+ );
781
+
782
+ // Assert the result
783
+ expect(result).toEqual([
784
+ { SystemCode: 'system3' },
785
+ { SystemCode: 'system1' },
786
+ { SystemCode: 'system2' },
787
+ ]);
788
+ });
789
+ });
790
+
791
+ describe('checkPrivileges', () => {
792
+ it('should return true if user has the specified privilege', async () => {
793
+ // Arrange
794
+ const systemCode = 'SS';
795
+ const privilegeName = 'Privilege 1';
796
+ const sessionService = await SessionService.init();
797
+ const loginUser = await LoginUser.init(sessionService);
798
+ loginUser.ObjectId = '1';
799
+
800
+ // Mock the necessary methods
801
+ const rus = jest.spyOn(SessionService.prototype, 'retrieveUserSession').mockResolvedValueOnce({
802
+ systemLogins: [
803
+ {
804
+ id: '1',
805
+ sessionId: 'sessionId',
806
+ code: systemCode,
807
+ privileges: [privilegeName],
808
+ },
809
+ ],
810
+ });
811
+ // Act
812
+ const hasPrivilege = await loginUser.checkPrivileges(systemCode, privilegeName);
813
+
814
+ // Assert
815
+ expect(hasPrivilege).toBe(true);
816
+ expect(rus).toHaveBeenCalledWith(loginUser.ObjectId);
817
+ });
818
+
819
+ it('should return false if user does not have the specified privilege', async () => {
820
+ // Arrange
821
+ const systemCode = 'SS';
822
+ const privilegeName = 'Privilege 1';
823
+ const sessionService = await SessionService.init();
824
+ const loginUser = await LoginUser.init(sessionService);
825
+ loginUser.ObjectId = '1';
826
+
827
+ // Mock the necessary methods
828
+ const rus = jest.spyOn(SessionService.prototype, 'retrieveUserSession').mockResolvedValueOnce({
829
+ systemLogins: [
830
+ {
831
+ id: '1',
832
+ sessionId: 'sessionId',
833
+ code: systemCode,
834
+ privileges: [],
835
+ },
836
+ ],
837
+ });
838
+ // Act
839
+ const hasPrivilege = await loginUser.checkPrivileges(systemCode, privilegeName);
840
+
841
+ // Assert
842
+ expect(hasPrivilege).toBe(false);
843
+ expect(rus).toHaveBeenCalledWith(loginUser.ObjectId);
844
+ });
845
+
846
+ it('should throw an error if ObjectId is not set', async () => {
847
+ // Arrange
848
+ const systemCode = 'SS';
849
+ const privilegeName = 'Privilege 1';
850
+ const sessionService = await SessionService.init();
851
+ const loginUser = await LoginUser.init(sessionService);
852
+ loginUser.ObjectId = null;
853
+
854
+ // Act & Assert
855
+ await expect(loginUser.checkPrivileges(systemCode, privilegeName)).rejects.toThrowError();
856
+ });
857
+ });
858
+
859
+ describe('getObjectPrivileges', () => {
860
+ it('should return an array of privileges', async () => {
861
+ // Mock the dependencies and setup the test data
862
+ const systemCode = 'system1';
863
+ const dbTransaction = null;
864
+ const sessionService = await SessionService.init();
865
+ const loginUser = await LoginUser.init(sessionService);
866
+
867
+ // Mock the UserObjectPrivilegeRepo findAll method
868
+ const findAllMock = jest
869
+ .spyOn(LoginUser['_UserObjectPrivilegeRepo'], 'findAll')
870
+ .mockResolvedValue([
871
+ {
872
+ PrivilegeCode: 'privilege1',
873
+ Privilege: {
874
+ PrivilegeCode: 'privilege1',
875
+ SystemCode: systemCode,
876
+ Name: 'privilege1',
877
+ },
878
+ },
879
+ {
880
+ PrivilegeCode: 'privilege2',
881
+ Privilege: {
882
+ PrivilegeCode: 'privilege2',
883
+ Name: 'privilege2',
884
+ },
885
+ },
886
+ ] as any);
887
+
888
+ // Call the getObjectPrivileges method
889
+ const result = await loginUser['getObjectPrivileges'](systemCode, dbTransaction);
890
+
891
+ // Assertions
892
+ expect(findAllMock).toHaveBeenCalledTimes(1);
893
+ expect(findAllMock).toHaveBeenCalledWith({
894
+ where: {
895
+ UserId: loginUser.UserId,
896
+ },
897
+ include: {
898
+ model: SystemPrivilegeModel,
899
+ where: {
900
+ SystemCode: systemCode,
901
+ Status: 'Active',
902
+ },
903
+ },
904
+ transaction: dbTransaction,
905
+ });
906
+ expect(result).toEqual(['privilege1', 'privilege2']);
907
+ });
908
+
909
+ it('should throw an error if an exception occurs', async () => {
910
+ // Mock the dependencies and setup the test data
911
+ const systemCode = 'system1';
912
+ const dbTransaction = null;
913
+ const sessionService = await SessionService.init();
914
+ const loginUser = await LoginUser.init(sessionService);
915
+
916
+ // Mock the UserObjectPrivilegeRepo findAll method to throw an error
917
+ jest.spyOn(LoginUser['_UserObjectPrivilegeRepo'], 'findAll').mockRejectedValue(new Error('Database error'));
918
+
919
+ // Call the getObjectPrivileges method and expect it to throw an error
920
+ await expect(loginUser['getObjectPrivileges'](systemCode, dbTransaction)).rejects.toThrow(Error);
921
+ });
922
+ });
923
+
924
+ describe('getUserPersonalPrivileges', () => {
925
+ it('should return an array of privileges', async () => {
926
+ // Arrange
927
+ const sessionService = await SessionService.init();
928
+ const loginUser = await LoginUser.init(sessionService);
929
+ const systemCode = 'system1';
930
+ const dbTransaction = null;
931
+
932
+ // Mock the findAll method of UserPrivilegeRepo
933
+ const findAllMock = jest.spyOn(LoginUser['_UserPrivilegeRepo'], 'findAll');
934
+ findAllMock.mockResolvedValue([
935
+ { Privilege: { Name: 'privilege1' } },
936
+ { Privilege: { Name: 'privilege2' } },
937
+ ] as any);
938
+
939
+ // Act
940
+ const privileges = await loginUser['getUserPersonalPrivileges'](
941
+ systemCode,
942
+ dbTransaction
943
+ );
944
+
945
+ // Assert
946
+ expect(privileges).toEqual(['privilege1', 'privilege2']);
947
+ expect(findAllMock).toHaveBeenCalledTimes(1);
948
+ expect(findAllMock).toHaveBeenCalledWith({
949
+ where: {
950
+ UserId: loginUser.UserId,
951
+ Status: 'Active',
952
+ },
953
+ include: {
954
+ model: SystemPrivilegeModel,
955
+ where: {
956
+ SystemCode: systemCode,
957
+ Status: 'Active',
958
+ },
959
+ },
960
+ transaction: dbTransaction,
961
+ });
962
+ });
963
+
964
+ it('should throw an error if an error occurs', async () => {
965
+ // Arrange
966
+ const sessionService = await SessionService.init();
967
+ const loginUser = await LoginUser.init(sessionService);
968
+ const systemCode = 'system1';
969
+ const dbTransaction = null;
970
+
971
+ // Mock the findAll method of UserPrivilegeRepo to throw an error
972
+ const findAllMock = jest.spyOn(LoginUser['_UserPrivilegeRepo'], 'findAll');
973
+ findAllMock.mockRejectedValue(new Error('Database error'));
974
+
975
+ // Act and Assert
976
+ await expect(
977
+ loginUser['getUserPersonalPrivileges'](systemCode, dbTransaction)
978
+ ).rejects.toThrow(Error);
979
+
980
+ expect(findAllMock).toHaveBeenCalledTimes(1);
981
+ expect(findAllMock).toHaveBeenCalledWith({
982
+ where: {
983
+ UserId: loginUser.UserId,
984
+ Status: 'Active',
985
+ },
986
+ include: {
987
+ model: SystemPrivilegeModel,
988
+ where: {
989
+ SystemCode: systemCode,
990
+ Status: 'Active',
991
+ },
992
+ },
993
+ transaction: dbTransaction,
994
+ });
995
+ });
996
+ });
997
+
998
+ describe('getInheritedSystemAccess', () => {
999
+ it('should return group system access with its parent group system access if applicable', async () => {
1000
+ // Mock the necessary dependencies
1001
+ const dbTransaction = null;
1002
+ const group = {
1003
+ GroupCode: 'group1',
1004
+ InheritParentPrivilegeYN: 'Y',
1005
+ ParentGroupCode: 'parentGroup',
1006
+ } as any;
1007
+ const parentGroup = {
1008
+ GroupCode: 'parentGroup',
1009
+ InheritParentPrivilegeYN: 'N',
1010
+ ParentGroupCode: null,
1011
+ } as any;
1012
+
1013
+ const systemAccess = [
1014
+ { SystemCode: 'system1', GroupCode: 'group1', System: { SystemCode: 'system1' } },
1015
+ { SystemCode: 'system2', GroupCode: 'group1', System: { SystemCode: 'system1' } },
1016
+ ] as any;
1017
+
1018
+ const parentSystemAccess = [
1019
+ { SystemCode: 'system3', GroupCode: 'parentGroup', System: { SystemCode: 'system3' } },
1020
+ ] as any;
1021
+
1022
+ // Mock the necessary repository methods
1023
+ const groupFindByPkMock = jest
1024
+ .spyOn(GroupRepository.prototype, 'findByPk')
1025
+ .mockResolvedValueOnce(parentGroup as any);
1026
+ const systemAccessFindAllMock = jest
1027
+ .spyOn(GroupSystemAccessRepository.prototype, 'findAll')
1028
+ .mockImplementation((options: any) => {
1029
+ if (options.where.GroupCode === group.GroupCode) {
1030
+ return Promise.resolve(systemAccess);
1031
+ } else if (options.where.GroupCode === parentGroup.GroupCode) {
1032
+ return Promise.resolve(parentSystemAccess);
1033
+ }
1034
+ });
1035
+
1036
+ // Call the method
1037
+ const result = await LoginUser['getInheritedSystemAccess'](
1038
+ dbTransaction,
1039
+ group
1040
+ );
1041
+
1042
+ console.log(result);
1043
+ // Assert the result
1044
+ expect(result).toEqual([
1045
+ {
1046
+ SystemCode: 'system1',
1047
+ GroupCode: 'group1',
1048
+ System: { SystemCode: 'system1' }
1049
+ },
1050
+ {
1051
+ SystemCode: 'system2',
1052
+ GroupCode: 'group1',
1053
+ System: { SystemCode: 'system1' }
1054
+ },
1055
+ {
1056
+ SystemCode: 'system3',
1057
+ GroupCode: 'parentGroup',
1058
+ System: { SystemCode: 'system3' }
1059
+ }
1060
+ ]);
1061
+ expect(groupFindByPkMock).toHaveBeenCalledWith(group.ParentGroupCode, dbTransaction);
1062
+ expect(systemAccessFindAllMock).toHaveBeenCalledTimes(2);
1063
+ })
1064
+ });
1065
1065
  });