@flusys/nestjs-auth 7.0.2 → 8.0.0-beta.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.
Files changed (237) hide show
  1. package/README.md +68 -1
  2. package/config/auth.constants.d.ts +2 -0
  3. package/config/event-actions.d.ts +2 -0
  4. package/config/index.d.ts +3 -3
  5. package/config/message-keys.d.ts +23 -0
  6. package/controllers/authentication.controller.d.ts +5 -5
  7. package/controllers/branch.controller.d.ts +2 -2
  8. package/controllers/company-selection.controller.d.ts +4 -4
  9. package/controllers/company.controller.d.ts +2 -2
  10. package/controllers/email-verification.controller.d.ts +6 -6
  11. package/controllers/index.d.ts +9 -7
  12. package/controllers/social-auth-config.controller.d.ts +22 -0
  13. package/controllers/social-auth.controller.d.ts +11 -0
  14. package/controllers/user-permission.controller.d.ts +5 -4
  15. package/controllers/user.controller.d.ts +7 -7
  16. package/docs/auth-swagger.config.d.ts +2 -2
  17. package/docs/index.d.ts +1 -1
  18. package/dtos/index.d.ts +6 -5
  19. package/dtos/social-auth.dto.d.ts +42 -0
  20. package/entities/company-branch.entity.d.ts +1 -1
  21. package/entities/company.entity.d.ts +1 -1
  22. package/entities/index.d.ts +15 -13
  23. package/entities/pending-registration.entity.d.ts +1 -1
  24. package/entities/social-auth-config.entity.d.ts +9 -0
  25. package/entities/user-company-permission.entity.d.ts +4 -4
  26. package/enums/index.d.ts +2 -1
  27. package/enums/social-provider-type.enum.d.ts +6 -0
  28. package/fesm/118.js +1230 -0
  29. package/fesm/120.js +2197 -0
  30. package/fesm/24.js +4389 -0
  31. package/fesm/370.js +1163 -0
  32. package/fesm/446.js +877 -0
  33. package/fesm/chunks/117.js +69 -0
  34. package/fesm/config/index.js +244 -3
  35. package/fesm/controllers/index.js +27 -7
  36. package/fesm/docs/index.js +479 -1
  37. package/fesm/dtos/index.js +123 -5
  38. package/fesm/entities/index.js +68 -19
  39. package/fesm/enums/index.js +53 -1
  40. package/fesm/helpers/index.js +115 -2
  41. package/fesm/index.js +1437 -12
  42. package/fesm/interceptors/index.js +508 -2
  43. package/fesm/interfaces/index.js +75 -7
  44. package/fesm/modules/index.js +546 -1
  45. package/fesm/runtime.js +134 -0
  46. package/fesm/services/index.js +67 -9
  47. package/helpers/auth-response.mapper.d.ts +3 -3
  48. package/helpers/index.d.ts +2 -2
  49. package/helpers/token.helper.d.ts +2 -2
  50. package/index.d.ts +13 -12
  51. package/interceptors/clear-token.interceptor.d.ts +2 -2
  52. package/interceptors/index.d.ts +2 -2
  53. package/interceptors/set-token.interceptor.d.ts +2 -2
  54. package/interfaces/auth-module-options.interface.d.ts +3 -3
  55. package/interfaces/authentication.interface.d.ts +3 -3
  56. package/interfaces/company-branch.interface.d.ts +2 -2
  57. package/interfaces/company.interface.d.ts +1 -1
  58. package/interfaces/index.d.ts +8 -7
  59. package/interfaces/social-auth.interface.d.ts +43 -0
  60. package/interfaces/user-enricher.interface.d.ts +3 -3
  61. package/modules/auth.module.d.ts +4 -4
  62. package/modules/index.d.ts +1 -1
  63. package/package.json +32 -39
  64. package/providers/facebook.social-provider.d.ts +10 -0
  65. package/providers/google.social-provider.d.ts +10 -0
  66. package/providers/index.d.ts +1 -0
  67. package/providers/linkedin.social-provider.d.ts +10 -0
  68. package/providers/microsoft.social-provider.d.ts +10 -0
  69. package/providers/oauth-http.util.d.ts +10 -0
  70. package/providers/social-provider.registry.d.ts +3 -0
  71. package/services/auth-config.service.d.ts +4 -3
  72. package/services/auth-datasource.provider.d.ts +4 -3
  73. package/services/auth-email.service.d.ts +3 -3
  74. package/services/authentication.service.d.ts +15 -11
  75. package/services/branch.service.d.ts +7 -7
  76. package/services/company.service.d.ts +6 -6
  77. package/services/index.d.ts +11 -9
  78. package/services/social-auth-config.service.d.ts +21 -0
  79. package/services/social-auth.service.d.ts +24 -0
  80. package/services/user-permission.service.d.ts +8 -4
  81. package/services/user.service.d.ts +7 -7
  82. package/strategies/jwt.strategy.d.ts +4 -4
  83. package/validators/password.validator.d.ts +1 -1
  84. package/cjs/config/auth.constants.js +0 -41
  85. package/cjs/config/event-actions.js +0 -45
  86. package/cjs/config/index.js +0 -20
  87. package/cjs/config/message-keys.js +0 -108
  88. package/cjs/controllers/authentication.controller.js +0 -427
  89. package/cjs/controllers/authentication.controller.spec.js +0 -219
  90. package/cjs/controllers/branch.controller.js +0 -102
  91. package/cjs/controllers/branch.controller.spec.js +0 -134
  92. package/cjs/controllers/company-selection.controller.js +0 -198
  93. package/cjs/controllers/company-selection.controller.spec.js +0 -84
  94. package/cjs/controllers/company.controller.js +0 -102
  95. package/cjs/controllers/company.controller.spec.js +0 -131
  96. package/cjs/controllers/email-verification.controller.js +0 -224
  97. package/cjs/controllers/email-verification.controller.spec.js +0 -142
  98. package/cjs/controllers/index.js +0 -24
  99. package/cjs/controllers/user-permission.controller.js +0 -223
  100. package/cjs/controllers/user-permission.controller.spec.js +0 -138
  101. package/cjs/controllers/user.controller.js +0 -438
  102. package/cjs/controllers/user.controller.spec.js +0 -204
  103. package/cjs/docs/auth-swagger.config.js +0 -453
  104. package/cjs/docs/auth-swagger.config.spec.js +0 -130
  105. package/cjs/docs/index.js +0 -18
  106. package/cjs/dtos/authentication.dto.js +0 -755
  107. package/cjs/dtos/company-branch.dto.js +0 -294
  108. package/cjs/dtos/company.dto.js +0 -298
  109. package/cjs/dtos/index.js +0 -22
  110. package/cjs/dtos/user-permission.dto.js +0 -358
  111. package/cjs/dtos/user.dto.js +0 -413
  112. package/cjs/entities/app-user.entity.js +0 -25
  113. package/cjs/entities/company-branch.entity.js +0 -163
  114. package/cjs/entities/company.entity.js +0 -146
  115. package/cjs/entities/index.js +0 -65
  116. package/cjs/entities/index.spec.js +0 -73
  117. package/cjs/entities/pending-registration.entity.js +0 -135
  118. package/cjs/entities/user-company-permission.entity.js +0 -153
  119. package/cjs/entities/user-token.entity.js +0 -120
  120. package/cjs/enums/index.js +0 -18
  121. package/cjs/enums/user-org-permission-type.enum.js +0 -15
  122. package/cjs/helpers/auth-response.mapper.js +0 -46
  123. package/cjs/helpers/auth-response.mapper.spec.js +0 -139
  124. package/cjs/helpers/index.js +0 -19
  125. package/cjs/helpers/token.helper.js +0 -58
  126. package/cjs/helpers/token.helper.spec.js +0 -86
  127. package/cjs/index.js +0 -29
  128. package/cjs/interceptors/clear-token.interceptor.js +0 -73
  129. package/cjs/interceptors/clear-token.interceptor.spec.js +0 -107
  130. package/cjs/interceptors/index.js +0 -19
  131. package/cjs/interceptors/set-token.interceptor.js +0 -107
  132. package/cjs/interceptors/set-token.interceptor.spec.js +0 -165
  133. package/cjs/interfaces/auth-email-provider.interface.js +0 -45
  134. package/cjs/interfaces/auth-module-options.interface.js +0 -4
  135. package/cjs/interfaces/authentication.interface.js +0 -4
  136. package/cjs/interfaces/company-branch.interface.js +0 -4
  137. package/cjs/interfaces/company.interface.js +0 -4
  138. package/cjs/interfaces/index.js +0 -24
  139. package/cjs/interfaces/user-enricher.interface.js +0 -4
  140. package/cjs/interfaces/user.interface.js +0 -4
  141. package/cjs/modules/auth.module.js +0 -202
  142. package/cjs/modules/auth.module.spec.js +0 -243
  143. package/cjs/modules/index.js +0 -18
  144. package/cjs/services/auth-config.service.js +0 -110
  145. package/cjs/services/auth-config.service.spec.js +0 -97
  146. package/cjs/services/auth-datasource.provider.js +0 -134
  147. package/cjs/services/auth-datasource.provider.spec.js +0 -161
  148. package/cjs/services/auth-email.service.js +0 -539
  149. package/cjs/services/auth-email.service.spec.js +0 -456
  150. package/cjs/services/authentication.service.js +0 -1173
  151. package/cjs/services/authentication.service.spec.js +0 -962
  152. package/cjs/services/branch.service.js +0 -153
  153. package/cjs/services/branch.service.spec.js +0 -169
  154. package/cjs/services/company-selection-session.service.js +0 -121
  155. package/cjs/services/company-selection-session.service.spec.js +0 -90
  156. package/cjs/services/company.service.js +0 -140
  157. package/cjs/services/company.service.spec.js +0 -127
  158. package/cjs/services/index.js +0 -26
  159. package/cjs/services/user-permission.service.js +0 -236
  160. package/cjs/services/user-permission.service.spec.js +0 -254
  161. package/cjs/services/user.service.js +0 -671
  162. package/cjs/services/user.service.spec.js +0 -718
  163. package/cjs/strategies/jwt.strategy.js +0 -140
  164. package/cjs/strategies/jwt.strategy.spec.js +0 -166
  165. package/cjs/validators/password.validator.js +0 -45
  166. package/cjs/validators/password.validator.spec.js +0 -161
  167. package/fesm/config/auth.constants.js +0 -11
  168. package/fesm/config/event-actions.js +0 -24
  169. package/fesm/config/message-keys.js +0 -78
  170. package/fesm/controllers/authentication.controller.js +0 -417
  171. package/fesm/controllers/authentication.controller.spec.js +0 -215
  172. package/fesm/controllers/branch.controller.js +0 -92
  173. package/fesm/controllers/branch.controller.spec.js +0 -130
  174. package/fesm/controllers/company-selection.controller.js +0 -188
  175. package/fesm/controllers/company-selection.controller.spec.js +0 -80
  176. package/fesm/controllers/company.controller.js +0 -92
  177. package/fesm/controllers/company.controller.spec.js +0 -127
  178. package/fesm/controllers/email-verification.controller.js +0 -214
  179. package/fesm/controllers/email-verification.controller.spec.js +0 -138
  180. package/fesm/controllers/user-permission.controller.js +0 -213
  181. package/fesm/controllers/user-permission.controller.spec.js +0 -134
  182. package/fesm/controllers/user.controller.js +0 -428
  183. package/fesm/controllers/user.controller.spec.js +0 -200
  184. package/fesm/docs/auth-swagger.config.js +0 -446
  185. package/fesm/docs/auth-swagger.config.spec.js +0 -126
  186. package/fesm/dtos/authentication.dto.js +0 -683
  187. package/fesm/dtos/company-branch.dto.js +0 -279
  188. package/fesm/dtos/company.dto.js +0 -283
  189. package/fesm/dtos/user-permission.dto.js +0 -339
  190. package/fesm/dtos/user.dto.js +0 -391
  191. package/fesm/entities/app-user.entity.js +0 -15
  192. package/fesm/entities/company-branch.entity.js +0 -153
  193. package/fesm/entities/company.entity.js +0 -136
  194. package/fesm/entities/index.spec.js +0 -69
  195. package/fesm/entities/pending-registration.entity.js +0 -125
  196. package/fesm/entities/user-company-permission.entity.js +0 -136
  197. package/fesm/entities/user-token.entity.js +0 -104
  198. package/fesm/enums/user-org-permission-type.enum.js +0 -5
  199. package/fesm/helpers/auth-response.mapper.js +0 -25
  200. package/fesm/helpers/auth-response.mapper.spec.js +0 -135
  201. package/fesm/helpers/token.helper.js +0 -40
  202. package/fesm/helpers/token.helper.spec.js +0 -82
  203. package/fesm/interceptors/clear-token.interceptor.js +0 -63
  204. package/fesm/interceptors/clear-token.interceptor.spec.js +0 -103
  205. package/fesm/interceptors/set-token.interceptor.js +0 -98
  206. package/fesm/interceptors/set-token.interceptor.spec.js +0 -161
  207. package/fesm/interfaces/auth-email-provider.interface.js +0 -38
  208. package/fesm/interfaces/auth-module-options.interface.js +0 -3
  209. package/fesm/interfaces/authentication.interface.js +0 -1
  210. package/fesm/interfaces/company-branch.interface.js +0 -1
  211. package/fesm/interfaces/company.interface.js +0 -1
  212. package/fesm/interfaces/user-enricher.interface.js +0 -4
  213. package/fesm/interfaces/user.interface.js +0 -1
  214. package/fesm/modules/auth.module.js +0 -192
  215. package/fesm/modules/auth.module.spec.js +0 -239
  216. package/fesm/services/auth-config.service.js +0 -100
  217. package/fesm/services/auth-config.service.spec.js +0 -93
  218. package/fesm/services/auth-datasource.provider.js +0 -83
  219. package/fesm/services/auth-datasource.provider.spec.js +0 -157
  220. package/fesm/services/auth-email.service.js +0 -488
  221. package/fesm/services/auth-email.service.spec.js +0 -411
  222. package/fesm/services/authentication.service.js +0 -1122
  223. package/fesm/services/authentication.service.spec.js +0 -917
  224. package/fesm/services/branch.service.js +0 -143
  225. package/fesm/services/branch.service.spec.js +0 -165
  226. package/fesm/services/company-selection-session.service.js +0 -111
  227. package/fesm/services/company-selection-session.service.spec.js +0 -86
  228. package/fesm/services/company.service.js +0 -130
  229. package/fesm/services/company.service.spec.js +0 -123
  230. package/fesm/services/user-permission.service.js +0 -226
  231. package/fesm/services/user-permission.service.spec.js +0 -250
  232. package/fesm/services/user.service.js +0 -620
  233. package/fesm/services/user.service.spec.js +0 -673
  234. package/fesm/strategies/jwt.strategy.js +0 -130
  235. package/fesm/strategies/jwt.strategy.spec.js +0 -162
  236. package/fesm/validators/password.validator.js +0 -48
  237. package/fesm/validators/password.validator.spec.js +0 -157
@@ -1,962 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- const _common = require("@nestjs/common");
6
- const _bcrypt = /*#__PURE__*/ _interop_require_wildcard(require("bcrypt"));
7
- const _repositorymock = require("@test-utils/mocks/repository.mock");
8
- const _classes = require("@flusys/nestjs-shared/classes");
9
- const _authenticationservice = require("./authentication.service");
10
- const _appuserentity = require("../entities/app-user.entity");
11
- const _companyentity = require("../entities/company.entity");
12
- const _companybranchentity = require("../entities/company-branch.entity");
13
- const _usercompanypermissionentity = require("../entities/user-company-permission.entity");
14
- function _getRequireWildcardCache(nodeInterop) {
15
- if (typeof WeakMap !== "function") return null;
16
- var cacheBabelInterop = new WeakMap();
17
- var cacheNodeInterop = new WeakMap();
18
- return (_getRequireWildcardCache = function(nodeInterop) {
19
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
20
- })(nodeInterop);
21
- }
22
- function _interop_require_wildcard(obj, nodeInterop) {
23
- if (!nodeInterop && obj && obj.__esModule) {
24
- return obj;
25
- }
26
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
27
- return {
28
- default: obj
29
- };
30
- }
31
- var cache = _getRequireWildcardCache(nodeInterop);
32
- if (cache && cache.has(obj)) {
33
- return cache.get(obj);
34
- }
35
- var newObj = {
36
- __proto__: null
37
- };
38
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
39
- for(var key in obj){
40
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
41
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
42
- if (desc && (desc.get || desc.set)) {
43
- Object.defineProperty(newObj, key, desc);
44
- } else {
45
- newObj[key] = obj[key];
46
- }
47
- }
48
- }
49
- newObj.default = obj;
50
- if (cache) {
51
- cache.set(obj, newObj);
52
- }
53
- return newObj;
54
- }
55
- jest.mock('bcrypt', ()=>({
56
- hash: jest.fn().mockResolvedValue('hashed-password'),
57
- compare: jest.fn().mockResolvedValue(true)
58
- }));
59
- function buildUser(overrides = {}) {
60
- return {
61
- id: 'user-1',
62
- email: 'user@example.com',
63
- name: 'Test User',
64
- phone: null,
65
- password: 'hashed',
66
- isActive: true,
67
- emailVerified: true,
68
- phoneVerified: false,
69
- profilePictureId: null,
70
- lastLoginAt: null,
71
- additionalFields: null,
72
- createdAt: new Date(),
73
- updatedAt: new Date(),
74
- deletedAt: null,
75
- createdById: null,
76
- updatedById: null,
77
- deletedById: null,
78
- ...overrides
79
- };
80
- }
81
- function buildCompany(overrides = {}) {
82
- return {
83
- id: 'company-1',
84
- name: 'Acme Corp',
85
- slug: 'acme-corp',
86
- logoId: null,
87
- isActive: true,
88
- readOnly: false,
89
- address: null,
90
- phone: null,
91
- email: null,
92
- website: null,
93
- serial: null,
94
- settings: null,
95
- additionalFields: null,
96
- branches: [],
97
- ...overrides
98
- };
99
- }
100
- function buildBranch(overrides = {}) {
101
- return {
102
- id: 'branch-1',
103
- name: 'Main Branch',
104
- slug: 'main-branch',
105
- companyId: 'company-1',
106
- logoId: null,
107
- isActive: true,
108
- readOnly: false,
109
- address: null,
110
- phone: null,
111
- email: null,
112
- serial: null,
113
- additionalFields: null,
114
- parentId: null,
115
- parent: null,
116
- children: [],
117
- ...overrides
118
- };
119
- }
120
- describe('AuthenticationService', ()=>{
121
- let userRepo;
122
- let companyRepo;
123
- let branchRepo;
124
- let permissionRepo;
125
- let mockJwtService;
126
- let mockAuthConfig;
127
- let mockCacheManager;
128
- let mockDataSourceProvider;
129
- let mockSessionService;
130
- let mockAuthEmailService;
131
- let mockUserEnricher;
132
- let companyFeatureEnabled;
133
- let emailVerificationEnabled;
134
- function buildService(overrides = {}) {
135
- return new _authenticationservice.AuthenticationService(mockJwtService, mockAuthConfig, 'cacheManager' in overrides ? overrides.cacheManager : mockCacheManager, 'dataSourceProvider' in overrides ? overrides.dataSourceProvider : mockDataSourceProvider, 'sessionService' in overrides ? overrides.sessionService : mockSessionService, 'authEmailService' in overrides ? overrides.authEmailService : mockAuthEmailService, 'userEnricher' in overrides ? overrides.userEnricher : mockUserEnricher, 'notificationAdapter' in overrides ? overrides.notificationAdapter : undefined);
136
- }
137
- beforeEach(()=>{
138
- jest.clearAllMocks();
139
- _bcrypt.compare.mockResolvedValue(true);
140
- _bcrypt.hash.mockResolvedValue('hashed-password');
141
- userRepo = (0, _repositorymock.createMockRepository)();
142
- companyRepo = (0, _repositorymock.createMockRepository)();
143
- branchRepo = (0, _repositorymock.createMockRepository)();
144
- permissionRepo = (0, _repositorymock.createMockRepository)();
145
- Object.assign(branchRepo.createQueryBuilder(), {
146
- leftJoinAndSelect: jest.fn().mockReturnThis()
147
- });
148
- companyFeatureEnabled = false;
149
- emailVerificationEnabled = false;
150
- mockJwtService = {
151
- sign: jest.fn().mockReturnValue('signed-token'),
152
- verify: jest.fn()
153
- };
154
- mockAuthConfig = {
155
- isCompanyFeatureEnabled: jest.fn().mockImplementation(()=>companyFeatureEnabled),
156
- isEmailVerificationEnabled: jest.fn().mockImplementation(()=>emailVerificationEnabled),
157
- getJwtSecret: jest.fn().mockReturnValue('jwt-secret'),
158
- getJwtExpiration: jest.fn().mockReturnValue('1h'),
159
- getRefreshTokenSecret: jest.fn().mockReturnValue('refresh-secret'),
160
- getRefreshTokenExpiration: jest.fn().mockReturnValue('7d')
161
- };
162
- mockCacheManager = {
163
- get: jest.fn(),
164
- set: jest.fn(),
165
- del: jest.fn()
166
- };
167
- mockDataSourceProvider = {
168
- isCompanyFeatureEnabled: jest.fn().mockImplementation(()=>companyFeatureEnabled),
169
- isEmailVerificationEnabled: jest.fn().mockImplementation(()=>emailVerificationEnabled),
170
- getRepository: jest.fn().mockImplementation(async (entity)=>{
171
- if (entity === _appuserentity.AppUser) return userRepo;
172
- if (entity === _companyentity.Company) return companyRepo;
173
- if (entity === _companybranchentity.CompanyBranch) return branchRepo;
174
- if (entity === _usercompanypermissionentity.UserCompanyPermission) return permissionRepo;
175
- throw new Error(`Unexpected entity: ${String(entity)}`);
176
- }),
177
- getDataSource: jest.fn()
178
- };
179
- mockSessionService = {
180
- createSession: jest.fn().mockResolvedValue('session-1'),
181
- consumeSession: jest.fn()
182
- };
183
- mockAuthEmailService = {
184
- isEmailEnabled: jest.fn().mockReturnValue(true),
185
- hasPendingRegistration: jest.fn().mockResolvedValue(false),
186
- createPendingRegistration: jest.fn()
187
- };
188
- mockUserEnricher = {
189
- onUserCreated: jest.fn()
190
- };
191
- });
192
- describe('login', ()=>{
193
- it('throws UnauthorizedException for a non-existent user (invalid credentials)', async ()=>{
194
- userRepo.findOne.mockResolvedValue(null);
195
- const service = buildService();
196
- await expect(service.login('missing@example.com', 'password')).rejects.toThrow(_common.UnauthorizedException);
197
- });
198
- it('throws UnauthorizedException when the password does not match', async ()=>{
199
- userRepo.findOne.mockResolvedValue(buildUser());
200
- _bcrypt.compare.mockResolvedValue(false);
201
- const service = buildService();
202
- await expect(service.login('user@example.com', 'wrong-password')).rejects.toThrow(_common.UnauthorizedException);
203
- });
204
- it('throws ForbiddenException when the account is deactivated', async ()=>{
205
- userRepo.findOne.mockResolvedValue(buildUser({
206
- isActive: false
207
- }));
208
- userRepo.save.mockResolvedValue(buildUser({
209
- isActive: false
210
- }));
211
- const service = buildService();
212
- await expect(service.login('user@example.com', 'password')).rejects.toThrow(_common.ForbiddenException);
213
- });
214
- it('returns requiresEmailVerification when a pending registration exists for the email', async ()=>{
215
- emailVerificationEnabled = true;
216
- mockAuthEmailService.hasPendingRegistration.mockResolvedValue(true);
217
- const service = buildService();
218
- const result = await service.login('pending@example.com', 'password');
219
- expect(result).toEqual(expect.objectContaining({
220
- requiresEmailVerification: true,
221
- email: 'pending@example.com'
222
- }));
223
- expect(userRepo.findOne).not.toHaveBeenCalled();
224
- });
225
- it('returns requiresEmailVerification when the user has not verified their email', async ()=>{
226
- emailVerificationEnabled = true;
227
- userRepo.findOne.mockResolvedValue(buildUser({
228
- emailVerified: false
229
- }));
230
- userRepo.save.mockResolvedValue(buildUser({
231
- emailVerified: false
232
- }));
233
- const service = buildService();
234
- const result = await service.login('user@example.com', 'password');
235
- expect(result).toEqual(expect.objectContaining({
236
- requiresEmailVerification: true
237
- }));
238
- });
239
- it('logs in successfully without the company feature enabled', async ()=>{
240
- userRepo.findOne.mockResolvedValue(buildUser());
241
- userRepo.save.mockResolvedValue(buildUser());
242
- const service = buildService();
243
- const result = await service.login('user@example.com', 'password');
244
- expect(result).toEqual(expect.objectContaining({
245
- accessToken: 'signed-token',
246
- refreshToken: 'signed-token',
247
- user: expect.objectContaining({
248
- id: 'user-1'
249
- })
250
- }));
251
- expect(userRepo.save).toHaveBeenCalledWith(expect.objectContaining({
252
- lastLoginAt: expect.any(Date)
253
- }));
254
- });
255
- it('auto-logs in with company/branch when the user has exactly one of each', async ()=>{
256
- companyFeatureEnabled = true;
257
- userRepo.findOne.mockResolvedValue(buildUser());
258
- userRepo.save.mockResolvedValue(buildUser());
259
- permissionRepo.find.mockImplementation(async ({ where })=>{
260
- if (where.permissionType === _usercompanypermissionentity.PermissionType.COMPANY) {
261
- return [
262
- {
263
- targetId: 'company-1'
264
- }
265
- ];
266
- }
267
- return [
268
- {
269
- targetId: 'branch-1'
270
- }
271
- ];
272
- });
273
- companyRepo.createQueryBuilder().getMany.mockResolvedValue([
274
- buildCompany()
275
- ]);
276
- branchRepo.createQueryBuilder().getMany.mockResolvedValue([
277
- buildBranch()
278
- ]);
279
- const service = buildService();
280
- const result = await service.login('user@example.com', 'password');
281
- expect(result).toEqual(expect.objectContaining({
282
- company: expect.objectContaining({
283
- id: 'company-1'
284
- }),
285
- branch: expect.objectContaining({
286
- id: 'branch-1'
287
- })
288
- }));
289
- });
290
- it('returns a company selection response when the user has multiple companies', async ()=>{
291
- companyFeatureEnabled = true;
292
- userRepo.findOne.mockResolvedValue(buildUser());
293
- userRepo.save.mockResolvedValue(buildUser());
294
- permissionRepo.find.mockImplementation(async ({ where })=>{
295
- if (where.permissionType === _usercompanypermissionentity.PermissionType.COMPANY) {
296
- return [
297
- {
298
- targetId: 'company-1'
299
- },
300
- {
301
- targetId: 'company-2'
302
- }
303
- ];
304
- }
305
- return [];
306
- });
307
- companyRepo.createQueryBuilder().getMany.mockResolvedValue([
308
- buildCompany(),
309
- buildCompany({
310
- id: 'company-2'
311
- })
312
- ]);
313
- const service = buildService();
314
- const result = await service.login('user@example.com', 'password');
315
- expect(result).toEqual(expect.objectContaining({
316
- requiresSelection: true,
317
- sessionId: 'session-1'
318
- }));
319
- expect(mockSessionService.createSession).toHaveBeenCalledWith('user-1');
320
- });
321
- it('falls back to a plain login response when company feature is enabled but the user has no company', async ()=>{
322
- companyFeatureEnabled = true;
323
- userRepo.findOne.mockResolvedValue(buildUser());
324
- userRepo.save.mockResolvedValue(buildUser());
325
- permissionRepo.find.mockResolvedValue([]);
326
- const service = buildService();
327
- const result = await service.login('user@example.com', 'password');
328
- expect(result).toEqual(expect.objectContaining({
329
- accessToken: 'signed-token'
330
- }));
331
- expect(result.company).toBeUndefined();
332
- });
333
- });
334
- describe('register', ()=>{
335
- it('throws BadRequestException when the email exists and company feature is disabled', async ()=>{
336
- userRepo.findOne.mockResolvedValue(buildUser());
337
- const service = buildService();
338
- await expect(service.register({
339
- email: 'user@example.com',
340
- name: 'Test',
341
- password: 'Password1!'
342
- })).rejects.toThrow(_common.BadRequestException);
343
- });
344
- it('routes existing users to company registration when the company feature is enabled', async ()=>{
345
- companyFeatureEnabled = true;
346
- userRepo.findOne.mockResolvedValue(buildUser());
347
- companyRepo.findOne.mockResolvedValue(null);
348
- companyRepo.createQueryBuilder().getMany.mockResolvedValue([]);
349
- companyRepo.create.mockReturnValue(buildCompany());
350
- companyRepo.save.mockResolvedValue(buildCompany());
351
- branchRepo.create.mockReturnValue(buildBranch());
352
- branchRepo.save.mockResolvedValue(buildBranch());
353
- permissionRepo.save.mockResolvedValue({});
354
- const service = buildService();
355
- const result = await service.register({
356
- email: 'user@example.com',
357
- name: 'Test',
358
- password: 'Password1!',
359
- newCompanyName: 'New Co'
360
- });
361
- expect(result).toEqual(expect.objectContaining({
362
- accessToken: expect.any(String)
363
- }));
364
- });
365
- it('sends a verification email and returns requiresEmailVerification when email verification is enabled', async ()=>{
366
- emailVerificationEnabled = true;
367
- userRepo.findOne.mockResolvedValue(null);
368
- mockAuthEmailService.createPendingRegistration.mockResolvedValue({
369
- success: true,
370
- message: 'sent',
371
- messageKey: 'email.verification.sent'
372
- });
373
- const service = buildService();
374
- const result = await service.register({
375
- email: 'new@example.com',
376
- name: 'New User',
377
- password: 'Password1!'
378
- }, 'https://app.test/verify');
379
- expect(result).toEqual(expect.objectContaining({
380
- requiresEmailVerification: true
381
- }));
382
- expect(mockAuthEmailService.createPendingRegistration).toHaveBeenCalled();
383
- });
384
- it('creates the user directly and hashes the password when email verification is not required', async ()=>{
385
- userRepo.findOne.mockResolvedValue(null);
386
- const queryRunner = {
387
- connect: jest.fn(),
388
- startTransaction: jest.fn(),
389
- commitTransaction: jest.fn(),
390
- rollbackTransaction: jest.fn(),
391
- release: jest.fn(),
392
- manager: {
393
- create: jest.fn().mockReturnValue(buildUser()),
394
- save: jest.fn().mockResolvedValue(buildUser())
395
- }
396
- };
397
- mockDataSourceProvider.getDataSource.mockResolvedValue({
398
- createQueryRunner: ()=>queryRunner
399
- });
400
- const service = buildService();
401
- const result = await service.register({
402
- email: 'new@example.com',
403
- name: 'New User',
404
- password: 'Password1!'
405
- });
406
- expect(_bcrypt.hash).toHaveBeenCalledWith('Password1!', 12);
407
- expect(queryRunner.commitTransaction).toHaveBeenCalled();
408
- expect(result).toEqual(expect.objectContaining({
409
- accessToken: expect.any(String)
410
- }));
411
- });
412
- it('rolls back the transaction and rethrows a BadRequestException on failure', async ()=>{
413
- userRepo.findOne.mockResolvedValue(null);
414
- const queryRunner = {
415
- connect: jest.fn(),
416
- startTransaction: jest.fn(),
417
- commitTransaction: jest.fn(),
418
- rollbackTransaction: jest.fn(),
419
- release: jest.fn(),
420
- manager: {
421
- create: jest.fn().mockReturnValue(buildUser()),
422
- save: jest.fn().mockRejectedValue(new Error('db error'))
423
- }
424
- };
425
- mockDataSourceProvider.getDataSource.mockResolvedValue({
426
- createQueryRunner: ()=>queryRunner
427
- });
428
- const service = buildService();
429
- await expect(service.register({
430
- email: 'new@example.com',
431
- name: 'New',
432
- password: 'Password1!'
433
- })).rejects.toThrow(_common.BadRequestException);
434
- expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
435
- expect(queryRunner.release).toHaveBeenCalled();
436
- });
437
- });
438
- describe('refreshToken', ()=>{
439
- it('throws UnauthorizedException when the token is malformed/invalid', async ()=>{
440
- mockJwtService.verify.mockImplementation(()=>{
441
- throw new Error('invalid signature');
442
- });
443
- const service = buildService();
444
- await expect(service.refreshToken('bad-token')).rejects.toThrow(_common.UnauthorizedException);
445
- });
446
- it('throws UnauthorizedException when the token has been revoked', async ()=>{
447
- mockJwtService.verify.mockReturnValue({
448
- id: 'user-1',
449
- iat: 1000
450
- });
451
- mockCacheManager.get.mockResolvedValue(2000);
452
- const service = buildService();
453
- await expect(service.refreshToken('revoked-token')).rejects.toThrow(_common.UnauthorizedException);
454
- });
455
- it('throws UnauthorizedException when the user no longer exists or is inactive', async ()=>{
456
- mockJwtService.verify.mockReturnValue({
457
- id: 'user-1',
458
- iat: 1000
459
- });
460
- mockCacheManager.get.mockResolvedValue(undefined);
461
- userRepo.findOne.mockResolvedValue(null);
462
- const service = buildService();
463
- await expect(service.refreshToken('token')).rejects.toThrow(_common.UnauthorizedException);
464
- });
465
- it('issues a new token pair for a valid non-company refresh token', async ()=>{
466
- mockJwtService.verify.mockReturnValue({
467
- id: 'user-1',
468
- iat: 1000
469
- });
470
- mockCacheManager.get.mockResolvedValue(undefined);
471
- userRepo.findOne.mockResolvedValue(buildUser());
472
- const service = buildService();
473
- const result = await service.refreshToken('token');
474
- expect(result).toEqual({
475
- accessToken: 'signed-token',
476
- refreshToken: 'signed-token'
477
- });
478
- });
479
- it('throws UnauthorizedException when the company from the payload no longer exists', async ()=>{
480
- mockJwtService.verify.mockReturnValue({
481
- id: 'user-1',
482
- iat: 1000,
483
- companyId: 'company-1'
484
- });
485
- mockCacheManager.get.mockResolvedValue(undefined);
486
- userRepo.findOne.mockResolvedValue(buildUser());
487
- companyFeatureEnabled = true;
488
- companyRepo.findOne.mockResolvedValue(null);
489
- const service = buildService();
490
- await expect(service.refreshToken('token')).rejects.toThrow(_common.UnauthorizedException);
491
- });
492
- it('throws UnauthorizedException when the user no longer has access to the payload company', async ()=>{
493
- mockJwtService.verify.mockReturnValue({
494
- id: 'user-1',
495
- iat: 1000,
496
- companyId: 'company-1'
497
- });
498
- mockCacheManager.get.mockResolvedValue(undefined);
499
- userRepo.findOne.mockResolvedValue(buildUser());
500
- companyFeatureEnabled = true;
501
- companyRepo.findOne.mockResolvedValue(buildCompany());
502
- permissionRepo.findOne.mockResolvedValue(null);
503
- const service = buildService();
504
- await expect(service.refreshToken('token')).rejects.toThrow(_common.UnauthorizedException);
505
- });
506
- it('validates branch access when a branchId is present in the payload', async ()=>{
507
- mockJwtService.verify.mockReturnValue({
508
- id: 'user-1',
509
- iat: 1000,
510
- companyId: 'company-1',
511
- branchId: 'branch-1'
512
- });
513
- mockCacheManager.get.mockResolvedValue(undefined);
514
- userRepo.findOne.mockResolvedValue(buildUser());
515
- companyFeatureEnabled = true;
516
- companyRepo.findOne.mockResolvedValue(buildCompany());
517
- permissionRepo.findOne.mockResolvedValue({});
518
- branchRepo.findOne.mockResolvedValue(null);
519
- const service = buildService();
520
- await expect(service.refreshToken('token')).rejects.toThrow(_common.UnauthorizedException);
521
- });
522
- it('issues a new token pair when company and branch access is valid', async ()=>{
523
- mockJwtService.verify.mockReturnValue({
524
- id: 'user-1',
525
- iat: 1000,
526
- companyId: 'company-1',
527
- branchId: 'branch-1'
528
- });
529
- mockCacheManager.get.mockResolvedValue(undefined);
530
- userRepo.findOne.mockResolvedValue(buildUser());
531
- companyFeatureEnabled = true;
532
- companyRepo.findOne.mockResolvedValue(buildCompany());
533
- permissionRepo.findOne.mockResolvedValue({});
534
- branchRepo.findOne.mockResolvedValue(buildBranch());
535
- const service = buildService();
536
- const result = await service.refreshToken('token');
537
- expect(result).toEqual({
538
- accessToken: 'signed-token',
539
- refreshToken: 'signed-token'
540
- });
541
- });
542
- });
543
- describe('changePassword', ()=>{
544
- it('throws UnauthorizedException when the user does not exist', async ()=>{
545
- userRepo.findOne.mockResolvedValue(null);
546
- const service = buildService();
547
- await expect(service.changePassword('user-1', 'old', 'NewPass1!')).rejects.toThrow(_common.UnauthorizedException);
548
- });
549
- it('throws UnauthorizedException when the current password is incorrect', async ()=>{
550
- userRepo.findOne.mockResolvedValue(buildUser());
551
- _bcrypt.compare.mockResolvedValue(false);
552
- const service = buildService();
553
- await expect(service.changePassword('user-1', 'wrong', 'NewPass1!')).rejects.toThrow(_common.UnauthorizedException);
554
- });
555
- it('hashes and saves the new password on success', async ()=>{
556
- userRepo.findOne.mockResolvedValue(buildUser());
557
- userRepo.save.mockResolvedValue(buildUser());
558
- const service = buildService();
559
- await service.changePassword('user-1', 'old', 'NewPass1!');
560
- expect(_bcrypt.hash).toHaveBeenCalledWith('NewPass1!', 12);
561
- expect(userRepo.save).toHaveBeenCalledWith(expect.objectContaining({
562
- password: 'hashed-password'
563
- }));
564
- });
565
- });
566
- describe('logout', ()=>{
567
- it('throws BadRequestException when the cache manager is unavailable', async ()=>{
568
- const service = buildService({
569
- cacheManager: undefined
570
- });
571
- await expect(service.logout('user-1')).rejects.toThrow(_common.BadRequestException);
572
- });
573
- it('records the revocation timestamp in the cache', async ()=>{
574
- const service = buildService();
575
- await service.logout('user-1');
576
- expect(mockCacheManager.set).toHaveBeenCalledWith('auth:revoked:user-1', expect.any(Number), expect.any(Number));
577
- });
578
- });
579
- describe('isTokenRevoked', ()=>{
580
- it('returns false when there is no cache manager', async ()=>{
581
- const service = buildService({
582
- cacheManager: undefined
583
- });
584
- expect(await service.isTokenRevoked('user-1', 1000)).toBe(false);
585
- });
586
- it('returns false when tokenIssuedAt is not provided', async ()=>{
587
- const service = buildService();
588
- expect(await service.isTokenRevoked('user-1')).toBe(false);
589
- });
590
- it('returns false when nothing has been revoked', async ()=>{
591
- mockCacheManager.get.mockResolvedValue(undefined);
592
- const service = buildService();
593
- expect(await service.isTokenRevoked('user-1', 1000)).toBe(false);
594
- });
595
- it('returns true when the token was issued before the revocation time', async ()=>{
596
- mockCacheManager.get.mockResolvedValue(2000);
597
- const service = buildService();
598
- expect(await service.isTokenRevoked('user-1', 1000)).toBe(true);
599
- });
600
- it('returns false when the token was issued after the revocation time', async ()=>{
601
- mockCacheManager.get.mockResolvedValue(500);
602
- const service = buildService();
603
- expect(await service.isTokenRevoked('user-1', 1000)).toBe(false);
604
- });
605
- });
606
- describe('getUserCompanies / getUserBranches', ()=>{
607
- it('throws BadRequestException when the company feature is disabled', async ()=>{
608
- const service = buildService();
609
- await expect(service.getUserCompanies('user-1')).rejects.toThrow(_common.BadRequestException);
610
- });
611
- it('returns an empty array when the user has no company permissions', async ()=>{
612
- companyFeatureEnabled = true;
613
- permissionRepo.find.mockResolvedValue([]);
614
- const service = buildService();
615
- expect(await service.getUserCompanies('user-1')).toEqual([]);
616
- });
617
- it('returns companies the user has access to', async ()=>{
618
- companyFeatureEnabled = true;
619
- permissionRepo.find.mockResolvedValue([
620
- {
621
- targetId: 'company-1'
622
- }
623
- ]);
624
- companyRepo.createQueryBuilder().getMany.mockResolvedValue([
625
- buildCompany()
626
- ]);
627
- const service = buildService();
628
- const result = await service.getUserCompanies('user-1');
629
- expect(result).toEqual([
630
- buildCompany()
631
- ]);
632
- });
633
- it('throws UnauthorizedException from getUserBranches when the user lacks company access', async ()=>{
634
- companyFeatureEnabled = true;
635
- permissionRepo.findOne.mockResolvedValue(null);
636
- const service = buildService();
637
- await expect(service.getUserBranches('user-1', 'company-1')).rejects.toThrow(_common.UnauthorizedException);
638
- });
639
- it('returns branches the user has access to within the company', async ()=>{
640
- companyFeatureEnabled = true;
641
- permissionRepo.findOne.mockResolvedValue({});
642
- permissionRepo.find.mockResolvedValue([
643
- {
644
- targetId: 'branch-1'
645
- }
646
- ]);
647
- branchRepo.createQueryBuilder().getMany.mockResolvedValue([
648
- buildBranch()
649
- ]);
650
- const service = buildService();
651
- const result = await service.getUserBranches('user-1', 'company-1');
652
- expect(result).toEqual([
653
- buildBranch()
654
- ]);
655
- });
656
- });
657
- describe('getCompanyById / getBranchById', ()=>{
658
- it('getCompanyById returns null when the company feature is disabled (scoped repo unavailable)', async ()=>{
659
- companyFeatureEnabled = false;
660
- const service = buildService();
661
- expect(await service.getCompanyById('company-1')).toBeNull();
662
- expect(companyRepo.findOne).not.toHaveBeenCalled();
663
- });
664
- it('getCompanyById returns the company by id when found', async ()=>{
665
- companyFeatureEnabled = true;
666
- companyRepo.findOne.mockResolvedValue(buildCompany());
667
- const service = buildService();
668
- const result = await service.getCompanyById('company-1');
669
- expect(companyRepo.findOne).toHaveBeenCalledWith({
670
- where: {
671
- id: 'company-1'
672
- }
673
- });
674
- expect(result).toEqual(buildCompany());
675
- });
676
- it('getCompanyById returns null when no company matches', async ()=>{
677
- companyFeatureEnabled = true;
678
- companyRepo.findOne.mockResolvedValue(null);
679
- const service = buildService();
680
- expect(await service.getCompanyById('missing')).toBeNull();
681
- });
682
- it('getBranchById returns null when the company feature is disabled (scoped repo unavailable)', async ()=>{
683
- companyFeatureEnabled = false;
684
- const service = buildService();
685
- expect(await service.getBranchById('branch-1')).toBeNull();
686
- expect(branchRepo.findOne).not.toHaveBeenCalled();
687
- });
688
- it('getBranchById returns the branch by id when found', async ()=>{
689
- companyFeatureEnabled = true;
690
- branchRepo.findOne.mockResolvedValue(buildBranch());
691
- const service = buildService();
692
- const result = await service.getBranchById('branch-1');
693
- expect(branchRepo.findOne).toHaveBeenCalledWith({
694
- where: {
695
- id: 'branch-1'
696
- }
697
- });
698
- expect(result).toEqual(buildBranch());
699
- });
700
- });
701
- describe('select', ()=>{
702
- it('throws BadRequestException when the company feature is disabled', async ()=>{
703
- const service = buildService();
704
- await expect(service.select('session-1', 'company-1', 'branch-1')).rejects.toThrow(_common.BadRequestException);
705
- });
706
- it('throws UnauthorizedException when the session is invalid or expired', async ()=>{
707
- companyFeatureEnabled = true;
708
- mockSessionService.consumeSession.mockResolvedValue(null);
709
- const service = buildService();
710
- await expect(service.select('bad-session', 'company-1', 'branch-1')).rejects.toThrow(_common.UnauthorizedException);
711
- });
712
- it('logs in with the selected company/branch when the session is valid', async ()=>{
713
- companyFeatureEnabled = true;
714
- mockSessionService.consumeSession.mockResolvedValue({
715
- sessionId: 'session-1',
716
- userId: 'user-1',
717
- createdAt: new Date(),
718
- expiresAt: new Date()
719
- });
720
- userRepo.findOne.mockResolvedValue(buildUser());
721
- permissionRepo.findOne.mockResolvedValue({});
722
- companyRepo.findOne.mockResolvedValue(buildCompany());
723
- branchRepo.findOne.mockResolvedValue(buildBranch());
724
- const service = buildService();
725
- const result = await service.select('session-1', 'company-1', 'branch-1');
726
- expect(result).toEqual(expect.objectContaining({
727
- accessToken: 'signed-token'
728
- }));
729
- });
730
- });
731
- describe('switchCompany / validateAndLoginWithCompany', ()=>{
732
- it('throws NotFoundException when the user does not exist', async ()=>{
733
- companyFeatureEnabled = true;
734
- userRepo.findOne.mockResolvedValue(null);
735
- const service = buildService();
736
- await expect(service.switchCompany('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.NotFoundException);
737
- });
738
- it('throws ForbiddenException when the user has no access to the company', async ()=>{
739
- companyFeatureEnabled = true;
740
- userRepo.findOne.mockResolvedValue(buildUser());
741
- permissionRepo.findOne.mockResolvedValue(null);
742
- const service = buildService();
743
- await expect(service.switchCompany('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.ForbiddenException);
744
- });
745
- it('throws NotFoundException when the company is inactive or missing', async ()=>{
746
- companyFeatureEnabled = true;
747
- userRepo.findOne.mockResolvedValue(buildUser());
748
- permissionRepo.findOne.mockResolvedValueOnce({}).mockResolvedValueOnce(null);
749
- companyRepo.findOne.mockResolvedValue(null);
750
- const service = buildService();
751
- await expect(service.switchCompany('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.NotFoundException);
752
- });
753
- it('throws ForbiddenException when the user has no access to the branch', async ()=>{
754
- companyFeatureEnabled = true;
755
- userRepo.findOne.mockResolvedValue(buildUser());
756
- companyRepo.findOne.mockResolvedValue(buildCompany());
757
- permissionRepo.findOne.mockResolvedValueOnce({}).mockResolvedValueOnce(null);
758
- const service = buildService();
759
- await expect(service.switchCompany('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.ForbiddenException);
760
- });
761
- it('throws NotFoundException when the branch is inactive or missing', async ()=>{
762
- companyFeatureEnabled = true;
763
- userRepo.findOne.mockResolvedValue(buildUser());
764
- companyRepo.findOne.mockResolvedValue(buildCompany());
765
- permissionRepo.findOne.mockResolvedValue({});
766
- branchRepo.findOne.mockResolvedValue(null);
767
- const service = buildService();
768
- await expect(service.switchCompany('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.NotFoundException);
769
- });
770
- it('returns a new login response when switching succeeds', async ()=>{
771
- companyFeatureEnabled = true;
772
- userRepo.findOne.mockResolvedValue(buildUser());
773
- companyRepo.findOne.mockResolvedValue(buildCompany());
774
- permissionRepo.findOne.mockResolvedValue({});
775
- branchRepo.findOne.mockResolvedValue(buildBranch());
776
- const service = buildService();
777
- const result = await service.switchCompany('user-1', 'company-1', 'branch-1');
778
- expect(result).toEqual(expect.objectContaining({
779
- accessToken: 'signed-token',
780
- company: expect.objectContaining({
781
- id: 'company-1'
782
- }),
783
- branch: expect.objectContaining({
784
- id: 'branch-1'
785
- })
786
- }));
787
- });
788
- });
789
- describe('getUserInfo', ()=>{
790
- it('throws UnauthorizedException when the user does not exist', async ()=>{
791
- userRepo.findOne.mockResolvedValue(null);
792
- const service = buildService();
793
- await expect(service.getUserInfo('user-1')).rejects.toThrow(_common.UnauthorizedException);
794
- });
795
- it('returns only user info when the company feature is disabled', async ()=>{
796
- userRepo.findOne.mockResolvedValue(buildUser());
797
- const service = buildService();
798
- const result = await service.getUserInfo('user-1');
799
- expect(result).toEqual({
800
- user: expect.objectContaining({
801
- id: 'user-1'
802
- })
803
- });
804
- });
805
- it('throws UnauthorizedException when the company feature is enabled but no companyId is given', async ()=>{
806
- companyFeatureEnabled = true;
807
- userRepo.findOne.mockResolvedValue(buildUser());
808
- const service = buildService();
809
- await expect(service.getUserInfo('user-1')).rejects.toThrow(_common.UnauthorizedException);
810
- });
811
- it('throws UnauthorizedException when the company cannot be found', async ()=>{
812
- companyFeatureEnabled = true;
813
- userRepo.findOne.mockResolvedValue(buildUser());
814
- companyRepo.findOne.mockResolvedValue(null);
815
- const service = buildService();
816
- await expect(service.getUserInfo('user-1', 'company-1')).rejects.toThrow(_common.UnauthorizedException);
817
- });
818
- it('throws UnauthorizedException when the branch cannot be found', async ()=>{
819
- companyFeatureEnabled = true;
820
- userRepo.findOne.mockResolvedValue(buildUser());
821
- companyRepo.findOne.mockResolvedValue(buildCompany());
822
- branchRepo.findOne.mockResolvedValue(null);
823
- const service = buildService();
824
- await expect(service.getUserInfo('user-1', 'company-1', 'branch-1')).rejects.toThrow(_common.UnauthorizedException);
825
- });
826
- it('returns user, company, and branch info when everything resolves', async ()=>{
827
- companyFeatureEnabled = true;
828
- userRepo.findOne.mockResolvedValue(buildUser());
829
- companyRepo.findOne.mockResolvedValue(buildCompany());
830
- branchRepo.findOne.mockResolvedValue(buildBranch());
831
- const service = buildService();
832
- const result = await service.getUserInfo('user-1', 'company-1', 'branch-1');
833
- expect(result).toEqual({
834
- user: expect.objectContaining({
835
- id: 'user-1'
836
- }),
837
- company: expect.objectContaining({
838
- id: 'company-1'
839
- }),
840
- branch: expect.objectContaining({
841
- id: 'branch-1'
842
- })
843
- });
844
- });
845
- });
846
- describe('createUserFromPendingRegistration', ()=>{
847
- it('throws BadRequestException when the email is already registered', async ()=>{
848
- const queryRunner = {
849
- connect: jest.fn(),
850
- startTransaction: jest.fn(),
851
- commitTransaction: jest.fn(),
852
- rollbackTransaction: jest.fn(),
853
- release: jest.fn(),
854
- manager: {
855
- findOne: jest.fn().mockResolvedValue(buildUser()),
856
- create: jest.fn(),
857
- save: jest.fn()
858
- }
859
- };
860
- mockDataSourceProvider.getDataSource.mockResolvedValue({
861
- createQueryRunner: ()=>queryRunner
862
- });
863
- const service = buildService();
864
- await expect(service.createUserFromPendingRegistration({
865
- email: 'user@example.com',
866
- name: 'Test',
867
- passwordHash: 'hash'
868
- })).rejects.toThrow(_common.BadRequestException);
869
- expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
870
- });
871
- it('creates the user with the email marked as verified', async ()=>{
872
- const queryRunner = {
873
- connect: jest.fn(),
874
- startTransaction: jest.fn(),
875
- commitTransaction: jest.fn(),
876
- rollbackTransaction: jest.fn(),
877
- release: jest.fn(),
878
- manager: {
879
- findOne: jest.fn().mockResolvedValue(null),
880
- create: jest.fn().mockReturnValue(buildUser({
881
- emailVerified: true
882
- })),
883
- save: jest.fn().mockResolvedValue(buildUser({
884
- emailVerified: true
885
- }))
886
- }
887
- };
888
- mockDataSourceProvider.getDataSource.mockResolvedValue({
889
- createQueryRunner: ()=>queryRunner
890
- });
891
- const service = buildService();
892
- const result = await service.createUserFromPendingRegistration({
893
- email: 'new@example.com',
894
- name: 'New User',
895
- passwordHash: 'hash'
896
- });
897
- expect(queryRunner.manager.create).toHaveBeenCalledWith(_appuserentity.AppUser, expect.objectContaining({
898
- emailVerified: true
899
- }));
900
- expect(result).toEqual(expect.objectContaining({
901
- accessToken: expect.any(String)
902
- }));
903
- });
904
- });
905
- describe('domain events', ()=>{
906
- let publish;
907
- beforeEach(()=>{
908
- publish = jest.fn().mockResolvedValue(undefined);
909
- _classes.EventBusRegistry.reset();
910
- _classes.EventBusRegistry.setBus({
911
- publish,
912
- subscribe: jest.fn(),
913
- close: jest.fn()
914
- });
915
- _classes.EventBusRegistry.configureModule('auth', {
916
- enabled: true
917
- });
918
- });
919
- afterEach(()=>{
920
- _classes.EventBusRegistry.reset();
921
- });
922
- it('publishes auth.session.logged-in when a session is issued', async ()=>{
923
- userRepo.findOne.mockResolvedValue(buildUser());
924
- _bcrypt.compare.mockResolvedValue(true);
925
- const service = buildService();
926
- await service.login('user@example.com', 'password');
927
- expect(publish).toHaveBeenCalledWith(expect.objectContaining({
928
- name: 'auth.session.logged-in',
929
- action: 'logged-in',
930
- payload: expect.objectContaining({
931
- ids: [
932
- 'user-1'
933
- ]
934
- })
935
- }));
936
- });
937
- it('publishes auth.session.logged-out on logout', async ()=>{
938
- const service = buildService();
939
- await service.logout('user-1');
940
- expect(publish).toHaveBeenCalledWith(expect.objectContaining({
941
- name: 'auth.session.logged-out'
942
- }));
943
- });
944
- it('publishes auth.appuser.password-changed after a successful password change', async ()=>{
945
- userRepo.findOne.mockResolvedValue(buildUser());
946
- _bcrypt.compare.mockResolvedValue(true);
947
- const service = buildService();
948
- await service.changePassword('user-1', 'old', 'new');
949
- expect(publish).toHaveBeenCalledWith(expect.objectContaining({
950
- name: 'auth.appuser.password-changed'
951
- }));
952
- });
953
- it('does not publish when the auth module has events disabled', async ()=>{
954
- _classes.EventBusRegistry.configureModule('auth', {
955
- enabled: false
956
- });
957
- const service = buildService();
958
- await service.logout('user-1');
959
- expect(publish).not.toHaveBeenCalled();
960
- });
961
- });
962
- });