@jmruthers/pace-core 0.5.118 → 0.5.119
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-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
- package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
- package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
- package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
- package/dist/chunk-BHWIUEYH.js.map +1 -0
- package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
- package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
- package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
- package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
- package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
- package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
- package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
- package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
- package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/utils.js +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 +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/DataRecord.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/EventAppRoleData.md +1 -1
- 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/GrantEventAppRoleParams.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/ProtectedRouteProps.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/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.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 +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/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.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 +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
- package/src/hooks/__tests__/index.unit.test.ts +223 -0
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
- package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
- package/src/hooks/useSecureDataAccess.test.ts +1 -0
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/services/EventService.ts +1 -0
- package/src/services/__tests__/AuthService.test.ts +473 -0
- package/src/services/__tests__/EventService.test.ts +390 -0
- package/src/services/__tests__/InactivityService.test.ts +217 -0
- package/src/services/__tests__/OrganisationService.test.ts +371 -0
- package/dist/chunk-KA3PSVNV.js.map +0 -1
- package/src/components/DataTable/utils/debugTools.ts +0 -609
- package/src/rbac/testing/index.tsx +0 -340
- /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
- /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
- /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
- /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
- /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
- /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
- /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
- /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
- /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
- /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
- /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
|
@@ -824,4 +824,477 @@ describe('AuthService', () => {
|
|
|
824
824
|
}
|
|
825
825
|
});
|
|
826
826
|
});
|
|
827
|
+
|
|
828
|
+
describe('Session Restoration Edge Cases', () => {
|
|
829
|
+
it('should handle getSession throwing exception', async () => {
|
|
830
|
+
mockSupabase.auth.getSession.mockRejectedValue(new Error('getSession exception'));
|
|
831
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
832
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
await authService.initialize();
|
|
836
|
+
|
|
837
|
+
// Should handle exception gracefully
|
|
838
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('should handle getUser when getSession fails', async () => {
|
|
842
|
+
const sessionError = new AuthError('Session error');
|
|
843
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
844
|
+
data: { session: null },
|
|
845
|
+
error: sessionError
|
|
846
|
+
});
|
|
847
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
848
|
+
data: { user: null },
|
|
849
|
+
error: null
|
|
850
|
+
});
|
|
851
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
852
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
await authService.initialize();
|
|
856
|
+
|
|
857
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it('should handle getUser also failing', async () => {
|
|
861
|
+
const sessionError = new AuthError('Session error');
|
|
862
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
863
|
+
data: { session: null },
|
|
864
|
+
error: sessionError
|
|
865
|
+
});
|
|
866
|
+
mockSupabase.auth.getUser.mockRejectedValue(new Error('getUser exception'));
|
|
867
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
868
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
await authService.initialize();
|
|
872
|
+
|
|
873
|
+
// Should handle both failures gracefully
|
|
874
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
it('should handle session restoration timeout', async () => {
|
|
878
|
+
// Mock getSession to hang (simulate timeout scenario)
|
|
879
|
+
mockSupabase.auth.getSession.mockImplementation(() => {
|
|
880
|
+
return new Promise(() => {
|
|
881
|
+
// Never resolves - will timeout after 5 seconds in real scenario
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
885
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// Start initialization (will hang waiting for getSession)
|
|
889
|
+
const initPromise = authService.initialize();
|
|
890
|
+
|
|
891
|
+
// Use a timeout to simulate the service's internal timeout
|
|
892
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
893
|
+
setTimeout(() => reject(new Error('Test timeout')), 100);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// Race between initialization and test timeout
|
|
897
|
+
try {
|
|
898
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
899
|
+
} catch (error) {
|
|
900
|
+
// Expected to timeout in test
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Verify that restoration state is managed
|
|
904
|
+
const restorationState = authService.getSessionRestorationState();
|
|
905
|
+
expect(restorationState).toBeDefined();
|
|
906
|
+
}, 1000);
|
|
907
|
+
|
|
908
|
+
it('should finish restoration with timeout error', async () => {
|
|
909
|
+
// Mock getSession to hang (simulate timeout scenario)
|
|
910
|
+
mockSupabase.auth.getSession.mockImplementation(() => {
|
|
911
|
+
return new Promise(() => {
|
|
912
|
+
// Never resolves - will timeout after 5 seconds in real scenario
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
916
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// Start initialization (will hang waiting for getSession)
|
|
920
|
+
const initPromise = authService.initialize();
|
|
921
|
+
|
|
922
|
+
// Use a timeout to simulate the service's internal timeout
|
|
923
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
924
|
+
setTimeout(() => reject(new Error('Test timeout')), 100);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
// Race between initialization and test timeout
|
|
928
|
+
try {
|
|
929
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
930
|
+
} catch (error) {
|
|
931
|
+
// Expected to timeout in test
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Verify that restoration state is managed
|
|
935
|
+
const restorationState = authService.getSessionRestorationState();
|
|
936
|
+
expect(restorationState).toBeDefined();
|
|
937
|
+
}, 1000);
|
|
938
|
+
|
|
939
|
+
it('should handle restoration error during initialization', async () => {
|
|
940
|
+
const restorationError = new Error('Restoration failed');
|
|
941
|
+
mockSupabase.auth.getSession.mockRejectedValue(restorationError);
|
|
942
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
943
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
await authService.initialize();
|
|
947
|
+
|
|
948
|
+
const restorationState = authService.getSessionRestorationState();
|
|
949
|
+
expect(restorationState.restorationError).toBeDefined();
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it('should reset restoration state when valid session arrives after timeout', async () => {
|
|
953
|
+
let authStateCallback: any;
|
|
954
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
955
|
+
authStateCallback = callback;
|
|
956
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
// Mock getSession to fail initially
|
|
960
|
+
mockSupabase.auth.getSession.mockRejectedValue(new Error('Timeout'));
|
|
961
|
+
|
|
962
|
+
await authService.initialize();
|
|
963
|
+
|
|
964
|
+
// Simulate INITIAL_SESSION arriving after timeout
|
|
965
|
+
const mockSession = { access_token: 'token', user: { id: '1', email: 'test@example.com' } };
|
|
966
|
+
if (authStateCallback) {
|
|
967
|
+
authStateCallback('INITIAL_SESSION', mockSession);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Wait a bit for state to update
|
|
971
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
972
|
+
|
|
973
|
+
const restorationState = authService.getSessionRestorationState();
|
|
974
|
+
expect(restorationState.restorationComplete).toBe(true);
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
describe('Error Handlers', () => {
|
|
979
|
+
it('should setup error handlers on initialization', async () => {
|
|
980
|
+
const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
|
|
981
|
+
|
|
982
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
983
|
+
data: { session: null },
|
|
984
|
+
error: null
|
|
985
|
+
});
|
|
986
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
987
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
await authService.initialize();
|
|
991
|
+
|
|
992
|
+
// Error handlers should be set up
|
|
993
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('error', expect.any(Function));
|
|
994
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('unhandledrejection', expect.any(Function));
|
|
995
|
+
|
|
996
|
+
addEventListenerSpy.mockRestore();
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
it('should suppress AuthSessionMissingError in error handler', async () => {
|
|
1000
|
+
const preventDefaultSpy = vi.fn();
|
|
1001
|
+
const mockErrorEvent = {
|
|
1002
|
+
error: { message: 'AuthSessionMissingError' },
|
|
1003
|
+
preventDefault: preventDefaultSpy
|
|
1004
|
+
} as any;
|
|
1005
|
+
|
|
1006
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1007
|
+
data: { session: null },
|
|
1008
|
+
error: null
|
|
1009
|
+
});
|
|
1010
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1011
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
await authService.initialize();
|
|
1015
|
+
|
|
1016
|
+
// Trigger error handler
|
|
1017
|
+
const errorEvent = new ErrorEvent('error', { error: { message: 'AuthSessionMissingError' } as any });
|
|
1018
|
+
window.dispatchEvent(errorEvent);
|
|
1019
|
+
|
|
1020
|
+
// Error should be suppressed
|
|
1021
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled(); // In test environment, preventDefault may not work
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
it('should suppress AuthSessionMissingError in unhandled rejection', async () => {
|
|
1025
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1026
|
+
data: { session: null },
|
|
1027
|
+
error: null
|
|
1028
|
+
});
|
|
1029
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1030
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
await authService.initialize();
|
|
1034
|
+
|
|
1035
|
+
// Create a promise that will be rejected
|
|
1036
|
+
const rejectedPromise = Promise.reject(new Error('AuthSessionMissingError'));
|
|
1037
|
+
|
|
1038
|
+
// Catch the rejection to prevent it from being unhandled
|
|
1039
|
+
rejectedPromise.catch(() => {
|
|
1040
|
+
// The error handler should suppress this
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
// Trigger unhandled rejection using a custom event
|
|
1044
|
+
const rejectionEvent = new Event('unhandledrejection') as any;
|
|
1045
|
+
rejectionEvent.promise = rejectedPromise;
|
|
1046
|
+
rejectionEvent.reason = { message: 'AuthSessionMissingError' };
|
|
1047
|
+
|
|
1048
|
+
// The handler should be set up and will suppress the error
|
|
1049
|
+
window.dispatchEvent(rejectionEvent);
|
|
1050
|
+
|
|
1051
|
+
// Test passes if no error propagates
|
|
1052
|
+
expect(true).toBe(true);
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
it('should handle window undefined environment', async () => {
|
|
1056
|
+
const originalWindow = global.window;
|
|
1057
|
+
// @ts-ignore - for testing
|
|
1058
|
+
delete global.window;
|
|
1059
|
+
|
|
1060
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1061
|
+
data: { session: null },
|
|
1062
|
+
error: null
|
|
1063
|
+
});
|
|
1064
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1065
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
// Should not throw error
|
|
1069
|
+
await expect(authService.initialize()).resolves.toBeUndefined();
|
|
1070
|
+
|
|
1071
|
+
global.window = originalWindow;
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
describe('Session Tracking Edge Cases', () => {
|
|
1076
|
+
it('should handle tracking when app lookup fails', async () => {
|
|
1077
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1078
|
+
(mockSupabase as any).from = vi.fn().mockReturnValue({
|
|
1079
|
+
select: vi.fn().mockReturnValue({
|
|
1080
|
+
eq: vi.fn().mockReturnValue({
|
|
1081
|
+
eq: vi.fn().mockReturnValue({
|
|
1082
|
+
single: vi.fn().mockResolvedValue({
|
|
1083
|
+
data: null,
|
|
1084
|
+
error: { message: 'App not found' }
|
|
1085
|
+
})
|
|
1086
|
+
})
|
|
1087
|
+
})
|
|
1088
|
+
})
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
let authStateCallback: any;
|
|
1092
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1093
|
+
authStateCallback = callback;
|
|
1094
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
1098
|
+
await authServiceWithApp.initialize();
|
|
1099
|
+
|
|
1100
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
1101
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
1102
|
+
|
|
1103
|
+
if (authStateCallback) {
|
|
1104
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
1105
|
+
|
|
1106
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1107
|
+
|
|
1108
|
+
// Should still track with null app_id
|
|
1109
|
+
expect((mockSupabase as any).rpc).toHaveBeenCalledWith('rbac_session_track', expect.objectContaining({
|
|
1110
|
+
p_app_id: undefined
|
|
1111
|
+
}));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
authServiceWithApp.cleanup();
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
it('should handle tracking when RPC fails', async () => {
|
|
1118
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1119
|
+
(mockSupabase as any).rpc = vi.fn().mockResolvedValue({
|
|
1120
|
+
error: { message: 'RPC failed' }
|
|
1121
|
+
});
|
|
1122
|
+
(mockSupabase as any).from = vi.fn().mockReturnValue({
|
|
1123
|
+
select: vi.fn().mockReturnValue({
|
|
1124
|
+
eq: vi.fn().mockReturnValue({
|
|
1125
|
+
eq: vi.fn().mockReturnValue({
|
|
1126
|
+
single: vi.fn().mockResolvedValue({
|
|
1127
|
+
data: { id: 'app-id-123' },
|
|
1128
|
+
error: null
|
|
1129
|
+
})
|
|
1130
|
+
})
|
|
1131
|
+
})
|
|
1132
|
+
})
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
let authStateCallback: any;
|
|
1136
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1137
|
+
authStateCallback = callback;
|
|
1138
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
1142
|
+
await authServiceWithApp.initialize();
|
|
1143
|
+
|
|
1144
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
1145
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
1146
|
+
|
|
1147
|
+
if (authStateCallback) {
|
|
1148
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
1149
|
+
|
|
1150
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1151
|
+
|
|
1152
|
+
// Should log warning but not break authentication
|
|
1153
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1154
|
+
expect.stringContaining('Failed to track login session'),
|
|
1155
|
+
expect.anything()
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
consoleWarnSpy.mockRestore();
|
|
1160
|
+
authServiceWithApp.cleanup();
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
it('should not track when session is null', async () => {
|
|
1164
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1165
|
+
|
|
1166
|
+
let authStateCallback: any;
|
|
1167
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1168
|
+
authStateCallback = callback;
|
|
1169
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
await authService.initialize();
|
|
1173
|
+
|
|
1174
|
+
if (authStateCallback) {
|
|
1175
|
+
authStateCallback('SIGNED_OUT', null);
|
|
1176
|
+
|
|
1177
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1178
|
+
|
|
1179
|
+
// Should not track when session is null
|
|
1180
|
+
expect((mockSupabase as any).rpc).not.toHaveBeenCalled();
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('should not track when user is null in session', async () => {
|
|
1185
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1186
|
+
|
|
1187
|
+
let authStateCallback: any;
|
|
1188
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1189
|
+
authStateCallback = callback;
|
|
1190
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
await authService.initialize();
|
|
1194
|
+
|
|
1195
|
+
if (authStateCallback) {
|
|
1196
|
+
const sessionWithoutUser = { access_token: 'token', user: null };
|
|
1197
|
+
authStateCallback('SIGNED_IN', sessionWithoutUser);
|
|
1198
|
+
|
|
1199
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1200
|
+
|
|
1201
|
+
// Should not track when user is null
|
|
1202
|
+
expect((mockSupabase as any).rpc).not.toHaveBeenCalled();
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
describe('Auth State Change Events', () => {
|
|
1208
|
+
it('should handle INITIAL_SESSION event with null session', async () => {
|
|
1209
|
+
let authStateCallback: any;
|
|
1210
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1211
|
+
authStateCallback = callback;
|
|
1212
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1216
|
+
data: { session: null },
|
|
1217
|
+
error: null
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
await authService.initialize();
|
|
1221
|
+
|
|
1222
|
+
if (authStateCallback) {
|
|
1223
|
+
authStateCallback('INITIAL_SESSION', null);
|
|
1224
|
+
|
|
1225
|
+
expect(authService.getUser()).toBeNull();
|
|
1226
|
+
expect(authService.getSession()).toBeNull();
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it('should handle TOKEN_REFRESHED event', async () => {
|
|
1231
|
+
let authStateCallback: any;
|
|
1232
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1233
|
+
authStateCallback = callback;
|
|
1234
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1238
|
+
data: { session: null },
|
|
1239
|
+
error: null
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
await authService.initialize();
|
|
1243
|
+
|
|
1244
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
1245
|
+
const refreshedSession = { access_token: 'new_token', user: mockUser };
|
|
1246
|
+
|
|
1247
|
+
if (authStateCallback) {
|
|
1248
|
+
authStateCallback('TOKEN_REFRESHED', refreshedSession);
|
|
1249
|
+
|
|
1250
|
+
expect(authService.getSession()).toEqual(refreshedSession);
|
|
1251
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
it('should handle errors in auth state change callback', async () => {
|
|
1256
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1257
|
+
|
|
1258
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1259
|
+
try {
|
|
1260
|
+
// Simulate error in callback
|
|
1261
|
+
callback('INITIAL_SESSION', null);
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
// Error should be caught
|
|
1264
|
+
}
|
|
1265
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1269
|
+
data: { session: null },
|
|
1270
|
+
error: null
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
await authService.initialize();
|
|
1274
|
+
|
|
1275
|
+
// Should handle error gracefully
|
|
1276
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
1277
|
+
|
|
1278
|
+
consoleWarnSpy.mockRestore();
|
|
1279
|
+
});
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
describe('Session Restoration State', () => {
|
|
1283
|
+
it('should return session restoration state', () => {
|
|
1284
|
+
const state = authService.getSessionRestorationState();
|
|
1285
|
+
|
|
1286
|
+
expect(state).toHaveProperty('isRestoring');
|
|
1287
|
+
expect(state).toHaveProperty('restorationComplete');
|
|
1288
|
+
expect(state).toHaveProperty('restorationError');
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
it('should return copy of restoration state', () => {
|
|
1292
|
+
const state1 = authService.getSessionRestorationState();
|
|
1293
|
+
const state2 = authService.getSessionRestorationState();
|
|
1294
|
+
|
|
1295
|
+
// Should be different objects (copies)
|
|
1296
|
+
expect(state1).not.toBe(state2);
|
|
1297
|
+
expect(state1).toEqual(state2);
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
827
1300
|
});
|