@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.
- package/dist/{DataTable-ZQDRE46Q.js → DataTable-3SSI644S.js} +2 -2
- package/dist/{chunk-M4RW7PIP.js → chunk-2BJFM2JC.js} +105 -81
- package/dist/chunk-2BJFM2JC.js.map +1 -0
- package/dist/{chunk-5H3C2SWM.js → chunk-RTCA5ZNK.js} +2 -2
- package/dist/components.js +2 -2
- package/dist/index.js +2 -2
- package/dist/styles/core.css +3 -0
- package/dist/utils.js +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +34 -34
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +3 -3
- package/docs/implementation-guides/data-tables.md +20 -0
- package/docs/quick-reference.md +9 -0
- package/docs/rbac/examples.md +4 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/test-utils.tsx +147 -1
- package/src/components/DataTable/DataTable.tsx +20 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test 2.tsx +191 -0
- package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +191 -0
- package/src/components/DataTable/components/DataTableCore.tsx +167 -138
- package/src/components/Header/Header.test.tsx +1 -1
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +1 -1
- package/src/hooks/__tests__/hooks.integration.test.tsx +575 -0
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +115 -0
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +133 -0
- package/src/hooks/__tests__/useDebounce.unit.test.ts +82 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +293 -0
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +385 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +286 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +838 -0
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +104 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +633 -0
- package/src/hooks/__tests__/useRBAC.unit.test.ts +856 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +537 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +62 -0
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +37 -0
- package/src/rbac/api.test.ts +511 -0
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +843 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1007 -0
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +806 -0
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +741 -0
- package/src/rbac/hooks/useCan.test.ts +1 -1
- package/src/rbac/hooks/usePermissions.test.ts +10 -5
- package/src/rbac/hooks/useRBAC.test.ts +141 -93
- package/src/rbac/utils/__tests__/eventContext.test.ts +428 -0
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +428 -0
- package/src/styles/core.css +3 -0
- package/src/utils/__tests__/appConfig.unit.test.ts +55 -0
- package/src/utils/__tests__/audit.unit.test.ts +69 -0
- package/src/utils/__tests__/auth-utils.unit.test.ts +70 -0
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +317 -0
- package/src/utils/__tests__/cn.unit.test.ts +34 -0
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +503 -0
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +322 -0
- package/src/utils/__tests__/formatDate.unit.test.ts +109 -0
- package/src/utils/__tests__/formatting.unit.test.ts +66 -0
- package/src/utils/__tests__/index.unit.test.ts +251 -0
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +309 -0
- package/src/utils/__tests__/organisationContext.unit.test.ts +192 -0
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +259 -0
- package/src/utils/__tests__/permissionTypes.unit.test.ts +250 -0
- package/src/utils/__tests__/permissionUtils.unit.test.ts +362 -0
- package/src/utils/__tests__/sanitization.unit.test.ts +346 -0
- package/src/utils/__tests__/schemaUtils.unit.test.ts +441 -0
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +334 -0
- package/src/utils/__tests__/secureErrors.unit.test.ts +377 -0
- package/src/utils/__tests__/secureStorage.unit.test.ts +293 -0
- package/src/utils/__tests__/security.unit.test.ts +127 -0
- package/src/utils/__tests__/securityMonitor.unit.test.ts +280 -0
- package/src/utils/__tests__/sessionTracking.unit.test.ts +356 -0
- package/src/utils/__tests__/validation.unit.test.ts +84 -0
- package/src/utils/__tests__/validationUtils.unit.test.ts +571 -0
- package/src/validation/__tests__/common.unit.test.ts +101 -0
- package/src/validation/__tests__/csrf.unit.test.ts +302 -0
- package/src/validation/__tests__/passwordSchema.unit.test 2.ts +98 -0
- package/src/validation/__tests__/passwordSchema.unit.test.ts +98 -0
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +466 -0
- package/dist/chunk-M4RW7PIP.js.map +0 -1
- /package/dist/{DataTable-ZQDRE46Q.js.map → DataTable-3SSI644S.js.map} +0 -0
- /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
|
+
});
|