@jmruthers/pace-core 0.5.189 → 0.5.191
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-WKRZD47S.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +84 -25
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-FTSG5XH7.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-IHKALJZD.js} +4 -2
- package/dist/{chunk-VGZZXKBR.js → chunk-6LTQQAT6.js} +351 -157
- package/dist/chunk-6LTQQAT6.js.map +1 -0
- package/dist/{chunk-MX64ZF6I.js → chunk-6TQDD426.js} +15 -15
- package/dist/chunk-6TQDD426.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-G37KK66H.js} +2 -75
- package/dist/chunk-G37KK66H.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-LOMZXPSN.js} +141 -326
- package/dist/chunk-LOMZXPSN.js.map +1 -0
- package/dist/chunk-OETXORNB.js +614 -0
- package/dist/chunk-OETXORNB.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-ROXMHMY2.js} +403 -46
- package/dist/chunk-ROXMHMY2.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-ULHIJK66.js} +228 -177
- package/dist/{chunk-2UUZZJFT.js.map → chunk-ULHIJK66.js.map} +1 -1
- package/dist/{chunk-YGPFYGA6.js → chunk-VKB2CO4Z.js} +838 -503
- package/dist/chunk-VKB2CO4Z.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-VRGWKHDB.js} +238 -301
- package/dist/chunk-VRGWKHDB.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-XNYQOL3Z.js} +431 -384
- package/dist/chunk-XNYQOL3Z.js.map +1 -0
- package/dist/{chunk-DDM4CCYT.js → chunk-XYXSXPUK.js} +79 -59
- package/dist/chunk-XYXSXPUK.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +5 -6
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +20 -15
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.js +86 -81
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +77 -13
- package/dist/rbac/index.js +12 -9
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +17 -10
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +25 -20
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- 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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- 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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.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/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.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/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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 +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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 +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +5 -5
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.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 +165 -106
- package/docs/api-reference/components.md +15 -7
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +767 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -3
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +19 -19
- package/src/__tests__/rls-policies.test.ts +210 -74
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +7 -6
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +42 -22
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +65 -20
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.tsx +3 -3
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +1 -1
- package/src/hooks/__tests__/usePublicEvent.test.ts +608 -0
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +67 -24
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +10 -10
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +298 -36
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +27 -6
- package/src/hooks/useSecureDataAccess.test.ts +87 -42
- package/src/hooks/useSecureDataAccess.ts +95 -48
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +8 -3
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +74 -12
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +52 -3
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +125 -137
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/file-reference.ts +13 -10
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +65 -37
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-WKRZD47S.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-IHKALJZD.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -40,6 +40,16 @@ vi.mock('../../utils/security/secureStorage', () => {
|
|
|
40
40
|
import { secureStorage } from '../../utils/security/secureStorage';
|
|
41
41
|
const mockSecureStorage = secureStorage as any;
|
|
42
42
|
|
|
43
|
+
// Mock getAppConfigByName to return org-required app config by default
|
|
44
|
+
vi.mock('../../rbac/api', async () => {
|
|
45
|
+
const actual = await vi.importActual('../../rbac/api');
|
|
46
|
+
return {
|
|
47
|
+
...actual,
|
|
48
|
+
getAppConfigByName: vi.fn().mockResolvedValue({ requires_event: false }),
|
|
49
|
+
isSuperAdmin: vi.fn().mockResolvedValue(false),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
43
53
|
// Mock Supabase client
|
|
44
54
|
const createMockSupabaseClient = () => ({
|
|
45
55
|
rpc: vi.fn(),
|
|
@@ -175,9 +185,11 @@ describe('EventService', () => {
|
|
|
175
185
|
|
|
176
186
|
await eventService.initialize();
|
|
177
187
|
|
|
188
|
+
// For org-required apps: uses selectedOrganisation.id
|
|
189
|
+
// For event-required apps: uses selectedEvent.organisation_id or null for super admins
|
|
178
190
|
expect(mockSupabase.rpc).toHaveBeenCalledWith('data_user_events_get', {
|
|
179
191
|
p_user_id: mockUser.id,
|
|
180
|
-
p_organisation_id: mockOrganisation.id,
|
|
192
|
+
p_organisation_id: mockOrganisation.id, // Org-required app uses selectedOrganisation
|
|
181
193
|
p_app_name: 'test-app'
|
|
182
194
|
});
|
|
183
195
|
|
|
@@ -229,7 +241,7 @@ describe('EventService', () => {
|
|
|
229
241
|
expect(mockSetSelectedEventId).toHaveBeenCalledWith(null);
|
|
230
242
|
});
|
|
231
243
|
|
|
232
|
-
it('should
|
|
244
|
+
it('should allow selecting event from any organisation (no validation)', () => {
|
|
233
245
|
// Clear any auto-selected event first
|
|
234
246
|
eventService.setSelectedEvent(null);
|
|
235
247
|
|
|
@@ -240,8 +252,8 @@ describe('EventService', () => {
|
|
|
240
252
|
|
|
241
253
|
eventService.setSelectedEvent(eventFromDifferentOrg);
|
|
242
254
|
|
|
243
|
-
// Should
|
|
244
|
-
expect(eventService.getSelectedEvent()).
|
|
255
|
+
// Should allow selection - org validation removed (org derived from event for event-required apps)
|
|
256
|
+
expect(eventService.getSelectedEvent()).toEqual(eventFromDifferentOrg);
|
|
245
257
|
});
|
|
246
258
|
|
|
247
259
|
it('should refresh events', async () => {
|
|
@@ -976,8 +988,8 @@ describe('EventService', () => {
|
|
|
976
988
|
});
|
|
977
989
|
});
|
|
978
990
|
|
|
979
|
-
describe('Event
|
|
980
|
-
it('should
|
|
991
|
+
describe('Event Selection', () => {
|
|
992
|
+
it('should allow selecting event without organisation validation', () => {
|
|
981
993
|
vi.clearAllMocks();
|
|
982
994
|
|
|
983
995
|
const eventFromDifferentOrg: Event = {
|
|
@@ -985,22 +997,16 @@ describe('EventService', () => {
|
|
|
985
997
|
organisation_id: 'org-2'
|
|
986
998
|
};
|
|
987
999
|
|
|
988
|
-
// Should not throw error
|
|
1000
|
+
// Should not throw error - validation removed (org derived from event for event-required apps)
|
|
989
1001
|
eventService.setSelectedEvent(eventFromDifferentOrg);
|
|
990
1002
|
|
|
991
|
-
//
|
|
992
|
-
expect(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
expect.objectContaining({
|
|
996
|
-
eventOrganisationId: 'org-2',
|
|
997
|
-
selectedOrganisationId: 'org-1',
|
|
998
|
-
eventName: expect.any(String)
|
|
999
|
-
})
|
|
1000
|
-
);
|
|
1003
|
+
// Event should be selected successfully
|
|
1004
|
+
expect(eventService.getSelectedEvent()).toEqual(eventFromDifferentOrg);
|
|
1005
|
+
// No error should be logged
|
|
1006
|
+
expect(mockLoggerFunctions.error).not.toHaveBeenCalled();
|
|
1001
1007
|
});
|
|
1002
1008
|
|
|
1003
|
-
it('should
|
|
1009
|
+
it('should allow selecting event when organisation is null (event-required apps)', () => {
|
|
1004
1010
|
const serviceWithoutOrg = new EventService(
|
|
1005
1011
|
mockSupabase as any,
|
|
1006
1012
|
mockUser,
|
|
@@ -1010,11 +1016,10 @@ describe('EventService', () => {
|
|
|
1010
1016
|
mockSetSelectedEventId
|
|
1011
1017
|
);
|
|
1012
1018
|
|
|
1013
|
-
// Should not throw error
|
|
1019
|
+
// Should not throw error - org is derived from event for event-required apps
|
|
1014
1020
|
serviceWithoutOrg.setSelectedEvent(mockEvent);
|
|
1015
1021
|
|
|
1016
|
-
//
|
|
1017
|
-
// So the event can still be selected
|
|
1022
|
+
// Event should be selected successfully
|
|
1018
1023
|
expect(serviceWithoutOrg.getSelectedEvent()).toEqual(mockEvent);
|
|
1019
1024
|
});
|
|
1020
1025
|
});
|
|
@@ -88,16 +88,29 @@ describe('OrganisationService Pagination & Validation', () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
91
|
-
if (table === '
|
|
91
|
+
if (table === 'rbac_organisation_roles') {
|
|
92
92
|
return {
|
|
93
93
|
select: vi.fn().mockResolvedValue({
|
|
94
|
-
data: [
|
|
94
|
+
data: [
|
|
95
|
+
{
|
|
96
|
+
...mockMembership,
|
|
97
|
+
core_organisations: mockOrganisation
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
...mockMembership2,
|
|
101
|
+
core_organisations: mockOrganisation2
|
|
102
|
+
}
|
|
103
|
+
],
|
|
95
104
|
error: null
|
|
96
|
-
})
|
|
105
|
+
}),
|
|
106
|
+
eq: vi.fn().mockReturnThis(),
|
|
107
|
+
is: vi.fn().mockReturnThis()
|
|
97
108
|
};
|
|
98
109
|
}
|
|
99
110
|
return {
|
|
100
|
-
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
111
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
112
|
+
eq: vi.fn().mockReturnThis(),
|
|
113
|
+
is: vi.fn().mockReturnThis()
|
|
101
114
|
};
|
|
102
115
|
});
|
|
103
116
|
|
|
@@ -175,16 +188,29 @@ describe('OrganisationService Pagination & Validation', () => {
|
|
|
175
188
|
});
|
|
176
189
|
|
|
177
190
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
178
|
-
if (table === '
|
|
191
|
+
if (table === 'rbac_organisation_roles') {
|
|
179
192
|
return {
|
|
180
193
|
select: vi.fn().mockResolvedValue({
|
|
181
|
-
data: [
|
|
194
|
+
data: [
|
|
195
|
+
{
|
|
196
|
+
...mockMembership,
|
|
197
|
+
core_organisations: mockOrganisation
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
...inactiveMembership,
|
|
201
|
+
core_organisations: inactiveOrganisation
|
|
202
|
+
}
|
|
203
|
+
],
|
|
182
204
|
error: null
|
|
183
|
-
})
|
|
205
|
+
}),
|
|
206
|
+
eq: vi.fn().mockReturnThis(),
|
|
207
|
+
is: vi.fn().mockReturnThis()
|
|
184
208
|
};
|
|
185
209
|
}
|
|
186
210
|
return {
|
|
187
|
-
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
211
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
212
|
+
eq: vi.fn().mockReturnThis(),
|
|
213
|
+
is: vi.fn().mockReturnThis()
|
|
188
214
|
};
|
|
189
215
|
});
|
|
190
216
|
|
|
@@ -94,7 +94,7 @@ describe('OrganisationService', () => {
|
|
|
94
94
|
// Mock the from().select() chain to return organisations
|
|
95
95
|
// The select() method should return a promise that resolves to { data, error }
|
|
96
96
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
97
|
-
if (table === '
|
|
97
|
+
if (table === 'core_organisations') {
|
|
98
98
|
return {
|
|
99
99
|
select: vi.fn().mockResolvedValue({
|
|
100
100
|
data: [mockOrganisation, mockOrganisation2],
|
|
@@ -132,19 +132,28 @@ describe('OrganisationService', () => {
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it('should load user organisations on initialization', async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// Mock the direct query to rbac_organisation_roles with join
|
|
136
|
+
const mockQueryBuilder = {
|
|
137
|
+
select: vi.fn().mockReturnThis(),
|
|
138
|
+
eq: vi.fn().mockReturnThis(),
|
|
139
|
+
is: vi.fn().mockResolvedValue({
|
|
140
|
+
data: [{
|
|
141
|
+
id: mockMembership.id,
|
|
142
|
+
user_id: mockUser.id,
|
|
143
|
+
organisation_id: mockOrganisation.id,
|
|
144
|
+
role: mockMembership.role,
|
|
145
|
+
status: mockMembership.status,
|
|
146
|
+
granted_at: mockMembership.granted_at,
|
|
147
|
+
revoked_at: mockMembership.revoked_at,
|
|
148
|
+
core_organisations: mockOrganisation
|
|
149
|
+
}],
|
|
150
|
+
error: null
|
|
151
|
+
})
|
|
152
|
+
};
|
|
139
153
|
|
|
140
154
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
141
|
-
if (table === '
|
|
142
|
-
return
|
|
143
|
-
select: vi.fn().mockResolvedValue({
|
|
144
|
-
data: [mockOrganisation],
|
|
145
|
-
error: null
|
|
146
|
-
})
|
|
147
|
-
};
|
|
155
|
+
if (table === 'rbac_organisation_roles') {
|
|
156
|
+
return mockQueryBuilder;
|
|
148
157
|
}
|
|
149
158
|
return {
|
|
150
159
|
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
@@ -153,10 +162,10 @@ describe('OrganisationService', () => {
|
|
|
153
162
|
|
|
154
163
|
await organisationService.initialize();
|
|
155
164
|
|
|
156
|
-
expect(mockSupabase.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
expect(mockSupabase.from).toHaveBeenCalledWith('rbac_organisation_roles');
|
|
166
|
+
expect(mockQueryBuilder.eq).toHaveBeenCalledWith('user_id', mockUser.id);
|
|
167
|
+
expect(mockQueryBuilder.eq).toHaveBeenCalledWith('status', 'active');
|
|
168
|
+
expect(mockQueryBuilder.is).toHaveBeenCalledWith('revoked_at', null);
|
|
160
169
|
});
|
|
161
170
|
|
|
162
171
|
it('should handle missing dependencies gracefully', async () => {
|
|
@@ -209,6 +218,33 @@ describe('OrganisationService', () => {
|
|
|
209
218
|
|
|
210
219
|
it('should refresh organisations', async () => {
|
|
211
220
|
// First initialize the service to set up the basic state
|
|
221
|
+
const mockQueryBuilder = {
|
|
222
|
+
select: vi.fn().mockReturnThis(),
|
|
223
|
+
eq: vi.fn().mockReturnThis(),
|
|
224
|
+
is: vi.fn().mockResolvedValue({
|
|
225
|
+
data: [{
|
|
226
|
+
id: mockMembership.id,
|
|
227
|
+
user_id: mockUser.id,
|
|
228
|
+
organisation_id: mockOrganisation.id,
|
|
229
|
+
role: mockMembership.role,
|
|
230
|
+
status: mockMembership.status,
|
|
231
|
+
granted_at: mockMembership.granted_at,
|
|
232
|
+
revoked_at: mockMembership.revoked_at,
|
|
233
|
+
core_organisations: mockOrganisation
|
|
234
|
+
}],
|
|
235
|
+
error: null
|
|
236
|
+
})
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
240
|
+
if (table === 'rbac_organisation_roles') {
|
|
241
|
+
return mockQueryBuilder;
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
|
|
212
248
|
await organisationService.initialize();
|
|
213
249
|
|
|
214
250
|
const newMembership: OrganisationMembership = {
|
|
@@ -229,44 +265,49 @@ describe('OrganisationService', () => {
|
|
|
229
265
|
updated_at: '2024-01-01T00:00:00Z'
|
|
230
266
|
};
|
|
231
267
|
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
// Update mock for refresh
|
|
269
|
+
mockQueryBuilder.is.mockResolvedValue({
|
|
270
|
+
data: [
|
|
271
|
+
{
|
|
272
|
+
id: mockMembership.id,
|
|
273
|
+
user_id: mockUser.id,
|
|
274
|
+
organisation_id: mockOrganisation.id,
|
|
275
|
+
role: mockMembership.role,
|
|
276
|
+
status: mockMembership.status,
|
|
277
|
+
granted_at: mockMembership.granted_at,
|
|
278
|
+
revoked_at: mockMembership.revoked_at,
|
|
279
|
+
core_organisations: mockOrganisation
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: mockMembership2.id,
|
|
283
|
+
user_id: mockUser.id,
|
|
284
|
+
organisation_id: mockOrganisation2.id,
|
|
285
|
+
role: mockMembership2.role,
|
|
286
|
+
status: mockMembership2.status,
|
|
287
|
+
granted_at: mockMembership2.granted_at,
|
|
288
|
+
revoked_at: mockMembership2.revoked_at,
|
|
289
|
+
core_organisations: mockOrganisation2
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: newMembership.id,
|
|
293
|
+
user_id: mockUser.id,
|
|
294
|
+
organisation_id: newOrg.id,
|
|
295
|
+
role: newMembership.role,
|
|
296
|
+
status: newMembership.status,
|
|
297
|
+
granted_at: newMembership.granted_at,
|
|
298
|
+
revoked_at: newMembership.revoked_at,
|
|
299
|
+
core_organisations: newOrg
|
|
300
|
+
}
|
|
301
|
+
],
|
|
234
302
|
error: null
|
|
235
303
|
});
|
|
236
304
|
|
|
237
|
-
mockSupabase.from.mockImplementation((table: string) => {
|
|
238
|
-
if (table === 'organisations') {
|
|
239
|
-
return {
|
|
240
|
-
select: vi.fn().mockResolvedValue({
|
|
241
|
-
data: [mockOrganisation, mockOrganisation2, newOrg],
|
|
242
|
-
error: null
|
|
243
|
-
})
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
return {
|
|
247
|
-
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
248
|
-
};
|
|
249
|
-
});
|
|
250
|
-
|
|
251
305
|
await organisationService.refreshOrganisations();
|
|
252
306
|
|
|
253
|
-
expect(mockSupabase.
|
|
254
|
-
|
|
255
|
-
// Since the mock might not work correctly, let's verify the refresh was called
|
|
256
|
-
// and then manually set the expected state to verify the method works
|
|
257
|
-
const roleMap = new Map<string, string>();
|
|
258
|
-
roleMap.set('org-1', 'org_admin');
|
|
259
|
-
roleMap.set('org-2', 'member');
|
|
260
|
-
roleMap.set('org-3', 'member');
|
|
261
|
-
|
|
262
|
-
organisationService.setTestState(
|
|
263
|
-
[mockOrganisation, mockOrganisation2, newOrg],
|
|
264
|
-
[mockMembership, mockMembership2, newMembership],
|
|
265
|
-
roleMap,
|
|
266
|
-
mockOrganisation
|
|
267
|
-
);
|
|
307
|
+
expect(mockSupabase.from).toHaveBeenCalledWith('rbac_organisation_roles');
|
|
268
308
|
|
|
269
|
-
|
|
309
|
+
// Verify the refresh was called and state was updated
|
|
310
|
+
expect(organisationService.getOrganisations().length).toBeGreaterThanOrEqual(1);
|
|
270
311
|
});
|
|
271
312
|
|
|
272
313
|
it('should ensure organisation context', () => {
|
|
@@ -548,20 +589,53 @@ describe('OrganisationService', () => {
|
|
|
548
589
|
|
|
549
590
|
describe('Error Handling', () => {
|
|
550
591
|
it('should handle RPC errors gracefully', async () => {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
592
|
+
const mockQueryBuilder = {
|
|
593
|
+
select: vi.fn().mockReturnThis(),
|
|
594
|
+
eq: vi.fn().mockReturnThis(),
|
|
595
|
+
is: vi.fn().mockResolvedValue({
|
|
596
|
+
data: null,
|
|
597
|
+
error: { message: 'RPC error', code: 'PGRST_ERROR' }
|
|
598
|
+
})
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
602
|
+
if (table === 'rbac_organisation_roles') {
|
|
603
|
+
return mockQueryBuilder;
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
607
|
+
};
|
|
554
608
|
});
|
|
555
609
|
|
|
556
610
|
await organisationService.initialize();
|
|
557
611
|
|
|
558
612
|
expect(organisationService.getError()).toBeDefined();
|
|
559
|
-
|
|
613
|
+
// The error object is thrown, so check the message property
|
|
614
|
+
const error = organisationService.getError();
|
|
615
|
+
expect(error).toBeDefined();
|
|
616
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
617
|
+
expect((error as any).message).toBe('RPC error');
|
|
618
|
+
} else {
|
|
619
|
+
expect(String(error)).toContain('RPC error');
|
|
620
|
+
}
|
|
560
621
|
expect(organisationService.getOrganisations()).toEqual([]);
|
|
561
622
|
});
|
|
562
623
|
|
|
563
624
|
it('should handle network errors', async () => {
|
|
564
|
-
|
|
625
|
+
const mockQueryBuilder = {
|
|
626
|
+
select: vi.fn().mockReturnThis(),
|
|
627
|
+
eq: vi.fn().mockReturnThis(),
|
|
628
|
+
is: vi.fn().mockRejectedValue(new Error('Network error'))
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
632
|
+
if (table === 'rbac_organisation_roles') {
|
|
633
|
+
return mockQueryBuilder;
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
637
|
+
};
|
|
638
|
+
});
|
|
565
639
|
|
|
566
640
|
await organisationService.initialize();
|
|
567
641
|
|
|
@@ -570,9 +644,22 @@ describe('OrganisationService', () => {
|
|
|
570
644
|
});
|
|
571
645
|
|
|
572
646
|
it('should handle no memberships error', async () => {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
647
|
+
const mockQueryBuilder = {
|
|
648
|
+
select: vi.fn().mockReturnThis(),
|
|
649
|
+
eq: vi.fn().mockReturnThis(),
|
|
650
|
+
is: vi.fn().mockResolvedValue({
|
|
651
|
+
data: [],
|
|
652
|
+
error: null
|
|
653
|
+
})
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
657
|
+
if (table === 'rbac_organisation_roles') {
|
|
658
|
+
return mockQueryBuilder;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
662
|
+
};
|
|
576
663
|
});
|
|
577
664
|
|
|
578
665
|
await organisationService.initialize();
|
|
@@ -582,31 +669,67 @@ describe('OrganisationService', () => {
|
|
|
582
669
|
});
|
|
583
670
|
|
|
584
671
|
it('should handle invalid organisation IDs', async () => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
672
|
+
const mockQueryBuilder = {
|
|
673
|
+
select: vi.fn().mockReturnThis(),
|
|
674
|
+
eq: vi.fn().mockReturnThis(),
|
|
675
|
+
is: vi.fn().mockResolvedValue({
|
|
676
|
+
data: [{
|
|
677
|
+
id: mockMembership.id,
|
|
678
|
+
user_id: mockUser.id,
|
|
679
|
+
organisation_id: 'invalid-id', // Invalid UUID
|
|
680
|
+
role: mockMembership.role,
|
|
681
|
+
status: mockMembership.status,
|
|
682
|
+
granted_at: mockMembership.granted_at,
|
|
683
|
+
revoked_at: mockMembership.revoked_at,
|
|
684
|
+
core_organisations: null // No organisation data due to invalid ID
|
|
685
|
+
}],
|
|
686
|
+
error: null
|
|
687
|
+
})
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
691
|
+
if (table === 'rbac_organisation_roles') {
|
|
692
|
+
return mockQueryBuilder;
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
696
|
+
};
|
|
588
697
|
});
|
|
589
698
|
|
|
590
699
|
await organisationService.initialize();
|
|
591
700
|
|
|
592
701
|
expect(organisationService.getError()).toBeDefined();
|
|
593
|
-
|
|
702
|
+
// The service will throw "No organisations found in role data" when organisations array is empty
|
|
703
|
+
expect(organisationService.getError()?.message).toContain('No organisations found');
|
|
594
704
|
});
|
|
595
705
|
|
|
596
706
|
it('should handle no active organisations', async () => {
|
|
597
707
|
const inactiveOrg = { ...mockOrganisation, is_active: false };
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
708
|
+
const mockQueryBuilder = {
|
|
709
|
+
select: vi.fn().mockReturnThis(),
|
|
710
|
+
eq: vi.fn().mockReturnThis(),
|
|
711
|
+
is: vi.fn().mockResolvedValue({
|
|
712
|
+
data: [{
|
|
713
|
+
id: mockMembership.id,
|
|
714
|
+
user_id: mockUser.id,
|
|
715
|
+
organisation_id: mockOrganisation.id,
|
|
716
|
+
role: mockMembership.role,
|
|
717
|
+
status: mockMembership.status,
|
|
718
|
+
granted_at: mockMembership.granted_at,
|
|
719
|
+
revoked_at: mockMembership.revoked_at,
|
|
720
|
+
core_organisations: inactiveOrg
|
|
721
|
+
}],
|
|
608
722
|
error: null
|
|
609
723
|
})
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
727
|
+
if (table === 'rbac_organisation_roles') {
|
|
728
|
+
return mockQueryBuilder;
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
732
|
+
};
|
|
610
733
|
});
|
|
611
734
|
|
|
612
735
|
await organisationService.initialize();
|
|
@@ -792,7 +915,7 @@ describe('OrganisationService', () => {
|
|
|
792
915
|
})
|
|
793
916
|
};
|
|
794
917
|
}
|
|
795
|
-
if (table === '
|
|
918
|
+
if (table === 'core_organisations') {
|
|
796
919
|
return {
|
|
797
920
|
select: vi.fn().mockResolvedValue({
|
|
798
921
|
data: [mockOrganisation],
|
|
@@ -847,19 +970,27 @@ describe('OrganisationService', () => {
|
|
|
847
970
|
});
|
|
848
971
|
|
|
849
972
|
it('should handle invalid organisation IDs in memberships', async () => {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
973
|
+
const mockQueryBuilder = {
|
|
974
|
+
select: vi.fn().mockReturnThis(),
|
|
975
|
+
eq: vi.fn().mockReturnThis(),
|
|
976
|
+
is: vi.fn().mockResolvedValue({
|
|
977
|
+
data: [{
|
|
978
|
+
id: mockMembership.id,
|
|
979
|
+
user_id: mockUser.id,
|
|
980
|
+
organisation_id: '', // Empty/invalid ID
|
|
981
|
+
role: mockMembership.role,
|
|
982
|
+
status: mockMembership.status,
|
|
983
|
+
granted_at: mockMembership.granted_at,
|
|
984
|
+
revoked_at: mockMembership.revoked_at,
|
|
985
|
+
core_organisations: null // No organisation data due to invalid ID
|
|
986
|
+
}],
|
|
987
|
+
error: null
|
|
988
|
+
})
|
|
989
|
+
};
|
|
854
990
|
|
|
855
991
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
856
|
-
if (table === '
|
|
857
|
-
return
|
|
858
|
-
select: vi.fn().mockResolvedValue({
|
|
859
|
-
data: [],
|
|
860
|
-
error: null
|
|
861
|
-
})
|
|
862
|
-
};
|
|
992
|
+
if (table === 'rbac_organisation_roles') {
|
|
993
|
+
return mockQueryBuilder;
|
|
863
994
|
}
|
|
864
995
|
return {
|
|
865
996
|
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
@@ -869,7 +1000,8 @@ describe('OrganisationService', () => {
|
|
|
869
1000
|
await organisationService.initialize();
|
|
870
1001
|
|
|
871
1002
|
expect(organisationService.getError()).toBeDefined();
|
|
872
|
-
|
|
1003
|
+
// The service throws "No organisations found in role data" when organisations array is empty
|
|
1004
|
+
expect(organisationService.getError()?.message).toContain('No organisations found');
|
|
873
1005
|
});
|
|
874
1006
|
|
|
875
1007
|
it('should handle non-UUID organisation IDs', async () => {
|
|
@@ -879,7 +1011,7 @@ describe('OrganisationService', () => {
|
|
|
879
1011
|
});
|
|
880
1012
|
|
|
881
1013
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
882
|
-
if (table === '
|
|
1014
|
+
if (table === 'core_organisations') {
|
|
883
1015
|
return {
|
|
884
1016
|
select: vi.fn().mockResolvedValue({
|
|
885
1017
|
data: [],
|
|
@@ -952,7 +1084,7 @@ describe('OrganisationService', () => {
|
|
|
952
1084
|
});
|
|
953
1085
|
|
|
954
1086
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
955
|
-
if (table === '
|
|
1087
|
+
if (table === 'core_organisations') {
|
|
956
1088
|
return {
|
|
957
1089
|
select: vi.fn().mockResolvedValue({
|
|
958
1090
|
data: [mockOrganisation],
|
|
@@ -980,7 +1112,7 @@ describe('OrganisationService', () => {
|
|
|
980
1112
|
});
|
|
981
1113
|
|
|
982
1114
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
983
|
-
if (table === '
|
|
1115
|
+
if (table === 'core_organisations') {
|
|
984
1116
|
return {
|
|
985
1117
|
select: vi.fn().mockResolvedValue({
|
|
986
1118
|
data: [mockOrganisation],
|