@flusys/nestjs-auth 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/controllers/authentication.controller.spec.js +219 -0
- package/cjs/controllers/branch.controller.spec.js +134 -0
- package/cjs/controllers/company-selection.controller.spec.js +84 -0
- package/cjs/controllers/company.controller.spec.js +131 -0
- package/cjs/controllers/email-verification.controller.spec.js +142 -0
- package/cjs/controllers/user-permission.controller.spec.js +138 -0
- package/cjs/controllers/user.controller.spec.js +204 -0
- package/cjs/docs/auth-swagger.config.spec.js +130 -0
- package/cjs/entities/company-branch.entity.js +2 -3
- package/cjs/entities/index.spec.js +73 -0
- package/cjs/helpers/auth-response.mapper.js +46 -0
- package/cjs/helpers/auth-response.mapper.spec.js +139 -0
- package/cjs/helpers/index.js +19 -0
- package/cjs/helpers/token.helper.js +58 -0
- package/cjs/helpers/token.helper.spec.js +86 -0
- package/cjs/index.js +1 -0
- package/cjs/interceptors/clear-token.interceptor.spec.js +107 -0
- package/cjs/interceptors/set-token.interceptor.spec.js +165 -0
- package/cjs/modules/auth.module.spec.js +243 -0
- package/cjs/services/auth-config.service.spec.js +97 -0
- package/cjs/services/auth-datasource.provider.spec.js +161 -0
- package/cjs/services/auth-email.service.spec.js +404 -0
- package/cjs/services/authentication.service.js +37 -109
- package/cjs/services/authentication.service.spec.js +904 -0
- package/cjs/services/branch.service.js +30 -3
- package/cjs/services/branch.service.spec.js +154 -0
- package/cjs/services/company-selection-session.service.spec.js +90 -0
- package/cjs/services/company.service.js +34 -3
- package/cjs/services/company.service.spec.js +113 -0
- package/cjs/services/user-permission.service.js +35 -4
- package/cjs/services/user-permission.service.spec.js +202 -0
- package/cjs/services/user.service.spec.js +704 -0
- package/cjs/strategies/jwt.strategy.js +1 -1
- package/cjs/strategies/jwt.strategy.spec.js +166 -0
- package/cjs/validators/password.validator.spec.js +161 -0
- package/entities/company-branch.entity.d.ts +1 -1
- package/entities/pending-registration.entity.d.ts +2 -7
- package/fesm/controllers/authentication.controller.spec.js +215 -0
- package/fesm/controllers/branch.controller.spec.js +130 -0
- package/fesm/controllers/company-selection.controller.spec.js +80 -0
- package/fesm/controllers/company.controller.spec.js +127 -0
- package/fesm/controllers/email-verification.controller.spec.js +138 -0
- package/fesm/controllers/user-permission.controller.spec.js +134 -0
- package/fesm/controllers/user.controller.spec.js +200 -0
- package/fesm/docs/auth-swagger.config.spec.js +126 -0
- package/fesm/entities/company-branch.entity.js +2 -3
- package/fesm/entities/index.spec.js +69 -0
- package/fesm/helpers/auth-response.mapper.js +25 -0
- package/fesm/helpers/auth-response.mapper.spec.js +135 -0
- package/fesm/helpers/index.js +2 -0
- package/fesm/helpers/token.helper.js +40 -0
- package/fesm/helpers/token.helper.spec.js +82 -0
- package/fesm/index.js +1 -0
- package/fesm/interceptors/clear-token.interceptor.spec.js +103 -0
- package/fesm/interceptors/set-token.interceptor.spec.js +161 -0
- package/fesm/interfaces/authentication.interface.js +1 -1
- package/fesm/modules/auth.module.spec.js +239 -0
- package/fesm/services/auth-config.service.spec.js +93 -0
- package/fesm/services/auth-datasource.provider.spec.js +157 -0
- package/fesm/services/auth-email.service.spec.js +359 -0
- package/fesm/services/authentication.service.js +37 -109
- package/fesm/services/authentication.service.spec.js +859 -0
- package/fesm/services/branch.service.js +32 -5
- package/fesm/services/branch.service.spec.js +150 -0
- package/fesm/services/company-selection-session.service.spec.js +86 -0
- package/fesm/services/company.service.js +36 -5
- package/fesm/services/company.service.spec.js +109 -0
- package/fesm/services/user-permission.service.js +36 -5
- package/fesm/services/user-permission.service.spec.js +198 -0
- package/fesm/services/user.service.spec.js +659 -0
- package/fesm/strategies/jwt.strategy.js +1 -1
- package/fesm/strategies/jwt.strategy.spec.js +162 -0
- package/fesm/validators/password.validator.spec.js +157 -0
- package/helpers/auth-response.mapper.d.ts +22 -0
- package/helpers/index.d.ts +2 -0
- package/helpers/token.helper.d.ts +8 -0
- package/index.d.ts +1 -0
- package/interfaces/authentication.interface.d.ts +7 -0
- package/package.json +8 -3
- package/services/authentication.service.d.ts +1 -8
- package/services/branch.service.d.ts +7 -5
- package/services/company.service.d.ts +7 -4
- package/services/user-permission.service.d.ts +4 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _common = require("@nestjs/common");
|
|
6
|
+
const _emailverificationcontroller = require("./email-verification.controller");
|
|
7
|
+
function buildRequest() {
|
|
8
|
+
return {
|
|
9
|
+
headers: {},
|
|
10
|
+
get: jest.fn((h)=>h === 'host' ? 'app.example.com' : undefined),
|
|
11
|
+
protocol: 'https'
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
describe('EmailVerificationController', ()=>{
|
|
15
|
+
let controller;
|
|
16
|
+
let mockAuthService;
|
|
17
|
+
let mockAuthEmailService;
|
|
18
|
+
let mockAuthConfig;
|
|
19
|
+
let mockDataSourceProvider;
|
|
20
|
+
beforeEach(()=>{
|
|
21
|
+
mockAuthService = {
|
|
22
|
+
createUserFromPendingRegistration: jest.fn()
|
|
23
|
+
};
|
|
24
|
+
mockAuthEmailService = {
|
|
25
|
+
forgotPassword: jest.fn(),
|
|
26
|
+
resetPassword: jest.fn(),
|
|
27
|
+
resendVerificationEmail: jest.fn(),
|
|
28
|
+
verifyEmail: jest.fn()
|
|
29
|
+
};
|
|
30
|
+
mockAuthConfig = {};
|
|
31
|
+
mockDataSourceProvider = {
|
|
32
|
+
isEmailVerificationEnabled: jest.fn().mockReturnValue(true)
|
|
33
|
+
};
|
|
34
|
+
controller = new _emailverificationcontroller.EmailVerificationController(mockAuthService, mockAuthEmailService, mockAuthConfig, mockDataSourceProvider);
|
|
35
|
+
});
|
|
36
|
+
describe('when email verification is disabled for the tenant', ()=>{
|
|
37
|
+
beforeEach(()=>{
|
|
38
|
+
mockDataSourceProvider.isEmailVerificationEnabled.mockReturnValue(false);
|
|
39
|
+
});
|
|
40
|
+
it('rejects forgotPassword', async ()=>{
|
|
41
|
+
await expect(controller.forgotPassword(buildRequest(), {
|
|
42
|
+
email: 'a@example.com'
|
|
43
|
+
})).rejects.toThrow(_common.BadRequestException);
|
|
44
|
+
});
|
|
45
|
+
it('rejects resetPassword', async ()=>{
|
|
46
|
+
await expect(controller.resetPassword({
|
|
47
|
+
token: 't',
|
|
48
|
+
newPassword: 'New1!aaaa'
|
|
49
|
+
})).rejects.toThrow(_common.BadRequestException);
|
|
50
|
+
});
|
|
51
|
+
it('rejects resendVerification', async ()=>{
|
|
52
|
+
await expect(controller.resendVerification(buildRequest(), {
|
|
53
|
+
email: 'a@example.com'
|
|
54
|
+
})).rejects.toThrow(_common.BadRequestException);
|
|
55
|
+
});
|
|
56
|
+
it('rejects verifyEmail', async ()=>{
|
|
57
|
+
await expect(controller.verifyEmail({
|
|
58
|
+
token: 't'
|
|
59
|
+
})).rejects.toThrow(_common.BadRequestException);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('forgotPassword', ()=>{
|
|
63
|
+
it('delegates to authEmailService with a built frontend reset URL', async ()=>{
|
|
64
|
+
mockAuthEmailService.forgotPassword.mockResolvedValue({
|
|
65
|
+
success: true,
|
|
66
|
+
message: 'sent',
|
|
67
|
+
messageKey: 'auth.password.reset.email.sent'
|
|
68
|
+
});
|
|
69
|
+
const result = await controller.forgotPassword(buildRequest(), {
|
|
70
|
+
email: 'a@example.com'
|
|
71
|
+
});
|
|
72
|
+
expect(mockAuthEmailService.forgotPassword).toHaveBeenCalledWith('a@example.com', expect.any(String));
|
|
73
|
+
expect(result.success).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('resetPassword', ()=>{
|
|
77
|
+
it('delegates to authEmailService.resetPassword', async ()=>{
|
|
78
|
+
mockAuthEmailService.resetPassword.mockResolvedValue({
|
|
79
|
+
success: true,
|
|
80
|
+
message: 'done',
|
|
81
|
+
messageKey: 'auth.password.reset.success'
|
|
82
|
+
});
|
|
83
|
+
const result = await controller.resetPassword({
|
|
84
|
+
token: 'tok',
|
|
85
|
+
newPassword: 'New1!aaaa'
|
|
86
|
+
});
|
|
87
|
+
expect(mockAuthEmailService.resetPassword).toHaveBeenCalledWith('tok', 'New1!aaaa');
|
|
88
|
+
expect(result.success).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('resendVerification', ()=>{
|
|
92
|
+
it('delegates to authEmailService.resendVerificationEmail', async ()=>{
|
|
93
|
+
mockAuthEmailService.resendVerificationEmail.mockResolvedValue({
|
|
94
|
+
success: true,
|
|
95
|
+
message: 'sent',
|
|
96
|
+
messageKey: 'email.verification.resend.success'
|
|
97
|
+
});
|
|
98
|
+
await controller.resendVerification(buildRequest(), {
|
|
99
|
+
email: 'a@example.com'
|
|
100
|
+
});
|
|
101
|
+
expect(mockAuthEmailService.resendVerificationEmail).toHaveBeenCalledWith('a@example.com', expect.any(String));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('verifyEmail', ()=>{
|
|
105
|
+
it('completes registration when the token resolves a pending registration', async ()=>{
|
|
106
|
+
mockAuthEmailService.verifyEmail.mockResolvedValue({
|
|
107
|
+
type: 'pending',
|
|
108
|
+
pending: {
|
|
109
|
+
id: 'pending-1'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
mockAuthService.createUserFromPendingRegistration.mockResolvedValue({
|
|
113
|
+
accessToken: 'a'
|
|
114
|
+
});
|
|
115
|
+
const result = await controller.verifyEmail({
|
|
116
|
+
token: 'tok'
|
|
117
|
+
});
|
|
118
|
+
expect(mockAuthService.createUserFromPendingRegistration).toHaveBeenCalledWith({
|
|
119
|
+
id: 'pending-1'
|
|
120
|
+
});
|
|
121
|
+
expect(result.data).toEqual({
|
|
122
|
+
accessToken: 'a'
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it('returns the verification message when the token verifies an existing user token', async ()=>{
|
|
126
|
+
mockAuthEmailService.verifyEmail.mockResolvedValue({
|
|
127
|
+
type: 'verified',
|
|
128
|
+
message: 'Email has been verified successfully.',
|
|
129
|
+
messageKey: 'email.verification.success'
|
|
130
|
+
});
|
|
131
|
+
const result = await controller.verifyEmail({
|
|
132
|
+
token: 'tok'
|
|
133
|
+
});
|
|
134
|
+
expect(result).toEqual({
|
|
135
|
+
success: true,
|
|
136
|
+
message: 'Email has been verified successfully.',
|
|
137
|
+
messageKey: 'email.verification.success'
|
|
138
|
+
});
|
|
139
|
+
expect(mockAuthService.createUserFromPendingRegistration).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _userpermissioncontroller = require("./user-permission.controller");
|
|
6
|
+
const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
|
|
7
|
+
describe('UserPermissionController', ()=>{
|
|
8
|
+
let controller;
|
|
9
|
+
let mockPermissionService;
|
|
10
|
+
beforeEach(()=>{
|
|
11
|
+
mockPermissionService = {
|
|
12
|
+
findUserCompanies: jest.fn(),
|
|
13
|
+
findUserBranches: jest.fn(),
|
|
14
|
+
assignPermission: jest.fn(),
|
|
15
|
+
revokePermission: jest.fn()
|
|
16
|
+
};
|
|
17
|
+
controller = new _userpermissioncontroller.UserPermissionController(mockPermissionService);
|
|
18
|
+
});
|
|
19
|
+
describe('assignUserCompany', ()=>{
|
|
20
|
+
it('assigns and revokes company permissions in a single batch', async ()=>{
|
|
21
|
+
const result = await controller.assignUserCompany({
|
|
22
|
+
userId: 'user-1',
|
|
23
|
+
items: [
|
|
24
|
+
{
|
|
25
|
+
targetId: 'company-1',
|
|
26
|
+
isAdd: true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
targetId: 'company-2',
|
|
30
|
+
isAdd: false
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
expect(mockPermissionService.assignPermission).toHaveBeenCalledWith('user-1', 'company-1', _usercompanypermissionentity.PermissionType.COMPANY);
|
|
35
|
+
expect(mockPermissionService.revokePermission).toHaveBeenCalledWith('user-1', 'company-2', _usercompanypermissionentity.PermissionType.COMPANY);
|
|
36
|
+
expect(result.messageVariables).toEqual({
|
|
37
|
+
added: 1,
|
|
38
|
+
removed: 1
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('getUserCompanies', ()=>{
|
|
43
|
+
it('maps permission entities to the response DTO shape', async ()=>{
|
|
44
|
+
mockPermissionService.findUserCompanies.mockResolvedValue([
|
|
45
|
+
{
|
|
46
|
+
id: 'perm-1',
|
|
47
|
+
userId: 'user-1',
|
|
48
|
+
targetId: 'company-1',
|
|
49
|
+
company: {
|
|
50
|
+
id: 'company-1',
|
|
51
|
+
name: 'Acme',
|
|
52
|
+
slug: 'acme'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
const result = await controller.getUserCompanies('user-1');
|
|
57
|
+
expect(result.data).toEqual([
|
|
58
|
+
{
|
|
59
|
+
id: 'perm-1',
|
|
60
|
+
userId: 'user-1',
|
|
61
|
+
companyId: 'company-1',
|
|
62
|
+
companyName: 'Acme',
|
|
63
|
+
companySlug: 'acme'
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
it('falls back to targetId and empty strings when the company relation is missing', async ()=>{
|
|
68
|
+
mockPermissionService.findUserCompanies.mockResolvedValue([
|
|
69
|
+
{
|
|
70
|
+
id: 'perm-1',
|
|
71
|
+
userId: 'user-1',
|
|
72
|
+
targetId: 'company-1',
|
|
73
|
+
company: undefined
|
|
74
|
+
}
|
|
75
|
+
]);
|
|
76
|
+
const result = await controller.getUserCompanies('user-1');
|
|
77
|
+
expect(result.data[0]).toEqual({
|
|
78
|
+
id: 'perm-1',
|
|
79
|
+
userId: 'user-1',
|
|
80
|
+
companyId: 'company-1',
|
|
81
|
+
companyName: '',
|
|
82
|
+
companySlug: ''
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('assignUserBranch', ()=>{
|
|
87
|
+
it('assigns and revokes branch permissions in a single batch', async ()=>{
|
|
88
|
+
const result = await controller.assignUserBranch({
|
|
89
|
+
userId: 'user-1',
|
|
90
|
+
items: [
|
|
91
|
+
{
|
|
92
|
+
targetId: 'branch-1',
|
|
93
|
+
isAdd: true
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
expect(mockPermissionService.assignPermission).toHaveBeenCalledWith('user-1', 'branch-1', _usercompanypermissionentity.PermissionType.BRANCH);
|
|
98
|
+
expect(result.messageVariables).toEqual({
|
|
99
|
+
added: 1,
|
|
100
|
+
removed: 0
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('getUserBranches', ()=>{
|
|
105
|
+
it('maps permission entities to the response DTO shape', async ()=>{
|
|
106
|
+
mockPermissionService.findUserBranches.mockResolvedValue([
|
|
107
|
+
{
|
|
108
|
+
id: 'perm-1',
|
|
109
|
+
userId: 'user-1',
|
|
110
|
+
targetId: 'branch-1',
|
|
111
|
+
branch: {
|
|
112
|
+
id: 'branch-1',
|
|
113
|
+
name: 'Main',
|
|
114
|
+
slug: 'main',
|
|
115
|
+
companyId: 'company-1'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]);
|
|
119
|
+
const result = await controller.getUserBranches('user-1', 'company-1');
|
|
120
|
+
expect(mockPermissionService.findUserBranches).toHaveBeenCalledWith('user-1', 'company-1');
|
|
121
|
+
expect(result.data).toEqual([
|
|
122
|
+
{
|
|
123
|
+
id: 'perm-1',
|
|
124
|
+
userId: 'user-1',
|
|
125
|
+
branchId: 'branch-1',
|
|
126
|
+
branchName: 'Main',
|
|
127
|
+
branchSlug: 'main',
|
|
128
|
+
companyId: 'company-1'
|
|
129
|
+
}
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
it('supports an optional companyId filter', async ()=>{
|
|
133
|
+
mockPermissionService.findUserBranches.mockResolvedValue([]);
|
|
134
|
+
await controller.getUserBranches('user-1');
|
|
135
|
+
expect(mockPermissionService.findUserBranches).toHaveBeenCalledWith('user-1', undefined);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _loggedusermock = require("@test-utils/mocks/logged-user.mock");
|
|
6
|
+
const _usercontroller = require("./user.controller");
|
|
7
|
+
function buildUser(overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
id: 'user-1',
|
|
10
|
+
name: 'Test User',
|
|
11
|
+
email: 'test@example.com',
|
|
12
|
+
phone: null,
|
|
13
|
+
isActive: true,
|
|
14
|
+
readOnly: false,
|
|
15
|
+
createdAt: new Date(),
|
|
16
|
+
updatedAt: new Date(),
|
|
17
|
+
...overrides
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
describe('UserController', ()=>{
|
|
21
|
+
let controller;
|
|
22
|
+
let mockUserService;
|
|
23
|
+
beforeEach(()=>{
|
|
24
|
+
mockUserService = {
|
|
25
|
+
insert: jest.fn(),
|
|
26
|
+
insertMany: jest.fn(),
|
|
27
|
+
findById: jest.fn(),
|
|
28
|
+
findByIds: jest.fn(),
|
|
29
|
+
update: jest.fn(),
|
|
30
|
+
updateMany: jest.fn(),
|
|
31
|
+
bulkUpsert: jest.fn(),
|
|
32
|
+
getAll: jest.fn(),
|
|
33
|
+
delete: jest.fn(),
|
|
34
|
+
getProfileWithExtras: jest.fn(),
|
|
35
|
+
verifyEmail: jest.fn(),
|
|
36
|
+
verifyPhone: jest.fn(),
|
|
37
|
+
updateStatus: jest.fn(),
|
|
38
|
+
profile: jest.fn(),
|
|
39
|
+
getProfileSections: jest.fn(),
|
|
40
|
+
getProfileSectionData: jest.fn(),
|
|
41
|
+
updateProfileSection: jest.fn(),
|
|
42
|
+
getProfileCompletion: jest.fn()
|
|
43
|
+
};
|
|
44
|
+
controller = new _usercontroller.UserController(mockUserService);
|
|
45
|
+
});
|
|
46
|
+
describe('insert', ()=>{
|
|
47
|
+
it('creates a user and returns the mapped response', async ()=>{
|
|
48
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
49
|
+
mockUserService.insert.mockResolvedValue(buildUser());
|
|
50
|
+
const result = await controller.insert({
|
|
51
|
+
name: 'Test User',
|
|
52
|
+
email: 'test@example.com',
|
|
53
|
+
password: 'Password1!'
|
|
54
|
+
}, user);
|
|
55
|
+
expect(mockUserService.insert).toHaveBeenCalledWith({
|
|
56
|
+
name: 'Test User',
|
|
57
|
+
email: 'test@example.com',
|
|
58
|
+
password: 'Password1!'
|
|
59
|
+
}, user);
|
|
60
|
+
expect(result.success).toBe(true);
|
|
61
|
+
expect(result.data).toEqual(expect.objectContaining({
|
|
62
|
+
id: 'user-1'
|
|
63
|
+
}));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('getAll', ()=>{
|
|
67
|
+
it('paginates users and maps the response list', async ()=>{
|
|
68
|
+
mockUserService.getAll.mockResolvedValue({
|
|
69
|
+
data: [
|
|
70
|
+
buildUser()
|
|
71
|
+
],
|
|
72
|
+
total: 1
|
|
73
|
+
});
|
|
74
|
+
const result = await controller.getAll({}, (0, _loggedusermock.buildMockUser)(), '');
|
|
75
|
+
expect(mockUserService.getAll).toHaveBeenCalledWith('', {}, (0, _loggedusermock.buildMockUser)());
|
|
76
|
+
expect(result.data).toHaveLength(1);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('getProfileWithExtras', ()=>{
|
|
80
|
+
it('returns the enriched profile payload', async ()=>{
|
|
81
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
82
|
+
mockUserService.getProfileWithExtras.mockResolvedValue({
|
|
83
|
+
...buildUser(),
|
|
84
|
+
completionPercentage: 80
|
|
85
|
+
});
|
|
86
|
+
const result = await controller.getProfileWithExtras('user-1', user);
|
|
87
|
+
expect(mockUserService.getProfileWithExtras).toHaveBeenCalledWith('user-1', user);
|
|
88
|
+
expect(result).toEqual({
|
|
89
|
+
success: true,
|
|
90
|
+
message: 'Profile extras retrieved successfully',
|
|
91
|
+
messageKey: expect.any(String),
|
|
92
|
+
data: expect.objectContaining({
|
|
93
|
+
completionPercentage: 80
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('verifyEmail', ()=>{
|
|
99
|
+
it('verifies the email and returns a success message', async ()=>{
|
|
100
|
+
mockUserService.verifyEmail.mockResolvedValue(undefined);
|
|
101
|
+
const result = await controller.verifyEmail('user-1');
|
|
102
|
+
expect(mockUserService.verifyEmail).toHaveBeenCalledWith('user-1');
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('verifyPhone', ()=>{
|
|
107
|
+
it('verifies the phone and returns a success message', async ()=>{
|
|
108
|
+
mockUserService.verifyPhone.mockResolvedValue(undefined);
|
|
109
|
+
const result = await controller.verifyPhone('user-1');
|
|
110
|
+
expect(mockUserService.verifyPhone).toHaveBeenCalledWith('user-1');
|
|
111
|
+
expect(result.success).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('updateStatus', ()=>{
|
|
115
|
+
it('updates the active status for the given user', async ()=>{
|
|
116
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
117
|
+
mockUserService.updateStatus.mockResolvedValue(undefined);
|
|
118
|
+
const result = await controller.updateStatus('user-1', false, user);
|
|
119
|
+
expect(mockUserService.updateStatus).toHaveBeenCalledWith('user-1', false, user);
|
|
120
|
+
expect(result.success).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('profile', ()=>{
|
|
124
|
+
it('updates the current user profile and returns the data', async ()=>{
|
|
125
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
126
|
+
mockUserService.profile.mockResolvedValue('user-1');
|
|
127
|
+
const result = await controller.profile({
|
|
128
|
+
id: 'user-1',
|
|
129
|
+
name: 'New Name'
|
|
130
|
+
}, user);
|
|
131
|
+
expect(mockUserService.profile).toHaveBeenCalledWith({
|
|
132
|
+
id: 'user-1',
|
|
133
|
+
name: 'New Name'
|
|
134
|
+
}, user);
|
|
135
|
+
expect(result).toEqual({
|
|
136
|
+
success: true,
|
|
137
|
+
message: 'Profile updated successfully',
|
|
138
|
+
messageKey: expect.any(String),
|
|
139
|
+
data: 'user-1'
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe('getProfileSections', ()=>{
|
|
144
|
+
it('returns the profile section definitions', async ()=>{
|
|
145
|
+
mockUserService.getProfileSections.mockResolvedValue([
|
|
146
|
+
{
|
|
147
|
+
id: 's1',
|
|
148
|
+
name: 'Basic',
|
|
149
|
+
order: 1
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
const result = await controller.getProfileSections();
|
|
153
|
+
expect(result.data).toEqual([
|
|
154
|
+
{
|
|
155
|
+
id: 's1',
|
|
156
|
+
name: 'Basic',
|
|
157
|
+
order: 1
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('getProfileSectionData', ()=>{
|
|
163
|
+
it('returns data for the requested section', async ()=>{
|
|
164
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
165
|
+
mockUserService.getProfileSectionData.mockResolvedValue({
|
|
166
|
+
sectionId: 's1',
|
|
167
|
+
data: {
|
|
168
|
+
foo: 'bar'
|
|
169
|
+
},
|
|
170
|
+
isComplete: true
|
|
171
|
+
});
|
|
172
|
+
const result = await controller.getProfileSectionData('user-1', 's1', user);
|
|
173
|
+
expect(mockUserService.getProfileSectionData).toHaveBeenCalledWith('user-1', 's1', user);
|
|
174
|
+
expect(result.data).toEqual(expect.objectContaining({
|
|
175
|
+
sectionId: 's1',
|
|
176
|
+
isComplete: true
|
|
177
|
+
}));
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe('updateProfileSection', ()=>{
|
|
181
|
+
it('updates the requested section', async ()=>{
|
|
182
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
183
|
+
mockUserService.updateProfileSection.mockResolvedValue(undefined);
|
|
184
|
+
const result = await controller.updateProfileSection('user-1', 's1', {
|
|
185
|
+
foo: 'bar'
|
|
186
|
+
}, user);
|
|
187
|
+
expect(mockUserService.updateProfileSection).toHaveBeenCalledWith('user-1', 's1', {
|
|
188
|
+
foo: 'bar'
|
|
189
|
+
}, user);
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('getProfileCompletion', ()=>{
|
|
194
|
+
it('returns the completion percentage', async ()=>{
|
|
195
|
+
const user = (0, _loggedusermock.buildMockUser)();
|
|
196
|
+
mockUserService.getProfileCompletion.mockResolvedValue(42);
|
|
197
|
+
const result = await controller.getProfileCompletion('user-1', user);
|
|
198
|
+
expect(mockUserService.getProfileCompletion).toHaveBeenCalledWith('user-1', user);
|
|
199
|
+
expect(result.data).toEqual({
|
|
200
|
+
completionPercentage: 42
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
const _authswaggerconfig = require("./auth-swagger.config");
|
|
6
|
+
describe('authSwaggerConfig', ()=>{
|
|
7
|
+
it('should default to both features enabled and single-database mode when no config is given', ()=>{
|
|
8
|
+
const config = (0, _authswaggerconfig.authSwaggerConfig)();
|
|
9
|
+
expect(config.title).toBe('Auth API');
|
|
10
|
+
expect(config.path).toBe('api/docs/auth');
|
|
11
|
+
expect(config.bearerAuth).toBe(true);
|
|
12
|
+
expect(config.excludeTags).toEqual([]);
|
|
13
|
+
expect(config.excludeSchemaProperties).toBeUndefined();
|
|
14
|
+
expect(config.excludeExamples).toBeUndefined();
|
|
15
|
+
expect(config.excludePaths).toBeUndefined();
|
|
16
|
+
expect(config.description).not.toContain('Multi-Tenant Mode');
|
|
17
|
+
});
|
|
18
|
+
it('should exclude company-related tags, schema properties, and examples when the company feature is off', ()=>{
|
|
19
|
+
const config = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
20
|
+
enableCompanyFeature: false,
|
|
21
|
+
databaseMode: 'single'
|
|
22
|
+
});
|
|
23
|
+
expect(config.excludeTags).toEqual(expect.arrayContaining([
|
|
24
|
+
'Companies',
|
|
25
|
+
'Branches',
|
|
26
|
+
'User Permissions',
|
|
27
|
+
'Company Selection'
|
|
28
|
+
]));
|
|
29
|
+
expect(config.excludeSchemaProperties).toEqual(expect.arrayContaining([
|
|
30
|
+
expect.objectContaining({
|
|
31
|
+
schemaName: 'RegistrationDto'
|
|
32
|
+
})
|
|
33
|
+
]));
|
|
34
|
+
expect(config.excludeExamples).toEqual(expect.arrayContaining([
|
|
35
|
+
expect.objectContaining({
|
|
36
|
+
pathPattern: '/auth/login',
|
|
37
|
+
examples: [
|
|
38
|
+
'loginSuccessWithCompany',
|
|
39
|
+
'companySelection'
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
]));
|
|
43
|
+
// Email verification is still enabled by default, so its paths must not be excluded
|
|
44
|
+
expect(config.excludePaths).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
it('should exclude email-verification paths, schema properties, and examples when disabled', ()=>{
|
|
47
|
+
const config = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
48
|
+
enableEmailVerification: false
|
|
49
|
+
});
|
|
50
|
+
expect(config.excludePaths).toEqual([
|
|
51
|
+
'/auth/forgot-password',
|
|
52
|
+
'/auth/reset-password',
|
|
53
|
+
'/auth/resend-verification',
|
|
54
|
+
'/auth/verify-email'
|
|
55
|
+
]);
|
|
56
|
+
expect(config.excludeSchemaProperties).toEqual(expect.arrayContaining([
|
|
57
|
+
expect.objectContaining({
|
|
58
|
+
schemaName: 'LoginResponseRequiresEmailVerificationDto'
|
|
59
|
+
})
|
|
60
|
+
]));
|
|
61
|
+
expect(config.excludeExamples).toEqual(expect.arrayContaining([
|
|
62
|
+
expect.objectContaining({
|
|
63
|
+
pathPattern: '/auth/login',
|
|
64
|
+
examples: [
|
|
65
|
+
'emailVerification'
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]));
|
|
69
|
+
// Company feature is still enabled by default, so its tags must not be excluded
|
|
70
|
+
expect(config.excludeTags).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
it('should merge exclusions from both features when both are disabled', ()=>{
|
|
73
|
+
const config = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
74
|
+
enableCompanyFeature: false,
|
|
75
|
+
enableEmailVerification: false
|
|
76
|
+
});
|
|
77
|
+
expect(config.excludeTags?.length).toBeGreaterThan(0);
|
|
78
|
+
expect(config.excludePaths?.length).toBeGreaterThan(0);
|
|
79
|
+
// Both feature's schema exclusions should be present together
|
|
80
|
+
const schemaNames = config.excludeSchemaProperties?.map((e)=>e.schemaName) ?? [];
|
|
81
|
+
expect(schemaNames).toContain('RegistrationDto');
|
|
82
|
+
expect(schemaNames).toContain('LoginResponseRequiresEmailVerificationDto');
|
|
83
|
+
// Both feature's example exclusions merge onto the same /auth/login path
|
|
84
|
+
const loginExclusions = config.excludeExamples?.filter((e)=>e.pathPattern === '/auth/login');
|
|
85
|
+
expect(loginExclusions).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
it('should mention multi-tenant mode in the description only for multi-tenant database mode', ()=>{
|
|
88
|
+
const multiTenant = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
89
|
+
databaseMode: 'multi-tenant'
|
|
90
|
+
});
|
|
91
|
+
const single = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
92
|
+
databaseMode: 'single'
|
|
93
|
+
});
|
|
94
|
+
expect(multiTenant.description).toContain('Multi-Tenant Mode');
|
|
95
|
+
expect(single.description).not.toContain('Multi-Tenant Mode');
|
|
96
|
+
});
|
|
97
|
+
it('should describe the multi-company login flow only when the company feature is enabled', ()=>{
|
|
98
|
+
const withCompany = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
99
|
+
enableCompanyFeature: true
|
|
100
|
+
});
|
|
101
|
+
const withoutCompany = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
102
|
+
enableCompanyFeature: false
|
|
103
|
+
});
|
|
104
|
+
expect(withCompany.description).toContain('Multi-Company Login');
|
|
105
|
+
expect(withCompany.description).toContain('/auth/switch-company');
|
|
106
|
+
expect(withoutCompany.description).not.toContain('Multi-Company Login');
|
|
107
|
+
expect(withoutCompany.description).not.toContain('/auth/switch-company');
|
|
108
|
+
});
|
|
109
|
+
it('should describe email verification endpoints only when enabled', ()=>{
|
|
110
|
+
const withVerification = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
111
|
+
enableEmailVerification: true
|
|
112
|
+
});
|
|
113
|
+
const withoutVerification = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
114
|
+
enableEmailVerification: false
|
|
115
|
+
});
|
|
116
|
+
expect(withVerification.description).toContain('/auth/forgot-password');
|
|
117
|
+
expect(withoutVerification.description).not.toContain('### Email Verification Endpoints');
|
|
118
|
+
});
|
|
119
|
+
it('should list company administration endpoints only when the company feature is enabled', ()=>{
|
|
120
|
+
const withCompany = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
121
|
+
enableCompanyFeature: true
|
|
122
|
+
});
|
|
123
|
+
const withoutCompany = (0, _authswaggerconfig.authSwaggerConfig)({
|
|
124
|
+
enableCompanyFeature: false
|
|
125
|
+
});
|
|
126
|
+
expect(withCompany.description).toContain('/administration/company/*');
|
|
127
|
+
expect(withoutCompany.description).not.toContain('/administration/company/*');
|
|
128
|
+
expect(withoutCompany.description).toContain('/administration/users/*');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -139,10 +139,9 @@ _ts_decorate([
|
|
|
139
139
|
_ts_decorate([
|
|
140
140
|
(0, _typeorm.Column)({
|
|
141
141
|
name: 'company_id',
|
|
142
|
-
type: 'uuid'
|
|
143
|
-
nullable: true
|
|
142
|
+
type: 'uuid'
|
|
144
143
|
}),
|
|
145
|
-
_ts_metadata("design:type",
|
|
144
|
+
_ts_metadata("design:type", String)
|
|
146
145
|
], CompanyBranch.prototype, "companyId", void 0);
|
|
147
146
|
_ts_decorate([
|
|
148
147
|
(0, _typeorm.ManyToOne)(()=>_companyentity.Company, (company)=>company.branches, {
|