@tomei/sso 0.65.0 → 0.65.1-test.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +234 -13
- package/.husky/commit-msg +0 -0
- package/.husky/pre-commit +0 -0
- package/__tests__/unit/components/api-key/api-key.spec.ts +201 -0
- package/__tests__/unit/components/group/group.spec.ts +6 -0
- package/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.ts +9 -1
- package/__tests__/unit/components/login-user/login.spec.ts +1199 -5
- package/__tests__/unit/components/system/system.spec.ts +1 -0
- package/__tests__/unit/components/user-password-history/user-password-history.spec.ts +165 -0
- package/__tests__/unit/components/user-reporting-hierarchy/user-reporting-hierarchy.spec.ts +233 -0
- package/coverage/cobertura-coverage.xml +6837 -0
- package/coverage/test-report.xml +161 -0
- package/dist/__tests__/unit/components/api-key/api-key.spec.d.ts +1 -0
- package/dist/__tests__/unit/components/api-key/api-key.spec.js +158 -0
- package/dist/__tests__/unit/components/api-key/api-key.spec.js.map +1 -0
- package/dist/__tests__/unit/components/group/group.spec.js +4 -0
- package/dist/__tests__/unit/components/group/group.spec.js.map +1 -1
- package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js +9 -1
- package/dist/__tests__/unit/components/group-reporting-user/group-reporting-user.spec.js.map +1 -1
- package/dist/__tests__/unit/components/login-user/login.spec.js +703 -0
- package/dist/__tests__/unit/components/login-user/login.spec.js.map +1 -1
- package/dist/__tests__/unit/components/system/system.spec.js +1 -0
- package/dist/__tests__/unit/components/system/system.spec.js.map +1 -1
- package/dist/__tests__/unit/components/user-password-history/user-password-history.spec.d.ts +1 -0
- package/dist/__tests__/unit/components/user-password-history/user-password-history.spec.js +138 -0
- package/dist/__tests__/unit/components/user-password-history/user-password-history.spec.js.map +1 -0
- package/dist/__tests__/unit/components/user-reporting-hierarchy/user-reporting-hierarchy.spec.d.ts +1 -0
- package/dist/__tests__/unit/components/user-reporting-hierarchy/user-reporting-hierarchy.spec.js +182 -0
- package/dist/__tests__/unit/components/user-reporting-hierarchy/user-reporting-hierarchy.spec.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jest.config.js +2 -0
- package/migrations/20250805085707-add-bulk-approval-code-to-sso-user.js +29 -0
- package/package.json +2 -2
|
@@ -243,6 +243,7 @@ describe('System', () => {
|
|
|
243
243
|
const result = await System.findAll(dbTransaction, loginUser);
|
|
244
244
|
|
|
245
245
|
expect(findAllWithPaginationSpy).toHaveBeenCalledWith({
|
|
246
|
+
distinct: true,
|
|
246
247
|
where: {},
|
|
247
248
|
order: [['CreatedAt', 'DESC']],
|
|
248
249
|
transaction: dbTransaction,
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { UserPasswordHistory } from '../../../../src/components/user-password-history/user-password-history';
|
|
2
|
+
import { UserPasswordHistoryRepository } from '../../../../src/components/user-password-history/user-password-history.repository';
|
|
3
|
+
import { PasswordHashService } from '../../../../src/components/password-hash';
|
|
4
|
+
import { ComponentConfig } from '@tomei/config';
|
|
5
|
+
import { ClassError } from '@tomei/general';
|
|
6
|
+
|
|
7
|
+
describe('UserPasswordHistory', () => {
|
|
8
|
+
const historyData = {
|
|
9
|
+
HistoryId: '1',
|
|
10
|
+
UserId: 1,
|
|
11
|
+
PasswordHash: '$argon2id$v=19$m=65536,t=3,p=4$hash1',
|
|
12
|
+
CreatedAt: new Date('2024-01-01'),
|
|
13
|
+
get: (opts?: any) => ({
|
|
14
|
+
HistoryId: '1',
|
|
15
|
+
UserId: 1,
|
|
16
|
+
PasswordHash: '$argon2id$v=19$m=65536,t=3,p=4$hash1',
|
|
17
|
+
CreatedAt: new Date('2024-01-01'),
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('init', () => {
|
|
26
|
+
it('should return new instance when no historyId is provided', async () => {
|
|
27
|
+
const result = await UserPasswordHistory.init();
|
|
28
|
+
expect(result).toBeInstanceOf(UserPasswordHistory);
|
|
29
|
+
expect(result.HistoryId).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should initialize UserPasswordHistory with valid historyId', async () => {
|
|
33
|
+
jest
|
|
34
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findByPk')
|
|
35
|
+
.mockResolvedValueOnce(historyData as any);
|
|
36
|
+
|
|
37
|
+
const result = await UserPasswordHistory.init(1);
|
|
38
|
+
|
|
39
|
+
expect(result).toBeInstanceOf(UserPasswordHistory);
|
|
40
|
+
expect(result.UserId).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw ClassError when historyId is not found', async () => {
|
|
44
|
+
jest
|
|
45
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findByPk')
|
|
46
|
+
.mockResolvedValueOnce(null);
|
|
47
|
+
|
|
48
|
+
await expect(UserPasswordHistory.init(999)).rejects.toThrow(
|
|
49
|
+
'UserPasswordHistory not found',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('validate', () => {
|
|
55
|
+
const passwordHashService = new PasswordHashService();
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
jest
|
|
59
|
+
.spyOn(ComponentConfig, 'getComponentConfigValue')
|
|
60
|
+
.mockReturnValue(3 as any);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return null when no password history exists', async () => {
|
|
64
|
+
jest
|
|
65
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
66
|
+
.mockResolvedValueOnce([] as any);
|
|
67
|
+
|
|
68
|
+
const result = await UserPasswordHistory.validate(
|
|
69
|
+
null,
|
|
70
|
+
1,
|
|
71
|
+
'NewPassword1!',
|
|
72
|
+
passwordHashService,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(result).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should throw ClassError when password matches history', async () => {
|
|
79
|
+
jest
|
|
80
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
81
|
+
.mockResolvedValueOnce([historyData] as any);
|
|
82
|
+
jest
|
|
83
|
+
.spyOn(passwordHashService, 'verify')
|
|
84
|
+
.mockResolvedValueOnce(true as any);
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
UserPasswordHistory.validate(null, 1, 'OldPassword1!', passwordHashService),
|
|
88
|
+
).rejects.toThrow(ClassError);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not throw when password does not match any history', async () => {
|
|
92
|
+
jest
|
|
93
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
94
|
+
.mockResolvedValueOnce([historyData] as any);
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(passwordHashService, 'verify')
|
|
97
|
+
.mockResolvedValueOnce(false as any);
|
|
98
|
+
|
|
99
|
+
await expect(
|
|
100
|
+
UserPasswordHistory.validate(null, 1, 'NewPassword1!', passwordHashService),
|
|
101
|
+
).resolves.not.toThrow();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('create', () => {
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
jest
|
|
108
|
+
.spyOn(ComponentConfig, 'getComponentConfigValue')
|
|
109
|
+
.mockReturnValue(3 as any);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should create a new password history record', async () => {
|
|
113
|
+
const createSpy = jest
|
|
114
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'create')
|
|
115
|
+
.mockResolvedValueOnce(historyData as any);
|
|
116
|
+
jest
|
|
117
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
118
|
+
.mockResolvedValueOnce([historyData] as any);
|
|
119
|
+
|
|
120
|
+
await UserPasswordHistory.create(null, 1, 'hashedPassword');
|
|
121
|
+
|
|
122
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should purge oldest records when history exceeds policy limit', async () => {
|
|
126
|
+
const oldRecords = [
|
|
127
|
+
{ HistoryId: '1', CreatedAt: new Date('2024-01-01') },
|
|
128
|
+
{ HistoryId: '2', CreatedAt: new Date('2024-02-01') },
|
|
129
|
+
{ HistoryId: '3', CreatedAt: new Date('2024-03-01') },
|
|
130
|
+
{ HistoryId: '4', CreatedAt: new Date('2024-04-01') },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
jest
|
|
134
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'create')
|
|
135
|
+
.mockResolvedValueOnce(historyData as any);
|
|
136
|
+
jest
|
|
137
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
138
|
+
.mockResolvedValueOnce(oldRecords as any);
|
|
139
|
+
const destroySpy = jest
|
|
140
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'destroyMultiple')
|
|
141
|
+
.mockResolvedValueOnce(undefined as any);
|
|
142
|
+
|
|
143
|
+
await UserPasswordHistory.create(null, 1, 'hashedPassword');
|
|
144
|
+
|
|
145
|
+
expect(destroySpy).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(destroySpy).toHaveBeenCalledWith(['4'], null);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should not purge when history is within policy limit', async () => {
|
|
150
|
+
jest
|
|
151
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'create')
|
|
152
|
+
.mockResolvedValueOnce(historyData as any);
|
|
153
|
+
jest
|
|
154
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'findAll')
|
|
155
|
+
.mockResolvedValueOnce([historyData] as any);
|
|
156
|
+
const destroySpy = jest
|
|
157
|
+
.spyOn(UserPasswordHistoryRepository.prototype, 'destroyMultiple')
|
|
158
|
+
.mockResolvedValueOnce(undefined as any);
|
|
159
|
+
|
|
160
|
+
await UserPasswordHistory.create(null, 1, 'hashedPassword');
|
|
161
|
+
|
|
162
|
+
expect(destroySpy).not.toHaveBeenCalled();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// LoginUser must be imported first to prime the module cache and avoid circular dependency
|
|
2
|
+
// (user-reporting-hierarchy → user.ts → login-user.ts → user.ts circular)
|
|
3
|
+
import { LoginUser } from '../../../../src/components/login-user/login-user';
|
|
4
|
+
import { User } from '../../../../src/components/login-user/user';
|
|
5
|
+
import { UserReportingHierarchy } from '../../../../src/components/user-reporting-hierarchy/user-reporting-hierarchy';
|
|
6
|
+
import { UserReportingHierarchyRepository } from '../../../../src/components/user-reporting-hierarchy/user-reporting-hierarchy.repository';
|
|
7
|
+
import { ApplicationConfig } from '@tomei/config';
|
|
8
|
+
import { Activity } from '@tomei/activity-history';
|
|
9
|
+
import { ClassError } from '@tomei/general';
|
|
10
|
+
|
|
11
|
+
describe('UserReportingHierarchy', () => {
|
|
12
|
+
const loginUser = new (LoginUser.prototype as any).constructor();
|
|
13
|
+
loginUser.ObjectId = '1';
|
|
14
|
+
loginUser.UserId = 1;
|
|
15
|
+
|
|
16
|
+
const hierarchyData = {
|
|
17
|
+
UserReportingHierarchyId: 1,
|
|
18
|
+
ReportingUserId: 10,
|
|
19
|
+
UserId: 20,
|
|
20
|
+
Rank: 1,
|
|
21
|
+
Status: 'Active',
|
|
22
|
+
CreatedById: 1,
|
|
23
|
+
CreatedAt: new Date('2024-01-01'),
|
|
24
|
+
UpdatedById: 1,
|
|
25
|
+
UpdatedAt: new Date('2024-01-01'),
|
|
26
|
+
get: (opts?: any) => ({
|
|
27
|
+
UserReportingHierarchyId: 1,
|
|
28
|
+
ReportingUserId: 10,
|
|
29
|
+
UserId: 20,
|
|
30
|
+
Rank: 1,
|
|
31
|
+
Status: 'Active',
|
|
32
|
+
CreatedById: 1,
|
|
33
|
+
CreatedAt: new Date('2024-01-01'),
|
|
34
|
+
UpdatedById: 1,
|
|
35
|
+
UpdatedAt: new Date('2024-01-01'),
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest
|
|
45
|
+
.spyOn(ApplicationConfig, 'getComponentConfigValue')
|
|
46
|
+
.mockReturnValue('TST' as any);
|
|
47
|
+
jest
|
|
48
|
+
.spyOn(Activity.prototype, 'create')
|
|
49
|
+
.mockResolvedValue(undefined as any);
|
|
50
|
+
jest
|
|
51
|
+
.spyOn(LoginUser.prototype, 'checkPrivileges')
|
|
52
|
+
.mockResolvedValue(true as any);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('init', () => {
|
|
56
|
+
it('should return a new instance when no id is provided', async () => {
|
|
57
|
+
const result = await UserReportingHierarchy.init();
|
|
58
|
+
expect(result).toBeInstanceOf(UserReportingHierarchy);
|
|
59
|
+
expect(result.UserReportingHierarchyId).toBeNaN();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should initialize with valid id', async () => {
|
|
63
|
+
jest
|
|
64
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findByPk')
|
|
65
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
66
|
+
|
|
67
|
+
const result = await UserReportingHierarchy.init(1);
|
|
68
|
+
|
|
69
|
+
expect(result).toBeInstanceOf(UserReportingHierarchy);
|
|
70
|
+
expect(result.ReportingUserId).toBe(10);
|
|
71
|
+
expect(result.UserId).toBe(20);
|
|
72
|
+
expect(result.Rank).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw ClassError when id is not found', async () => {
|
|
76
|
+
jest
|
|
77
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findByPk')
|
|
78
|
+
.mockResolvedValueOnce(null);
|
|
79
|
+
|
|
80
|
+
await expect(UserReportingHierarchy.init(999)).rejects.toThrow(
|
|
81
|
+
'UserReportingHierarchy not found',
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('createUserReportingHierarchy', () => {
|
|
87
|
+
it('should throw when user lacks USER_REPORTING_HIERARCHY_CREATE privilege', async () => {
|
|
88
|
+
jest
|
|
89
|
+
.spyOn(LoginUser.prototype, 'checkPrivileges')
|
|
90
|
+
.mockResolvedValueOnce(false as any);
|
|
91
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
92
|
+
|
|
93
|
+
const hierarchy = await UserReportingHierarchy.init();
|
|
94
|
+
await expect(
|
|
95
|
+
hierarchy.createUserReportingHierarchy(loginUser, null, 10, 20, 1, 'Active'),
|
|
96
|
+
).rejects.toThrow('User does not have the required privileges');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should throw when relationship already exists', async () => {
|
|
100
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
101
|
+
jest
|
|
102
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findOne')
|
|
103
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
104
|
+
|
|
105
|
+
const hierarchy = await UserReportingHierarchy.init();
|
|
106
|
+
await expect(
|
|
107
|
+
hierarchy.createUserReportingHierarchy(loginUser, null, 10, 20, 1, 'Active'),
|
|
108
|
+
).rejects.toThrow('Relationship already exists');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should throw when rank already exists', async () => {
|
|
112
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
113
|
+
jest
|
|
114
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findOne')
|
|
115
|
+
.mockResolvedValueOnce(null) // no existing relationship
|
|
116
|
+
.mockResolvedValueOnce(hierarchyData as any); // rank exists
|
|
117
|
+
|
|
118
|
+
const hierarchy = await UserReportingHierarchy.init();
|
|
119
|
+
await expect(
|
|
120
|
+
hierarchy.createUserReportingHierarchy(loginUser, null, 10, 20, 1, 'Active'),
|
|
121
|
+
).rejects.toThrow('Rank already exists');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should throw when previous rank is not yet assigned (rank > 1)', async () => {
|
|
125
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
126
|
+
jest
|
|
127
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findOne')
|
|
128
|
+
.mockResolvedValueOnce(null) // no existing relationship
|
|
129
|
+
.mockResolvedValueOnce(null) // rank 2 not taken
|
|
130
|
+
.mockResolvedValueOnce(null); // rank 1 (predecessor) not found
|
|
131
|
+
|
|
132
|
+
const hierarchy = await UserReportingHierarchy.init();
|
|
133
|
+
await expect(
|
|
134
|
+
hierarchy.createUserReportingHierarchy(loginUser, null, 10, 20, 2, 'Active'),
|
|
135
|
+
).rejects.toThrow('Rank before the new rank is not yet assigned to the user');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should create hierarchy successfully', async () => {
|
|
139
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
140
|
+
jest
|
|
141
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findOne')
|
|
142
|
+
.mockResolvedValueOnce(null) // no existing relationship
|
|
143
|
+
.mockResolvedValueOnce(null); // rank 1 not taken
|
|
144
|
+
const createSpy = jest
|
|
145
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'create')
|
|
146
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
147
|
+
|
|
148
|
+
const hierarchy = await UserReportingHierarchy.init();
|
|
149
|
+
const result = await hierarchy.createUserReportingHierarchy(
|
|
150
|
+
loginUser,
|
|
151
|
+
null,
|
|
152
|
+
10,
|
|
153
|
+
20,
|
|
154
|
+
1,
|
|
155
|
+
'Active',
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
159
|
+
expect(result).toBeInstanceOf(UserReportingHierarchy);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('updateUserReportingHierarchy', () => {
|
|
164
|
+
it('should throw when user lacks USER_REPORTING_HIERARCHY_UPDATE privilege', async () => {
|
|
165
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
166
|
+
jest
|
|
167
|
+
.spyOn(LoginUser.prototype, 'checkPrivileges')
|
|
168
|
+
.mockResolvedValueOnce(false as any);
|
|
169
|
+
|
|
170
|
+
jest
|
|
171
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findByPk')
|
|
172
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
173
|
+
|
|
174
|
+
const hierarchy = await UserReportingHierarchy.init(1);
|
|
175
|
+
await expect(
|
|
176
|
+
hierarchy.updateUserReportingHierarchy(loginUser, null, 10, 20, 1, 'Active'),
|
|
177
|
+
).rejects.toThrow('User does not have the required privileges');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should update successfully', async () => {
|
|
181
|
+
jest.spyOn(User, 'init').mockResolvedValue(loginUser as any);
|
|
182
|
+
jest
|
|
183
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findByPk')
|
|
184
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
185
|
+
jest
|
|
186
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findOne')
|
|
187
|
+
.mockResolvedValueOnce(null) // no duplicate relationship
|
|
188
|
+
.mockResolvedValueOnce(null); // rank 1 not taken by other
|
|
189
|
+
const updateSpy = jest
|
|
190
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'update')
|
|
191
|
+
.mockResolvedValueOnce(undefined as any);
|
|
192
|
+
|
|
193
|
+
const hierarchy = await UserReportingHierarchy.init(1);
|
|
194
|
+
const result = await hierarchy.updateUserReportingHierarchy(
|
|
195
|
+
loginUser,
|
|
196
|
+
null,
|
|
197
|
+
10,
|
|
198
|
+
20,
|
|
199
|
+
1,
|
|
200
|
+
'Active',
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
204
|
+
expect(result).toBeInstanceOf(UserReportingHierarchy);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('removeUserReportingHierarchy', () => {
|
|
209
|
+
it('should throw when user lacks USER_REPORTING_HIERARCHY_REMOVE privilege', async () => {
|
|
210
|
+
jest
|
|
211
|
+
.spyOn(LoginUser.prototype, 'checkPrivileges')
|
|
212
|
+
.mockResolvedValueOnce(false as any);
|
|
213
|
+
|
|
214
|
+
await expect(
|
|
215
|
+
UserReportingHierarchy.removeUserReportingHierarchy(loginUser, null, 1),
|
|
216
|
+
).rejects.toThrow('Insufficient privileges to remove reporting hierarchy');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should remove hierarchy successfully', async () => {
|
|
220
|
+
jest
|
|
221
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'findByPk')
|
|
222
|
+
.mockResolvedValueOnce(hierarchyData as any);
|
|
223
|
+
const destroySpy = jest
|
|
224
|
+
.spyOn(UserReportingHierarchyRepository.prototype, 'destroy')
|
|
225
|
+
.mockResolvedValueOnce(undefined as any);
|
|
226
|
+
|
|
227
|
+
await UserReportingHierarchy.removeUserReportingHierarchy(loginUser, null, 1);
|
|
228
|
+
|
|
229
|
+
expect(destroySpy).toHaveBeenCalledTimes(1);
|
|
230
|
+
expect(destroySpy).toHaveBeenCalledWith(1, null);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|