@jmruthers/pace-core 0.5.3 → 0.5.5

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 (170) hide show
  1. package/dist/{DataTable-ZQDRE46Q.js → DataTable-3SSI644S.js} +2 -2
  2. package/dist/{chunk-M4RW7PIP.js → chunk-2BJFM2JC.js} +105 -81
  3. package/dist/chunk-2BJFM2JC.js.map +1 -0
  4. package/dist/{chunk-5H3C2SWM.js → chunk-RTCA5ZNK.js} +2 -2
  5. package/dist/components.js +2 -2
  6. package/dist/index.js +2 -2
  7. package/dist/styles/core.css +3 -0
  8. package/dist/utils.js +1 -1
  9. package/docs/api/classes/ErrorBoundary.md +1 -1
  10. package/docs/api/classes/InvalidScopeError.md +1 -1
  11. package/docs/api/classes/MissingUserContextError.md +1 -1
  12. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  13. package/docs/api/classes/PermissionDeniedError.md +1 -1
  14. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  15. package/docs/api/classes/RBACAuditManager.md +1 -1
  16. package/docs/api/classes/RBACCache.md +1 -1
  17. package/docs/api/classes/RBACEngine.md +1 -1
  18. package/docs/api/classes/RBACError.md +1 -1
  19. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  20. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  21. package/docs/api/interfaces/AggregateConfig.md +1 -1
  22. package/docs/api/interfaces/ButtonProps.md +1 -1
  23. package/docs/api/interfaces/CardProps.md +1 -1
  24. package/docs/api/interfaces/ColorPalette.md +1 -1
  25. package/docs/api/interfaces/ColorShade.md +1 -1
  26. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  27. package/docs/api/interfaces/DataTableAction.md +1 -1
  28. package/docs/api/interfaces/DataTableColumn.md +1 -1
  29. package/docs/api/interfaces/DataTableProps.md +34 -34
  30. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  31. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  32. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  33. package/docs/api/interfaces/EventContextType.md +1 -1
  34. package/docs/api/interfaces/EventLogoProps.md +1 -1
  35. package/docs/api/interfaces/EventProviderProps.md +1 -1
  36. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  37. package/docs/api/interfaces/FileUploadProps.md +1 -1
  38. package/docs/api/interfaces/FooterProps.md +1 -1
  39. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  40. package/docs/api/interfaces/InputProps.md +1 -1
  41. package/docs/api/interfaces/LabelProps.md +1 -1
  42. package/docs/api/interfaces/LoginFormProps.md +1 -1
  43. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  44. package/docs/api/interfaces/NavigationContextType.md +1 -1
  45. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  46. package/docs/api/interfaces/NavigationItem.md +1 -1
  47. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  48. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  49. package/docs/api/interfaces/Organisation.md +1 -1
  50. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  51. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  52. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  53. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  54. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  55. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  56. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  57. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  58. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  59. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  60. package/docs/api/interfaces/PaletteData.md +1 -1
  61. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  62. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  63. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  64. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  65. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  66. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  67. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  68. package/docs/api/interfaces/RBACConfig.md +1 -1
  69. package/docs/api/interfaces/RBACContextType.md +1 -1
  70. package/docs/api/interfaces/RBACLogger.md +1 -1
  71. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  72. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  73. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  74. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  75. package/docs/api/interfaces/RouteConfig.md +1 -1
  76. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  77. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  78. package/docs/api/interfaces/StorageConfig.md +1 -1
  79. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  80. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  81. package/docs/api/interfaces/StorageListOptions.md +1 -1
  82. package/docs/api/interfaces/StorageListResult.md +1 -1
  83. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  84. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  85. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  86. package/docs/api/interfaces/StyleImport.md +1 -1
  87. package/docs/api/interfaces/ToastActionElement.md +1 -1
  88. package/docs/api/interfaces/ToastProps.md +1 -1
  89. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  90. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  91. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  92. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  93. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  94. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  95. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  96. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  97. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  98. package/docs/api/interfaces/UserEventAccess.md +1 -1
  99. package/docs/api/interfaces/UserMenuProps.md +1 -1
  100. package/docs/api/interfaces/UserProfile.md +1 -1
  101. package/docs/api/modules.md +3 -3
  102. package/docs/implementation-guides/data-tables.md +20 -0
  103. package/docs/quick-reference.md +9 -0
  104. package/docs/rbac/examples.md +4 -0
  105. package/package.json +1 -1
  106. package/src/__tests__/helpers/test-utils.tsx +147 -1
  107. package/src/components/DataTable/DataTable.tsx +20 -0
  108. package/src/components/DataTable/__tests__/DataTable.hooks.test 2.tsx +191 -0
  109. package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +191 -0
  110. package/src/components/DataTable/components/DataTableCore.tsx +167 -138
  111. package/src/components/Header/Header.test.tsx +1 -1
  112. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +1 -1
  113. package/src/hooks/__tests__/hooks.integration.test.tsx +575 -0
  114. package/src/hooks/__tests__/useApiFetch.unit.test.ts +115 -0
  115. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +133 -0
  116. package/src/hooks/__tests__/useDebounce.unit.test.ts +82 -0
  117. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +293 -0
  118. package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +385 -0
  119. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +286 -0
  120. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +838 -0
  121. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +104 -0
  122. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +633 -0
  123. package/src/hooks/__tests__/useRBAC.unit.test.ts +856 -0
  124. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +537 -0
  125. package/src/hooks/__tests__/useToast.unit.test.tsx +62 -0
  126. package/src/hooks/__tests__/useZodForm.unit.test.tsx +37 -0
  127. package/src/rbac/api.test.ts +511 -0
  128. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +843 -0
  129. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1007 -0
  130. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +806 -0
  131. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +741 -0
  132. package/src/rbac/hooks/useCan.test.ts +1 -1
  133. package/src/rbac/hooks/usePermissions.test.ts +10 -5
  134. package/src/rbac/hooks/useRBAC.test.ts +141 -93
  135. package/src/rbac/utils/__tests__/eventContext.test.ts +428 -0
  136. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +428 -0
  137. package/src/styles/core.css +3 -0
  138. package/src/utils/__tests__/appConfig.unit.test.ts +55 -0
  139. package/src/utils/__tests__/audit.unit.test.ts +69 -0
  140. package/src/utils/__tests__/auth-utils.unit.test.ts +70 -0
  141. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +317 -0
  142. package/src/utils/__tests__/cn.unit.test.ts +34 -0
  143. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +503 -0
  144. package/src/utils/__tests__/dynamicUtils.unit.test.ts +322 -0
  145. package/src/utils/__tests__/formatDate.unit.test.ts +109 -0
  146. package/src/utils/__tests__/formatting.unit.test.ts +66 -0
  147. package/src/utils/__tests__/index.unit.test.ts +251 -0
  148. package/src/utils/__tests__/lazyLoad.unit.test.tsx +309 -0
  149. package/src/utils/__tests__/organisationContext.unit.test.ts +192 -0
  150. package/src/utils/__tests__/performanceBudgets.unit.test.ts +259 -0
  151. package/src/utils/__tests__/permissionTypes.unit.test.ts +250 -0
  152. package/src/utils/__tests__/permissionUtils.unit.test.ts +362 -0
  153. package/src/utils/__tests__/sanitization.unit.test.ts +346 -0
  154. package/src/utils/__tests__/schemaUtils.unit.test.ts +441 -0
  155. package/src/utils/__tests__/secureDataAccess.unit.test.ts +334 -0
  156. package/src/utils/__tests__/secureErrors.unit.test.ts +377 -0
  157. package/src/utils/__tests__/secureStorage.unit.test.ts +293 -0
  158. package/src/utils/__tests__/security.unit.test.ts +127 -0
  159. package/src/utils/__tests__/securityMonitor.unit.test.ts +280 -0
  160. package/src/utils/__tests__/sessionTracking.unit.test.ts +356 -0
  161. package/src/utils/__tests__/validation.unit.test.ts +84 -0
  162. package/src/utils/__tests__/validationUtils.unit.test.ts +571 -0
  163. package/src/validation/__tests__/common.unit.test.ts +101 -0
  164. package/src/validation/__tests__/csrf.unit.test.ts +302 -0
  165. package/src/validation/__tests__/passwordSchema.unit.test 2.ts +98 -0
  166. package/src/validation/__tests__/passwordSchema.unit.test.ts +98 -0
  167. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +466 -0
  168. package/dist/chunk-M4RW7PIP.js.map +0 -1
  169. /package/dist/{DataTable-ZQDRE46Q.js.map → DataTable-3SSI644S.js.map} +0 -0
  170. /package/dist/{chunk-5H3C2SWM.js.map → chunk-RTCA5ZNK.js.map} +0 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * @file Event Context Utilities Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/EventContext/Tests
5
+ * @since 1.0.0
6
+ *
7
+ * Comprehensive tests for event context utilities in the RBAC system.
8
+ */
9
+
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import { SupabaseClient } from '@supabase/supabase-js';
12
+ import { Database } from '../../../types/database';
13
+ import { UUID, Scope } from '../../types';
14
+ import {
15
+ getOrganisationFromEvent,
16
+ createScopeFromEvent,
17
+ isEventBasedScope,
18
+ isValidEventBasedScope
19
+ } from '../eventContext';
20
+
21
+ // Mock Supabase client
22
+ const createMockSupabaseClient = () => {
23
+ const mockQuery = {
24
+ select: vi.fn().mockReturnThis(),
25
+ eq: vi.fn().mockReturnThis(),
26
+ single: vi.fn()
27
+ };
28
+
29
+ return {
30
+ from: vi.fn().mockReturnValue(mockQuery),
31
+ query: mockQuery
32
+ } as unknown as SupabaseClient<Database>;
33
+ };
34
+
35
+ describe('Event Context Utilities', () => {
36
+ let mockSupabase: SupabaseClient<Database>;
37
+ let mockQuery: any;
38
+
39
+ beforeEach(() => {
40
+ mockSupabase = createMockSupabaseClient();
41
+ mockQuery = mockSupabase.from('event');
42
+ });
43
+
44
+ afterEach(() => {
45
+ vi.clearAllMocks();
46
+ });
47
+
48
+ describe('getOrganisationFromEvent', () => {
49
+ it('should return organisation ID when event exists', async () => {
50
+ const eventId = 'event-123';
51
+ const organisationId = 'org-456';
52
+
53
+ mockQuery.single.mockResolvedValue({
54
+ data: { organisation_id: organisationId },
55
+ error: null
56
+ });
57
+
58
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
59
+
60
+ expect(result).toBe(organisationId);
61
+ expect(mockSupabase.from).toHaveBeenCalledWith('event');
62
+ expect(mockQuery.select).toHaveBeenCalledWith('organisation_id');
63
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', eventId);
64
+ expect(mockQuery.single).toHaveBeenCalled();
65
+ });
66
+
67
+ it('should return null when event does not exist', async () => {
68
+ const eventId = 'nonexistent-event';
69
+
70
+ mockQuery.single.mockResolvedValue({
71
+ data: null,
72
+ error: { message: 'Event not found' }
73
+ });
74
+
75
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
76
+
77
+ expect(result).toBeNull();
78
+ });
79
+
80
+ it('should return null when data is null', async () => {
81
+ const eventId = 'event-123';
82
+
83
+ mockQuery.single.mockResolvedValue({
84
+ data: null,
85
+ error: null
86
+ });
87
+
88
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
89
+
90
+ expect(result).toBeNull();
91
+ });
92
+
93
+ it('should handle database errors gracefully', async () => {
94
+ const eventId = 'event-123';
95
+ const dbError = new Error('Database connection failed');
96
+
97
+ mockQuery.single.mockRejectedValue(dbError);
98
+
99
+ await expect(getOrganisationFromEvent(mockSupabase, eventId))
100
+ .rejects.toThrow('Database connection failed');
101
+ });
102
+
103
+ it('should handle empty organisation_id', async () => {
104
+ const eventId = 'event-123';
105
+
106
+ mockQuery.single.mockResolvedValue({
107
+ data: { organisation_id: null },
108
+ error: null
109
+ });
110
+
111
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
112
+
113
+ expect(result).toBeNull();
114
+ });
115
+ });
116
+
117
+ describe('createScopeFromEvent', () => {
118
+ it('should create complete scope when event exists', async () => {
119
+ const eventId = 'event-123';
120
+ const organisationId = 'org-456';
121
+ const appId = 'app-789';
122
+
123
+ mockQuery.single.mockResolvedValue({
124
+ data: { organisation_id: organisationId },
125
+ error: null
126
+ });
127
+
128
+ const result = await createScopeFromEvent(mockSupabase, eventId, appId);
129
+
130
+ expect(result).toEqual({
131
+ organisationId,
132
+ eventId,
133
+ appId
134
+ });
135
+ });
136
+
137
+ it('should create scope without appId when not provided', async () => {
138
+ const eventId = 'event-123';
139
+ const organisationId = 'org-456';
140
+
141
+ mockQuery.single.mockResolvedValue({
142
+ data: { organisation_id: organisationId },
143
+ error: null
144
+ });
145
+
146
+ const result = await createScopeFromEvent(mockSupabase, eventId);
147
+
148
+ expect(result).toEqual({
149
+ organisationId,
150
+ eventId,
151
+ appId: undefined
152
+ });
153
+ });
154
+
155
+ it('should return null when event does not exist', async () => {
156
+ const eventId = 'nonexistent-event';
157
+
158
+ mockQuery.single.mockResolvedValue({
159
+ data: null,
160
+ error: { message: 'Event not found' }
161
+ });
162
+
163
+ const result = await createScopeFromEvent(mockSupabase, eventId);
164
+
165
+ expect(result).toBeNull();
166
+ });
167
+
168
+ it('should return null when organisation lookup fails', async () => {
169
+ const eventId = 'event-123';
170
+
171
+ mockQuery.single.mockResolvedValue({
172
+ data: { organisation_id: null },
173
+ error: null
174
+ });
175
+
176
+ const result = await createScopeFromEvent(mockSupabase, eventId);
177
+
178
+ expect(result).toBeNull();
179
+ });
180
+
181
+ it('should handle database errors gracefully', async () => {
182
+ const eventId = 'event-123';
183
+ const dbError = new Error('Database connection failed');
184
+
185
+ mockQuery.single.mockRejectedValue(dbError);
186
+
187
+ await expect(createScopeFromEvent(mockSupabase, eventId))
188
+ .rejects.toThrow('Database connection failed');
189
+ });
190
+ });
191
+
192
+ describe('isEventBasedScope', () => {
193
+ it('should return true for event-based scope (no organisationId, has eventId)', () => {
194
+ const scope: Scope = {
195
+ eventId: 'event-123',
196
+ appId: 'app-456'
197
+ };
198
+
199
+ expect(isEventBasedScope(scope)).toBe(true);
200
+ });
201
+
202
+ it('should return false when organisationId is present', () => {
203
+ const scope: Scope = {
204
+ organisationId: 'org-123',
205
+ eventId: 'event-123',
206
+ appId: 'app-456'
207
+ };
208
+
209
+ expect(isEventBasedScope(scope)).toBe(false);
210
+ });
211
+
212
+ it('should return false when eventId is missing', () => {
213
+ const scope: Scope = {
214
+ appId: 'app-456'
215
+ };
216
+
217
+ expect(isEventBasedScope(scope)).toBe(false);
218
+ });
219
+
220
+ it('should return false when both organisationId and eventId are missing', () => {
221
+ const scope: Scope = {
222
+ appId: 'app-456'
223
+ };
224
+
225
+ expect(isEventBasedScope(scope)).toBe(false);
226
+ });
227
+
228
+ it('should return false when eventId is null', () => {
229
+ const scope: Scope = {
230
+ eventId: null,
231
+ appId: 'app-456'
232
+ };
233
+
234
+ expect(isEventBasedScope(scope)).toBe(false);
235
+ });
236
+
237
+ it('should return false when eventId is undefined', () => {
238
+ const scope: Scope = {
239
+ eventId: undefined,
240
+ appId: 'app-456'
241
+ };
242
+
243
+ expect(isEventBasedScope(scope)).toBe(false);
244
+ });
245
+ });
246
+
247
+ describe('isValidEventBasedScope', () => {
248
+ it('should return true for valid event-based scope', () => {
249
+ const scope: Scope = {
250
+ eventId: 'event-123',
251
+ appId: 'app-456'
252
+ };
253
+
254
+ expect(isValidEventBasedScope(scope)).toBe(true);
255
+ });
256
+
257
+ it('should return false when eventId is missing', () => {
258
+ const scope: Scope = {
259
+ appId: 'app-456'
260
+ };
261
+
262
+ expect(isValidEventBasedScope(scope)).toBe(false);
263
+ });
264
+
265
+ it('should return false when eventId is null', () => {
266
+ const scope: Scope = {
267
+ eventId: null,
268
+ appId: 'app-456'
269
+ };
270
+
271
+ expect(isValidEventBasedScope(scope)).toBe(false);
272
+ });
273
+
274
+ it('should return false when eventId is undefined', () => {
275
+ const scope: Scope = {
276
+ eventId: undefined,
277
+ appId: 'app-456'
278
+ };
279
+
280
+ expect(isValidEventBasedScope(scope)).toBe(false);
281
+ });
282
+
283
+ it('should return false when organisationId is present (not event-based)', () => {
284
+ const scope: Scope = {
285
+ organisationId: 'org-123',
286
+ eventId: 'event-123',
287
+ appId: 'app-456'
288
+ };
289
+
290
+ expect(isValidEventBasedScope(scope)).toBe(false);
291
+ });
292
+
293
+ it('should return true for event-based scope without appId', () => {
294
+ const scope: Scope = {
295
+ eventId: 'event-123'
296
+ };
297
+
298
+ expect(isValidEventBasedScope(scope)).toBe(true);
299
+ });
300
+ });
301
+
302
+ describe('Edge Cases and Error Handling', () => {
303
+ it('should handle malformed event IDs', async () => {
304
+ const malformedEventId = '';
305
+
306
+ mockQuery.single.mockResolvedValue({
307
+ data: null,
308
+ error: { message: 'Invalid event ID' }
309
+ });
310
+
311
+ const result = await getOrganisationFromEvent(mockSupabase, malformedEventId);
312
+
313
+ expect(result).toBeNull();
314
+ });
315
+
316
+ it('should handle very long event IDs', async () => {
317
+ const longEventId = 'a'.repeat(1000);
318
+ const organisationId = 'org-456';
319
+
320
+ mockQuery.single.mockResolvedValue({
321
+ data: { organisation_id: organisationId },
322
+ error: null
323
+ });
324
+
325
+ const result = await getOrganisationFromEvent(mockSupabase, longEventId);
326
+
327
+ expect(result).toBe(organisationId);
328
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', longEventId);
329
+ });
330
+
331
+ it('should handle special characters in event IDs', async () => {
332
+ const specialEventId = 'event-123!@#$%^&*()';
333
+ const organisationId = 'org-456';
334
+
335
+ mockQuery.single.mockResolvedValue({
336
+ data: { organisation_id: organisationId },
337
+ error: null
338
+ });
339
+
340
+ const result = await getOrganisationFromEvent(mockSupabase, specialEventId);
341
+
342
+ expect(result).toBe(organisationId);
343
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', specialEventId);
344
+ });
345
+
346
+ it('should handle concurrent calls to getOrganisationFromEvent', async () => {
347
+ const eventId1 = 'event-123';
348
+ const eventId2 = 'event-456';
349
+ const organisationId1 = 'org-123';
350
+ const organisationId2 = 'org-456';
351
+
352
+ mockQuery.single
353
+ .mockResolvedValueOnce({
354
+ data: { organisation_id: organisationId1 },
355
+ error: null
356
+ })
357
+ .mockResolvedValueOnce({
358
+ data: { organisation_id: organisationId2 },
359
+ error: null
360
+ });
361
+
362
+ const [result1, result2] = await Promise.all([
363
+ getOrganisationFromEvent(mockSupabase, eventId1),
364
+ getOrganisationFromEvent(mockSupabase, eventId2)
365
+ ]);
366
+
367
+ expect(result1).toBe(organisationId1);
368
+ expect(result2).toBe(organisationId2);
369
+ });
370
+
371
+ it('should handle concurrent calls to createScopeFromEvent', async () => {
372
+ const eventId1 = 'event-123';
373
+ const eventId2 = 'event-456';
374
+ const organisationId1 = 'org-123';
375
+ const organisationId2 = 'org-456';
376
+ const appId1 = 'app-123';
377
+ const appId2 = 'app-456';
378
+
379
+ mockQuery.single
380
+ .mockResolvedValueOnce({
381
+ data: { organisation_id: organisationId1 },
382
+ error: null
383
+ })
384
+ .mockResolvedValueOnce({
385
+ data: { organisation_id: organisationId2 },
386
+ error: null
387
+ });
388
+
389
+ const [result1, result2] = await Promise.all([
390
+ createScopeFromEvent(mockSupabase, eventId1, appId1),
391
+ createScopeFromEvent(mockSupabase, eventId2, appId2)
392
+ ]);
393
+
394
+ expect(result1).toEqual({
395
+ organisationId: organisationId1,
396
+ eventId: eventId1,
397
+ appId: appId1
398
+ });
399
+ expect(result2).toEqual({
400
+ organisationId: organisationId2,
401
+ eventId: eventId2,
402
+ appId: appId2
403
+ });
404
+ });
405
+ });
406
+
407
+ describe('Type Safety', () => {
408
+ it('should handle UUID types correctly', () => {
409
+ const validUUID = '123e4567-e89b-12d3-a456-426614174000';
410
+ const scope: Scope = {
411
+ eventId: validUUID,
412
+ appId: validUUID
413
+ };
414
+
415
+ expect(isValidEventBasedScope(scope)).toBe(true);
416
+ });
417
+
418
+ it('should handle string types correctly', () => {
419
+ const stringId = 'event-123';
420
+ const scope: Scope = {
421
+ eventId: stringId,
422
+ appId: 'app-456'
423
+ };
424
+
425
+ expect(isValidEventBasedScope(scope)).toBe(true);
426
+ });
427
+ });
428
+ });
@@ -7,6 +7,9 @@
7
7
  --font-serif: "Open Sans", sans-serif;
8
8
  --font-mono: "Reddit Mono", monospace;
9
9
  }
10
+ @theme static {
11
+ --app-width: 90rem;
12
+ }
10
13
 
11
14
  @theme inline {
12
15
  /* Semantic token mapping */
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { setAppConfig, getAppConfig, getCurrentAppName, getCurrentAppId } from '../appConfig';
3
+
4
+ describe('App Configuration Utilities', () => {
5
+ beforeEach(() => {
6
+ // Clear any existing config by resetting the module
7
+ vi.resetModules();
8
+ });
9
+
10
+ it('should set app configuration', () => {
11
+ const config = {
12
+ appName: 'Test App',
13
+ appId: 'test-app'
14
+ };
15
+
16
+ setAppConfig(config);
17
+
18
+ // Verify the config was set by getting it back
19
+ const result = getAppConfig();
20
+ expect(result).toEqual(config);
21
+ });
22
+
23
+ it('should get app configuration', () => {
24
+ const config = {
25
+ appName: 'Test App',
26
+ appId: 'test-app'
27
+ };
28
+
29
+ setAppConfig(config);
30
+ const result = getAppConfig();
31
+ expect(result).toEqual(config);
32
+ });
33
+
34
+ it('should get current app name', () => {
35
+ const config = {
36
+ appName: 'Test App',
37
+ appId: 'test-app'
38
+ };
39
+
40
+ setAppConfig(config);
41
+ const result = getCurrentAppName();
42
+ expect(result).toBe('Test App');
43
+ });
44
+
45
+ it('should get current app id', () => {
46
+ const config = {
47
+ appName: 'Test App',
48
+ appId: 'test-app'
49
+ };
50
+
51
+ setAppConfig(config);
52
+ const result = getCurrentAppId();
53
+ expect(result).toBe('test-app');
54
+ });
55
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import {
3
+ auditLogger,
4
+ logAuthEvent,
5
+ logPermissionEvent,
6
+ logSecurityEvent
7
+ } from '../audit';
8
+
9
+ describe('Audit Logger Utility', () => {
10
+ beforeEach(() => {
11
+ auditLogger.clearEvents();
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ it('logs a generic event and stores it', () => {
16
+ auditLogger.log({ type: 'auth', action: 'login', user: 'alice' });
17
+ const events = auditLogger.getEvents();
18
+ expect(events.length).toBe(1);
19
+ expect(events[0].type).toBe('auth');
20
+ expect(events[0].action).toBe('login');
21
+ expect(events[0].user).toBe('alice');
22
+ });
23
+
24
+ it('logs an auth event via logAuthEvent', () => {
25
+ logAuthEvent('login', 'bob', { ip: '127.0.0.1' });
26
+ const events = auditLogger.getEvents();
27
+ expect(events.length).toBe(1);
28
+ expect(events[0].type).toBe('auth');
29
+ expect(events[0].action).toBe('login');
30
+ expect(events[0].user).toBe('bob');
31
+ expect(events[0].details).toEqual({ ip: '127.0.0.1' });
32
+ });
33
+
34
+ it('logs a permission event via logPermissionEvent', () => {
35
+ logPermissionEvent('grant', 'carol', { resource: 'file' });
36
+ const events = auditLogger.getEvents();
37
+ expect(events.length).toBe(1);
38
+ expect(events[0].type).toBe('permission');
39
+ expect(events[0].action).toBe('grant');
40
+ expect(events[0].user).toBe('carol');
41
+ expect(events[0].details).toEqual({ resource: 'file' });
42
+ });
43
+
44
+ it('logs a security event via logSecurityEvent', () => {
45
+ logSecurityEvent('breach', 'dave', { severity: 'high' });
46
+ const events = auditLogger.getEvents();
47
+ expect(events.length).toBe(1);
48
+ expect(events[0].type).toBe('security');
49
+ expect(events[0].action).toBe('breach');
50
+ expect(events[0].user).toBe('dave');
51
+ expect(events[0].details).toEqual({ severity: 'high' });
52
+ });
53
+
54
+ it('can retrieve only security events', async () => {
55
+ auditLogger.log({ type: 'auth', action: 'login' });
56
+ auditLogger.log({ type: 'security', action: 'breach' });
57
+ auditLogger.log({ type: 'security', action: 'scan' });
58
+ const securityEvents = await auditLogger.getSecurityEvents();
59
+ expect(securityEvents.length).toBe(2);
60
+ expect(securityEvents.every(e => e.type === 'security')).toBe(true);
61
+ });
62
+
63
+ it('clears events', () => {
64
+ auditLogger.log({ type: 'auth', action: 'login' });
65
+ expect(auditLogger.getEvents().length).toBe(1);
66
+ auditLogger.clearEvents();
67
+ expect(auditLogger.getEvents().length).toBe(0);
68
+ });
69
+ });
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import {
3
+ signInWithEmail,
4
+ signUpWithEmail,
5
+ resetPassword,
6
+ updatePassword,
7
+ auditLogger
8
+ } from '../auth-utils';
9
+
10
+ describe('Auth Utils', () => {
11
+ beforeEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+
15
+ it('signInWithEmail logs and returns error', async () => {
16
+ const result = await signInWithEmail({ email: 'test@example.com', password: 'pw' });
17
+ expect(result).toHaveProperty('error');
18
+ expect(result.error).toBeInstanceOf(Error);
19
+ });
20
+
21
+ it('signUpWithEmail logs and returns error', async () => {
22
+ const result = await signUpWithEmail({ email: 'new@example.com', password: 'pw' });
23
+ expect(result).toHaveProperty('error');
24
+ expect(result.error).toBeInstanceOf(Error);
25
+ });
26
+
27
+ it('resetPassword logs and returns error', async () => {
28
+ const result = await resetPassword('reset@example.com');
29
+ expect(result).toHaveProperty('error');
30
+ expect(result.error).toBeInstanceOf(Error);
31
+ });
32
+
33
+ it('updatePassword calls supabaseClient.auth.updateUser and returns error if thrown', async () => {
34
+ const updateUser = vi.fn().mockResolvedValue({ error: undefined });
35
+ const supabaseClient = { auth: { updateUser } } as any;
36
+ const result = await updatePassword(supabaseClient, 'newpw');
37
+ expect(updateUser).toHaveBeenCalledWith({ password: 'newpw' });
38
+ expect(result).toEqual({ error: undefined });
39
+ });
40
+
41
+ it('updatePassword returns error if thrown', async () => {
42
+ const updateUser = vi.fn().mockRejectedValue(new Error('fail'));
43
+ const supabaseClient = { auth: { updateUser } } as any;
44
+ const result = await updatePassword(supabaseClient, 'pw');
45
+ expect(result.error).toBeInstanceOf(Error);
46
+ if (result.error) {
47
+ expect(result.error.message).toBe('fail');
48
+ }
49
+ });
50
+
51
+ it('auditLogger.log logs event', () => {
52
+ // Test that the function exists and can be called without error
53
+ expect(() => auditLogger.log('event', { foo: 'bar' })).not.toThrow();
54
+ });
55
+
56
+ it('auditLogger.logAuthEvent logs auth event', () => {
57
+ // Test that the function exists and can be called without error
58
+ expect(() => auditLogger.logAuthEvent('user1', 'LOGIN', { ip: '1.2.3.4' })).not.toThrow();
59
+ });
60
+
61
+ it('auditLogger.logPermissionEvent logs permission check', () => {
62
+ // Test that the function exists and can be called without error
63
+ expect(() => auditLogger.logPermissionEvent('user2', 'admin', true)).not.toThrow();
64
+ });
65
+
66
+ it('auditLogger.logEvent logs generic event', () => {
67
+ // Test that the function exists and can be called without error
68
+ expect(() => auditLogger.logEvent({ action: 'SOMETHING', details: { foo: 1 } })).not.toThrow();
69
+ });
70
+ });