@jmruthers/pace-core 0.5.135 → 0.5.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-A36PJG6N.js → DataTable-6M4L6BI2.js} +26 -13
- package/dist/{DataTable-C7GaRZye.d.ts → DataTable-CWAZZcXC.d.ts} +1 -1
- package/dist/{PublicLoadingSpinner-CUAnTvcg.d.ts → EventLogo-rFL_kRjk.d.ts} +123 -135
- package/dist/{UnifiedAuthProvider-BVKmQd9u.d.ts → UnifiedAuthProvider-DJxGTftH.d.ts} +1 -1
- package/dist/{UnifiedAuthProvider-CQDZRJIS.js → UnifiedAuthProvider-XIQQ7LVU.js} +5 -5
- package/dist/{api-TNIBJWLM.js → api-45XYYO2A.js} +4 -3
- package/dist/{audit-T36HM7IM.js → audit-64X3VJXB.js} +3 -2
- package/dist/{chunk-F64FFPOZ.js → chunk-22WKWKRX.js} +26 -20
- package/dist/chunk-22WKWKRX.js.map +1 -0
- package/dist/{chunk-VZ5OR6HD.js → chunk-4C7EXCAR.js} +62 -150
- package/dist/chunk-4C7EXCAR.js.map +1 -0
- package/dist/{chunk-PYUXFQJ3.js → chunk-56XJ3TU6.js} +2 -2
- package/dist/chunk-56XJ3TU6.js.map +1 -0
- package/dist/{chunk-CTJRBUX2.js → chunk-6LAAY47Q.js} +2 -2
- package/dist/{chunk-UJI6WSMD.js → chunk-7QCC6MCP.js} +90 -3
- package/dist/chunk-7QCC6MCP.js.map +1 -0
- package/dist/{chunk-66C4BSAY.js → chunk-ANBQRTPX.js} +9 -2
- package/dist/chunk-ANBQRTPX.js.map +1 -0
- package/dist/{chunk-CQZU6TFE.js → chunk-BCIBECNB.js} +100 -62
- package/dist/chunk-BCIBECNB.js.map +1 -0
- package/dist/{chunk-GKHF54DI.js → chunk-BESYRHQM.js} +10 -4
- package/dist/chunk-BESYRHQM.js.map +1 -0
- package/dist/chunk-BJPBT3CU.js +21 -0
- package/dist/chunk-BJPBT3CU.js.map +1 -0
- package/dist/{chunk-BYXRHAIF.js → chunk-BLCXZEYF.js} +23 -14
- package/dist/chunk-BLCXZEYF.js.map +1 -0
- package/dist/{chunk-WP5I5GLN.js → chunk-BVYWGZVV.js} +112 -97
- package/dist/chunk-BVYWGZVV.js.map +1 -0
- package/dist/{chunk-GEVIB2UB.js → chunk-ERISIBYU.js} +14 -5
- package/dist/chunk-ERISIBYU.js.map +1 -0
- package/dist/{chunk-O3NWNXDY.js → chunk-FMUCXFII.js} +2 -2
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-GVDR7WNV.js → chunk-HAWZXGR2.js} +334 -614
- package/dist/chunk-HAWZXGR2.js.map +1 -0
- package/dist/{chunk-ZV77RZMU.js → chunk-INQLMHPF.js} +2 -2
- package/dist/chunk-JISYG63F.js +70 -0
- package/dist/chunk-JISYG63F.js.map +1 -0
- package/dist/{chunk-HMNOSGVA.js → chunk-KYRHUBIU.js} +576 -767
- package/dist/chunk-KYRHUBIU.js.map +1 -0
- package/dist/{chunk-M6DDYFUD.js → chunk-LS353YLY.js} +19 -16
- package/dist/chunk-LS353YLY.js.map +1 -0
- package/dist/{chunk-TGIY2AR2.js → chunk-MA6EPSGZ.js} +4 -3
- package/dist/{chunk-TGIY2AR2.js.map → chunk-MA6EPSGZ.js.map} +1 -1
- package/dist/chunk-OWAG3GSU.js +58 -0
- package/dist/chunk-OWAG3GSU.js.map +1 -0
- package/dist/{chunk-JCQZ6LA7.js → chunk-Q5QRDWKI.js} +9 -3
- package/dist/chunk-Q5QRDWKI.js.map +1 -0
- package/dist/chunk-S5OFRT4M.js +94 -0
- package/dist/chunk-S5OFRT4M.js.map +1 -0
- package/dist/{chunk-3DBFLLLU.js → chunk-SBVILCCA.js} +14 -9
- package/dist/chunk-SBVILCCA.js.map +1 -0
- package/dist/{chunk-ZYZCRSBD.js → chunk-T6JN6LH6.js} +16 -11
- package/dist/chunk-T6JN6LH6.js.map +1 -0
- package/dist/chunk-XDNLUEXI.js +138 -0
- package/dist/chunk-XDNLUEXI.js.map +1 -0
- package/dist/{chunk-3CG5L6RN.js → chunk-YCWDTTUK.js} +90 -75
- package/dist/chunk-YCWDTTUK.js.map +1 -0
- package/dist/{chunk-5F3NDPJV.js → chunk-ZZ2SS7NI.js} +10 -5
- package/dist/chunk-ZZ2SS7NI.js.map +1 -0
- package/dist/components.d.ts +7 -287
- package/dist/components.js +27 -157
- package/dist/components.js.map +1 -1
- package/dist/{file-reference-C9isKNPn.d.ts → file-reference-C6Gkn77H.d.ts} +1 -1
- package/dist/{formatting-DFcCxUEk.d.ts → formatting-CvUXy2mF.d.ts} +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +21 -16
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +101 -9
- package/dist/index.js +44 -31
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/rbac/index.js +12 -12
- package/dist/schema-DTDZQe2u.d.ts +28 -0
- package/dist/styles/index.js +2 -1
- package/dist/theming/runtime.d.ts +2 -19
- package/dist/theming/runtime.js +2 -1
- package/dist/{types-D5rqZQXk.d.ts → types-Dfz9dmVH.d.ts} +12 -1
- package/dist/types.d.ts +153 -4
- package/dist/types.js +51 -16
- package/dist/types.js.map +1 -1
- package/dist/{useInactivityTracker-MRUU55XI.js → useInactivityTracker-TO6ZOF35.js} +3 -2
- package/dist/{usePublicRouteParams-Dyt1tzI9.d.ts → usePublicRouteParams-B7PabvuH.d.ts} +1 -1
- package/dist/utils.d.ts +221 -173
- package/dist/utils.js +185 -225
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +24 -115
- package/dist/validation.js +19 -474
- package/dist/validation.js.map +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +6 -6
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +6 -6
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +7 -7
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/BadgeProps.md +27 -0
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +29 -4
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +18 -18
- package/docs/api/interfaces/DataTableColumn.md +61 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +14 -14
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +152 -0
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +15 -15
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +10 -10
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +4 -4
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +4 -4
- package/docs/api/interfaces/PermissionEnforcerProps.md +11 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +21 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +9 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +5 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +9 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +591 -220
- package/docs/api-reference/components.md +106 -26
- package/docs/architecture/README.md +0 -3
- package/docs/implementation-guides/data-tables.md +277 -13
- package/docs/implementation-guides/forms.md +1 -16
- package/docs/implementation-guides/permission-enforcement.md +8 -2
- package/docs/styles/README.md +0 -2
- package/examples/README.md +30 -14
- package/examples/STRUCTURE.md +125 -0
- package/examples/components 2/DataTable/HierarchicalActionsExample.tsx +421 -0
- package/examples/components 2/DataTable/HierarchicalExample.tsx +475 -0
- package/examples/components 2/DataTable/InitialPageSizeExample.tsx +177 -0
- package/examples/components 2/DataTable/PerformanceExample.tsx +506 -0
- package/examples/components 2/DataTable/index.ts +13 -0
- package/examples/components 2/Dialog/BasicHtmlTest.tsx +55 -0
- package/examples/components 2/Dialog/DebugHtmlExample.tsx +68 -0
- package/examples/components 2/Dialog/HtmlDialogExample.tsx +202 -0
- package/examples/components 2/Dialog/ScrollableDialogExample.tsx +290 -0
- package/examples/components 2/Dialog/SimpleHtmlTest.tsx +61 -0
- package/examples/components 2/Dialog/SmartDialogExample.tsx +322 -0
- package/examples/components 2/Dialog/index.ts +15 -0
- package/examples/components 2/index.ts +11 -0
- package/examples/features/index.ts +12 -0
- package/{src/examples → examples/features/public-pages}/CorrectPublicPageImplementation.tsx +14 -17
- package/{src/examples → examples/features/public-pages}/PublicEventPage.tsx +14 -27
- package/{src/examples → examples/features/public-pages}/PublicPageApp.tsx +15 -28
- package/{src/examples → examples/features/public-pages}/PublicPageUsageExample.tsx +8 -10
- package/examples/features/public-pages/index.ts +14 -0
- package/examples/features/rbac/CompleteRBACExample.tsx +324 -0
- package/examples/features/rbac/EventBasedApp.tsx +239 -0
- package/examples/features/rbac/PermissionExample.tsx +151 -0
- package/examples/features/rbac/index.ts +13 -0
- package/examples/index.ts +11 -3
- package/package.json +30 -19
- package/src/__tests__/TEST_STANDARD.md +92 -0
- package/src/components/Alert/Alert.tsx +1 -1
- package/src/components/Avatar/Avatar.tsx +1 -1
- package/src/components/Badge/Badge.test.tsx +314 -0
- package/src/components/Badge/Badge.tsx +304 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/Checkbox/Checkbox.tsx +1 -1
- package/src/components/DataTable/DataTable.test.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +1 -30
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +562 -0
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
- package/src/components/DataTable/__tests__/styles.test.ts +3 -3
- package/src/components/DataTable/components/ActionButtons.tsx +0 -15
- package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
- package/src/components/DataTable/components/DataTableBody.tsx +461 -0
- package/src/components/DataTable/components/DataTableCore.tsx +4 -185
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +1 -1
- package/src/components/DataTable/components/DataTableModals.tsx +1 -27
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
- package/src/components/DataTable/components/EditableRow.tsx +1 -1
- package/src/components/DataTable/components/FilterRow.tsx +9 -3
- package/src/components/DataTable/components/ImportModal.tsx +2 -14
- package/src/components/DataTable/components/PaginationControls.tsx +2 -1
- package/src/components/DataTable/components/UnifiedTableBody.tsx +109 -82
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
- package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +1 -1
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +1 -1
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +1 -1
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +1 -1
- package/src/components/DataTable/core/ActionManager.ts +235 -0
- package/src/components/DataTable/core/ColumnManager.ts +205 -0
- package/src/components/DataTable/core/DataManager.ts +188 -0
- package/src/components/DataTable/core/DataTableContext.tsx +181 -0
- package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
- package/src/components/DataTable/core/PluginRegistry.ts +229 -0
- package/src/components/DataTable/core/StateManager.ts +311 -0
- package/src/components/DataTable/core/interfaces.ts +338 -0
- package/src/components/DataTable/examples/GroupingAggregationExample.tsx +273 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +1 -1
- package/src/components/DataTable/examples/__tests__/HierarchicalActionsExample.test.tsx +1 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +1 -1
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +1 -1
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +2 -23
- package/src/components/DataTable/index.ts +4 -0
- package/src/components/DataTable/styles.ts +28 -7
- package/src/components/DataTable/types.ts +13 -0
- package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +1 -1
- package/src/components/DataTable/utils/aggregationUtils.ts +161 -0
- package/src/components/DataTable/utils/columnUtils.ts +40 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +1 -1
- package/src/components/DataTable/utils/flexibleImport.ts +1 -11
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +1 -1
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +8 -1
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +35 -7
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +5 -4
- package/src/components/EventSelector/EventSelector.tsx +3 -2
- package/src/components/FileDisplay/FileDisplay.tsx +2 -36
- package/src/components/FileUpload/FileUpload.test.tsx +2 -2
- package/src/components/FileUpload/FileUpload.tsx +2 -2
- package/src/components/Footer/Footer.test.tsx +1 -1
- package/src/components/Footer/Footer.tsx +1 -1
- package/src/components/Form/Form.test.tsx +5 -510
- package/src/components/Form/Form.tsx +1 -1
- package/src/components/Form/FormField.tsx +1 -1
- package/src/components/Form/index.ts +0 -12
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/Input/Input.tsx +1 -1
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +1 -1
- package/src/components/LoginForm/LoginForm.tsx +1 -1
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -3
- package/src/components/NavigationMenu/NavigationMenu.tsx +9 -8
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +4 -3
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +14 -12
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +0 -16
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +0 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +0 -9
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +35 -3
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +14 -13
- package/src/components/PasswordReset/PasswordChangeForm.tsx +1 -1
- package/src/components/PasswordReset/index.ts +0 -2
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +35 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -2
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +1 -1
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +1 -1
- package/src/components/PublicLayout/PublicPageContextChecker.tsx +44 -43
- package/src/components/PublicLayout/PublicPageFooter.tsx +1 -1
- package/src/components/PublicLayout/PublicPageHeader.tsx +1 -15
- package/src/components/PublicLayout/PublicPageProvider.tsx +3 -2
- package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +2 -0
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
- package/src/components/PublicLayout/index.ts +4 -2
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +21 -9
- package/src/components/{SessionRestorationLoader.tsx → SessionRestorationLoader/SessionRestorationLoader.tsx} +3 -2
- package/src/components/SessionRestorationLoader/index.ts +3 -0
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Table/__tests__/Table.test.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/index.ts +7 -10
- package/src/hooks/__tests__/hooks.integration.test.tsx +37 -22
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +33 -17
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +28 -3
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +36 -9
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +26 -2
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +19 -6
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +17 -4
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +17 -4
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +26 -6
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +16 -6
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +3 -3
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +17 -3
- package/src/hooks/public/usePublicEvent.ts +7 -6
- package/src/hooks/public/usePublicEventLogo.ts +7 -4
- package/src/hooks/public/usePublicFileDisplay.ts +6 -150
- package/src/hooks/useComponentPerformance.ts +4 -1
- package/src/hooks/useDataTablePerformance.ts +4 -3
- package/src/hooks/useEventTheme.test.ts +18 -5
- package/src/hooks/useEventTheme.ts +4 -1
- package/src/hooks/useEvents.ts +2 -0
- package/src/hooks/useFileDisplay.ts +9 -8
- package/src/hooks/useFileReference.ts +4 -1
- package/src/hooks/useFileUrl.ts +4 -1
- package/src/hooks/useInactivityTracker.ts +5 -4
- package/src/hooks/useOrganisationSecurity.test.ts +33 -12
- package/src/hooks/useOrganisationSecurity.ts +8 -7
- package/src/hooks/usePerformanceMonitor.ts +6 -3
- package/src/hooks/usePermissionCache.ts +13 -6
- package/src/hooks/useSecureDataAccess.test.ts +2 -2
- package/src/hooks/useSecureDataAccess.ts +9 -8
- package/src/hooks/useSessionRestoration.ts +4 -1
- package/src/hooks/useStorage.ts +4 -1
- package/src/index.ts +20 -7
- package/src/providers/services/AuthServiceProvider.tsx +3 -2
- package/src/providers/services/EventServiceProvider.tsx +2 -1
- package/src/providers/services/InactivityServiceProvider.tsx +2 -1
- package/src/providers/services/OrganisationServiceProvider.tsx +2 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +4 -3
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +22 -2
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +24 -2
- package/src/rbac/__tests__/cache-invalidation.test.ts +20 -6
- package/src/rbac/api.ts +5 -2
- package/src/rbac/audit-enhanced.ts +6 -6
- package/src/rbac/audit.test.ts +60 -38
- package/src/rbac/audit.ts +8 -8
- package/src/rbac/cache-invalidation.ts +7 -4
- package/src/rbac/components/EnhancedNavigationMenu.tsx +11 -5
- package/src/rbac/components/NavigationGuard.tsx +7 -3
- package/src/rbac/components/NavigationProvider.tsx +6 -3
- package/src/rbac/components/PagePermissionGuard.tsx +28 -16
- package/src/rbac/components/PagePermissionProvider.tsx +4 -1
- package/src/rbac/components/PermissionEnforcer.tsx +9 -3
- package/src/rbac/components/RoleBasedRouter.tsx +3 -1
- package/src/rbac/components/SecureDataProvider.tsx +7 -3
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +87 -61
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +83 -33
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +36 -13
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +2 -2
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +22 -8
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +19 -6
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +43 -17
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +42 -17
- package/src/rbac/engine.ts +15 -7
- package/src/rbac/hooks/usePermissions.ts +7 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +2 -2
- package/src/rbac/hooks/useResolvedScope.ts +10 -7
- package/src/rbac/permissions.ts +5 -2
- package/src/rbac/security.test.ts +27 -16
- package/src/rbac/security.ts +5 -4
- package/src/services/AuthService.ts +22 -21
- package/src/services/EventService.ts +12 -12
- package/src/services/InactivityService.ts +5 -4
- package/src/services/OrganisationService.ts +26 -25
- package/src/services/__tests__/AuthService.test.ts +51 -19
- package/src/services/__tests__/EventService.test.ts +37 -5
- package/src/services/__tests__/InactivityService.test.ts +38 -4
- package/src/services/__tests__/OrganisationService.test.ts +3 -8
- package/src/services/base/BaseService.ts +3 -1
- package/src/styles/core.css +3 -0
- package/src/theming/__tests__/runtime.test.ts +21 -12
- package/src/theming/parseEventColours.ts +5 -19
- package/src/theming/runtime.ts +8 -4
- package/src/types/validation.ts +2 -29
- package/src/utils/__tests__/appConfig.unit.test.ts +1 -1
- package/src/utils/__tests__/audit.unit.test.ts +1 -1
- package/src/utils/__tests__/auth-utils.unit.test.ts +1 -1
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +19 -19
- package/src/utils/__tests__/cn.unit.test.ts +1 -1
- package/src/utils/__tests__/debugLogger.test.ts +1 -1
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -1
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -1
- package/src/utils/__tests__/formatting.unit.test.ts +1 -1
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +1 -1
- package/src/utils/__tests__/logger.unit.test.ts +1 -1
- package/src/utils/__tests__/organisationContext.unit.test.ts +1 -1
- package/src/utils/__tests__/performanceBenchmark.test.ts +1 -1
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +1 -1
- package/src/utils/__tests__/permissionTypes.unit.test.ts +1 -1
- package/src/utils/__tests__/permissionUtils.unit.test.ts +1 -1
- package/src/utils/__tests__/sanitization.unit.test.ts +1 -1
- package/src/utils/__tests__/schemaUtils.unit.test.ts +1 -1
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +1 -1
- package/src/utils/__tests__/secureErrors.unit.test.ts +33 -15
- package/src/utils/__tests__/secureStorage.unit.test.ts +1 -1
- package/src/utils/__tests__/security.unit.test.ts +40 -18
- package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -1
- package/src/utils/__tests__/sessionTracking.unit.test.ts +40 -29
- package/src/utils/__tests__/validationUtils.unit.test.ts +19 -6
- package/src/utils/app/appConfig.ts +47 -0
- package/src/utils/app/appIdResolver.test.ts +497 -0
- package/src/utils/app/appIdResolver.ts +133 -0
- package/src/utils/app/appNameResolver.simple.test.ts +212 -0
- package/src/utils/app/appNameResolver.test.ts +121 -0
- package/src/utils/app/appNameResolver.ts +195 -0
- package/src/utils/audit/audit.ts +127 -0
- package/src/utils/context/organisationContext.test.ts +322 -0
- package/src/utils/context/organisationContext.ts +156 -0
- package/src/utils/context/sessionTracking.ts +125 -0
- package/src/utils/core/cn.ts +7 -0
- package/src/utils/core/debugLogger.ts +67 -0
- package/src/utils/core/logger.ts +181 -0
- package/src/utils/device/deviceFingerprint.ts +215 -0
- package/src/utils/dynamic/dynamicUtils.ts +105 -0
- package/src/utils/dynamic/lazyLoad.tsx +44 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +788 -0
- package/src/utils/file-reference/index.ts +501 -0
- package/src/utils/formatting/formatDate.test.ts +237 -0
- package/src/utils/formatting/formatting.ts +133 -0
- package/src/utils/index.ts +39 -54
- package/src/utils/performance/bundleAnalysis.ts +129 -0
- package/src/utils/performance/performanceBenchmark.ts +64 -0
- package/src/utils/performance/performanceBudgets.ts +110 -0
- package/src/utils/permissions/permissionTypes.ts +37 -0
- package/src/utils/permissions/permissionUtils.test.ts +393 -0
- package/src/utils/permissions/permissionUtils.ts +34 -0
- package/src/utils/security/auth-utils.ts +96 -0
- package/src/utils/security/secureDataAccess.test.ts +711 -0
- package/src/utils/security/secureDataAccess.ts +377 -0
- package/src/utils/security/secureErrors.ts +82 -0
- package/src/utils/security/secureStorage.ts +244 -0
- package/src/utils/security/security.ts +159 -0
- package/src/utils/security/securityMonitor.ts +45 -0
- package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -4
- package/src/utils/storage/helpers.ts +15 -8
- package/src/utils/validation/__tests__/htmlSanitization.unit.test.ts +598 -0
- package/src/{validation → utils/validation}/csrf.ts +1 -1
- package/src/utils/validation/htmlSanitization.ts +184 -0
- package/src/utils/validation/index.ts +79 -0
- package/src/utils/validation/sanitization.ts +333 -0
- package/src/{validation/schemaUtils.ts → utils/validation/schema.ts} +11 -6
- package/src/{validation → utils/validation}/sqlInjectionProtection.ts +2 -0
- package/src/utils/validation/validation.ts +111 -0
- package/src/utils/validation/validationUtils.ts +123 -0
- package/src/validation/index.ts +3 -34
- package/dist/chunk-24MKLB7U.js +0 -81
- package/dist/chunk-24MKLB7U.js.map +0 -1
- package/dist/chunk-3CG5L6RN.js.map +0 -1
- package/dist/chunk-3DBFLLLU.js.map +0 -1
- package/dist/chunk-5F3NDPJV.js.map +0 -1
- package/dist/chunk-66C4BSAY.js.map +0 -1
- package/dist/chunk-BDZUMRBD.js +0 -87
- package/dist/chunk-BDZUMRBD.js.map +0 -1
- package/dist/chunk-BYXRHAIF.js.map +0 -1
- package/dist/chunk-CDQ3PX7L.js +0 -18
- package/dist/chunk-CDQ3PX7L.js.map +0 -1
- package/dist/chunk-CQZU6TFE.js.map +0 -1
- package/dist/chunk-F64FFPOZ.js.map +0 -1
- package/dist/chunk-GEVIB2UB.js.map +0 -1
- package/dist/chunk-GKHF54DI.js.map +0 -1
- package/dist/chunk-GVDR7WNV.js.map +0 -1
- package/dist/chunk-HMNOSGVA.js.map +0 -1
- package/dist/chunk-JCQZ6LA7.js.map +0 -1
- package/dist/chunk-M6DDYFUD.js.map +0 -1
- package/dist/chunk-O3NWNXDY.js.map +0 -1
- package/dist/chunk-PYUXFQJ3.js.map +0 -1
- package/dist/chunk-UJI6WSMD.js.map +0 -1
- package/dist/chunk-VZ5OR6HD.js.map +0 -1
- package/dist/chunk-WP5I5GLN.js.map +0 -1
- package/dist/chunk-ZYZCRSBD.js.map +0 -1
- package/dist/validation-DnhrNMju.d.ts +0 -159
- package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +0 -185
- package/src/validation/__tests__/common.unit.test.ts +0 -101
- package/src/validation/__tests__/csrf.unit.test.ts +0 -365
- package/src/validation/__tests__/passwordSchema.unit.test.ts +0 -203
- package/src/validation/__tests__/sanitization.unit.test.ts +0 -250
- package/src/validation/__tests__/schemaUtils.unit.test.ts +0 -451
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +0 -462
- package/src/validation/__tests__/user.unit.test.ts +0 -440
- package/src/validation/sanitization.ts +0 -96
- /package/dist/{DataTable-A36PJG6N.js.map → DataTable-6M4L6BI2.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CQDZRJIS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
- /package/dist/{api-TNIBJWLM.js.map → api-45XYYO2A.js.map} +0 -0
- /package/dist/{audit-T36HM7IM.js.map → audit-64X3VJXB.js.map} +0 -0
- /package/dist/{chunk-CTJRBUX2.js.map → chunk-6LAAY47Q.js.map} +0 -0
- /package/dist/{chunk-ZV77RZMU.js.map → chunk-INQLMHPF.js.map} +0 -0
- /package/dist/{useInactivityTracker-MRUU55XI.js.map → useInactivityTracker-TO6ZOF35.js.map} +0 -0
- /package/src/{validation → utils/validation}/common.ts +0 -0
- /package/src/{validation → utils/validation}/passwordSchema.ts +0 -0
- /package/src/{validation → utils/validation}/user.ts +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file App ID Resolver Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/AppIdResolver
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for app ID resolution utility functions covering all critical functionality.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
11
|
+
import { getAppId, getAppIds, CachedAppIdResolver } from './appIdResolver';
|
|
12
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
13
|
+
import type { Database } from '../types/database';
|
|
14
|
+
|
|
15
|
+
// Mock the Logger module
|
|
16
|
+
vi.mock('../core/logger', () => {
|
|
17
|
+
const mockLoggerInstance = {
|
|
18
|
+
error: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
createLogger: vi.fn(() => mockLoggerInstance),
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Get the mock instance after mock is set up
|
|
26
|
+
import { createLogger } from '../core/logger';
|
|
27
|
+
const getMockLogger = () => createLogger('test');
|
|
28
|
+
|
|
29
|
+
// Mock Supabase client
|
|
30
|
+
const createMockSupabaseClient = () => {
|
|
31
|
+
const queryBuilder = {
|
|
32
|
+
select: vi.fn().mockReturnThis(),
|
|
33
|
+
ilike: vi.fn().mockReturnThis(),
|
|
34
|
+
eq: vi.fn().mockReturnThis(),
|
|
35
|
+
or: vi.fn().mockReturnThis(),
|
|
36
|
+
single: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
37
|
+
mockResolvedValue: vi.fn(),
|
|
38
|
+
mockRejectedValue: vi.fn()
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Make the chained methods return the query builder with mock methods
|
|
42
|
+
queryBuilder.select.mockReturnValue(queryBuilder);
|
|
43
|
+
queryBuilder.ilike.mockReturnValue(queryBuilder);
|
|
44
|
+
queryBuilder.eq.mockReturnValue(queryBuilder);
|
|
45
|
+
queryBuilder.or.mockReturnValue(queryBuilder);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
from: vi.fn(() => queryBuilder),
|
|
49
|
+
queryBuilder // Export the query builder for direct access
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('App ID Resolver', () => {
|
|
54
|
+
let mockSupabase: any;
|
|
55
|
+
let mockQueryBuilder: any;
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
vi.clearAllMocks();
|
|
59
|
+
mockSupabase = createMockSupabaseClient();
|
|
60
|
+
mockQueryBuilder = mockSupabase.queryBuilder;
|
|
61
|
+
const logger = getMockLogger();
|
|
62
|
+
vi.mocked(logger.error).mockClear();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('getAppId', () => {
|
|
66
|
+
it('resolves app ID for valid app name', async () => {
|
|
67
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
68
|
+
data: { id: 'app-123' },
|
|
69
|
+
error: null
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
73
|
+
|
|
74
|
+
expect(result).toBe('app-123');
|
|
75
|
+
expect(mockSupabase.from).toHaveBeenCalledWith('rbac_apps');
|
|
76
|
+
expect(mockQueryBuilder.select).toHaveBeenCalledWith('id');
|
|
77
|
+
expect(mockQueryBuilder.ilike).toHaveBeenCalledWith('name', 'MY_APP');
|
|
78
|
+
expect(mockQueryBuilder.eq).toHaveBeenCalledWith('is_active', true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns null when app not found', async () => {
|
|
82
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
83
|
+
data: null,
|
|
84
|
+
error: { message: 'No rows found' }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const result = await getAppId(mockSupabase as any, 'NONEXISTENT_APP');
|
|
88
|
+
|
|
89
|
+
expect(result).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('handles database errors gracefully', async () => {
|
|
93
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
94
|
+
data: null,
|
|
95
|
+
error: { message: 'Database error' }
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
99
|
+
|
|
100
|
+
expect(result).toBeNull();
|
|
101
|
+
const logger = getMockLogger();
|
|
102
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
103
|
+
'Failed to resolve app ID for app name:',
|
|
104
|
+
'MY_APP',
|
|
105
|
+
{ message: 'Database error' }
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('handles exceptions gracefully', async () => {
|
|
110
|
+
mockQueryBuilder.single.mockRejectedValue(new Error('Network error'));
|
|
111
|
+
|
|
112
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
113
|
+
|
|
114
|
+
expect(result).toBeNull();
|
|
115
|
+
const logger = getMockLogger();
|
|
116
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
117
|
+
'Error resolving app ID for app name:',
|
|
118
|
+
'MY_APP',
|
|
119
|
+
expect.any(Error)
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('handles null/undefined app name', async () => {
|
|
124
|
+
const result1 = await getAppId(mockSupabase as any, null as any);
|
|
125
|
+
const result2 = await getAppId(mockSupabase as any, undefined as any);
|
|
126
|
+
|
|
127
|
+
expect(result1).toBeNull();
|
|
128
|
+
expect(result2).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('handles empty app name', async () => {
|
|
132
|
+
const result = await getAppId(mockSupabase as any, '');
|
|
133
|
+
|
|
134
|
+
expect(result).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('getAppIds', () => {
|
|
139
|
+
it('resolves multiple app IDs', async () => {
|
|
140
|
+
// Mock the chained call sequence for getAppIds
|
|
141
|
+
const mockChain = {
|
|
142
|
+
select: vi.fn().mockReturnThis(),
|
|
143
|
+
or: vi.fn().mockReturnThis(),
|
|
144
|
+
eq: vi.fn().mockResolvedValue({
|
|
145
|
+
data: [
|
|
146
|
+
{ id: 'app-123', name: 'MY_APP' },
|
|
147
|
+
{ id: 'app-456', name: 'ANOTHER_APP' }
|
|
148
|
+
],
|
|
149
|
+
error: null
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
154
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
155
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
156
|
+
|
|
157
|
+
const result = await getAppIds(mockSupabase as any, ['MY_APP', 'ANOTHER_APP', 'MISSING_APP']);
|
|
158
|
+
|
|
159
|
+
expect(result).toEqual({
|
|
160
|
+
'MY_APP': 'app-123',
|
|
161
|
+
'ANOTHER_APP': 'app-456',
|
|
162
|
+
'MISSING_APP': null
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('handles case-insensitive matching', async () => {
|
|
167
|
+
// Mock the chained call sequence for getAppIds
|
|
168
|
+
const mockChain = {
|
|
169
|
+
select: vi.fn().mockReturnThis(),
|
|
170
|
+
or: vi.fn().mockReturnThis(),
|
|
171
|
+
eq: vi.fn().mockResolvedValue({
|
|
172
|
+
data: [
|
|
173
|
+
{ id: 'app-123', name: 'my_app' },
|
|
174
|
+
{ id: 'app-456', name: 'another_app' }
|
|
175
|
+
],
|
|
176
|
+
error: null
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
181
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
182
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
183
|
+
|
|
184
|
+
const result = await getAppIds(mockSupabase as any, ['MY_APP', 'ANOTHER_APP']);
|
|
185
|
+
|
|
186
|
+
expect(result).toEqual({
|
|
187
|
+
'MY_APP': 'app-123',
|
|
188
|
+
'ANOTHER_APP': 'app-456'
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('returns empty object for empty app names array', async () => {
|
|
193
|
+
const result = await getAppIds(mockSupabase as any, []);
|
|
194
|
+
|
|
195
|
+
expect(result).toEqual({});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('handles database errors gracefully', async () => {
|
|
199
|
+
const mockChain = {
|
|
200
|
+
select: vi.fn().mockReturnThis(),
|
|
201
|
+
or: vi.fn().mockReturnThis(),
|
|
202
|
+
eq: vi.fn().mockResolvedValue({
|
|
203
|
+
data: null,
|
|
204
|
+
error: { message: 'Database error' }
|
|
205
|
+
})
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
209
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
210
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
211
|
+
|
|
212
|
+
const result = await getAppIds(mockSupabase as any, ['MY_APP', 'ANOTHER_APP']);
|
|
213
|
+
|
|
214
|
+
expect(result).toEqual({});
|
|
215
|
+
const logger = getMockLogger();
|
|
216
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
217
|
+
'Failed to resolve app IDs for app names:',
|
|
218
|
+
['MY_APP', 'ANOTHER_APP'],
|
|
219
|
+
{ message: 'Database error' }
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('handles exceptions gracefully', async () => {
|
|
224
|
+
const mockChain = {
|
|
225
|
+
select: vi.fn().mockReturnThis(),
|
|
226
|
+
or: vi.fn().mockReturnThis(),
|
|
227
|
+
eq: vi.fn().mockRejectedValue(new Error('Network error'))
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
231
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
232
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
233
|
+
|
|
234
|
+
const result = await getAppIds(mockSupabase as any, ['MY_APP', 'ANOTHER_APP']);
|
|
235
|
+
|
|
236
|
+
expect(result).toEqual({});
|
|
237
|
+
const logger = getMockLogger();
|
|
238
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
239
|
+
'Error resolving app IDs for app names:',
|
|
240
|
+
['MY_APP', 'ANOTHER_APP'],
|
|
241
|
+
expect.any(Error)
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('handles null/undefined app names array', async () => {
|
|
246
|
+
const result1 = await getAppIds(mockSupabase as any, null as any);
|
|
247
|
+
const result2 = await getAppIds(mockSupabase as any, undefined as any);
|
|
248
|
+
|
|
249
|
+
expect(result1).toEqual({});
|
|
250
|
+
expect(result2).toEqual({});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('CachedAppIdResolver', () => {
|
|
255
|
+
let resolver: CachedAppIdResolver;
|
|
256
|
+
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
resolver = new CachedAppIdResolver();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('resolves app ID and caches result', async () => {
|
|
262
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
263
|
+
data: { id: 'app-123' },
|
|
264
|
+
error: null
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result1 = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
268
|
+
const result2 = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
269
|
+
|
|
270
|
+
expect(result1).toBe('app-123');
|
|
271
|
+
expect(result2).toBe('app-123');
|
|
272
|
+
expect(mockQueryBuilder.single).toHaveBeenCalledTimes(1); // Should only call once due to caching
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('expires cached results after TTL', async () => {
|
|
276
|
+
resolver = new CachedAppIdResolver();
|
|
277
|
+
|
|
278
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
279
|
+
data: { id: 'app-123' },
|
|
280
|
+
error: null
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const result1 = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
284
|
+
expect(result1).toBe('app-123');
|
|
285
|
+
|
|
286
|
+
// Clear cache to simulate TTL expiration
|
|
287
|
+
resolver.clearCacheForApp('MY_APP');
|
|
288
|
+
|
|
289
|
+
const result2 = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
290
|
+
expect(result2).toBe('app-123');
|
|
291
|
+
expect(mockQueryBuilder.single).toHaveBeenCalledTimes(2); // Should call again after cache clear
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('handles cache misses gracefully', async () => {
|
|
295
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
296
|
+
data: null,
|
|
297
|
+
error: { message: 'No rows found' }
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const result = await resolver.getAppId(mockSupabase as any, 'NONEXISTENT_APP');
|
|
301
|
+
|
|
302
|
+
expect(result).toBeNull();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('handles database errors gracefully', async () => {
|
|
306
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
307
|
+
data: null,
|
|
308
|
+
error: { message: 'Database error' }
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const result = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
312
|
+
|
|
313
|
+
expect(result).toBeNull();
|
|
314
|
+
const logger = getMockLogger();
|
|
315
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
316
|
+
'Failed to resolve app ID for app name:',
|
|
317
|
+
'MY_APP',
|
|
318
|
+
{ message: 'Database error' }
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('clears cache correctly', async () => {
|
|
323
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
324
|
+
data: { id: 'app-123' },
|
|
325
|
+
error: null
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
329
|
+
resolver.clearCache();
|
|
330
|
+
|
|
331
|
+
const result = await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
332
|
+
|
|
333
|
+
expect(result).toBe('app-123');
|
|
334
|
+
expect(mockQueryBuilder.single).toHaveBeenCalledTimes(2); // Should call again after cache clear
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('respects max cache size', async () => {
|
|
338
|
+
resolver = new CachedAppIdResolver(60000, 2); // 2 max cache entries
|
|
339
|
+
|
|
340
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
341
|
+
data: { id: 'app-123' },
|
|
342
|
+
error: null
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Fill cache beyond max size
|
|
346
|
+
await resolver.getAppId(mockSupabase as any, 'APP1');
|
|
347
|
+
await resolver.getAppId(mockSupabase as any, 'APP2');
|
|
348
|
+
await resolver.getAppId(mockSupabase as any, 'APP3');
|
|
349
|
+
|
|
350
|
+
// Should have called database for all three apps
|
|
351
|
+
expect(mockQueryBuilder.single).toHaveBeenCalledTimes(3);
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('Integration Tests', () => {
|
|
356
|
+
it('integrates with real Supabase client structure', async () => {
|
|
357
|
+
const mockClient = {
|
|
358
|
+
from: vi.fn(() => ({
|
|
359
|
+
select: vi.fn().mockReturnThis(),
|
|
360
|
+
ilike: vi.fn().mockReturnThis(),
|
|
361
|
+
eq: vi.fn().mockReturnThis(),
|
|
362
|
+
single: vi.fn().mockResolvedValue({
|
|
363
|
+
data: { id: 'app-123' },
|
|
364
|
+
error: null
|
|
365
|
+
})
|
|
366
|
+
}))
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const result = await getAppId(mockClient as any, 'MY_APP');
|
|
370
|
+
|
|
371
|
+
expect(result).toBe('app-123');
|
|
372
|
+
expect(mockClient.from).toHaveBeenCalledWith('rbac_apps');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('handles complex app name patterns', async () => {
|
|
376
|
+
const appNames = [
|
|
377
|
+
'MY_APP',
|
|
378
|
+
'my-app',
|
|
379
|
+
'my_app',
|
|
380
|
+
'MyApp',
|
|
381
|
+
'myapp'
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
const mockChain = {
|
|
385
|
+
select: vi.fn().mockReturnThis(),
|
|
386
|
+
or: vi.fn().mockReturnThis(),
|
|
387
|
+
eq: vi.fn().mockResolvedValue({
|
|
388
|
+
data: [
|
|
389
|
+
{ id: 'app-123', name: 'my_app' },
|
|
390
|
+
{ id: 'app-456', name: 'my-app' }
|
|
391
|
+
],
|
|
392
|
+
error: null
|
|
393
|
+
})
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
397
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
398
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
399
|
+
|
|
400
|
+
const result = await getAppIds(mockSupabase as any, appNames);
|
|
401
|
+
|
|
402
|
+
expect(result).toEqual({
|
|
403
|
+
'MY_APP': 'app-123',
|
|
404
|
+
'my-app': 'app-456',
|
|
405
|
+
'my_app': null,
|
|
406
|
+
'MyApp': null,
|
|
407
|
+
'myapp': null
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('Error Handling', () => {
|
|
413
|
+
it('handles network timeouts', async () => {
|
|
414
|
+
mockQueryBuilder.single.mockRejectedValue(new Error('Request timeout'));
|
|
415
|
+
|
|
416
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
417
|
+
|
|
418
|
+
expect(result).toBeNull();
|
|
419
|
+
const logger = getMockLogger();
|
|
420
|
+
expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
|
|
421
|
+
'Error resolving app ID for app name:',
|
|
422
|
+
'MY_APP',
|
|
423
|
+
expect.any(Error)
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('handles malformed responses', async () => {
|
|
428
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
429
|
+
data: { invalid: 'structure' },
|
|
430
|
+
error: null
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
434
|
+
|
|
435
|
+
expect(result).toBeNull();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('handles null data responses', async () => {
|
|
439
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
440
|
+
data: null,
|
|
441
|
+
error: null
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const result = await getAppId(mockSupabase as any, 'MY_APP');
|
|
445
|
+
|
|
446
|
+
expect(result).toBeNull();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('Performance', () => {
|
|
451
|
+
it('handles large numbers of app names efficiently', async () => {
|
|
452
|
+
const appNames = Array.from({ length: 100 }, (_, i) => `APP_${i}`);
|
|
453
|
+
|
|
454
|
+
const mockChain = {
|
|
455
|
+
select: vi.fn().mockReturnThis(),
|
|
456
|
+
or: vi.fn().mockReturnThis(),
|
|
457
|
+
eq: vi.fn().mockResolvedValue({
|
|
458
|
+
data: appNames.map((name, i) => ({ id: `app-${i}`, name: name.toLowerCase() })),
|
|
459
|
+
error: null
|
|
460
|
+
})
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
mockQueryBuilder.select.mockReturnValue(mockChain);
|
|
464
|
+
mockQueryBuilder.or.mockReturnValue(mockChain);
|
|
465
|
+
mockQueryBuilder.eq.mockReturnValue(mockChain);
|
|
466
|
+
|
|
467
|
+
const startTime = Date.now();
|
|
468
|
+
const result = await getAppIds(mockSupabase as any, appNames);
|
|
469
|
+
const endTime = Date.now();
|
|
470
|
+
|
|
471
|
+
expect(Object.keys(result)).toHaveLength(100);
|
|
472
|
+
expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('caches results efficiently', async () => {
|
|
476
|
+
const resolver = new CachedAppIdResolver();
|
|
477
|
+
|
|
478
|
+
mockQueryBuilder.single.mockResolvedValue({
|
|
479
|
+
data: { id: 'app-123' },
|
|
480
|
+
error: null
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const startTime = Date.now();
|
|
484
|
+
|
|
485
|
+
// First call - should hit database
|
|
486
|
+
await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
487
|
+
|
|
488
|
+
// Second call - should hit cache
|
|
489
|
+
await resolver.getAppId(mockSupabase as any, 'MY_APP');
|
|
490
|
+
|
|
491
|
+
const endTime = Date.now();
|
|
492
|
+
|
|
493
|
+
expect(mockQueryBuilder.single).toHaveBeenCalledTimes(1);
|
|
494
|
+
expect(endTime - startTime).toBeLessThan(100); // Should be very fast due to caching
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App ID Resolution Utility
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/AppIdResolver
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* This module provides utilities to resolve app names to app IDs for database operations.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
11
|
+
import type { Database } from '../../types/database';
|
|
12
|
+
import { createLogger } from '../core/logger';
|
|
13
|
+
|
|
14
|
+
const log = createLogger('AppIdResolver');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves an app name to its corresponding app ID
|
|
18
|
+
*
|
|
19
|
+
* @param supabase - Supabase client instance
|
|
20
|
+
* @param appName - The app name to resolve
|
|
21
|
+
* @returns Promise resolving to the app ID or null if not found
|
|
22
|
+
*/
|
|
23
|
+
export async function getAppId(
|
|
24
|
+
supabase: SupabaseClient<Database>,
|
|
25
|
+
appName: string
|
|
26
|
+
): Promise<string | null> {
|
|
27
|
+
try {
|
|
28
|
+
const { data, error } = await supabase
|
|
29
|
+
.from('rbac_apps')
|
|
30
|
+
.select('id')
|
|
31
|
+
.ilike('name', appName)
|
|
32
|
+
.eq('is_active', true)
|
|
33
|
+
.single();
|
|
34
|
+
|
|
35
|
+
if (error) {
|
|
36
|
+
log.error('Failed to resolve app ID for app name:', appName, error);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (data as { id: string } | null)?.id || null;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
log.error('Error resolving app ID for app name:', appName, error);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves multiple app names to their corresponding app IDs
|
|
49
|
+
*
|
|
50
|
+
* @param supabase - Supabase client instance
|
|
51
|
+
* @param appNames - Array of app names to resolve
|
|
52
|
+
* @returns Promise resolving to a map of app names to app IDs
|
|
53
|
+
*/
|
|
54
|
+
export async function getAppIds(
|
|
55
|
+
supabase: SupabaseClient<Database>,
|
|
56
|
+
appNames: string[]
|
|
57
|
+
): Promise<Record<string, string | null>> {
|
|
58
|
+
try {
|
|
59
|
+
// For case-insensitive matching with multiple values, we need to use OR conditions
|
|
60
|
+
// since PostgreSQL doesn't support case-insensitive IN with ILIKE
|
|
61
|
+
const orConditions = appNames.map(name => `name.ilike.${name}`).join(',');
|
|
62
|
+
|
|
63
|
+
const { data, error } = await supabase
|
|
64
|
+
.from('rbac_apps')
|
|
65
|
+
.select('id, name')
|
|
66
|
+
.or(orConditions)
|
|
67
|
+
.eq('is_active', true);
|
|
68
|
+
|
|
69
|
+
if (error) {
|
|
70
|
+
log.error('Failed to resolve app IDs for app names:', appNames, error);
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result: Record<string, string | null> = {};
|
|
75
|
+
|
|
76
|
+
// Initialize all app names with null
|
|
77
|
+
appNames.forEach(name => {
|
|
78
|
+
result[name] = null;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Set resolved app IDs - match case-insensitively
|
|
82
|
+
(data as { id: string; name: string }[] | null)?.forEach(app => {
|
|
83
|
+
// Find the original app name that matches (case-insensitive)
|
|
84
|
+
const originalName = appNames.find(name =>
|
|
85
|
+
name.toLowerCase() === app.name.toLowerCase()
|
|
86
|
+
);
|
|
87
|
+
if (originalName) {
|
|
88
|
+
result[originalName] = app.id;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
log.error('Error resolving app IDs for app names:', appNames, error);
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Cached app ID resolver with TTL
|
|
101
|
+
*/
|
|
102
|
+
export class CachedAppIdResolver {
|
|
103
|
+
private cache = new Map<string, { id: string | null; expires: number }>();
|
|
104
|
+
private ttl = 5 * 60 * 1000; // 5 minutes
|
|
105
|
+
|
|
106
|
+
async getAppId(
|
|
107
|
+
supabase: SupabaseClient<Database>,
|
|
108
|
+
appName: string
|
|
109
|
+
): Promise<string | null> {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const cached = this.cache.get(appName);
|
|
112
|
+
|
|
113
|
+
if (cached && cached.expires > now) {
|
|
114
|
+
return cached.id;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const id = await getAppId(supabase, appName);
|
|
118
|
+
this.cache.set(appName, { id, expires: now + this.ttl });
|
|
119
|
+
|
|
120
|
+
return id;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
clearCache(): void {
|
|
124
|
+
this.cache.clear();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
clearCacheForApp(appName: string): void {
|
|
128
|
+
this.cache.delete(appName);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Export singleton instance
|
|
133
|
+
export const cachedAppIdResolver = new CachedAppIdResolver();
|