@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
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file PublicPageDebugger Component Tests
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Components/PublicLayout/__tests__
|
|
5
|
-
* @since 1.0.0
|
|
6
|
-
*
|
|
7
|
-
* Comprehensive tests for the PublicPageDebugger component
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
import { render, screen } from '@testing-library/react';
|
|
12
|
-
import { PublicPageDebugger } from '../PublicPageDebugger';
|
|
13
|
-
import { PublicPageProvider } from '../PublicPageProvider';
|
|
14
|
-
|
|
15
|
-
describe('PublicPageDebugger', () => {
|
|
16
|
-
const consoleLogSpy = vi.spyOn(console, 'log');
|
|
17
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn');
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
vi.clearAllMocks();
|
|
21
|
-
consoleLogSpy.mockImplementation(() => {});
|
|
22
|
-
consoleWarnSpy.mockImplementation(() => {});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
consoleLogSpy.mockRestore();
|
|
27
|
-
consoleWarnSpy.mockRestore();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('Rendering', () => {
|
|
31
|
-
it('renders debugger UI when enabled', () => {
|
|
32
|
-
render(
|
|
33
|
-
<PublicPageProvider>
|
|
34
|
-
<PublicPageDebugger enabled={true} />
|
|
35
|
-
</PublicPageProvider>
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
39
|
-
expect(screen.getByText('Check console for context analysis')).toBeInTheDocument();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('does not render when disabled', () => {
|
|
43
|
-
render(
|
|
44
|
-
<PublicPageProvider>
|
|
45
|
-
<PublicPageDebugger enabled={false} />
|
|
46
|
-
</PublicPageProvider>
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
expect(screen.queryByText('Public Page Debugger')).not.toBeInTheDocument();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('renders with default enabled state', () => {
|
|
53
|
-
render(
|
|
54
|
-
<PublicPageProvider>
|
|
55
|
-
<PublicPageDebugger />
|
|
56
|
-
</PublicPageProvider>
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('renders with custom label', () => {
|
|
63
|
-
render(
|
|
64
|
-
<PublicPageProvider>
|
|
65
|
-
<PublicPageDebugger label="CustomLabel" />
|
|
66
|
-
</PublicPageProvider>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('Console Logging', () => {
|
|
74
|
-
it('renders with debugging enabled', () => {
|
|
75
|
-
render(
|
|
76
|
-
<PublicPageProvider>
|
|
77
|
-
<PublicPageDebugger enabled={true} label="TestLabel" />
|
|
78
|
-
</PublicPageProvider>
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('does not render when disabled', () => {
|
|
85
|
-
render(
|
|
86
|
-
<PublicPageProvider>
|
|
87
|
-
<PublicPageDebugger enabled={false} label="TestLabel" />
|
|
88
|
-
</PublicPageProvider>
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
expect(screen.queryByText('Public Page Debugger')).not.toBeInTheDocument();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('Context Detection', () => {
|
|
96
|
-
it('renders within public page context', () => {
|
|
97
|
-
render(
|
|
98
|
-
<PublicPageProvider>
|
|
99
|
-
<PublicPageDebugger enabled={true} label="TestLabel" />
|
|
100
|
-
</PublicPageProvider>
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Visual Debugger', () => {
|
|
108
|
-
it('displays fixed position debugger', () => {
|
|
109
|
-
render(
|
|
110
|
-
<PublicPageProvider>
|
|
111
|
-
<PublicPageDebugger enabled={true} />
|
|
112
|
-
</PublicPageProvider>
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
// Visual debugger should be rendered
|
|
116
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('renders debugger with correct text content', () => {
|
|
120
|
-
render(
|
|
121
|
-
<PublicPageProvider>
|
|
122
|
-
<PublicPageDebugger enabled={true} />
|
|
123
|
-
</PublicPageProvider>
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
127
|
-
expect(screen.getByText('Check console for context analysis')).toBeInTheDocument();
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('Edge Cases', () => {
|
|
132
|
-
it('renders without crashing', () => {
|
|
133
|
-
render(
|
|
134
|
-
<PublicPageProvider>
|
|
135
|
-
<PublicPageDebugger enabled={true} />
|
|
136
|
-
</PublicPageProvider>
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('handles multiple instances', () => {
|
|
143
|
-
render(
|
|
144
|
-
<PublicPageProvider>
|
|
145
|
-
<PublicPageDebugger enabled={true} label="FirstDebugger" />
|
|
146
|
-
<PublicPageDebugger enabled={true} label="SecondDebugger" />
|
|
147
|
-
</PublicPageProvider>
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
expect(screen.getAllByText('Public Page Debugger')).toHaveLength(2);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('handles label changes on re-render', () => {
|
|
154
|
-
const { rerender } = render(
|
|
155
|
-
<PublicPageProvider>
|
|
156
|
-
<PublicPageDebugger enabled={true} label="Label1" />
|
|
157
|
-
</PublicPageProvider>
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
161
|
-
|
|
162
|
-
rerender(
|
|
163
|
-
<PublicPageProvider>
|
|
164
|
-
<PublicPageDebugger enabled={true} label="Label2" />
|
|
165
|
-
</PublicPageProvider>
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Component should still render
|
|
169
|
-
expect(screen.getByText('Public Page Debugger')).toBeInTheDocument();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('Performance', () => {
|
|
174
|
-
it('renders quickly without performance issues', () => {
|
|
175
|
-
const { container } = render(
|
|
176
|
-
<PublicPageProvider>
|
|
177
|
-
<PublicPageDebugger enabled={true} label="TestLabel" />
|
|
178
|
-
</PublicPageProvider>
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
expect(container).toBeInTheDocument();
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
emailSchema,
|
|
4
|
-
nameSchema,
|
|
5
|
-
phoneSchema,
|
|
6
|
-
urlSchema,
|
|
7
|
-
dateSchema
|
|
8
|
-
} from '../common';
|
|
9
|
-
|
|
10
|
-
describe('Common Validation Schemas', () => {
|
|
11
|
-
describe('emailSchema', () => {
|
|
12
|
-
it('should validate correct email addresses', () => {
|
|
13
|
-
expect(emailSchema.safeParse('test@example.com').success).toBe(true);
|
|
14
|
-
expect(emailSchema.safeParse('user.name+tag@domain.co.uk').success).toBe(true);
|
|
15
|
-
expect(emailSchema.safeParse('123@domain.org').success).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should reject invalid email addresses', () => {
|
|
19
|
-
expect(emailSchema.safeParse('invalid-email').success).toBe(false);
|
|
20
|
-
expect(emailSchema.safeParse('@domain.com').success).toBe(false);
|
|
21
|
-
expect(emailSchema.safeParse('user@').success).toBe(false);
|
|
22
|
-
expect(emailSchema.safeParse('').success).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should reject emails that are too long', () => {
|
|
26
|
-
const longEmail = 'a'.repeat(255) + '@example.com';
|
|
27
|
-
expect(emailSchema.safeParse(longEmail).success).toBe(false);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('nameSchema', () => {
|
|
32
|
-
it('should validate correct names', () => {
|
|
33
|
-
expect(nameSchema.safeParse('John Doe').success).toBe(true);
|
|
34
|
-
expect(nameSchema.safeParse('Mary-Jane').success).toBe(true);
|
|
35
|
-
expect(nameSchema.safeParse("O'Connor").success).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should reject invalid names', () => {
|
|
39
|
-
expect(nameSchema.safeParse('').success).toBe(false);
|
|
40
|
-
expect(nameSchema.safeParse('John123').success).toBe(false);
|
|
41
|
-
expect(nameSchema.safeParse('John@Doe').success).toBe(false);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should reject names that are too long', () => {
|
|
45
|
-
const longName = 'A'.repeat(101);
|
|
46
|
-
expect(nameSchema.safeParse(longName).success).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('phoneSchema', () => {
|
|
51
|
-
it('should validate correct phone numbers', () => {
|
|
52
|
-
expect(phoneSchema.safeParse('+1-555-123-4567').success).toBe(true);
|
|
53
|
-
expect(phoneSchema.safeParse('555-123-4567').success).toBe(true);
|
|
54
|
-
expect(phoneSchema.safeParse('(555) 123-4567').success).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should reject invalid phone numbers', () => {
|
|
58
|
-
expect(phoneSchema.safeParse('123').success).toBe(false);
|
|
59
|
-
expect(phoneSchema.safeParse('not-a-number').success).toBe(false);
|
|
60
|
-
expect(phoneSchema.safeParse('').success).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should reject phone numbers that are too short or long', () => {
|
|
64
|
-
expect(phoneSchema.safeParse('123').success).toBe(false);
|
|
65
|
-
expect(phoneSchema.safeParse('1'.repeat(21)).success).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('urlSchema', () => {
|
|
70
|
-
it('should validate correct URLs', () => {
|
|
71
|
-
expect(urlSchema.safeParse('https://example.com').success).toBe(true);
|
|
72
|
-
expect(urlSchema.safeParse('http://sub.domain.co.uk/path').success).toBe(true);
|
|
73
|
-
expect(urlSchema.safeParse('https://example.com?param=value').success).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should reject invalid URLs', () => {
|
|
77
|
-
expect(urlSchema.safeParse('not-a-url').success).toBe(false);
|
|
78
|
-
expect(urlSchema.safeParse('').success).toBe(false);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should reject URLs that are too long', () => {
|
|
82
|
-
const longUrl = 'https://example.com/' + 'a'.repeat(2048);
|
|
83
|
-
expect(urlSchema.safeParse(longUrl).success).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('dateSchema', () => {
|
|
88
|
-
it('should validate correct dates', () => {
|
|
89
|
-
expect(dateSchema.safeParse('2023-12-25').success).toBe(true);
|
|
90
|
-
expect(dateSchema.safeParse('2023-01-01').success).toBe(true);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should reject invalid dates', () => {
|
|
94
|
-
expect(dateSchema.safeParse('invalid-date').success).toBe(false);
|
|
95
|
-
expect(dateSchema.safeParse('2023/12/25').success).toBe(false);
|
|
96
|
-
expect(dateSchema.safeParse('12/25/2023').success).toBe(false);
|
|
97
|
-
expect(dateSchema.safeParse('2023-13-45').success).toBe(false);
|
|
98
|
-
expect(dateSchema.safeParse('').success).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
});
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file CSRF Protection Unit Tests
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Validation/CSRF/Tests
|
|
5
|
-
* @since 0.1.0
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
-
import {
|
|
10
|
-
generateCSRFToken,
|
|
11
|
-
validateCSRFToken,
|
|
12
|
-
getCSRFToken,
|
|
13
|
-
csrfManager,
|
|
14
|
-
CSRFTokenData,
|
|
15
|
-
} from '../csrf';
|
|
16
|
-
|
|
17
|
-
// Mock crypto for testing
|
|
18
|
-
const mockCrypto = {
|
|
19
|
-
getRandomValues: vi.fn(),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
Object.defineProperty(global, 'crypto', {
|
|
23
|
-
value: mockCrypto,
|
|
24
|
-
writable: true,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Mock secure storage
|
|
28
|
-
vi.mock('../../utils/secureStorage', () => ({
|
|
29
|
-
secureStorage: {
|
|
30
|
-
setItem: vi.fn().mockResolvedValue(undefined),
|
|
31
|
-
getItem: vi.fn().mockResolvedValue(null),
|
|
32
|
-
removeItem: vi.fn().mockResolvedValue(undefined),
|
|
33
|
-
},
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
describe('CSRF Protection', () => {
|
|
37
|
-
const testSessionId = 'test-session-123';
|
|
38
|
-
const testSessionId2 = 'test-session-456';
|
|
39
|
-
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
vi.clearAllMocks();
|
|
42
|
-
mockCrypto.getRandomValues.mockImplementation((array) => {
|
|
43
|
-
for (let i = 0; i < array.length; i++) {
|
|
44
|
-
array[i] = Math.floor(Math.random() * 256);
|
|
45
|
-
}
|
|
46
|
-
return array;
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
afterEach(async () => {
|
|
51
|
-
// Clear session tokens
|
|
52
|
-
await csrfManager.clearSession(testSessionId);
|
|
53
|
-
await csrfManager.clearSession(testSessionId2);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('generateCSRFToken', () => {
|
|
57
|
-
it('should generate a valid CSRF token', async () => {
|
|
58
|
-
const token = await generateCSRFToken(testSessionId);
|
|
59
|
-
|
|
60
|
-
expect(token).toBeDefined();
|
|
61
|
-
expect(typeof token).toBe('string');
|
|
62
|
-
expect(token.length).toBe(64); // 32 bytes * 2 hex chars
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should generate different tokens on subsequent calls', async () => {
|
|
66
|
-
const token1 = await generateCSRFToken(testSessionId);
|
|
67
|
-
const token2 = await generateCSRFToken(testSessionId);
|
|
68
|
-
|
|
69
|
-
expect(token1).not.toBe(token2);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should generate tokens for different sessions', async () => {
|
|
73
|
-
const token1 = await generateCSRFToken(testSessionId);
|
|
74
|
-
const token2 = await generateCSRFToken(testSessionId2);
|
|
75
|
-
|
|
76
|
-
expect(token1).not.toBe(token2);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should handle crypto.getRandomValues errors', async () => {
|
|
80
|
-
mockCrypto.getRandomValues.mockImplementation(() => {
|
|
81
|
-
throw new Error('Crypto unavailable');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
await expect(generateCSRFToken(testSessionId)).rejects.toThrow('CSRF token generation failed');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('validateCSRFToken', () => {
|
|
89
|
-
it('should validate a valid token for correct session', async () => {
|
|
90
|
-
const token = await generateCSRFToken(testSessionId);
|
|
91
|
-
const isValid = await validateCSRFToken(token, testSessionId);
|
|
92
|
-
|
|
93
|
-
expect(isValid).toBe(true);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should reject token for wrong session', async () => {
|
|
97
|
-
const token = await generateCSRFToken(testSessionId);
|
|
98
|
-
const isValid = await validateCSRFToken(token, testSessionId2);
|
|
99
|
-
|
|
100
|
-
expect(isValid).toBe(false);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should reject invalid token format', async () => {
|
|
104
|
-
const invalidToken = 'invalid-token';
|
|
105
|
-
const isValid = await validateCSRFToken(invalidToken, testSessionId);
|
|
106
|
-
|
|
107
|
-
expect(isValid).toBe(false);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should reject reused tokens (one-time use)', async () => {
|
|
111
|
-
const token = await generateCSRFToken(testSessionId);
|
|
112
|
-
|
|
113
|
-
// First use should succeed
|
|
114
|
-
const firstUse = await validateCSRFToken(token, testSessionId);
|
|
115
|
-
expect(firstUse).toBe(true);
|
|
116
|
-
|
|
117
|
-
// Second use should fail
|
|
118
|
-
const secondUse = await validateCSRFToken(token, testSessionId);
|
|
119
|
-
expect(secondUse).toBe(false);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should handle empty or null tokens', async () => {
|
|
123
|
-
expect(await validateCSRFToken('', testSessionId)).toBe(false);
|
|
124
|
-
expect(await validateCSRFToken(null as any, testSessionId)).toBe(false);
|
|
125
|
-
expect(await validateCSRFToken(undefined as any, testSessionId)).toBe(false);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('getCSRFToken', () => {
|
|
130
|
-
it('should return existing valid token for session', async () => {
|
|
131
|
-
const generatedToken = await generateCSRFToken(testSessionId);
|
|
132
|
-
const retrievedToken = await getCSRFToken(testSessionId);
|
|
133
|
-
|
|
134
|
-
expect(retrievedToken).toBeDefined();
|
|
135
|
-
expect(typeof retrievedToken).toBe('string');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should generate new token if none exists', async () => {
|
|
139
|
-
const token = await getCSRFToken(testSessionId);
|
|
140
|
-
|
|
141
|
-
expect(token).toBeDefined();
|
|
142
|
-
expect(typeof token).toBe('string');
|
|
143
|
-
expect(token!.length).toBe(64);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should return different tokens for different sessions', async () => {
|
|
147
|
-
const token1 = await getCSRFToken(testSessionId);
|
|
148
|
-
const token2 = await getCSRFToken(testSessionId2);
|
|
149
|
-
|
|
150
|
-
expect(token1).not.toBe(token2);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('csrfManager', () => {
|
|
155
|
-
it('should manage multiple sessions', async () => {
|
|
156
|
-
const token1 = await csrfManager.generateToken(testSessionId);
|
|
157
|
-
const token2 = await csrfManager.generateToken(testSessionId2);
|
|
158
|
-
|
|
159
|
-
expect(await csrfManager.validateToken(token1, testSessionId)).toBe(true);
|
|
160
|
-
expect(await csrfManager.validateToken(token2, testSessionId2)).toBe(true);
|
|
161
|
-
|
|
162
|
-
// Cross-session validation should fail
|
|
163
|
-
expect(await csrfManager.validateToken(token1, testSessionId2)).toBe(false);
|
|
164
|
-
expect(await csrfManager.validateToken(token2, testSessionId)).toBe(false);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should clear session tokens', async () => {
|
|
168
|
-
const token = await csrfManager.generateToken(testSessionId);
|
|
169
|
-
|
|
170
|
-
// Token should be valid
|
|
171
|
-
expect(await csrfManager.validateToken(token, testSessionId)).toBe(true);
|
|
172
|
-
|
|
173
|
-
// Clear session
|
|
174
|
-
await csrfManager.clearSession(testSessionId);
|
|
175
|
-
|
|
176
|
-
// Token should no longer be valid
|
|
177
|
-
expect(await csrfManager.validateToken(token, testSessionId)).toBe(false);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should handle getCurrentToken for existing session', async () => {
|
|
181
|
-
await csrfManager.generateToken(testSessionId);
|
|
182
|
-
const currentToken = await csrfManager.getCurrentToken(testSessionId);
|
|
183
|
-
|
|
184
|
-
expect(currentToken).toBeDefined();
|
|
185
|
-
expect(typeof currentToken).toBe('string');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should handle getCurrentToken for new session', async () => {
|
|
189
|
-
const currentToken = await csrfManager.getCurrentToken('new-session-789');
|
|
190
|
-
|
|
191
|
-
expect(currentToken).toBeDefined();
|
|
192
|
-
expect(typeof currentToken).toBe('string');
|
|
193
|
-
expect(currentToken!.length).toBe(64);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('Token Lifecycle and Expiry', () => {
|
|
198
|
-
it('should handle token expiry', async () => {
|
|
199
|
-
// Mock Date.now to simulate time passage
|
|
200
|
-
const originalNow = Date.now;
|
|
201
|
-
const mockNow = vi.fn();
|
|
202
|
-
Date.now = mockNow;
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const initialTime = 1000000;
|
|
206
|
-
mockNow.mockReturnValue(initialTime);
|
|
207
|
-
|
|
208
|
-
const token = await generateCSRFToken(testSessionId);
|
|
209
|
-
|
|
210
|
-
// Token should be valid immediately
|
|
211
|
-
expect(await validateCSRFToken(token, testSessionId)).toBe(true);
|
|
212
|
-
|
|
213
|
-
// Regenerate token since it was consumed
|
|
214
|
-
const token2 = await generateCSRFToken(testSessionId);
|
|
215
|
-
|
|
216
|
-
// Simulate time passage beyond expiry (30 minutes + 1 second)
|
|
217
|
-
mockNow.mockReturnValue(initialTime + (30 * 60 * 1000) + 1000);
|
|
218
|
-
|
|
219
|
-
// Token should now be expired
|
|
220
|
-
expect(await validateCSRFToken(token2, testSessionId)).toBe(false);
|
|
221
|
-
} finally {
|
|
222
|
-
Date.now = originalNow;
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe('Security Features', () => {
|
|
228
|
-
it('should limit tokens per session', async () => {
|
|
229
|
-
// Generate maximum tokens for session
|
|
230
|
-
const tokens: string[] = [];
|
|
231
|
-
for (let i = 0; i < 15; i++) { // More than MAX_TOKENS_PER_SESSION (10)
|
|
232
|
-
const token = await generateCSRFToken(testSessionId);
|
|
233
|
-
tokens.push(token);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
expect(tokens.length).toBe(15);
|
|
237
|
-
expect(new Set(tokens).size).toBe(15); // All should be unique
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should use cryptographically secure random values', async () => {
|
|
241
|
-
const calledValues: Uint8Array[] = [];
|
|
242
|
-
mockCrypto.getRandomValues.mockImplementation((array) => {
|
|
243
|
-
calledValues.push(new Uint8Array(array));
|
|
244
|
-
for (let i = 0; i < array.length; i++) {
|
|
245
|
-
array[i] = i % 256; // Deterministic but different values
|
|
246
|
-
}
|
|
247
|
-
return array;
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
await generateCSRFToken(testSessionId);
|
|
251
|
-
|
|
252
|
-
expect(mockCrypto.getRandomValues).toHaveBeenCalledTimes(1);
|
|
253
|
-
expect(calledValues[0].length).toBe(32); // 32 bytes
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe('Error Handling', () => {
|
|
258
|
-
it('should handle storage failures gracefully', async () => {
|
|
259
|
-
// Mock storage to fail
|
|
260
|
-
const { secureStorage } = await import('../../utils/secureStorage');
|
|
261
|
-
vi.mocked(secureStorage.setItem).mockRejectedValue(new Error('Storage failed'));
|
|
262
|
-
|
|
263
|
-
// Should still generate token despite storage failure
|
|
264
|
-
const token = await generateCSRFToken(testSessionId);
|
|
265
|
-
expect(token).toBeDefined();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should handle storage retrieval failures', async () => {
|
|
269
|
-
const { secureStorage } = await import('../../utils/secureStorage');
|
|
270
|
-
vi.mocked(secureStorage.getItem).mockRejectedValue(new Error('Retrieval failed'));
|
|
271
|
-
|
|
272
|
-
// Should still work by creating new tokens
|
|
273
|
-
const token = await getCSRFToken(testSessionId);
|
|
274
|
-
expect(token).toBeDefined();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('should handle malformed stored data', async () => {
|
|
278
|
-
const { secureStorage } = await import('../../utils/secureStorage');
|
|
279
|
-
vi.mocked(secureStorage.getItem).mockResolvedValue('invalid-json');
|
|
280
|
-
|
|
281
|
-
// Should clear cache and continue working
|
|
282
|
-
const token = await getCSRFToken(testSessionId);
|
|
283
|
-
expect(token).toBeDefined();
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
describe('Type Safety', () => {
|
|
288
|
-
it('should have correct CSRFTokenData interface', () => {
|
|
289
|
-
const tokenData: CSRFTokenData = {
|
|
290
|
-
token: 'test-token',
|
|
291
|
-
sessionId: 'test-session',
|
|
292
|
-
timestamp: Date.now(),
|
|
293
|
-
used: false,
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
expect(tokenData.token).toBe('test-token');
|
|
297
|
-
expect(tokenData.sessionId).toBe('test-session');
|
|
298
|
-
expect(typeof tokenData.timestamp).toBe('number');
|
|
299
|
-
expect(typeof tokenData.used).toBe('boolean');
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
describe('Edge Cases', () => {
|
|
304
|
-
it('should handle very long session IDs', async () => {
|
|
305
|
-
const longSessionId = 'a'.repeat(500);
|
|
306
|
-
const token = await generateCSRFToken(longSessionId);
|
|
307
|
-
|
|
308
|
-
expect(token).toBeDefined();
|
|
309
|
-
expect(token.length).toBe(64);
|
|
310
|
-
|
|
311
|
-
// Should be valid for that session
|
|
312
|
-
const isValid = await validateCSRFToken(token, longSessionId);
|
|
313
|
-
expect(isValid).toBe(true);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('should handle rapid token generation', async () => {
|
|
317
|
-
const tokens = await Promise.all(
|
|
318
|
-
Array.from({ length: 20 }, () => generateCSRFToken(testSessionId))
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
expect(tokens).toHaveLength(20);
|
|
322
|
-
// All should be unique 32-byte tokens
|
|
323
|
-
const uniqueTokens = new Set(tokens);
|
|
324
|
-
expect(uniqueTokens.size).toBe(20);
|
|
325
|
-
|
|
326
|
-
// Last token should be valid
|
|
327
|
-
const lastToken = tokens[tokens.length - 1];
|
|
328
|
-
expect(await validateCSRFToken(lastToken, testSessionId)).toBe(true);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('should handle storage corruption by clearing cache', async () => {
|
|
332
|
-
const { secureStorage } = await import('../../utils/secureStorage');
|
|
333
|
-
|
|
334
|
-
// Simulate malformed storage data
|
|
335
|
-
vi.mocked(secureStorage.getItem).mockResolvedValueOnce('{{invalid json}');
|
|
336
|
-
|
|
337
|
-
// Should handle gracefully and generate new token
|
|
338
|
-
const token = await getCSRFToken(testSessionId);
|
|
339
|
-
expect(token).toBeDefined();
|
|
340
|
-
expect(typeof token).toBe('string');
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it('should handle concurrent token generation safely', async () => {
|
|
344
|
-
const promises = Array.from({ length: 10 }, (_, i) =>
|
|
345
|
-
generateCSRFToken(`session-${i}`)
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
const tokens = await Promise.all(promises);
|
|
349
|
-
|
|
350
|
-
expect(tokens).toHaveLength(10);
|
|
351
|
-
expect(new Set(tokens).size).toBe(10);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('should handle session ID collision edge cases', async () => {
|
|
355
|
-
const sameSession = 'collision-session';
|
|
356
|
-
|
|
357
|
-
const token1 = await generateCSRFToken(sameSession);
|
|
358
|
-
const token2 = await generateCSRFToken(sameSession);
|
|
359
|
-
|
|
360
|
-
// Both should be valid for same session
|
|
361
|
-
expect(await validateCSRFToken(token1, sameSession)).toBe(true);
|
|
362
|
-
expect(await validateCSRFToken(token2, sameSession)).toBe(true);
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
});
|