@jmruthers/pace-core 0.6.4 → 0.6.6
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/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-pace-core-compliance.mdc +128 -26
- package/cursor-rules/01-standards-compliance.mdc +49 -8
- package/cursor-rules/02-project-structure.mdc +6 -0
- package/cursor-rules/03-solid-principles.mdc +2 -0
- package/cursor-rules/04-testing-standards.mdc +2 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
- package/cursor-rules/06-code-quality.mdc +2 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
- package/cursor-rules/08-markup-quality.mdc +52 -27
- package/cursor-rules/09-rbac-compliance.mdc +462 -0
- package/cursor-rules/10-error-handling-patterns.mdc +179 -0
- package/cursor-rules/11-performance-optimization.mdc +169 -0
- package/cursor-rules/12-ci-cd-integration.mdc +150 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
- package/dist/DataTable-LRJL4IRV.js +15 -0
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
- package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
- package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/chunk-4T7OBVTU.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
- package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
- package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
- package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/chunk-OHIK3MIO.js +994 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
- package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
- package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
- package/dist/components.d.ts +6 -6
- package/dist/components.js +57 -267
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +22 -0
- package/dist/eslint-rules/rules/compliance.cjs +348 -0
- package/dist/eslint-rules/rules/components.cjs +113 -0
- package/dist/eslint-rules/rules/imports.cjs +102 -0
- package/dist/eslint-rules/rules/rbac.cjs +790 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +62 -270
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +36 -26
- package/dist/index.js +87 -690
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +124 -594
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +16 -6
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +454 -930
- package/docs/api-reference/components.md +3 -1
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/best-practices/accessibility.md +6 -3
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/permission-enforcement.md +4 -0
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +12 -3
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/secure-client-protection.md +0 -34
- package/docs/standards/00-pace-core-compliance.md +967 -0
- package/docs/standards/01-standards-compliance.md +188 -0
- package/docs/standards/02-project-structure.md +985 -0
- package/docs/standards/03-solid-principles.md +39 -0
- package/docs/standards/04-testing-standards.md +36 -0
- package/docs/standards/05-bug-reports-and-features.md +27 -0
- package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
- package/docs/standards/07-tech-stack-compliance.md +30 -0
- package/docs/standards/08-markup-quality.md +345 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
- package/docs/standards/10-error-handling-patterns.md +401 -0
- package/docs/standards/11-performance-optimization.md +348 -0
- package/docs/standards/12-ci-cd-integration.md +370 -0
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
- package/docs/standards/README.md +62 -33
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +20 -4
- package/package.json +31 -21
- package/scripts/audit/audit-compliance.cjs +1295 -0
- package/scripts/audit/audit-components.cjs +260 -0
- package/scripts/audit/audit-dependencies.cjs +395 -0
- package/scripts/audit/audit-rbac.cjs +954 -0
- package/scripts/audit/audit-standards.cjs +1268 -0
- package/scripts/audit/index.cjs +1898 -194
- package/scripts/install-cursor-rules.cjs +259 -8
- package/scripts/validate-master.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/ContextSelector/ContextSelector.tsx +42 -39
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/DataTableBody.tsx +55 -31
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +7 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/core/DataTableContext.tsx +1 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -0
- package/src/components/DateTimeField/DateTimeField.tsx +20 -20
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +361 -318
- package/src/components/Dialog/Dialog.tsx +1154 -323
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
- package/src/components/FileDisplay/FileDisplay.tsx +28 -22
- package/src/components/Form/Form.test.tsx +9 -10
- package/src/components/Form/Form.tsx +369 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Select/types.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +39 -34
- package/src/components/index.ts +3 -4
- package/src/eslint-rules/index.cjs +22 -0
- package/src/eslint-rules/rules/compliance.cjs +348 -0
- package/src/eslint-rules/rules/components.cjs +113 -0
- package/src/eslint-rules/rules/imports.cjs +102 -0
- package/src/eslint-rules/rules/rbac.cjs +790 -0
- package/src/eslint-rules/utils/helpers.cjs +42 -0
- package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +62 -190
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +19 -9
- package/src/hooks/useAppConfig.ts +26 -24
- package/src/hooks/useEventTheme.test.ts +211 -233
- package/src/hooks/useEventTheme.ts +19 -28
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +9 -11
- package/src/hooks/useOrganisations.ts +13 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +16 -1
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
- package/src/rbac/README.md +20 -20
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
- package/src/rbac/hooks/useResourcePermissions.ts +182 -63
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +45 -7
- package/src/services/AuthService.ts +132 -23
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +155 -58
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +4 -0
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/formatting/formatTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-E7YQZD7D.js +0 -175
- package/dist/DataTable-E7YQZD7D.js.map +0 -1
- package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
- package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
- package/dist/api-6LVZTHDS.js +0 -52
- package/dist/api-6LVZTHDS.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-36LVWXB2.js +0 -227
- package/dist/chunk-36LVWXB2.js.map +0 -1
- package/dist/chunk-3LPHPB62.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-ATKZM7RX.js +0 -2053
- package/dist/chunk-ATKZM7RX.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-I6DAQMWX.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-YKRAFF5K.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-OOPCLPZW.js +0 -9
- package/dist/contextValidator-OOPCLPZW.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/eslint-rules/pace-core-compliance.cjs +0 -510
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -40,13 +40,16 @@
|
|
|
40
40
|
* - Missing user context results in all permissions being denied
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { useMemo } from 'react';
|
|
43
|
+
import { useMemo, useState, useEffect, useRef } from 'react';
|
|
44
44
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
45
45
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
46
46
|
import { useEvents } from '../../hooks/useEvents';
|
|
47
|
+
import type { Event } from '../../types/event';
|
|
47
48
|
import { useResolvedScope } from './useResolvedScope';
|
|
48
49
|
import { useCan } from './usePermissions';
|
|
49
|
-
import
|
|
50
|
+
import { isSuperAdmin } from '../api';
|
|
51
|
+
import { createLogger } from '../../utils/core/logger';
|
|
52
|
+
import type { Scope, Permission } from '../types';
|
|
50
53
|
|
|
51
54
|
export interface UseResourcePermissionsOptions {
|
|
52
55
|
/** Whether to check read permissions (default: false) */
|
|
@@ -80,15 +83,22 @@ export interface ResourcePermissions {
|
|
|
80
83
|
* and provides a simple API for permission checking.
|
|
81
84
|
*
|
|
82
85
|
* **Page Permission Support:**
|
|
83
|
-
* When an `appId` is available in the resolved scope, the
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
86
|
+
* When an `appId` is available in the resolved scope, the hook automatically:
|
|
87
|
+
* 1. Waits for scope resolution to complete (including `appId` being set)
|
|
88
|
+
* 2. Constructs permission strings with the `page.` prefix (e.g., `create:page.planning`)
|
|
89
|
+
* 3. Passes the resource name as `pageId` to enable page-based permission checks
|
|
87
90
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
91
|
+
* This ensures permission strings match the format returned by `rbac_permissions_get`
|
|
92
|
+
* (e.g., `create:page.planning`) rather than resource-based format (e.g., `create:planning`).
|
|
93
|
+
*
|
|
94
|
+
* **Scope Resolution Timing:**
|
|
95
|
+
* The hook waits for scope resolution to complete before constructing permission strings.
|
|
96
|
+
* This prevents timing issues where permission checks use the wrong format (e.g., `delete:planning`
|
|
97
|
+
* instead of `delete:page.planning`) when `appId` is not yet available in the scope.
|
|
98
|
+
*
|
|
99
|
+
* The RPC function `rbac_check_permission_simplified` will resolve the page name to a page ID
|
|
100
|
+
* and check page permissions if the resource matches a registered page in `rbac_app_pages`.
|
|
101
|
+
* If the resource is not a registered page, it will fall back to resource-based permission checking.
|
|
92
102
|
*
|
|
93
103
|
* @param resource - The resource name (e.g., 'contacts', 'risks', 'planning')
|
|
94
104
|
* Can be a resource name or a page name registered in rbac_app_pages
|
|
@@ -133,15 +143,80 @@ export function useResourcePermissions(
|
|
|
133
143
|
options: UseResourcePermissionsOptions = {}
|
|
134
144
|
): ResourcePermissions {
|
|
135
145
|
const { enableRead = false, requireScope = true } = options;
|
|
146
|
+
const logger = createLogger('ResourcePermissions');
|
|
136
147
|
|
|
137
148
|
// Get user and supabase client from UnifiedAuth
|
|
138
149
|
const { user, supabase } = useUnifiedAuth();
|
|
150
|
+
|
|
151
|
+
// Super admin status - check if user has super admin privileges
|
|
152
|
+
// Super admins bypass all permission checks (similar to useDataTablePermissions)
|
|
153
|
+
// PERFORMANCE OPTIMIZATION: Check super admin once and share with all useCan hooks to avoid duplicate queries
|
|
154
|
+
// Use null to indicate "not checked yet" vs false which means "checked and not super admin"
|
|
155
|
+
const [isSuperAdminUser, setIsSuperAdminUser] = useState<boolean | null>(null);
|
|
156
|
+
const [isCheckingSuperAdmin, setIsCheckingSuperAdmin] = useState<boolean>(() => !!user?.id);
|
|
157
|
+
const lastCheckedUserIdRef = useRef<string | null>(null);
|
|
158
|
+
const isCheckingRef = useRef<boolean>(false);
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
// Skip if already checked for this user ID
|
|
162
|
+
if (lastCheckedUserIdRef.current === user?.id && isSuperAdminUser !== null) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Skip if already checking
|
|
167
|
+
if (isCheckingRef.current) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const checkSuperAdminStatus = async () => {
|
|
172
|
+
if (!user?.id) {
|
|
173
|
+
setIsSuperAdminUser(false); // No user = not super admin
|
|
174
|
+
setIsCheckingSuperAdmin(false);
|
|
175
|
+
lastCheckedUserIdRef.current = null;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Mark as checking and track user ID
|
|
180
|
+
isCheckingRef.current = true;
|
|
181
|
+
lastCheckedUserIdRef.current = user.id;
|
|
182
|
+
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
setIsCheckingSuperAdmin(true);
|
|
185
|
+
|
|
186
|
+
// Add timeout to prevent infinite hanging
|
|
187
|
+
const timeoutId = setTimeout(() => {
|
|
188
|
+
logger.warn('useResourcePermissions', 'Super admin check taking longer than 5 seconds', {
|
|
189
|
+
userId: user?.id,
|
|
190
|
+
elapsedMs: Date.now() - startTime,
|
|
191
|
+
});
|
|
192
|
+
}, 5000);
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const superAdminStatus = await isSuperAdmin(user.id);
|
|
196
|
+
setIsSuperAdminUser(superAdminStatus);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const elapsed = Date.now() - startTime;
|
|
199
|
+
logger.error('useResourcePermissions', 'Error checking super admin status', {
|
|
200
|
+
userId: user?.id,
|
|
201
|
+
error,
|
|
202
|
+
elapsedMs: elapsed,
|
|
203
|
+
});
|
|
204
|
+
setIsSuperAdminUser(false); // Error = assume not super admin for security
|
|
205
|
+
} finally {
|
|
206
|
+
clearTimeout(timeoutId);
|
|
207
|
+
setIsCheckingSuperAdmin(false);
|
|
208
|
+
isCheckingRef.current = false;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
checkSuperAdminStatus();
|
|
213
|
+
}, [user?.id, logger]);
|
|
139
214
|
|
|
140
215
|
// Get selected organisation
|
|
141
216
|
const { selectedOrganisation } = useOrganisations();
|
|
142
217
|
|
|
143
218
|
// Get selected event (optional - wrap in try/catch)
|
|
144
|
-
let selectedEvent:
|
|
219
|
+
let selectedEvent: Event | null = null;
|
|
145
220
|
try {
|
|
146
221
|
const eventsContext = useEvents();
|
|
147
222
|
selectedEvent = eventsContext.selectedEvent;
|
|
@@ -154,52 +229,70 @@ export function useResourcePermissions(
|
|
|
154
229
|
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
155
230
|
supabase,
|
|
156
231
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
157
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
232
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
233
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
158
234
|
});
|
|
159
235
|
|
|
160
|
-
//
|
|
236
|
+
// CRITICAL FIX: Only use resolvedScope when it's available (not during loading)
|
|
237
|
+
// This ensures we wait for appId to be resolved before constructing permission strings
|
|
238
|
+
// If resolvedScope is null (still loading), we can't determine if we should use page permissions
|
|
239
|
+
// so we must wait for scope resolution to complete
|
|
161
240
|
const scope: Scope = resolvedScope || {
|
|
162
241
|
organisationId: selectedOrganisation?.id || '',
|
|
163
242
|
eventId: selectedEvent?.event_id || undefined,
|
|
164
243
|
appId: undefined
|
|
165
244
|
};
|
|
166
245
|
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// This
|
|
171
|
-
const
|
|
246
|
+
// CRITICAL FIX: Only use page permissions when appId is actually available in resolvedScope
|
|
247
|
+
// If scope is still loading (resolvedScope is null), we can't know if appId will be available
|
|
248
|
+
// so we must wait for scope resolution before constructing page permission strings
|
|
249
|
+
// This prevents using wrong permission format (delete:planning instead of delete:page.planning)
|
|
250
|
+
const hasAppId = !!resolvedScope?.appId;
|
|
251
|
+
const pageId = hasAppId ? resource : undefined;
|
|
252
|
+
|
|
253
|
+
// When appId is available in resolved scope, construct permission strings with page. prefix
|
|
254
|
+
// This matches the format that rbac_permissions_get returns (e.g., 'create:page.planning')
|
|
255
|
+
// and ensures consistent permission checking for page-based resources
|
|
256
|
+
// IMPORTANT: Only use page format when appId is actually resolved, not during loading
|
|
257
|
+
const isPagePermission = hasAppId && !!pageId;
|
|
258
|
+
const createPermission = isPagePermission ? `create:page.${resource}` : `create:${resource}`;
|
|
259
|
+
const updatePermission = isPagePermission ? `update:page.${resource}` : `update:${resource}`;
|
|
260
|
+
const deletePermission = isPagePermission ? `delete:page.${resource}` : `delete:${resource}`;
|
|
261
|
+
const readPermission = isPagePermission ? `read:page.${resource}` : `read:${resource}`;
|
|
172
262
|
|
|
173
263
|
// Permission checks for create, update, delete
|
|
174
|
-
// Pass
|
|
175
|
-
//
|
|
264
|
+
// PERFORMANCE OPTIMIZATION: Pass precomputed super admin status to avoid duplicate checks
|
|
265
|
+
// Each useCan hook would normally check super admin separately (4+ queries), but we check once here
|
|
266
|
+
// and share the result. Pass null if not checked yet (hooks will check), false/true if checked.
|
|
267
|
+
// CRITICAL: useCan will wait for appId when pageId is provided (it checks needsAppIdForPageName)
|
|
268
|
+
// But we must ensure permission strings are correct before calling useCan
|
|
176
269
|
const { can: canCreateResult, isLoading: createLoading, error: createError } = useCan(
|
|
177
270
|
user?.id || '',
|
|
178
271
|
scope,
|
|
179
|
-
|
|
272
|
+
createPermission as Permission,
|
|
180
273
|
pageId, // Pass resource name as pageId when appId is available to enable page permission checks
|
|
181
274
|
true, // useCache
|
|
182
|
-
|
|
275
|
+
isSuperAdminUser, // precomputedSuperAdmin - null if checking, false/true if checked
|
|
183
276
|
undefined // appName
|
|
184
277
|
);
|
|
185
278
|
|
|
186
279
|
const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(
|
|
187
280
|
user?.id || '',
|
|
188
281
|
scope,
|
|
189
|
-
|
|
282
|
+
updatePermission as Permission,
|
|
190
283
|
pageId, // Pass resource name as pageId when appId is available to enable page permission checks
|
|
191
284
|
true, // useCache
|
|
192
|
-
|
|
285
|
+
isSuperAdminUser, // precomputedSuperAdmin - null if checking, false/true if checked
|
|
193
286
|
undefined // appName
|
|
194
287
|
);
|
|
195
288
|
|
|
196
289
|
const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(
|
|
197
290
|
user?.id || '',
|
|
198
291
|
scope,
|
|
199
|
-
|
|
292
|
+
deletePermission as Permission,
|
|
200
293
|
pageId, // Pass resource name as pageId when appId is available to enable page permission checks
|
|
201
294
|
true, // useCache
|
|
202
|
-
|
|
295
|
+
isSuperAdminUser, // precomputedSuperAdmin - null if checking, false/true if checked
|
|
203
296
|
undefined // appName
|
|
204
297
|
);
|
|
205
298
|
|
|
@@ -207,17 +300,25 @@ export function useResourcePermissions(
|
|
|
207
300
|
const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(
|
|
208
301
|
user?.id || '',
|
|
209
302
|
scope,
|
|
210
|
-
|
|
303
|
+
readPermission as Permission,
|
|
211
304
|
pageId, // Pass resource name as pageId when appId is available to enable page permission checks
|
|
212
305
|
true, // useCache
|
|
213
|
-
|
|
306
|
+
isSuperAdminUser, // precomputedSuperAdmin - null if checking, false/true if checked
|
|
214
307
|
undefined // appName
|
|
215
308
|
);
|
|
216
309
|
|
|
217
310
|
// Aggregate loading states - any permission check or scope resolution loading
|
|
311
|
+
// CRITICAL: When requireScope is true, we must wait for scope resolution to complete
|
|
312
|
+
// so we can determine the correct permission format (page vs resource permissions)
|
|
313
|
+
// This prevents using wrong permission format (delete:planning instead of delete:page.planning)
|
|
314
|
+
// Also wait for super admin check to complete to ensure accurate permission results
|
|
218
315
|
const isLoading = useMemo(() => {
|
|
219
|
-
|
|
220
|
-
|
|
316
|
+
// If scope resolution is required, wait for it to complete
|
|
317
|
+
const waitingForScope = requireScope && scopeLoading;
|
|
318
|
+
// Wait for super admin check to complete (null means still checking)
|
|
319
|
+
const waitingForSuperAdmin = isSuperAdminUser === null && isCheckingSuperAdmin;
|
|
320
|
+
return waitingForScope || waitingForSuperAdmin || createLoading || updateLoading || deleteLoading || (enableRead && readLoading);
|
|
321
|
+
}, [scopeLoading, requireScope, isSuperAdminUser, isCheckingSuperAdmin, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);
|
|
221
322
|
|
|
222
323
|
// Aggregate errors - prefer scope error, then any permission error
|
|
223
324
|
const error = useMemo(() => {
|
|
@@ -232,41 +333,59 @@ export function useResourcePermissions(
|
|
|
232
333
|
// Return wrapper functions that take resource name and return permission result
|
|
233
334
|
// Note: The resource parameter in the function is for consistency with the API,
|
|
234
335
|
// but we're checking permissions for the resource passed to the hook
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
canUpdate: (res: string) => {
|
|
245
|
-
if (res !== resource) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
return canUpdateResult;
|
|
249
|
-
},
|
|
250
|
-
canDelete: (res: string) => {
|
|
251
|
-
if (res !== resource) {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
return canDeleteResult;
|
|
255
|
-
},
|
|
256
|
-
canRead: (res: string) => {
|
|
257
|
-
if (!enableRead) {
|
|
258
|
-
return true; // If read checking is disabled, allow read
|
|
336
|
+
// CRITICAL FIX: When isSuperAdminUser is true, immediately grant all permissions without waiting
|
|
337
|
+
// for useCan results. This ensures super admins can use resource operations immediately.
|
|
338
|
+
return useMemo(() => {
|
|
339
|
+
// Helper to create a permission result that bypasses for super admins
|
|
340
|
+
const createSuperAdminAwarePermission = (result: boolean) => {
|
|
341
|
+
// CRITICAL: If super admin is confirmed, immediately grant permission
|
|
342
|
+
// Don't wait for useCan results - super admins bypass all checks
|
|
343
|
+
if (isSuperAdminUser === true) {
|
|
344
|
+
return true;
|
|
259
345
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
346
|
+
|
|
347
|
+
// For non-super-admins or while checking, use normal permission results
|
|
348
|
+
return result;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
canCreate: (res: string) => {
|
|
353
|
+
// For now, we only check the resource passed to the hook
|
|
354
|
+
// Future enhancement could support checking different resources
|
|
355
|
+
if (res !== resource) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return createSuperAdminAwarePermission(canCreateResult);
|
|
359
|
+
},
|
|
360
|
+
canUpdate: (res: string) => {
|
|
361
|
+
if (res !== resource) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
return createSuperAdminAwarePermission(canUpdateResult);
|
|
365
|
+
},
|
|
366
|
+
canDelete: (res: string) => {
|
|
367
|
+
if (res !== resource) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return createSuperAdminAwarePermission(canDeleteResult);
|
|
371
|
+
},
|
|
372
|
+
canRead: (res: string) => {
|
|
373
|
+
if (!enableRead) {
|
|
374
|
+
return true; // If read checking is disabled, allow read
|
|
375
|
+
}
|
|
376
|
+
if (res !== resource) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
return createSuperAdminAwarePermission(canReadResult);
|
|
380
|
+
},
|
|
381
|
+
scope,
|
|
382
|
+
isLoading: isCheckingSuperAdmin || isLoading,
|
|
383
|
+
error
|
|
384
|
+
};
|
|
385
|
+
}, [
|
|
269
386
|
resource,
|
|
387
|
+
isSuperAdminUser,
|
|
388
|
+
isCheckingSuperAdmin,
|
|
270
389
|
canCreateResult,
|
|
271
390
|
canUpdateResult,
|
|
272
391
|
canDeleteResult,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Tests focus on behavior: role granting, revoking, loading states, and error handling.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { renderHook, waitFor } from '@testing-library/react';
|
|
11
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
12
12
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
13
13
|
import { useRoleManagement } from './useRoleManagement';
|
|
14
14
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
@@ -105,7 +105,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
105
105
|
describe('revokeEventAppRole', () => {
|
|
106
106
|
it('revokes role successfully', async () => {
|
|
107
107
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
108
|
-
data: true,
|
|
108
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
109
109
|
error: null,
|
|
110
110
|
});
|
|
111
111
|
|
|
@@ -117,19 +117,18 @@ describe('useRoleManagement Hook', () => {
|
|
|
117
117
|
expect(revokeResult.message).toBe('Role revoked successfully');
|
|
118
118
|
expect(revokeResult.error).toBeUndefined();
|
|
119
119
|
expect(result.current.error).toBeNull();
|
|
120
|
-
expect(mockSupabase.rpc).toHaveBeenCalledWith('
|
|
120
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_role_revoke', {
|
|
121
121
|
p_user_id: 'user-456',
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
p_role: 'viewer',
|
|
122
|
+
p_role_type: 'event_app',
|
|
123
|
+
p_role_name: 'viewer',
|
|
124
|
+
p_context_id: 'event-123:app-123',
|
|
126
125
|
p_revoked_by: 'user-123',
|
|
127
126
|
});
|
|
128
127
|
});
|
|
129
128
|
|
|
130
129
|
it('handles case when role not found', async () => {
|
|
131
130
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
132
|
-
data: false,
|
|
131
|
+
data: [{ success: false, message: 'No matching role found', revoked_count: 0, error_code: 'ROLE_NOT_FOUND' }],
|
|
133
132
|
error: null,
|
|
134
133
|
});
|
|
135
134
|
|
|
@@ -138,13 +137,55 @@ describe('useRoleManagement Hook', () => {
|
|
|
138
137
|
const revokeResult = await result.current.revokeEventAppRole(mockRoleParams);
|
|
139
138
|
|
|
140
139
|
expect(revokeResult.success).toBe(false);
|
|
141
|
-
expect(revokeResult.message).toBe('No role found
|
|
140
|
+
expect(revokeResult.message).toBe('No matching role found');
|
|
142
141
|
expect(revokeResult.error).toBe('No matching role found');
|
|
143
142
|
});
|
|
144
143
|
|
|
144
|
+
it('handles empty data response', async () => {
|
|
145
|
+
(mockSupabase.rpc as any).mockResolvedValue({
|
|
146
|
+
data: [],
|
|
147
|
+
error: null,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const { result } = renderHook(() => useRoleManagement());
|
|
151
|
+
|
|
152
|
+
const revokeResult = await result.current.revokeEventAppRole(mockRoleParams);
|
|
153
|
+
|
|
154
|
+
expect(revokeResult.success).toBe(false);
|
|
155
|
+
expect(revokeResult.error).toBe('No response from database - role revocation may have failed');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('handles null data response', async () => {
|
|
159
|
+
(mockSupabase.rpc as any).mockResolvedValue({
|
|
160
|
+
data: null,
|
|
161
|
+
error: null,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const { result } = renderHook(() => useRoleManagement());
|
|
165
|
+
|
|
166
|
+
const revokeResult = await result.current.revokeEventAppRole(mockRoleParams);
|
|
167
|
+
|
|
168
|
+
expect(revokeResult.success).toBe(false);
|
|
169
|
+
expect(revokeResult.error).toBe('No response from database - role revocation may have failed');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('handles result with success false but no message', async () => {
|
|
173
|
+
(mockSupabase.rpc as any).mockResolvedValue({
|
|
174
|
+
data: [{ success: false, message: null, revoked_count: 0, error_code: 'ROLE_NOT_FOUND' }],
|
|
175
|
+
error: null,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { result } = renderHook(() => useRoleManagement());
|
|
179
|
+
|
|
180
|
+
const revokeResult = await result.current.revokeEventAppRole(mockRoleParams);
|
|
181
|
+
|
|
182
|
+
expect(revokeResult.success).toBe(false);
|
|
183
|
+
expect(revokeResult.error).toBe('ROLE_NOT_FOUND');
|
|
184
|
+
});
|
|
185
|
+
|
|
145
186
|
it('uses provided revoked_by parameter', async () => {
|
|
146
187
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
147
|
-
data: true,
|
|
188
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
148
189
|
error: null,
|
|
149
190
|
});
|
|
150
191
|
|
|
@@ -156,7 +197,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
156
197
|
});
|
|
157
198
|
|
|
158
199
|
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
159
|
-
'
|
|
200
|
+
'rbac_role_revoke',
|
|
160
201
|
expect.objectContaining({
|
|
161
202
|
p_revoked_by: 'admin-789',
|
|
162
203
|
})
|
|
@@ -165,7 +206,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
165
206
|
|
|
166
207
|
it('uses user ID as revoked_by when not provided', async () => {
|
|
167
208
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
168
|
-
data: true,
|
|
209
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
169
210
|
error: null,
|
|
170
211
|
});
|
|
171
212
|
|
|
@@ -174,7 +215,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
174
215
|
await result.current.revokeEventAppRole(mockRoleParams);
|
|
175
216
|
|
|
176
217
|
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
177
|
-
'
|
|
218
|
+
'rbac_role_revoke',
|
|
178
219
|
expect.objectContaining({
|
|
179
220
|
p_revoked_by: 'user-123',
|
|
180
221
|
})
|
|
@@ -262,7 +303,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
262
303
|
);
|
|
263
304
|
|
|
264
305
|
resolvePromise!({
|
|
265
|
-
data: true,
|
|
306
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
266
307
|
error: null,
|
|
267
308
|
});
|
|
268
309
|
|
|
@@ -461,14 +502,16 @@ describe('useRoleManagement Hook', () => {
|
|
|
461
502
|
|
|
462
503
|
const { result } = renderHook(() => useRoleManagement());
|
|
463
504
|
|
|
505
|
+
// Call grantEventAppRole - loading state is set synchronously, but React state updates are async
|
|
464
506
|
const grantPromise = result.current.grantEventAppRole(mockRoleParams);
|
|
465
507
|
|
|
466
|
-
//
|
|
508
|
+
// Wait for React to process the state update (setIsLoading(true))
|
|
509
|
+
// Increased timeout to allow React to batch and process state updates
|
|
467
510
|
await waitFor(
|
|
468
511
|
() => {
|
|
469
512
|
expect(result.current.isLoading).toBe(true);
|
|
470
513
|
},
|
|
471
|
-
{ timeout:
|
|
514
|
+
{ timeout: 1000 }
|
|
472
515
|
);
|
|
473
516
|
|
|
474
517
|
resolvePromise!({
|
|
@@ -797,9 +840,9 @@ describe('useRoleManagement Hook', () => {
|
|
|
797
840
|
});
|
|
798
841
|
|
|
799
842
|
expect(mockSupabase.rpc).toHaveBeenCalledWith(
|
|
800
|
-
'
|
|
843
|
+
'rbac_role_revoke',
|
|
801
844
|
expect.objectContaining({
|
|
802
|
-
|
|
845
|
+
p_role_name: role,
|
|
803
846
|
})
|
|
804
847
|
);
|
|
805
848
|
}
|
|
@@ -825,7 +868,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
825
868
|
|
|
826
869
|
// Second call succeeds
|
|
827
870
|
(mockSupabase.rpc as any).mockResolvedValueOnce({
|
|
828
|
-
data: true,
|
|
871
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
829
872
|
error: null,
|
|
830
873
|
});
|
|
831
874
|
|
|
@@ -850,7 +893,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
850
893
|
|
|
851
894
|
// Second call succeeds
|
|
852
895
|
(mockSupabase.rpc as any).mockResolvedValueOnce({
|
|
853
|
-
data: true,
|
|
896
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
854
897
|
error: null,
|
|
855
898
|
});
|
|
856
899
|
|
|
@@ -864,7 +907,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
864
907
|
describe('Concurrent Operations', () => {
|
|
865
908
|
it('handles concurrent role operations', async () => {
|
|
866
909
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
867
|
-
data: true,
|
|
910
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
868
911
|
error: null,
|
|
869
912
|
});
|
|
870
913
|
|
|
@@ -885,7 +928,7 @@ describe('useRoleManagement Hook', () => {
|
|
|
885
928
|
|
|
886
929
|
it('handles rapid sequential operations', async () => {
|
|
887
930
|
(mockSupabase.rpc as any).mockResolvedValue({
|
|
888
|
-
data: true,
|
|
931
|
+
data: [{ success: true, message: 'Role revoked successfully', revoked_count: 1 }],
|
|
889
932
|
error: null,
|
|
890
933
|
});
|
|
891
934
|
|