@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,596 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { Collection } from 'mongodb';
3
+ import { z } from 'zod';
4
+ import * as models from '@trailmix-cms/models';
5
+ import { PROVIDER_SYMBOLS } from '@/constants/provider-symbols';
6
+ import { CMSCollectionName } from '@/constants/cms-collection-names';
7
+
8
+ // Mock the database module
9
+ jest.mock('@trailmix-cms/db', () => {
10
+ class MockBaseCollection { }
11
+ class MockAuditedCollection extends MockBaseCollection { }
12
+
13
+ return {
14
+ createDatabaseProviders: jest.fn(() => [
15
+ { provide: 'MOCK_DB_PROVIDER', useValue: 'mock' }
16
+ ]),
17
+ collectionFactory: jest.fn((name: string) => ({
18
+ provide: `COLLECTION_${name}`,
19
+ useValue: name
20
+ })),
21
+ BaseCollection: MockBaseCollection,
22
+ AuditedCollection: MockAuditedCollection,
23
+ DocumentCollection: jest.fn(() => () => { }),
24
+ DatabaseService: class DatabaseService { },
25
+ Collections: {
26
+ AuditCollection: class AuditCollection { }
27
+ }
28
+ };
29
+ });
30
+
31
+ // Mock collections, services, managers, and controllers to avoid dependency issues
32
+ jest.mock('@/collections', () => ({
33
+ AccountCollection: class AccountCollection { },
34
+ OrganizationCollection: class OrganizationCollection { },
35
+ RoleCollection: class RoleCollection { },
36
+ SecurityAuditCollection: class SecurityAuditCollection { },
37
+ ApiKeyCollection: class ApiKeyCollection { },
38
+ }));
39
+
40
+ jest.mock('@/services', () => ({
41
+ FeatureService: jest.fn().mockImplementation((features) => ({
42
+ constructor: { name: 'FeatureService' },
43
+ features
44
+ })),
45
+ AccountService: class AccountService { },
46
+ AuthService: class AuthService { },
47
+ GlobalRoleService: class GlobalRoleService { },
48
+ OrganizationService: class OrganizationService { },
49
+ OrganizationRoleService: class OrganizationRoleService { },
50
+ AuthorizationService: class AuthorizationService { },
51
+ ApiKeyService: class ApiKeyService { },
52
+ }));
53
+
54
+ jest.mock('@/managers', () => ({
55
+ GlobalRoleManager: class GlobalRoleManager { },
56
+ OrganizationRoleManager: class OrganizationRoleManager { },
57
+ OrganizationManager: class OrganizationManager { },
58
+ }));
59
+
60
+ jest.mock('@/controllers', () => {
61
+ const AccountController = class AccountController { };
62
+ const AuditsController = class AuditsController { };
63
+ const GlobalRolesController = class GlobalRolesController { };
64
+ const SecurityAuditsController = class SecurityAuditsController { };
65
+ const OrganizationsController = class OrganizationsController { };
66
+ const OrganizationRolesController = class OrganizationRolesController { };
67
+ const ApiKeysController = class ApiKeysController { };
68
+
69
+ return {
70
+ AccountController,
71
+ AuditsController,
72
+ GlobalRolesController,
73
+ SecurityAuditsController,
74
+ OrganizationsController,
75
+ OrganizationRolesController,
76
+ ApiKeysController,
77
+ };
78
+ });
79
+
80
+ import { setupTrailmixCMS, TrailmixCMSOptions } from '@/module';
81
+ import * as Collections from '@/collections';
82
+ import * as Services from '@/services';
83
+ import * as Managers from '@/managers';
84
+ import * as Controllers from '@/controllers';
85
+
86
+ describe('setupTrailmixCMS', () => {
87
+ beforeEach(() => {
88
+ jest.clearAllMocks();
89
+ });
90
+
91
+ describe('default behavior (no options)', () => {
92
+ let module: TestingModule;
93
+ let providers: any[];
94
+ let controllers: any[];
95
+ beforeEach(async () => {
96
+ jest.clearAllMocks();
97
+ const result = setupTrailmixCMS();
98
+ providers = result.providers;
99
+ controllers = result.controllers;
100
+ module = await Test.createTestingModule({
101
+ providers,
102
+ controllers,
103
+ }).compile();
104
+ });
105
+
106
+ it('should return providers and controllers with default configuration', () => {
107
+ expect(providers).toBeDefined();
108
+ expect(controllers).toBeDefined();
109
+ expect(Array.isArray(providers)).toBe(true);
110
+ expect(Array.isArray(controllers)).toBe(true);
111
+ });
112
+
113
+ it('should create a valid NestJS TestingModule with default providers', () => {
114
+ expect(module).toBeDefined();
115
+ });
116
+
117
+ it('should include default account providers', () => {
118
+ const accountSchema = module.get(PROVIDER_SYMBOLS.ACCOUNT_SCHEMA);
119
+ const accountConfig = module.get(PROVIDER_SYMBOLS.ACCOUNT_CONFIG);
120
+ const accountMapEntity = module.get(PROVIDER_SYMBOLS.ACCOUNT_MAP_ENTITY);
121
+
122
+ expect(accountSchema).toBe(models.Account.schema);
123
+ expect(accountConfig).toBeDefined();
124
+ expect(accountMapEntity).toBeDefined();
125
+ // Account setup should not be available when not provided
126
+ expect(() => module.get(PROVIDER_SYMBOLS.ACCOUNT_SETUP)).toThrow();
127
+ });
128
+
129
+ it('should not include account setup provider when not provided', () => {
130
+ expect(() => module.get(PROVIDER_SYMBOLS.ACCOUNT_SETUP)).toThrow();
131
+ });
132
+
133
+ it('should use default account config when not provided', () => {
134
+ const accountConfig = module.get(PROVIDER_SYMBOLS.ACCOUNT_CONFIG);
135
+ expect(accountConfig).toBeDefined();
136
+ expect(accountConfig).toEqual({ disableDefaultIndexes: false });
137
+ });
138
+
139
+ it('should use default account mapEntity function that returns entity as-is', () => {
140
+ const accountMapEntity = module.get(PROVIDER_SYMBOLS.ACCOUNT_MAP_ENTITY);
141
+ expect(accountMapEntity).toBeDefined();
142
+
143
+ const testEntity = { _id: 'test-id', email: 'test@example.com' };
144
+ const mapperFn = accountMapEntity as (entity: any) => any;
145
+ expect(mapperFn(testEntity)).toBe(testEntity);
146
+ });
147
+
148
+ it('should include default services', () => {
149
+ const accountService = module.get(Services.AccountService);
150
+ const authService = module.get(Services.AuthService);
151
+ const globalRoleService = module.get(Services.GlobalRoleService);
152
+
153
+ expect(accountService).toBeDefined();
154
+ expect(authService).toBeDefined();
155
+ expect(globalRoleService).toBeDefined();
156
+ });
157
+
158
+ it('should include default managers', () => {
159
+ const globalRoleManager = module.get(Managers.GlobalRoleManager);
160
+
161
+ expect(globalRoleManager).toBeDefined();
162
+ });
163
+
164
+ it('should include default collections', () => {
165
+ const accountCollection = module.get(Collections.AccountCollection);
166
+ const roleCollection = module.get(Collections.RoleCollection);
167
+ const securityAuditCollection = module.get(Collections.SecurityAuditCollection);
168
+
169
+ expect(accountCollection).toBeDefined();
170
+ expect(roleCollection).toBeDefined();
171
+ expect(securityAuditCollection).toBeDefined();
172
+ });
173
+
174
+ it('should include default controllers', () => {
175
+ expect(controllers).toContain(Controllers.AccountController);
176
+ expect(controllers).toContain(Controllers.AuditsController);
177
+ expect(controllers).toContain(Controllers.GlobalRolesController);
178
+ expect(controllers).toContain(Controllers.SecurityAuditsController);
179
+ });
180
+
181
+ it('should not include organization-related providers when organizations are disabled', () => {
182
+ expect(() => module.get(PROVIDER_SYMBOLS.ORGANIZATION_SCHEMA)).toThrow();
183
+ expect(() => module.get(Collections.OrganizationCollection)).toThrow();
184
+ expect(() => module.get(Services.OrganizationService)).toThrow();
185
+ });
186
+
187
+ it('should not include API key-related providers when API keys are disabled', () => {
188
+ expect(() => module.get(Collections.ApiKeyCollection)).toThrow();
189
+ expect(() => module.get(Services.ApiKeyService)).toThrow();
190
+ });
191
+ });
192
+
193
+ describe('with organizations enabled', () => {
194
+ let module: TestingModule;
195
+ let providers: any[];
196
+ let controllers: any[];
197
+ beforeEach(async () => {
198
+ jest.clearAllMocks();
199
+ const options: TrailmixCMSOptions = {
200
+ features: {
201
+ enableOrganizations: true
202
+ }
203
+ };
204
+ const result = setupTrailmixCMS(options);
205
+ providers = result.providers;
206
+ controllers = result.controllers;
207
+ module = await Test.createTestingModule({
208
+ providers,
209
+ controllers,
210
+ }).compile();
211
+ });
212
+
213
+ it('should include organization controllers when enableOrganizations is true', () => {
214
+ expect(controllers).toContain(Controllers.OrganizationsController);
215
+ expect(controllers).toContain(Controllers.OrganizationRolesController);
216
+ });
217
+
218
+ it('should include organization providers when enableOrganizations is true', async () => {
219
+ const organizationSchema = module.get(PROVIDER_SYMBOLS.ORGANIZATION_SCHEMA);
220
+ const organizationConfig = module.get(PROVIDER_SYMBOLS.ORGANIZATION_CONFIG);
221
+ const organizationMapEntity = module.get(PROVIDER_SYMBOLS.ORGANIZATION_MAP_ENTITY);
222
+
223
+ expect(organizationSchema).toBe(models.Organization.schema);
224
+ expect(organizationConfig).toBeDefined();
225
+ expect(organizationMapEntity).toBeDefined();
226
+ // Organization setup should not be available when not provided
227
+ expect(() => module.get(PROVIDER_SYMBOLS.ORGANIZATION_SETUP)).toThrow();
228
+ });
229
+
230
+ it('should not include organization setup provider when not provided', async () => {
231
+ expect(() => module.get(PROVIDER_SYMBOLS.ORGANIZATION_SETUP)).toThrow();
232
+ });
233
+
234
+ it('should use default organization config when not provided', async () => {
235
+ const organizationConfig = module.get(PROVIDER_SYMBOLS.ORGANIZATION_CONFIG);
236
+ expect(organizationConfig).toBeDefined();
237
+ expect(organizationConfig).toEqual({ disableDefaultIndexes: false });
238
+ });
239
+
240
+ it('should use default organization mapEntity function that returns entity as-is', async () => {
241
+ const organizationMapEntity = module.get(PROVIDER_SYMBOLS.ORGANIZATION_MAP_ENTITY);
242
+ expect(organizationMapEntity).toBeDefined();
243
+
244
+ const testEntity = { _id: 'test-id', name: 'Test Organization' };
245
+ const mapperFn = organizationMapEntity as (entity: any) => any;
246
+ expect(mapperFn(testEntity)).toBe(testEntity);
247
+ });
248
+
249
+ it('should include organization services and managers when enableOrganizations is true', async () => {
250
+ const organizationCollection = module.get(Collections.OrganizationCollection);
251
+ const organizationService = module.get(Services.OrganizationService);
252
+ const organizationRoleService = module.get(Services.OrganizationRoleService);
253
+ const organizationRoleManager = module.get(Managers.OrganizationRoleManager);
254
+ const organizationManager = module.get(Managers.OrganizationManager);
255
+ const authorizationService = module.get(Services.AuthorizationService);
256
+
257
+ expect(organizationCollection).toBeDefined();
258
+ expect(organizationService).toBeDefined();
259
+ expect(organizationRoleService).toBeDefined();
260
+ expect(organizationRoleManager).toBeDefined();
261
+ expect(organizationManager).toBeDefined();
262
+ expect(authorizationService).toBeDefined();
263
+ });
264
+ });
265
+
266
+ describe('with API keys enabled', () => {
267
+ let module: TestingModule;
268
+ let providers: any[];
269
+ let controllers: any[];
270
+ beforeEach(async () => {
271
+ jest.clearAllMocks();
272
+ const options: TrailmixCMSOptions = {
273
+ features: {
274
+ apiKeys: {
275
+ enabled: true
276
+ }
277
+ }
278
+ };
279
+ const result = setupTrailmixCMS(options);
280
+ providers = result.providers;
281
+ controllers = result.controllers;
282
+ module = await Test.createTestingModule({
283
+ providers,
284
+ controllers,
285
+ }).compile();
286
+ });
287
+
288
+ it('should include API key providers when apiKeys.enabled is true', () => {
289
+ const apiKeyCollection = module.get(Collections.ApiKeyCollection);
290
+ const apiKeyService = module.get(Services.ApiKeyService);
291
+
292
+ expect(apiKeyCollection).toBeDefined();
293
+ expect(apiKeyService).toBeDefined();
294
+ });
295
+
296
+ it('should include API keys controller when apiKeys.enabled is true', () => {
297
+ expect(controllers).toContain(Controllers.ApiKeysController);
298
+ });
299
+ });
300
+
301
+ describe('with both features enabled', () => {
302
+ let module: TestingModule;
303
+ let providers: any[];
304
+ let controllers: any[];
305
+ beforeEach(async () => {
306
+ jest.clearAllMocks();
307
+ const options: TrailmixCMSOptions = {
308
+ features: {
309
+ enableOrganizations: true,
310
+ apiKeys: {
311
+ enabled: true
312
+ }
313
+ }
314
+ };
315
+ const result = setupTrailmixCMS(options);
316
+ providers = result.providers;
317
+ controllers = result.controllers;
318
+ module = await Test.createTestingModule({
319
+ providers,
320
+ controllers,
321
+ }).compile();
322
+ });
323
+
324
+ it('should include both organization and API key providers', () => {
325
+ const organizationCollection = module.get(Collections.OrganizationCollection);
326
+ const apiKeyCollection = module.get(Collections.ApiKeyCollection);
327
+ const organizationService = module.get(Services.OrganizationService);
328
+ const apiKeyService = module.get(Services.ApiKeyService);
329
+
330
+ expect(organizationCollection).toBeDefined();
331
+ expect(apiKeyCollection).toBeDefined();
332
+ expect(organizationService).toBeDefined();
333
+ expect(apiKeyService).toBeDefined();
334
+ });
335
+ });
336
+
337
+ describe('custom account configuration', () => {
338
+ it('should use custom account schema when provided', async () => {
339
+ const customSchema = z.object({
340
+ _id: z.any().optional(),
341
+ email: z.string(),
342
+ customField: z.string()
343
+ }) as any; // Type assertion for test schema
344
+ const options: TrailmixCMSOptions = {
345
+ entities: {
346
+ accountSchema: customSchema
347
+ }
348
+ };
349
+ const result = setupTrailmixCMS(options);
350
+ const module: TestingModule = await Test.createTestingModule({
351
+ providers: result.providers,
352
+ controllers: result.controllers,
353
+ }).compile();
354
+
355
+ const accountSchema = module.get(PROVIDER_SYMBOLS.ACCOUNT_SCHEMA);
356
+ expect(accountSchema).toBe(customSchema);
357
+ });
358
+
359
+ it('should use custom account setup when provided', async () => {
360
+ const customSetup = jest.fn(async (collection: Collection<any>) => {
361
+ await collection.createIndex({ email: 1 });
362
+ });
363
+ const options: TrailmixCMSOptions = {
364
+ entities: {
365
+ accountSetup: customSetup
366
+ }
367
+ };
368
+ const result = setupTrailmixCMS(options);
369
+ const module: TestingModule = await Test.createTestingModule({
370
+ providers: result.providers,
371
+ controllers: result.controllers,
372
+ }).compile();
373
+
374
+ const accountSetup = module.get(PROVIDER_SYMBOLS.ACCOUNT_SETUP);
375
+ expect(accountSetup).toBe(customSetup);
376
+ });
377
+
378
+ it('should use custom account config when provided', async () => {
379
+ const customConfig = {
380
+ disableDefaultIndexes: true
381
+ };
382
+ const options: TrailmixCMSOptions = {
383
+ entities: {
384
+ accountConfig: customConfig
385
+ }
386
+ };
387
+ const result = setupTrailmixCMS(options);
388
+ const module: TestingModule = await Test.createTestingModule({
389
+ providers: result.providers,
390
+ controllers: result.controllers,
391
+ }).compile();
392
+
393
+ const accountConfig = module.get(PROVIDER_SYMBOLS.ACCOUNT_CONFIG);
394
+ expect(accountConfig).toEqual(customConfig);
395
+ });
396
+
397
+ it('should use custom account mapEntity when provided', async () => {
398
+ const customMapper = (entity: any) => ({ ...entity, mapped: true });
399
+ const options: TrailmixCMSOptions = {
400
+ entities: {
401
+ accountMapEntity: customMapper
402
+ }
403
+ };
404
+ const result = setupTrailmixCMS(options);
405
+ const module: TestingModule = await Test.createTestingModule({
406
+ providers: result.providers,
407
+ controllers: result.controllers,
408
+ }).compile();
409
+
410
+ const accountMapEntity = module.get(PROVIDER_SYMBOLS.ACCOUNT_MAP_ENTITY);
411
+ expect(accountMapEntity).toBe(customMapper);
412
+ });
413
+ });
414
+
415
+ describe('custom organization configuration', () => {
416
+ it('should use custom organization schema when provided', async () => {
417
+ const customSchema = z.object({
418
+ _id: z.any().optional(),
419
+ name: z.string(),
420
+ customField: z.string()
421
+ }) as any; // Type assertion for test schema
422
+ const options: TrailmixCMSOptions = {
423
+ features: {
424
+ enableOrganizations: true
425
+ },
426
+ entities: {
427
+ organizationSchema: customSchema
428
+ }
429
+ };
430
+ const result = setupTrailmixCMS(options);
431
+ const module: TestingModule = await Test.createTestingModule({
432
+ providers: result.providers,
433
+ controllers: result.controllers,
434
+ }).compile();
435
+
436
+ const organizationSchema = module.get(PROVIDER_SYMBOLS.ORGANIZATION_SCHEMA);
437
+ expect(organizationSchema).toBe(customSchema);
438
+ });
439
+
440
+ it('should use custom organization setup when provided', async () => {
441
+ const customSetup = jest.fn(async (collection: Collection<any>) => {
442
+ await collection.createIndex({ name: 1 });
443
+ });
444
+ const options: TrailmixCMSOptions = {
445
+ features: {
446
+ enableOrganizations: true
447
+ },
448
+ entities: {
449
+ organizationSetup: customSetup
450
+ }
451
+ };
452
+ const result = setupTrailmixCMS(options);
453
+ const module: TestingModule = await Test.createTestingModule({
454
+ providers: result.providers,
455
+ controllers: result.controllers,
456
+ }).compile();
457
+
458
+ const organizationSetup = module.get(PROVIDER_SYMBOLS.ORGANIZATION_SETUP);
459
+ expect(organizationSetup).toBe(customSetup);
460
+ });
461
+
462
+ it('should use custom organization config when provided', async () => {
463
+ const customConfig = {
464
+ disableDefaultIndexes: true
465
+ };
466
+ const options: TrailmixCMSOptions = {
467
+ features: {
468
+ enableOrganizations: true
469
+ },
470
+ entities: {
471
+ organizationConfig: customConfig
472
+ }
473
+ };
474
+ const result = setupTrailmixCMS(options);
475
+ const module: TestingModule = await Test.createTestingModule({
476
+ providers: result.providers,
477
+ controllers: result.controllers,
478
+ }).compile();
479
+
480
+ const organizationConfig = module.get(PROVIDER_SYMBOLS.ORGANIZATION_CONFIG);
481
+ expect(organizationConfig).toEqual(customConfig);
482
+ });
483
+
484
+ it('should use custom organization mapEntity when provided', async () => {
485
+ const customMapper = (entity: any) => ({ ...entity, mapped: true });
486
+ const options: TrailmixCMSOptions = {
487
+ features: {
488
+ enableOrganizations: true
489
+ },
490
+ entities: {
491
+ organizationMapEntity: customMapper
492
+ }
493
+ };
494
+ const result = setupTrailmixCMS(options);
495
+ const module: TestingModule = await Test.createTestingModule({
496
+ providers: result.providers,
497
+ controllers: result.controllers,
498
+ }).compile();
499
+
500
+ const organizationMapEntity = module.get(PROVIDER_SYMBOLS.ORGANIZATION_MAP_ENTITY);
501
+ expect(organizationMapEntity).toBe(customMapper);
502
+ });
503
+
504
+ it('should not include organization providers when enableOrganizations is false', async () => {
505
+ const customSchema = z.object({
506
+ _id: z.any().optional(),
507
+ name: z.string()
508
+ }) as any; // Type assertion for test schema
509
+ const options: TrailmixCMSOptions = {
510
+ features: {
511
+ enableOrganizations: false
512
+ },
513
+ entities: {
514
+ organizationSchema: customSchema
515
+ }
516
+ };
517
+ const result = setupTrailmixCMS(options);
518
+ const module: TestingModule = await Test.createTestingModule({
519
+ providers: result.providers,
520
+ controllers: result.controllers,
521
+ }).compile();
522
+
523
+ expect(() => module.get(PROVIDER_SYMBOLS.ORGANIZATION_SCHEMA)).toThrow();
524
+ });
525
+ });
526
+
527
+ describe('FeatureService initialization', () => {
528
+ it('should initialize FeatureService with provided features', async () => {
529
+ const features = {
530
+ enableOrganizations: true,
531
+ apiKeys: {
532
+ enabled: true as const
533
+ }
534
+ };
535
+ const options: TrailmixCMSOptions = {
536
+ features
537
+ };
538
+ const result = setupTrailmixCMS(options);
539
+ const module: TestingModule = await Test.createTestingModule({
540
+ providers: result.providers,
541
+ controllers: result.controllers,
542
+ }).compile();
543
+
544
+ const featureService = module.get(Services.FeatureService);
545
+ expect(featureService).toBeDefined();
546
+ expect((featureService as any).features).toEqual(features);
547
+ });
548
+
549
+ it('should initialize FeatureService with undefined when no features provided', async () => {
550
+ const result = setupTrailmixCMS();
551
+ const module: TestingModule = await Test.createTestingModule({
552
+ providers: result.providers,
553
+ controllers: result.controllers,
554
+ }).compile();
555
+
556
+ const featureService = module.get(Services.FeatureService);
557
+ expect(featureService).toBeDefined();
558
+ expect((featureService as any).features).toBeUndefined();
559
+ });
560
+ });
561
+
562
+ describe('collection factory calls', () => {
563
+ it('should call collectionFactory for default collections', () => {
564
+ const { collectionFactory } = require('@trailmix-cms/db');
565
+ setupTrailmixCMS();
566
+
567
+ expect(collectionFactory).toHaveBeenCalledWith(CMSCollectionName.Account);
568
+ expect(collectionFactory).toHaveBeenCalledWith(CMSCollectionName.Role);
569
+ expect(collectionFactory).toHaveBeenCalledWith(CMSCollectionName.SecurityAudit);
570
+ });
571
+
572
+ it('should call collectionFactory for organization collection when enabled', () => {
573
+ const { collectionFactory } = require('@trailmix-cms/db');
574
+ setupTrailmixCMS({
575
+ features: {
576
+ enableOrganizations: true
577
+ }
578
+ });
579
+
580
+ expect(collectionFactory).toHaveBeenCalledWith(CMSCollectionName.Organization);
581
+ });
582
+
583
+ it('should call collectionFactory for API key collection when enabled', () => {
584
+ const { collectionFactory } = require('@trailmix-cms/db');
585
+ setupTrailmixCMS({
586
+ features: {
587
+ apiKeys: {
588
+ enabled: true
589
+ }
590
+ }
591
+ });
592
+
593
+ expect(collectionFactory).toHaveBeenCalledWith(CMSCollectionName.ApiKey);
594
+ });
595
+ });
596
+ });
@@ -0,0 +1,90 @@
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { Logger } from '@nestjs/common';
3
+ import { faker } from '@faker-js/faker';
4
+
5
+ import * as TestUtils from '../../utils';
6
+
7
+ import { AccountService } from '@/services';
8
+ import { AccountCollection } from '@/collections';
9
+
10
+ describe('AccountService', () => {
11
+ let service: AccountService;
12
+ let accountCollection: jest.Mocked<AccountCollection>;
13
+
14
+ beforeEach(async () => {
15
+ // Mock Logger methods to prevent console output during tests
16
+ jest.spyOn(Logger.prototype, 'log').mockImplementation();
17
+ jest.spyOn(Logger.prototype, 'error').mockImplementation();
18
+ jest.spyOn(Logger.prototype, 'warn').mockImplementation();
19
+ jest.spyOn(Logger.prototype, 'debug').mockImplementation();
20
+ jest.spyOn(Logger.prototype, 'verbose').mockImplementation();
21
+
22
+ const mockAccountCollection = {
23
+ findOne: jest.fn(),
24
+ upsertOne: jest.fn(),
25
+ };
26
+
27
+ const module: TestingModule = await Test.createTestingModule({
28
+ providers: [
29
+ AccountService,
30
+ {
31
+ provide: AccountCollection,
32
+ useValue: mockAccountCollection,
33
+ },
34
+ ],
35
+ }).compile();
36
+
37
+ service = module.get<AccountService>(AccountService);
38
+ accountCollection = module.get(AccountCollection);
39
+ });
40
+
41
+ afterEach(() => {
42
+ jest.clearAllMocks();
43
+ });
44
+
45
+ afterAll(() => {
46
+ // Restore Logger methods after all tests
47
+ jest.restoreAllMocks();
48
+ });
49
+
50
+ describe('getAccount', () => {
51
+ it('calls findOne with correct query and returns result', async () => {
52
+ const userId = faker.string.uuid();
53
+ const account = TestUtils.Entities.createAccount({ user_id: userId });
54
+
55
+ accountCollection.findOne.mockResolvedValue(account);
56
+
57
+ const result = await service.getAccount(userId);
58
+
59
+ expect(accountCollection.findOne).toHaveBeenCalledWith({ user_id: userId });
60
+ expect(result).toEqual(account);
61
+ });
62
+ });
63
+
64
+ describe('upsertAccount', () => {
65
+ it('calls upsertOne with correct parameters and returns result', async () => {
66
+ const userId = faker.string.uuid();
67
+ const account = TestUtils.Entities.createAccount({ user_id: userId });
68
+
69
+ accountCollection.upsertOne.mockResolvedValue(account);
70
+
71
+ const result = await service.upsertAccount(userId);
72
+
73
+ expect(accountCollection.upsertOne).toHaveBeenCalledWith(
74
+ { user_id: userId },
75
+ {
76
+ $set: {
77
+ user_id: userId,
78
+ },
79
+ $setOnInsert: {},
80
+ },
81
+ {
82
+ system: true,
83
+ message: 'Account upserted',
84
+ source: AccountService.name,
85
+ }
86
+ );
87
+ expect(result).toEqual(account);
88
+ });
89
+ });
90
+ });