@jmruthers/pace-core 0.5.73 → 0.5.75
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-INW5YIFV.js → DataTable-HWZQGASI.js} +8 -8
- package/dist/{PublicLoadingSpinner-DLpF5bbs.d.ts → PublicLoadingSpinner-BKNBT6b6.d.ts} +2 -2
- package/dist/RBACService-C4udt_Zp.d.ts +528 -0
- package/dist/{UnifiedAuthProvider-6SYT5WFN.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
- package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
- package/dist/{chunk-2PRPDH66.js → chunk-2CHATWBF.js} +5 -7
- package/dist/chunk-2CHATWBF.js.map +1 -0
- package/dist/{chunk-43C63KLH.js → chunk-2DFZ432F.js} +496 -30
- package/dist/chunk-2DFZ432F.js.map +1 -0
- package/dist/{chunk-M4UMXYNK.js → chunk-33PHABLB.js} +36 -3
- package/dist/chunk-33PHABLB.js.map +1 -0
- package/dist/chunk-5F3NDPJV.js +232 -0
- package/dist/chunk-5F3NDPJV.js.map +1 -0
- package/dist/chunk-A4FUBC7B.js +17 -0
- package/dist/chunk-A4FUBC7B.js.map +1 -0
- package/dist/{chunk-SMJZMKYN.js → chunk-A6HBIY5P.js} +2 -11
- package/dist/{chunk-SMJZMKYN.js.map → chunk-A6HBIY5P.js.map} +1 -1
- package/dist/{chunk-GBC5PC3N.js → chunk-CY3AHGO4.js} +6256 -1937
- package/dist/chunk-CY3AHGO4.js.map +1 -0
- package/dist/{chunk-BYG6OSTC.js → chunk-DAXLNIDY.js} +48 -50
- package/dist/chunk-DAXLNIDY.js.map +1 -0
- package/dist/{chunk-VKOCWWVY.js → chunk-L3RV2ALE.js} +1 -6
- package/dist/{chunk-VKOCWWVY.js.map → chunk-L3RV2ALE.js.map} +1 -1
- package/dist/chunk-LW7MMEAQ.js +59 -0
- package/dist/chunk-LW7MMEAQ.js.map +1 -0
- package/dist/{chunk-LANO5IFV.js → chunk-NTNILOBC.js} +7 -9
- package/dist/chunk-NTNILOBC.js.map +1 -0
- package/dist/chunk-PYUXFQJ3.js +11 -0
- package/dist/chunk-PYUXFQJ3.js.map +1 -0
- package/dist/chunk-URUTVZ7N.js +27 -0
- package/dist/chunk-URUTVZ7N.js.map +1 -0
- package/dist/chunk-WN6XJWOS.js +2468 -0
- package/dist/chunk-WN6XJWOS.js.map +1 -0
- package/dist/{chunk-3SP4P7NS.js → chunk-XLZ7U46Z.js} +59 -1
- package/dist/chunk-XLZ7U46Z.js.map +1 -0
- package/dist/{chunk-UC2BWIK7.js → chunk-ZTT2AXMX.js} +9 -14
- package/dist/chunk-ZTT2AXMX.js.map +1 -0
- package/dist/components.d.ts +4 -5
- package/dist/components.js +32 -39
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +156 -10
- package/dist/index.js +188 -93
- package/dist/index.js.map +1 -1
- package/dist/{organisation-t-vvQC3g.d.ts → organisation-BtshODVF.d.ts} +4 -3
- package/dist/providers.d.ts +27 -38
- package/dist/providers.js +33 -23
- package/dist/rbac/index.d.ts +61 -5
- package/dist/rbac/index.js +13 -14
- package/dist/styles/index.js +2 -2
- package/dist/theming/runtime.js +1 -3
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/{unified-CMPjE_fv.d.ts → unified-CM7T0aTK.d.ts} +1 -1
- package/dist/useInactivityTracker-MRUU55XI.js +10 -0
- package/dist/useInactivityTracker-MRUU55XI.js.map +1 -0
- package/dist/{usePublicRouteParams-Ua1Vz-HG.d.ts → usePublicRouteParams-B-CumWRc.d.ts} +3 -3
- package/dist/utils.js +7 -9
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/docs/api/classes/ColumnFactory.md +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/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +3 -3
- package/docs/api/interfaces/CardProps.md +2 -2
- 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 +1 -1
- 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/EventLogoProps.md +2 -2
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.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 +2 -2
- 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 +28 -17
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +2 -2
- 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 +2 -2
- 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 +5 -11
- 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/SwitchProps.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 +524 -440
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +14 -14
- 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 +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +179 -52
- package/docs/architecture/services.md +30 -32
- package/docs/breaking-changes.md +2 -5
- package/docs/implementation-guides/data-tables.md +82 -1
- package/docs/migration/service-architecture.md +121 -260
- package/docs/rbac/README-rbac-rls-integration.md +48 -38
- package/{src/rbac/examples → examples/RBAC}/CompleteRBACExample.tsx +3 -2
- package/{src/rbac/examples → examples/RBAC}/EventBasedApp.tsx +5 -4
- package/{src/components/examples → examples/RBAC}/PermissionExample.tsx +7 -6
- package/examples/RBAC/__tests__/PermissionExample.test.tsx +150 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/README.md +37 -0
- package/examples/index.ts +22 -0
- package/{src/examples → examples/public-pages}/CorrectPublicPageImplementation.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicEventPage.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageApp.tsx +1 -1
- package/{src/examples → examples/public-pages}/PublicPageUsageExample.tsx +1 -1
- package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +159 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +22 -18
- package/src/__tests__/TEST_GUIDE_CURSOR.md +650 -9
- package/src/__tests__/helpers/README.md +255 -0
- package/src/__tests__/helpers/index.ts +62 -0
- package/src/__tests__/helpers/supabaseMock.ts +27 -3
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -8
- package/src/components/DataTable/components/DataTableCore.tsx +37 -3
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +55 -0
- package/src/components/DataTable/core/ColumnManager.ts +10 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +254 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +193 -0
- package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +45 -0
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +117 -0
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +71 -0
- package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +122 -0
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/Header/Header.test.tsx +35 -1
- package/src/components/Header/Header.tsx +3 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -3
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +24 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +3 -2
- package/src/components/Toast/Toast.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/hooks/__tests__/useFocusManagement.unit.test.ts +220 -0
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +117 -0
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +295 -0
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +29 -19
- package/src/hooks/__tests__/useRBAC.unit.test.ts +7 -3
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +115 -19
- package/src/hooks/useEventTheme.test.ts +350 -0
- package/src/hooks/useEventTheme.ts +1 -1
- package/src/hooks/useEvents.ts +61 -0
- package/src/hooks/useOrganisationSecurity.test.ts +4 -4
- package/src/hooks/useOrganisationSecurity.ts +2 -2
- package/src/hooks/useOrganisations.ts +64 -0
- package/src/hooks/useSecureDataAccess.test.ts +9 -5
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +18 -3
- package/src/providers/AuthProvider.tsx +8 -292
- package/src/providers/EventProvider.tsx +15 -425
- package/src/providers/InactivityProvider.tsx +8 -231
- package/src/providers/OrganisationProvider.test.simple.tsx +3 -2
- package/src/providers/OrganisationProvider.tsx +11 -890
- package/src/providers/UnifiedAuthProvider.tsx +8 -320
- package/src/providers/__tests__/AuthProvider.test.tsx +18 -17
- package/src/providers/__tests__/EventProvider.test.tsx +253 -2
- package/src/providers/__tests__/InactivityProvider.test-helper.tsx +65 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +46 -114
- package/src/providers/__tests__/OrganisationProvider.test.tsx +313 -3
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +383 -2
- package/src/providers/index.ts +8 -7
- package/src/providers/services/EventServiceProvider.tsx +3 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +3 -0
- package/src/rbac/hooks/usePermissions.test.ts +296 -0
- package/src/rbac/hooks/useRBAC.test.ts +9 -5
- package/src/rbac/hooks/useRBAC.ts +3 -3
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +688 -0
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +507 -0
- package/src/services/AuthService.ts +19 -4
- package/src/services/__tests__/AuthService.test.ts +288 -0
- package/src/styles/core.css +2 -0
- package/src/types/__tests__/guards.test.ts +246 -0
- package/src/types/guards.ts +1 -0
- package/src/types/organisation.ts +3 -2
- package/src/validation/__tests__/sanitization.unit.test.ts +250 -0
- package/src/validation/__tests__/schemaUtils.unit.test.ts +451 -0
- package/src/validation/__tests__/user.unit.test.ts +440 -0
- package/dist/RBACProvider-BO4ilsQB.d.ts +0 -63
- package/dist/UnifiedAuthProvider-D02AMXgO.d.ts +0 -103
- package/dist/chunk-2PRPDH66.js.map +0 -1
- package/dist/chunk-3SP4P7NS.js.map +0 -1
- package/dist/chunk-43C63KLH.js.map +0 -1
- package/dist/chunk-5A4RL4BC.js +0 -5670
- package/dist/chunk-5A4RL4BC.js.map +0 -1
- package/dist/chunk-BYG6OSTC.js.map +0 -1
- package/dist/chunk-CDDYJCYU.js +0 -79
- package/dist/chunk-CDDYJCYU.js.map +0 -1
- package/dist/chunk-F24P24TZ.js +0 -17
- package/dist/chunk-F24P24TZ.js.map +0 -1
- package/dist/chunk-GBC5PC3N.js.map +0 -1
- package/dist/chunk-LANO5IFV.js.map +0 -1
- package/dist/chunk-M4UMXYNK.js.map +0 -1
- package/dist/chunk-RJNE764D.js +0 -953
- package/dist/chunk-RJNE764D.js.map +0 -1
- package/dist/chunk-UC2BWIK7.js.map +0 -1
- package/dist/rbac/cli/policy-manager.js +0 -278
- package/dist/rbac/cli/policy-manager.js.map +0 -1
- package/docs/api/interfaces/EventContextType.md +0 -96
- package/docs/api/interfaces/EventProviderProps.md +0 -19
- package/src/providers/OrganisationProvider.test.tsx +0 -164
- package/src/providers/UnifiedAuthProvider.test.tsx +0 -124
- package/src/providers/__tests__/AuthProvider.test.tsx.backup +0 -771
- package/src/providers/__tests__/EventProvider.test.tsx.backup +0 -824
- package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +0 -820
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +0 -911
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +0 -166
- package/src/rbac/cli/__tests__/policy-manager.test.ts +0 -339
- package/src/rbac/cli/policy-manager.ts +0 -443
- package/dist/{DataTable-INW5YIFV.js.map → DataTable-HWZQGASI.js.map} +0 -0
- package/dist/{UnifiedAuthProvider-6SYT5WFN.js.map → UnifiedAuthProvider-3NKDOSOK.js.map} +0 -0
- package/dist/{validation-PM_iOaTI.d.ts → validation-D8VcbTzC.d.ts} +2 -2
- /package/src/utils/{appNameResolver.test.ts.backup → appNameResolver.test 2.ts} +0 -0
|
@@ -352,5 +352,293 @@ describe('AuthService', () => {
|
|
|
352
352
|
await authService.signIn('test@example.com', 'password');
|
|
353
353
|
expect(authService.getError()).toBeNull();
|
|
354
354
|
});
|
|
355
|
+
|
|
356
|
+
it('should handle exceptions during sign in', async () => {
|
|
357
|
+
const exceptionError = new Error('Unexpected exception');
|
|
358
|
+
mockSupabase.auth.signInWithPassword.mockRejectedValue(exceptionError);
|
|
359
|
+
|
|
360
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
361
|
+
|
|
362
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
363
|
+
expect(result.error?.message).toBe('Unexpected exception');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should handle exceptions during sign up', async () => {
|
|
367
|
+
const exceptionError = new Error('Unexpected exception');
|
|
368
|
+
mockSupabase.auth.signUp.mockRejectedValue(exceptionError);
|
|
369
|
+
|
|
370
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
371
|
+
|
|
372
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
373
|
+
expect(result.error?.message).toBe('Unexpected exception');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should handle exceptions during sign out', async () => {
|
|
377
|
+
const exceptionError = new Error('Unexpected exception');
|
|
378
|
+
mockSupabase.auth.signOut.mockRejectedValue(exceptionError);
|
|
379
|
+
|
|
380
|
+
const result = await authService.signOut();
|
|
381
|
+
|
|
382
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
383
|
+
expect(result.error?.message).toBe('Unexpected exception');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should handle exceptions during password reset', async () => {
|
|
387
|
+
const exceptionError = new Error('Unexpected exception');
|
|
388
|
+
mockSupabase.auth.resetPasswordForEmail.mockRejectedValue(exceptionError);
|
|
389
|
+
|
|
390
|
+
const result = await authService.resetPassword('test@example.com');
|
|
391
|
+
|
|
392
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should handle missing Supabase client on sign up', async () => {
|
|
396
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
397
|
+
|
|
398
|
+
const result = await serviceWithoutClient.signUp('test@example.com', 'password');
|
|
399
|
+
|
|
400
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
401
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should handle missing Supabase client on sign out', async () => {
|
|
405
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
406
|
+
|
|
407
|
+
const result = await serviceWithoutClient.signOut();
|
|
408
|
+
|
|
409
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
410
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should handle missing Supabase client on password reset', async () => {
|
|
414
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
415
|
+
|
|
416
|
+
const result = await serviceWithoutClient.resetPassword('test@example.com');
|
|
417
|
+
|
|
418
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
419
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should handle missing Supabase client on update password', async () => {
|
|
423
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
424
|
+
|
|
425
|
+
const result = await serviceWithoutClient.updatePassword('newpassword');
|
|
426
|
+
|
|
427
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
428
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should handle missing Supabase client on session refresh', async () => {
|
|
432
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
433
|
+
|
|
434
|
+
const result = await serviceWithoutClient.refreshSession();
|
|
435
|
+
|
|
436
|
+
expect(result.error).toBeInstanceOf(AuthError);
|
|
437
|
+
expect(result.error?.message).toBe('Supabase client not available');
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('Session Management Edge Cases', () => {
|
|
442
|
+
it('should handle session refresh with null data', async () => {
|
|
443
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
444
|
+
data: { session: null, user: null },
|
|
445
|
+
error: null
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const result = await authService.refreshSession();
|
|
449
|
+
|
|
450
|
+
expect(result.user).toBeNull();
|
|
451
|
+
expect(result.session).toBeNull();
|
|
452
|
+
expect(result.error).toBeNull();
|
|
453
|
+
expect(authService.getUser()).toBeNull();
|
|
454
|
+
expect(authService.getSession()).toBeNull();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('should handle session refresh with user but no session by clearing both', async () => {
|
|
458
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
459
|
+
|
|
460
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
461
|
+
data: { session: null, user: mockUser },
|
|
462
|
+
error: null
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const result = await authService.refreshSession();
|
|
466
|
+
|
|
467
|
+
// If there's no session, we should clear user state too
|
|
468
|
+
// This is intentional - without a valid session, we can't trust the user object
|
|
469
|
+
expect(result.user).toBeNull();
|
|
470
|
+
expect(result.session).toBeNull();
|
|
471
|
+
expect(authService.getUser()).toBeNull();
|
|
472
|
+
expect(authService.getSession()).toBeNull();
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should handle refresh session errors by clearing state', async () => {
|
|
476
|
+
const mockError = new AuthError('Session expired');
|
|
477
|
+
|
|
478
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
479
|
+
data: { session: null, user: null },
|
|
480
|
+
error: mockError
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const result = await authService.refreshSession();
|
|
484
|
+
|
|
485
|
+
expect(result.error).toEqual(mockError);
|
|
486
|
+
expect(authService.getUser()).toBeNull();
|
|
487
|
+
expect(authService.getSession()).toBeNull();
|
|
488
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('Initialization Edge Cases', () => {
|
|
493
|
+
it('should handle initialization without Supabase client', async () => {
|
|
494
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
495
|
+
await serviceWithoutClient.initialize();
|
|
496
|
+
|
|
497
|
+
expect(mockSupabase.auth.getSession).not.toHaveBeenCalled();
|
|
498
|
+
expect(mockSupabase.auth.onAuthStateChange).not.toHaveBeenCalled();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should handle initialization with getSession error', async () => {
|
|
502
|
+
const sessionError = new AuthError('Session error');
|
|
503
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
504
|
+
data: { session: null },
|
|
505
|
+
error: sessionError
|
|
506
|
+
});
|
|
507
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
508
|
+
data: { user: null },
|
|
509
|
+
error: null
|
|
510
|
+
});
|
|
511
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
512
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
await authService.initialize();
|
|
516
|
+
|
|
517
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
518
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should handle initialization with getUser error', async () => {
|
|
522
|
+
const userError = new AuthError('User error');
|
|
523
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
524
|
+
data: { session: null },
|
|
525
|
+
error: null
|
|
526
|
+
});
|
|
527
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
528
|
+
data: { user: null },
|
|
529
|
+
error: userError
|
|
530
|
+
});
|
|
531
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
532
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
await authService.initialize();
|
|
536
|
+
|
|
537
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('should restore session from storage during initialization', async () => {
|
|
541
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
542
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
543
|
+
|
|
544
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
545
|
+
data: { session: mockSession },
|
|
546
|
+
error: null
|
|
547
|
+
});
|
|
548
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
549
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
await authService.initialize();
|
|
553
|
+
|
|
554
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
555
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
556
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should handle auth state change unsubscribe gracefully', () => {
|
|
560
|
+
const unsubscribeFn = vi.fn();
|
|
561
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
562
|
+
data: { subscription: { unsubscribe: unsubscribeFn } }
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
authService.cleanup();
|
|
566
|
+
|
|
567
|
+
// Should not throw error
|
|
568
|
+
expect(unsubscribeFn).not.toHaveBeenCalled();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should handle auth state change events correctly', async () => {
|
|
572
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
573
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
574
|
+
|
|
575
|
+
let authStateCallback: any;
|
|
576
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
577
|
+
authStateCallback = callback;
|
|
578
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await authService.initialize();
|
|
582
|
+
|
|
583
|
+
// Simulate SIGNED_IN event
|
|
584
|
+
if (authStateCallback) {
|
|
585
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
586
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
587
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Simulate SIGNED_OUT event
|
|
591
|
+
if (authStateCallback) {
|
|
592
|
+
authStateCallback('SIGNED_OUT', null);
|
|
593
|
+
expect(authService.getUser()).toBeNull();
|
|
594
|
+
expect(authService.getSession()).toBeNull();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Simulate TOKEN_REFRESHED event
|
|
598
|
+
const refreshedSession = { ...mockSession, access_token: 'new_token' };
|
|
599
|
+
if (authStateCallback) {
|
|
600
|
+
authStateCallback('TOKEN_REFRESHED', refreshedSession);
|
|
601
|
+
expect(authService.getSession()).toEqual(refreshedSession);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Simulate INITIAL_SESSION event
|
|
605
|
+
const initialSession = { access_token: 'initial_token', user: mockUser };
|
|
606
|
+
if (authStateCallback) {
|
|
607
|
+
authStateCallback('INITIAL_SESSION', initialSession);
|
|
608
|
+
expect(authService.getSession()).toEqual(initialSession);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('should handle errors in auth state change callback', async () => {
|
|
613
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
614
|
+
|
|
615
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
616
|
+
// Simulate error in callback
|
|
617
|
+
try {
|
|
618
|
+
callback('INITIAL_SESSION', null);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
// Error is expected to be caught and logged
|
|
621
|
+
}
|
|
622
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
await authService.initialize();
|
|
626
|
+
|
|
627
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
628
|
+
consoleWarnSpy.mockRestore();
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
describe('Get Session with Null User', () => {
|
|
633
|
+
it('should handle session with null user object', () => {
|
|
634
|
+
const mockSessionWithNullUser = { access_token: 'token', user: null };
|
|
635
|
+
|
|
636
|
+
// This would be set via internal state
|
|
637
|
+
(authService as any).session = mockSessionWithNullUser;
|
|
638
|
+
|
|
639
|
+
expect(authService.getSession()).toEqual(mockSessionWithNullUser);
|
|
640
|
+
expect(authService.getUser()).toBeNull();
|
|
641
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
642
|
+
});
|
|
355
643
|
});
|
|
356
644
|
});
|
package/src/styles/core.css
CHANGED
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
font-weight: 600;
|
|
105
105
|
line-height: 1.4;
|
|
106
106
|
color: var(--color-main-700);
|
|
107
|
+
letter-spacing: -0.05rem;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
h5 {
|
|
@@ -111,6 +112,7 @@
|
|
|
111
112
|
font-weight: 500;
|
|
112
113
|
line-height: 1.4;
|
|
113
114
|
color: var(--color-main-800);
|
|
115
|
+
letter-spacing: -0.025rem;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
h6 {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Type Guard Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Types/Guards
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for type guard functions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { isAuthErrorCode, isUser, isSession } from '../guards';
|
|
12
|
+
import { AuthErrorCode } from '../unified';
|
|
13
|
+
|
|
14
|
+
describe('Type Guards', () => {
|
|
15
|
+
describe('isAuthErrorCode', () => {
|
|
16
|
+
it('should return true for valid auth error codes', () => {
|
|
17
|
+
expect(isAuthErrorCode('INVALID_CREDENTIALS')).toBe(true);
|
|
18
|
+
expect(isAuthErrorCode('USER_NOT_FOUND')).toBe(true);
|
|
19
|
+
expect(isAuthErrorCode('EMAIL_NOT_CONFIRMED')).toBe(true);
|
|
20
|
+
expect(isAuthErrorCode('PASSWORD_TOO_WEAK')).toBe(true);
|
|
21
|
+
expect(isAuthErrorCode('SESSION_EXPIRED')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return false for invalid auth error codes', () => {
|
|
25
|
+
expect(isAuthErrorCode('INVALID_CODE')).toBe(false);
|
|
26
|
+
expect(isAuthErrorCode('unknown_error')).toBe(false);
|
|
27
|
+
expect(isAuthErrorCode('')).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return false for non-string values', () => {
|
|
31
|
+
expect(isAuthErrorCode(null as any)).toBe(false);
|
|
32
|
+
expect(isAuthErrorCode(undefined as any)).toBe(false);
|
|
33
|
+
expect(isAuthErrorCode(123 as any)).toBe(false);
|
|
34
|
+
expect(isAuthErrorCode({} as any)).toBe(false);
|
|
35
|
+
expect(isAuthErrorCode([] as any)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return true for all enum values', () => {
|
|
39
|
+
Object.values(AuthErrorCode).forEach(code => {
|
|
40
|
+
expect(isAuthErrorCode(code)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('isUser', () => {
|
|
46
|
+
it('should return true for valid user objects', () => {
|
|
47
|
+
const validUser = {
|
|
48
|
+
id: 'user-123',
|
|
49
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
50
|
+
email: 'test@example.com'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
expect(isUser(validUser)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return false for null or undefined', () => {
|
|
57
|
+
expect(isUser(null)).toBe(false);
|
|
58
|
+
expect(isUser(undefined)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return false for non-object types', () => {
|
|
62
|
+
expect(isUser('string')).toBe(false);
|
|
63
|
+
expect(isUser(123)).toBe(false);
|
|
64
|
+
expect(isUser(true)).toBe(false);
|
|
65
|
+
expect(isUser([])).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return false for objects missing id', () => {
|
|
69
|
+
expect(isUser({ created_at: '2024-01-01T00:00:00Z' })).toBe(false);
|
|
70
|
+
expect(isUser({ email: 'test@example.com' })).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return false for objects with non-string id', () => {
|
|
74
|
+
expect(isUser({ id: 123, created_at: '2024-01-01T00:00:00Z' })).toBe(false);
|
|
75
|
+
expect(isUser({ id: null, created_at: '2024-01-01T00:00:00Z' })).toBe(false);
|
|
76
|
+
expect(isUser({ id: undefined, created_at: '2024-01-01T00:00:00Z' })).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return false for objects missing created_at', () => {
|
|
80
|
+
expect(isUser({ id: 'user-123' })).toBe(false);
|
|
81
|
+
expect(isUser({ id: 'user-123', email: 'test@example.com' })).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return false for objects with non-string created_at', () => {
|
|
85
|
+
expect(isUser({ id: 'user-123', created_at: 123 })).toBe(false);
|
|
86
|
+
expect(isUser({ id: 'user-123', created_at: null })).toBe(false);
|
|
87
|
+
expect(isUser({ id: 'user-123', created_at: undefined })).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should return true for users with additional properties', () => {
|
|
91
|
+
const userWithExtraProps = {
|
|
92
|
+
id: 'user-123',
|
|
93
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
94
|
+
email: 'test@example.com',
|
|
95
|
+
metadata: { role: 'admin' },
|
|
96
|
+
extraField: 'value'
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
expect(isUser(userWithExtraProps)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should return true for empty string id (edge case)', () => {
|
|
103
|
+
const userWithEmptyId = {
|
|
104
|
+
id: '',
|
|
105
|
+
created_at: '2024-01-01T00:00:00Z'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
expect(isUser(userWithEmptyId)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('isSession', () => {
|
|
113
|
+
it('should return true for valid session objects', () => {
|
|
114
|
+
const validSession = {
|
|
115
|
+
access_token: 'token-123',
|
|
116
|
+
user: {
|
|
117
|
+
id: 'user-123',
|
|
118
|
+
created_at: '2024-01-01T00:00:00Z'
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
expect(isSession(validSession)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return false for null or undefined', () => {
|
|
126
|
+
expect(isSession(null)).toBe(false);
|
|
127
|
+
expect(isSession(undefined)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should return false for non-object types', () => {
|
|
131
|
+
expect(isSession('string')).toBe(false);
|
|
132
|
+
expect(isSession(123)).toBe(false);
|
|
133
|
+
expect(isSession(true)).toBe(false);
|
|
134
|
+
expect(isSession([])).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should return false for objects missing access_token', () => {
|
|
138
|
+
expect(isSession({ user: { id: 'user-123', created_at: '2024-01-01T00:00:00Z' } })).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should return false for objects with non-string access_token', () => {
|
|
142
|
+
expect(isSession({
|
|
143
|
+
access_token: 123,
|
|
144
|
+
user: { id: 'user-123', created_at: '2024-01-01T00:00:00Z' }
|
|
145
|
+
})).toBe(false);
|
|
146
|
+
|
|
147
|
+
expect(isSession({
|
|
148
|
+
access_token: null,
|
|
149
|
+
user: { id: 'user-123', created_at: '2024-01-01T00:00:00Z' }
|
|
150
|
+
})).toBe(false);
|
|
151
|
+
|
|
152
|
+
expect(isSession({
|
|
153
|
+
access_token: undefined,
|
|
154
|
+
user: { id: 'user-123', created_at: '2024-01-01T00:00:00Z' }
|
|
155
|
+
})).toBe(false);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should return false for objects missing user', () => {
|
|
159
|
+
expect(isSession({ access_token: 'token-123' })).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should return false for objects with non-object user', () => {
|
|
163
|
+
expect(isSession({
|
|
164
|
+
access_token: 'token-123',
|
|
165
|
+
user: 'not an object'
|
|
166
|
+
})).toBe(false);
|
|
167
|
+
|
|
168
|
+
expect(isSession({
|
|
169
|
+
access_token: 'token-123',
|
|
170
|
+
user: null
|
|
171
|
+
})).toBe(false);
|
|
172
|
+
|
|
173
|
+
expect(isSession({
|
|
174
|
+
access_token: 'token-123',
|
|
175
|
+
user: undefined
|
|
176
|
+
})).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should return true for sessions with additional properties', () => {
|
|
180
|
+
const sessionWithExtraProps = {
|
|
181
|
+
access_token: 'token-123',
|
|
182
|
+
refresh_token: 'refresh-123',
|
|
183
|
+
user: {
|
|
184
|
+
id: 'user-123',
|
|
185
|
+
created_at: '2024-01-01T00:00:00Z'
|
|
186
|
+
},
|
|
187
|
+
expires_at: 1234567890,
|
|
188
|
+
expires_in: 3600
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
expect(isSession(sessionWithExtraProps)).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should return true for empty string access_token (edge case)', () => {
|
|
195
|
+
const sessionWithEmptyToken = {
|
|
196
|
+
access_token: '',
|
|
197
|
+
user: {
|
|
198
|
+
id: 'user-123',
|
|
199
|
+
created_at: '2024-01-01T00:00:00Z'
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
expect(isSession(sessionWithEmptyToken)).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('Edge Cases', () => {
|
|
208
|
+
it('should handle deeply nested objects correctly', () => {
|
|
209
|
+
const deeplyNested = {
|
|
210
|
+
id: 'user-123',
|
|
211
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
212
|
+
nested: {
|
|
213
|
+
deeply: {
|
|
214
|
+
nested: {
|
|
215
|
+
property: 'value'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
expect(isUser(deeplyNested)).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should handle special object types', () => {
|
|
225
|
+
const dateObj = new Date();
|
|
226
|
+
expect(isUser(dateObj)).toBe(false);
|
|
227
|
+
expect(isSession(dateObj)).toBe(false);
|
|
228
|
+
|
|
229
|
+
const arrayObj = [];
|
|
230
|
+
expect(isUser(arrayObj)).toBe(false);
|
|
231
|
+
expect(isSession(arrayObj)).toBe(false);
|
|
232
|
+
|
|
233
|
+
const functionObj = () => {};
|
|
234
|
+
expect(isUser(functionObj)).toBe(false);
|
|
235
|
+
expect(isSession(functionObj)).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle prototype pollution attempts', () => {
|
|
239
|
+
const malicious = Object.create(null);
|
|
240
|
+
(malicious as any).__proto__ = { id: 'user-123', created_at: '2024-01-01T00:00:00Z' };
|
|
241
|
+
|
|
242
|
+
expect(isUser(malicious)).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
package/src/types/guards.ts
CHANGED
|
@@ -103,7 +103,7 @@ export interface OrganisationSecurityError extends Error {
|
|
|
103
103
|
|
|
104
104
|
export interface OrganisationContextType {
|
|
105
105
|
// Current organisation context
|
|
106
|
-
selectedOrganisation: Organisation;
|
|
106
|
+
selectedOrganisation: Organisation | null;
|
|
107
107
|
organisations: Organisation[];
|
|
108
108
|
userMemberships: OrganisationMembership[];
|
|
109
109
|
|
|
@@ -111,9 +111,10 @@ export interface OrganisationContextType {
|
|
|
111
111
|
isLoading: boolean;
|
|
112
112
|
error: Error | null;
|
|
113
113
|
hasValidOrganisationContext: boolean;
|
|
114
|
+
isContextReady: boolean;
|
|
114
115
|
|
|
115
116
|
// Organisation management
|
|
116
|
-
setSelectedOrganisation: (org: Organisation) => void;
|
|
117
|
+
setSelectedOrganisation: (org: Organisation | null) => void;
|
|
117
118
|
switchOrganisation: (orgId: string) => Promise<void>;
|
|
118
119
|
|
|
119
120
|
// Security helpers
|