@flusys/nestjs-iam 7.1.0 → 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 (173) hide show
  1. package/config/index.d.ts +3 -3
  2. package/controllers/action.controller.d.ts +4 -4
  3. package/controllers/company-action-permission.controller.d.ts +2 -2
  4. package/controllers/index.d.ts +6 -6
  5. package/controllers/my-permission.controller.d.ts +3 -3
  6. package/controllers/role-permission.controller.d.ts +4 -4
  7. package/controllers/role.controller.d.ts +2 -2
  8. package/controllers/user-action-permission.controller.d.ts +4 -4
  9. package/docs/iam-swagger.config.d.ts +2 -2
  10. package/docs/index.d.ts +1 -1
  11. package/dtos/action.dto.d.ts +2 -2
  12. package/dtos/index.d.ts +3 -3
  13. package/entities/action.entity.d.ts +2 -2
  14. package/entities/index.d.ts +11 -11
  15. package/entities/permission-with-company.entity.d.ts +1 -1
  16. package/entities/role-with-company.entity.d.ts +1 -1
  17. package/entities/role.entity.d.ts +1 -1
  18. package/entities/user-iam-permission.entity.d.ts +1 -1
  19. package/enums/index.d.ts +4 -4
  20. package/fesm/284.js +1262 -0
  21. package/fesm/867.js +3699 -0
  22. package/fesm/chunks/364.js +86 -0
  23. package/fesm/config/index.js +133 -3
  24. package/fesm/controllers/index.js +18 -7
  25. package/fesm/docs/index.js +240 -1
  26. package/fesm/dtos/index.js +1090 -3
  27. package/fesm/entities/index.js +670 -31
  28. package/fesm/enums/index.js +97 -4
  29. package/fesm/helpers/index.js +125 -2
  30. package/fesm/index.js +806 -11
  31. package/fesm/interfaces/index.js +17 -3
  32. package/fesm/modules/index.js +259 -1
  33. package/fesm/runtime.js +134 -0
  34. package/fesm/services/index.js +43 -6
  35. package/fesm/types/index.js +15 -1
  36. package/helpers/company-access.helper.d.ts +2 -2
  37. package/helpers/index.d.ts +2 -2
  38. package/helpers/permission-mode.helper.d.ts +1 -1
  39. package/index.d.ts +11 -11
  40. package/interfaces/action.interface.d.ts +2 -2
  41. package/interfaces/iam-module-options.interface.d.ts +3 -3
  42. package/interfaces/index.d.ts +3 -3
  43. package/modules/iam.module.d.ts +2 -2
  44. package/modules/index.d.ts +1 -1
  45. package/package.json +30 -37
  46. package/services/action.service.d.ts +9 -9
  47. package/services/iam-config.service.d.ts +4 -4
  48. package/services/iam-datasource.service.d.ts +3 -3
  49. package/services/index.d.ts +6 -6
  50. package/services/permission-cache.service.d.ts +5 -5
  51. package/services/permission.service.d.ts +4 -4
  52. package/services/role.service.d.ts +8 -8
  53. package/types/index.d.ts +1 -1
  54. package/cjs/config/event-actions.js +0 -36
  55. package/cjs/config/iam.constants.js +0 -11
  56. package/cjs/config/index.js +0 -20
  57. package/cjs/config/message-keys.js +0 -71
  58. package/cjs/controllers/action.controller.js +0 -174
  59. package/cjs/controllers/action.controller.spec.js +0 -79
  60. package/cjs/controllers/company-action-permission.controller.js +0 -119
  61. package/cjs/controllers/company-action-permission.controller.spec.js +0 -65
  62. package/cjs/controllers/index.js +0 -23
  63. package/cjs/controllers/my-permission.controller.js +0 -147
  64. package/cjs/controllers/my-permission.controller.spec.js +0 -128
  65. package/cjs/controllers/role-permission.controller.js +0 -191
  66. package/cjs/controllers/role-permission.controller.spec.js +0 -140
  67. package/cjs/controllers/role.controller.js +0 -102
  68. package/cjs/controllers/role.controller.spec.js +0 -86
  69. package/cjs/controllers/user-action-permission.controller.js +0 -129
  70. package/cjs/controllers/user-action-permission.controller.spec.js +0 -68
  71. package/cjs/docs/iam-swagger.config.js +0 -209
  72. package/cjs/docs/iam-swagger.config.spec.js +0 -99
  73. package/cjs/docs/index.js +0 -18
  74. package/cjs/dtos/action.dto.js +0 -282
  75. package/cjs/dtos/index.js +0 -21
  76. package/cjs/dtos/permission.dto.js +0 -637
  77. package/cjs/dtos/role.dto.js +0 -155
  78. package/cjs/entities/action.entity.js +0 -138
  79. package/cjs/entities/index.js +0 -80
  80. package/cjs/entities/index.spec.js +0 -56
  81. package/cjs/entities/permission-base.entity.js +0 -107
  82. package/cjs/entities/permission-with-company.entity.js +0 -100
  83. package/cjs/entities/role-base.entity.js +0 -80
  84. package/cjs/entities/role-with-company.entity.js +0 -55
  85. package/cjs/entities/role.entity.js +0 -25
  86. package/cjs/entities/user-iam-permission.entity.js +0 -43
  87. package/cjs/enums/action-type.enum.js +0 -22
  88. package/cjs/enums/iam-entity-type.enum.js +0 -17
  89. package/cjs/enums/iam-permission-type.enum.js +0 -17
  90. package/cjs/enums/index.js +0 -21
  91. package/cjs/enums/permission-type.enum.js +0 -16
  92. package/cjs/helpers/company-access.helper.js +0 -23
  93. package/cjs/helpers/company-access.helper.spec.js +0 -68
  94. package/cjs/helpers/index.js +0 -19
  95. package/cjs/helpers/permission-mode.helper.js +0 -49
  96. package/cjs/helpers/permission-mode.helper.spec.js +0 -54
  97. package/cjs/index.js +0 -28
  98. package/cjs/interfaces/action.interface.js +0 -4
  99. package/cjs/interfaces/iam-module-options.interface.js +0 -4
  100. package/cjs/interfaces/index.js +0 -20
  101. package/cjs/interfaces/role.interface.js +0 -7
  102. package/cjs/modules/iam.module.js +0 -186
  103. package/cjs/modules/iam.module.spec.js +0 -282
  104. package/cjs/modules/index.js +0 -18
  105. package/cjs/services/action.service.js +0 -337
  106. package/cjs/services/action.service.spec.js +0 -351
  107. package/cjs/services/iam-config.service.js +0 -87
  108. package/cjs/services/iam-config.service.spec.js +0 -110
  109. package/cjs/services/iam-datasource.service.js +0 -129
  110. package/cjs/services/iam-datasource.service.spec.js +0 -110
  111. package/cjs/services/index.js +0 -23
  112. package/cjs/services/permission-cache.service.js +0 -544
  113. package/cjs/services/permission-cache.service.spec.js +0 -718
  114. package/cjs/services/permission.service.js +0 -914
  115. package/cjs/services/permission.service.spec.js +0 -918
  116. package/cjs/services/role.service.js +0 -224
  117. package/cjs/services/role.service.spec.js +0 -347
  118. package/cjs/types/index.js +0 -18
  119. package/cjs/types/logic-node.type.js +0 -30
  120. package/fesm/config/event-actions.js +0 -15
  121. package/fesm/config/iam.constants.js +0 -1
  122. package/fesm/config/message-keys.js +0 -35
  123. package/fesm/controllers/action.controller.js +0 -164
  124. package/fesm/controllers/action.controller.spec.js +0 -75
  125. package/fesm/controllers/company-action-permission.controller.js +0 -109
  126. package/fesm/controllers/company-action-permission.controller.spec.js +0 -61
  127. package/fesm/controllers/my-permission.controller.js +0 -137
  128. package/fesm/controllers/my-permission.controller.spec.js +0 -124
  129. package/fesm/controllers/role-permission.controller.js +0 -181
  130. package/fesm/controllers/role-permission.controller.spec.js +0 -136
  131. package/fesm/controllers/role.controller.js +0 -92
  132. package/fesm/controllers/role.controller.spec.js +0 -82
  133. package/fesm/controllers/user-action-permission.controller.js +0 -119
  134. package/fesm/controllers/user-action-permission.controller.spec.js +0 -64
  135. package/fesm/docs/iam-swagger.config.js +0 -199
  136. package/fesm/docs/iam-swagger.config.spec.js +0 -95
  137. package/fesm/dtos/action.dto.js +0 -252
  138. package/fesm/dtos/permission.dto.js +0 -571
  139. package/fesm/dtos/role.dto.js +0 -134
  140. package/fesm/entities/action.entity.js +0 -128
  141. package/fesm/entities/index.spec.js +0 -52
  142. package/fesm/entities/permission-base.entity.js +0 -100
  143. package/fesm/entities/permission-with-company.entity.js +0 -90
  144. package/fesm/entities/role-base.entity.js +0 -70
  145. package/fesm/entities/role-with-company.entity.js +0 -45
  146. package/fesm/entities/role.entity.js +0 -15
  147. package/fesm/entities/user-iam-permission.entity.js +0 -33
  148. package/fesm/enums/action-type.enum.js +0 -12
  149. package/fesm/enums/iam-entity-type.enum.js +0 -7
  150. package/fesm/enums/iam-permission-type.enum.js +0 -7
  151. package/fesm/enums/permission-type.enum.js +0 -6
  152. package/fesm/helpers/company-access.helper.js +0 -18
  153. package/fesm/helpers/company-access.helper.spec.js +0 -64
  154. package/fesm/helpers/permission-mode.helper.js +0 -49
  155. package/fesm/helpers/permission-mode.helper.spec.js +0 -50
  156. package/fesm/interfaces/action.interface.js +0 -3
  157. package/fesm/interfaces/iam-module-options.interface.js +0 -3
  158. package/fesm/interfaces/role.interface.js +0 -4
  159. package/fesm/modules/iam.module.js +0 -176
  160. package/fesm/modules/iam.module.spec.js +0 -278
  161. package/fesm/services/action.service.js +0 -327
  162. package/fesm/services/action.service.spec.js +0 -347
  163. package/fesm/services/iam-config.service.js +0 -77
  164. package/fesm/services/iam-config.service.spec.js +0 -106
  165. package/fesm/services/iam-datasource.service.js +0 -78
  166. package/fesm/services/iam-datasource.service.spec.js +0 -106
  167. package/fesm/services/permission-cache.service.js +0 -527
  168. package/fesm/services/permission-cache.service.spec.js +0 -714
  169. package/fesm/services/permission.service.js +0 -904
  170. package/fesm/services/permission.service.spec.js +0 -914
  171. package/fesm/services/role.service.js +0 -214
  172. package/fesm/services/role.service.spec.js +0 -343
  173. package/fesm/types/logic-node.type.js +0 -27
@@ -1,214 +0,0 @@
1
- function _define_property(obj, key, value) {
2
- if (key in obj) {
3
- Object.defineProperty(obj, key, {
4
- value: value,
5
- enumerable: true,
6
- configurable: true,
7
- writable: true
8
- });
9
- } else {
10
- obj[key] = value;
11
- }
12
- return obj;
13
- }
14
- function _ts_decorate(decorators, target, key, desc) {
15
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
- return c > 3 && r && Object.defineProperty(target, key, r), r;
19
- }
20
- function _ts_metadata(k, v) {
21
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
- }
23
- function _ts_param(paramIndex, decorator) {
24
- return function(target, key) {
25
- decorator(target, key, paramIndex);
26
- };
27
- }
28
- import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
29
- import { UtilsService } from '@flusys/nestjs-shared/modules';
30
- import { applyCompanyFilter } from '@flusys/nestjs-shared/utils';
31
- import { ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
32
- import { In, IsNull } from 'typeorm';
33
- import { IAM_EVENT_ENTITIES, IAM_EVENT_MODULE, ROLE_MESSAGES } from '../config';
34
- import { UserIamPermissionWithCompany } from '../entities/permission-with-company.entity';
35
- import { RoleWithCompany } from '../entities/role-with-company.entity';
36
- import { Role } from '../entities/role.entity';
37
- import { UserIamPermission } from '../entities/user-iam-permission.entity';
38
- import { IamEntityType } from '../enums/iam-entity-type.enum';
39
- import { IamPermissionType } from '../enums/iam-permission-type.enum';
40
- import { IAMConfigService } from './iam-config.service';
41
- import { IAMDataSourceService } from './iam-datasource.service';
42
- import { PermissionCacheService } from './permission-cache.service';
43
- export class RoleService extends ApiService {
44
- async beforeUpdateOperation(dto, _user, _queryRunner) {
45
- const dtos = Array.isArray(dto) ? dto : [
46
- dto
47
- ];
48
- const ids = dtos.map((d)=>d.id);
49
- await this.ensureDataSourceRepository();
50
- const readonlyRoles = await this.repository.find({
51
- where: {
52
- id: In(ids),
53
- readOnly: true,
54
- deletedAt: IsNull()
55
- }
56
- });
57
- if (readonlyRoles.length > 0) {
58
- const names = readonlyRoles.map((r)=>r.name).join(', ');
59
- throw new ForbiddenException({
60
- message: `Cannot modify readonly role(s): ${names}`,
61
- messageKey: ROLE_MESSAGES.READONLY_UPDATE_FORBIDDEN,
62
- messageVariables: {
63
- names
64
- }
65
- });
66
- }
67
- }
68
- async beforeDeleteOperation(dto, user, queryRunner) {
69
- await super.beforeDeleteOperation(dto, user, queryRunner);
70
- const roleIds = Array.isArray(dto.id) ? dto.id : [
71
- dto.id
72
- ];
73
- if (roleIds.length === 0) {
74
- return;
75
- }
76
- await this.ensureDataSourceRepository();
77
- const readonlyRoles = await this.repository.find({
78
- where: {
79
- id: In(roleIds),
80
- readOnly: true,
81
- deletedAt: IsNull()
82
- }
83
- });
84
- if (readonlyRoles.length > 0) {
85
- const names = readonlyRoles.map((r)=>r.name).join(', ');
86
- throw new ForbiddenException({
87
- message: `Cannot delete readonly role(s): ${names}`,
88
- messageKey: ROLE_MESSAGES.READONLY_DELETE_FORBIDDEN,
89
- messageVariables: {
90
- names
91
- }
92
- });
93
- }
94
- const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
95
- const permissionEntity = enableCompanyFeature ? UserIamPermissionWithCompany : UserIamPermission;
96
- const permissionRepo = queryRunner.manager.getRepository(permissionEntity);
97
- const userRoleRows = await permissionRepo.find({
98
- where: {
99
- permissionType: IamPermissionType.USER_ROLE,
100
- sourceType: IamEntityType.USER,
101
- targetType: IamEntityType.ROLE,
102
- targetId: In(roleIds)
103
- }
104
- });
105
- const affectedUserIds = [
106
- ...new Set(userRoleRows.map((row)=>row.sourceId))
107
- ];
108
- await permissionRepo.delete({
109
- permissionType: IamPermissionType.ROLE_ACTION,
110
- sourceType: IamEntityType.ROLE,
111
- sourceId: In(roleIds)
112
- });
113
- await permissionRepo.delete({
114
- permissionType: IamPermissionType.USER_ROLE,
115
- targetType: IamEntityType.ROLE,
116
- targetId: In(roleIds)
117
- });
118
- if (affectedUserIds.length > 0) {
119
- await this.permissionCacheService.invalidateUsers(affectedUserIds);
120
- }
121
- }
122
- // Entity Conversion
123
- async convertSingleDtoToEntity(dto, user) {
124
- const entity = await super.convertSingleDtoToEntity(dto, user);
125
- // Handle companyId based on company feature
126
- if (this.iamConfigService.isCompanyFeatureEnabled()) {
127
- const isUpdate = 'id' in dto && dto.id;
128
- // For create: use dto.companyId or user's companyId
129
- // For update: only override if explicitly provided
130
- if (!isUpdate || dto.companyId !== undefined) {
131
- entity.companyId = dto.companyId ?? user?.companyId ?? null;
132
- }
133
- }
134
- return entity;
135
- }
136
- // Query Customization
137
- async getSelectQuery(query, _user, select) {
138
- if (!select?.length) {
139
- select = [
140
- 'id',
141
- 'name',
142
- 'description',
143
- 'isActive',
144
- 'readOnly',
145
- 'serial',
146
- 'createdAt'
147
- ];
148
- if (this.iamConfigService.isCompanyFeatureEnabled()) {
149
- select.push('companyId');
150
- }
151
- }
152
- query.select(select.map((f)=>`${this.entityName}.${f}`));
153
- return {
154
- query,
155
- isRaw: false
156
- };
157
- }
158
- async getGlobalSearchQuery(query, search) {
159
- query.andWhere('(role.name LIKE :search OR role.description LIKE :search)', {
160
- search: `%${search}%`
161
- });
162
- return {
163
- query,
164
- isRaw: false
165
- };
166
- }
167
- async getExtraManipulateQuery(query, filterDto, user) {
168
- const result = await super.getExtraManipulateQuery(query, filterDto, user);
169
- applyCompanyFilter(query, {
170
- isCompanyFeatureEnabled: this.iamConfigService.isCompanyFeatureEnabled(),
171
- entityAlias: 'role'
172
- }, user);
173
- return result;
174
- }
175
- // Response Conversion
176
- convertEntityToResponseDto(entity, _isRaw) {
177
- return {
178
- id: entity.id,
179
- readOnly: entity.readOnly,
180
- name: entity.name,
181
- description: entity.description,
182
- isActive: entity.isActive,
183
- serial: entity.serial,
184
- companyId: ('companyId' in entity ? entity.companyId : null) ?? null,
185
- createdAt: entity.createdAt,
186
- updatedAt: entity.updatedAt,
187
- deletedAt: entity.deletedAt,
188
- createdById: entity.createdById,
189
- updatedById: entity.updatedById,
190
- deletedById: entity.deletedById
191
- };
192
- }
193
- constructor(cacheManager, utilsService, iamConfigService, dataSourceProvider, permissionCacheService){
194
- super(IAM_EVENT_ENTITIES.ROLE, cacheManager, utilsService, RoleService.name, true, IAM_EVENT_MODULE, dataSourceProvider, iamConfigService.isCompanyFeatureEnabled() ? RoleWithCompany : Role), _define_property(this, "cacheManager", void 0), _define_property(this, "utilsService", void 0), _define_property(this, "iamConfigService", void 0), _define_property(this, "permissionCacheService", void 0), this.cacheManager = cacheManager, this.utilsService = utilsService, this.iamConfigService = iamConfigService, this.permissionCacheService = permissionCacheService;
195
- }
196
- }
197
- RoleService = _ts_decorate([
198
- Injectable({
199
- scope: Scope.REQUEST
200
- }),
201
- _ts_param(0, Inject('CACHE_INSTANCE')),
202
- _ts_param(1, Inject(UtilsService)),
203
- _ts_param(2, Inject(IAMConfigService)),
204
- _ts_param(3, Inject(IAMDataSourceService)),
205
- _ts_param(4, Inject(PermissionCacheService)),
206
- _ts_metadata("design:type", Function),
207
- _ts_metadata("design:paramtypes", [
208
- typeof HybridCache === "undefined" ? Object : HybridCache,
209
- typeof UtilsService === "undefined" ? Object : UtilsService,
210
- typeof IAMConfigService === "undefined" ? Object : IAMConfigService,
211
- typeof IAMDataSourceService === "undefined" ? Object : IAMDataSourceService,
212
- typeof PermissionCacheService === "undefined" ? Object : PermissionCacheService
213
- ])
214
- ], RoleService);
@@ -1,343 +0,0 @@
1
- import { Test } from '@nestjs/testing';
2
- import { UtilsService } from '@flusys/nestjs-shared/modules';
3
- import { createMockDataSourceProvider, createMockRepository } from '@test-utils/mocks/repository.mock';
4
- import { buildMockUser } from '@test-utils/mocks/logged-user.mock';
5
- import { Role } from '../entities/role.entity';
6
- import { RoleWithCompany } from '../entities/role-with-company.entity';
7
- import { IAMConfigService } from './iam-config.service';
8
- import { IAMDataSourceService } from './iam-datasource.service';
9
- import { PermissionCacheService } from './permission-cache.service';
10
- import { RoleService } from './role.service';
11
- function buildRole(overrides = {}) {
12
- return {
13
- id: 'role-uuid-1',
14
- readOnly: false,
15
- name: 'Manager',
16
- description: null,
17
- isActive: true,
18
- serial: 1,
19
- createdAt: new Date(),
20
- updatedAt: new Date(),
21
- deletedAt: null,
22
- createdById: null,
23
- updatedById: null,
24
- deletedById: null,
25
- ...overrides
26
- };
27
- }
28
- describe('RoleService', ()=>{
29
- let service;
30
- let module;
31
- let mockRepo;
32
- let mockIamConfigService;
33
- let mockPermissionCacheService;
34
- beforeEach(async ()=>{
35
- mockRepo = createMockRepository();
36
- mockRepo.find.mockResolvedValue([]);
37
- const mockDataSourceProvider = createMockDataSourceProvider(mockRepo);
38
- mockIamConfigService = {
39
- isCompanyFeatureEnabled: jest.fn().mockReturnValue(false)
40
- };
41
- mockPermissionCacheService = {
42
- invalidateUsers: jest.fn()
43
- };
44
- module = await Test.createTestingModule({
45
- providers: [
46
- RoleService,
47
- {
48
- provide: 'CACHE_INSTANCE',
49
- useValue: {}
50
- },
51
- {
52
- provide: UtilsService,
53
- useValue: {}
54
- },
55
- {
56
- provide: IAMConfigService,
57
- useValue: mockIamConfigService
58
- },
59
- {
60
- provide: IAMDataSourceService,
61
- useValue: mockDataSourceProvider
62
- },
63
- {
64
- provide: PermissionCacheService,
65
- useValue: mockPermissionCacheService
66
- }
67
- ]
68
- }).compile();
69
- service = await module.resolve(RoleService);
70
- });
71
- describe('entity selection', ()=>{
72
- it('uses Role when the company feature is disabled', async ()=>{
73
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
74
- const built = await module.resolve(RoleService);
75
- expect(built['_entity']).toBe(Role);
76
- });
77
- it('uses RoleWithCompany when the company feature is enabled', async ()=>{
78
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
79
- const built = await module.resolve(RoleService);
80
- expect(built['_entity']).toBe(RoleWithCompany);
81
- });
82
- });
83
- describe('convertSingleDtoToEntity', ()=>{
84
- it('creates a plain entity without companyId when the company feature is disabled', async ()=>{
85
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
86
- await service.ensureDataSourceRepository();
87
- mockRepo.create.mockReturnValue({});
88
- const entity = await service.convertSingleDtoToEntity({
89
- name: 'Manager'
90
- }, buildMockUser());
91
- expect(entity.companyId).toBeUndefined();
92
- });
93
- it('stamps companyId from the DTO on create when the company feature is enabled', async ()=>{
94
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
95
- await service.ensureDataSourceRepository();
96
- mockRepo.create.mockReturnValue({});
97
- const entity = await service.convertSingleDtoToEntity({
98
- name: 'Manager',
99
- companyId: 'company-explicit'
100
- }, buildMockUser({
101
- companyId: 'company-from-user'
102
- }));
103
- expect(entity.companyId).toBe('company-explicit');
104
- });
105
- it('falls back to the user companyId on create when the DTO omits it', async ()=>{
106
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
107
- await service.ensureDataSourceRepository();
108
- mockRepo.create.mockReturnValue({});
109
- const entity = await service.convertSingleDtoToEntity({
110
- name: 'Manager'
111
- }, buildMockUser({
112
- companyId: 'company-from-user'
113
- }));
114
- expect(entity.companyId).toBe('company-from-user');
115
- });
116
- it('does not override companyId on update when the DTO omits it', async ()=>{
117
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
118
- await service.ensureDataSourceRepository();
119
- const existing = buildRole({
120
- id: 'role-uuid-1',
121
- companyId: 'existing-company'
122
- });
123
- mockRepo.findOne.mockResolvedValue(existing);
124
- const entity = await service.convertSingleDtoToEntity({
125
- id: 'role-uuid-1',
126
- name: 'Manager Updated'
127
- }, buildMockUser({
128
- companyId: 'company-from-user'
129
- }));
130
- expect(entity.companyId).toBe('existing-company');
131
- });
132
- it('overrides companyId on update when explicitly provided in the DTO', async ()=>{
133
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
134
- await service.ensureDataSourceRepository();
135
- const existing = buildRole({
136
- id: 'role-uuid-1',
137
- companyId: 'existing-company'
138
- });
139
- mockRepo.findOne.mockResolvedValue(existing);
140
- const entity = await service.convertSingleDtoToEntity({
141
- id: 'role-uuid-1',
142
- name: 'Manager Updated',
143
- companyId: 'new-company'
144
- }, buildMockUser());
145
- expect(entity.companyId).toBe('new-company');
146
- });
147
- it('throws NotFoundException when updating a role that does not exist', async ()=>{
148
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
149
- await service.ensureDataSourceRepository();
150
- mockRepo.findOne.mockResolvedValue(null);
151
- await expect(service.convertSingleDtoToEntity({
152
- id: 'missing-role'
153
- }, buildMockUser())).rejects.toThrow();
154
- });
155
- });
156
- describe('getSelectQuery', ()=>{
157
- it('selects the default field set without companyId when the company feature is disabled', async ()=>{
158
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
159
- const query = mockRepo.createQueryBuilder();
160
- await service.getSelectQuery(query, buildMockUser());
161
- expect(query.select).toHaveBeenCalledWith(expect.not.arrayContaining([
162
- 'role.companyId'
163
- ]));
164
- });
165
- it('includes companyId in the default field set when the company feature is enabled', async ()=>{
166
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
167
- const query = mockRepo.createQueryBuilder();
168
- await service.getSelectQuery(query, buildMockUser());
169
- expect(query.select).toHaveBeenCalledWith(expect.arrayContaining([
170
- 'role.companyId'
171
- ]));
172
- });
173
- it('uses the explicitly provided select list as-is', async ()=>{
174
- const query = mockRepo.createQueryBuilder();
175
- await service.getSelectQuery(query, buildMockUser(), [
176
- 'id',
177
- 'name'
178
- ]);
179
- expect(query.select).toHaveBeenCalledWith([
180
- 'role.id',
181
- 'role.name'
182
- ]);
183
- });
184
- });
185
- describe('getGlobalSearchQuery', ()=>{
186
- it('filters by name or description', async ()=>{
187
- const query = mockRepo.createQueryBuilder();
188
- await service.getGlobalSearchQuery(query, 'manager');
189
- expect(query.andWhere).toHaveBeenCalledWith('(role.name LIKE :search OR role.description LIKE :search)', {
190
- search: '%manager%'
191
- });
192
- });
193
- });
194
- describe('getExtraManipulateQuery', ()=>{
195
- it('applies a company filter clause when the company feature is enabled and the user has a company', async ()=>{
196
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(true);
197
- const query = mockRepo.createQueryBuilder();
198
- await service.getExtraManipulateQuery(query, {}, buildMockUser({
199
- companyId: 'company-1'
200
- }));
201
- expect(query.andWhere).toHaveBeenCalledWith('role.companyId = :companyId', {
202
- companyId: 'company-1'
203
- });
204
- });
205
- it('does not apply a company filter when the company feature is disabled', async ()=>{
206
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
207
- const query = mockRepo.createQueryBuilder();
208
- await service.getExtraManipulateQuery(query, {}, buildMockUser({
209
- companyId: 'company-1'
210
- }));
211
- expect(query.andWhere).not.toHaveBeenCalled();
212
- });
213
- });
214
- describe('convertEntityToResponseDto', ()=>{
215
- it('defaults companyId to null when the entity has no companyId field', ()=>{
216
- const entity = buildRole();
217
- const dto = service.convertEntityToResponseDto(entity, false);
218
- expect(dto.companyId).toBeNull();
219
- });
220
- it('surfaces companyId when present on the entity', ()=>{
221
- const entity = buildRole({
222
- companyId: 'company-1'
223
- });
224
- const dto = service.convertEntityToResponseDto(entity, false);
225
- expect(dto.companyId).toBe('company-1');
226
- });
227
- });
228
- describe('beforeDeleteOperation', ()=>{
229
- function buildQueryRunner(permissionRepo) {
230
- return {
231
- manager: {
232
- getRepository: jest.fn().mockReturnValue(permissionRepo)
233
- }
234
- };
235
- }
236
- it('does nothing when the delete DTO has no ids', async ()=>{
237
- const permissionRepo = createMockRepository();
238
- const queryRunner = buildQueryRunner(permissionRepo);
239
- await service.beforeDeleteOperation({
240
- id: [],
241
- type: 'delete'
242
- }, buildMockUser(), queryRunner);
243
- expect(permissionRepo.find).not.toHaveBeenCalled();
244
- });
245
- it('cascades ROLE_ACTION and USER_ROLE cleanup and invalidates affected users cache', async ()=>{
246
- mockIamConfigService.isCompanyFeatureEnabled.mockReturnValue(false);
247
- const permissionRepo = createMockRepository();
248
- permissionRepo.find.mockResolvedValue([
249
- {
250
- sourceId: 'user-1'
251
- },
252
- {
253
- sourceId: 'user-2'
254
- }
255
- ]);
256
- const queryRunner = buildQueryRunner(permissionRepo);
257
- await service.beforeDeleteOperation({
258
- id: 'role-uuid-1',
259
- type: 'delete'
260
- }, buildMockUser(), queryRunner);
261
- expect(permissionRepo.delete).toHaveBeenCalledWith(expect.objectContaining({
262
- permissionType: 'role_action',
263
- sourceType: 'role'
264
- }));
265
- expect(permissionRepo.delete).toHaveBeenCalledWith(expect.objectContaining({
266
- permissionType: 'user_role',
267
- targetType: 'role'
268
- }));
269
- expect(mockPermissionCacheService.invalidateUsers).toHaveBeenCalledWith([
270
- 'user-1',
271
- 'user-2'
272
- ]);
273
- });
274
- it('does not invalidate any cache when no users are affected', async ()=>{
275
- const permissionRepo = createMockRepository();
276
- permissionRepo.find.mockResolvedValue([]);
277
- const queryRunner = buildQueryRunner(permissionRepo);
278
- await service.beforeDeleteOperation({
279
- id: [
280
- 'role-uuid-1'
281
- ],
282
- type: 'delete'
283
- }, buildMockUser(), queryRunner);
284
- expect(mockPermissionCacheService.invalidateUsers).not.toHaveBeenCalled();
285
- });
286
- it('supports array ids and dedupes affected user ids', async ()=>{
287
- const permissionRepo = createMockRepository();
288
- permissionRepo.find.mockResolvedValue([
289
- {
290
- sourceId: 'user-1'
291
- },
292
- {
293
- sourceId: 'user-1'
294
- }
295
- ]);
296
- const queryRunner = buildQueryRunner(permissionRepo);
297
- await service.beforeDeleteOperation({
298
- id: [
299
- 'role-uuid-1',
300
- 'role-uuid-2'
301
- ],
302
- type: 'delete'
303
- }, buildMockUser(), queryRunner);
304
- expect(mockPermissionCacheService.invalidateUsers).toHaveBeenCalledWith([
305
- 'user-1'
306
- ]);
307
- });
308
- it('rejects deleting a readonly role', async ()=>{
309
- mockRepo.find.mockResolvedValue([
310
- buildRole({
311
- readOnly: true
312
- })
313
- ]);
314
- const permissionRepo = createMockRepository();
315
- const queryRunner = buildQueryRunner(permissionRepo);
316
- await expect(service.beforeDeleteOperation({
317
- id: 'role-uuid-1',
318
- type: 'delete'
319
- }, buildMockUser(), queryRunner)).rejects.toThrow('Cannot delete readonly role');
320
- expect(permissionRepo.find).not.toHaveBeenCalled();
321
- });
322
- });
323
- describe('beforeUpdateOperation', ()=>{
324
- it('rejects updating a readonly role', async ()=>{
325
- mockRepo.find.mockResolvedValue([
326
- buildRole({
327
- readOnly: true
328
- })
329
- ]);
330
- await expect(service.beforeUpdateOperation({
331
- id: 'role-uuid-1',
332
- name: 'Renamed'
333
- }, buildMockUser(), {})).rejects.toThrow('Cannot modify readonly role');
334
- });
335
- it('allows updating a non-readonly role', async ()=>{
336
- mockRepo.find.mockResolvedValue([]);
337
- await expect(service.beforeUpdateOperation({
338
- id: 'role-uuid-1',
339
- name: 'Renamed'
340
- }, buildMockUser(), {})).resolves.toBeUndefined();
341
- });
342
- });
343
- });
@@ -1,27 +0,0 @@
1
- /**
2
- * LogicNode - Represents a node in a permission logic tree
3
- *
4
- * Used for complex permission evaluation with AND/OR logic.
5
- * Supports hierarchical permission structures.
6
- *
7
- * Example:
8
- * ```typescript
9
- * const logic: LogicNode = {
10
- * id: 'root',
11
- * type: 'group',
12
- * operator: 'AND',
13
- * children: [
14
- * { id: '1', type: 'action', actionId: 'create-user' },
15
- * {
16
- * id: '2',
17
- * type: 'group',
18
- * operator: 'OR',
19
- * children: [
20
- * { id: '2-1', type: 'action', actionId: 'view-reports' },
21
- * { id: '2-2', type: 'action', actionId: 'view-dashboard' }
22
- * ]
23
- * }
24
- * ]
25
- * };
26
- * ```
27
- */ export { };