@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
|
@@ -11,6 +11,7 @@ import { renderHook, waitFor } from '@testing-library/react';
|
|
|
11
11
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
12
|
import { useResourcePermissions } from './useResourcePermissions';
|
|
13
13
|
import type { Scope } from '../types';
|
|
14
|
+
import { isSuperAdmin } from '../api';
|
|
14
15
|
|
|
15
16
|
// Mock dependencies
|
|
16
17
|
vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
|
|
@@ -33,6 +34,12 @@ vi.mock('./usePermissions', () => ({
|
|
|
33
34
|
useCan: vi.fn(),
|
|
34
35
|
}));
|
|
35
36
|
|
|
37
|
+
vi.mock('../api', () => ({
|
|
38
|
+
isSuperAdmin: vi.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const mockIsSuperAdmin = vi.mocked(isSuperAdmin);
|
|
42
|
+
|
|
36
43
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
37
44
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
38
45
|
import { useEvents } from '../../hooks/useEvents';
|
|
@@ -89,11 +96,14 @@ describe('useResourcePermissions Hook', () => {
|
|
|
89
96
|
} as any);
|
|
90
97
|
|
|
91
98
|
mockUseResolvedScope.mockReturnValue({
|
|
92
|
-
resolvedScope: mockScope,
|
|
99
|
+
resolvedScope: mockScope, // Use correct property name from UseResolvedScopeReturn interface
|
|
93
100
|
isLoading: false,
|
|
94
101
|
error: null,
|
|
95
102
|
});
|
|
96
103
|
|
|
104
|
+
// Mock isSuperAdmin to resolve immediately (prevents isLoading from being true due to super admin check)
|
|
105
|
+
mockIsSuperAdmin.mockResolvedValue(false);
|
|
106
|
+
|
|
97
107
|
// Default useCan mocks - all permissions allowed
|
|
98
108
|
mockUseCan.mockReturnValue({
|
|
99
109
|
can: true,
|
|
@@ -117,9 +127,16 @@ describe('useResourcePermissions Hook', () => {
|
|
|
117
127
|
expect(result.current.canRead).toBeTypeOf('function');
|
|
118
128
|
});
|
|
119
129
|
|
|
120
|
-
it('returns scope object', () => {
|
|
130
|
+
it('returns scope object', async () => {
|
|
121
131
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
122
132
|
|
|
133
|
+
// Wait for super admin check to complete
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(result.current.isLoading).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// The hook returns resolvedScope if available, otherwise fallback scope
|
|
139
|
+
// Since mockScope has appId, it should be returned as-is
|
|
123
140
|
expect(result.current.scope).toEqual(mockScope);
|
|
124
141
|
});
|
|
125
142
|
|
|
@@ -130,27 +147,46 @@ describe('useResourcePermissions Hook', () => {
|
|
|
130
147
|
supabase: mockSupabase,
|
|
131
148
|
selectedOrganisationId: 'org-123',
|
|
132
149
|
selectedEventId: 'event-123',
|
|
150
|
+
selectedEventOrganisationId: null, // Mock event doesn't have organisation_id, so it becomes null
|
|
133
151
|
});
|
|
134
152
|
});
|
|
135
153
|
|
|
136
|
-
it('calls useCan for each permission type', () => {
|
|
154
|
+
it('calls useCan for each permission type', async () => {
|
|
137
155
|
renderHook(() => useResourcePermissions('contacts'));
|
|
138
156
|
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
// Wait for super admin check to complete
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// The hook calls useCan for each permission type (create, update, delete, read)
|
|
163
|
+
// Note: It may be called multiple times due to re-renders during super admin check,
|
|
164
|
+
// so we verify that all four permission types are checked rather than exact call count
|
|
165
|
+
const calls = mockUseCan.mock.calls;
|
|
166
|
+
expect(calls.length).toBeGreaterThanOrEqual(4);
|
|
167
|
+
|
|
168
|
+
// When appId is available in resolvedScope, permission strings include page. prefix
|
|
169
|
+
// and resource name is passed as pageId to enable page permission checks
|
|
141
170
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
142
171
|
'user-123',
|
|
143
|
-
mockScope,
|
|
144
|
-
'create:contacts',
|
|
145
|
-
'contacts', // pageId is resource name when appId is available
|
|
172
|
+
mockScope, // resolvedScope is used when available
|
|
173
|
+
'create:page.contacts',
|
|
174
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
146
175
|
true,
|
|
147
|
-
|
|
176
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
148
177
|
undefined // appName
|
|
149
178
|
);
|
|
179
|
+
|
|
180
|
+
// Verify all four permission types are checked
|
|
181
|
+
const permissions = calls.map(call => call[2]); // permission is the 3rd argument
|
|
182
|
+
expect(permissions).toContain('create:page.contacts');
|
|
183
|
+
expect(permissions).toContain('update:page.contacts');
|
|
184
|
+
expect(permissions).toContain('delete:page.contacts');
|
|
185
|
+
expect(permissions).toContain('read:page.contacts');
|
|
150
186
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
151
187
|
'user-123',
|
|
152
188
|
mockScope,
|
|
153
|
-
'update:contacts',
|
|
189
|
+
'update:page.contacts',
|
|
154
190
|
'contacts', // pageId is resource name when appId is available
|
|
155
191
|
true,
|
|
156
192
|
null, // precomputedSuperAdmin
|
|
@@ -159,7 +195,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
159
195
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
160
196
|
'user-123',
|
|
161
197
|
mockScope,
|
|
162
|
-
'delete:contacts',
|
|
198
|
+
'delete:page.contacts',
|
|
163
199
|
'contacts', // pageId is resource name when appId is available
|
|
164
200
|
true,
|
|
165
201
|
null, // precomputedSuperAdmin
|
|
@@ -168,7 +204,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
168
204
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
169
205
|
'user-123',
|
|
170
206
|
mockScope,
|
|
171
|
-
'read:contacts',
|
|
207
|
+
'read:page.contacts',
|
|
172
208
|
'contacts', // pageId is resource name when appId is available
|
|
173
209
|
true,
|
|
174
210
|
null, // precomputedSuperAdmin
|
|
@@ -191,9 +227,10 @@ describe('useResourcePermissions Hook', () => {
|
|
|
191
227
|
expect(result.current.canCreate('contacts')).toBe(true);
|
|
192
228
|
});
|
|
193
229
|
|
|
194
|
-
it('returns false when user cannot create resource', () => {
|
|
230
|
+
it('returns false when user cannot create resource', async () => {
|
|
195
231
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
196
|
-
|
|
232
|
+
// When appId is available in resolvedScope, permission format is 'create:page.contacts'
|
|
233
|
+
if (permission === 'create:page.contacts' || permission === 'create:contacts') {
|
|
197
234
|
return {
|
|
198
235
|
can: false,
|
|
199
236
|
isLoading: false,
|
|
@@ -211,6 +248,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
211
248
|
|
|
212
249
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
213
250
|
|
|
251
|
+
// Wait for super admin check to complete
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
expect(result.current.isLoading).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
214
256
|
expect(result.current.canCreate('contacts')).toBe(false);
|
|
215
257
|
expect(result.current.canUpdate('contacts')).toBe(true);
|
|
216
258
|
expect(result.current.canDelete('contacts')).toBe(true);
|
|
@@ -232,9 +274,10 @@ describe('useResourcePermissions Hook', () => {
|
|
|
232
274
|
expect(result.current.canRead('contacts')).toBe(true);
|
|
233
275
|
});
|
|
234
276
|
|
|
235
|
-
it('checks read permissions when enableRead is true', () => {
|
|
277
|
+
it('checks read permissions when enableRead is true', async () => {
|
|
236
278
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
237
|
-
|
|
279
|
+
// When appId is available in resolvedScope, permission format is 'read:page.contacts'
|
|
280
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
238
281
|
return {
|
|
239
282
|
can: true,
|
|
240
283
|
isLoading: false,
|
|
@@ -254,12 +297,18 @@ describe('useResourcePermissions Hook', () => {
|
|
|
254
297
|
useResourcePermissions('contacts', { enableRead: true })
|
|
255
298
|
);
|
|
256
299
|
|
|
300
|
+
// Wait for super admin check to complete
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(result.current.isLoading).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
257
305
|
expect(result.current.canRead('contacts')).toBe(true);
|
|
258
306
|
});
|
|
259
307
|
|
|
260
|
-
it('returns false for read when permission is denied and enableRead is true', () => {
|
|
308
|
+
it('returns false for read when permission is denied and enableRead is true', async () => {
|
|
261
309
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
262
|
-
|
|
310
|
+
// When appId is available in resolvedScope, permission format is 'read:page.contacts'
|
|
311
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
263
312
|
return {
|
|
264
313
|
can: false,
|
|
265
314
|
isLoading: false,
|
|
@@ -279,6 +328,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
279
328
|
useResourcePermissions('contacts', { enableRead: true })
|
|
280
329
|
);
|
|
281
330
|
|
|
331
|
+
// Wait for super admin check to complete
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(result.current.isLoading).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
282
336
|
expect(result.current.canRead('contacts')).toBe(false);
|
|
283
337
|
});
|
|
284
338
|
});
|
|
@@ -398,7 +452,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
398
452
|
});
|
|
399
453
|
|
|
400
454
|
describe('Missing User Context', () => {
|
|
401
|
-
it('handles missing user gracefully', () => {
|
|
455
|
+
it('handles missing user gracefully', async () => {
|
|
402
456
|
mockUseUnifiedAuth.mockReturnValue({
|
|
403
457
|
user: null,
|
|
404
458
|
supabase: mockSupabase,
|
|
@@ -406,21 +460,26 @@ describe('useResourcePermissions Hook', () => {
|
|
|
406
460
|
|
|
407
461
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
408
462
|
|
|
463
|
+
// Wait for super admin check to complete (will be false when user is null)
|
|
464
|
+
await waitFor(() => {
|
|
465
|
+
expect(result.current.isLoading).toBe(false);
|
|
466
|
+
});
|
|
467
|
+
|
|
409
468
|
// When user is null, userId is empty string, but scope and permissions are still checked
|
|
410
|
-
// Since mockScope has appId,
|
|
469
|
+
// Since resolvedScope (mockScope) has appId, permission strings include page. prefix
|
|
411
470
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
412
471
|
'',
|
|
413
|
-
mockScope,
|
|
414
|
-
'create:contacts',
|
|
415
|
-
'contacts', // pageId is resource name when appId is available in
|
|
472
|
+
mockScope, // resolvedScope is used when available
|
|
473
|
+
'create:page.contacts',
|
|
474
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
416
475
|
true,
|
|
417
|
-
|
|
476
|
+
false, // precomputedSuperAdmin (false when user is null)
|
|
418
477
|
undefined // appName
|
|
419
478
|
);
|
|
420
479
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
421
480
|
'',
|
|
422
481
|
mockScope,
|
|
423
|
-
'update:contacts',
|
|
482
|
+
'update:page.contacts',
|
|
424
483
|
'contacts',
|
|
425
484
|
true,
|
|
426
485
|
null, // precomputedSuperAdmin
|
|
@@ -429,7 +488,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
429
488
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
430
489
|
'',
|
|
431
490
|
mockScope,
|
|
432
|
-
'delete:contacts',
|
|
491
|
+
'delete:page.contacts',
|
|
433
492
|
'contacts',
|
|
434
493
|
true,
|
|
435
494
|
null, // precomputedSuperAdmin
|
|
@@ -438,7 +497,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
438
497
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
439
498
|
'',
|
|
440
499
|
mockScope,
|
|
441
|
-
'read:contacts',
|
|
500
|
+
'read:page.contacts',
|
|
442
501
|
'contacts',
|
|
443
502
|
true,
|
|
444
503
|
null, // precomputedSuperAdmin
|
|
@@ -453,11 +512,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
453
512
|
throw new Error('Event provider not available');
|
|
454
513
|
});
|
|
455
514
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
515
|
+
mockUseResolvedScope.mockReturnValue({
|
|
516
|
+
resolvedScope: mockScope,
|
|
517
|
+
isLoading: false, // Scope is resolved
|
|
518
|
+
error: null,
|
|
519
|
+
});
|
|
461
520
|
|
|
462
521
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
463
522
|
|
|
@@ -481,7 +540,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
481
540
|
|
|
482
541
|
it('aggregates loading states from permission checks', () => {
|
|
483
542
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
484
|
-
if (permission === 'create:contacts') {
|
|
543
|
+
if (permission === 'create:page.contacts') {
|
|
485
544
|
return {
|
|
486
545
|
can: false,
|
|
487
546
|
isLoading: true,
|
|
@@ -504,7 +563,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
504
563
|
|
|
505
564
|
it('includes read loading state when enableRead is true', () => {
|
|
506
565
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
507
|
-
if (permission === 'read:contacts') {
|
|
566
|
+
if (permission === 'read:page.contacts') {
|
|
508
567
|
return {
|
|
509
568
|
can: false,
|
|
510
569
|
isLoading: true,
|
|
@@ -527,12 +586,20 @@ describe('useResourcePermissions Hook', () => {
|
|
|
527
586
|
expect(result.current.isLoading).toBe(true);
|
|
528
587
|
});
|
|
529
588
|
|
|
530
|
-
it('excludes read loading state when enableRead is false', () => {
|
|
589
|
+
it('excludes read loading state when enableRead is false', async () => {
|
|
590
|
+
// Ensure scope is resolved (not loading) - this prevents scopeLoading from contributing to isLoading
|
|
591
|
+
mockUseResolvedScope.mockReturnValue({
|
|
592
|
+
resolvedScope: mockScope, // Use correct property name
|
|
593
|
+
isLoading: false,
|
|
594
|
+
error: null,
|
|
595
|
+
});
|
|
596
|
+
|
|
531
597
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
532
|
-
|
|
598
|
+
// Read permission check should not contribute to isLoading when enableRead is false
|
|
599
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
533
600
|
return {
|
|
534
601
|
can: false,
|
|
535
|
-
isLoading: true,
|
|
602
|
+
isLoading: true, // Read is loading, but should be excluded
|
|
536
603
|
error: null,
|
|
537
604
|
refetch: vi.fn(),
|
|
538
605
|
} as any;
|
|
@@ -545,9 +612,12 @@ describe('useResourcePermissions Hook', () => {
|
|
|
545
612
|
} as any;
|
|
546
613
|
});
|
|
547
614
|
|
|
548
|
-
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
615
|
+
const { result } = renderHook(() => useResourcePermissions('contacts', { enableRead: false }));
|
|
549
616
|
|
|
550
|
-
|
|
617
|
+
// Wait for super admin check to complete
|
|
618
|
+
await waitFor(() => {
|
|
619
|
+
expect(result.current.isLoading).toBe(false);
|
|
620
|
+
});
|
|
551
621
|
});
|
|
552
622
|
});
|
|
553
623
|
|
|
@@ -565,10 +635,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
565
635
|
expect(result.current.error).toEqual(scopeError);
|
|
566
636
|
});
|
|
567
637
|
|
|
568
|
-
it('aggregates errors from permission checks', () => {
|
|
638
|
+
it('aggregates errors from permission checks', async () => {
|
|
569
639
|
const permissionError = new Error('Permission check failed');
|
|
570
640
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
571
|
-
|
|
641
|
+
// When appId is available in resolvedScope, permission format is 'create:page.contacts'
|
|
642
|
+
if (permission === 'create:page.contacts' || permission === 'create:contacts') {
|
|
572
643
|
return {
|
|
573
644
|
can: false,
|
|
574
645
|
isLoading: false,
|
|
@@ -586,6 +657,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
586
657
|
|
|
587
658
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
588
659
|
|
|
660
|
+
// Wait for super admin check to complete
|
|
661
|
+
await waitFor(() => {
|
|
662
|
+
expect(result.current.isLoading).toBe(false);
|
|
663
|
+
});
|
|
664
|
+
|
|
589
665
|
expect(result.current.error).toEqual(permissionError);
|
|
590
666
|
});
|
|
591
667
|
|
|
@@ -600,7 +676,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
600
676
|
});
|
|
601
677
|
|
|
602
678
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
603
|
-
if (permission === 'create:contacts') {
|
|
679
|
+
if (permission === 'create:page.contacts') {
|
|
604
680
|
return {
|
|
605
681
|
can: false,
|
|
606
682
|
isLoading: false,
|
|
@@ -629,17 +705,22 @@ describe('useResourcePermissions Hook', () => {
|
|
|
629
705
|
});
|
|
630
706
|
|
|
631
707
|
describe('Options', () => {
|
|
632
|
-
it('respects enableRead option', () => {
|
|
708
|
+
it('respects enableRead option', async () => {
|
|
633
709
|
renderHook(() => useResourcePermissions('contacts', { enableRead: true }));
|
|
634
710
|
|
|
635
|
-
//
|
|
711
|
+
// Wait for super admin check to complete
|
|
712
|
+
await waitFor(() => {
|
|
713
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Should call useCan for read permission with page. prefix when appId is available in resolvedScope
|
|
636
717
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
637
718
|
expect.any(String),
|
|
638
719
|
expect.any(Object),
|
|
639
|
-
'read:contacts',
|
|
640
|
-
'contacts', // pageId is resource name when appId is available
|
|
720
|
+
'read:page.contacts',
|
|
721
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
641
722
|
true,
|
|
642
|
-
|
|
723
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
643
724
|
undefined // appName
|
|
644
725
|
);
|
|
645
726
|
});
|
|
@@ -653,22 +734,28 @@ describe('useResourcePermissions Hook', () => {
|
|
|
653
734
|
});
|
|
654
735
|
|
|
655
736
|
describe('Different Resources', () => {
|
|
656
|
-
it('works with different resource names', () => {
|
|
737
|
+
it('works with different resource names', async () => {
|
|
657
738
|
renderHook(() => useResourcePermissions('risks'));
|
|
658
739
|
|
|
740
|
+
// Wait for super admin check to complete
|
|
741
|
+
await waitFor(() => {
|
|
742
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// When appId is available in resolvedScope, permission format includes page. prefix
|
|
659
746
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
660
747
|
expect.any(String),
|
|
661
748
|
expect.any(Object),
|
|
662
|
-
'create:risks',
|
|
663
|
-
'risks', // pageId is resource name when appId is available
|
|
749
|
+
'create:page.risks',
|
|
750
|
+
'risks', // pageId is resource name when appId is available in resolvedScope
|
|
664
751
|
true,
|
|
665
|
-
|
|
752
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
666
753
|
undefined // appName
|
|
667
754
|
);
|
|
668
755
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
669
756
|
expect.any(String),
|
|
670
757
|
expect.any(Object),
|
|
671
|
-
'update:risks',
|
|
758
|
+
'update:page.risks',
|
|
672
759
|
'risks', // pageId is resource name when appId is available
|
|
673
760
|
true,
|
|
674
761
|
null, // precomputedSuperAdmin
|
|
@@ -677,7 +764,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
677
764
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
678
765
|
expect.any(String),
|
|
679
766
|
expect.any(Object),
|
|
680
|
-
'delete:risks',
|
|
767
|
+
'delete:page.risks',
|
|
681
768
|
'risks', // pageId is resource name when appId is available
|
|
682
769
|
true,
|
|
683
770
|
null, // precomputedSuperAdmin
|
|
@@ -687,7 +774,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
687
774
|
});
|
|
688
775
|
|
|
689
776
|
describe('Page Permission Support', () => {
|
|
690
|
-
it('
|
|
777
|
+
it('constructs permission strings with page. prefix when appId is available', () => {
|
|
691
778
|
const scopeWithAppId: Scope = {
|
|
692
779
|
organisationId: 'org-123',
|
|
693
780
|
eventId: 'event-123',
|
|
@@ -702,20 +789,38 @@ describe('useResourcePermissions Hook', () => {
|
|
|
702
789
|
|
|
703
790
|
renderHook(() => useResourcePermissions('planning'));
|
|
704
791
|
|
|
705
|
-
// When appId is available,
|
|
706
|
-
//
|
|
792
|
+
// When appId is available, permission strings should include page. prefix
|
|
793
|
+
// and resource name should be passed as pageId to enable page permission checks
|
|
707
794
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
708
795
|
'user-123',
|
|
709
796
|
scopeWithAppId,
|
|
710
|
-
'create:planning',
|
|
797
|
+
'create:page.planning',
|
|
711
798
|
'planning', // Resource name passed as pageId to enable page permission checks
|
|
712
799
|
true,
|
|
713
800
|
null, // precomputedSuperAdmin
|
|
714
801
|
undefined // appName
|
|
715
802
|
);
|
|
803
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
804
|
+
'user-123',
|
|
805
|
+
scopeWithAppId,
|
|
806
|
+
'update:page.planning',
|
|
807
|
+
'planning',
|
|
808
|
+
true,
|
|
809
|
+
null,
|
|
810
|
+
undefined
|
|
811
|
+
);
|
|
812
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
813
|
+
'user-123',
|
|
814
|
+
scopeWithAppId,
|
|
815
|
+
'delete:page.planning',
|
|
816
|
+
'planning',
|
|
817
|
+
true,
|
|
818
|
+
null,
|
|
819
|
+
undefined
|
|
820
|
+
);
|
|
716
821
|
});
|
|
717
822
|
|
|
718
|
-
it('does not
|
|
823
|
+
it('does not add page. prefix when appId is not available', () => {
|
|
719
824
|
const scopeWithoutAppId: Scope = {
|
|
720
825
|
organisationId: 'org-123',
|
|
721
826
|
eventId: 'event-123',
|
|
@@ -730,8 +835,8 @@ describe('useResourcePermissions Hook', () => {
|
|
|
730
835
|
|
|
731
836
|
renderHook(() => useResourcePermissions('planning'));
|
|
732
837
|
|
|
733
|
-
// When appId is not available,
|
|
734
|
-
//
|
|
838
|
+
// When appId is not available, permission strings should NOT include page. prefix
|
|
839
|
+
// and pageId should be undefined - falls back to resource-based permission checking
|
|
735
840
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
736
841
|
'user-123',
|
|
737
842
|
scopeWithoutAppId,
|
|
@@ -741,6 +846,82 @@ describe('useResourcePermissions Hook', () => {
|
|
|
741
846
|
null, // precomputedSuperAdmin
|
|
742
847
|
undefined // appName
|
|
743
848
|
);
|
|
849
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
850
|
+
'user-123',
|
|
851
|
+
scopeWithoutAppId,
|
|
852
|
+
'update:planning',
|
|
853
|
+
undefined,
|
|
854
|
+
true,
|
|
855
|
+
null,
|
|
856
|
+
undefined
|
|
857
|
+
);
|
|
858
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
859
|
+
'user-123',
|
|
860
|
+
scopeWithoutAppId,
|
|
861
|
+
'delete:planning',
|
|
862
|
+
undefined,
|
|
863
|
+
true,
|
|
864
|
+
null,
|
|
865
|
+
undefined
|
|
866
|
+
);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it('waits for scope resolution before using page permissions (timing fix)', () => {
|
|
870
|
+
// Simulate scope resolution in progress (resolvedScope is null, isLoading is true)
|
|
871
|
+
mockUseResolvedScope.mockReturnValue({
|
|
872
|
+
resolvedScope: null,
|
|
873
|
+
isLoading: true,
|
|
874
|
+
error: null,
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
renderHook(() => useResourcePermissions('planning'));
|
|
878
|
+
|
|
879
|
+
// During scope loading, should use resource-based permissions (no page. prefix)
|
|
880
|
+
// because resolvedScope is null, so hasAppId is false
|
|
881
|
+
const fallbackScope: Scope = {
|
|
882
|
+
organisationId: 'org-123',
|
|
883
|
+
eventId: 'event-123',
|
|
884
|
+
appId: undefined,
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
888
|
+
'user-123',
|
|
889
|
+
fallbackScope,
|
|
890
|
+
'create:planning', // Resource-based permission (no page. prefix) during loading
|
|
891
|
+
undefined, // No pageId when appId is not available
|
|
892
|
+
true,
|
|
893
|
+
null,
|
|
894
|
+
undefined
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
// Now simulate scope resolution completing with appId
|
|
898
|
+
const scopeWithAppId: Scope = {
|
|
899
|
+
organisationId: 'org-123',
|
|
900
|
+
eventId: 'event-123',
|
|
901
|
+
appId: 'app-123',
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
mockUseResolvedScope.mockReturnValue({
|
|
905
|
+
resolvedScope: scopeWithAppId,
|
|
906
|
+
isLoading: false,
|
|
907
|
+
error: null,
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// Re-render to trigger update
|
|
911
|
+
const { rerender } = renderHook(() => useResourcePermissions('planning'));
|
|
912
|
+
rerender();
|
|
913
|
+
|
|
914
|
+
// After scope resolution, should use page permissions (with page. prefix)
|
|
915
|
+
// because resolvedScope.appId is now available, so hasAppId is true
|
|
916
|
+
expect(mockUseCan).toHaveBeenCalledWith(
|
|
917
|
+
'user-123',
|
|
918
|
+
scopeWithAppId,
|
|
919
|
+
'create:page.planning', // Page-based permission (with page. prefix) after resolution
|
|
920
|
+
'planning', // pageId is resource name when appId is available
|
|
921
|
+
true,
|
|
922
|
+
null,
|
|
923
|
+
undefined
|
|
924
|
+
);
|
|
744
925
|
});
|
|
745
926
|
});
|
|
746
927
|
});
|