@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,356 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import type { SupabaseClient } from '@supabase/supabase-js';
3
+
4
+ describe('sessionTracking', () => {
5
+ let consoleSpy: {
6
+ log: ReturnType<typeof vi.spyOn>;
7
+ warn: ReturnType<typeof vi.spyOn>;
8
+ error: ReturnType<typeof vi.spyOn>;
9
+ };
10
+ let mockSupabase: SupabaseClient;
11
+ let trackingFunctions: any;
12
+
13
+ beforeEach(() => {
14
+ // Mock console methods before each test
15
+ consoleSpy = {
16
+ log: vi.spyOn(console, 'log').mockImplementation(() => {}),
17
+ warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
18
+ error: vi.spyOn(console, 'error').mockImplementation(() => {})
19
+ };
20
+
21
+ // Create a mock Supabase client
22
+ mockSupabase = {
23
+ rpc: vi.fn(),
24
+ auth: {
25
+ getUser: vi.fn()
26
+ }
27
+ } as unknown as SupabaseClient;
28
+ });
29
+
30
+ afterEach(() => {
31
+ vi.restoreAllMocks();
32
+ });
33
+
34
+ describe('trackLogin', () => {
35
+ it('should track login successfully', async () => {
36
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
37
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
38
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
39
+
40
+ mockSupabase.auth.getUser = mockGetUser;
41
+ mockSupabase.rpc = mockRpc;
42
+
43
+ // Import the module after setting up mocks
44
+ vi.resetModules();
45
+ const { useSessionTracking } = await import('../sessionTracking');
46
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
47
+
48
+ await trackingFunctions.trackLogin('event-123');
49
+
50
+ expect(mockGetUser).toHaveBeenCalled();
51
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
52
+ p_session_type: 'login',
53
+ p_event_id: 'event-123',
54
+ p_app_id: undefined
55
+ });
56
+ expect(consoleSpy.log).toHaveBeenCalledWith('Login session tracked successfully');
57
+ });
58
+
59
+ it('should track login without event ID', async () => {
60
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
61
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
62
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
63
+
64
+ mockSupabase.auth.getUser = mockGetUser;
65
+ mockSupabase.rpc = mockRpc;
66
+
67
+ vi.resetModules();
68
+ const { useSessionTracking } = await import('../sessionTracking');
69
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
70
+
71
+ await trackingFunctions.trackLogin();
72
+
73
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
74
+ p_session_type: 'login',
75
+ p_event_id: undefined,
76
+ p_app_id: undefined
77
+ });
78
+ });
79
+
80
+ it('should handle no authenticated user', async () => {
81
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: null } });
82
+ mockSupabase.auth.getUser = mockGetUser;
83
+
84
+ vi.resetModules();
85
+ const { useSessionTracking } = await import('../sessionTracking');
86
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
87
+
88
+ await trackingFunctions.trackLogin();
89
+
90
+ expect(consoleSpy.warn).toHaveBeenCalledWith('No authenticated user found for session tracking');
91
+ });
92
+
93
+ it('should handle tracking error', async () => {
94
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
95
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
96
+ const mockRpc = vi.fn().mockResolvedValue({ error: { message: 'Database error' } });
97
+
98
+ mockSupabase.auth.getUser = mockGetUser;
99
+ mockSupabase.rpc = mockRpc;
100
+
101
+ vi.resetModules();
102
+ const { useSessionTracking } = await import('../sessionTracking');
103
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
104
+
105
+ await trackingFunctions.trackLogin();
106
+
107
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track login session:', { message: 'Database error' });
108
+ });
109
+
110
+ it('should handle unexpected errors', async () => {
111
+ const mockGetUser = vi.fn().mockRejectedValue(new Error('Auth error'));
112
+ mockSupabase.auth.getUser = mockGetUser;
113
+
114
+ vi.resetModules();
115
+ const { useSessionTracking } = await import('../sessionTracking');
116
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
117
+
118
+ await trackingFunctions.trackLogin();
119
+
120
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track login:', expect.any(Error));
121
+ });
122
+ });
123
+
124
+ describe('trackEventSwitch', () => {
125
+ it('should track event switch successfully', async () => {
126
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
127
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
128
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
129
+
130
+ mockSupabase.auth.getUser = mockGetUser;
131
+ mockSupabase.rpc = mockRpc;
132
+
133
+ vi.resetModules();
134
+ const { useSessionTracking } = await import('../sessionTracking');
135
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
136
+
137
+ await trackingFunctions.trackEventSwitch('event-456');
138
+
139
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
140
+ p_session_type: 'event_switch',
141
+ p_event_id: 'event-456',
142
+ p_app_id: undefined
143
+ });
144
+ expect(consoleSpy.log).toHaveBeenCalledWith('Event switch session tracked successfully');
145
+ });
146
+
147
+ it('should handle no authenticated user', async () => {
148
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: null } });
149
+ mockSupabase.auth.getUser = mockGetUser;
150
+
151
+ vi.resetModules();
152
+ const { useSessionTracking } = await import('../sessionTracking');
153
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
154
+
155
+ await trackingFunctions.trackEventSwitch('event-456');
156
+
157
+ expect(consoleSpy.warn).toHaveBeenCalledWith('No authenticated user found for session tracking');
158
+ });
159
+
160
+ it('should handle tracking error', async () => {
161
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
162
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
163
+ const mockRpc = vi.fn().mockResolvedValue({ error: { message: 'Database error' } });
164
+
165
+ mockSupabase.auth.getUser = mockGetUser;
166
+ mockSupabase.rpc = mockRpc;
167
+
168
+ vi.resetModules();
169
+ const { useSessionTracking } = await import('../sessionTracking');
170
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
171
+
172
+ await trackingFunctions.trackEventSwitch('event-456');
173
+
174
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track event switch session:', { message: 'Database error' });
175
+ });
176
+
177
+ it('should handle unexpected errors', async () => {
178
+ const mockGetUser = vi.fn().mockRejectedValue(new Error('Auth error'));
179
+ mockSupabase.auth.getUser = mockGetUser;
180
+
181
+ vi.resetModules();
182
+ const { useSessionTracking } = await import('../sessionTracking');
183
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
184
+
185
+ await trackingFunctions.trackEventSwitch('event-456');
186
+
187
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track event switch:', expect.any(Error));
188
+ });
189
+ });
190
+
191
+ describe('trackLogout', () => {
192
+ it('should track logout successfully', async () => {
193
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
194
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
195
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
196
+
197
+ mockSupabase.auth.getUser = mockGetUser;
198
+ mockSupabase.rpc = mockRpc;
199
+
200
+ vi.resetModules();
201
+ const { useSessionTracking } = await import('../sessionTracking');
202
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
203
+
204
+ await trackingFunctions.trackLogout();
205
+
206
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
207
+ p_session_type: 'logout',
208
+ p_app_id: undefined
209
+ });
210
+ expect(consoleSpy.log).toHaveBeenCalledWith('Logout session tracked successfully');
211
+ });
212
+
213
+ it('should handle no authenticated user', async () => {
214
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: null } });
215
+ mockSupabase.auth.getUser = mockGetUser;
216
+
217
+ vi.resetModules();
218
+ const { useSessionTracking } = await import('../sessionTracking');
219
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
220
+
221
+ await trackingFunctions.trackLogout();
222
+
223
+ expect(consoleSpy.warn).toHaveBeenCalledWith('No authenticated user found for session tracking');
224
+ });
225
+
226
+ it('should handle tracking error', async () => {
227
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
228
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
229
+ const mockRpc = vi.fn().mockResolvedValue({ error: { message: 'Database error' } });
230
+
231
+ mockSupabase.auth.getUser = mockGetUser;
232
+ mockSupabase.rpc = mockRpc;
233
+
234
+ vi.resetModules();
235
+ const { useSessionTracking } = await import('../sessionTracking');
236
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
237
+
238
+ await trackingFunctions.trackLogout();
239
+
240
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track logout session:', { message: 'Database error' });
241
+ });
242
+
243
+ it('should handle unexpected errors', async () => {
244
+ const mockGetUser = vi.fn().mockRejectedValue(new Error('Auth error'));
245
+ mockSupabase.auth.getUser = mockGetUser;
246
+
247
+ vi.resetModules();
248
+ const { useSessionTracking } = await import('../sessionTracking');
249
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
250
+
251
+ await trackingFunctions.trackLogout();
252
+
253
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track logout:', expect.any(Error));
254
+ });
255
+ });
256
+
257
+ describe('trackSessionExpired', () => {
258
+ it('should track session expiration successfully', async () => {
259
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
260
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
261
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
262
+
263
+ mockSupabase.auth.getUser = mockGetUser;
264
+ mockSupabase.rpc = mockRpc;
265
+
266
+ vi.resetModules();
267
+ const { useSessionTracking } = await import('../sessionTracking');
268
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
269
+
270
+ await trackingFunctions.trackSessionExpired();
271
+
272
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
273
+ p_session_type: 'session_expired',
274
+ p_app_id: undefined
275
+ });
276
+ expect(consoleSpy.log).toHaveBeenCalledWith('Session expiration tracked successfully');
277
+ });
278
+
279
+ it('should handle no authenticated user', async () => {
280
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: null } });
281
+ mockSupabase.auth.getUser = mockGetUser;
282
+
283
+ vi.resetModules();
284
+ const { useSessionTracking } = await import('../sessionTracking');
285
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
286
+
287
+ await trackingFunctions.trackSessionExpired();
288
+
289
+ expect(consoleSpy.warn).toHaveBeenCalledWith('No authenticated user found for session tracking');
290
+ });
291
+
292
+ it('should handle tracking error', async () => {
293
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
294
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
295
+ const mockRpc = vi.fn().mockResolvedValue({ error: { message: 'Database error' } });
296
+
297
+ mockSupabase.auth.getUser = mockGetUser;
298
+ mockSupabase.rpc = mockRpc;
299
+
300
+ vi.resetModules();
301
+ const { useSessionTracking } = await import('../sessionTracking');
302
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
303
+
304
+ await trackingFunctions.trackSessionExpired();
305
+
306
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track session expiration:', { message: 'Database error' });
307
+ });
308
+
309
+ it('should handle unexpected errors', async () => {
310
+ const mockGetUser = vi.fn().mockRejectedValue(new Error('Auth error'));
311
+ mockSupabase.auth.getUser = mockGetUser;
312
+
313
+ vi.resetModules();
314
+ const { useSessionTracking } = await import('../sessionTracking');
315
+ trackingFunctions = useSessionTracking(mockSupabase, 'test-app');
316
+
317
+ await trackingFunctions.trackSessionExpired();
318
+
319
+ expect(consoleSpy.error).toHaveBeenCalledWith('Failed to track session expiration:', expect.any(Error));
320
+ });
321
+ });
322
+
323
+ describe('useSessionTracking without app name', () => {
324
+ it('should work without app name parameter', async () => {
325
+ vi.resetModules();
326
+ const { useSessionTracking } = await import('../sessionTracking');
327
+ const trackingWithoutApp = useSessionTracking(mockSupabase);
328
+
329
+ expect(trackingWithoutApp).toHaveProperty('trackLogin');
330
+ expect(trackingWithoutApp).toHaveProperty('trackEventSwitch');
331
+ expect(trackingWithoutApp).toHaveProperty('trackLogout');
332
+ expect(trackingWithoutApp).toHaveProperty('trackSessionExpired');
333
+ });
334
+
335
+ it('should pass undefined app name to tracking calls', async () => {
336
+ const mockUser = { id: 'user-123', email: 'test@example.com' };
337
+ const mockGetUser = vi.fn().mockResolvedValue({ data: { user: mockUser } });
338
+ const mockRpc = vi.fn().mockResolvedValue({ error: null });
339
+
340
+ mockSupabase.auth.getUser = mockGetUser;
341
+ mockSupabase.rpc = mockRpc;
342
+
343
+ vi.resetModules();
344
+ const { useSessionTracking } = await import('../sessionTracking');
345
+ const trackingWithoutApp = useSessionTracking(mockSupabase);
346
+
347
+ await trackingWithoutApp.trackLogin();
348
+
349
+ expect(mockRpc).toHaveBeenCalledWith('track_user_session', {
350
+ p_session_type: 'login',
351
+ p_event_id: undefined,
352
+ p_app_name: undefined
353
+ });
354
+ });
355
+ });
356
+ });
@@ -0,0 +1,84 @@
1
+
2
+ import { describe, it, expect } from 'vitest';
3
+ import {
4
+ isValidEmail,
5
+ isEmpty,
6
+ isStrongPassword,
7
+ isValidUrl,
8
+ isValidDate,
9
+ isWithinRange,
10
+ matchesPattern
11
+ } from '../validation';
12
+
13
+ describe('validation utilities', () => {
14
+ describe('isValidEmail', () => {
15
+ it('validates correct emails', () => {
16
+ expect(isValidEmail('test@example.com')).toBe(true);
17
+ expect(isValidEmail('user.name+tag@domain.co')).toBe(true);
18
+ });
19
+ it('invalidates incorrect emails', () => {
20
+ expect(isValidEmail('not-an-email')).toBe(false);
21
+ expect(isValidEmail('user@.com')).toBe(false);
22
+ });
23
+ });
24
+
25
+ describe('isEmpty', () => {
26
+ it('returns true for null, undefined, or whitespace', () => {
27
+ expect(isEmpty(null)).toBe(true);
28
+ expect(isEmpty(undefined)).toBe(true);
29
+ expect(isEmpty(' ')).toBe(true);
30
+ });
31
+ it('returns false for non-empty strings', () => {
32
+ expect(isEmpty('hello')).toBe(false);
33
+ });
34
+ });
35
+
36
+ describe('isStrongPassword', () => {
37
+ it('validates strong passwords', () => {
38
+ expect(isStrongPassword('Abcdefg1')).toBe(true);
39
+ });
40
+ it('invalidates weak passwords', () => {
41
+ expect(isStrongPassword('abcdefg')).toBe(false);
42
+ expect(isStrongPassword('ABCDEFG1')).toBe(false);
43
+ expect(isStrongPassword('abc1')).toBe(false);
44
+ });
45
+ });
46
+
47
+ describe('isValidUrl', () => {
48
+ it('validates correct URLs', () => {
49
+ expect(isValidUrl('https://example.com')).toBe(true);
50
+ expect(isValidUrl('http://localhost:3000')).toBe(true);
51
+ });
52
+ it('invalidates incorrect URLs', () => {
53
+ expect(isValidUrl('not-a-url')).toBe(false);
54
+ });
55
+ });
56
+
57
+ describe('isValidDate', () => {
58
+ it('validates correct date strings', () => {
59
+ expect(isValidDate('2023-01-01')).toBe(true);
60
+ });
61
+ it('invalidates incorrect date strings', () => {
62
+ expect(isValidDate('not-a-date')).toBe(false);
63
+ });
64
+ });
65
+
66
+ describe('isWithinRange', () => {
67
+ it('returns true if value is within range', () => {
68
+ expect(isWithinRange(5, 1, 10)).toBe(true);
69
+ });
70
+ it('returns false if value is outside range', () => {
71
+ expect(isWithinRange(0, 1, 10)).toBe(false);
72
+ expect(isWithinRange(11, 1, 10)).toBe(false);
73
+ });
74
+ });
75
+
76
+ describe('matchesPattern', () => {
77
+ it('returns true if value matches pattern', () => {
78
+ expect(matchesPattern('abc123', /^[a-z]+\d+$/)).toBe(true);
79
+ });
80
+ it('returns false if value does not match pattern', () => {
81
+ expect(matchesPattern('123abc', /^[a-z]+\d+$/)).toBe(false);
82
+ });
83
+ });
84
+ });