@trailmix-cms/cms 0.4.4 → 0.7.2

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 (267) 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 +24 -95
  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 +7 -6
  81. package/dist/decorators/auth.decorator.d.ts.map +1 -1
  82. package/dist/decorators/auth.decorator.js +38 -5
  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 +50 -0
  161. package/dist/services/auth.service.d.ts.map +1 -0
  162. package/dist/services/auth.service.js +259 -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 +52 -17
  215. package/test/unit/auth.guard.spec.ts +355 -0
  216. package/test/unit/collections/api-key.collection.spec.ts +416 -0
  217. package/test/unit/managers/global-role.manager.spec.ts +269 -0
  218. package/test/unit/managers/organization-role.manager.spec.ts +632 -0
  219. package/test/unit/managers/organization.manager.spec.ts +395 -0
  220. package/test/unit/module.spec.ts +596 -0
  221. package/test/unit/services/account.service.spec.ts +90 -0
  222. package/test/unit/services/api-key.service.spec.ts +1244 -0
  223. package/test/unit/services/auth.service.spec.ts +1036 -0
  224. package/test/unit/services/authorization.service.spec.ts +636 -0
  225. package/test/unit/services/feature.service.spec.ts +56 -0
  226. package/test/unit/services/global-role.service.spec.ts +289 -0
  227. package/test/unit/services/organization-role.service.spec.ts +300 -0
  228. package/test/unit/services/organization.service.spec.ts +385 -0
  229. package/test/utils/auth-guard.ts +114 -0
  230. package/test/utils/base.ts +16 -0
  231. package/test/utils/entities/account.ts +13 -0
  232. package/test/utils/entities/api-key.ts +15 -0
  233. package/test/utils/entities/audit.ts +18 -0
  234. package/test/utils/entities/index.ts +6 -0
  235. package/test/utils/entities/mapping.ts +20 -0
  236. package/test/utils/entities/organization.ts +13 -0
  237. package/test/utils/entities/role.ts +21 -0
  238. package/test/utils/entities/security-audit.ts +16 -0
  239. package/test/utils/index.ts +4 -0
  240. package/test/utils/models/audit-context.ts +10 -0
  241. package/test/utils/models/authorization.ts +7 -0
  242. package/test/utils/models/global-role.ts +22 -0
  243. package/test/utils/models/index.ts +5 -0
  244. package/test/utils/models/organization-role.ts +23 -0
  245. package/test/utils/models/publishable.ts +7 -0
  246. package/tsconfig.build.json +36 -0
  247. package/tsconfig.build.tsbuildinfo +1 -0
  248. package/dist/auth-guard-hook.d.ts.map +0 -1
  249. package/dist/auth-guard-hook.js.map +0 -1
  250. package/dist/cms.module.d.ts +0 -8
  251. package/dist/cms.module.d.ts.map +0 -1
  252. package/dist/cms.module.js +0 -44
  253. package/dist/cms.module.js.map +0 -1
  254. package/dist/cms.providers.d.ts +0 -120
  255. package/dist/cms.providers.d.ts.map +0 -1
  256. package/dist/cms.providers.js +0 -126
  257. package/dist/cms.providers.js.map +0 -1
  258. package/dist/collections/file.collection.d.ts +0 -21
  259. package/dist/collections/file.collection.d.ts.map +0 -1
  260. package/dist/collections/file.collection.js.map +0 -1
  261. package/dist/collections/text.collection.d.ts +0 -20
  262. package/dist/collections/text.collection.d.ts.map +0 -1
  263. package/dist/collections/text.collection.js +0 -56
  264. package/dist/collections/text.collection.js.map +0 -1
  265. package/dist/pipes/file.pipe.d.ts +0 -8
  266. /package/dist/{auth-guard-hook.d.ts → types/hooks/auth-guard-hook.d.ts} +0 -0
  267. /package/dist/{auth-guard-hook.js → types/hooks/auth-guard-hook.js} +0 -0
@@ -0,0 +1,289 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { 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 { GlobalRoleService } from '@/services';
9
+ import { RoleCollection } from '@/collections';
10
+ import { createAuditContextForPrincipal } from '@/decorators/audit-context.decorator';
11
+ import { RequestPrincipal } from '@/types';
12
+
13
+ describe('GlobalRoleService', () => {
14
+ let service: GlobalRoleService;
15
+ let roleCollection: jest.Mocked<RoleCollection>;
16
+
17
+ const accountEntity = TestUtils.Entities.createAccount();
18
+ const accountPrincipal: RequestPrincipal = {
19
+ principal_type: trailmixModels.Principal.Account,
20
+ entity: accountEntity,
21
+ };
22
+ const auditContext = createAuditContextForPrincipal(accountPrincipal);
23
+
24
+ beforeEach(async () => {
25
+ // Mock Logger methods to prevent console output during tests
26
+ jest.spyOn(Logger.prototype, 'log').mockImplementation();
27
+ jest.spyOn(Logger.prototype, 'error').mockImplementation();
28
+ jest.spyOn(Logger.prototype, 'warn').mockImplementation();
29
+ jest.spyOn(Logger.prototype, 'debug').mockImplementation();
30
+ jest.spyOn(Logger.prototype, 'verbose').mockImplementation();
31
+
32
+ const mockRoleCollection = {
33
+ insertOne: jest.fn(),
34
+ find: jest.fn(),
35
+ findOne: jest.fn(),
36
+ deleteOne: jest.fn(),
37
+ };
38
+
39
+ const module: TestingModule = await Test.createTestingModule({
40
+ providers: [
41
+ GlobalRoleService,
42
+ {
43
+ provide: RoleCollection,
44
+ useValue: mockRoleCollection,
45
+ },
46
+ ],
47
+ }).compile();
48
+
49
+ service = module.get<GlobalRoleService>(GlobalRoleService);
50
+ roleCollection = module.get(RoleCollection);
51
+ });
52
+
53
+ afterEach(() => {
54
+ jest.clearAllMocks();
55
+ });
56
+
57
+ afterAll(() => {
58
+ jest.restoreAllMocks();
59
+ });
60
+
61
+ describe('insertOne', () => {
62
+ it('successfully creates a global role (ensuring global role creation works)', async () => {
63
+ const params = {
64
+ principal_id: new ObjectId(),
65
+ principal_type: trailmixModels.Principal.Account,
66
+ role: trailmixModels.RoleValue.Admin,
67
+ };
68
+ const roleEntity = TestUtils.Entities.createRole({
69
+ ...params,
70
+ type: trailmixModels.RoleType.Global,
71
+ });
72
+ const globalRoleModel = TestUtils.Models.createGlobalRoleModel(params);
73
+
74
+ roleCollection.insertOne.mockResolvedValue(roleEntity);
75
+
76
+ const result = await service.insertOne(params, auditContext);
77
+
78
+ expect(roleCollection.insertOne).toHaveBeenCalledWith(
79
+ {
80
+ ...params,
81
+ type: trailmixModels.RoleType.Global,
82
+ },
83
+ auditContext,
84
+ );
85
+ expect(result).toBeDefined();
86
+ expect(result.principal_id).toEqual(params.principal_id);
87
+ expect(result.principal_type).toEqual(params.principal_type);
88
+ expect(result.role).toEqual(params.role);
89
+ });
90
+
91
+ it('sets type to Global when creating role (ensuring type is correctly set)', async () => {
92
+ const params = {
93
+ principal_id: new ObjectId(),
94
+ principal_type: trailmixModels.Principal.Account,
95
+ role: trailmixModels.RoleValue.User,
96
+ };
97
+ const roleEntity = TestUtils.Entities.createRole({
98
+ ...params,
99
+ type: trailmixModels.RoleType.Global,
100
+ });
101
+
102
+ roleCollection.insertOne.mockResolvedValue(roleEntity);
103
+
104
+ await service.insertOne(params, auditContext);
105
+
106
+ expect(roleCollection.insertOne).toHaveBeenCalledWith(
107
+ expect.objectContaining({
108
+ type: trailmixModels.RoleType.Global,
109
+ }),
110
+ auditContext,
111
+ );
112
+ });
113
+ });
114
+
115
+ describe('find', () => {
116
+ it('successfully finds global roles with filter (ensuring filtered queries work)', async () => {
117
+ const filter = {
118
+ principal_id: new ObjectId(),
119
+ };
120
+ const roleEntities = [
121
+ TestUtils.Entities.createRole({
122
+ ...filter,
123
+ type: trailmixModels.RoleType.Global,
124
+ role: trailmixModels.RoleValue.Admin,
125
+ }),
126
+ TestUtils.Entities.createRole({
127
+ ...filter,
128
+ type: trailmixModels.RoleType.Global,
129
+ role: trailmixModels.RoleValue.User,
130
+ }),
131
+ ];
132
+
133
+ roleCollection.find.mockResolvedValue(roleEntities);
134
+
135
+ const result = await service.find(filter);
136
+
137
+ expect(roleCollection.find).toHaveBeenCalledWith({
138
+ ...filter,
139
+ type: trailmixModels.RoleType.Global,
140
+ });
141
+ expect(result).toHaveLength(2);
142
+ expect(result[0].principal_id).toEqual(filter.principal_id);
143
+ expect(result[1].principal_id).toEqual(filter.principal_id);
144
+ });
145
+
146
+ it('successfully finds all global roles when no filter provided (ensuring unfiltered queries work)', async () => {
147
+ const roleEntities = [
148
+ TestUtils.Entities.createRole({ type: trailmixModels.RoleType.Global }),
149
+ TestUtils.Entities.createRole({ type: trailmixModels.RoleType.Global }),
150
+ ];
151
+
152
+ roleCollection.find.mockResolvedValue(roleEntities);
153
+
154
+ const result = await service.find();
155
+
156
+ expect(roleCollection.find).toHaveBeenCalledWith({
157
+ type: trailmixModels.RoleType.Global,
158
+ });
159
+ expect(result).toHaveLength(2);
160
+ });
161
+
162
+ it('returns empty array when no global roles found (ensuring empty results are handled)', async () => {
163
+ roleCollection.find.mockResolvedValue([]);
164
+
165
+ const result = await service.find();
166
+
167
+ expect(result).toEqual([]);
168
+ });
169
+
170
+ it('filters by type Global only (ensuring only global roles are returned)', async () => {
171
+ const filter = { role: trailmixModels.RoleValue.Admin };
172
+ const globalRoleEntity = TestUtils.Entities.createRole({
173
+ ...filter,
174
+ type: trailmixModels.RoleType.Global,
175
+ });
176
+
177
+ roleCollection.find.mockResolvedValue([globalRoleEntity]);
178
+
179
+ const result = await service.find(filter);
180
+
181
+ expect(roleCollection.find).toHaveBeenCalledWith({
182
+ ...filter,
183
+ type: trailmixModels.RoleType.Global,
184
+ });
185
+ // The service should filter out non-global roles in mapToModel
186
+ expect(result.length).toBeGreaterThanOrEqual(1);
187
+ });
188
+ });
189
+
190
+ describe('findOne', () => {
191
+ it('successfully finds a single global role (ensuring single role queries work)', async () => {
192
+ const filter = {
193
+ principal_id: new ObjectId(),
194
+ principal_type: trailmixModels.Principal.Account,
195
+ };
196
+ const roleEntity = TestUtils.Entities.createRole({
197
+ ...filter,
198
+ type: trailmixModels.RoleType.Global,
199
+ });
200
+
201
+ roleCollection.findOne.mockResolvedValue(roleEntity);
202
+
203
+ const result = await service.findOne(filter);
204
+
205
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
206
+ ...filter,
207
+ type: trailmixModels.RoleType.Global,
208
+ });
209
+ expect(result).toBeDefined();
210
+ expect(result?.principal_id).toEqual(filter.principal_id);
211
+ });
212
+
213
+ it('returns null when global role not found (ensuring null results are handled)', async () => {
214
+ const filter = {
215
+ principal_id: new ObjectId(),
216
+ };
217
+
218
+ roleCollection.findOne.mockResolvedValue(null);
219
+
220
+ const result = await service.findOne(filter);
221
+
222
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
223
+ ...filter,
224
+ type: trailmixModels.RoleType.Global,
225
+ });
226
+ expect(result).toBeNull();
227
+ });
228
+
229
+ it('filters by type Global only (ensuring only global roles are returned)', async () => {
230
+ const filter = { _id: new ObjectId() };
231
+ const globalRoleEntity = TestUtils.Entities.createRole({
232
+ ...filter,
233
+ type: trailmixModels.RoleType.Global,
234
+ });
235
+
236
+ roleCollection.findOne.mockResolvedValue(globalRoleEntity);
237
+
238
+ const result = await service.findOne(filter);
239
+
240
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
241
+ ...filter,
242
+ type: trailmixModels.RoleType.Global,
243
+ });
244
+ expect(result).toBeDefined();
245
+ });
246
+ });
247
+
248
+ describe('deleteOne', () => {
249
+ it('successfully deletes a global role (ensuring role deletion works)', async () => {
250
+ const roleId = new ObjectId();
251
+
252
+ roleCollection.deleteOne.mockResolvedValue({ deletedCount: 1 } as any);
253
+
254
+ await service.deleteOne(roleId, auditContext);
255
+
256
+ expect(roleCollection.deleteOne).toHaveBeenCalledWith(roleId, auditContext);
257
+ });
258
+ });
259
+
260
+ describe('mapToModel', () => {
261
+ it('throws error when entity is not a global role (ensuring type validation works)', async () => {
262
+ const organizationRoleEntity = TestUtils.Entities.createRole({
263
+ type: trailmixModels.RoleType.Organization,
264
+ });
265
+
266
+ roleCollection.find.mockResolvedValue([organizationRoleEntity]);
267
+
268
+ await expect(service.find({})).rejects.toThrow('Entity is not a global role');
269
+ });
270
+
271
+ it('successfully maps global role entity to model (ensuring mapping works)', async () => {
272
+ const roleEntity = TestUtils.Entities.createRole({
273
+ type: trailmixModels.RoleType.Global,
274
+ principal_id: new ObjectId(),
275
+ principal_type: trailmixModels.Principal.Account,
276
+ role: trailmixModels.RoleValue.Admin,
277
+ });
278
+
279
+ roleCollection.find.mockResolvedValue([roleEntity]);
280
+
281
+ const result = await service.find({});
282
+
283
+ expect(result).toHaveLength(1);
284
+ expect(result[0].principal_id).toEqual(roleEntity.principal_id);
285
+ expect(result[0].principal_type).toEqual(roleEntity.principal_type);
286
+ expect(result[0].role).toEqual(roleEntity.role);
287
+ });
288
+ });
289
+ });
@@ -0,0 +1,300 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { 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 { OrganizationRoleService } from '@/services';
9
+ import { RoleCollection } from '@/collections';
10
+ import { createAuditContextForPrincipal } from '@/decorators/audit-context.decorator';
11
+ import { RequestPrincipal } from '@/types';
12
+
13
+ describe('OrganizationRoleService', () => {
14
+ let service: OrganizationRoleService;
15
+ let roleCollection: jest.Mocked<RoleCollection>;
16
+
17
+ const accountEntity = TestUtils.Entities.createAccount();
18
+ const accountPrincipal: RequestPrincipal = {
19
+ principal_type: trailmixModels.Principal.Account,
20
+ entity: accountEntity,
21
+ };
22
+ const auditContext = createAuditContextForPrincipal(accountPrincipal);
23
+ const organizationId = new ObjectId();
24
+
25
+ beforeEach(async () => {
26
+ // Mock Logger methods to prevent console output during tests
27
+ jest.spyOn(Logger.prototype, 'log').mockImplementation();
28
+ jest.spyOn(Logger.prototype, 'error').mockImplementation();
29
+ jest.spyOn(Logger.prototype, 'warn').mockImplementation();
30
+ jest.spyOn(Logger.prototype, 'debug').mockImplementation();
31
+ jest.spyOn(Logger.prototype, 'verbose').mockImplementation();
32
+
33
+ const mockRoleCollection = {
34
+ insertOne: jest.fn(),
35
+ find: jest.fn(),
36
+ findOne: jest.fn(),
37
+ deleteOne: jest.fn(),
38
+ };
39
+
40
+ const module: TestingModule = await Test.createTestingModule({
41
+ providers: [
42
+ OrganizationRoleService,
43
+ {
44
+ provide: RoleCollection,
45
+ useValue: mockRoleCollection,
46
+ },
47
+ ],
48
+ }).compile();
49
+
50
+ service = module.get<OrganizationRoleService>(OrganizationRoleService);
51
+ roleCollection = module.get(RoleCollection);
52
+ });
53
+
54
+ afterEach(() => {
55
+ jest.clearAllMocks();
56
+ });
57
+
58
+ afterAll(() => {
59
+ jest.restoreAllMocks();
60
+ });
61
+
62
+ describe('insertOne', () => {
63
+ it('successfully creates an organization role (ensuring organization role creation works)', async () => {
64
+ const params = {
65
+ principal_id: new ObjectId(),
66
+ principal_type: trailmixModels.Principal.Account,
67
+ organization_id: organizationId,
68
+ role: trailmixModels.RoleValue.Admin,
69
+ };
70
+ const roleEntity = TestUtils.Entities.createRole({
71
+ ...params,
72
+ type: trailmixModels.RoleType.Organization,
73
+ });
74
+
75
+ roleCollection.insertOne.mockResolvedValue(roleEntity);
76
+
77
+ const result = await service.insertOne(params, auditContext);
78
+
79
+ expect(roleCollection.insertOne).toHaveBeenCalledWith(
80
+ {
81
+ ...params,
82
+ type: trailmixModels.RoleType.Organization,
83
+ },
84
+ auditContext,
85
+ );
86
+ expect(result).toBeDefined();
87
+ expect(result.principal_id).toEqual(params.principal_id);
88
+ expect(result.principal_type).toEqual(params.principal_type);
89
+ expect(result.organization_id).toEqual(params.organization_id);
90
+ expect(result.role).toEqual(params.role);
91
+ });
92
+
93
+ it('sets type to Organization when creating role (ensuring type is correctly set)', async () => {
94
+ const params = {
95
+ principal_id: new ObjectId(),
96
+ principal_type: trailmixModels.Principal.Account,
97
+ organization_id: organizationId,
98
+ role: trailmixModels.RoleValue.User,
99
+ };
100
+ const roleEntity = TestUtils.Entities.createRole({
101
+ ...params,
102
+ type: trailmixModels.RoleType.Organization,
103
+ });
104
+
105
+ roleCollection.insertOne.mockResolvedValue(roleEntity);
106
+
107
+ await service.insertOne(params, auditContext);
108
+
109
+ expect(roleCollection.insertOne).toHaveBeenCalledWith(
110
+ expect.objectContaining({
111
+ type: trailmixModels.RoleType.Organization,
112
+ }),
113
+ auditContext,
114
+ );
115
+ });
116
+ });
117
+
118
+ describe('find', () => {
119
+ it('successfully finds organization roles with filter (ensuring filtered queries work)', async () => {
120
+ const filter = {
121
+ principal_id: new ObjectId(),
122
+ organization_id: organizationId,
123
+ };
124
+ const roleEntities = [
125
+ TestUtils.Entities.createRole({
126
+ ...filter,
127
+ type: trailmixModels.RoleType.Organization,
128
+ role: trailmixModels.RoleValue.Admin,
129
+ }),
130
+ TestUtils.Entities.createRole({
131
+ ...filter,
132
+ type: trailmixModels.RoleType.Organization,
133
+ role: trailmixModels.RoleValue.User,
134
+ }),
135
+ ];
136
+
137
+ roleCollection.find.mockResolvedValue(roleEntities);
138
+
139
+ const result = await service.find(filter);
140
+
141
+ expect(roleCollection.find).toHaveBeenCalledWith({
142
+ ...filter,
143
+ type: trailmixModels.RoleType.Organization,
144
+ });
145
+ expect(result).toHaveLength(2);
146
+ expect(result[0].principal_id).toEqual(filter.principal_id);
147
+ expect(result[0].organization_id).toEqual(filter.organization_id);
148
+ expect(result[1].principal_id).toEqual(filter.principal_id);
149
+ expect(result[1].organization_id).toEqual(filter.organization_id);
150
+ });
151
+
152
+ it('returns empty array when no organization roles found (ensuring empty results are handled)', async () => {
153
+ roleCollection.find.mockResolvedValue([]);
154
+
155
+ const result = await service.find({});
156
+
157
+ expect(result).toEqual([]);
158
+ });
159
+
160
+ it('filters by type Organization only (ensuring only organization roles are returned)', async () => {
161
+ const filter = { role: trailmixModels.RoleValue.Admin };
162
+ const organizationRoleEntity = TestUtils.Entities.createRole({
163
+ ...filter,
164
+ type: trailmixModels.RoleType.Organization,
165
+ organization_id: organizationId,
166
+ });
167
+
168
+ roleCollection.find.mockResolvedValue([organizationRoleEntity]);
169
+
170
+ const result = await service.find(filter);
171
+
172
+ expect(roleCollection.find).toHaveBeenCalledWith({
173
+ ...filter,
174
+ type: trailmixModels.RoleType.Organization,
175
+ });
176
+ // The service should filter out non-organization roles in mapToModel
177
+ expect(result.length).toBeGreaterThanOrEqual(1);
178
+ });
179
+ });
180
+
181
+ describe('findOne', () => {
182
+ it('successfully finds a single organization role (ensuring single role queries work)', async () => {
183
+ const filter = {
184
+ principal_id: new ObjectId(),
185
+ principal_type: trailmixModels.Principal.Account,
186
+ organization_id: organizationId,
187
+ };
188
+ const roleEntity = TestUtils.Entities.createRole({
189
+ ...filter,
190
+ type: trailmixModels.RoleType.Organization,
191
+ });
192
+
193
+ roleCollection.findOne.mockResolvedValue(roleEntity);
194
+
195
+ const result = await service.findOne(filter);
196
+
197
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
198
+ ...filter,
199
+ type: trailmixModels.RoleType.Organization,
200
+ });
201
+ expect(result).toBeDefined();
202
+ expect(result?.principal_id).toEqual(filter.principal_id);
203
+ expect(result?.organization_id).toEqual(filter.organization_id);
204
+ });
205
+
206
+ it('returns null when organization role not found (ensuring null results are handled)', async () => {
207
+ const filter = {
208
+ principal_id: new ObjectId(),
209
+ organization_id: organizationId,
210
+ };
211
+
212
+ roleCollection.findOne.mockResolvedValue(null);
213
+
214
+ const result = await service.findOne(filter);
215
+
216
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
217
+ ...filter,
218
+ type: trailmixModels.RoleType.Organization,
219
+ });
220
+ expect(result).toBeNull();
221
+ });
222
+
223
+ it('filters by type Organization only (ensuring only organization roles are returned)', async () => {
224
+ const filter = { _id: new ObjectId() };
225
+ const organizationRoleEntity = TestUtils.Entities.createRole({
226
+ ...filter,
227
+ type: trailmixModels.RoleType.Organization,
228
+ organization_id: organizationId,
229
+ });
230
+
231
+ roleCollection.findOne.mockResolvedValue(organizationRoleEntity);
232
+
233
+ const result = await service.findOne(filter);
234
+
235
+ expect(roleCollection.findOne).toHaveBeenCalledWith({
236
+ ...filter,
237
+ type: trailmixModels.RoleType.Organization,
238
+ });
239
+ expect(result).toBeDefined();
240
+ });
241
+ });
242
+
243
+ describe('deleteOne', () => {
244
+ it('successfully deletes an organization role (ensuring role deletion works)', async () => {
245
+ const roleId = new ObjectId();
246
+
247
+ roleCollection.deleteOne.mockResolvedValue({ deletedCount: 1 } as any);
248
+
249
+ await service.deleteOne(roleId, auditContext);
250
+
251
+ expect(roleCollection.deleteOne).toHaveBeenCalledWith(roleId, auditContext);
252
+ });
253
+ });
254
+
255
+ describe('mapToModel', () => {
256
+ it('throws error when entity is not an organization role (ensuring type validation works)', async () => {
257
+ const globalRoleEntity = TestUtils.Entities.createRole({
258
+ type: trailmixModels.RoleType.Global,
259
+ });
260
+
261
+ roleCollection.find.mockResolvedValue([globalRoleEntity]);
262
+
263
+ await expect(service.find({})).rejects.toThrow('Entity is not an organization role');
264
+ });
265
+
266
+ it('throws error when organization role lacks organization_id (ensuring organization_id validation works)', async () => {
267
+ const roleEntityWithoutOrgId = {
268
+ ...TestUtils.Entities.createRole({
269
+ type: trailmixModels.RoleType.Organization,
270
+ organization_id: organizationId,
271
+ }),
272
+ organization_id: undefined,
273
+ } as any;
274
+
275
+ roleCollection.find.mockResolvedValue([roleEntityWithoutOrgId]);
276
+
277
+ await expect(service.find({})).rejects.toThrow('Organization role must have organization_id');
278
+ });
279
+
280
+ it('successfully maps organization role entity to model (ensuring mapping works)', async () => {
281
+ const roleEntity = TestUtils.Entities.createRole({
282
+ type: trailmixModels.RoleType.Organization,
283
+ principal_id: new ObjectId(),
284
+ principal_type: trailmixModels.Principal.Account,
285
+ organization_id: organizationId,
286
+ role: trailmixModels.RoleValue.Admin,
287
+ });
288
+
289
+ roleCollection.find.mockResolvedValue([roleEntity]);
290
+
291
+ const result = await service.find({});
292
+
293
+ expect(result).toHaveLength(1);
294
+ expect(result[0].principal_id).toEqual(roleEntity.principal_id);
295
+ expect(result[0].principal_type).toEqual(roleEntity.principal_type);
296
+ expect(result[0].organization_id).toEqual(roleEntity.organization_id);
297
+ expect(result[0].role).toEqual(roleEntity.role);
298
+ });
299
+ });
300
+ });