@jmruthers/pace-core 0.6.9 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/audit-tool/00-dependencies.cjs +46 -13
- package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
- package/audit-tool/audits/02-project-structure.cjs +74 -2
- package/audit-tool/audits/03-architecture.cjs +220 -20
- package/audit-tool/audits/04-code-quality.cjs +95 -3
- package/audit-tool/audits/05-styling.cjs +19 -7
- package/audit-tool/audits/06-security-rbac.cjs +214 -25
- package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
- package/audit-tool/audits/08-testing-documentation.cjs +11 -3
- package/audit-tool/audits/09-operations.cjs +19 -7
- package/audit-tool/index.cjs +22 -11
- package/audit-tool/utils/report-utils.cjs +4 -0
- package/cursor-rules/01-pace-core-compliance.mdc +1 -0
- package/cursor-rules/02-project-structure.mdc +3 -26
- package/cursor-rules/03-architecture.mdc +3 -1
- package/cursor-rules/04-code-quality.mdc +1 -0
- package/cursor-rules/05-styling.mdc +120 -8
- package/cursor-rules/06-security-rbac.mdc +126 -2
- package/cursor-rules/07-api-tech-stack.mdc +1 -0
- package/cursor-rules/08-testing-documentation.mdc +1 -0
- package/cursor-rules/09-operations.mdc +1 -0
- package/dist/DataTable-EFYP2QLE.js +16 -0
- package/dist/InactivityServiceProvider-BbxwwDz1.d.ts +308 -0
- package/dist/UnifiedAuthProvider-Bkt_tzdS.d.ts +183 -0
- package/dist/api-BZR2CYXL.js +5 -0
- package/dist/api-result-USV1Czr-.d.ts +51 -0
- package/dist/assets/app-icons/admin_favicon.svg +462 -0
- package/dist/assets/app-icons/base_favicon.svg +85 -0
- package/dist/assets/app-icons/cake_favicon.svg +68 -0
- package/dist/assets/app-icons/core_favicon.svg +256 -0
- package/dist/assets/app-icons/gear_favicon.svg +91 -0
- package/dist/assets/app-icons/medi_favicon.svg +92 -0
- package/dist/assets/app-icons/mint_favicon.svg +83 -0
- package/dist/assets/app-icons/pace_favicon.svg +49 -0
- package/dist/assets/app-icons/pump_favicon.svg +68 -0
- package/dist/assets/app-icons/seed_favicon.svg +91 -0
- package/dist/assets/app-icons/team_favicon.svg +67 -0
- package/dist/assets/app-icons/trac_favicon.svg +112 -0
- package/dist/assets/app-icons/trip_favicon.svg +102 -0
- package/dist/audit-HI2DHUVU.js +4 -0
- package/dist/auth-JvdRVaud.d.ts +49 -0
- package/dist/chunk-2DL2WSOE.js +327 -0
- package/dist/chunk-2OEVOGGR.js +9598 -0
- package/dist/chunk-44CNXN4P.js +15 -0
- package/dist/chunk-4R3T5ENU.js +2943 -0
- package/dist/chunk-7A6IMHH2.js +2321 -0
- package/dist/chunk-BTHN5MKC.js +121 -0
- package/dist/chunk-CU2BU2MQ.js +2 -0
- package/dist/chunk-D6BMFMQZ.js +200 -0
- package/dist/chunk-DDMPHZ3D.js +58 -0
- package/dist/chunk-ENLXB7GP.js +721 -0
- package/dist/chunk-J2KQK6DG.js +2159 -0
- package/dist/chunk-KJXRL3XE.js +6434 -0
- package/dist/chunk-L5LFKKLJ.js +61 -0
- package/dist/chunk-PCSHBLPB.js +811 -0
- package/dist/chunk-QRYSEPHB.js +429 -0
- package/dist/chunk-RMLY6KB5.js +187 -0
- package/dist/chunk-SACF5YSM.js +31 -0
- package/dist/chunk-UZNAFKGW.js +125 -0
- package/dist/chunk-V7FTM2LU.js +1080 -0
- package/dist/chunk-WY6Y7KC3.js +264 -0
- package/dist/chunk-XOJME5T7.js +407 -0
- package/dist/chunk-XPFVT3GN.js +492 -0
- package/dist/chunk-YFTFFJIV.js +529 -0
- package/dist/chunk-YYTWKVHO.js +1334 -0
- package/dist/components.d.ts +12 -89
- package/dist/components.js +23 -55
- package/dist/database.generated-qkdoiVrJ.d.ts +9441 -0
- package/dist/eslint-rules/index.cjs +3 -0
- package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
- package/dist/eslint-rules/rules/05-styling.cjs +507 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +84 -0
- package/dist/event-BfCox3N2.d.ts +265 -0
- package/dist/file-reference-DU1hcawx.d.ts +164 -0
- package/dist/functions-DH45k8ec.d.ts +208 -0
- package/dist/hooks.d.ts +28 -14
- package/dist/hooks.js +90 -56
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +392 -155
- package/dist/index.js +337 -347
- package/dist/pagination-BW1mqywp.d.ts +201 -0
- package/dist/papaparseLoader-WG2UXQ22.js +7 -0
- package/dist/providers.d.ts +29 -14
- package/dist/providers.js +7 -5
- package/dist/rbac/eslint-rules.js +2 -2
- package/dist/rbac/index.d.ts +180 -351
- package/dist/rbac/index.js +13 -11
- package/dist/theming/runtime.d.ts +28 -5
- package/dist/theming/runtime.js +2 -2
- package/dist/timezone-BTWWXKVY.d.ts +696 -0
- package/dist/types-BE2sEHKd.d.ts +55 -0
- package/dist/types-CvOPXWWZ.d.ts +111 -0
- package/dist/types-Dr8sNhER.d.ts +50 -0
- package/dist/types.d.ts +20 -13
- package/dist/types.js +1 -0
- package/dist/usePublicPageContext-B91dGYW1.d.ts +4367 -0
- package/dist/usePublicRouteParams-BgV6VhMi.d.ts +946 -0
- package/dist/utils.d.ts +338 -156
- package/dist/utils.js +78 -60
- package/dist/validation-g5n0hDkh.d.ts +177 -0
- package/docs/api/modules.md +1226 -1094
- package/docs/api-reference/components.md +5 -5
- package/docs/api-reference/rpc-functions.md +12 -3
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/getting-started/cursor-rules.md +17 -20
- package/docs/getting-started/dependencies.md +1 -1
- package/docs/getting-started/setup.md +235 -0
- package/docs/implementation-guides/authentication.md +27 -0
- package/docs/implementation-guides/data-tables.md +365 -10
- package/docs/migration/ApiResult-migration.md +25 -0
- package/docs/rbac/RBAC_CONTRACT.md +0 -12
- package/docs/rbac/api-reference.md +33 -31
- package/docs/standards/0-standards-overview.md +50 -15
- package/docs/standards/1-pace-core-compliance-standards.md +62 -57
- package/docs/standards/2-project-structure-standards.md +45 -90
- package/docs/standards/3-architecture-standards.md +41 -1
- package/docs/standards/4-code-quality-standards.md +26 -6
- package/docs/standards/5-styling-standards.md +35 -1
- package/docs/standards/6-security-rbac-standards.md +288 -7
- package/docs/standards/7-api-tech-stack-standards.md +116 -17
- package/docs/standards/8-testing-documentation-standards.md +31 -0
- package/docs/standards/9-operations-standards.md +19 -0
- package/docs/standards/README.md +20 -201
- package/docs/testing/README.md +10 -0
- package/docs/testing/test-setup-for-consumers.md +916 -0
- package/docs/troubleshooting/common-issues.md +17 -1
- package/docs/troubleshooting/organisation-context-setup.md +8 -0
- package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
- package/eslint-config-pace-core.cjs +24 -0
- package/package.json +14 -20
- package/scripts/build-docs.js +180 -0
- package/scripts/setup.cjs +536 -0
- package/scripts/validate.cjs +480 -0
- package/src/__mocks__/lucide-react.ts +0 -2
- package/src/__tests__/helpers/component-test-utils.test.tsx +260 -0
- package/src/__tests__/helpers/optimized-test-setup.test.ts +224 -0
- package/src/__tests__/helpers/supabaseMock.test.ts +273 -0
- package/src/__tests__/helpers/test-providers.test.tsx +99 -0
- package/src/__tests__/helpers/test-providers.tsx +37 -39
- package/src/__tests__/helpers/test-utils.test.tsx +447 -0
- package/src/__tests__/helpers/timer-utils.test.ts +371 -0
- package/src/assets/app-icons/admin_favicon.svg +462 -0
- package/src/assets/app-icons/base_favicon.svg +85 -0
- package/src/assets/app-icons/cake_favicon.svg +68 -0
- package/src/assets/app-icons/core_favicon.svg +256 -0
- package/src/assets/app-icons/gear_favicon.svg +91 -0
- package/src/assets/app-icons/index.test.ts +304 -0
- package/src/assets/app-icons/index.ts +83 -0
- package/src/assets/app-icons/medi_favicon.svg +92 -0
- package/src/assets/app-icons/mint_favicon.svg +83 -0
- package/src/assets/app-icons/pace_favicon.svg +49 -0
- package/src/assets/app-icons/pump_favicon.svg +68 -0
- package/src/assets/app-icons/seed_favicon.svg +91 -0
- package/src/assets/app-icons/team_favicon.svg +67 -0
- package/src/assets/app-icons/trac_favicon.svg +112 -0
- package/src/assets/app-icons/trip_favicon.svg +102 -0
- package/src/components/AddressField/AddressField.test.tsx +379 -4
- package/src/components/AddressField/AddressField.tsx +239 -213
- package/src/components/AddressField/types.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +35 -25
- package/src/components/Alert/Alert.tsx +8 -8
- package/src/components/AppSwitcher/AppSwitcher.test.tsx +1250 -0
- package/src/components/AppSwitcher/AppSwitcher.tsx +315 -0
- package/src/components/Avatar/Avatar.test.tsx +11 -1
- package/src/components/Avatar/Avatar.tsx +3 -2
- package/src/components/Badge/Badge.test.tsx +11 -1
- package/src/components/Button/Button.test.tsx +13 -3
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Calendar/Calendar.test.tsx +523 -131
- package/src/components/Calendar/Calendar.tsx +107 -488
- package/src/components/Card/Card.test.tsx +384 -258
- package/src/components/Card/Card.tsx +19 -10
- package/src/components/Checkbox/Checkbox.test.tsx +58 -174
- package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
- package/src/components/ContextSelector/ContextSelector.test.tsx +360 -0
- package/src/components/ContextSelector/ContextSelector.tsx +66 -280
- package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
- package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
- package/src/components/DataTable/AUDIT_REPORT.md +59 -44
- package/src/components/DataTable/DataTable.comprehensive.test.tsx +759 -0
- package/src/components/DataTable/DataTable.default-state.test.tsx +524 -0
- package/src/components/DataTable/DataTable.export.test.tsx +705 -0
- package/src/components/DataTable/DataTable.grouping-aggregation.test.tsx +658 -0
- package/src/components/DataTable/DataTable.hooks.test.tsx +192 -0
- package/src/components/DataTable/DataTable.select-label-display.test.tsx +485 -0
- package/src/components/DataTable/DataTable.test.tsx +787 -416
- package/src/components/DataTable/DataTable.tsx +14 -14
- package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
- package/src/components/DataTable/DataTableCore.test-setup.ts +221 -0
- package/src/components/DataTable/DataTableCore.test.tsx +970 -0
- package/src/components/DataTable/README.md +155 -0
- package/src/components/DataTable/TESTING.md +101 -0
- package/src/components/DataTable/a11y.basic.test.tsx +788 -0
- package/src/components/DataTable/components/DataTableCore.tsx +126 -894
- package/src/components/DataTable/components/GroupingDropdown.test.tsx +621 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +2 -3
- package/src/components/DataTable/components/ImportModal.tsx +82 -408
- package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
- package/src/components/DataTable/context/DataTableContext.test.tsx +328 -0
- package/src/components/DataTable/context/DataTableContext.tsx +13 -13
- package/src/components/DataTable/core/ColumnFactory.test.ts +403 -0
- package/src/components/DataTable/core/ColumnFactory.ts +3 -3
- package/src/components/DataTable/hooks/useColumnOrderPersistence.test.ts +516 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +12 -9
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.test.ts +256 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +12 -9
- package/src/components/DataTable/hooks/useDataTableConfiguration.test.ts +297 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +15 -3
- package/src/components/DataTable/hooks/useDataTableDataPipeline.test.ts +270 -0
- package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
- package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
- package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
- package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
- package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
- package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
- package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
- package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.test.ts +280 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +81 -260
- package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
- package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
- package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
- package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
- package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
- package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
- package/src/components/DataTable/hooks/useDataTableState.test.ts +733 -0
- package/src/components/DataTable/hooks/useDataTableState.ts +161 -114
- package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
- package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
- package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
- package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
- package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
- package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.test.ts +183 -0
- package/src/components/DataTable/hooks/useHierarchicalState.test.ts +294 -0
- package/src/components/DataTable/hooks/useImportModalFocus.test.ts +184 -0
- package/src/components/DataTable/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
- package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.test.ts +787 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +311 -271
- package/src/components/DataTable/hooks/usePermissionTracking.test.ts +381 -0
- package/src/components/DataTable/hooks/usePermissionTracking.ts +122 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.test.ts +258 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +27 -4
- package/src/components/DataTable/hooks/useTableColumns.test.ts +499 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +15 -39
- package/src/components/DataTable/hooks/useTableHandlers.test.ts +461 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +13 -22
- package/src/components/DataTable/index.ts +28 -5
- package/src/components/DataTable/keyboard.test.tsx +734 -0
- package/src/components/DataTable/mocks/MockRBACProvider.tsx +66 -0
- package/src/components/DataTable/pagination.modes.test.tsx +728 -0
- package/src/components/DataTable/ssr.strict-mode.test.tsx +319 -0
- package/src/components/DataTable/styles.test.ts +379 -0
- package/src/components/DataTable/styles.ts +0 -1
- package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
- package/src/components/DataTable/test-utils/dataFactories.ts +103 -0
- package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
- package/src/components/DataTable/test-utils/sharedTestUtils.ts +419 -0
- package/src/components/DataTable/test-utils.ts +94 -0
- package/src/components/DataTable/types/actions.ts +71 -0
- package/src/components/DataTable/types/base.ts +39 -0
- package/src/components/DataTable/types/columns.ts +125 -0
- package/src/components/DataTable/types/export.ts +32 -0
- package/src/components/DataTable/types/features.ts +81 -0
- package/src/components/DataTable/types/hierarchical.ts +44 -0
- package/src/components/DataTable/types/index.ts +43 -0
- package/src/components/DataTable/types/pagination.ts +85 -0
- package/src/components/DataTable/types/performance.ts +47 -0
- package/src/components/DataTable/types/props.ts +62 -0
- package/src/components/DataTable/types/rbac.ts +45 -0
- package/src/components/DataTable/ui/layout/DataTableCore.test.tsx +1194 -0
- package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
- package/src/components/DataTable/ui/layout/DataTableErrorBoundary.test.tsx +438 -0
- package/src/components/DataTable/ui/layout/DataTableErrorBoundary.tsx +225 -0
- package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
- package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
- package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
- package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
- package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
- package/src/components/DataTable/ui/modals/DataTableModals.tsx +341 -0
- package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
- package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
- package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
- package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
- package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
- package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
- package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
- package/src/components/DataTable/ui/shared/AccessDeniedPage.test.tsx +245 -0
- package/src/components/DataTable/ui/shared/AccessDeniedPage.tsx +159 -0
- package/src/components/DataTable/ui/shared/ActionButtons.test.tsx +921 -0
- package/src/components/DataTable/ui/shared/ActionButtons.tsx +195 -0
- package/src/components/DataTable/ui/shared/ColumnFilter.test.tsx +497 -0
- package/src/components/DataTable/ui/shared/ColumnFilter.tsx +113 -0
- package/src/components/DataTable/ui/shared/PaginationControls.test.tsx +451 -0
- package/src/components/DataTable/ui/shared/PaginationControls.tsx +291 -0
- package/src/components/DataTable/ui/shared/SortIndicator.test.tsx +135 -0
- package/src/components/DataTable/ui/shared/SortIndicator.tsx +50 -0
- package/src/components/DataTable/ui/table/EditFields.test.tsx +526 -0
- package/src/components/DataTable/ui/table/EditFields.tsx +355 -0
- package/src/components/DataTable/ui/table/EditableRow.test.tsx +1003 -0
- package/src/components/DataTable/ui/table/EditableRow.tsx +444 -0
- package/src/components/DataTable/ui/table/EmptyState.test.tsx +360 -0
- package/src/components/DataTable/ui/table/EmptyState.tsx +74 -0
- package/src/components/DataTable/ui/table/FilterRow.test.tsx +416 -0
- package/src/components/DataTable/ui/table/FilterRow.tsx +148 -0
- package/src/components/DataTable/ui/table/LoadingState.test.tsx +77 -0
- package/src/components/DataTable/ui/table/LoadingState.tsx +17 -0
- package/src/components/DataTable/ui/table/RowComponent.test.tsx +1024 -0
- package/src/components/DataTable/ui/table/RowComponent.tsx +429 -0
- package/src/components/DataTable/ui/table/UnifiedTableBody.test.tsx +1273 -0
- package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
- package/src/components/DataTable/ui/table/cellValueUtils.test.ts +453 -0
- package/src/components/DataTable/ui/table/cellValueUtils.ts +40 -0
- package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.test.tsx +551 -0
- package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.tsx +160 -0
- package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.test.tsx +751 -0
- package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.tsx +114 -0
- package/src/components/DataTable/ui/toolbar/DataTableToolbar.test.tsx +629 -0
- package/src/components/DataTable/ui/toolbar/DataTableToolbar.tsx +271 -0
- package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
- package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
- package/src/components/DataTable/utils/a11yUtils.test.ts +548 -0
- package/src/components/DataTable/utils/a11yUtils.ts +1 -1
- package/src/components/DataTable/utils/aggregationUtils.test.ts +288 -0
- package/src/components/DataTable/utils/aggregationUtils.ts +5 -5
- package/src/components/DataTable/utils/columnUtils.test.ts +94 -0
- package/src/components/DataTable/utils/csvParse.test.ts +74 -0
- package/src/components/DataTable/utils/csvParse.ts +65 -0
- package/src/components/DataTable/utils/errorHandling.test.ts +209 -0
- package/src/components/DataTable/utils/errorHandling.ts +3 -1
- package/src/components/DataTable/utils/exportUtils.test.ts +954 -0
- package/src/components/DataTable/utils/exportUtils.ts +1 -1
- package/src/components/DataTable/utils/flexibleImport.test.ts +573 -0
- package/src/components/DataTable/utils/flexibleImport.ts +3 -186
- package/src/components/DataTable/utils/hierarchicalSorting.test.ts +235 -0
- package/src/components/DataTable/utils/hierarchicalSorting.ts +3 -3
- package/src/components/DataTable/utils/hierarchicalUtils.test.ts +586 -0
- package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
- package/src/components/DataTable/utils/importDateParser.ts +114 -0
- package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
- package/src/components/DataTable/utils/importValueParser.ts +91 -0
- package/src/components/DataTable/utils/paginationUtils.test.ts +593 -0
- package/src/components/DataTable/utils/paginationUtils.ts +7 -4
- package/src/components/DataTable/utils/performanceUtils.test.ts +470 -0
- package/src/components/DataTable/utils/performanceUtils.ts +1 -1
- package/src/components/DataTable/utils/rowUtils.test.ts +235 -0
- package/src/components/DataTable/utils/selectFieldUtils.test.ts +271 -0
- package/src/components/DataTable/utils/selectFieldUtils.ts +97 -67
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +18 -25
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +1 -1
- package/src/components/DateTimeField/DateTimeField.test.tsx +3 -16
- package/src/components/DateTimeField/DateTimeField.tsx +1 -1
- package/src/components/Dialog/Dialog.test-utils.ts +49 -0
- package/src/components/Dialog/Dialog.test.tsx +2865 -458
- package/src/components/Dialog/Dialog.tsx +183 -986
- package/src/components/Dialog/dialogLock.test.ts +238 -0
- package/src/components/Dialog/dialogLock.ts +98 -0
- package/src/components/Dialog/index.ts +2 -0
- package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
- package/src/components/Dialog/useDialogDimensions.ts +140 -0
- package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
- package/src/components/Dialog/useDialogLifecycle.ts +135 -0
- package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
- package/src/components/Dialog/useDialogPersistence.ts +357 -0
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +2 -62
- package/src/components/ErrorBoundary/ErrorBoundaryContext.context.ts +17 -0
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +2 -45
- package/src/components/ErrorBoundary/ErrorBoundaryContext.types.ts +41 -0
- package/src/components/ErrorBoundary/index.ts +3 -4
- package/src/components/ErrorBoundary/useErrorBoundaryContext.ts +20 -0
- package/src/components/FileDisplay/FileDisplay.test.tsx +479 -247
- package/src/components/FileDisplay/FileDisplay.tsx +29 -659
- package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
- package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
- package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
- package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
- package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
- package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
- package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
- package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
- package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
- package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
- package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
- package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
- package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
- package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
- package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
- package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
- package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
- package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
- package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
- package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
- package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
- package/src/components/FileDisplay/fallbackUtils.ts +44 -0
- package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
- package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
- package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
- package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
- package/src/components/FileDisplay/index.tsx +1 -1
- package/src/components/FileDisplay/useFileDisplay.test.ts +538 -0
- package/src/components/FileDisplay/useFileDisplay.ts +515 -0
- package/src/components/FileDisplay/useFileDisplay.unit.test.ts +1438 -0
- package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
- package/src/components/FileDisplay/usePublicFileDisplay.test.ts +729 -0
- package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
- package/src/components/FileUpload/FileUpload.test.tsx +69 -27
- package/src/components/FileUpload/FileUpload.tsx +112 -527
- package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
- package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
- package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
- package/src/components/FileUpload/index.tsx +1 -1
- package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
- package/src/components/FileUpload/useFileUploadManager.ts +454 -0
- package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
- package/src/components/FileUpload/useResolvedAppId.ts +77 -0
- package/src/components/Footer/Footer.test.tsx +15 -382
- package/src/components/Footer/Footer.tsx +8 -125
- package/src/components/Form/Form.test.tsx +425 -88
- package/src/components/Form/Form.tsx +91 -299
- package/src/components/Form/useFormPersistence.ts +257 -0
- package/src/components/Header/Header.test.tsx +653 -163
- package/src/components/Header/Header.tsx +62 -44
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +35 -76
- package/src/components/Input/Input.test.tsx +34 -120
- package/src/components/Input/Input.tsx +1 -1
- package/src/components/Label/Label.test.tsx +46 -45
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +8 -11
- package/src/components/LoginForm/LoginForm.test.tsx +0 -1
- package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +2422 -102
- package/src/components/NavigationMenu/NavigationMenu.tsx +62 -362
- package/src/components/NavigationMenu/index.ts +6 -1
- package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
- package/src/components/NavigationMenu/useNavigationFiltering.test.ts +1949 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +199 -308
- package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
- package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +1322 -0
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +50 -49
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +81 -38
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +103 -85
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +774 -44
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +282 -764
- package/src/components/PaceAppLayout/README.md +0 -9
- package/src/components/PaceAppLayout/test-setup.tsx +15 -9
- package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
- package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
- package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
- package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +782 -20
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +33 -125
- package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +1 -1
- package/src/components/Progress/Progress.test.tsx +127 -1
- package/src/components/Progress/Progress.tsx +1 -2
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +1196 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -217
- package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
- package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
- package/src/components/PublicLayout/PublicLayout.test.tsx +1640 -38
- package/src/components/PublicLayout/PublicPageContext.ts +28 -0
- package/src/components/PublicLayout/PublicPageLayout.tsx +134 -75
- package/src/components/PublicLayout/PublicPageProvider.tsx +7 -42
- package/src/components/PublicLayout/usePublicPageContext.ts +36 -0
- package/src/components/Select/Select.test.tsx +45 -8
- package/src/components/Select/Select.tsx +57 -40
- package/src/components/Select/context.test.tsx +56 -0
- package/src/components/Select/text.test.tsx +104 -0
- package/src/components/Select/text.ts +26 -0
- package/src/components/Select/types.ts +3 -0
- package/src/components/Select/useSelectEvents.test.ts +279 -0
- package/src/components/Select/useSelectEvents.ts +87 -0
- package/src/components/Select/useSelectSearch.test.tsx +295 -0
- package/src/components/Select/useSelectSearch.ts +91 -0
- package/src/components/Select/useSelectState.test.ts +268 -0
- package/src/components/Select/useSelectState.ts +104 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.test.tsx +28 -112
- package/src/components/Switch/Switch.test.tsx +57 -153
- package/src/components/Table/Table.test.tsx +395 -317
- package/src/components/Tabs/Tabs.test.tsx +270 -0
- package/src/components/Tabs/Tabs.tsx +4 -4
- package/src/components/Textarea/Textarea.test.tsx +11 -38
- package/src/components/Toast/Toast.test.tsx +425 -496
- package/src/components/Tooltip/Tooltip.test.tsx +4 -21
- package/src/components/UserMenu/UserMenu.test.tsx +1 -21
- package/src/components/UserMenu/UserMenu.tsx +0 -1
- package/src/components/index.test.ts +346 -0
- package/src/components/index.ts +12 -1
- package/src/constants/performance.test.ts +91 -0
- package/src/hooks/ServiceHooks.test.tsx +725 -0
- package/src/hooks/hooks.integration.test.tsx +608 -0
- package/src/hooks/index.ts +18 -3
- package/src/hooks/index.unit.test.ts +220 -0
- package/src/hooks/public/usePublicEvent.test.ts +304 -0
- package/src/hooks/public/usePublicEvent.ts +11 -11
- package/src/hooks/public/usePublicEventLogo.test.ts +655 -120
- package/src/hooks/public/usePublicEventLogo.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.test.ts +595 -0
- package/src/hooks/public/usePublicRouteParams.ts +2 -2
- package/src/hooks/services/useAuth.ts +9 -7
- package/src/hooks/services/useAuthService.ts +1 -1
- package/src/hooks/services/useEventService.ts +1 -1
- package/src/hooks/useAccessibleApps.test.ts +400 -0
- package/src/hooks/useAccessibleApps.ts +264 -0
- package/src/hooks/useAddressAutocomplete.test.ts +170 -47
- package/src/hooks/useAddressAutocomplete.ts +109 -81
- package/src/hooks/useApiFetch.unit.test.ts +111 -0
- package/src/hooks/useAppConfig.ts +13 -3
- package/src/hooks/useAppConfig.unit.test.ts +712 -0
- package/src/hooks/useComponentPerformance.unit.test.tsx +314 -0
- package/src/hooks/useDataTablePerformance.ts +111 -130
- package/src/hooks/useDataTablePerformance.unit.test.ts +720 -0
- package/src/hooks/useDataTableState.test.ts +170 -0
- package/src/hooks/useDataTableState.ts +5 -5
- package/src/hooks/useDebounce.unit.test.ts +157 -0
- package/src/hooks/useEventTheme.test.ts +70 -18
- package/src/hooks/useEventTheme.ts +50 -22
- package/src/hooks/useEvents.ts +49 -2
- package/src/hooks/useEvents.unit.test.ts +227 -0
- package/src/hooks/useFileReference.test.ts +388 -107
- package/src/hooks/useFileReference.ts +184 -179
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useFileUrl.unit.test.ts +686 -0
- package/src/hooks/useFileUrlCache.test.ts +319 -0
- package/src/hooks/useFileUrlCache.ts +5 -2
- package/src/hooks/useFocusManagement.unit.test.ts +604 -0
- package/src/hooks/useFocusTrap.unit.test.tsx +613 -0
- package/src/hooks/useFormDialog.test.ts +307 -0
- package/src/hooks/useFormDialog.ts +2 -2
- package/src/hooks/useInactivityTracker.ts +141 -134
- package/src/hooks/useInactivityTracker.unit.test.ts +446 -0
- package/src/hooks/useIsMobile.unit.test.ts +317 -0
- package/src/hooks/useIsPrint.ts +62 -0
- package/src/hooks/useIsPrint.unit.test.ts +545 -0
- package/src/hooks/useKeyboardShortcuts.unit.test.ts +907 -0
- package/src/hooks/useOrganisationPermissions.test.ts +1 -2
- package/src/hooks/useOrganisationPermissions.ts +1 -4
- package/src/hooks/useOrganisationPermissions.unit.test.tsx +293 -0
- package/src/hooks/useOrganisationSecurity.test.ts +4 -33
- package/src/hooks/useOrganisationSecurity.ts +192 -203
- package/src/hooks/useOrganisationSecurity.unit.test.tsx +959 -0
- package/src/hooks/useOrganisations.ts +1 -1
- package/src/hooks/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/usePerformanceMonitor.ts +1 -1
- package/src/hooks/usePerformanceMonitor.unit.test.ts +693 -0
- package/src/hooks/usePermissionCache.test.ts +298 -329
- package/src/hooks/usePermissionCache.ts +277 -276
- package/src/hooks/usePreventTabReload.test.ts +307 -0
- package/src/hooks/usePublicEvent.simple.test.ts +794 -0
- package/src/hooks/usePublicEvent.test.ts +670 -0
- package/src/hooks/usePublicEvent.unit.test.ts +638 -0
- package/src/hooks/usePublicFileDisplay.test.ts +948 -0
- package/src/hooks/usePublicRouteParams.unit.test.ts +442 -0
- package/src/hooks/useQueryCache.test.ts +391 -0
- package/src/hooks/useQueryCache.ts +7 -9
- package/src/hooks/useRBAC.unit.test.ts +253 -0
- package/src/hooks/useSessionDraft.test.ts +556 -0
- package/src/hooks/useSessionDraft.ts +14 -11
- package/src/hooks/useSessionRestoration.ts +1 -1
- package/src/hooks/useSessionRestoration.unit.test.tsx +381 -0
- package/src/hooks/useStorage.ts +94 -54
- package/src/hooks/useStorage.unit.test.ts +684 -0
- package/src/hooks/useToast.test.ts +413 -0
- package/src/hooks/useToast.ts +2 -2
- package/src/hooks/useToast.unit.test.tsx +481 -0
- package/src/hooks/useZodForm.ts +3 -3
- package/src/hooks/useZodForm.unit.test.tsx +191 -0
- package/src/icons/index.test.ts +133 -0
- package/src/icons/index.ts +3 -1
- package/src/index.test.ts +528 -0
- package/src/index.ts +56 -9
- package/src/providers/AuthProvider.test.tsx +218 -0
- package/src/providers/EventProvider.test.tsx +487 -0
- package/src/providers/InactivityProvider.test-helper.tsx +40 -0
- package/src/providers/InactivityProvider.test.tsx +421 -0
- package/src/providers/ProviderLifecycle.test.tsx +308 -0
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +7 -12
- package/src/providers/UnifiedAuthProvider.test.tsx +503 -0
- package/src/providers/index.test.ts +138 -0
- package/src/providers/services/AuthServiceContext.ts +27 -0
- package/src/providers/services/AuthServiceProvider.integration.test.tsx +229 -0
- package/src/providers/services/AuthServiceProvider.test.tsx +638 -0
- package/src/providers/services/AuthServiceProvider.tsx +81 -20
- package/src/providers/services/EventServiceContext.ts +25 -0
- package/src/providers/services/EventServiceProvider.test.tsx +839 -0
- package/src/providers/services/EventServiceProvider.tsx +11 -20
- package/src/providers/services/InactivityServiceContext.ts +25 -0
- package/src/providers/services/InactivityServiceProvider.test.tsx +662 -0
- package/src/providers/services/InactivityServiceProvider.tsx +7 -17
- package/src/providers/services/OrganisationServiceContext.ts +25 -0
- package/src/providers/services/OrganisationServiceProvider.test.tsx +440 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +7 -17
- package/src/providers/services/UnifiedAuthContext.ts +102 -0
- package/src/providers/services/UnifiedAuthProvider.advanced.test.tsx +434 -0
- package/src/providers/services/UnifiedAuthProvider.appId.test.tsx +408 -0
- package/src/providers/services/UnifiedAuthProvider.integration.test.tsx +304 -0
- package/src/providers/services/UnifiedAuthProvider.test.tsx +212 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +147 -497
- package/src/providers/services/contexts.test.tsx +281 -0
- package/src/providers/services/useUnifiedAuth.test.tsx +251 -0
- package/src/providers/services/useUnifiedAuth.ts +29 -0
- package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
- package/src/providers/useInactivity.test-helper.ts +27 -0
- package/src/rbac/README.md +5 -5
- package/src/rbac/adapters.comprehensive.test.tsx +429 -0
- package/src/rbac/adapters.test.tsx +654 -0
- package/src/rbac/adapters.tsx +53 -38
- package/src/rbac/api.test.ts +986 -259
- package/src/rbac/api.ts +260 -216
- package/src/rbac/audit-batched.test.ts +550 -0
- package/src/rbac/audit-batched.ts +5 -4
- package/src/rbac/audit.test.ts +225 -28
- package/src/rbac/audit.ts +26 -18
- package/src/rbac/auth-rbac-security.integration.test.tsx +300 -0
- package/src/rbac/auth-rbac.e2e.test.tsx +510 -0
- package/src/rbac/cache-invalidation.test.ts +715 -0
- package/src/rbac/cache-invalidation.ts +18 -15
- package/src/rbac/cache.test.ts +123 -63
- package/src/rbac/cache.ts +3 -4
- package/src/rbac/components/AccessDenied.test.tsx +324 -0
- package/src/rbac/components/AccessDenied.tsx +20 -18
- package/src/rbac/components/NavigationGuard.test.tsx +1148 -0
- package/src/rbac/components/NavigationGuard.tsx +10 -8
- package/src/rbac/components/PagePermissionGuard.guard.test.tsx +236 -0
- package/src/rbac/components/PagePermissionGuard.performance.test.tsx +252 -0
- package/src/rbac/components/PagePermissionGuard.race-condition.test.tsx +243 -0
- package/src/rbac/components/PagePermissionGuard.test.tsx +1430 -0
- package/src/rbac/components/PagePermissionGuard.tsx +188 -381
- package/src/rbac/components/PagePermissionGuard.verification.test.tsx +185 -0
- package/src/rbac/config.test.ts +131 -48
- package/src/rbac/config.ts +69 -26
- package/src/rbac/docs/event-based-apps.md +26 -13
- package/src/rbac/engine.comprehensive.test.ts +808 -0
- package/src/rbac/engine.test.ts +974 -130
- package/src/rbac/engine.ts +53 -13
- package/src/rbac/errors.test.ts +99 -87
- package/src/rbac/errors.ts +89 -55
- package/src/rbac/eslint-rules.js +2 -2
- package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
- package/src/rbac/hooks/permissions/useAccessLevel.test.ts +622 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +23 -14
- package/src/rbac/hooks/permissions/useCan.test.ts +798 -0
- package/src/rbac/hooks/permissions/useCan.ts +173 -253
- package/src/rbac/hooks/permissions/useMultiplePermissions.test.ts +843 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +63 -10
- package/src/rbac/hooks/permissions/usePermissions.test.ts +543 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +50 -78
- package/src/rbac/hooks/useCan.test.ts +348 -32
- package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
- package/src/rbac/hooks/usePageGuardScope.ts +117 -0
- package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
- package/src/rbac/hooks/usePermissions.integration.test.ts +427 -0
- package/src/rbac/hooks/usePermissions.stability.test.ts +268 -0
- package/src/rbac/hooks/usePermissions.test.ts +459 -33
- package/src/rbac/hooks/usePermissions.ts +5 -7
- package/src/rbac/hooks/useRBAC.test.ts +1784 -21
- package/src/rbac/hooks/useRBAC.ts +148 -88
- package/src/rbac/hooks/useResolvedScope.test.ts +442 -5
- package/src/rbac/hooks/useResolvedScope.ts +4 -1
- package/src/rbac/hooks/useResourcePermissions.test.ts +561 -24
- package/src/rbac/hooks/useResourcePermissions.ts +76 -140
- package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
- package/src/rbac/hooks/useRoleManagement.test.ts +634 -61
- package/src/rbac/hooks/useRoleManagement.ts +158 -586
- package/src/rbac/hooks/useSecureSupabase.test.ts +1179 -0
- package/src/rbac/hooks/useSecureSupabase.ts +21 -14
- package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
- package/src/rbac/index.test.ts +107 -0
- package/src/rbac/index.ts +32 -32
- package/src/rbac/performance.test.ts +451 -0
- package/src/rbac/permissions.test.ts +149 -68
- package/src/rbac/permissions.ts +0 -3
- package/src/rbac/rbac-core.test.tsx +276 -0
- package/src/rbac/rbac-engine-core-logic.test.ts +387 -0
- package/src/rbac/rbac-engine-simplified.test.ts +252 -0
- package/src/rbac/rbac-functions.test.ts +703 -0
- package/src/rbac/rbac-integration.test.ts +523 -0
- package/src/rbac/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/request-deduplication.test.ts +352 -0
- package/src/rbac/request-deduplication.ts +5 -4
- package/src/rbac/scenarios.user-role.test.tsx +271 -0
- package/src/rbac/secureClient.test.ts +499 -115
- package/src/rbac/secureClient.ts +54 -28
- package/src/rbac/security.test.ts +448 -44
- package/src/rbac/security.ts +7 -6
- package/src/rbac/types/roleManagement.ts +66 -0
- package/src/rbac/types.test.ts +236 -0
- package/src/rbac/types.ts +7 -5
- package/src/rbac/utils/clientSecurity.test.ts +192 -0
- package/src/rbac/utils/clientSecurity.ts +6 -4
- package/src/rbac/utils/contextValidator.test.ts +126 -0
- package/src/rbac/utils/contextValidator.ts +6 -3
- package/src/rbac/utils/deep-equal.test.ts +76 -0
- package/src/rbac/utils/eventContext.test.ts +401 -0
- package/src/rbac/utils/eventContext.ts +38 -34
- package/src/rbac/utils/fetchPermissionMap.ts +13 -0
- package/src/rbac/utils/permissionMapHelpers.ts +34 -0
- package/src/rbac/utils/roleManagementRpc.ts +303 -0
- package/src/services/AuthService.edge-cases.test.ts +746 -0
- package/src/services/AuthService.restoreSession.test.ts +59 -0
- package/src/services/AuthService.test.ts +1362 -0
- package/src/services/AuthService.ts +197 -216
- package/src/services/BaseService.edge-cases.test.ts +506 -0
- package/src/services/BaseService.test.ts +363 -0
- package/src/services/EventService.edge-cases.test.ts +636 -0
- package/src/services/EventService.eventColours.test.ts +64 -0
- package/src/services/EventService.test.ts +1250 -0
- package/src/services/EventService.ts +244 -315
- package/src/services/InactivityService.edge-cases.test.ts +492 -0
- package/src/services/InactivityService.lifecycle.test.ts +406 -0
- package/src/services/InactivityService.test.ts +829 -0
- package/src/services/InactivityService.ts +172 -213
- package/src/services/OrganisationService.edge-cases.test.ts +633 -0
- package/src/services/OrganisationService.pagination.test.ts +409 -0
- package/src/services/OrganisationService.test.ts +1579 -0
- package/src/services/OrganisationService.ts +186 -257
- package/src/services/base/BaseService.test.ts +214 -0
- package/src/services/interfaces/IAuthService.test.ts +184 -0
- package/src/services/interfaces/IAuthService.ts +10 -9
- package/src/services/interfaces/IEventService.test.ts +176 -0
- package/src/services/interfaces/IInactivityService.test.ts +183 -0
- package/src/services/interfaces/IOrganisationService.test.ts +207 -0
- package/src/services/interfaces/IOrganisationService.ts +0 -1
- package/src/styles/core.css +244 -12
- package/src/theming/parseEventColours.test.ts +321 -0
- package/src/theming/parseEventColours.ts +18 -9
- package/src/theming/runtime.test.ts +495 -0
- package/src/theming/runtime.ts +72 -7
- package/src/types/api-result.ts +53 -0
- package/src/types/auth.ts +0 -1
- package/src/types/core.test.ts +397 -0
- package/src/types/database-generated.test.ts +78 -0
- package/src/types/database.generated.ts +45 -10
- package/src/types/event.ts +39 -19
- package/src/types/file-reference.test.ts +351 -0
- package/src/types/file-reference.ts +37 -12
- package/src/types/guards.test.ts +246 -0
- package/src/types/index.test.ts +265 -0
- package/src/types/index.ts +3 -0
- package/src/types/organisation.roles.test.ts +55 -0
- package/src/types/organisation.test.ts +1105 -0
- package/src/types/organisation.ts +15 -15
- package/src/types/rpc-responses.ts +33 -0
- package/src/types/supabase.ts +14 -6
- package/src/types/theme.test.ts +830 -0
- package/src/types/type-validation.test.ts +526 -0
- package/src/types/validation.test.ts +729 -0
- package/src/types/vitest-globals.d.ts +1 -1
- package/src/utils/app/appConfig.test.ts +235 -0
- package/src/utils/app/appIdResolver.test.ts +252 -57
- package/src/utils/app/appIdResolver.ts +31 -20
- package/src/utils/app/appNameResolver.test.ts +18 -10
- package/src/utils/app/appNameResolver.ts +11 -9
- package/src/utils/app/appPortMap.test.ts +125 -0
- package/src/utils/app/appPortMap.ts +51 -0
- package/src/utils/app/buildAppUrl.test.ts +273 -0
- package/src/utils/app/buildAppUrl.ts +114 -0
- package/src/utils/appConfig.unit.test.ts +55 -0
- package/src/utils/audit/audit.test.ts +354 -39
- package/src/utils/audit.unit.test.ts +69 -0
- package/src/utils/auth-utils.unit.test.ts +69 -0
- package/src/utils/bundleAnalysis.unit.test.ts +326 -0
- package/src/utils/cn.unit.test.ts +34 -0
- package/src/utils/context/organisationContext.test.ts +115 -95
- package/src/utils/context/organisationContext.ts +32 -43
- package/src/utils/context/sessionTracking.test.ts +354 -0
- package/src/utils/core/cn.test.ts +66 -0
- package/src/utils/core/debugLogger.test.ts +113 -0
- package/src/utils/core/debugLogger.ts +15 -8
- package/src/utils/core/logger.test.ts +217 -0
- package/src/utils/core/logger.ts +20 -16
- package/src/utils/core/mergeRefs.ts +24 -0
- package/src/utils/debugLogger.test.ts +417 -0
- package/src/utils/device/deviceFingerprint.test.ts +8 -5
- package/src/utils/device/deviceFingerprint.ts +3 -3
- package/src/utils/deviceFingerprint.unit.test.ts +818 -0
- package/src/utils/dynamic/createLazyComponent.tsx +46 -0
- package/src/utils/dynamic/dynamicUtils.test.ts +185 -0
- package/src/utils/dynamic/dynamicUtils.ts +6 -6
- package/src/utils/dynamic/lazyLoad.test.tsx +156 -0
- package/src/utils/dynamic/lazyLoad.tsx +8 -36
- package/src/utils/dynamic/papaparseLoader.ts +7 -0
- package/src/utils/dynamicUtils.unit.test.ts +331 -0
- package/src/utils/file-reference/file-reference.test.ts +1238 -0
- package/src/utils/file-reference/index.ts +330 -348
- package/src/utils/formatDate.unit.test.ts +109 -0
- package/src/utils/formatting/formatDate.test.ts +22 -148
- package/src/utils/formatting/formatDateTime.test.ts +41 -119
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +41 -85
- package/src/utils/formatting/formatNumber.test.ts +259 -0
- package/src/utils/formatting/formatTime.test.ts +36 -128
- package/src/utils/formatting/formatting.ts +1 -1
- package/src/utils/formatting.unit.test.ts +99 -0
- package/src/utils/google-places/googlePlacesUtils.test.ts +127 -36
- package/src/utils/google-places/googlePlacesUtils.ts +67 -86
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +68 -8
- package/src/utils/google-places/loadGoogleMapsScript.ts +140 -118
- package/src/utils/index.ts +52 -11
- package/src/utils/index.unit.test.ts +251 -0
- package/src/utils/lazyLoad.unit.test.tsx +319 -0
- package/src/utils/location/location.test.ts +19 -116
- package/src/utils/logger.unit.test.ts +398 -0
- package/src/utils/organisationContext.unit.test.ts +180 -0
- package/src/utils/performance/bundleAnalysis.test.ts +148 -0
- package/src/utils/performance/bundleAnalysis.ts +16 -22
- package/src/utils/performance/performanceBenchmark.test.ts +251 -0
- package/src/utils/performance/performanceBenchmark.ts +12 -4
- package/src/utils/performance/performanceBudgets.test.ts +241 -0
- package/src/utils/performance/performanceBudgets.ts +9 -6
- package/src/utils/performanceBenchmark.test.ts +174 -0
- package/src/utils/performanceBudgets.unit.test.ts +288 -0
- package/src/utils/permissionTypes.unit.test.ts +250 -0
- package/src/utils/permissionUtils.unit.test.ts +362 -0
- package/src/utils/permissions/permissionTypes.test.ts +149 -0
- package/src/utils/permissions/permissionUtils.test.ts +20 -42
- package/src/utils/persistence/keyDerivation.test.ts +306 -0
- package/src/utils/persistence/sensitiveFieldDetection.test.ts +271 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +2 -2
- package/src/utils/request-deduplication.test.ts +349 -0
- package/src/utils/request-deduplication.ts +6 -4
- package/src/utils/sanitization.unit.test.ts +346 -0
- package/src/utils/schemaUtils.unit.test.ts +441 -0
- package/src/utils/secureDataAccess.unit.test.ts +334 -0
- package/src/utils/secureErrors.unit.test.ts +390 -0
- package/src/utils/secureStorage.unit.test.ts +289 -0
- package/src/utils/security/auth-utils.ts +38 -27
- package/src/utils/security/secureDataAccess.test.ts +22 -191
- package/src/utils/security/secureDataAccess.ts +241 -281
- package/src/utils/security/secureErrors.test.ts +163 -0
- package/src/utils/security/secureStorage.test.ts +156 -0
- package/src/utils/security/secureStorage.ts +1 -1
- package/src/utils/security/security.test.ts +212 -0
- package/src/utils/security/security.ts +15 -18
- package/src/utils/security/securityMonitor.test.ts +90 -0
- package/src/utils/security/securityMonitor.ts +1 -1
- package/src/utils/security.unit.test.ts +155 -0
- package/src/utils/securityMonitor.unit.test.ts +276 -0
- package/src/utils/sessionTracking.unit.test.ts +218 -0
- package/src/utils/storage/config.unit.test.ts +239 -0
- package/src/utils/storage/helpers.test.ts +769 -456
- package/src/utils/storage/helpers.ts +174 -253
- package/src/utils/storage/index.unit.test.ts +68 -0
- package/src/utils/storage/storageUtils.ts +32 -0
- package/src/utils/storage/types.ts +9 -2
- package/src/utils/supabase/createBaseClient.test.ts +201 -0
- package/src/utils/supabase/createBaseClient.ts +2 -1
- package/src/utils/timezone/timezone.test.ts +26 -44
- package/src/utils/timezone.test.ts +345 -0
- package/src/utils/validation/common.test.ts +115 -0
- package/src/utils/validation/csrf.test.ts +198 -0
- package/src/utils/validation/csrf.ts +42 -41
- package/src/utils/validation/htmlSanitization.ts +27 -31
- package/src/utils/validation/htmlSanitization.unit.test.ts +618 -0
- package/src/utils/validation/passwordSchema.test.ts +164 -0
- package/src/utils/validation/schema.test.ts +127 -0
- package/src/utils/validation/schema.ts +6 -3
- package/src/utils/validation/sqlInjectionProtection.test.ts +165 -0
- package/src/utils/validation/sqlInjectionProtection.ts +2 -2
- package/src/utils/validation/user.test.ts +173 -0
- package/src/utils/validation/validation.test.ts +197 -0
- package/src/utils/validation/validationUtils.test.ts +294 -0
- package/src/utils/validation.unit.test.ts +307 -0
- package/src/utils/validationUtils.unit.test.ts +558 -0
- package/src/vite-env.d.ts +6 -0
- package/dist/AuthService-DmfO5rGS.d.ts +0 -524
- package/dist/DataTable-DRUIgtUH.d.ts +0 -166
- package/dist/DataTable-SOAFXIWY.js +0 -15
- package/dist/PublicPageProvider-CIGSujI2.d.ts +0 -4147
- package/dist/UnifiedAuthProvider-7SNDOWYD.js +0 -7
- package/dist/UnifiedAuthProvider-CKvHP1MK.d.ts +0 -139
- package/dist/api-7P7DI652.js +0 -4
- package/dist/audit-MYQXYZFU.js +0 -3
- package/dist/auth-BZOJqrdd.d.ts +0 -49
- package/dist/chunk-4DDCYDQ3.js +0 -544
- package/dist/chunk-5HNSDQWH.js +0 -5046
- package/dist/chunk-5W2A3DRC.js +0 -164
- package/dist/chunk-6GLLNA6U.js +0 -31
- package/dist/chunk-7ILTDCL2.js +0 -80
- package/dist/chunk-A3W6LW53.js +0 -70
- package/dist/chunk-AHU7G2R5.js +0 -423
- package/dist/chunk-C7ZQ5O4C.js +0 -481
- package/dist/chunk-EF2UGZWY.js +0 -611
- package/dist/chunk-FEJLJNWA.js +0 -181
- package/dist/chunk-FYHN4DD5.js +0 -415
- package/dist/chunk-GS5672WG.js +0 -2003
- package/dist/chunk-HF6O3O37.js +0 -187
- package/dist/chunk-J2U36LHD.js +0 -8517
- package/dist/chunk-LX6U42O3.js +0 -2177
- package/dist/chunk-MPBLMWVR.js +0 -2161
- package/dist/chunk-OJ4SKRSV.js +0 -105
- package/dist/chunk-S6ZQKDY6.js +0 -62
- package/dist/chunk-S7DKJPLT.js +0 -699
- package/dist/chunk-T5CVK4R3.js +0 -2816
- package/dist/chunk-TTRFSOKR.js +0 -121
- package/dist/chunk-Z2FNRKF3.js +0 -994
- package/dist/database.generated-DT8JTZiP.d.ts +0 -9406
- package/dist/event-CW5YB_2p.d.ts +0 -239
- package/dist/file-reference-BavO2eQj.d.ts +0 -148
- package/dist/functions-lBy5L2ry.d.ts +0 -208
- package/dist/timezone-0AyangqX.d.ts +0 -697
- package/dist/types-BeoeWV5I.d.ts +0 -110
- package/dist/types-DXstZpNI.d.ts +0 -614
- package/dist/types-t9H8qKRw.d.ts +0 -55
- package/dist/usePublicRouteParams-DQLrDqDb.d.ts +0 -876
- package/dist/useToast-AyaT-x7p.d.ts +0 -68
- package/dist/validation-643vUDZW.d.ts +0 -177
- package/scripts/build-docs-incremental.js +0 -179
- package/scripts/eslint-audit.cjs +0 -123
- package/scripts/generate-docs.js +0 -157
- package/scripts/install-cursor-rules.cjs +0 -255
- package/scripts/install-eslint-config.cjs +0 -349
- package/scripts/setup-build-cache.js +0 -73
- package/scripts/validate-pre-publish.js +0 -145
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +0 -260
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +0 -224
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +0 -273
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +0 -99
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +0 -448
- package/src/__tests__/helpers/__tests__/timer-utils.test.ts +0 -371
- package/src/__tests__/hooks/usePermissions.test.ts +0 -268
- package/src/__tests__/integration/UserProfile.test.tsx +0 -124
- package/src/__tests__/public-recipe-view.test.ts +0 -228
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +0 -220
- package/src/__tests__/rls-policies.test.ts +0 -471
- package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +0 -759
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +0 -524
- package/src/components/DataTable/__tests__/DataTable.export.test.tsx +0 -705
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +0 -658
- package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +0 -192
- package/src/components/DataTable/__tests__/DataTable.select-label-display.test.tsx +0 -483
- package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +0 -220
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +0 -1474
- package/src/components/DataTable/__tests__/README.md +0 -145
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +0 -788
- package/src/components/DataTable/__tests__/keyboard.test.tsx +0 -756
- package/src/components/DataTable/__tests__/mocks/MockRBACProvider.tsx +0 -66
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +0 -730
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +0 -325
- package/src/components/DataTable/__tests__/styles.test.ts +0 -382
- package/src/components/DataTable/__tests__/test-utils/dataFactories.ts +0 -103
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -380
- package/src/components/DataTable/__tests__/test-utils.ts +0 -94
- package/src/components/DataTable/components/AccessDeniedPage.tsx +0 -159
- package/src/components/DataTable/components/ActionButtons.tsx +0 -190
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +0 -160
- package/src/components/DataTable/components/ColumnFilter.tsx +0 -118
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +0 -114
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +0 -225
- package/src/components/DataTable/components/DataTableLayout.tsx +0 -573
- package/src/components/DataTable/components/DataTableModals.tsx +0 -245
- package/src/components/DataTable/components/DataTableToolbar.tsx +0 -271
- package/src/components/DataTable/components/EditFields.tsx +0 -327
- package/src/components/DataTable/components/EditableRow.tsx +0 -462
- package/src/components/DataTable/components/EmptyState.tsx +0 -79
- package/src/components/DataTable/components/FilterRow.tsx +0 -141
- package/src/components/DataTable/components/LoadingState.tsx +0 -17
- package/src/components/DataTable/components/PaginationControls.tsx +0 -289
- package/src/components/DataTable/components/RowComponent.tsx +0 -403
- package/src/components/DataTable/components/SortIndicator.tsx +0 -50
- package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -355
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +0 -657
- package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +0 -913
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +0 -572
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +0 -612
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +0 -708
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +0 -479
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -475
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +0 -157
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +0 -1061
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +0 -437
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +0 -474
- package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +0 -617
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -1093
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +0 -139
- package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +0 -519
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +0 -1004
- package/src/components/DataTable/components/cellValueUtils.ts +0 -40
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +0 -53
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -122
- package/src/components/DataTable/components/index.ts +0 -16
- package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +0 -342
- package/src/components/DataTable/core/ActionManager.ts +0 -235
- package/src/components/DataTable/core/ColumnManager.ts +0 -205
- package/src/components/DataTable/core/DataManager.ts +0 -188
- package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
- package/src/components/DataTable/core/PluginRegistry.ts +0 -229
- package/src/components/DataTable/core/StateManager.ts +0 -312
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -123
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +0 -305
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -84
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -115
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -100
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -120
- package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -104
- package/src/components/DataTable/core/index.ts +0 -1
- package/src/components/DataTable/core/interfaces.ts +0 -338
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +0 -521
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +0 -167
- package/src/components/DataTable/hooks/__tests__/useDataTableConfiguration.test.ts +0 -124
- package/src/components/DataTable/hooks/__tests__/useDataTableDataPipeline.test.ts +0 -117
- package/src/components/DataTable/hooks/__tests__/useDataTablePermissions.test.ts +0 -102
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +0 -596
- package/src/components/DataTable/hooks/__tests__/useEffectiveColumnOrder.test.ts +0 -53
- package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +0 -214
- package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +0 -448
- package/src/components/DataTable/hooks/index.ts +0 -13
- package/src/components/DataTable/types.ts +0 -761
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +0 -612
- package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +0 -94
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +0 -266
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +0 -954
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +0 -573
- package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +0 -247
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +0 -570
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +0 -470
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +0 -251
- package/src/components/DataTable/utils/__tests__/selectFieldUtils.test.ts +0 -207
- package/src/components/DataTable/utils/index.ts +0 -10
- package/src/components/PublicLayout/index.ts +0 -32
- package/src/components/Select/hooks/useSelectEvents.ts +0 -87
- package/src/components/Select/hooks/useSelectSearch.ts +0 -91
- package/src/components/Select/hooks/useSelectState.ts +0 -104
- package/src/components/Select/utils/text.ts +0 -26
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -615
- package/src/hooks/__tests__/hooks.integration.test.tsx +0 -607
- package/src/hooks/__tests__/index.unit.test.ts +0 -220
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +0 -111
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +0 -347
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +0 -144
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +0 -776
- package/src/hooks/__tests__/useDataTableState.test.ts +0 -76
- package/src/hooks/__tests__/useDebounce.unit.test.ts +0 -82
- package/src/hooks/__tests__/useEvents.unit.test.ts +0 -252
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +0 -1112
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +0 -916
- package/src/hooks/__tests__/useFileUrlCache.test.ts +0 -129
- package/src/hooks/__tests__/useFocusManagement.unit.test.ts +0 -230
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +0 -828
- package/src/hooks/__tests__/useFormDialog.test.ts +0 -478
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +0 -446
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +0 -317
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +0 -910
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +0 -294
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +0 -961
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +0 -369
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +0 -694
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePreventTabReload.test.ts +0 -88
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -785
- package/src/hooks/__tests__/usePublicEvent.test.ts +0 -678
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -630
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +0 -951
- package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +0 -443
- package/src/hooks/__tests__/useQueryCache.test.ts +0 -144
- package/src/hooks/__tests__/useRBAC.unit.test.ts +0 -236
- package/src/hooks/__tests__/useSessionDraft.test.ts +0 -163
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +0 -390
- package/src/hooks/__tests__/useStorage.unit.test.ts +0 -751
- package/src/hooks/__tests__/useToast.unit.test.tsx +0 -481
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +0 -37
- package/src/hooks/public/index.ts +0 -36
- package/src/hooks/public/usePublicFileDisplay.ts +0 -504
- package/src/hooks/useFileDisplay.ts +0 -715
- package/src/providers/OrganisationProvider.tsx +0 -92
- package/src/providers/__tests__/AuthProvider.test.tsx +0 -287
- package/src/providers/__tests__/EventProvider.test.tsx +0 -551
- package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
- package/src/providers/__tests__/InactivityProvider.test.tsx +0 -572
- package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -617
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +0 -424
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +0 -596
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +0 -263
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +0 -294
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +0 -434
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +0 -313
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +0 -486
- package/src/rbac/__tests__/cache-invalidation.test.ts +0 -399
- package/src/rbac/__tests__/engine.comprehensive.test.ts +0 -813
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +0 -82
- package/src/rbac/__tests__/rbac-core.test.tsx +0 -276
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +0 -392
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +0 -258
- package/src/rbac/__tests__/rbac-functions.test.ts +0 -647
- package/src/rbac/__tests__/rbac-integration.test.ts +0 -524
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +0 -456
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +0 -282
- package/src/rbac/audit-enhanced.ts +0 -384
- package/src/rbac/compliance/database-validator.ts +0 -165
- package/src/rbac/compliance/index.ts +0 -48
- package/src/rbac/compliance/pattern-detector.ts +0 -553
- package/src/rbac/compliance/quick-fix-suggestions.ts +0 -209
- package/src/rbac/compliance/runtime-compliance.ts +0 -99
- package/src/rbac/compliance/setup-validator.ts +0 -131
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +0 -975
- package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +0 -248
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +0 -242
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +0 -1107
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +0 -184
- package/src/rbac/components/index.ts +0 -26
- package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +0 -432
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +0 -579
- package/src/rbac/hooks/index.ts +0 -34
- package/src/rbac/hooks/permissions/index.ts +0 -4
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/contextValidator.test.ts +0 -128
- package/src/rbac/utils/__tests__/deep-equal.test.ts +0 -53
- package/src/rbac/utils/__tests__/eventContext.test.ts +0 -433
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -490
- package/src/services/__tests__/AuthService.restoreSession.test.ts +0 -39
- package/src/services/__tests__/AuthService.test.ts +0 -1332
- package/src/services/__tests__/BaseService.test.ts +0 -314
- package/src/services/__tests__/EventService.eventColours.test.ts +0 -76
- package/src/services/__tests__/EventService.test.ts +0 -1025
- package/src/services/__tests__/InactivityService.lifecycle.test.ts +0 -411
- package/src/services/__tests__/InactivityService.test.ts +0 -654
- package/src/services/__tests__/OrganisationService.pagination.test.ts +0 -409
- package/src/services/__tests__/OrganisationService.test.ts +0 -1176
- package/src/theming/__tests__/parseEventColours.test.ts +0 -321
- package/src/theming/__tests__/runtime.test.ts +0 -569
- package/src/types/__tests__/file-reference.test.ts +0 -447
- package/src/types/__tests__/guards.test.ts +0 -246
- package/src/types/__tests__/organisation.roles.test.ts +0 -55
- package/src/types/__tests__/organisation.test.ts +0 -1133
- package/src/types/__tests__/theme.test.ts +0 -830
- package/src/types/__tests__/type-validation.test.ts +0 -526
- package/src/types/__tests__/validation.test.ts +0 -731
- package/src/utils/__tests__/appConfig.unit.test.ts +0 -55
- package/src/utils/__tests__/audit.unit.test.ts +0 -69
- package/src/utils/__tests__/auth-utils.unit.test.ts +0 -70
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +0 -339
- package/src/utils/__tests__/cn.unit.test.ts +0 -34
- package/src/utils/__tests__/debugLogger.test.ts +0 -417
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +0 -818
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +0 -318
- package/src/utils/__tests__/formatDate.unit.test.ts +0 -109
- package/src/utils/__tests__/formatting.unit.test.ts +0 -99
- package/src/utils/__tests__/index.unit.test.ts +0 -251
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +0 -321
- package/src/utils/__tests__/logger.unit.test.ts +0 -398
- package/src/utils/__tests__/organisationContext.unit.test.ts +0 -191
- package/src/utils/__tests__/performanceBenchmark.test.ts +0 -175
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +0 -253
- package/src/utils/__tests__/permissionTypes.unit.test.ts +0 -250
- package/src/utils/__tests__/permissionUtils.unit.test.ts +0 -362
- package/src/utils/__tests__/sanitization.unit.test.ts +0 -346
- package/src/utils/__tests__/schemaUtils.unit.test.ts +0 -441
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +0 -335
- package/src/utils/__tests__/secureErrors.unit.test.ts +0 -390
- package/src/utils/__tests__/secureStorage.unit.test.ts +0 -289
- package/src/utils/__tests__/security.unit.test.ts +0 -149
- package/src/utils/__tests__/securityMonitor.unit.test.ts +0 -276
- package/src/utils/__tests__/sessionTracking.unit.test.ts +0 -218
- package/src/utils/__tests__/timezone.test.ts +0 -345
- package/src/utils/__tests__/validation.unit.test.ts +0 -308
- package/src/utils/__tests__/validationUtils.unit.test.ts +0 -555
- package/src/utils/app/appNameResolver.simple.test.ts +0 -212
- package/src/utils/file-reference/__tests__/file-reference.test.ts +0 -875
- package/src/utils/google-places/index.ts +0 -26
- package/src/utils/location/index.ts +0 -16
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +0 -135
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +0 -123
- package/src/utils/storage/__tests__/helpers.unit.test.ts +0 -332
- package/src/utils/storage/__tests__/index.unit.test.ts +0 -16
- package/src/utils/storage/index.ts +0 -67
- package/src/utils/timezone/index.ts +0 -17
- package/src/utils/validation/__tests__/csrf.test.ts +0 -105
- package/src/utils/validation/__tests__/htmlSanitization.unit.test.ts +0 -598
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +0 -92
- package/src/utils/validation/__tests__/validationUtils.test.ts +0 -72
- package/src/utils/validation/index.ts +0 -73
- /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
- /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
- /package/src/providers/{__tests__/README.md → README.md} +0 -0
- /package/src/types/{__tests__/README.md → README.md} +0 -0
|
@@ -0,0 +1,1362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file AuthService Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Services
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Unit tests for AuthService class.
|
|
8
|
+
* Tests authentication operations, state management, and error handling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|
12
|
+
import { AuthService } from './AuthService';
|
|
13
|
+
import { AuthError } from '@supabase/supabase-js';
|
|
14
|
+
|
|
15
|
+
// Don't mock the logger - it now works in test mode
|
|
16
|
+
// We'll spy on the Logger class methods in beforeEach to verify calls
|
|
17
|
+
import { Logger } from '../utils/core/logger';
|
|
18
|
+
|
|
19
|
+
let mockLoggerFunctions: {
|
|
20
|
+
debug: ReturnType<typeof vi.spyOn>;
|
|
21
|
+
error: ReturnType<typeof vi.spyOn>;
|
|
22
|
+
warn: ReturnType<typeof vi.spyOn>;
|
|
23
|
+
info: ReturnType<typeof vi.spyOn>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Mock Supabase client
|
|
27
|
+
const createMockSupabaseClient = () => ({
|
|
28
|
+
auth: {
|
|
29
|
+
signInWithPassword: vi.fn(),
|
|
30
|
+
signUp: vi.fn(),
|
|
31
|
+
signOut: vi.fn(),
|
|
32
|
+
resetPasswordForEmail: vi.fn(),
|
|
33
|
+
updateUser: vi.fn(),
|
|
34
|
+
refreshSession: vi.fn(),
|
|
35
|
+
getSession: vi.fn(),
|
|
36
|
+
getUser: vi.fn(),
|
|
37
|
+
onAuthStateChange: vi.fn(),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('AuthService', () => {
|
|
42
|
+
let mockSupabase: ReturnType<typeof createMockSupabaseClient>;
|
|
43
|
+
let authService: AuthService;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
// Spy on Logger methods to verify calls (logger now works in test mode)
|
|
47
|
+
mockLoggerFunctions = {
|
|
48
|
+
debug: vi.spyOn(Logger, 'debug'),
|
|
49
|
+
error: vi.spyOn(Logger, 'error'),
|
|
50
|
+
warn: vi.spyOn(Logger, 'warn'),
|
|
51
|
+
info: vi.spyOn(Logger, 'info'),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
mockSupabase = createMockSupabaseClient();
|
|
55
|
+
authService = new AuthService(mockSupabase as any);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
authService.cleanup();
|
|
60
|
+
// Restore spies
|
|
61
|
+
mockLoggerFunctions.debug.mockRestore();
|
|
62
|
+
mockLoggerFunctions.error.mockRestore();
|
|
63
|
+
mockLoggerFunctions.warn.mockRestore();
|
|
64
|
+
mockLoggerFunctions.info.mockRestore();
|
|
65
|
+
vi.clearAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('Initialization', () => {
|
|
69
|
+
it('should initialize with default state', () => {
|
|
70
|
+
expect(authService.getUser()).toBeNull();
|
|
71
|
+
expect(authService.getSession()).toBeNull();
|
|
72
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
73
|
+
expect(authService.isLoading()).toBe(false);
|
|
74
|
+
expect(authService.getError()).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should initialize service when created', async () => {
|
|
78
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
79
|
+
data: { session: null },
|
|
80
|
+
error: null
|
|
81
|
+
});
|
|
82
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
83
|
+
data: { user: null },
|
|
84
|
+
error: null
|
|
85
|
+
});
|
|
86
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
87
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await authService.initialize();
|
|
91
|
+
|
|
92
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
93
|
+
expect(mockSupabase.auth.onAuthStateChange).toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('Sign In', () => {
|
|
98
|
+
it('should sign in user successfully', async () => {
|
|
99
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
100
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
101
|
+
|
|
102
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
103
|
+
data: { user: mockUser, session: mockSession },
|
|
104
|
+
error: null
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
108
|
+
|
|
109
|
+
expect(result.ok).toBe(true);
|
|
110
|
+
expect(result.data?.user).toEqual(mockUser);
|
|
111
|
+
expect(result.data?.session).toEqual(mockSession);
|
|
112
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
113
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
114
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle sign in errors', async () => {
|
|
118
|
+
const mockError = new AuthError('Invalid credentials');
|
|
119
|
+
|
|
120
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
121
|
+
data: { user: null, session: null },
|
|
122
|
+
error: mockError
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = await authService.signIn('test@example.com', 'wrongpassword');
|
|
126
|
+
|
|
127
|
+
expect(result.ok).toBe(false);
|
|
128
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
129
|
+
expect(authService.getError()).toEqual(mockError);
|
|
130
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should handle missing Supabase client', async () => {
|
|
134
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
135
|
+
|
|
136
|
+
const result = await serviceWithoutClient.signIn('test@example.com', 'password');
|
|
137
|
+
|
|
138
|
+
expect(result.ok).toBe(false);
|
|
139
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('Sign Up', () => {
|
|
144
|
+
it('should sign up user successfully', async () => {
|
|
145
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
146
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
147
|
+
|
|
148
|
+
mockSupabase.auth.signUp.mockResolvedValue({
|
|
149
|
+
data: { user: mockUser, session: mockSession },
|
|
150
|
+
error: null
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
154
|
+
|
|
155
|
+
expect(result.ok).toBe(true);
|
|
156
|
+
expect(result.data?.user).toEqual(mockUser);
|
|
157
|
+
expect(result.data?.session).toEqual(mockSession);
|
|
158
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
159
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
160
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle sign up errors', async () => {
|
|
164
|
+
const mockError = new AuthError('Email already registered');
|
|
165
|
+
|
|
166
|
+
mockSupabase.auth.signUp.mockResolvedValue({
|
|
167
|
+
data: { user: null, session: null },
|
|
168
|
+
error: mockError
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
172
|
+
|
|
173
|
+
expect(result.ok).toBe(false);
|
|
174
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
175
|
+
expect(authService.getError()).toEqual(mockError);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Sign Out', () => {
|
|
180
|
+
it('should sign out user successfully', async () => {
|
|
181
|
+
// First sign in a user
|
|
182
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
183
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
184
|
+
|
|
185
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
186
|
+
data: { user: mockUser, session: mockSession },
|
|
187
|
+
error: null
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await authService.signIn('test@example.com', 'password');
|
|
191
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
192
|
+
|
|
193
|
+
// Then sign out
|
|
194
|
+
mockSupabase.auth.signOut.mockResolvedValue({
|
|
195
|
+
error: null
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const result = await authService.signOut();
|
|
199
|
+
|
|
200
|
+
expect(result.ok).toBe(true);
|
|
201
|
+
expect(authService.getUser()).toBeNull();
|
|
202
|
+
expect(authService.getSession()).toBeNull();
|
|
203
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle sign out errors', async () => {
|
|
207
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
208
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
209
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
210
|
+
data: { user: mockUser, session: mockSession },
|
|
211
|
+
error: null
|
|
212
|
+
});
|
|
213
|
+
await authService.signIn('test@example.com', 'password');
|
|
214
|
+
expect(authService.getUser()).not.toBeNull();
|
|
215
|
+
|
|
216
|
+
const mockError = new AuthError('Sign out failed');
|
|
217
|
+
mockSupabase.auth.signOut
|
|
218
|
+
.mockResolvedValueOnce({ error: mockError })
|
|
219
|
+
.mockResolvedValueOnce({ error: null });
|
|
220
|
+
|
|
221
|
+
const result = await authService.signOut();
|
|
222
|
+
|
|
223
|
+
expect(result.ok).toBe(false);
|
|
224
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
225
|
+
expect(authService.getUser()).toBeNull();
|
|
226
|
+
expect(authService.getSession()).toBeNull();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('should clear user and session when signOut throws', async () => {
|
|
230
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
231
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
232
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
233
|
+
data: { user: mockUser, session: mockSession },
|
|
234
|
+
error: null
|
|
235
|
+
});
|
|
236
|
+
await authService.signIn('test@example.com', 'password');
|
|
237
|
+
expect(authService.getUser()).not.toBeNull();
|
|
238
|
+
|
|
239
|
+
const exceptionError = new Error('Network error');
|
|
240
|
+
mockSupabase.auth.signOut
|
|
241
|
+
.mockRejectedValueOnce(exceptionError)
|
|
242
|
+
.mockResolvedValueOnce({ error: null });
|
|
243
|
+
|
|
244
|
+
const result = await authService.signOut();
|
|
245
|
+
|
|
246
|
+
expect(result.ok).toBe(false);
|
|
247
|
+
expect(authService.getUser()).toBeNull();
|
|
248
|
+
expect(authService.getSession()).toBeNull();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('Password Reset', () => {
|
|
253
|
+
it('should reset password successfully', async () => {
|
|
254
|
+
mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
|
|
255
|
+
error: null
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = await authService.resetPassword('test@example.com');
|
|
259
|
+
|
|
260
|
+
expect(result.ok).toBe(true);
|
|
261
|
+
expect(mockSupabase.auth.resetPasswordForEmail).toHaveBeenCalledWith('test@example.com');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should handle password reset errors', async () => {
|
|
265
|
+
const mockError = new AuthError('Email not found');
|
|
266
|
+
|
|
267
|
+
mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
|
|
268
|
+
error: mockError
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const result = await authService.resetPassword('test@example.com');
|
|
272
|
+
|
|
273
|
+
expect(result.ok).toBe(false);
|
|
274
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
275
|
+
expect(authService.getError()).toEqual(mockError);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('Update Password', () => {
|
|
280
|
+
it('should update password successfully', async () => {
|
|
281
|
+
mockSupabase.auth.updateUser.mockResolvedValue({
|
|
282
|
+
error: null
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const result = await authService.updatePassword('newpassword');
|
|
286
|
+
|
|
287
|
+
expect(result.ok).toBe(true);
|
|
288
|
+
expect(mockSupabase.auth.updateUser).toHaveBeenCalledWith({
|
|
289
|
+
password: 'newpassword'
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should handle update password errors', async () => {
|
|
294
|
+
const mockError = new AuthError('Password too weak');
|
|
295
|
+
|
|
296
|
+
mockSupabase.auth.updateUser.mockResolvedValue({
|
|
297
|
+
error: mockError
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const result = await authService.updatePassword('weak');
|
|
301
|
+
|
|
302
|
+
expect(result.ok).toBe(false);
|
|
303
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
304
|
+
expect(authService.getError()).toEqual(mockError);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('Refresh Session', () => {
|
|
309
|
+
it('should refresh session successfully', async () => {
|
|
310
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
311
|
+
error: null
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const result = await authService.refreshSession();
|
|
315
|
+
|
|
316
|
+
expect(result.ok).toBe(true);
|
|
317
|
+
expect(mockSupabase.auth.refreshSession).toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should handle refresh session errors', async () => {
|
|
321
|
+
const mockError = new AuthError('Session expired');
|
|
322
|
+
|
|
323
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
324
|
+
error: mockError
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const result = await authService.refreshSession();
|
|
328
|
+
|
|
329
|
+
expect(result.ok).toBe(false);
|
|
330
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
331
|
+
expect(authService.getError()).toEqual(mockError);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('State Management', () => {
|
|
336
|
+
it('should notify subscribers when state changes', async () => {
|
|
337
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
338
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
339
|
+
|
|
340
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
341
|
+
data: { user: mockUser, session: mockSession },
|
|
342
|
+
error: null
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const subscriber = vi.fn();
|
|
346
|
+
const unsubscribe = authService.subscribe(subscriber);
|
|
347
|
+
|
|
348
|
+
await authService.signIn('test@example.com', 'password');
|
|
349
|
+
|
|
350
|
+
expect(subscriber).toHaveBeenCalled();
|
|
351
|
+
|
|
352
|
+
unsubscribe();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should cleanup subscriptions on cleanup', () => {
|
|
356
|
+
const subscriber = vi.fn();
|
|
357
|
+
authService.subscribe(subscriber);
|
|
358
|
+
|
|
359
|
+
authService.cleanup();
|
|
360
|
+
|
|
361
|
+
// After cleanup, new state changes shouldn't notify subscribers
|
|
362
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
363
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
364
|
+
|
|
365
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
366
|
+
data: { user: mockUser, session: mockSession },
|
|
367
|
+
error: null
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
authService.signIn('test@example.com', 'password');
|
|
371
|
+
|
|
372
|
+
// Subscriber should not be called after cleanup
|
|
373
|
+
expect(subscriber).not.toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('Error Handling', () => {
|
|
378
|
+
it('should handle network errors', async () => {
|
|
379
|
+
const networkError = new Error('Network error');
|
|
380
|
+
|
|
381
|
+
mockSupabase.auth.signInWithPassword.mockRejectedValue(networkError);
|
|
382
|
+
|
|
383
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
384
|
+
|
|
385
|
+
expect(result.ok).toBe(false);
|
|
386
|
+
expect(result.error.message).toBe('Network error');
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should clear errors on successful operations', async () => {
|
|
390
|
+
// First cause an error
|
|
391
|
+
const mockError = new AuthError('Invalid credentials');
|
|
392
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
393
|
+
data: { user: null, session: null },
|
|
394
|
+
error: mockError
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await authService.signIn('test@example.com', 'wrongpassword');
|
|
398
|
+
expect(authService.getError()).toEqual(mockError);
|
|
399
|
+
|
|
400
|
+
// Then succeed
|
|
401
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
402
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
403
|
+
|
|
404
|
+
mockSupabase.auth.signInWithPassword.mockResolvedValue({
|
|
405
|
+
data: { user: mockUser, session: mockSession },
|
|
406
|
+
error: null
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
await authService.signIn('test@example.com', 'password');
|
|
410
|
+
expect(authService.getError()).toBeNull();
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should handle exceptions during sign in', async () => {
|
|
414
|
+
const exceptionError = new Error('Unexpected exception');
|
|
415
|
+
mockSupabase.auth.signInWithPassword.mockRejectedValue(exceptionError);
|
|
416
|
+
|
|
417
|
+
const result = await authService.signIn('test@example.com', 'password');
|
|
418
|
+
|
|
419
|
+
expect(result.ok).toBe(false);
|
|
420
|
+
expect(result.error.message).toBe('Unexpected exception');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should handle exceptions during sign up', async () => {
|
|
424
|
+
const exceptionError = new Error('Unexpected exception');
|
|
425
|
+
mockSupabase.auth.signUp.mockRejectedValue(exceptionError);
|
|
426
|
+
|
|
427
|
+
const result = await authService.signUp('test@example.com', 'password');
|
|
428
|
+
|
|
429
|
+
expect(result.ok).toBe(false);
|
|
430
|
+
expect(result.error.message).toBe('Unexpected exception');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should handle exceptions during sign out', async () => {
|
|
434
|
+
const exceptionError = new Error('Unexpected exception');
|
|
435
|
+
mockSupabase.auth.signOut.mockRejectedValue(exceptionError);
|
|
436
|
+
|
|
437
|
+
const result = await authService.signOut();
|
|
438
|
+
|
|
439
|
+
expect(result.ok).toBe(false);
|
|
440
|
+
expect(result.error.message).toBe('Unexpected exception');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should handle exceptions during password reset', async () => {
|
|
444
|
+
const exceptionError = new Error('Unexpected exception');
|
|
445
|
+
mockSupabase.auth.resetPasswordForEmail.mockRejectedValue(exceptionError);
|
|
446
|
+
|
|
447
|
+
const result = await authService.resetPassword('test@example.com');
|
|
448
|
+
|
|
449
|
+
expect(result.ok).toBe(false);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should handle missing Supabase client on sign up', async () => {
|
|
453
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
454
|
+
|
|
455
|
+
const result = await serviceWithoutClient.signUp('test@example.com', 'password');
|
|
456
|
+
|
|
457
|
+
expect(result.ok).toBe(false);
|
|
458
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should handle missing Supabase client on sign out', async () => {
|
|
462
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
463
|
+
|
|
464
|
+
const result = await serviceWithoutClient.signOut();
|
|
465
|
+
|
|
466
|
+
expect(result.ok).toBe(false);
|
|
467
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should handle missing Supabase client on password reset', async () => {
|
|
471
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
472
|
+
|
|
473
|
+
const result = await serviceWithoutClient.resetPassword('test@example.com');
|
|
474
|
+
|
|
475
|
+
expect(result.ok).toBe(false);
|
|
476
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should handle missing Supabase client on update password', async () => {
|
|
480
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
481
|
+
|
|
482
|
+
const result = await serviceWithoutClient.updatePassword('newpassword');
|
|
483
|
+
|
|
484
|
+
expect(result.ok).toBe(false);
|
|
485
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should handle missing Supabase client on session refresh', async () => {
|
|
489
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
490
|
+
|
|
491
|
+
const result = await serviceWithoutClient.refreshSession();
|
|
492
|
+
|
|
493
|
+
expect(result.ok).toBe(false);
|
|
494
|
+
expect(result.error.message).toBe('Supabase client not available');
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe('Session Management Edge Cases', () => {
|
|
499
|
+
it('should handle session refresh with null data', async () => {
|
|
500
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
501
|
+
data: { session: null, user: null },
|
|
502
|
+
error: null
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const result = await authService.refreshSession();
|
|
506
|
+
|
|
507
|
+
expect(result.ok).toBe(true);
|
|
508
|
+
expect(result.data?.user).toBeNull();
|
|
509
|
+
expect(result.data?.session).toBeNull();
|
|
510
|
+
expect(authService.getUser()).toBeNull();
|
|
511
|
+
expect(authService.getSession()).toBeNull();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should handle session refresh with user but no session by clearing both', async () => {
|
|
515
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
516
|
+
|
|
517
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
518
|
+
data: { session: null, user: mockUser },
|
|
519
|
+
error: null
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const result = await authService.refreshSession();
|
|
523
|
+
|
|
524
|
+
// If there's no session, we should clear user state too
|
|
525
|
+
expect(result.ok).toBe(true);
|
|
526
|
+
expect(result.data?.user).toBeNull();
|
|
527
|
+
expect(result.data?.session).toBeNull();
|
|
528
|
+
expect(authService.getUser()).toBeNull();
|
|
529
|
+
expect(authService.getSession()).toBeNull();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should handle refresh session errors by clearing state', async () => {
|
|
533
|
+
const mockError = new AuthError('Session expired');
|
|
534
|
+
|
|
535
|
+
mockSupabase.auth.refreshSession.mockResolvedValue({
|
|
536
|
+
data: { session: null, user: null },
|
|
537
|
+
error: mockError
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const result = await authService.refreshSession();
|
|
541
|
+
|
|
542
|
+
expect(result.ok).toBe(false);
|
|
543
|
+
expect(result.error).toMatchObject({ message: mockError.message });
|
|
544
|
+
expect(authService.getUser()).toBeNull();
|
|
545
|
+
expect(authService.getSession()).toBeNull();
|
|
546
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
describe('Initialization Edge Cases', () => {
|
|
551
|
+
it('should handle initialization without Supabase client', async () => {
|
|
552
|
+
const serviceWithoutClient = new AuthService(null as any);
|
|
553
|
+
await serviceWithoutClient.initialize();
|
|
554
|
+
|
|
555
|
+
expect(mockSupabase.auth.getSession).not.toHaveBeenCalled();
|
|
556
|
+
expect(mockSupabase.auth.onAuthStateChange).not.toHaveBeenCalled();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('should handle initialization with getSession error', async () => {
|
|
560
|
+
const sessionError = new AuthError('Session error');
|
|
561
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
562
|
+
data: { session: null },
|
|
563
|
+
error: sessionError
|
|
564
|
+
});
|
|
565
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
566
|
+
data: { user: null },
|
|
567
|
+
error: null
|
|
568
|
+
});
|
|
569
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
570
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
await authService.initialize();
|
|
574
|
+
|
|
575
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
576
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should not call getUser when getSession succeeds with no session (prevents AuthSessionMissingError)', async () => {
|
|
580
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
581
|
+
data: { session: null },
|
|
582
|
+
error: null
|
|
583
|
+
});
|
|
584
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
585
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
await authService.initialize();
|
|
589
|
+
|
|
590
|
+
// When getSession succeeds with no session, getUser is NOT called to prevent
|
|
591
|
+
// AuthSessionMissingError from being raised on public pages (e.g., login page)
|
|
592
|
+
// This is intentional behavior to avoid noisy error banners for benign unauthenticated states
|
|
593
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
594
|
+
expect(mockSupabase.auth.getUser).not.toHaveBeenCalled();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should restore session from storage during initialization', async () => {
|
|
598
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
599
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
600
|
+
|
|
601
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
602
|
+
data: { session: mockSession },
|
|
603
|
+
error: null
|
|
604
|
+
});
|
|
605
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
606
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
await authService.initialize();
|
|
610
|
+
|
|
611
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
612
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
613
|
+
expect(authService.isAuthenticated()).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('should handle auth state change unsubscribe gracefully', () => {
|
|
617
|
+
const unsubscribeFn = vi.fn();
|
|
618
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
619
|
+
data: { subscription: { unsubscribe: unsubscribeFn } }
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
authService.cleanup();
|
|
623
|
+
|
|
624
|
+
// Should not throw error
|
|
625
|
+
expect(unsubscribeFn).not.toHaveBeenCalled();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('should handle auth state change events correctly', async () => {
|
|
629
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
630
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
631
|
+
|
|
632
|
+
let authStateCallback: any;
|
|
633
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
634
|
+
authStateCallback = callback;
|
|
635
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
await authService.initialize();
|
|
639
|
+
|
|
640
|
+
// Simulate SIGNED_IN event
|
|
641
|
+
if (authStateCallback) {
|
|
642
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
643
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
644
|
+
expect(authService.getSession()).toEqual(mockSession);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Simulate SIGNED_OUT event
|
|
648
|
+
if (authStateCallback) {
|
|
649
|
+
authStateCallback('SIGNED_OUT', null);
|
|
650
|
+
expect(authService.getUser()).toBeNull();
|
|
651
|
+
expect(authService.getSession()).toBeNull();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Simulate TOKEN_REFRESHED event
|
|
655
|
+
const refreshedSession = { ...mockSession, access_token: 'new_token' };
|
|
656
|
+
if (authStateCallback) {
|
|
657
|
+
authStateCallback('TOKEN_REFRESHED', refreshedSession);
|
|
658
|
+
expect(authService.getSession()).toEqual(refreshedSession);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Simulate INITIAL_SESSION event
|
|
662
|
+
const initialSession = { access_token: 'initial_token', user: mockUser };
|
|
663
|
+
if (authStateCallback) {
|
|
664
|
+
authStateCallback('INITIAL_SESSION', initialSession);
|
|
665
|
+
expect(authService.getSession()).toEqual(initialSession);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('should handle errors in auth state change callback', async () => {
|
|
670
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
671
|
+
|
|
672
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
673
|
+
// Simulate error in callback
|
|
674
|
+
try {
|
|
675
|
+
callback('INITIAL_SESSION', null);
|
|
676
|
+
} catch (_error) {
|
|
677
|
+
// Error is expected to be caught and logged
|
|
678
|
+
}
|
|
679
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
await authService.initialize();
|
|
683
|
+
|
|
684
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
685
|
+
consoleWarnSpy.mockRestore();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe('Get Session with Null User', () => {
|
|
690
|
+
it('should handle session with null user object', () => {
|
|
691
|
+
const mockSessionWithNullUser = { access_token: 'token', user: null };
|
|
692
|
+
|
|
693
|
+
// This would be set via internal state
|
|
694
|
+
(authService as any).session = mockSessionWithNullUser;
|
|
695
|
+
|
|
696
|
+
expect(authService.getSession()).toEqual(mockSessionWithNullUser);
|
|
697
|
+
expect(authService.getUser()).toBeNull();
|
|
698
|
+
expect(authService.isAuthenticated()).toBe(false);
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
describe('Automatic Session Tracking', () => {
|
|
703
|
+
beforeEach(() => {
|
|
704
|
+
// Mock rpc function for session tracking
|
|
705
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
706
|
+
// Mock from().select() chain for app ID resolution
|
|
707
|
+
(mockSupabase as any).from = vi.fn().mockReturnValue({
|
|
708
|
+
select: vi.fn().mockReturnValue({
|
|
709
|
+
eq: vi.fn().mockReturnValue({
|
|
710
|
+
eq: vi.fn().mockReturnValue({
|
|
711
|
+
single: vi.fn().mockResolvedValue({
|
|
712
|
+
data: { id: 'app-id-123' },
|
|
713
|
+
error: null
|
|
714
|
+
})
|
|
715
|
+
})
|
|
716
|
+
})
|
|
717
|
+
})
|
|
718
|
+
});
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it('should track login session automatically on SIGNED_IN event', async () => {
|
|
722
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
723
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
724
|
+
|
|
725
|
+
(mockSupabase as any).rpc.mockResolvedValue({ error: null });
|
|
726
|
+
|
|
727
|
+
let authStateCallback: any;
|
|
728
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
729
|
+
authStateCallback = callback;
|
|
730
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Initialize with appName to test app ID resolution
|
|
734
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
735
|
+
await authServiceWithApp.initialize();
|
|
736
|
+
|
|
737
|
+
// Simulate SIGNED_IN event
|
|
738
|
+
if (authStateCallback) {
|
|
739
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
740
|
+
|
|
741
|
+
// Wait a bit for async tracking to complete
|
|
742
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
743
|
+
|
|
744
|
+
// Verify rbac_session_track was called with correct parameters
|
|
745
|
+
expect((mockSupabase as any).rpc).toHaveBeenCalledWith('rbac_session_track', expect.objectContaining({
|
|
746
|
+
p_user_id: 'user-123',
|
|
747
|
+
p_session_type: 'login',
|
|
748
|
+
p_event_id: null,
|
|
749
|
+
p_app_id: 'app-id-123', // Should be resolved from appName
|
|
750
|
+
// p_user_agent, p_device_fingerprint, p_ip_address may be undefined in test environment
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
authServiceWithApp.cleanup();
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should track logout session automatically on SIGNED_OUT event', async () => {
|
|
758
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
759
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
760
|
+
|
|
761
|
+
(mockSupabase as any).rpc.mockResolvedValue({ error: null });
|
|
762
|
+
|
|
763
|
+
let authStateCallback: any;
|
|
764
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
765
|
+
authStateCallback = callback;
|
|
766
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
770
|
+
await authServiceWithApp.initialize();
|
|
771
|
+
|
|
772
|
+
// Simulate SIGNED_OUT event
|
|
773
|
+
if (authStateCallback) {
|
|
774
|
+
authStateCallback('SIGNED_OUT', mockSession);
|
|
775
|
+
|
|
776
|
+
// Wait a bit for async tracking to complete
|
|
777
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
778
|
+
|
|
779
|
+
// Verify rbac_session_track was called with logout type
|
|
780
|
+
expect((mockSupabase as any).rpc).toHaveBeenCalledWith('rbac_session_track', expect.objectContaining({
|
|
781
|
+
p_user_id: 'user-123',
|
|
782
|
+
p_session_type: 'logout',
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
authServiceWithApp.cleanup();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it('should NOT track session on TOKEN_REFRESHED event (to avoid duplicate login records)', async () => {
|
|
790
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
791
|
+
const mockSession = { access_token: 'new_token', user: mockUser };
|
|
792
|
+
|
|
793
|
+
(mockSupabase as any).rpc.mockResolvedValue({ error: null });
|
|
794
|
+
|
|
795
|
+
let authStateCallback: any;
|
|
796
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
797
|
+
authStateCallback = callback;
|
|
798
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
802
|
+
await authServiceWithApp.initialize();
|
|
803
|
+
|
|
804
|
+
// Simulate TOKEN_REFRESHED event
|
|
805
|
+
if (authStateCallback) {
|
|
806
|
+
authStateCallback('TOKEN_REFRESHED', mockSession);
|
|
807
|
+
|
|
808
|
+
// Wait a bit for any async operations
|
|
809
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
810
|
+
|
|
811
|
+
// Verify rbac_session_track was NOT called
|
|
812
|
+
expect((mockSupabase as any).rpc).not.toHaveBeenCalled();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
authServiceWithApp.cleanup();
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
it('should handle tracking errors gracefully without breaking authentication', async () => {
|
|
819
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
820
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
821
|
+
|
|
822
|
+
// Mock rpc to reject (this will trigger the catch block in trackSession)
|
|
823
|
+
(mockSupabase as any).rpc = vi.fn().mockRejectedValue(new Error('Tracking failed'));
|
|
824
|
+
|
|
825
|
+
let authStateCallback: any;
|
|
826
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
827
|
+
authStateCallback = callback;
|
|
828
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
832
|
+
await authServiceWithApp.initialize();
|
|
833
|
+
|
|
834
|
+
// Simulate SIGNED_IN event
|
|
835
|
+
if (authStateCallback) {
|
|
836
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
837
|
+
|
|
838
|
+
// Wait for async tracking to complete (trackSession is async)
|
|
839
|
+
// The catch block in trackSession will log the error
|
|
840
|
+
// Note: The logger may be called in the .catch() handler with "Failed to track"
|
|
841
|
+
// or in the catch block with "Error tracking"
|
|
842
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
843
|
+
|
|
844
|
+
// Check for either error message pattern
|
|
845
|
+
const warnCalls = mockLoggerFunctions.warn.mock.calls;
|
|
846
|
+
const hasErrorTracking = warnCalls.some(call =>
|
|
847
|
+
call[0] === 'AuthService' &&
|
|
848
|
+
typeof call[1] === 'string' &&
|
|
849
|
+
call[1].includes('Error tracking')
|
|
850
|
+
);
|
|
851
|
+
const hasFailedToTrack = warnCalls.some(call =>
|
|
852
|
+
call[0] === 'AuthService' &&
|
|
853
|
+
typeof call[1] === 'string' &&
|
|
854
|
+
call[1].includes('Failed to track')
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
expect(hasErrorTracking || hasFailedToTrack).toBe(true);
|
|
858
|
+
expect(mockLoggerFunctions.warn).toHaveBeenCalled();
|
|
859
|
+
expect(authServiceWithApp.isAuthenticated()).toBe(true);
|
|
860
|
+
}
|
|
861
|
+
authServiceWithApp.cleanup();
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('should work without appName (app_id will be null)', async () => {
|
|
865
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
866
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
867
|
+
|
|
868
|
+
(mockSupabase as any).rpc.mockResolvedValue({ error: null });
|
|
869
|
+
|
|
870
|
+
let authStateCallback: any;
|
|
871
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
872
|
+
authStateCallback = callback;
|
|
873
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Initialize without appName
|
|
877
|
+
await authService.initialize();
|
|
878
|
+
|
|
879
|
+
// Simulate SIGNED_IN event
|
|
880
|
+
if (authStateCallback) {
|
|
881
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
882
|
+
|
|
883
|
+
// Wait a bit for async tracking to complete
|
|
884
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
885
|
+
|
|
886
|
+
// Verify rbac_session_track was called with null app_id
|
|
887
|
+
expect((mockSupabase as any).rpc).toHaveBeenCalledWith('rbac_session_track', expect.objectContaining({
|
|
888
|
+
p_user_id: 'user-123',
|
|
889
|
+
p_session_type: 'login',
|
|
890
|
+
p_app_id: undefined, // Should be undefined when appName not provided
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
describe('Session Restoration Edge Cases', () => {
|
|
897
|
+
it('should handle getSession throwing exception', async () => {
|
|
898
|
+
mockSupabase.auth.getSession.mockRejectedValue(new Error('getSession exception'));
|
|
899
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
900
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
await authService.initialize();
|
|
904
|
+
|
|
905
|
+
// Should handle exception gracefully
|
|
906
|
+
expect(mockSupabase.auth.getSession).toHaveBeenCalled();
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it('should handle getUser when getSession fails', async () => {
|
|
910
|
+
const sessionError = new AuthError('Session error');
|
|
911
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
912
|
+
data: { session: null },
|
|
913
|
+
error: sessionError
|
|
914
|
+
});
|
|
915
|
+
mockSupabase.auth.getUser.mockResolvedValue({
|
|
916
|
+
data: { user: null },
|
|
917
|
+
error: null
|
|
918
|
+
});
|
|
919
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
920
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
await authService.initialize();
|
|
924
|
+
|
|
925
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
it('should handle getUser also failing', async () => {
|
|
929
|
+
const sessionError = new AuthError('Session error');
|
|
930
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
931
|
+
data: { session: null },
|
|
932
|
+
error: sessionError
|
|
933
|
+
});
|
|
934
|
+
mockSupabase.auth.getUser.mockRejectedValue(new Error('getUser exception'));
|
|
935
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
936
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
await authService.initialize();
|
|
940
|
+
|
|
941
|
+
// Should handle both failures gracefully
|
|
942
|
+
expect(mockSupabase.auth.getUser).toHaveBeenCalled();
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it('should handle session restoration timeout', async () => {
|
|
946
|
+
// Mock getSession to hang (simulate timeout scenario)
|
|
947
|
+
mockSupabase.auth.getSession.mockImplementation(() => {
|
|
948
|
+
return new Promise(() => {
|
|
949
|
+
// Never resolves - will timeout after 5 seconds in real scenario
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
953
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// Start initialization (will hang waiting for getSession)
|
|
957
|
+
const initPromise = authService.initialize();
|
|
958
|
+
|
|
959
|
+
// Use a timeout to simulate the service's internal timeout
|
|
960
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
961
|
+
setTimeout(() => reject(new Error('Test timeout')), 100);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
// Race between initialization and test timeout
|
|
965
|
+
try {
|
|
966
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
967
|
+
} catch (_error) {
|
|
968
|
+
// Expected to timeout in test
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Verify that restoration state is managed
|
|
972
|
+
const restorationState = authService.getSessionRestorationState();
|
|
973
|
+
expect(restorationState).toBeDefined();
|
|
974
|
+
}, 1000);
|
|
975
|
+
|
|
976
|
+
it('should finish restoration with timeout error', async () => {
|
|
977
|
+
// Mock getSession to hang (simulate timeout scenario)
|
|
978
|
+
mockSupabase.auth.getSession.mockImplementation(() => {
|
|
979
|
+
return new Promise(() => {
|
|
980
|
+
// Never resolves - will timeout after 5 seconds in real scenario
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
984
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// Start initialization (will hang waiting for getSession)
|
|
988
|
+
const initPromise = authService.initialize();
|
|
989
|
+
|
|
990
|
+
// Use a timeout to simulate the service's internal timeout
|
|
991
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
992
|
+
setTimeout(() => reject(new Error('Test timeout')), 100);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
// Race between initialization and test timeout
|
|
996
|
+
try {
|
|
997
|
+
await Promise.race([initPromise, timeoutPromise]);
|
|
998
|
+
} catch (_error) {
|
|
999
|
+
// Expected to timeout in test
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Verify that restoration state is managed
|
|
1003
|
+
const restorationState = authService.getSessionRestorationState();
|
|
1004
|
+
expect(restorationState).toBeDefined();
|
|
1005
|
+
}, 1000);
|
|
1006
|
+
|
|
1007
|
+
it('should handle restoration error during initialization', async () => {
|
|
1008
|
+
const restorationError = new Error('Restoration failed');
|
|
1009
|
+
mockSupabase.auth.getSession.mockRejectedValue(restorationError);
|
|
1010
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1011
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
await authService.initialize();
|
|
1015
|
+
|
|
1016
|
+
const restorationState = authService.getSessionRestorationState();
|
|
1017
|
+
expect(restorationState.restorationError).toBeDefined();
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('should reset restoration state when valid session arrives after timeout', async () => {
|
|
1021
|
+
let authStateCallback: any;
|
|
1022
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1023
|
+
authStateCallback = callback;
|
|
1024
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// Mock getSession to fail initially
|
|
1028
|
+
mockSupabase.auth.getSession.mockRejectedValue(new Error('Timeout'));
|
|
1029
|
+
|
|
1030
|
+
await authService.initialize();
|
|
1031
|
+
|
|
1032
|
+
// Simulate INITIAL_SESSION arriving after timeout
|
|
1033
|
+
const mockSession = { access_token: 'token', user: { id: '1', email: 'test@example.com' } };
|
|
1034
|
+
if (authStateCallback) {
|
|
1035
|
+
authStateCallback('INITIAL_SESSION', mockSession);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Wait a bit for state to update
|
|
1039
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1040
|
+
|
|
1041
|
+
const restorationState = authService.getSessionRestorationState();
|
|
1042
|
+
expect(restorationState.restorationComplete).toBe(true);
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
describe('Error Handlers', () => {
|
|
1047
|
+
it('should setup error handlers on initialization', async () => {
|
|
1048
|
+
const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
|
|
1049
|
+
|
|
1050
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1051
|
+
data: { session: null },
|
|
1052
|
+
error: null
|
|
1053
|
+
});
|
|
1054
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1055
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
await authService.initialize();
|
|
1059
|
+
|
|
1060
|
+
// Error handlers should be set up
|
|
1061
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('error', expect.any(Function));
|
|
1062
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('unhandledrejection', expect.any(Function));
|
|
1063
|
+
|
|
1064
|
+
addEventListenerSpy.mockRestore();
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it('should suppress AuthSessionMissingError in error handler', async () => {
|
|
1068
|
+
const preventDefaultSpy = vi.fn();
|
|
1069
|
+
|
|
1070
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1071
|
+
data: { session: null },
|
|
1072
|
+
error: null
|
|
1073
|
+
});
|
|
1074
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1075
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
await authService.initialize();
|
|
1079
|
+
|
|
1080
|
+
// Trigger error handler
|
|
1081
|
+
const errorEvent = new ErrorEvent('error', { error: { message: 'AuthSessionMissingError' } as any });
|
|
1082
|
+
window.dispatchEvent(errorEvent);
|
|
1083
|
+
|
|
1084
|
+
// Error should be suppressed
|
|
1085
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled(); // In test environment, preventDefault may not work
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
it('should suppress AuthSessionMissingError in unhandled rejection', async () => {
|
|
1089
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1090
|
+
data: { session: null },
|
|
1091
|
+
error: null
|
|
1092
|
+
});
|
|
1093
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1094
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
await authService.initialize();
|
|
1098
|
+
|
|
1099
|
+
// Create a promise that will be rejected
|
|
1100
|
+
const rejectedPromise = Promise.reject(new Error('AuthSessionMissingError'));
|
|
1101
|
+
|
|
1102
|
+
// Catch the rejection to prevent it from being unhandled
|
|
1103
|
+
rejectedPromise.catch(() => {
|
|
1104
|
+
// The error handler should suppress this
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
// Trigger unhandled rejection using a custom event
|
|
1108
|
+
const rejectionEvent = new Event('unhandledrejection') as any;
|
|
1109
|
+
rejectionEvent.promise = rejectedPromise;
|
|
1110
|
+
rejectionEvent.reason = { message: 'AuthSessionMissingError' };
|
|
1111
|
+
|
|
1112
|
+
// The handler should be set up and will suppress the error
|
|
1113
|
+
window.dispatchEvent(rejectionEvent);
|
|
1114
|
+
|
|
1115
|
+
// Test passes if no error propagates
|
|
1116
|
+
expect(true).toBe(true);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('should handle window undefined environment', async () => {
|
|
1120
|
+
const originalWindow = global.window;
|
|
1121
|
+
// @ts-expect-error Deleting global.window for SSR/window-undefined test
|
|
1122
|
+
delete global.window;
|
|
1123
|
+
|
|
1124
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1125
|
+
data: { session: null },
|
|
1126
|
+
error: null
|
|
1127
|
+
});
|
|
1128
|
+
mockSupabase.auth.onAuthStateChange.mockReturnValue({
|
|
1129
|
+
data: { subscription: { unsubscribe: vi.fn() } }
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// Should not throw error
|
|
1133
|
+
await expect(authService.initialize()).resolves.toBeUndefined();
|
|
1134
|
+
|
|
1135
|
+
global.window = originalWindow;
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
describe('Session Tracking Edge Cases', () => {
|
|
1140
|
+
it('should handle tracking when app lookup fails', async () => {
|
|
1141
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1142
|
+
(mockSupabase as any).from = vi.fn().mockReturnValue({
|
|
1143
|
+
select: vi.fn().mockReturnValue({
|
|
1144
|
+
eq: vi.fn().mockReturnValue({
|
|
1145
|
+
eq: vi.fn().mockReturnValue({
|
|
1146
|
+
single: vi.fn().mockResolvedValue({
|
|
1147
|
+
data: null,
|
|
1148
|
+
error: { message: 'App not found' }
|
|
1149
|
+
})
|
|
1150
|
+
})
|
|
1151
|
+
})
|
|
1152
|
+
})
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
let authStateCallback: any;
|
|
1156
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1157
|
+
authStateCallback = callback;
|
|
1158
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
1162
|
+
await authServiceWithApp.initialize();
|
|
1163
|
+
|
|
1164
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
1165
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
1166
|
+
|
|
1167
|
+
if (authStateCallback) {
|
|
1168
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
1169
|
+
|
|
1170
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1171
|
+
|
|
1172
|
+
// Should still track with null app_id
|
|
1173
|
+
expect((mockSupabase as any).rpc).toHaveBeenCalledWith('rbac_session_track', expect.objectContaining({
|
|
1174
|
+
p_app_id: undefined
|
|
1175
|
+
}));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
authServiceWithApp.cleanup();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
it('should handle tracking when RPC fails', async () => {
|
|
1182
|
+
vi.clearAllMocks();
|
|
1183
|
+
(mockSupabase as any).rpc = vi.fn().mockResolvedValue({
|
|
1184
|
+
error: { message: 'RPC failed' }
|
|
1185
|
+
});
|
|
1186
|
+
(mockSupabase as any).from = vi.fn().mockReturnValue({
|
|
1187
|
+
select: vi.fn().mockReturnValue({
|
|
1188
|
+
eq: vi.fn().mockReturnValue({
|
|
1189
|
+
eq: vi.fn().mockReturnValue({
|
|
1190
|
+
single: vi.fn().mockResolvedValue({
|
|
1191
|
+
data: { id: 'app-id-123' },
|
|
1192
|
+
error: null
|
|
1193
|
+
})
|
|
1194
|
+
})
|
|
1195
|
+
})
|
|
1196
|
+
})
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
let authStateCallback: any;
|
|
1200
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1201
|
+
authStateCallback = callback;
|
|
1202
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const authServiceWithApp = new AuthService(mockSupabase as any, 'TEST_APP');
|
|
1206
|
+
await authServiceWithApp.initialize();
|
|
1207
|
+
|
|
1208
|
+
const mockUser = { id: 'user-123', email: 'test@example.com' };
|
|
1209
|
+
const mockSession = { access_token: 'token', user: mockUser };
|
|
1210
|
+
|
|
1211
|
+
if (authStateCallback) {
|
|
1212
|
+
authStateCallback('SIGNED_IN', mockSession);
|
|
1213
|
+
|
|
1214
|
+
// Wait for async tracking to complete
|
|
1215
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
1216
|
+
expect(mockLoggerFunctions.warn).toHaveBeenCalledWith(
|
|
1217
|
+
'AuthService',
|
|
1218
|
+
expect.stringContaining('Failed to track'),
|
|
1219
|
+
expect.anything()
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
authServiceWithApp.cleanup();
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
it('should not track when session is null', async () => {
|
|
1226
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1227
|
+
|
|
1228
|
+
let authStateCallback: any;
|
|
1229
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1230
|
+
authStateCallback = callback;
|
|
1231
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
await authService.initialize();
|
|
1235
|
+
|
|
1236
|
+
if (authStateCallback) {
|
|
1237
|
+
authStateCallback('SIGNED_OUT', null);
|
|
1238
|
+
|
|
1239
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1240
|
+
|
|
1241
|
+
// Should not track when session is null
|
|
1242
|
+
expect((mockSupabase as any).rpc).not.toHaveBeenCalled();
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it('should not track when user is null in session', async () => {
|
|
1247
|
+
(mockSupabase as any).rpc = vi.fn();
|
|
1248
|
+
|
|
1249
|
+
let authStateCallback: any;
|
|
1250
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1251
|
+
authStateCallback = callback;
|
|
1252
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
await authService.initialize();
|
|
1256
|
+
|
|
1257
|
+
if (authStateCallback) {
|
|
1258
|
+
const sessionWithoutUser = { access_token: 'token', user: null };
|
|
1259
|
+
authStateCallback('SIGNED_IN', sessionWithoutUser);
|
|
1260
|
+
|
|
1261
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1262
|
+
|
|
1263
|
+
// Should not track when user is null
|
|
1264
|
+
expect((mockSupabase as any).rpc).not.toHaveBeenCalled();
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
describe('Auth State Change Events', () => {
|
|
1270
|
+
it('should handle INITIAL_SESSION event with null session', async () => {
|
|
1271
|
+
let authStateCallback: any;
|
|
1272
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1273
|
+
authStateCallback = callback;
|
|
1274
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1278
|
+
data: { session: null },
|
|
1279
|
+
error: null
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
await authService.initialize();
|
|
1283
|
+
|
|
1284
|
+
if (authStateCallback) {
|
|
1285
|
+
authStateCallback('INITIAL_SESSION', null);
|
|
1286
|
+
|
|
1287
|
+
expect(authService.getUser()).toBeNull();
|
|
1288
|
+
expect(authService.getSession()).toBeNull();
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
it('should handle TOKEN_REFRESHED event', async () => {
|
|
1293
|
+
let authStateCallback: any;
|
|
1294
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1295
|
+
authStateCallback = callback;
|
|
1296
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1300
|
+
data: { session: null },
|
|
1301
|
+
error: null
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
await authService.initialize();
|
|
1305
|
+
|
|
1306
|
+
const mockUser = { id: '1', email: 'test@example.com' };
|
|
1307
|
+
const refreshedSession = { access_token: 'new_token', user: mockUser };
|
|
1308
|
+
|
|
1309
|
+
if (authStateCallback) {
|
|
1310
|
+
authStateCallback('TOKEN_REFRESHED', refreshedSession);
|
|
1311
|
+
|
|
1312
|
+
expect(authService.getSession()).toEqual(refreshedSession);
|
|
1313
|
+
expect(authService.getUser()).toEqual(mockUser);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
it('should handle errors in auth state change callback', async () => {
|
|
1318
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1319
|
+
|
|
1320
|
+
mockSupabase.auth.onAuthStateChange.mockImplementation((callback) => {
|
|
1321
|
+
try {
|
|
1322
|
+
// Simulate error in callback
|
|
1323
|
+
callback('INITIAL_SESSION', null);
|
|
1324
|
+
} catch (_error) {
|
|
1325
|
+
// Error should be caught
|
|
1326
|
+
}
|
|
1327
|
+
return { data: { subscription: { unsubscribe: vi.fn() } } };
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
mockSupabase.auth.getSession.mockResolvedValue({
|
|
1331
|
+
data: { session: null },
|
|
1332
|
+
error: null
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
await authService.initialize();
|
|
1336
|
+
|
|
1337
|
+
// Should handle error gracefully
|
|
1338
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
1339
|
+
|
|
1340
|
+
consoleWarnSpy.mockRestore();
|
|
1341
|
+
});
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
describe('Session Restoration State', () => {
|
|
1345
|
+
it('should return session restoration state', () => {
|
|
1346
|
+
const state = authService.getSessionRestorationState();
|
|
1347
|
+
|
|
1348
|
+
expect(state).toHaveProperty('isRestoring');
|
|
1349
|
+
expect(state).toHaveProperty('restorationComplete');
|
|
1350
|
+
expect(state).toHaveProperty('restorationError');
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
it('should return copy of restoration state', () => {
|
|
1354
|
+
const state1 = authService.getSessionRestorationState();
|
|
1355
|
+
const state2 = authService.getSessionRestorationState();
|
|
1356
|
+
|
|
1357
|
+
// Should be different objects (copies)
|
|
1358
|
+
expect(state1).not.toBe(state2);
|
|
1359
|
+
expect(state1).toEqual(state2);
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
});
|