@tomei/sso 0.66.1-test.2 → 0.66.1-test.4

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 (56) hide show
  1. package/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts +12 -8
  2. package/__tests__/unit/components/login-user/login.spec.ts +310 -72
  3. package/__tests__/unit/components/login-user/password-expiry.spec.ts +87 -0
  4. package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js +6 -2
  5. package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js.map +1 -1
  6. package/dist/__tests__/unit/components/login-user/login.spec.js +227 -69
  7. package/dist/__tests__/unit/components/login-user/login.spec.js.map +1 -1
  8. package/dist/__tests__/unit/components/login-user/password-expiry.spec.d.ts +1 -0
  9. package/dist/__tests__/unit/components/login-user/password-expiry.spec.js +86 -0
  10. package/dist/__tests__/unit/components/login-user/password-expiry.spec.js.map +1 -0
  11. package/dist/src/components/login-user/interfaces/user-info.interface.d.ts +1 -0
  12. package/dist/src/components/login-user/login-user.d.ts +1 -0
  13. package/dist/src/components/login-user/login-user.js +85 -14
  14. package/dist/src/components/login-user/login-user.js.map +1 -1
  15. package/dist/src/components/login-user/user.d.ts +1 -0
  16. package/dist/src/components/login-user/user.js +7 -1
  17. package/dist/src/components/login-user/user.js.map +1 -1
  18. package/dist/src/components/system/system.d.ts +1 -0
  19. package/dist/src/components/system/system.js +1 -0
  20. package/dist/src/components/system/system.js.map +1 -1
  21. package/dist/src/components/system-privilege/system-privilege.d.ts +1 -0
  22. package/dist/src/components/system-privilege/system-privilege.js +2 -0
  23. package/dist/src/components/system-privilege/system-privilege.js.map +1 -1
  24. package/dist/src/index.d.ts +1 -0
  25. package/dist/src/index.js +1 -0
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/interfaces/system-privilege.interface.d.ts +1 -0
  28. package/dist/src/interfaces/system.interface.d.ts +1 -0
  29. package/dist/src/models/group.entity.d.ts +1 -0
  30. package/dist/src/models/group.entity.js +8 -1
  31. package/dist/src/models/group.entity.js.map +1 -1
  32. package/dist/src/models/system-privilege.entity.d.ts +1 -0
  33. package/dist/src/models/system-privilege.entity.js +7 -0
  34. package/dist/src/models/system-privilege.entity.js.map +1 -1
  35. package/dist/src/models/system.entity.d.ts +1 -0
  36. package/dist/src/models/system.entity.js +7 -0
  37. package/dist/src/models/system.entity.js.map +1 -1
  38. package/dist/src/models/user.entity.d.ts +1 -0
  39. package/dist/src/models/user.entity.js +7 -0
  40. package/dist/src/models/user.entity.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +2 -1
  43. package/src/components/login-user/interfaces/user-info.interface.ts +1 -0
  44. package/src/components/login-user/login-user.ts +125 -17
  45. package/src/components/login-user/user.ts +14 -2
  46. package/src/components/system/system.ts +2 -0
  47. package/src/components/system-privilege/system-privilege.ts +3 -0
  48. package/src/index.ts +1 -0
  49. package/src/interfaces/system-privilege.interface.ts +1 -0
  50. package/src/interfaces/system.interface.ts +1 -0
  51. package/src/models/group.entity.ts +7 -1
  52. package/src/models/system-privilege.entity.ts +6 -0
  53. package/src/models/system.entity.ts +6 -0
  54. package/src/models/user.entity.ts +6 -0
  55. package/coverage/cobertura-coverage.xml +0 -6837
  56. package/coverage/test-report.xml +0 -161
@@ -27,12 +27,12 @@ describe('GroupReportingUser', () => {
27
27
  jest.clearAllMocks();
28
28
  });
29
29
 
30
-
31
30
  describe('init', () => {
32
31
  const mockDbTransaction = {};
33
32
 
34
33
  it('should initialize GroupReportingUser without GroupReportingUserId', async () => {
35
- const initGroupReportingUser = await GroupReportingUser.init(mockDbTransaction);
34
+ const initGroupReportingUser =
35
+ await GroupReportingUser.init(mockDbTransaction);
36
36
  expect(initGroupReportingUser).toBeDefined();
37
37
  expect(initGroupReportingUser).toBeInstanceOf(GroupReportingUser);
38
38
  });
@@ -47,7 +47,7 @@ describe('GroupReportingUser', () => {
47
47
 
48
48
  const initGroupReportingUser = await GroupReportingUser.init(
49
49
  mockDbTransaction,
50
- '1'
50
+ '1',
51
51
  );
52
52
  expect(initGroupReportingUser).toBeDefined();
53
53
  expect(initGroupReportingUser).toBeInstanceOf(GroupReportingUser);
@@ -57,19 +57,23 @@ describe('GroupReportingUser', () => {
57
57
  });
58
58
 
59
59
  it('should throw ClassError when GroupReportingUser is not found', async () => {
60
- jest.spyOn(GroupReportingUserRepository.prototype, 'findByPk').mockResolvedValue(null);
60
+ jest
61
+ .spyOn(GroupReportingUserRepository.prototype, 'findByPk')
62
+ .mockResolvedValue(null);
61
63
 
62
64
  await expect(
63
- GroupReportingUser.init(mockDbTransaction, '1')
65
+ GroupReportingUser.init(mockDbTransaction, '1'),
64
66
  ).rejects.toThrow(ClassError);
65
67
  });
66
68
 
67
69
  it('should throw ClassError when failed to initialize GroupReportingUser', async () => {
68
- jest.spyOn(GroupReportingUserRepository.prototype, 'findByPk').mockRejectedValue(new Error());
70
+ jest
71
+ .spyOn(GroupReportingUserRepository.prototype, 'findByPk')
72
+ .mockRejectedValue(new Error());
69
73
 
70
74
  await expect(
71
- GroupReportingUser.init(mockDbTransaction, '1')
75
+ GroupReportingUser.init(mockDbTransaction, '1'),
72
76
  ).rejects.toThrow(Error);
73
77
  });
74
78
  });
75
- });
79
+ });
@@ -9,6 +9,7 @@ import { YN } from '../../../../src/enum/yn.enum';
9
9
  import SystemPrivilegeModel from '../../../../src/models/system-privilege.entity';
10
10
  import { GroupSystemAccessRepository } from '../../../../src/components/group-system-access/group-system-access.repository';
11
11
  import { GroupRepository } from '../../../../src/components/group/group.repository';
12
+ import axios from 'axios';
12
13
 
13
14
  const mockUserData = {
14
15
  UserId: 1,
@@ -58,11 +59,23 @@ describe('LoginUser', () => {
58
59
  let sessionService: any;
59
60
  let userId: number;
60
61
  let dbTransaction: any;
62
+ const mockLoginApiEndpoint = {
63
+ baseUrl: 'http://sso-api',
64
+ getProfile: '/get-profile',
65
+ apiKey: 'test-api-key',
66
+ };
61
67
 
62
68
  beforeEach(() => {
63
69
  sessionService = {};
64
70
  userId = 1;
65
71
  dbTransaction = null;
72
+ jest
73
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
74
+ .mockImplementation((componentName: string, configKey: string) =>
75
+ configKey === 'useLoginApiYN'
76
+ ? ('Y' as any)
77
+ : (mockLoginApiEndpoint as any),
78
+ );
66
79
  });
67
80
 
68
81
  it('should initialize LoginUser with valid userId', async () => {
@@ -93,9 +106,9 @@ describe('LoginUser', () => {
93
106
  },
94
107
  };
95
108
 
96
- const findOneMock = jest
97
- .spyOn(UserRepository.prototype, 'findOne')
98
- .mockResolvedValueOnce(user as any);
109
+ const axiosPostMock = jest
110
+ .spyOn(axios, 'post')
111
+ .mockResolvedValueOnce({ data: { loginUser: user } } as any);
99
112
 
100
113
  const result = await LoginUser.init(
101
114
  sessionService,
@@ -103,63 +116,58 @@ describe('LoginUser', () => {
103
116
  dbTransaction,
104
117
  );
105
118
 
106
- expect(findOneMock).toHaveBeenCalledTimes(1);
107
- expect(findOneMock).toHaveBeenCalledWith({
108
- where: {
109
- UserId: userId,
110
- },
111
- include: [
112
- {
113
- model: expect.anything(),
114
- },
115
- ],
116
- transaction: dbTransaction,
117
- });
119
+ expect(axiosPostMock).toHaveBeenCalledTimes(1);
120
+ expect(axiosPostMock).toHaveBeenCalledWith(
121
+ `${mockLoginApiEndpoint.baseUrl}${mockLoginApiEndpoint.getProfile}`,
122
+ { UserId: userId },
123
+ { headers: { 'Content-Type': 'application/json', 'x-api-key': mockLoginApiEndpoint.apiKey } },
124
+ );
118
125
  expect(result).toBeInstanceOf(LoginUser);
119
126
  expect(result.UserId).toBe(user.UserId);
120
127
  expect(result.FullName).toBe(user.FullName);
121
128
  expect(result.Email).toBe(user.Email);
122
- expect(result.Password).toBe(user.Password);
123
129
  expect(result.Status).toBe(user.Status);
124
- expect(result.DefaultPasswordChangedYN).toBe(
125
- user.DefaultPasswordChangedYN,
126
- );
127
- expect(result.FirstLoginAt).toBe(user.FirstLoginAt);
128
- expect(result.LastLoginAt).toBe(user.LastLoginAt);
129
- expect(result.MFAEnabled).toBe(user.MFAEnabled);
130
- expect(result.MFAConfig).toBe(user.MFAConfig);
131
- expect(result.RecoveryEmail).toBe(user.RecoveryEmail);
132
- expect(result.FailedLoginAttemptCount).toBe(user.FailedLoginAttemptCount);
133
- expect(result.LastFailedLoginAt).toBe(user.LastFailedLoginAt);
134
- expect(result.LastPasswordChangedAt).toBe(user.LastPasswordChangedAt);
135
- expect(result.NeedToChangePasswordYN).toBe(user.NeedToChangePasswordYN);
136
- expect(result.CreatedById).toBe(user.CreatedById);
137
- expect(result.CreatedAt).toBe(user.CreatedAt);
138
- expect(result.UpdatedById).toBe(user.UpdatedById);
139
- expect(result.UpdatedAt).toBe(user.UpdatedAt);
140
130
  });
141
131
 
142
132
  it('should throw an error when user is not found', async () => {
143
- const findOneMock = jest
144
- .spyOn(UserRepository.prototype, 'findOne')
145
- .mockResolvedValueOnce(null);
133
+ const axiosPostMock = jest
134
+ .spyOn(axios, 'post')
135
+ .mockResolvedValueOnce({ data: { loginUser: null } } as any);
146
136
 
147
137
  await expect(
148
138
  LoginUser.init(sessionService, userId, dbTransaction),
149
139
  ).rejects.toThrow(Error);
150
140
 
151
- expect(findOneMock).toHaveBeenCalledTimes(1);
141
+ expect(axiosPostMock).toHaveBeenCalledTimes(1);
142
+ });
143
+
144
+ it('should initialize LoginUser via direct db query when useLoginApi is false', async () => {
145
+ jest
146
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
147
+ .mockImplementation((componentName: string, configKey: string) => {
148
+ if (configKey === 'useLoginApiYN') return 'N' as any;
149
+ return mockLoginApiEndpoint as any;
150
+ });
151
+
152
+ const findOneMock = jest
153
+ .spyOn(LoginUser['_Repository'], 'findOne')
154
+ .mockResolvedValueOnce(mockUserData as any);
155
+ const axiosPostMock = jest.spyOn(axios, 'post');
156
+
157
+ const result = await LoginUser.init(
158
+ sessionService,
159
+ userId,
160
+ dbTransaction,
161
+ );
162
+
152
163
  expect(findOneMock).toHaveBeenCalledWith({
153
- where: {
154
- UserId: userId,
155
- },
156
- include: [
157
- {
158
- model: expect.anything(),
159
- },
160
- ],
164
+ where: { UserId: userId },
165
+ include: [{ model: expect.anything() }],
161
166
  transaction: dbTransaction,
162
167
  });
168
+ expect(axiosPostMock).not.toHaveBeenCalled();
169
+ expect(result).toBeInstanceOf(LoginUser);
170
+ expect(result.Email).toBe(mockUserData.Email);
163
171
  });
164
172
  });
165
173
 
@@ -889,10 +897,23 @@ describe('LoginUser', () => {
889
897
  });
890
898
 
891
899
  describe('checkPrivileges', () => {
900
+ const mockLoginApiEndpoint = {
901
+ baseUrl: 'http://sso-api',
902
+ checkPrivileges: '/check-privileges',
903
+ apiKey: 'test-api-key',
904
+ };
905
+
892
906
  beforeEach(() => {
893
907
  jest
894
908
  .spyOn(ApplicationConfig, 'getComponentConfigValue')
895
909
  .mockReturnValue('test-session' as any);
910
+ jest
911
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
912
+ .mockImplementation((componentName: string, configKey: string) =>
913
+ configKey === 'useLoginApiYN'
914
+ ? ('Y' as any)
915
+ : (mockLoginApiEndpoint as any),
916
+ );
896
917
  });
897
918
 
898
919
  it('should return true if user has the specified privilege', async () => {
@@ -903,19 +924,10 @@ describe('LoginUser', () => {
903
924
  const loginUser = await LoginUser.init(sessionService);
904
925
  loginUser.ObjectId = '1';
905
926
 
906
- // Mock the necessary methods
907
- const rus = jest
908
- .spyOn(SessionService.prototype, 'retrieveUserSession')
909
- .mockResolvedValueOnce({
910
- systemLogins: [
911
- {
912
- id: '1',
913
- sessionId: 'sessionId',
914
- code: systemCode,
915
- privileges: [privilegeName],
916
- },
917
- ],
918
- });
927
+ const axiosPostMock = jest
928
+ .spyOn(axios, 'post')
929
+ .mockResolvedValueOnce({ data: { hasPrivilege: true } } as any);
930
+
919
931
  // Act
920
932
  const hasPrivilege = await loginUser.checkPrivileges(
921
933
  systemCode,
@@ -924,7 +936,11 @@ describe('LoginUser', () => {
924
936
 
925
937
  // Assert
926
938
  expect(hasPrivilege).toBe(true);
927
- expect(rus).toHaveBeenCalledWith(loginUser.ObjectId, 'test-session');
939
+ expect(axiosPostMock).toHaveBeenCalledWith(
940
+ `${mockLoginApiEndpoint.baseUrl}${mockLoginApiEndpoint.checkPrivileges}`,
941
+ { SystemCode: systemCode, SessionName: 'test-session', PrivilegeName: privilegeName, UserId: '1' },
942
+ { headers: { 'Content-Type': 'application/json', 'x-api-key': mockLoginApiEndpoint.apiKey } },
943
+ );
928
944
  });
929
945
 
930
946
  it('should return false if user does not have the specified privilege', async () => {
@@ -935,19 +951,10 @@ describe('LoginUser', () => {
935
951
  const loginUser = await LoginUser.init(sessionService);
936
952
  loginUser.ObjectId = '1';
937
953
 
938
- // Mock the necessary methods
939
- const rus = jest
940
- .spyOn(SessionService.prototype, 'retrieveUserSession')
941
- .mockResolvedValueOnce({
942
- systemLogins: [
943
- {
944
- id: '1',
945
- sessionId: 'sessionId',
946
- code: systemCode,
947
- privileges: [],
948
- },
949
- ],
950
- });
954
+ const axiosPostMock = jest
955
+ .spyOn(axios, 'post')
956
+ .mockResolvedValueOnce({ data: { hasPrivilege: false } } as any);
957
+
951
958
  // Act
952
959
  const hasPrivilege = await loginUser.checkPrivileges(
953
960
  systemCode,
@@ -956,7 +963,6 @@ describe('LoginUser', () => {
956
963
 
957
964
  // Assert
958
965
  expect(hasPrivilege).toBe(false);
959
- expect(rus).toHaveBeenCalledWith(loginUser.ObjectId, 'test-session');
960
966
  });
961
967
 
962
968
  it('should throw an error if ObjectId is not set', async () => {
@@ -972,6 +978,238 @@ describe('LoginUser', () => {
972
978
  loginUser.checkPrivileges(systemCode, privilegeName),
973
979
  ).rejects.toThrow();
974
980
  });
981
+
982
+ it('should check the session privileges directly when useLoginApi is false', async () => {
983
+ const systemCode = 'SS';
984
+ const privilegeName = 'Privilege 1';
985
+ jest
986
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
987
+ .mockImplementation((componentName: string, configKey: string) => {
988
+ if (configKey === 'useLoginApiYN') return 'N' as any;
989
+ return mockLoginApiEndpoint as any;
990
+ });
991
+
992
+ const sessionService = await SessionService.init();
993
+ const loginUser = await LoginUser.init(sessionService);
994
+ loginUser.ObjectId = '1';
995
+ loginUser['_SessionService'].retrieveUserSession = jest
996
+ .fn()
997
+ .mockResolvedValue({
998
+ systemLogins: [{ code: systemCode, privileges: [privilegeName] }],
999
+ });
1000
+
1001
+ const axiosPostMock = jest.spyOn(axios, 'post');
1002
+
1003
+ const hasPrivilege = await loginUser.checkPrivileges(
1004
+ systemCode,
1005
+ privilegeName,
1006
+ );
1007
+
1008
+ expect(hasPrivilege).toBe(true);
1009
+ expect(axiosPostMock).not.toHaveBeenCalled();
1010
+ });
1011
+ });
1012
+
1013
+ describe('checkSession', () => {
1014
+ const mockLoginApiEndpoint = {
1015
+ baseUrl: 'http://sso-api',
1016
+ checkSession: '/check-session',
1017
+ apiKey: 'test-api-key',
1018
+ };
1019
+
1020
+ beforeEach(() => {
1021
+ jest
1022
+ .spyOn(ApplicationConfig, 'getComponentConfigValue')
1023
+ .mockReturnValue('test-session' as any);
1024
+ });
1025
+
1026
+ it('should call the login-api endpoint when useLoginApi is true', async () => {
1027
+ jest
1028
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
1029
+ .mockImplementation((componentName: string, configKey: string) =>
1030
+ configKey === 'useLoginApiYN'
1031
+ ? ('Y' as any)
1032
+ : (mockLoginApiEndpoint as any),
1033
+ );
1034
+
1035
+ const systemCode = 'SS';
1036
+ const sessionId = 'session-1';
1037
+ const userId = '1';
1038
+ const sessionService = await SessionService.init();
1039
+ const loginUser = await LoginUser.init(sessionService);
1040
+
1041
+ const mockSystemLogin = { code: systemCode, sessionId };
1042
+ const axiosPostMock = jest
1043
+ .spyOn(axios, 'post')
1044
+ .mockResolvedValueOnce({
1045
+ data: { systemLogin: mockSystemLogin },
1046
+ } as any);
1047
+
1048
+ const systemLogin = await loginUser.checkSession(
1049
+ systemCode,
1050
+ sessionId,
1051
+ userId,
1052
+ );
1053
+
1054
+ expect(systemLogin).toEqual(mockSystemLogin);
1055
+ expect(axiosPostMock).toHaveBeenCalledWith(
1056
+ `${mockLoginApiEndpoint.baseUrl}${mockLoginApiEndpoint.checkSession}`,
1057
+ {
1058
+ SystemCode: systemCode,
1059
+ SessionName: 'test-session',
1060
+ SessionId: sessionId,
1061
+ UserId: userId,
1062
+ },
1063
+ {
1064
+ headers: {
1065
+ 'Content-Type': 'application/json',
1066
+ 'x-api-key': mockLoginApiEndpoint.apiKey,
1067
+ },
1068
+ },
1069
+ );
1070
+ });
1071
+
1072
+ it('should validate the session directly against redis when useLoginApi is false', async () => {
1073
+ jest
1074
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
1075
+ .mockImplementation((componentName: string, configKey: string) => {
1076
+ if (configKey === 'useLoginApiYN') return 'N' as any;
1077
+ return mockLoginApiEndpoint as any;
1078
+ });
1079
+
1080
+ const systemCode = 'SS';
1081
+ const sessionId = 'session-1';
1082
+ const userId = '1';
1083
+ const sessionService = await SessionService.init();
1084
+ const loginUser = await LoginUser.init(sessionService);
1085
+
1086
+ const mockSystemLogin = { code: systemCode, sessionId };
1087
+ loginUser['_SessionService'].retrieveUserSession = jest
1088
+ .fn()
1089
+ .mockResolvedValue({ systemLogins: [mockSystemLogin] });
1090
+ loginUser['_SessionService'].refreshDuration = jest.fn();
1091
+
1092
+ const axiosPostMock = jest.spyOn(axios, 'post');
1093
+
1094
+ const systemLogin = await loginUser.checkSession(
1095
+ systemCode,
1096
+ sessionId,
1097
+ userId,
1098
+ );
1099
+
1100
+ expect(systemLogin).toEqual(mockSystemLogin);
1101
+ expect(
1102
+ loginUser['_SessionService'].refreshDuration,
1103
+ ).toHaveBeenCalledWith(userId, 'test-session');
1104
+ expect(axiosPostMock).not.toHaveBeenCalled();
1105
+ });
1106
+
1107
+ it('should throw "Session expired." when useLoginApi is false and the session is stale', async () => {
1108
+ jest
1109
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
1110
+ .mockImplementation((componentName: string, configKey: string) => {
1111
+ if (configKey === 'useLoginApiYN') return 'N' as any;
1112
+ return mockLoginApiEndpoint as any;
1113
+ });
1114
+
1115
+ const sessionService = await SessionService.init();
1116
+ const loginUser = await LoginUser.init(sessionService);
1117
+ loginUser['_SessionService'].retrieveUserSession = jest
1118
+ .fn()
1119
+ .mockResolvedValue({ systemLogins: [] });
1120
+
1121
+ await expect(
1122
+ loginUser.checkSession('SS', 'session-1', '1'),
1123
+ ).rejects.toThrow('Session expired.');
1124
+ });
1125
+ });
1126
+
1127
+ describe('setSession', () => {
1128
+ const mockLoginApiEndpoint = {
1129
+ baseUrl: 'http://sso-api',
1130
+ setSession: '/set-session',
1131
+ apiKey: 'test-api-key',
1132
+ };
1133
+
1134
+ beforeEach(() => {
1135
+ jest
1136
+ .spyOn(ApplicationConfig, 'getComponentConfigValue')
1137
+ .mockReturnValue('test-session' as any);
1138
+ });
1139
+
1140
+ it('should call the login-api endpoint when useLoginApi is true', async () => {
1141
+ jest
1142
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
1143
+ .mockImplementation((componentName: string, configKey: string) =>
1144
+ configKey === 'useLoginApiYN'
1145
+ ? ('Y' as any)
1146
+ : (mockLoginApiEndpoint as any),
1147
+ );
1148
+
1149
+ const systemCode = 'SS';
1150
+ const sessionId = 'session-1';
1151
+ const sessionService = await SessionService.init();
1152
+ const loginUser = await LoginUser.init(sessionService);
1153
+ loginUser.ObjectId = '1';
1154
+
1155
+ const axiosPostMock = jest
1156
+ .spyOn(axios, 'post')
1157
+ .mockResolvedValueOnce({ data: {} } as any);
1158
+
1159
+ await loginUser.setSession(systemCode, sessionId, null);
1160
+
1161
+ expect(axiosPostMock).toHaveBeenCalledWith(
1162
+ `${mockLoginApiEndpoint.baseUrl}${mockLoginApiEndpoint.setSession}`,
1163
+ {
1164
+ SystemCode: systemCode,
1165
+ SessionName: 'test-session',
1166
+ SessionId: sessionId,
1167
+ UserId: '1',
1168
+ },
1169
+ {
1170
+ headers: {
1171
+ 'Content-Type': 'application/json',
1172
+ 'x-api-key': mockLoginApiEndpoint.apiKey,
1173
+ },
1174
+ },
1175
+ );
1176
+ });
1177
+
1178
+ it('should persist the session directly to redis when useLoginApi is false', async () => {
1179
+ jest
1180
+ .spyOn(ComponentConfig, 'getComponentConfigValue')
1181
+ .mockImplementation((componentName: string, configKey: string) => {
1182
+ if (configKey === 'useLoginApiYN') return 'N' as any;
1183
+ return mockLoginApiEndpoint as any;
1184
+ });
1185
+
1186
+ const systemCode = 'SS';
1187
+ const sessionId = 'session-1';
1188
+ const sessionService = await SessionService.init();
1189
+ const loginUser = await LoginUser.init(sessionService);
1190
+ loginUser.ObjectId = '1';
1191
+
1192
+ jest.spyOn(loginUser, 'getPrivileges').mockResolvedValue(['privilege1']);
1193
+ loginUser['_SessionService'].retrieveUserSession = jest
1194
+ .fn()
1195
+ .mockResolvedValue({ systemLogins: [] });
1196
+ loginUser['_SessionService'].setUserSession = jest.fn();
1197
+
1198
+ const axiosPostMock = jest.spyOn(axios, 'post');
1199
+
1200
+ await loginUser.setSession(systemCode, sessionId, null);
1201
+
1202
+ expect(axiosPostMock).not.toHaveBeenCalled();
1203
+ expect(loginUser['_SessionService'].setUserSession).toHaveBeenCalledWith(
1204
+ '1',
1205
+ {
1206
+ systemLogins: [
1207
+ { id: systemCode, code: systemCode, sessionId, privileges: ['privilege1'] },
1208
+ ],
1209
+ },
1210
+ 'test-session',
1211
+ );
1212
+ });
975
1213
  });
976
1214
 
977
1215
  describe('getObjectPrivileges', () => {
@@ -0,0 +1,87 @@
1
+ import { User } from '../../../../src/components/login-user/user';
2
+ import { UserPasswordHistory } from '../../../../src/components/user-password-history/user-password-history';
3
+ import { YN } from '../../../../src/enum/yn.enum';
4
+
5
+ // User and LoginUser import each other; this test exercises only User's expiry policy.
6
+ jest.mock('../../../../src/components/login-user/login-user', () => ({
7
+ LoginUser: class {},
8
+ }));
9
+
10
+ describe('User.getPasswordExpiryStatus', () => {
11
+ const originalExpiryMonths = process.env.PASSWORD_EXPIRY_MONTHS;
12
+ const originalWarningDays = process.env.PASSWORD_EXPIRY_WARNING_DAYS;
13
+
14
+ const user = (password: string | null) =>
15
+ Object.assign(Object.create(User.prototype), {
16
+ ObjectId: '123',
17
+ _Password: password,
18
+ _NeedToChangePasswordYN: YN.No,
19
+ }) as User;
20
+
21
+ beforeEach(() => {
22
+ process.env.PASSWORD_EXPIRY_MONTHS = '1';
23
+ process.env.PASSWORD_EXPIRY_WARNING_DAYS = '14';
24
+ jest.useFakeTimers().setSystemTime(new Date('2026-02-20T12:00:00Z'));
25
+ });
26
+
27
+ afterEach(() => {
28
+ jest.restoreAllMocks();
29
+ jest.useRealTimers();
30
+ if (originalExpiryMonths === undefined)
31
+ delete process.env.PASSWORD_EXPIRY_MONTHS;
32
+ else process.env.PASSWORD_EXPIRY_MONTHS = originalExpiryMonths;
33
+ if (originalWarningDays === undefined)
34
+ delete process.env.PASSWORD_EXPIRY_WARNING_DAYS;
35
+ else process.env.PASSWORD_EXPIRY_WARNING_DAYS = originalWarningDays;
36
+ });
37
+
38
+ it('disables expiry when the period is zero', async () => {
39
+ process.env.PASSWORD_EXPIRY_MONTHS = '0';
40
+ const lookup = jest.spyOn(UserPasswordHistory, 'getLatestByUserId');
41
+
42
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
43
+ expired: false,
44
+ expiringSoon: false,
45
+ expiresInDays: null,
46
+ });
47
+ expect(lookup).not.toHaveBeenCalled();
48
+ });
49
+
50
+ it('skips an account without a local password', async () => {
51
+ const lookup = jest.spyOn(UserPasswordHistory, 'getLatestByUserId');
52
+
53
+ expect(await user(null).getPasswordExpiryStatus()).toEqual({
54
+ expired: false,
55
+ expiringSoon: false,
56
+ expiresInDays: null,
57
+ });
58
+ expect(lookup).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it('expires an account without password history', async () => {
62
+ jest
63
+ .spyOn(UserPasswordHistory, 'getLatestByUserId')
64
+ .mockResolvedValue(null);
65
+
66
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
67
+ expired: true,
68
+ expiringSoon: false,
69
+ expiresInDays: null,
70
+ });
71
+ });
72
+
73
+ it('warns before expiry and clamps month-end dates', async () => {
74
+ jest.spyOn(UserPasswordHistory, 'getLatestByUserId').mockResolvedValue({
75
+ CreatedAt: new Date('2026-01-31T12:00:00Z'),
76
+ } as UserPasswordHistory);
77
+
78
+ expect(await user('hash').getPasswordExpiryStatus()).toEqual({
79
+ expired: false,
80
+ expiringSoon: true,
81
+ expiresInDays: 8,
82
+ });
83
+
84
+ jest.setSystemTime(new Date('2026-02-28T12:00:00Z'));
85
+ expect((await user('hash').getPasswordExpiryStatus()).expired).toBe(true);
86
+ });
87
+ });
@@ -54,11 +54,15 @@ describe('GroupReportingUser', () => {
54
54
  });
55
55
  }));
56
56
  it('should throw ClassError when GroupReportingUser is not found', () => __awaiter(void 0, void 0, void 0, function* () {
57
- jest.spyOn(group_reporting_user_repository_1.GroupReportingUserRepository.prototype, 'findByPk').mockResolvedValue(null);
57
+ jest
58
+ .spyOn(group_reporting_user_repository_1.GroupReportingUserRepository.prototype, 'findByPk')
59
+ .mockResolvedValue(null);
58
60
  yield expect(group_reporting_user_1.GroupReportingUser.init(mockDbTransaction, '1')).rejects.toThrow(general_1.ClassError);
59
61
  }));
60
62
  it('should throw ClassError when failed to initialize GroupReportingUser', () => __awaiter(void 0, void 0, void 0, function* () {
61
- jest.spyOn(group_reporting_user_repository_1.GroupReportingUserRepository.prototype, 'findByPk').mockRejectedValue(new Error());
63
+ jest
64
+ .spyOn(group_reporting_user_repository_1.GroupReportingUserRepository.prototype, 'findByPk')
65
+ .mockRejectedValue(new Error());
62
66
  yield expect(group_reporting_user_1.GroupReportingUser.init(mockDbTransaction, '1')).rejects.toThrow(Error);
63
67
  }));
64
68
  });
@@ -1 +1 @@
1
- {"version":3,"file":"group-reporting-user.spec.js","sourceRoot":"","sources":["../../../../../__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"group-reporting-user.spec.js","sourceRoot":"","sources":["../../../../../__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts"],"names":[],"mappings":"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"}