@tomei/sso 0.34.0 → 0.34.2

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 (139) 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/src/components/group/group.d.ts +4 -0
  103. package/dist/src/components/group/group.js +73 -0
  104. package/dist/src/components/group/group.js.map +1 -1
  105. package/dist/src/components/group/group.repository.d.ts +2 -0
  106. package/dist/src/components/group/group.repository.js +25 -0
  107. package/dist/src/components/group/group.repository.js.map +1 -1
  108. package/dist/src/components/login-user/login-user.d.ts +1 -1
  109. package/dist/src/interfaces/group-search-attr.interface.d.ts +1 -0
  110. package/dist/tsconfig.tsbuildinfo +1 -1
  111. package/jest.config.js +14 -14
  112. package/migrations/20240314080602-create-user-table.js +108 -108
  113. package/migrations/20240314080603-create-user-group-table.js +85 -85
  114. package/migrations/20240314080604-create-user-user-group-table.js +55 -55
  115. package/migrations/20240314080605-create-login-history-table.js +53 -53
  116. package/migrations/20240527064925-create-system-table.js +78 -78
  117. package/migrations/20240527064926-create-system-privilege-table.js +67 -67
  118. package/migrations/20240527065342-create-group-table.js +89 -89
  119. package/migrations/20240527065633-create-group-reporting-user-table.js +76 -76
  120. package/migrations/20240528011551-create-group-system-access-table.js +72 -72
  121. package/migrations/20240528023018-user-system-access-table.js +75 -75
  122. package/migrations/20240528032229-user-privilege-table.js +75 -75
  123. package/migrations/20240528063003-create-group-privilege-table.js +75 -75
  124. package/migrations/20240528063051-create-group-object-privilege-table.js +84 -84
  125. package/migrations/20240528063107-create-user-object-privilege-table.js +83 -83
  126. package/package.json +89 -89
  127. package/sampledotenv +7 -7
  128. package/sonar-project.properties +22 -22
  129. package/src/components/group/group.repository.ts +15 -0
  130. package/src/components/group/group.ts +115 -0
  131. package/src/interfaces/group-search-attr.interface.ts +1 -0
  132. package/tsconfig.build.json +5 -5
  133. package/tsconfig.json +22 -22
  134. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.d.ts +0 -1
  135. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js +0 -71
  136. package/dist/__tests__/unit/components/group-privilege/group-privilege.test.js.map +0 -1
  137. package/dist/__tests__/unit/components/login-user/login-user.spec.d.ts +0 -0
  138. package/dist/__tests__/unit/components/login-user/login-user.spec.js +0 -6
  139. package/dist/__tests__/unit/components/login-user/login-user.spec.js.map +0 -1
@@ -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
  });