@jmruthers/pace-core 0.5.189 → 0.5.190
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-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.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-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.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 +3 -4
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- 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 +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.js +79 -69
- 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 +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- 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 +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- 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 +1 -1
- 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 +1 -1
- 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 +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/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 +4 -4
- 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 +151 -92
- 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/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 +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- 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 +8 -8
- 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.tsx +5 -5
- 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.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- 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 +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- 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 +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- 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/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Auth/RBAC E2E Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Tests/E2E
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* End-to-end tests for critical auth/RBAC flows through UI.
|
|
8
|
+
* Tests complete user journeys: login → organisation selection → permission check → data access.
|
|
9
|
+
*
|
|
10
|
+
* These tests use RTL + userEvent to simulate real user interactions and verify
|
|
11
|
+
* that security constraints are enforced throughout the flow.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
17
|
+
import userEvent from '@testing-library/user-event';
|
|
18
|
+
import { UnifiedAuthProvider } from '../../providers/services/UnifiedAuthProvider';
|
|
19
|
+
import { OrganisationProvider, useOrganisations } from '../../providers/OrganisationProvider';
|
|
20
|
+
import { PagePermissionGuard } from '../components/PagePermissionGuard';
|
|
21
|
+
import { createTestSupabaseClient, createMockUser, createMockSession, createMockOrganisation } from '../../__tests__/fixtures/supabase';
|
|
22
|
+
import type { User, Session } from '@supabase/supabase-js';
|
|
23
|
+
|
|
24
|
+
const ORG_ONE_ID = '00000000-0000-0000-0000-000000000123';
|
|
25
|
+
const ORG_TWO_ID = '00000000-0000-0000-0000-000000000456';
|
|
26
|
+
const EVENT_ONE_ID = '00000000-0000-0000-0000-000000000789';
|
|
27
|
+
const EVENT_TWO_ID = '00000000-0000-0000-0000-000000000790';
|
|
28
|
+
|
|
29
|
+
// Test component that uses RBAC
|
|
30
|
+
const TestProtectedPage = ({ pageName = 'dashboard' }: { pageName?: string }) => {
|
|
31
|
+
return (
|
|
32
|
+
<PagePermissionGuard pageName={pageName} operation="read">
|
|
33
|
+
<div data-testid="protected-content">
|
|
34
|
+
<h1>Protected Content</h1>
|
|
35
|
+
<p>You have access to this page</p>
|
|
36
|
+
</div>
|
|
37
|
+
</PagePermissionGuard>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Test component for organisation selection
|
|
42
|
+
const TestOrganisationSelector = () => {
|
|
43
|
+
const { selectedOrganisation, organisations, switchOrganisation } = useOrganisations();
|
|
44
|
+
|
|
45
|
+
const handleChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
46
|
+
if (e.target.value && switchOrganisation) {
|
|
47
|
+
await switchOrganisation(e.target.value);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div data-testid="org-selector">
|
|
53
|
+
<select
|
|
54
|
+
data-testid="org-select"
|
|
55
|
+
value={selectedOrganisation?.id || ''}
|
|
56
|
+
onChange={handleChange}
|
|
57
|
+
>
|
|
58
|
+
<option value="">Select Organisation</option>
|
|
59
|
+
{organisations.map((org) => (
|
|
60
|
+
<option key={org.id} value={org.id}>
|
|
61
|
+
{org.name}
|
|
62
|
+
</option>
|
|
63
|
+
))}
|
|
64
|
+
</select>
|
|
65
|
+
{selectedOrganisation && (
|
|
66
|
+
<div data-testid="selected-org">{selectedOrganisation.name}</div>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const waitForOrganisationOption = async (label: string) => {
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(screen.getByRole('option', { name: label })).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
describe('Auth/RBAC E2E Flows', () => {
|
|
79
|
+
let mockSupabase: ReturnType<typeof createTestSupabaseClient>;
|
|
80
|
+
let mockUser: User;
|
|
81
|
+
let mockSession: Session;
|
|
82
|
+
let organisationsFixture: ReturnType<typeof createMockOrganisation>[];
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
vi.clearAllMocks();
|
|
86
|
+
localStorage.clear();
|
|
87
|
+
|
|
88
|
+
// Use shared fixtures for consistent test setup
|
|
89
|
+
mockUser = createMockUser({ id: '00000000-0000-0000-0000-000000000100', email: 'user@example.com' });
|
|
90
|
+
mockSession = createMockSession(mockUser);
|
|
91
|
+
organisationsFixture = [
|
|
92
|
+
createMockOrganisation({ id: ORG_ONE_ID, name: 'Test Organisation 1', slug: 'test-org-1' }),
|
|
93
|
+
createMockOrganisation({ id: ORG_TWO_ID, name: 'Test Organisation 2', slug: 'test-org-2' })
|
|
94
|
+
];
|
|
95
|
+
expect(organisationsFixture[0].id).toBe(ORG_ONE_ID);
|
|
96
|
+
expect(organisationsFixture[1].id).toBe(ORG_TWO_ID);
|
|
97
|
+
|
|
98
|
+
// Create test Supabase client with standard RBAC mocks
|
|
99
|
+
mockSupabase = createTestSupabaseClient({
|
|
100
|
+
user: mockUser,
|
|
101
|
+
session: mockSession,
|
|
102
|
+
rpcResponses: {
|
|
103
|
+
// Actual RPCs used by the RBAC system
|
|
104
|
+
rbac_check_permission_simplified: (params?: any) => {
|
|
105
|
+
// Grant access if organisation_id is provided
|
|
106
|
+
if (params?.p_organisation_id) {
|
|
107
|
+
return Promise.resolve({ data: true, error: null });
|
|
108
|
+
}
|
|
109
|
+
return Promise.resolve({ data: false, error: null });
|
|
110
|
+
},
|
|
111
|
+
util_app_resolve: (params?: any) => {
|
|
112
|
+
// Return app ID for TEST_APP
|
|
113
|
+
return Promise.resolve({ data: { id: 'app-123', name: 'TEST_APP' }, error: null });
|
|
114
|
+
},
|
|
115
|
+
data_user_organisation_roles_get: (params?: any) => {
|
|
116
|
+
// Return organisation roles/memberships
|
|
117
|
+
// The service expects: organisation_id, role (not role_name), user_id, status
|
|
118
|
+
// These must match the organisation IDs in tableData
|
|
119
|
+
return Promise.resolve({
|
|
120
|
+
data: [
|
|
121
|
+
{
|
|
122
|
+
organisation_id: ORG_ONE_ID,
|
|
123
|
+
role: 'member',
|
|
124
|
+
user_id: 'user-123',
|
|
125
|
+
status: 'active'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
organisation_id: ORG_TWO_ID,
|
|
129
|
+
role: 'member',
|
|
130
|
+
user_id: 'user-123',
|
|
131
|
+
status: 'active'
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
error: null
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
rbac_permissions_get: (params?: any) => {
|
|
138
|
+
return Promise.resolve({
|
|
139
|
+
data: [
|
|
140
|
+
{ permission_type: 'organisation_access', role_name: 'member', context_id: params?.p_organisation_id || ORG_ONE_ID }
|
|
141
|
+
],
|
|
142
|
+
error: null
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
rbac_access_validate: (params?: any) => {
|
|
146
|
+
return Promise.resolve({
|
|
147
|
+
data: [{ has_access: true, access_level: 'organisation', context_id: params?.p_resource_id }],
|
|
148
|
+
error: null
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
tableData: {
|
|
153
|
+
organisations: organisationsFixture,
|
|
154
|
+
event: [
|
|
155
|
+
{ event_id: EVENT_ONE_ID, event_name: 'Test Event', organisation_id: ORG_ONE_ID }
|
|
156
|
+
],
|
|
157
|
+
rbac_apps: [
|
|
158
|
+
{ id: 'app-123', name: 'TEST_APP', is_active: true, requires_event: false }
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.log('[auth-rbac.e2e] organisationsFixture', organisationsFixture);
|
|
165
|
+
|
|
166
|
+
const baseFrom = mockSupabase.from;
|
|
167
|
+
mockSupabase.from = vi.fn((table?: string) => {
|
|
168
|
+
// eslint-disable-next-line no-console
|
|
169
|
+
console.log('[auth-rbac.e2e] mockSupabase.from called with', table);
|
|
170
|
+
if (table === 'organisations') {
|
|
171
|
+
return {
|
|
172
|
+
select: vi.fn().mockResolvedValue({ data: organisationsFixture, error: null })
|
|
173
|
+
} as any;
|
|
174
|
+
}
|
|
175
|
+
return baseFrom(table as any);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Complete Auth → RBAC → Data Access Flow', () => {
|
|
180
|
+
it.skip('completes full flow: login → organisation selection → permission check → page access', async () => {
|
|
181
|
+
const user = userEvent.setup();
|
|
182
|
+
|
|
183
|
+
render(
|
|
184
|
+
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="TEST_APP">
|
|
185
|
+
<OrganisationProvider>
|
|
186
|
+
<TestOrganisationSelector />
|
|
187
|
+
<TestProtectedPage pageName="dashboard" />
|
|
188
|
+
</OrganisationProvider>
|
|
189
|
+
</UnifiedAuthProvider>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Step 1: Wait for auth to initialize
|
|
193
|
+
await waitFor(() => {
|
|
194
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Step 2: Select organisation
|
|
198
|
+
const orgSelect = screen.getByTestId('org-select');
|
|
199
|
+
await waitForOrganisationOption('Test Organisation 1');
|
|
200
|
+
await user.selectOptions(orgSelect, ORG_ONE_ID);
|
|
201
|
+
|
|
202
|
+
// Step 3: Verify organisation is selected
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(screen.getByTestId('selected-org')).toHaveTextContent('Test Organisation 1');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Step 4: Verify permission check was called with organisation context
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
210
|
+
'rbac_check_permission_simplified',
|
|
211
|
+
expect.objectContaining({
|
|
212
|
+
p_organisation_id: ORG_ONE_ID
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Step 5: Verify protected content is rendered
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(screen.getByTestId('protected-content')).toBeInTheDocument();
|
|
220
|
+
expect(screen.getByText('Protected Content')).toBeInTheDocument();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('prevents page access without organisation context', async () => {
|
|
225
|
+
// Mock RPC to deny access when no organisation
|
|
226
|
+
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
227
|
+
if (functionName === 'rbac_check_permission_simplified') {
|
|
228
|
+
if (!params?.p_organisation_id) {
|
|
229
|
+
return Promise.resolve({ data: false, error: null });
|
|
230
|
+
}
|
|
231
|
+
return Promise.resolve({ data: true, error: null });
|
|
232
|
+
}
|
|
233
|
+
// Handle other RPCs
|
|
234
|
+
if (functionName === 'util_app_resolve') {
|
|
235
|
+
return Promise.resolve({ data: { id: 'app-123', name: 'TEST_APP' }, error: null });
|
|
236
|
+
}
|
|
237
|
+
if (functionName === 'data_user_organisation_roles_get') {
|
|
238
|
+
return Promise.resolve({ data: [], error: null });
|
|
239
|
+
}
|
|
240
|
+
return Promise.resolve({ data: null, error: null });
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
render(
|
|
244
|
+
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="TEST_APP">
|
|
245
|
+
<OrganisationProvider>
|
|
246
|
+
<TestProtectedPage pageName="dashboard" />
|
|
247
|
+
</OrganisationProvider>
|
|
248
|
+
</UnifiedAuthProvider>
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Wait for auth to initialize
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Verify permission check was called (may be called with or without organisation)
|
|
257
|
+
// The actual check happens via rbac_check_permission_simplified
|
|
258
|
+
await waitFor(() => {
|
|
259
|
+
expect(mockSupabase.rpc).toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Protected content should not be visible (PagePermissionGuard should hide it)
|
|
263
|
+
// Note: PagePermissionGuard behavior depends on implementation
|
|
264
|
+
// This test verifies the permission check is called correctly
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it.skip('enforces organisation context switching and re-checks permissions', async () => {
|
|
268
|
+
const user = userEvent.setup();
|
|
269
|
+
|
|
270
|
+
render(
|
|
271
|
+
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="TEST_APP">
|
|
272
|
+
<OrganisationProvider>
|
|
273
|
+
<TestOrganisationSelector />
|
|
274
|
+
<TestProtectedPage pageName="dashboard" />
|
|
275
|
+
</OrganisationProvider>
|
|
276
|
+
</UnifiedAuthProvider>
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// Wait for initial load
|
|
280
|
+
await waitFor(() => {
|
|
281
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Select first organisation
|
|
285
|
+
const orgSelect = screen.getByTestId('org-select');
|
|
286
|
+
await waitForOrganisationOption('Test Organisation 1');
|
|
287
|
+
await user.selectOptions(orgSelect, ORG_ONE_ID);
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(screen.getByTestId('selected-org')).toHaveTextContent('Test Organisation 1');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Clear previous RPC calls
|
|
294
|
+
vi.clearAllMocks();
|
|
295
|
+
|
|
296
|
+
// Switch to second organisation
|
|
297
|
+
await waitForOrganisationOption('Test Organisation 2');
|
|
298
|
+
await user.selectOptions(orgSelect, ORG_TWO_ID);
|
|
299
|
+
|
|
300
|
+
// Verify permission check was called with new organisation context
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
303
|
+
'rbac_check_permission_simplified',
|
|
304
|
+
expect.objectContaining({
|
|
305
|
+
p_organisation_id: ORG_TWO_ID
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('Cross-Tenant Data Isolation', () => {
|
|
313
|
+
it.skip('prevents data access across organisation boundaries', async () => {
|
|
314
|
+
const user = userEvent.setup();
|
|
315
|
+
|
|
316
|
+
// Mock data queries to return organisation-scoped data
|
|
317
|
+
let currentOrgId = ORG_ONE_ID;
|
|
318
|
+
mockSupabase.from().select.mockImplementation((table: string) => {
|
|
319
|
+
if (table === 'organisations') {
|
|
320
|
+
return Promise.resolve({
|
|
321
|
+
data: [
|
|
322
|
+
{ id: ORG_ONE_ID, name: 'Org 1' },
|
|
323
|
+
{ id: ORG_TWO_ID, name: 'Org 2' }
|
|
324
|
+
],
|
|
325
|
+
error: null
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (table === 'event') {
|
|
329
|
+
// Return events only for current organisation
|
|
330
|
+
const events = [
|
|
331
|
+
{ event_id: EVENT_ONE_ID, event_name: 'Org 1 Event', organisation_id: ORG_ONE_ID },
|
|
332
|
+
{ event_id: EVENT_TWO_ID, event_name: 'Org 2 Event', organisation_id: ORG_TWO_ID }
|
|
333
|
+
];
|
|
334
|
+
return Promise.resolve({
|
|
335
|
+
data: events.filter(e => e.organisation_id === currentOrgId),
|
|
336
|
+
error: null
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return Promise.resolve({ data: null, error: null });
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
render(
|
|
343
|
+
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="TEST_APP">
|
|
344
|
+
<OrganisationProvider>
|
|
345
|
+
<TestOrganisationSelector />
|
|
346
|
+
</OrganisationProvider>
|
|
347
|
+
</UnifiedAuthProvider>
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// Select first organisation
|
|
351
|
+
const orgSelect = screen.getByTestId('org-select');
|
|
352
|
+
await waitForOrganisationOption('Org 1');
|
|
353
|
+
await user.selectOptions(orgSelect, ORG_ONE_ID);
|
|
354
|
+
|
|
355
|
+
await waitFor(() => {
|
|
356
|
+
expect(screen.getByTestId('selected-org')).toHaveTextContent('Org 1');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Verify events query was filtered by organisation
|
|
360
|
+
await waitFor(() => {
|
|
361
|
+
expect(mockSupabase.from).toHaveBeenCalledWith('event');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Switch to second organisation
|
|
365
|
+
currentOrgId = ORG_TWO_ID;
|
|
366
|
+
await waitForOrganisationOption('Org 2');
|
|
367
|
+
await user.selectOptions(orgSelect, ORG_TWO_ID);
|
|
368
|
+
|
|
369
|
+
await waitFor(() => {
|
|
370
|
+
expect(screen.getByTestId('selected-org')).toHaveTextContent('Org 2');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Verify new query was made with new organisation context
|
|
374
|
+
// (This tests that organisation switching triggers new data fetches)
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('Event-Based Scope Resolution', () => {
|
|
379
|
+
it.skip('resolves event-based permissions correctly', async () => {
|
|
380
|
+
const user = userEvent.setup();
|
|
381
|
+
|
|
382
|
+
// Mock event-based permission check
|
|
383
|
+
mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
|
|
384
|
+
if (functionName === 'rbac_check_permission_simplified') {
|
|
385
|
+
// Grant access if event_id is provided
|
|
386
|
+
if (params?.p_event_id) {
|
|
387
|
+
return Promise.resolve({ data: true, error: null });
|
|
388
|
+
}
|
|
389
|
+
// Also grant if organisation_id is provided
|
|
390
|
+
if (params?.p_organisation_id) {
|
|
391
|
+
return Promise.resolve({ data: true, error: null });
|
|
392
|
+
}
|
|
393
|
+
return Promise.resolve({ data: false, error: null });
|
|
394
|
+
}
|
|
395
|
+
// Handle other RPCs
|
|
396
|
+
if (functionName === 'util_app_resolve') {
|
|
397
|
+
return Promise.resolve({ data: { id: 'app-123', name: 'TEST_APP' }, error: null });
|
|
398
|
+
}
|
|
399
|
+
if (functionName === 'data_user_organisation_roles_get') {
|
|
400
|
+
return Promise.resolve({
|
|
401
|
+
data: [
|
|
402
|
+
{
|
|
403
|
+
organisation_id: ORG_ONE_ID,
|
|
404
|
+
role: 'member',
|
|
405
|
+
user_id: 'user-123',
|
|
406
|
+
status: 'active'
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
organisation_id: ORG_TWO_ID,
|
|
410
|
+
role: 'member',
|
|
411
|
+
user_id: 'user-123',
|
|
412
|
+
status: 'active'
|
|
413
|
+
}
|
|
414
|
+
],
|
|
415
|
+
error: null
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return Promise.resolve({ data: null, error: null });
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
render(
|
|
422
|
+
<UnifiedAuthProvider supabaseClient={mockSupabase} appName="TEST_APP">
|
|
423
|
+
<OrganisationProvider>
|
|
424
|
+
<TestOrganisationSelector />
|
|
425
|
+
<TestProtectedPage pageName="dashboard" />
|
|
426
|
+
</OrganisationProvider>
|
|
427
|
+
</UnifiedAuthProvider>
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
// Select organisation
|
|
431
|
+
const orgSelect = screen.getByTestId('org-select');
|
|
432
|
+
await waitForOrganisationOption('Test Organisation 1');
|
|
433
|
+
await user.selectOptions(orgSelect, ORG_ONE_ID);
|
|
434
|
+
|
|
435
|
+
await waitFor(() => {
|
|
436
|
+
expect(screen.getByTestId('selected-org')).toHaveTextContent('Test Organisation 1');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Verify permission check includes organisation context
|
|
440
|
+
await waitFor(() => {
|
|
441
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
442
|
+
'rbac_check_permission_simplified',
|
|
443
|
+
expect.objectContaining({
|
|
444
|
+
p_organisation_id: ORG_ONE_ID
|
|
445
|
+
})
|
|
446
|
+
);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
@@ -369,6 +369,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
369
369
|
is: vi.fn().mockReturnThis(),
|
|
370
370
|
lte: vi.fn().mockReturnThis(),
|
|
371
371
|
or: vi.fn().mockReturnThis(),
|
|
372
|
+
limit: vi.fn().mockResolvedValue({
|
|
373
|
+
data: [{ role: 'org_admin' }],
|
|
374
|
+
error: null
|
|
375
|
+
}),
|
|
372
376
|
single: vi.fn().mockResolvedValue({
|
|
373
377
|
data: { role: 'org_admin' },
|
|
374
378
|
error: null
|
|
@@ -432,6 +436,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
432
436
|
is: vi.fn().mockReturnThis(),
|
|
433
437
|
lte: vi.fn().mockReturnThis(),
|
|
434
438
|
or: vi.fn().mockReturnThis(),
|
|
439
|
+
limit: vi.fn().mockResolvedValue({
|
|
440
|
+
data: [],
|
|
441
|
+
error: { code: 'PGRST116' }
|
|
442
|
+
}),
|
|
435
443
|
single: vi.fn().mockResolvedValue({
|
|
436
444
|
data: null,
|
|
437
445
|
error: { code: 'PGRST116' }
|
|
@@ -502,6 +510,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
502
510
|
is: vi.fn().mockReturnThis(),
|
|
503
511
|
lte: vi.fn().mockReturnThis(),
|
|
504
512
|
or: vi.fn().mockReturnThis(),
|
|
513
|
+
limit: vi.fn().mockResolvedValue({
|
|
514
|
+
data: [],
|
|
515
|
+
error: null
|
|
516
|
+
}),
|
|
505
517
|
single: vi.fn().mockResolvedValue({
|
|
506
518
|
data: null,
|
|
507
519
|
error: { code: 'PGRST116' }
|
|
@@ -208,6 +208,10 @@ describe('RBACEngine - Core Logic Tests', () => {
|
|
|
208
208
|
is: vi.fn().mockReturnThis(),
|
|
209
209
|
lte: vi.fn().mockReturnThis(),
|
|
210
210
|
or: vi.fn().mockReturnThis(),
|
|
211
|
+
limit: vi.fn().mockResolvedValue({
|
|
212
|
+
data: [{ role: 'org_admin' }],
|
|
213
|
+
error: null
|
|
214
|
+
}),
|
|
211
215
|
single: vi.fn().mockResolvedValue({
|
|
212
216
|
data: { role: 'org_admin' },
|
|
213
217
|
error: null
|
|
@@ -256,6 +260,10 @@ describe('RBACEngine - Core Logic Tests', () => {
|
|
|
256
260
|
is: vi.fn().mockReturnThis(),
|
|
257
261
|
lte: vi.fn().mockReturnThis(),
|
|
258
262
|
or: vi.fn().mockReturnThis(),
|
|
263
|
+
limit: vi.fn().mockResolvedValue({
|
|
264
|
+
data: [],
|
|
265
|
+
error: null
|
|
266
|
+
}),
|
|
259
267
|
single: vi.fn().mockResolvedValue({ data: null, error: null })
|
|
260
268
|
};
|
|
261
269
|
|
|
@@ -176,6 +176,10 @@ describe('RBACEngine - Simplified Tests', () => {
|
|
|
176
176
|
is: vi.fn().mockReturnThis(),
|
|
177
177
|
lte: vi.fn().mockReturnThis(),
|
|
178
178
|
or: vi.fn().mockReturnThis(),
|
|
179
|
+
limit: vi.fn().mockResolvedValue({
|
|
180
|
+
data: [{ role: 'org_admin' }],
|
|
181
|
+
error: null
|
|
182
|
+
}),
|
|
179
183
|
single: vi.fn().mockResolvedValue({
|
|
180
184
|
data: { role: 'org_admin' },
|
|
181
185
|
error: null
|