@trailmix-cms/cms 0.4.3 → 0.7.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 (266) hide show
  1. package/dist/auth.guard.d.ts +5 -13
  2. package/dist/auth.guard.d.ts.map +1 -1
  3. package/dist/auth.guard.js +23 -91
  4. package/dist/auth.guard.js.map +1 -1
  5. package/dist/collections/account.collection.d.ts +5 -3
  6. package/dist/collections/account.collection.d.ts.map +1 -1
  7. package/dist/collections/account.collection.js +15 -8
  8. package/dist/collections/account.collection.js.map +1 -1
  9. package/dist/collections/api-key.collection.d.ts +54 -0
  10. package/dist/collections/api-key.collection.d.ts.map +1 -0
  11. package/dist/collections/api-key.collection.js +142 -0
  12. package/dist/collections/api-key.collection.js.map +1 -0
  13. package/dist/collections/index.d.ts +4 -2
  14. package/dist/collections/index.d.ts.map +1 -1
  15. package/dist/collections/index.js +9 -5
  16. package/dist/collections/index.js.map +1 -1
  17. package/dist/collections/organization.collection.d.ts +20 -0
  18. package/dist/collections/organization.collection.d.ts.map +1 -0
  19. package/dist/collections/{file.collection.js → organization.collection.js} +17 -17
  20. package/dist/collections/organization.collection.js.map +1 -0
  21. package/dist/collections/role.collection.d.ts +32 -0
  22. package/dist/collections/role.collection.d.ts.map +1 -0
  23. package/dist/collections/role.collection.js +90 -0
  24. package/dist/collections/role.collection.js.map +1 -0
  25. package/dist/collections/security-audit.collection.d.ts +30 -0
  26. package/dist/collections/security-audit.collection.d.ts.map +1 -0
  27. package/dist/collections/security-audit.collection.js +79 -0
  28. package/dist/collections/security-audit.collection.js.map +1 -0
  29. package/dist/constants/cms-collection-names.d.ts +4 -2
  30. package/dist/constants/cms-collection-names.d.ts.map +1 -1
  31. package/dist/constants/cms-collection-names.js +4 -2
  32. package/dist/constants/cms-collection-names.js.map +1 -1
  33. package/dist/constants/provider-symbols.d.ts +10 -12
  34. package/dist/constants/provider-symbols.d.ts.map +1 -1
  35. package/dist/constants/provider-symbols.js +10 -12
  36. package/dist/constants/provider-symbols.js.map +1 -1
  37. package/dist/controllers/account.controller.d.ts +11 -15
  38. package/dist/controllers/account.controller.d.ts.map +1 -1
  39. package/dist/controllers/account.controller.js +69 -13
  40. package/dist/controllers/account.controller.js.map +1 -1
  41. package/dist/controllers/api-keys.controller.d.ts +13 -0
  42. package/dist/controllers/api-keys.controller.d.ts.map +1 -0
  43. package/dist/controllers/api-keys.controller.js +125 -0
  44. package/dist/controllers/api-keys.controller.js.map +1 -0
  45. package/dist/controllers/audit.controller.d.ts.map +1 -1
  46. package/dist/controllers/audit.controller.js +3 -3
  47. package/dist/controllers/audit.controller.js.map +1 -1
  48. package/dist/controllers/audits.controller.d.ts +10 -0
  49. package/dist/controllers/audits.controller.d.ts.map +1 -0
  50. package/dist/controllers/audits.controller.js +107 -0
  51. package/dist/controllers/audits.controller.js.map +1 -0
  52. package/dist/controllers/global-roles.controller.d.ts +16 -0
  53. package/dist/controllers/global-roles.controller.d.ts.map +1 -0
  54. package/dist/controllers/global-roles.controller.js +137 -0
  55. package/dist/controllers/global-roles.controller.js.map +1 -0
  56. package/dist/controllers/index.d.ts +6 -1
  57. package/dist/controllers/index.d.ts.map +1 -1
  58. package/dist/controllers/index.js +6 -1
  59. package/dist/controllers/index.js.map +1 -1
  60. package/dist/controllers/organization-roles.controller.d.ts +16 -0
  61. package/dist/controllers/organization-roles.controller.d.ts.map +1 -0
  62. package/dist/controllers/organization-roles.controller.js +145 -0
  63. package/dist/controllers/organization-roles.controller.js.map +1 -0
  64. package/dist/controllers/organizations.controller.d.ts +65 -0
  65. package/dist/controllers/organizations.controller.d.ts.map +1 -0
  66. package/dist/controllers/organizations.controller.js +140 -0
  67. package/dist/controllers/organizations.controller.js.map +1 -0
  68. package/dist/controllers/security-audits.controller.d.ts +11 -0
  69. package/dist/controllers/security-audits.controller.d.ts.map +1 -0
  70. package/dist/controllers/security-audits.controller.js +130 -0
  71. package/dist/controllers/security-audits.controller.js.map +1 -0
  72. package/dist/decorators/account.decorator.d.ts +1 -3
  73. package/dist/decorators/account.decorator.d.ts.map +1 -1
  74. package/dist/decorators/account.decorator.js +3 -10
  75. package/dist/decorators/account.decorator.js.map +1 -1
  76. package/dist/decorators/audit-context.decorator.d.ts +6 -0
  77. package/dist/decorators/audit-context.decorator.d.ts.map +1 -1
  78. package/dist/decorators/audit-context.decorator.js +12 -3
  79. package/dist/decorators/audit-context.decorator.js.map +1 -1
  80. package/dist/decorators/auth.decorator.d.ts +5 -3
  81. package/dist/decorators/auth.decorator.d.ts.map +1 -1
  82. package/dist/decorators/auth.decorator.js +38 -3
  83. package/dist/decorators/auth.decorator.js.map +1 -1
  84. package/dist/decorators/index.d.ts +4 -0
  85. package/dist/decorators/index.d.ts.map +1 -0
  86. package/dist/decorators/index.js +20 -0
  87. package/dist/decorators/index.js.map +1 -0
  88. package/dist/dto/account.dto.d.ts +33 -0
  89. package/dist/dto/account.dto.d.ts.map +1 -0
  90. package/dist/dto/account.dto.js +14 -0
  91. package/dist/dto/account.dto.js.map +1 -0
  92. package/dist/dto/api-key.dto.d.ts +89 -0
  93. package/dist/dto/api-key.dto.d.ts.map +1 -0
  94. package/dist/dto/api-key.dto.js +27 -0
  95. package/dist/dto/api-key.dto.js.map +1 -0
  96. package/dist/dto/audit.dto.d.ts +11 -5
  97. package/dist/dto/audit.dto.d.ts.map +1 -1
  98. package/dist/dto/audit.dto.js +1 -1
  99. package/dist/dto/audit.dto.js.map +1 -1
  100. package/dist/dto/global-role.dto.d.ts +99 -0
  101. package/dist/dto/global-role.dto.d.ts.map +1 -0
  102. package/dist/dto/global-role.dto.js +26 -0
  103. package/dist/dto/global-role.dto.js.map +1 -0
  104. package/dist/dto/organization-role.dto.d.ts +107 -0
  105. package/dist/dto/organization-role.dto.d.ts.map +1 -0
  106. package/dist/dto/organization-role.dto.js +26 -0
  107. package/dist/dto/organization-role.dto.js.map +1 -0
  108. package/dist/dto/organization.dto.d.ts +57 -0
  109. package/dist/dto/organization.dto.d.ts.map +1 -0
  110. package/dist/dto/organization.dto.js +32 -0
  111. package/dist/dto/organization.dto.js.map +1 -0
  112. package/dist/dto/security-audit.dto.d.ts +95 -0
  113. package/dist/dto/security-audit.dto.d.ts.map +1 -0
  114. package/dist/dto/security-audit.dto.js +26 -0
  115. package/dist/dto/security-audit.dto.js.map +1 -0
  116. package/dist/index.d.ts +7 -2
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +8 -3
  119. package/dist/index.js.map +1 -1
  120. package/dist/managers/global-role.manager.d.ts +42 -0
  121. package/dist/managers/global-role.manager.d.ts.map +1 -0
  122. package/dist/managers/global-role.manager.js +117 -0
  123. package/dist/managers/global-role.manager.js.map +1 -0
  124. package/dist/managers/index.d.ts +4 -0
  125. package/dist/managers/index.d.ts.map +1 -0
  126. package/dist/managers/index.js +20 -0
  127. package/dist/managers/index.js.map +1 -0
  128. package/dist/managers/organization-role.manager.d.ts +47 -0
  129. package/dist/managers/organization-role.manager.d.ts.map +1 -0
  130. package/dist/managers/organization-role.manager.js +218 -0
  131. package/dist/managers/organization-role.manager.js.map +1 -0
  132. package/dist/managers/organization.manager.d.ts +39 -0
  133. package/dist/managers/organization.manager.d.ts.map +1 -0
  134. package/dist/managers/organization.manager.js +196 -0
  135. package/dist/managers/organization.manager.js.map +1 -0
  136. package/dist/module.d.ts +92 -0
  137. package/dist/module.d.ts.map +1 -0
  138. package/dist/module.js +137 -0
  139. package/dist/module.js.map +1 -0
  140. package/dist/pipes/api-key.pipe.d.ts +8 -0
  141. package/dist/pipes/api-key.pipe.d.ts.map +1 -0
  142. package/dist/pipes/api-key.pipe.js +28 -0
  143. package/dist/pipes/api-key.pipe.js.map +1 -0
  144. package/dist/pipes/organization.pipe.d.ts +8 -0
  145. package/dist/pipes/organization.pipe.d.ts.map +1 -0
  146. package/dist/pipes/organization.pipe.js +28 -0
  147. package/dist/pipes/organization.pipe.js.map +1 -0
  148. package/dist/pipes/role.pipe.d.ts +8 -0
  149. package/dist/pipes/{file.pipe.d.ts.map → role.pipe.d.ts.map} +1 -1
  150. package/dist/pipes/{file.pipe.js → role.pipe.js} +8 -8
  151. package/dist/pipes/{file.pipe.js.map → role.pipe.js.map} +1 -1
  152. package/dist/services/account.service.d.ts +0 -2
  153. package/dist/services/account.service.d.ts.map +1 -1
  154. package/dist/services/account.service.js +1 -37
  155. package/dist/services/account.service.js.map +1 -1
  156. package/dist/services/api-key.service.d.ts +42 -0
  157. package/dist/services/api-key.service.d.ts.map +1 -0
  158. package/dist/services/api-key.service.js +306 -0
  159. package/dist/services/api-key.service.js.map +1 -0
  160. package/dist/services/auth.service.d.ts +40 -0
  161. package/dist/services/auth.service.d.ts.map +1 -0
  162. package/dist/services/auth.service.js +227 -0
  163. package/dist/services/auth.service.js.map +1 -0
  164. package/dist/services/authorization.service.d.ts +44 -9
  165. package/dist/services/authorization.service.d.ts.map +1 -1
  166. package/dist/services/authorization.service.js +107 -41
  167. package/dist/services/authorization.service.js.map +1 -1
  168. package/dist/services/feature.service.d.ts +23 -0
  169. package/dist/services/feature.service.d.ts.map +1 -0
  170. package/dist/services/feature.service.js +49 -0
  171. package/dist/services/feature.service.js.map +1 -0
  172. package/dist/services/global-role.service.d.ts +17 -0
  173. package/dist/services/global-role.service.d.ts.map +1 -0
  174. package/dist/services/global-role.service.js +99 -0
  175. package/dist/services/global-role.service.js.map +1 -0
  176. package/dist/services/index.d.ts +9 -0
  177. package/dist/services/index.d.ts.map +1 -0
  178. package/dist/services/index.js +25 -0
  179. package/dist/services/index.js.map +1 -0
  180. package/dist/services/organization-role.service.d.ts +33 -0
  181. package/dist/services/organization-role.service.d.ts.map +1 -0
  182. package/dist/services/organization-role.service.js +102 -0
  183. package/dist/services/organization-role.service.js.map +1 -0
  184. package/dist/services/organization.service.d.ts +29 -0
  185. package/dist/services/organization.service.d.ts.map +1 -0
  186. package/dist/services/organization.service.js +95 -0
  187. package/dist/services/organization.service.js.map +1 -0
  188. package/dist/types/feature-config.d.ts +9 -0
  189. package/dist/types/feature-config.d.ts.map +1 -0
  190. package/dist/types/feature-config.js +3 -0
  191. package/dist/types/feature-config.js.map +1 -0
  192. package/dist/types/hooks/auth-guard-hook.d.ts.map +1 -0
  193. package/dist/types/hooks/auth-guard-hook.js.map +1 -0
  194. package/dist/types/hooks/index.d.ts +3 -0
  195. package/dist/types/hooks/index.d.ts.map +1 -0
  196. package/dist/types/hooks/index.js +19 -0
  197. package/dist/types/hooks/index.js.map +1 -0
  198. package/dist/types/hooks/organization-delete-hook.d.ts +20 -0
  199. package/dist/types/hooks/organization-delete-hook.d.ts.map +1 -0
  200. package/dist/types/hooks/organization-delete-hook.js +3 -0
  201. package/dist/types/hooks/organization-delete-hook.js.map +1 -0
  202. package/dist/types/index.d.ts +5 -0
  203. package/dist/types/index.d.ts.map +1 -0
  204. package/dist/types/index.js +21 -0
  205. package/dist/types/index.js.map +1 -0
  206. package/dist/types/request-principal.d.ts +9 -0
  207. package/dist/types/request-principal.d.ts.map +1 -0
  208. package/dist/types/request-principal.js +3 -0
  209. package/dist/types/request-principal.js.map +1 -0
  210. package/dist/utils/provider-helpers.d.ts +6 -1
  211. package/dist/utils/provider-helpers.d.ts.map +1 -1
  212. package/dist/utils/provider-helpers.js +11 -1
  213. package/dist/utils/provider-helpers.js.map +1 -1
  214. package/package.json +59 -17
  215. package/test/unit/collections/api-key.collection.spec.ts +416 -0
  216. package/test/unit/managers/global-role.manager.spec.ts +269 -0
  217. package/test/unit/managers/organization-role.manager.spec.ts +632 -0
  218. package/test/unit/managers/organization.manager.spec.ts +395 -0
  219. package/test/unit/module.spec.ts +596 -0
  220. package/test/unit/services/account.service.spec.ts +90 -0
  221. package/test/unit/services/api-key.service.spec.ts +1244 -0
  222. package/test/unit/services/auth.service.spec.ts +790 -0
  223. package/test/unit/services/authorization.service.spec.ts +636 -0
  224. package/test/unit/services/feature.service.spec.ts +56 -0
  225. package/test/unit/services/global-role.service.spec.ts +289 -0
  226. package/test/unit/services/organization-role.service.spec.ts +300 -0
  227. package/test/unit/services/organization.service.spec.ts +385 -0
  228. package/test/utils/auth-guard.ts +114 -0
  229. package/test/utils/base.ts +16 -0
  230. package/test/utils/entities/account.ts +13 -0
  231. package/test/utils/entities/api-key.ts +15 -0
  232. package/test/utils/entities/audit.ts +18 -0
  233. package/test/utils/entities/index.ts +6 -0
  234. package/test/utils/entities/mapping.ts +20 -0
  235. package/test/utils/entities/organization.ts +13 -0
  236. package/test/utils/entities/role.ts +21 -0
  237. package/test/utils/entities/security-audit.ts +16 -0
  238. package/test/utils/index.ts +4 -0
  239. package/test/utils/models/audit-context.ts +10 -0
  240. package/test/utils/models/authorization.ts +7 -0
  241. package/test/utils/models/global-role.ts +22 -0
  242. package/test/utils/models/index.ts +5 -0
  243. package/test/utils/models/organization-role.ts +23 -0
  244. package/test/utils/models/publishable.ts +7 -0
  245. package/tsconfig.build.json +36 -0
  246. package/tsconfig.build.tsbuildinfo +1 -0
  247. package/dist/auth-guard-hook.d.ts.map +0 -1
  248. package/dist/auth-guard-hook.js.map +0 -1
  249. package/dist/cms.module.d.ts +0 -8
  250. package/dist/cms.module.d.ts.map +0 -1
  251. package/dist/cms.module.js +0 -44
  252. package/dist/cms.module.js.map +0 -1
  253. package/dist/cms.providers.d.ts +0 -120
  254. package/dist/cms.providers.d.ts.map +0 -1
  255. package/dist/cms.providers.js +0 -126
  256. package/dist/cms.providers.js.map +0 -1
  257. package/dist/collections/file.collection.d.ts +0 -21
  258. package/dist/collections/file.collection.d.ts.map +0 -1
  259. package/dist/collections/file.collection.js.map +0 -1
  260. package/dist/collections/text.collection.d.ts +0 -20
  261. package/dist/collections/text.collection.d.ts.map +0 -1
  262. package/dist/collections/text.collection.js +0 -56
  263. package/dist/collections/text.collection.js.map +0 -1
  264. package/dist/pipes/file.pipe.d.ts +0 -8
  265. /package/dist/{auth-guard-hook.d.ts → types/hooks/auth-guard-hook.d.ts} +0 -0
  266. /package/dist/{auth-guard-hook.js → types/hooks/auth-guard-hook.js} +0 -0
@@ -0,0 +1,632 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { BadRequestException, ForbiddenException, NotFoundException, Logger } from '@nestjs/common';
3
+ import { ObjectId } from 'mongodb';
4
+ import * as trailmixModels from '@trailmix-cms/models';
5
+
6
+ import * as TestUtils from '../../utils';
7
+
8
+ import { OrganizationRoleManager } from '@/managers';
9
+ import { OrganizationRoleService, AuthorizationService } from '@/services';
10
+ import { OrganizationCollection, SecurityAuditCollection } from '@/collections';
11
+ import { RequestPrincipal } from '@/types';
12
+ import { createAuditContextForPrincipal } from '@/decorators/audit-context.decorator';
13
+
14
+ describe('OrganizationRoleManager', () => {
15
+ let manager: OrganizationRoleManager;
16
+ let organizationRoleService: jest.Mocked<OrganizationRoleService>;
17
+ let authorizationService: jest.Mocked<AuthorizationService>;
18
+ let organizationCollection: jest.Mocked<OrganizationCollection>;
19
+ let securityAuditCollection: jest.Mocked<SecurityAuditCollection>;
20
+
21
+ const accountEntity = TestUtils.Entities.createAccount();
22
+ const accountPrincipal: RequestPrincipal = {
23
+ principal_type: trailmixModels.Principal.Account,
24
+ entity: accountEntity,
25
+ };
26
+ const auditContext = createAuditContextForPrincipal(accountPrincipal);
27
+ const organizationId = new ObjectId();
28
+
29
+ beforeEach(async () => {
30
+ // Mock Logger methods to prevent console output during tests
31
+ jest.spyOn(Logger.prototype, 'log').mockImplementation();
32
+ jest.spyOn(Logger.prototype, 'error').mockImplementation();
33
+ jest.spyOn(Logger.prototype, 'warn').mockImplementation();
34
+ jest.spyOn(Logger.prototype, 'debug').mockImplementation();
35
+ jest.spyOn(Logger.prototype, 'verbose').mockImplementation();
36
+
37
+ const mockOrganizationRoleService = {
38
+ insertOne: jest.fn(),
39
+ find: jest.fn(),
40
+ findOne: jest.fn(),
41
+ deleteOne: jest.fn(),
42
+ };
43
+
44
+ const mockAuthorizationService = {
45
+ isGlobalAdmin: jest.fn(),
46
+ resolveAuthorization: jest.fn(),
47
+ resolveOrganizationAuthorization: jest.fn(),
48
+ };
49
+
50
+ const mockOrganizationCollection = {
51
+ get: jest.fn(),
52
+ };
53
+
54
+ const mockSecurityAuditCollection = {
55
+ insertOne: jest.fn().mockResolvedValue(undefined),
56
+ };
57
+
58
+ const module: TestingModule = await Test.createTestingModule({
59
+ providers: [
60
+ OrganizationRoleManager,
61
+ {
62
+ provide: OrganizationRoleService,
63
+ useValue: mockOrganizationRoleService,
64
+ },
65
+ {
66
+ provide: AuthorizationService,
67
+ useValue: mockAuthorizationService,
68
+ },
69
+ {
70
+ provide: OrganizationCollection,
71
+ useValue: mockOrganizationCollection,
72
+ },
73
+ {
74
+ provide: SecurityAuditCollection,
75
+ useValue: mockSecurityAuditCollection,
76
+ },
77
+ ],
78
+ }).compile();
79
+
80
+ manager = module.get<OrganizationRoleManager>(OrganizationRoleManager);
81
+ organizationRoleService = module.get(OrganizationRoleService);
82
+ authorizationService = module.get(AuthorizationService);
83
+ organizationCollection = module.get(OrganizationCollection);
84
+ securityAuditCollection = module.get(SecurityAuditCollection);
85
+ });
86
+
87
+ afterEach(() => {
88
+ jest.clearAllMocks();
89
+ });
90
+
91
+ afterAll(() => {
92
+ jest.restoreAllMocks();
93
+ });
94
+
95
+ describe('insertOne', () => {
96
+ const params = {
97
+ organization_id: organizationId,
98
+ principal_id: new ObjectId(),
99
+ principal_type: trailmixModels.Principal.Account,
100
+ role: trailmixModels.RoleValue.Admin,
101
+ };
102
+
103
+ it('successfully creates an organization role when organization exists and user has admin access (ensuring organization role creation works)', async () => {
104
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
105
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel(params);
106
+ const adminRole = TestUtils.Models.createOrganizationRoleModel({
107
+ principal_id: accountEntity._id,
108
+ principal_type: accountPrincipal.principal_type,
109
+ organization_id: organizationId,
110
+ role: trailmixModels.RoleValue.Admin,
111
+ });
112
+ organizationCollection.get.mockResolvedValue(organization);
113
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
114
+ hasAccess: true,
115
+ isGlobalAdmin: false,
116
+ globalRoles: [],
117
+ organizationRoles: [adminRole],
118
+ });
119
+ organizationRoleService.findOne.mockResolvedValue(null);
120
+ organizationRoleService.insertOne.mockResolvedValue(organizationRole);
121
+
122
+ const result = await manager.insertOne(params, accountPrincipal, auditContext);
123
+
124
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
125
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalledWith({
126
+ principal: accountPrincipal,
127
+ rolesAllowList: [trailmixModels.RoleValue.Admin, trailmixModels.RoleValue.Owner],
128
+ principalTypeAllowList: [trailmixModels.Principal.Account, trailmixModels.Principal.ApiKey],
129
+ organizationId: organizationId,
130
+ });
131
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith(params);
132
+ expect(organizationRoleService.insertOne).toHaveBeenCalledWith(params, auditContext);
133
+ expect(result).toEqual(organizationRole);
134
+ });
135
+
136
+ it('throws BadRequestException when organization does not exist (ensuring organization existence is validated)', async () => {
137
+ organizationCollection.get.mockResolvedValue(null);
138
+
139
+ await expect(
140
+ manager.insertOne(params, accountPrincipal, auditContext)
141
+ ).rejects.toThrow(BadRequestException);
142
+
143
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
144
+ expect(authorizationService.resolveOrganizationAuthorization).not.toHaveBeenCalled();
145
+ expect(organizationRoleService.insertOne).not.toHaveBeenCalled();
146
+ });
147
+
148
+ it('throws BadRequestException when role already exists (ensuring role uniqueness is enforced)', async () => {
149
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
150
+ const existingRole = TestUtils.Models.createOrganizationRoleModel(params);
151
+ const adminRole = TestUtils.Models.createOrganizationRoleModel({
152
+ principal_id: accountEntity._id,
153
+ principal_type: accountPrincipal.principal_type,
154
+ organization_id: organizationId,
155
+ role: trailmixModels.RoleValue.Admin,
156
+ });
157
+ organizationCollection.get.mockResolvedValue(organization);
158
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
159
+ hasAccess: true,
160
+ isGlobalAdmin: false,
161
+ globalRoles: [],
162
+ organizationRoles: [adminRole],
163
+ });
164
+ organizationRoleService.findOne.mockResolvedValue(existingRole);
165
+
166
+ await expect(
167
+ manager.insertOne(params, accountPrincipal, auditContext)
168
+ ).rejects.toThrow(BadRequestException);
169
+
170
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith(params);
171
+ expect(organizationRoleService.insertOne).not.toHaveBeenCalled();
172
+ });
173
+
174
+ it('throws ForbiddenException when user has organization access but not admin role (ensuring users with lower roles cannot create organization roles)', async () => {
175
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
176
+ const readerRole = TestUtils.Models.createOrganizationRoleModel({
177
+ principal_id: accountEntity._id,
178
+ principal_type: accountPrincipal.principal_type,
179
+ organization_id: organizationId,
180
+ role: trailmixModels.RoleValue.Reader,
181
+ });
182
+ organizationCollection.get.mockResolvedValue(organization);
183
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
184
+ hasAccess: false,
185
+ isGlobalAdmin: false,
186
+ globalRoles: [],
187
+ organizationRoles: [readerRole],
188
+ });
189
+
190
+ await expect(
191
+ manager.insertOne(params, accountPrincipal, auditContext)
192
+ ).rejects.toThrow(ForbiddenException);
193
+
194
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
195
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
196
+ expect(organizationRoleService.insertOne).not.toHaveBeenCalled();
197
+ expect(securityAuditCollection.insertOne).toHaveBeenCalled();
198
+ });
199
+
200
+ it('throws BadRequestException when user has no organization access (ensuring user with no access is rejected)', async () => {
201
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
202
+ organizationCollection.get.mockResolvedValue(organization);
203
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
204
+ hasAccess: false,
205
+ isGlobalAdmin: false,
206
+ globalRoles: [],
207
+ organizationRoles: [],
208
+ });
209
+
210
+ await expect(
211
+ manager.insertOne(params, accountPrincipal, auditContext)
212
+ ).rejects.toThrow(BadRequestException);
213
+
214
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
215
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
216
+ expect(organizationRoleService.insertOne).not.toHaveBeenCalled();
217
+ });
218
+ });
219
+
220
+ describe('find', () => {
221
+ it('allows global admin to find all organization roles without organization_id (ensuring global admin can find all organization roles)', async () => {
222
+ const params = {};
223
+ const organizationRoles = [
224
+ TestUtils.Models.createOrganizationRoleModel(),
225
+ TestUtils.Models.createOrganizationRoleModel(),
226
+ ];
227
+ authorizationService.isGlobalAdmin.mockResolvedValue(true);
228
+ organizationRoleService.find.mockResolvedValue(organizationRoles);
229
+
230
+ const result = await manager.find(params, accountPrincipal);
231
+
232
+ expect(authorizationService.isGlobalAdmin).toHaveBeenCalledWith(
233
+ accountEntity._id,
234
+ accountPrincipal.principal_type,
235
+ );
236
+ expect(organizationRoleService.find).toHaveBeenCalledWith(params);
237
+ expect(result).toEqual(organizationRoles);
238
+ });
239
+
240
+ it('throws BadRequestException when non-admin tries to find without organization_id (ensuring only admins can find organization roles)', async () => {
241
+ const params = {};
242
+ authorizationService.isGlobalAdmin.mockResolvedValue(false);
243
+
244
+ await expect(
245
+ manager.find(params, accountPrincipal)
246
+ ).rejects.toThrow(BadRequestException);
247
+
248
+ expect(authorizationService.isGlobalAdmin).toHaveBeenCalled();
249
+ expect(organizationRoleService.find).not.toHaveBeenCalled();
250
+ });
251
+
252
+ it('successfully finds organization roles when user has admin access to organization (ensuring organization role retrieval works)', async () => {
253
+ const params = { organization_id: organizationId };
254
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
255
+ const organizationRoles = [
256
+ TestUtils.Models.createOrganizationRoleModel({ organization_id: organizationId }),
257
+ ];
258
+ const adminRole = TestUtils.Models.createOrganizationRoleModel({
259
+ principal_id: accountEntity._id,
260
+ principal_type: accountPrincipal.principal_type,
261
+ organization_id: organizationId,
262
+ role: trailmixModels.RoleValue.Admin,
263
+ });
264
+ organizationCollection.get.mockResolvedValue(organization);
265
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
266
+ hasAccess: true,
267
+ isGlobalAdmin: false,
268
+ globalRoles: [],
269
+ organizationRoles: [adminRole],
270
+ });
271
+ organizationRoleService.find.mockResolvedValue(organizationRoles);
272
+
273
+ const result = await manager.find(params, accountPrincipal);
274
+
275
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
276
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
277
+ expect(organizationRoleService.find).toHaveBeenCalledWith(params);
278
+ expect(result).toEqual(organizationRoles);
279
+ });
280
+
281
+ it('returns principal organization roles when non-admin user has organization access and queries their own roles (ensuring non-admin users can view their own roles)', async () => {
282
+ const params = {
283
+ organization_id: organizationId,
284
+ principal_id: accountEntity._id,
285
+ principal_type: accountPrincipal.principal_type,
286
+ };
287
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
288
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
289
+ principal_id: accountEntity._id,
290
+ principal_type: accountPrincipal.principal_type,
291
+ organization_id: organizationId,
292
+ role: trailmixModels.RoleValue.User,
293
+ });
294
+ organizationCollection.get.mockResolvedValue(organization);
295
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
296
+ hasAccess: false,
297
+ isGlobalAdmin: false,
298
+ globalRoles: [],
299
+ organizationRoles: [userRole],
300
+ });
301
+
302
+ const result = await manager.find(params, accountPrincipal);
303
+
304
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
305
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
306
+ expect(organizationRoleService.find).not.toHaveBeenCalled();
307
+ expect(result).toEqual([userRole]);
308
+ });
309
+
310
+ it('throws BadRequestException when non-admin user tries to view other principal roles (ensuring non-admin users cannot view other principals roles)', async () => {
311
+ const otherPrincipalId = new ObjectId();
312
+ const params = {
313
+ organization_id: organizationId,
314
+ principal_id: otherPrincipalId,
315
+ };
316
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
317
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
318
+ principal_id: accountEntity._id,
319
+ principal_type: accountPrincipal.principal_type,
320
+ organization_id: organizationId,
321
+ role: trailmixModels.RoleValue.User,
322
+ });
323
+ organizationCollection.get.mockResolvedValue(organization);
324
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
325
+ hasAccess: false,
326
+ isGlobalAdmin: false,
327
+ globalRoles: [],
328
+ organizationRoles: [userRole],
329
+ });
330
+
331
+ await expect(
332
+ manager.find(params, accountPrincipal)
333
+ ).rejects.toThrow(BadRequestException);
334
+
335
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
336
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
337
+ expect(organizationRoleService.find).not.toHaveBeenCalled();
338
+ });
339
+
340
+ it('throws BadRequestException when non-admin user tries to view other principal type roles (ensuring non-admin users cannot view other principal types)', async () => {
341
+ const params = {
342
+ organization_id: organizationId,
343
+ principal_type: trailmixModels.Principal.ApiKey,
344
+ };
345
+ const organization = TestUtils.Entities.createOrganization({ _id: organizationId });
346
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
347
+ principal_id: accountEntity._id,
348
+ principal_type: accountPrincipal.principal_type,
349
+ organization_id: organizationId,
350
+ role: trailmixModels.RoleValue.User,
351
+ });
352
+ organizationCollection.get.mockResolvedValue(organization);
353
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
354
+ hasAccess: false,
355
+ isGlobalAdmin: false,
356
+ globalRoles: [],
357
+ organizationRoles: [userRole],
358
+ });
359
+
360
+ await expect(
361
+ manager.find(params, accountPrincipal)
362
+ ).rejects.toThrow(BadRequestException);
363
+
364
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
365
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
366
+ expect(organizationRoleService.find).not.toHaveBeenCalled();
367
+ });
368
+
369
+ it('throws BadRequestException when organization does not exist (ensuring organization existence is validated)', async () => {
370
+ const params = { organization_id: organizationId };
371
+ organizationCollection.get.mockResolvedValue(null);
372
+
373
+ await expect(
374
+ manager.find(params, accountPrincipal)
375
+ ).rejects.toThrow(BadRequestException);
376
+
377
+ expect(organizationCollection.get).toHaveBeenCalledWith(organizationId);
378
+ expect(organizationRoleService.find).not.toHaveBeenCalled();
379
+ });
380
+ });
381
+
382
+ describe('get', () => {
383
+ const roleId = new ObjectId();
384
+
385
+ it('successfully gets an organization role when user has admin access (ensuring organization role retrieval works)', async () => {
386
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
387
+ _id: roleId,
388
+ organization_id: organizationId,
389
+ principal_id: accountEntity._id,
390
+ principal_type: accountPrincipal.principal_type,
391
+ });
392
+ const adminRole = TestUtils.Models.createOrganizationRoleModel({
393
+ principal_id: accountEntity._id,
394
+ principal_type: accountPrincipal.principal_type,
395
+ organization_id: organizationId,
396
+ role: trailmixModels.RoleValue.Admin,
397
+ });
398
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
399
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
400
+ hasAccess: true,
401
+ isGlobalAdmin: false,
402
+ globalRoles: [],
403
+ organizationRoles: [adminRole],
404
+ });
405
+
406
+ const result = await manager.get(roleId, accountPrincipal);
407
+
408
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
409
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalledWith({
410
+ principal: accountPrincipal,
411
+ rolesAllowList: [trailmixModels.RoleValue.Admin, trailmixModels.RoleValue.Owner],
412
+ principalTypeAllowList: [trailmixModels.Principal.Account, trailmixModels.Principal.ApiKey],
413
+ organizationId: organizationId,
414
+ });
415
+ expect(result).toEqual(organizationRole);
416
+ });
417
+
418
+ it('successfully gets an organization role when non-admin user views their own role (ensuring non-admin users can view their own roles)', async () => {
419
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
420
+ _id: roleId,
421
+ organization_id: organizationId,
422
+ principal_id: accountEntity._id,
423
+ principal_type: accountPrincipal.principal_type,
424
+ role: trailmixModels.RoleValue.User,
425
+ });
426
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
427
+ principal_id: accountEntity._id,
428
+ principal_type: accountPrincipal.principal_type,
429
+ organization_id: organizationId,
430
+ role: trailmixModels.RoleValue.User,
431
+ });
432
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
433
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
434
+ hasAccess: false,
435
+ isGlobalAdmin: false,
436
+ globalRoles: [],
437
+ organizationRoles: [userRole],
438
+ });
439
+
440
+ const result = await manager.get(roleId, accountPrincipal);
441
+
442
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
443
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
444
+ expect(result).toEqual(organizationRole);
445
+ });
446
+
447
+ it('throws NotFoundException when role is assigned to different principal (ensuring users cannot view roles assigned to others)', async () => {
448
+ const otherPrincipalId = new ObjectId();
449
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
450
+ _id: roleId,
451
+ organization_id: organizationId,
452
+ principal_id: otherPrincipalId,
453
+ principal_type: accountPrincipal.principal_type,
454
+ });
455
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
456
+ principal_id: accountEntity._id,
457
+ principal_type: accountPrincipal.principal_type,
458
+ organization_id: organizationId,
459
+ role: trailmixModels.RoleValue.User,
460
+ });
461
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
462
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
463
+ hasAccess: false,
464
+ isGlobalAdmin: false,
465
+ globalRoles: [],
466
+ organizationRoles: [userRole],
467
+ });
468
+
469
+ await expect(
470
+ manager.get(roleId, accountPrincipal)
471
+ ).rejects.toThrow(NotFoundException);
472
+
473
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
474
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
475
+ });
476
+
477
+ it('throws NotFoundException when role is assigned to different principal type (ensuring users cannot view roles for other principal types)', async () => {
478
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
479
+ _id: roleId,
480
+ organization_id: organizationId,
481
+ principal_id: accountEntity._id,
482
+ principal_type: trailmixModels.Principal.ApiKey,
483
+ });
484
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
485
+ principal_id: accountEntity._id,
486
+ principal_type: accountPrincipal.principal_type,
487
+ organization_id: organizationId,
488
+ role: trailmixModels.RoleValue.User,
489
+ });
490
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
491
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
492
+ hasAccess: false,
493
+ isGlobalAdmin: false,
494
+ globalRoles: [],
495
+ organizationRoles: [userRole],
496
+ });
497
+
498
+ await expect(
499
+ manager.get(roleId, accountPrincipal)
500
+ ).rejects.toThrow(NotFoundException);
501
+
502
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
503
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
504
+ });
505
+
506
+ it('throws NotFoundException when role does not exist (ensuring role existence is validated)', async () => {
507
+ organizationRoleService.findOne.mockResolvedValue(null);
508
+
509
+ await expect(
510
+ manager.get(roleId, accountPrincipal)
511
+ ).rejects.toThrow(NotFoundException);
512
+
513
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
514
+ expect(authorizationService.resolveOrganizationAuthorization).not.toHaveBeenCalled();
515
+ });
516
+
517
+ it('throws BadRequestException when user does not have access (ensuring user with no access is rejected)', async () => {
518
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
519
+ _id: roleId,
520
+ organization_id: organizationId,
521
+ });
522
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
523
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
524
+ hasAccess: false,
525
+ isGlobalAdmin: false,
526
+ globalRoles: [],
527
+ organizationRoles: [],
528
+ });
529
+
530
+ await expect(
531
+ manager.get(roleId, accountPrincipal)
532
+ ).rejects.toThrow(BadRequestException);
533
+
534
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
535
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
536
+ expect(securityAuditCollection.insertOne).toHaveBeenCalled();
537
+ });
538
+ });
539
+
540
+ describe('deleteOne', () => {
541
+ const roleId = new ObjectId();
542
+
543
+ it('successfully deletes an organization role when user has admin access (ensuring organization role deletion works)', async () => {
544
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
545
+ _id: roleId,
546
+ organization_id: organizationId,
547
+ });
548
+ const adminRole = TestUtils.Models.createOrganizationRoleModel({
549
+ principal_id: accountEntity._id,
550
+ principal_type: accountPrincipal.principal_type,
551
+ organization_id: organizationId,
552
+ role: trailmixModels.RoleValue.Admin,
553
+ });
554
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
555
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
556
+ hasAccess: true,
557
+ isGlobalAdmin: false,
558
+ globalRoles: [],
559
+ organizationRoles: [adminRole],
560
+ });
561
+ organizationRoleService.deleteOne.mockResolvedValue(undefined);
562
+
563
+ await manager.deleteOne(roleId, accountPrincipal, auditContext);
564
+
565
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
566
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
567
+ expect(organizationRoleService.deleteOne).toHaveBeenCalledWith(roleId, auditContext);
568
+ });
569
+
570
+ it('throws NotFoundException when role does not exist (ensuring role existence is validated)', async () => {
571
+ organizationRoleService.findOne.mockResolvedValue(null);
572
+
573
+ await expect(
574
+ manager.deleteOne(roleId, accountPrincipal, auditContext)
575
+ ).rejects.toThrow(NotFoundException);
576
+
577
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
578
+ expect(organizationRoleService.deleteOne).not.toHaveBeenCalled();
579
+ });
580
+
581
+ it('throws BadRequestException when user does not have access (ensuring user with no access is rejected)', async () => {
582
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
583
+ _id: roleId,
584
+ organization_id: organizationId,
585
+ });
586
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
587
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
588
+ hasAccess: false,
589
+ isGlobalAdmin: false,
590
+ globalRoles: [],
591
+ organizationRoles: [],
592
+ });
593
+
594
+ await expect(
595
+ manager.deleteOne(roleId, accountPrincipal, auditContext)
596
+ ).rejects.toThrow(BadRequestException);
597
+
598
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
599
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
600
+ expect(organizationRoleService.deleteOne).not.toHaveBeenCalled();
601
+ });
602
+
603
+ it('throws ForbiddenException when user has organization access but not admin role (ensuring only admins can delete organization roles)', async () => {
604
+ const organizationRole = TestUtils.Models.createOrganizationRoleModel({
605
+ _id: roleId,
606
+ organization_id: organizationId,
607
+ });
608
+ const userRole = TestUtils.Models.createOrganizationRoleModel({
609
+ principal_id: accountEntity._id,
610
+ principal_type: accountPrincipal.principal_type,
611
+ organization_id: organizationId,
612
+ role: trailmixModels.RoleValue.User,
613
+ });
614
+ organizationRoleService.findOne.mockResolvedValue(organizationRole);
615
+ authorizationService.resolveOrganizationAuthorization.mockResolvedValue({
616
+ hasAccess: false,
617
+ isGlobalAdmin: false,
618
+ globalRoles: [],
619
+ organizationRoles: [userRole],
620
+ });
621
+
622
+ await expect(
623
+ manager.deleteOne(roleId, accountPrincipal, auditContext)
624
+ ).rejects.toThrow(ForbiddenException);
625
+
626
+ expect(organizationRoleService.findOne).toHaveBeenCalledWith({ _id: roleId });
627
+ expect(authorizationService.resolveOrganizationAuthorization).toHaveBeenCalled();
628
+ expect(securityAuditCollection.insertOne).toHaveBeenCalled();
629
+ expect(organizationRoleService.deleteOne).not.toHaveBeenCalled();
630
+ });
631
+ });
632
+ });