@jmruthers/pace-core 0.5.68 → 0.5.70

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.
Files changed (394) hide show
  1. package/dist/{DataTable-4IUY7BXB.js → DataTable-OSELOGMA.js} +6 -6
  2. package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-DLpF5bbs.d.ts} +78 -2
  3. package/dist/{chunk-OPCWH3A4.js → chunk-4YMVZ76F.js} +7 -6
  4. package/dist/chunk-4YMVZ76F.js.map +1 -0
  5. package/dist/{chunk-NN45OBIS.js → chunk-5G7JA3L5.js} +3 -5
  6. package/dist/{chunk-NN45OBIS.js.map → chunk-5G7JA3L5.js.map} +1 -1
  7. package/dist/{chunk-U6GPOF6J.js → chunk-5NV76BYF.js} +666 -110
  8. package/dist/chunk-5NV76BYF.js.map +1 -0
  9. package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
  10. package/dist/chunk-6RBH67W7.js.map +1 -0
  11. package/dist/{chunk-ZPG4XPV5.js → chunk-BHBMXMLT.js} +5 -7
  12. package/dist/chunk-BHBMXMLT.js.map +1 -0
  13. package/dist/{chunk-ZMS23NS5.js → chunk-FOT3WUV6.js} +3 -5
  14. package/dist/{chunk-ZMS23NS5.js.map → chunk-FOT3WUV6.js.map} +1 -1
  15. package/dist/{chunk-MOJXHWDE.js → chunk-GCUIIBLB.js} +382 -5
  16. package/dist/chunk-GCUIIBLB.js.map +1 -0
  17. package/dist/{chunk-PXWEDX7Y.js → chunk-KWQH4VO3.js} +3 -3
  18. package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
  19. package/dist/chunk-O3NWNXDY.js.map +1 -0
  20. package/dist/{chunk-KRCRNXPD.js → chunk-OTJUAYBG.js} +81 -18
  21. package/dist/chunk-OTJUAYBG.js.map +1 -0
  22. package/dist/chunk-SMJZMKYN.js +141 -0
  23. package/dist/chunk-SMJZMKYN.js.map +1 -0
  24. package/dist/{chunk-UYA6U6H7.js → chunk-V2TE7LOF.js} +4 -4
  25. package/dist/{chunk-L3RV2ALE.js → chunk-VKOCWWVY.js} +6 -1
  26. package/dist/{chunk-L3RV2ALE.js.map → chunk-VKOCWWVY.js.map} +1 -1
  27. package/dist/components.d.ts +4 -79
  28. package/dist/components.js +23 -581
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.d.ts +1 -1
  31. package/dist/hooks.js +9 -6
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.js +32 -19
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.js +6 -7
  37. package/dist/rbac/index.js +6 -6
  38. package/dist/styles/index.js +2 -2
  39. package/dist/theming/runtime.d.ts +4 -3
  40. package/dist/theming/runtime.js +3 -1
  41. package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
  42. package/dist/utils.d.ts +4 -1
  43. package/dist/utils.js +3 -3
  44. package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
  45. package/docs/README.md +22 -10
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +1 -1
  60. package/docs/api/enums/FileCategory.md +129 -0
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/ButtonProps.md +1 -1
  63. package/docs/api/interfaces/CardProps.md +1 -1
  64. package/docs/api/interfaces/ColorPalette.md +1 -1
  65. package/docs/api/interfaces/ColorShade.md +1 -1
  66. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  67. package/docs/api/interfaces/DataTableAction.md +1 -1
  68. package/docs/api/interfaces/DataTableColumn.md +1 -1
  69. package/docs/api/interfaces/DataTableProps.md +1 -1
  70. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  71. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  72. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  73. package/docs/api/interfaces/EventContextType.md +7 -7
  74. package/docs/api/interfaces/EventLogoProps.md +1 -1
  75. package/docs/api/interfaces/EventProviderProps.md +2 -2
  76. package/docs/api/interfaces/FileDisplayProps.md +107 -0
  77. package/docs/api/interfaces/FileMetadata.md +129 -0
  78. package/docs/api/interfaces/FileReference.md +118 -0
  79. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  80. package/docs/api/interfaces/FileUploadOptions.md +85 -0
  81. package/docs/api/interfaces/FileUploadProps.md +1 -1
  82. package/docs/api/interfaces/FooterProps.md +1 -1
  83. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  84. package/docs/api/interfaces/InputProps.md +1 -1
  85. package/docs/api/interfaces/LabelProps.md +1 -1
  86. package/docs/api/interfaces/LoginFormProps.md +1 -1
  87. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  88. package/docs/api/interfaces/NavigationContextType.md +1 -1
  89. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  90. package/docs/api/interfaces/NavigationItem.md +1 -1
  91. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  92. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  93. package/docs/api/interfaces/Organisation.md +1 -1
  94. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  95. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  96. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  97. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  98. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  99. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  100. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  101. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  102. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  103. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  104. package/docs/api/interfaces/PaletteData.md +1 -1
  105. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  106. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  107. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  108. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  109. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  110. package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
  111. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  112. package/docs/api/interfaces/RBACConfig.md +1 -1
  113. package/docs/api/interfaces/RBACContextType.md +1 -1
  114. package/docs/api/interfaces/RBACLogger.md +1 -1
  115. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  116. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  117. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  118. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  119. package/docs/api/interfaces/RouteConfig.md +1 -1
  120. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  121. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  122. package/docs/api/interfaces/StorageConfig.md +1 -1
  123. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  124. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  125. package/docs/api/interfaces/StorageListOptions.md +1 -1
  126. package/docs/api/interfaces/StorageListResult.md +1 -1
  127. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  128. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  129. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  130. package/docs/api/interfaces/StyleImport.md +1 -1
  131. package/docs/api/interfaces/SwitchProps.md +1 -1
  132. package/docs/api/interfaces/ToastActionElement.md +1 -1
  133. package/docs/api/interfaces/ToastProps.md +1 -1
  134. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  135. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  136. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  137. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  139. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  140. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  141. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  142. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  143. package/docs/api/interfaces/UserEventAccess.md +1 -1
  144. package/docs/api/interfaces/UserMenuProps.md +1 -1
  145. package/docs/api/interfaces/UserProfile.md +1 -1
  146. package/docs/api/modules.md +228 -23
  147. package/docs/architecture/services.md +374 -0
  148. package/docs/best-practices/README.md +1 -1
  149. package/docs/best-practices/testing.md +1 -1
  150. package/docs/breaking-changes.md +182 -0
  151. package/docs/common-patterns.md +445 -0
  152. package/docs/core-concepts/authentication.md +26 -11
  153. package/docs/core-concepts/events.md +2 -0
  154. package/docs/core-concepts/organisations.md +2 -0
  155. package/docs/core-concepts/permissions.md +2 -0
  156. package/docs/{INDEX.md → documentation-index.md} +26 -38
  157. package/docs/faq.md +286 -0
  158. package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
  159. package/docs/getting-started/installation-guide.md +284 -0
  160. package/docs/getting-started/quick-start.md +8 -1
  161. package/docs/implementation-guides/app-layout.md +3 -1
  162. package/docs/implementation-guides/data-tables.md +2 -0
  163. package/docs/implementation-guides/dynamic-colors.md +47 -2
  164. package/docs/implementation-guides/event-theming-summary.md +220 -0
  165. package/docs/implementation-guides/forms.md +9 -7
  166. package/docs/implementation-guides/navigation.md +2 -0
  167. package/docs/migration/service-architecture.md +351 -0
  168. package/docs/rbac/README-rbac-rls-integration.md +2 -2
  169. package/docs/rbac/README.md +1 -1
  170. package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
  171. package/docs/rbac/quick-start.md +2 -0
  172. package/docs/rbac/rbac-rls-integration.md +2 -2
  173. package/docs/style-guide.md +136 -1
  174. package/docs/testing/README.md +1 -1
  175. package/docs/troubleshooting/authentication-issues.md +334 -0
  176. package/docs/troubleshooting/common-issues.md +2 -0
  177. package/docs/troubleshooting/styling-issues.md +199 -144
  178. package/docs/usage.md +23 -2
  179. package/package.json +3 -2
  180. package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
  181. package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
  182. package/src/__tests__/fixtures/test-data.ts +90 -0
  183. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
  184. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
  185. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
  186. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
  187. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
  188. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
  189. package/src/__tests__/helpers/component-test-utils.tsx +14 -4
  190. package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
  191. package/src/__tests__/helpers/test-providers.tsx +329 -0
  192. package/src/__tests__/helpers/test-utils.tsx +91 -45
  193. package/src/__tests__/helpers/timer-utils.ts +71 -0
  194. package/src/__tests__/hooks/usePermissions.test.ts +1 -5
  195. package/src/__tests__/integration/UserProfile.test.tsx +1 -5
  196. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
  197. package/src/__tests__/setup.ts +34 -28
  198. package/src/components/Alert/Alert.test.tsx +1 -5
  199. package/src/components/Avatar/Avatar.test.tsx +1 -5
  200. package/src/components/Button/Button.test.tsx +4 -20
  201. package/src/components/Card/Card.test.tsx +1 -5
  202. package/src/components/Checkbox/Checkbox.test.tsx +1 -5
  203. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
  204. package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
  205. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
  206. package/src/components/DataTable/__tests__/styles.test.ts +382 -0
  207. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
  208. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
  209. package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
  210. package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
  211. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
  212. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
  213. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
  214. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
  215. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
  216. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
  217. package/src/components/Footer/Footer.test.tsx +1 -5
  218. package/src/components/Form/Form.test.tsx +22 -35
  219. package/src/components/Header/Header.test.tsx +1 -9
  220. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
  221. package/src/components/Input/Input.test.tsx +2 -10
  222. package/src/components/LoginForm/LoginForm.test.tsx +1 -5
  223. package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
  224. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
  225. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
  226. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -4
  227. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
  228. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
  229. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
  230. package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
  231. package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
  232. package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
  233. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
  234. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
  235. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
  236. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
  237. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
  238. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
  239. package/src/components/Select/Select.bug-test.tsx +69 -0
  240. package/src/components/Select/Select.refactored.tsx +497 -0
  241. package/src/components/Select/Select.test.tsx +42 -49
  242. package/src/components/Select/Select.tsx +5 -2
  243. package/src/components/Select/hooks.ts +254 -0
  244. package/src/components/Switch/Switch.test.tsx +1 -5
  245. package/src/components/Table/__tests__/Table.test.tsx +775 -0
  246. package/src/components/Toast/Toast.test.tsx +15 -8
  247. package/src/components/Tooltip/Tooltip.test.tsx +1 -5
  248. package/src/components/UserMenu/UserMenu.test.tsx +3 -15
  249. package/src/components/__tests__/FileDisplay.test.tsx +575 -0
  250. package/src/components/__tests__/FileUpload.test.tsx +446 -0
  251. package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
  252. package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
  253. package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
  254. package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
  255. package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
  256. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
  257. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
  258. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
  259. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
  260. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  261. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
  262. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
  263. package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
  264. package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
  265. package/src/hooks/index.ts +3 -0
  266. package/src/hooks/public/usePublicEvent.ts +30 -9
  267. package/src/hooks/public/usePublicRouteParams.ts +13 -3
  268. package/src/hooks/services/useAuth.ts +50 -0
  269. package/src/hooks/services/useAuthService.ts +30 -0
  270. package/src/hooks/services/useCurrentEvent.ts +36 -0
  271. package/src/hooks/services/useCurrentOrganisation.ts +52 -0
  272. package/src/hooks/services/useEventService.ts +30 -0
  273. package/src/hooks/services/useInactivityService.ts +30 -0
  274. package/src/hooks/services/useOrganisationService.ts +30 -0
  275. package/src/hooks/services/usePermissions.ts +70 -0
  276. package/src/hooks/services/useRBACService.ts +30 -0
  277. package/src/hooks/useCounter.test.ts +1 -5
  278. package/src/hooks/useEventTheme.ts +86 -0
  279. package/src/hooks/useOrganisationPermissions.test.ts +2 -5
  280. package/src/hooks/useOrganisationSecurity.test.ts +1 -5
  281. package/src/hooks/usePermissionCache.test.ts +1 -5
  282. package/src/hooks/usePermissionCheck.ts +150 -0
  283. package/src/hooks/useSecureDataAccess.test.ts +1 -5
  284. package/src/index.ts +7 -0
  285. package/src/providers/EventProvider.tsx +58 -2
  286. package/src/providers/OrganisationProvider.test.tsx +1 -5
  287. package/src/providers/OrganisationProvider.tsx +56 -4
  288. package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
  289. package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
  290. package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
  291. package/src/providers/__tests__/EventProvider.test.tsx +211 -110
  292. package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
  293. package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
  294. package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
  295. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
  296. package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
  297. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
  298. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
  299. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
  300. package/src/providers/services/AuthServiceProvider.tsx +65 -0
  301. package/src/providers/services/EventServiceProvider.tsx +83 -0
  302. package/src/providers/services/InactivityServiceProvider.tsx +83 -0
  303. package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
  304. package/src/providers/services/RBACServiceProvider.tsx +79 -0
  305. package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
  306. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
  307. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
  308. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
  309. package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
  310. package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
  311. package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
  312. package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
  313. package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
  314. package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
  315. package/src/rbac/api.test.ts +1 -9
  316. package/src/rbac/cache.test.ts +10 -8
  317. package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
  318. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
  319. package/src/rbac/components/NavigationProvider.test.tsx +1 -5
  320. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
  321. package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
  322. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
  323. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
  324. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
  325. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
  326. package/src/rbac/config.test.ts +1 -5
  327. package/src/rbac/hooks/useCan.test.ts +262 -9
  328. package/src/rbac/hooks/usePermissions.test.ts +246 -6
  329. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
  330. package/src/rbac/hooks/useRBAC.test.ts +472 -198
  331. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
  332. package/src/services/AuthService.ts +416 -0
  333. package/src/services/EventService.ts +366 -0
  334. package/src/services/InactivityService.ts +388 -0
  335. package/src/services/OrganisationService.ts +592 -0
  336. package/src/services/RBACService.ts +522 -0
  337. package/src/services/__tests__/AuthService.test.ts +356 -0
  338. package/src/services/__tests__/BaseService.test.ts +314 -0
  339. package/src/services/__tests__/EventService.test.ts +489 -0
  340. package/src/services/__tests__/InactivityService.test.ts +403 -0
  341. package/src/services/__tests__/OrganisationService.test.ts +660 -0
  342. package/src/services/__tests__/RBACService.test.ts +492 -0
  343. package/src/services/base/BaseService.ts +87 -0
  344. package/src/services/interfaces/IAuthService.ts +39 -0
  345. package/src/services/interfaces/IEventService.ts +30 -0
  346. package/src/services/interfaces/IInactivityService.ts +31 -0
  347. package/src/services/interfaces/IOrganisationService.ts +41 -0
  348. package/src/services/interfaces/IRBACService.ts +62 -0
  349. package/src/theming/__tests__/runtime.test.ts +560 -0
  350. package/src/theming/runtime.ts +71 -28
  351. package/src/types/__tests__/file-reference.test.ts +447 -0
  352. package/src/types/__tests__/organisation.test.ts +1133 -0
  353. package/src/types/__tests__/theme.test.ts +830 -0
  354. package/src/types/__tests__/type-validation.test.ts +527 -0
  355. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
  356. package/src/utils/__tests__/debugLogger.test.ts +417 -0
  357. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
  358. package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
  359. package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
  360. package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
  361. package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
  362. package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
  363. package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
  364. package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
  365. package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
  366. package/src/utils/appIdResolver.test.ts +6 -10
  367. package/src/utils/appNameResolver.simple.test.ts +142 -0
  368. package/src/utils/appNameResolver.test.ts +31 -458
  369. package/src/utils/appNameResolver.test.ts.backup +494 -0
  370. package/src/utils/debugLogger.ts +26 -5
  371. package/src/utils/formatDate.test.ts +1 -5
  372. package/src/utils/organisationContext.test.ts +1 -5
  373. package/src/utils/performanceBudgets.ts +3 -4
  374. package/src/utils/secureDataAccess.test.ts +1 -5
  375. package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
  376. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
  377. package/dist/chunk-D7ARGIA3.js.map +0 -1
  378. package/dist/chunk-IPCH4YPT.js +0 -315
  379. package/dist/chunk-IPCH4YPT.js.map +0 -1
  380. package/dist/chunk-KRCRNXPD.js.map +0 -1
  381. package/dist/chunk-MOJXHWDE.js.map +0 -1
  382. package/dist/chunk-N2EUGZRW.js +0 -98
  383. package/dist/chunk-N2EUGZRW.js.map +0 -1
  384. package/dist/chunk-OPCWH3A4.js.map +0 -1
  385. package/dist/chunk-U6GPOF6J.js.map +0 -1
  386. package/dist/chunk-ZPG4XPV5.js.map +0 -1
  387. package/dist/chunk-ZPK5656W.js.map +0 -1
  388. package/docs/getting-started/installation.md +0 -269
  389. package/src/__tests__/REBUILD_PLAN.md +0 -223
  390. package/src/styles/base.css +0 -208
  391. package/src/styles/semantic.css +0 -24
  392. /package/dist/{DataTable-4IUY7BXB.js.map → DataTable-OSELOGMA.js.map} +0 -0
  393. /package/dist/{chunk-PXWEDX7Y.js.map → chunk-KWQH4VO3.js.map} +0 -0
  394. /package/dist/{chunk-UYA6U6H7.js.map → chunk-V2TE7LOF.js.map} +0 -0
@@ -0,0 +1,954 @@
1
+ /**
2
+ * @file RBAC Engine Comprehensive Tests
3
+ * @description Comprehensive test suite for RBACEngine class
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
7
+ import { RBACEngine, createRBACEngine } from '../engine';
8
+ import {
9
+ UUID,
10
+ Permission,
11
+ Scope,
12
+ PermissionCheck,
13
+ AccessLevel,
14
+ Operation,
15
+ OrganisationRole,
16
+ EventAppRole
17
+ } from '../types';
18
+ import { Database } from '../../types/database';
19
+ import { rbacCache } from '../cache';
20
+
21
+ // Mock Supabase client with comprehensive mocking
22
+ const createMockSupabaseClient = () => ({
23
+ from: vi.fn(() => ({
24
+ select: vi.fn().mockReturnThis(),
25
+ eq: vi.fn().mockReturnThis(),
26
+ neq: vi.fn().mockReturnThis(),
27
+ in: vi.fn().mockReturnThis(),
28
+ is: vi.fn().mockReturnThis(),
29
+ lte: vi.fn().mockReturnThis(),
30
+ or: vi.fn().mockReturnThis(),
31
+ single: vi.fn(),
32
+ maybeSingle: vi.fn(),
33
+ })),
34
+ rpc: vi.fn(),
35
+ });
36
+
37
+ describe('RBACEngine - Comprehensive Tests', () => {
38
+ let engine: RBACEngine;
39
+ let mockSupabase: any;
40
+
41
+ beforeEach(() => {
42
+ mockSupabase = createMockSupabaseClient();
43
+ engine = new RBACEngine(mockSupabase as any);
44
+ // Clear cache before each test
45
+ rbacCache.clear();
46
+ });
47
+
48
+ afterEach(() => {
49
+ vi.clearAllMocks();
50
+ // Clear cache after each test
51
+ rbacCache.clear();
52
+ });
53
+
54
+ describe('Constructor and Factory', () => {
55
+ it('creates engine instance with Supabase client', () => {
56
+ expect(engine).toBeInstanceOf(RBACEngine);
57
+ expect(engine).toBeDefined();
58
+ });
59
+
60
+ it('creates engine using factory function', () => {
61
+ const factoryEngine = createRBACEngine(mockSupabase as any);
62
+ expect(factoryEngine).toBeInstanceOf(RBACEngine);
63
+ });
64
+ });
65
+
66
+ describe('Permission Checking - Super Admin Bypass', () => {
67
+ it('allows super admin to bypass all permissions', async () => {
68
+ // Mock super admin check to return true
69
+ mockSupabase.rpc.mockResolvedValue({
70
+ data: [{ has_permission: true, role_name: 'super_admin' }],
71
+ error: null
72
+ });
73
+
74
+ const permissionCheck: PermissionCheck = {
75
+ userId: 'super-admin-123' as UUID,
76
+ scope: { organisationId: 'org-123' as UUID },
77
+ permission: 'manage:everything' as Permission
78
+ };
79
+
80
+ const result = await engine.isPermitted(permissionCheck);
81
+ expect(result).toBe(true);
82
+ });
83
+
84
+ it('denies non-super admin users', async () => {
85
+ // Mock super admin check to return false
86
+ mockSupabase.rpc.mockResolvedValue({
87
+ data: [{ has_permission: false, role_name: null }],
88
+ error: null
89
+ });
90
+
91
+ // Mock app config
92
+ mockSupabase.from.mockReturnValue({
93
+ select: vi.fn().mockReturnThis(),
94
+ eq: vi.fn().mockReturnThis(),
95
+ single: vi.fn().mockResolvedValue({
96
+ data: { requires_event: false },
97
+ error: null
98
+ })
99
+ });
100
+
101
+ // Mock empty roles and permissions
102
+ mockSupabase.from.mockImplementation(() => ({
103
+ select: vi.fn().mockReturnThis(),
104
+ eq: vi.fn().mockReturnThis(),
105
+ lte: vi.fn().mockReturnThis(),
106
+ or: vi.fn().mockReturnThis(),
107
+ data: [],
108
+ error: null
109
+ }));
110
+
111
+ const permissionCheck: PermissionCheck = {
112
+ userId: 'regular-user-123' as UUID,
113
+ scope: { organisationId: 'org-123' as UUID },
114
+ permission: 'manage:everything' as Permission
115
+ };
116
+
117
+ const result = await engine.isPermitted(permissionCheck);
118
+ expect(result).toBe(false);
119
+ });
120
+ });
121
+
122
+ describe('Permission Checking - Context Validation', () => {
123
+ it('validates organisation context for organisation-based apps', async () => {
124
+ // Mock super admin check to return false
125
+ mockSupabase.rpc.mockResolvedValue({
126
+ data: [{ has_permission: false, role_name: null }],
127
+ error: null
128
+ });
129
+
130
+ // Mock app config - organisation-based app
131
+ mockSupabase.from.mockImplementation((table: string) => {
132
+ if (table === 'rbac_apps') {
133
+ return {
134
+ select: vi.fn().mockReturnThis(),
135
+ eq: vi.fn().mockReturnThis(),
136
+ single: vi.fn().mockResolvedValue({
137
+ data: { requires_event: false },
138
+ error: null
139
+ })
140
+ };
141
+ }
142
+ return {
143
+ select: vi.fn().mockReturnThis(),
144
+ eq: vi.fn().mockReturnThis(),
145
+ data: [],
146
+ error: null
147
+ };
148
+ });
149
+
150
+ const permissionCheck: PermissionCheck = {
151
+ userId: 'user-123' as UUID,
152
+ scope: { organisationId: 'org-123' as UUID, appId: 'app-123' as UUID },
153
+ permission: 'read:users' as Permission
154
+ };
155
+
156
+ const result = await engine.isPermitted(permissionCheck);
157
+ expect(result).toBe(false); // Should fail due to no permissions granted
158
+ });
159
+
160
+ it('validates event context for event-based apps', async () => {
161
+ // Mock super admin check to return false
162
+ mockSupabase.rpc.mockResolvedValue({
163
+ data: [{ has_permission: false, role_name: null }],
164
+ error: null
165
+ });
166
+
167
+ // Mock app config - event-based app
168
+ mockSupabase.from.mockImplementation((table: string) => {
169
+ if (table === 'rbac_apps') {
170
+ return {
171
+ select: vi.fn().mockReturnThis(),
172
+ eq: vi.fn().mockReturnThis(),
173
+ single: vi.fn().mockResolvedValue({
174
+ data: { requires_event: true },
175
+ error: null
176
+ })
177
+ };
178
+ }
179
+ if (table === 'event') {
180
+ return {
181
+ select: vi.fn().mockReturnThis(),
182
+ eq: vi.fn().mockReturnThis(),
183
+ single: vi.fn().mockResolvedValue({
184
+ data: { organisation_id: 'org-123' },
185
+ error: null
186
+ })
187
+ };
188
+ }
189
+ return {
190
+ select: vi.fn().mockReturnThis(),
191
+ eq: vi.fn().mockReturnThis(),
192
+ data: [],
193
+ error: null
194
+ };
195
+ });
196
+
197
+ const permissionCheck: PermissionCheck = {
198
+ userId: 'user-123' as UUID,
199
+ scope: {
200
+ organisationId: 'org-123' as UUID,
201
+ eventId: 'event-123',
202
+ appId: 'app-123' as UUID
203
+ },
204
+ permission: 'read:users' as Permission
205
+ };
206
+
207
+ const result = await engine.isPermitted(permissionCheck);
208
+ expect(result).toBe(false); // Should fail due to no permissions granted
209
+ });
210
+
211
+ it('rejects permission check without required context', async () => {
212
+ // Mock super admin check to return false
213
+ mockSupabase.rpc.mockResolvedValue({
214
+ data: [{ has_permission: false, role_name: null }],
215
+ error: null
216
+ });
217
+
218
+ // Mock app config - organisation-based app
219
+ mockSupabase.from.mockImplementation((table: string) => {
220
+ if (table === 'rbac_apps') {
221
+ return {
222
+ select: vi.fn().mockReturnThis(),
223
+ eq: vi.fn().mockReturnThis(),
224
+ single: vi.fn().mockResolvedValue({
225
+ data: { requires_event: false },
226
+ error: null
227
+ })
228
+ };
229
+ }
230
+ return {
231
+ select: vi.fn().mockReturnThis(),
232
+ eq: vi.fn().mockReturnThis(),
233
+ data: [],
234
+ error: null
235
+ };
236
+ });
237
+
238
+ const permissionCheck: PermissionCheck = {
239
+ userId: 'user-123' as UUID,
240
+ scope: { appId: 'app-123' as UUID }, // Missing organisationId
241
+ permission: 'read:users' as Permission
242
+ };
243
+
244
+ const result = await engine.isPermitted(permissionCheck);
245
+ expect(result).toBe(false);
246
+ });
247
+ });
248
+
249
+ describe('Permission Checking - Grant Collection', () => {
250
+ it('collects and processes page-level grants', async () => {
251
+ // Mock super admin check to return false
252
+ mockSupabase.rpc.mockResolvedValue({
253
+ data: [{ has_permission: false, role_name: null }],
254
+ error: null
255
+ });
256
+
257
+ // Mock app config
258
+ mockSupabase.from.mockImplementation((table: string) => {
259
+ if (table === 'rbac_apps') {
260
+ return {
261
+ select: vi.fn().mockReturnThis(),
262
+ eq: vi.fn().mockReturnThis(),
263
+ single: vi.fn().mockResolvedValue({
264
+ data: { requires_event: false },
265
+ error: null
266
+ })
267
+ };
268
+ }
269
+ if (table === 'rbac_app_pages') {
270
+ return {
271
+ select: vi.fn().mockReturnThis(),
272
+ eq: vi.fn().mockReturnThis(),
273
+ single: vi.fn().mockResolvedValue({
274
+ data: { id: 'page-123', page_name: 'users' },
275
+ error: null
276
+ })
277
+ };
278
+ }
279
+ return {
280
+ select: vi.fn().mockReturnThis(),
281
+ eq: vi.fn().mockReturnThis(),
282
+ lte: vi.fn().mockReturnThis(),
283
+ or: vi.fn().mockReturnThis(),
284
+ data: [],
285
+ error: null
286
+ };
287
+ });
288
+
289
+ // Mock RPC call for page permissions
290
+ mockSupabase.rpc.mockImplementation((functionName: string) => {
291
+ if (functionName === 'rbac_permissions_get') {
292
+ return Promise.resolve({
293
+ data: [
294
+ {
295
+ permission_type: 'read:page.users',
296
+ role_name: 'org_admin',
297
+ has_permission: true,
298
+ granted_at: '2023-01-01T00:00:00Z'
299
+ }
300
+ ],
301
+ error: null
302
+ });
303
+ }
304
+ return Promise.resolve({ data: null, error: null });
305
+ });
306
+
307
+ // Mock organisation roles to include org_admin
308
+ mockSupabase.from.mockImplementation((table: string) => {
309
+ if (table === 'rbac_apps') {
310
+ return {
311
+ select: vi.fn().mockReturnThis(),
312
+ eq: vi.fn().mockReturnThis(),
313
+ single: vi.fn().mockResolvedValue({
314
+ data: { requires_event: false },
315
+ error: null
316
+ })
317
+ };
318
+ }
319
+ if (table === 'rbac_app_pages') {
320
+ return {
321
+ select: vi.fn().mockReturnThis(),
322
+ eq: vi.fn().mockReturnThis(),
323
+ single: vi.fn().mockResolvedValue({
324
+ data: { id: 'page-123', page_name: 'users' },
325
+ error: null
326
+ })
327
+ };
328
+ }
329
+ if (table === 'rbac_organisation_roles') {
330
+ return {
331
+ select: vi.fn().mockReturnThis(),
332
+ eq: vi.fn().mockReturnThis(),
333
+ lte: vi.fn().mockReturnThis(),
334
+ or: vi.fn().mockReturnThis(),
335
+ data: [{ role: 'org_admin', status: 'active', valid_from: '2023-01-01', valid_to: null }],
336
+ error: null
337
+ };
338
+ }
339
+ return {
340
+ select: vi.fn().mockReturnThis(),
341
+ eq: vi.fn().mockReturnThis(),
342
+ lte: vi.fn().mockReturnThis(),
343
+ or: vi.fn().mockReturnThis(),
344
+ data: [],
345
+ error: null
346
+ };
347
+ });
348
+
349
+ const permissionCheck: PermissionCheck = {
350
+ userId: 'user-123' as UUID,
351
+ scope: {
352
+ organisationId: 'org-123' as UUID,
353
+ appId: 'app-123' as UUID
354
+ },
355
+ permission: 'read:page.users' as Permission,
356
+ pageId: 'users'
357
+ };
358
+
359
+ const result = await engine.isPermitted(permissionCheck);
360
+ expect(result).toBe(true);
361
+ });
362
+
363
+ it('collects and processes global grants', async () => {
364
+ // Mock super admin check to return false
365
+ mockSupabase.rpc.mockResolvedValue({
366
+ data: [{ has_permission: false, role_name: null }],
367
+ error: null
368
+ });
369
+
370
+ // Mock app config
371
+ mockSupabase.from.mockImplementation((table: string) => {
372
+ if (table === 'rbac_apps') {
373
+ return {
374
+ select: vi.fn().mockReturnThis(),
375
+ eq: vi.fn().mockReturnThis(),
376
+ single: vi.fn().mockResolvedValue({
377
+ data: { requires_event: false },
378
+ error: null
379
+ })
380
+ };
381
+ }
382
+ if (table === 'rbac_global_roles') {
383
+ return {
384
+ select: vi.fn().mockReturnThis(),
385
+ eq: vi.fn().mockReturnThis(),
386
+ lte: vi.fn().mockReturnThis(),
387
+ or: vi.fn().mockReturnThis(),
388
+ data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
389
+ error: null
390
+ };
391
+ }
392
+ return {
393
+ select: vi.fn().mockReturnThis(),
394
+ eq: vi.fn().mockReturnThis(),
395
+ lte: vi.fn().mockReturnThis(),
396
+ or: vi.fn().mockReturnThis(),
397
+ data: [],
398
+ error: null
399
+ };
400
+ });
401
+
402
+ const permissionCheck: PermissionCheck = {
403
+ userId: 'user-123' as UUID,
404
+ scope: { organisationId: 'org-123' as UUID },
405
+ permission: 'read:*' as Permission
406
+ };
407
+
408
+ const result = await engine.isPermitted(permissionCheck);
409
+ expect(result).toBe(true);
410
+ });
411
+ });
412
+
413
+ describe('Permission Matching Logic', () => {
414
+ it('matches exact permissions', async () => {
415
+ // Mock super admin check to return false
416
+ mockSupabase.rpc.mockResolvedValue({
417
+ data: [{ has_permission: false, role_name: null }],
418
+ error: null
419
+ });
420
+
421
+ // Mock app config
422
+ mockSupabase.from.mockImplementation((table: string) => {
423
+ if (table === 'rbac_apps') {
424
+ return {
425
+ select: vi.fn().mockReturnThis(),
426
+ eq: vi.fn().mockReturnThis(),
427
+ single: vi.fn().mockResolvedValue({
428
+ data: { requires_event: false },
429
+ error: null
430
+ })
431
+ };
432
+ }
433
+ if (table === 'rbac_global_roles') {
434
+ return {
435
+ select: vi.fn().mockReturnThis(),
436
+ eq: vi.fn().mockReturnThis(),
437
+ lte: vi.fn().mockReturnThis(),
438
+ or: vi.fn().mockReturnThis(),
439
+ data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
440
+ error: null
441
+ };
442
+ }
443
+ return {
444
+ select: vi.fn().mockReturnThis(),
445
+ eq: vi.fn().mockReturnThis(),
446
+ lte: vi.fn().mockReturnThis(),
447
+ or: vi.fn().mockReturnThis(),
448
+ data: [],
449
+ error: null
450
+ };
451
+ });
452
+
453
+ const permissionCheck: PermissionCheck = {
454
+ userId: 'user-123' as UUID,
455
+ scope: { organisationId: 'org-123' as UUID },
456
+ permission: 'read:users' as Permission
457
+ };
458
+
459
+ const result = await engine.isPermitted(permissionCheck);
460
+ expect(result).toBe(true); // Should match read:* wildcard
461
+ });
462
+
463
+ it('matches wildcard permissions', async () => {
464
+ // Mock super admin check to return false
465
+ mockSupabase.rpc.mockResolvedValue({
466
+ data: [{ has_permission: false, role_name: null }],
467
+ error: null
468
+ });
469
+
470
+ // Mock app config
471
+ mockSupabase.from.mockImplementation((table: string) => {
472
+ if (table === 'rbac_apps') {
473
+ return {
474
+ select: vi.fn().mockReturnThis(),
475
+ eq: vi.fn().mockReturnThis(),
476
+ single: vi.fn().mockResolvedValue({
477
+ data: { requires_event: false },
478
+ error: null
479
+ })
480
+ };
481
+ }
482
+ if (table === 'rbac_global_roles') {
483
+ return {
484
+ select: vi.fn().mockReturnThis(),
485
+ eq: vi.fn().mockReturnThis(),
486
+ lte: vi.fn().mockReturnThis(),
487
+ or: vi.fn().mockReturnThis(),
488
+ data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
489
+ error: null
490
+ };
491
+ }
492
+ return {
493
+ select: vi.fn().mockReturnThis(),
494
+ eq: vi.fn().mockReturnThis(),
495
+ lte: vi.fn().mockReturnThis(),
496
+ or: vi.fn().mockReturnThis(),
497
+ data: [],
498
+ error: null
499
+ };
500
+ });
501
+
502
+ const permissionCheck: PermissionCheck = {
503
+ userId: 'user-123' as UUID,
504
+ scope: { organisationId: 'org-123' as UUID },
505
+ permission: 'read:organisation.users' as Permission
506
+ };
507
+
508
+ const result = await engine.isPermitted(permissionCheck);
509
+ expect(result).toBe(true); // Should match read:* wildcard
510
+ });
511
+ });
512
+
513
+ describe('Access Level Resolution', () => {
514
+ it('resolves super admin access level', async () => {
515
+ // Mock super admin check to return true
516
+ mockSupabase.rpc.mockResolvedValue({
517
+ data: [{ has_permission: true, role_name: 'super_admin' }],
518
+ error: null
519
+ });
520
+
521
+ const scope: Scope = { organisationId: 'org-123' as UUID };
522
+ const accessLevel = await engine.getAccessLevel({
523
+ userId: 'super-admin-123' as UUID,
524
+ scope
525
+ });
526
+
527
+ expect(accessLevel).toBe('super');
528
+ });
529
+
530
+ it('resolves organisation admin access level', async () => {
531
+ // Mock super admin check to return false
532
+ mockSupabase.rpc.mockResolvedValue({
533
+ data: [{ has_permission: false, role_name: null }],
534
+ error: null
535
+ });
536
+
537
+ // Mock app config
538
+ mockSupabase.from.mockImplementation((table: string) => {
539
+ if (table === 'rbac_apps') {
540
+ return {
541
+ select: vi.fn().mockReturnThis(),
542
+ eq: vi.fn().mockReturnThis(),
543
+ single: vi.fn().mockResolvedValue({
544
+ data: { requires_event: false },
545
+ error: null
546
+ })
547
+ };
548
+ }
549
+ if (table === 'rbac_organisation_roles') {
550
+ return {
551
+ select: vi.fn().mockReturnThis(),
552
+ eq: vi.fn().mockReturnThis(),
553
+ single: vi.fn().mockResolvedValue({
554
+ data: { role: 'org_admin' },
555
+ error: null
556
+ })
557
+ };
558
+ }
559
+ return {
560
+ select: vi.fn().mockReturnThis(),
561
+ eq: vi.fn().mockReturnThis(),
562
+ data: [],
563
+ error: null
564
+ };
565
+ });
566
+
567
+ const scope: Scope = { organisationId: 'org-123' as UUID };
568
+ const accessLevel = await engine.getAccessLevel({
569
+ userId: 'user-123' as UUID,
570
+ scope
571
+ });
572
+
573
+ expect(accessLevel).toBe('admin');
574
+ });
575
+
576
+ it('resolves event-app roles access level', async () => {
577
+ // Mock super admin check to return false
578
+ mockSupabase.rpc.mockResolvedValue({
579
+ data: [{ has_permission: false, role_name: null }],
580
+ error: null
581
+ });
582
+
583
+ // Mock app config
584
+ mockSupabase.from.mockImplementation((table: string) => {
585
+ if (table === 'rbac_apps') {
586
+ return {
587
+ select: vi.fn().mockReturnThis(),
588
+ eq: vi.fn().mockReturnThis(),
589
+ single: vi.fn().mockResolvedValue({
590
+ data: { requires_event: true },
591
+ error: null
592
+ })
593
+ };
594
+ }
595
+ if (table === 'event') {
596
+ return {
597
+ select: vi.fn().mockReturnThis(),
598
+ eq: vi.fn().mockReturnThis(),
599
+ single: vi.fn().mockResolvedValue({
600
+ data: { organisation_id: 'org-123' },
601
+ error: null
602
+ })
603
+ };
604
+ }
605
+ if (table === 'rbac_event_app_roles') {
606
+ return {
607
+ select: vi.fn().mockReturnThis(),
608
+ eq: vi.fn().mockReturnThis(),
609
+ lte: vi.fn().mockReturnThis(),
610
+ or: vi.fn().mockReturnThis(),
611
+ single: vi.fn().mockResolvedValue({
612
+ data: { role: 'event_admin', status: 'active', valid_from: '2023-01-01', valid_to: null },
613
+ error: null
614
+ })
615
+ };
616
+ }
617
+ return {
618
+ select: vi.fn().mockReturnThis(),
619
+ eq: vi.fn().mockReturnThis(),
620
+ lte: vi.fn().mockReturnThis(),
621
+ or: vi.fn().mockReturnThis(),
622
+ single: vi.fn().mockResolvedValue({
623
+ data: null,
624
+ error: { code: 'PGRST116' }
625
+ })
626
+ };
627
+ });
628
+
629
+ const scope: Scope = {
630
+ organisationId: 'org-123' as UUID,
631
+ eventId: 'event-123',
632
+ appId: 'app-123' as UUID
633
+ };
634
+ const accessLevel = await engine.getAccessLevel({
635
+ userId: 'user-123' as UUID,
636
+ scope
637
+ });
638
+
639
+ expect(accessLevel).toBe('admin');
640
+ });
641
+
642
+ it('defaults to viewer access level', async () => {
643
+ // Mock super admin check to return false
644
+ mockSupabase.rpc.mockResolvedValue({
645
+ data: [{ has_permission: false, role_name: null }],
646
+ error: null
647
+ });
648
+
649
+ // Mock app config
650
+ mockSupabase.from.mockImplementation((table: string) => {
651
+ if (table === 'rbac_apps') {
652
+ return {
653
+ select: vi.fn().mockReturnThis(),
654
+ eq: vi.fn().mockReturnThis(),
655
+ single: vi.fn().mockResolvedValue({
656
+ data: { requires_event: false },
657
+ error: null
658
+ })
659
+ };
660
+ }
661
+ return {
662
+ select: vi.fn().mockReturnThis(),
663
+ eq: vi.fn().mockReturnThis(),
664
+ single: vi.fn().mockResolvedValue({
665
+ data: null,
666
+ error: { code: 'PGRST116' }
667
+ })
668
+ };
669
+ });
670
+
671
+ const scope: Scope = { organisationId: 'org-123' as UUID };
672
+ const accessLevel = await engine.getAccessLevel({
673
+ userId: 'user-123' as UUID,
674
+ scope
675
+ });
676
+
677
+ expect(accessLevel).toBe('viewer');
678
+ });
679
+ });
680
+
681
+ describe('Permission Map Generation', () => {
682
+ it('returns empty map for super admin', async () => {
683
+ // Mock super admin check to return true
684
+ mockSupabase.rpc.mockResolvedValue({
685
+ data: [{ has_permission: true, role_name: 'super_admin' }],
686
+ error: null
687
+ });
688
+
689
+ const scope: Scope = { organisationId: 'org-123' as UUID };
690
+ const permissionMap = await engine.getPermissionMap({
691
+ userId: 'super-admin-123' as UUID,
692
+ scope
693
+ });
694
+
695
+ expect(permissionMap).toEqual({});
696
+ });
697
+
698
+ it('generates permission map for regular user', async () => {
699
+ // Mock super admin check to return false
700
+ mockSupabase.rpc.mockResolvedValue({
701
+ data: [{ has_permission: false, role_name: null }],
702
+ error: null
703
+ });
704
+
705
+ // Mock app config
706
+ mockSupabase.from.mockImplementation((table: string) => {
707
+ if (table === 'rbac_apps') {
708
+ return {
709
+ select: vi.fn().mockReturnThis(),
710
+ eq: vi.fn().mockReturnThis(),
711
+ single: vi.fn().mockResolvedValue({
712
+ data: { requires_event: false },
713
+ error: null
714
+ })
715
+ };
716
+ }
717
+ if (table === 'rbac_app_pages') {
718
+ return {
719
+ select: vi.fn().mockReturnThis(),
720
+ eq: vi.fn().mockReturnThis(),
721
+ data: [
722
+ { id: 'page-1', page_name: 'users' },
723
+ { id: 'page-2', page_name: 'settings' }
724
+ ],
725
+ error: null
726
+ };
727
+ }
728
+ return {
729
+ select: vi.fn().mockReturnThis(),
730
+ eq: vi.fn().mockReturnThis(),
731
+ data: [],
732
+ error: null
733
+ };
734
+ });
735
+
736
+ // Mock permission checks
737
+ const originalIsPermitted = engine.isPermitted.bind(engine);
738
+ vi.spyOn(engine, 'isPermitted').mockImplementation(async (input) => {
739
+ if (input.permission === 'read:users' || input.permission === 'create:users') {
740
+ return true;
741
+ }
742
+ return false;
743
+ });
744
+
745
+ const scope: Scope = {
746
+ organisationId: 'org-123' as UUID,
747
+ appId: 'app-123' as UUID
748
+ };
749
+ const permissionMap = await engine.getPermissionMap({
750
+ userId: 'user-123' as UUID,
751
+ scope
752
+ });
753
+
754
+ expect(permissionMap).toBeDefined();
755
+ expect(typeof permissionMap).toBe('object');
756
+ });
757
+ });
758
+
759
+ describe('Error Handling', () => {
760
+ it('handles database errors gracefully', async () => {
761
+ // Mock super admin check to return false
762
+ mockSupabase.rpc.mockResolvedValue({
763
+ data: [{ has_permission: false, role_name: null }],
764
+ error: null
765
+ });
766
+
767
+ // Mock database error
768
+ mockSupabase.from.mockImplementation(() => {
769
+ throw new Error('Database connection failed');
770
+ });
771
+
772
+ const permissionCheck: PermissionCheck = {
773
+ userId: 'user-123' as UUID,
774
+ scope: { organisationId: 'org-123' as UUID },
775
+ permission: 'read:users' as Permission
776
+ };
777
+
778
+ const result = await engine.isPermitted(permissionCheck);
779
+ expect(result).toBe(false);
780
+ });
781
+
782
+ it('handles invalid inputs gracefully', async () => {
783
+ const invalidInputs = [
784
+ { userId: '' as UUID, scope: { organisationId: 'org-123' as UUID }, permission: 'read:users' as Permission },
785
+ { userId: 'user-123' as UUID, scope: {} as any, permission: 'read:users' as Permission },
786
+ { userId: 'user-123' as UUID, scope: { organisationId: 'org-123' as UUID }, permission: '' as Permission },
787
+ ];
788
+
789
+ for (const input of invalidInputs) {
790
+ const result = await engine.isPermitted(input);
791
+ expect(result).toBe(false);
792
+ }
793
+ });
794
+
795
+ it('handles RPC errors gracefully', async () => {
796
+ // Mock RPC error
797
+ mockSupabase.rpc.mockRejectedValue(new Error('RPC call failed'));
798
+
799
+ const permissionCheck: PermissionCheck = {
800
+ userId: 'user-123' as UUID,
801
+ scope: { organisationId: 'org-123' as UUID },
802
+ permission: 'read:users' as Permission
803
+ };
804
+
805
+ const result = await engine.isPermitted(permissionCheck);
806
+ expect(result).toBe(false);
807
+ });
808
+ });
809
+
810
+ describe('Cache Integration', () => {
811
+ it('uses cache for repeated permission checks', async () => {
812
+ // Mock super admin check to return false
813
+ mockSupabase.rpc.mockResolvedValue({
814
+ data: [{ has_permission: false, role_name: null }],
815
+ error: null
816
+ });
817
+
818
+ // Mock app config
819
+ mockSupabase.from.mockImplementation((table: string) => {
820
+ if (table === 'rbac_apps') {
821
+ return {
822
+ select: vi.fn().mockReturnThis(),
823
+ eq: vi.fn().mockReturnThis(),
824
+ single: vi.fn().mockResolvedValue({
825
+ data: { requires_event: false },
826
+ error: null
827
+ })
828
+ };
829
+ }
830
+ return {
831
+ select: vi.fn().mockReturnThis(),
832
+ eq: vi.fn().mockReturnThis(),
833
+ data: [],
834
+ error: null
835
+ };
836
+ });
837
+
838
+ const permissionCheck: PermissionCheck = {
839
+ userId: 'user-123' as UUID,
840
+ scope: { organisationId: 'org-123' as UUID },
841
+ permission: 'read:users' as Permission
842
+ };
843
+
844
+ // First call
845
+ const result1 = await engine.isPermitted(permissionCheck);
846
+ expect(result1).toBe(false);
847
+
848
+ // Second call should use cache
849
+ const result2 = await engine.isPermitted(permissionCheck);
850
+ expect(result2).toBe(false);
851
+
852
+ // Verify RPC was called only once (cached on second call)
853
+ expect(mockSupabase.rpc).toHaveBeenCalledTimes(1);
854
+ });
855
+ });
856
+
857
+ describe('Page ID Resolution', () => {
858
+ it('resolves page names to UUIDs', async () => {
859
+ // Mock super admin check to return false
860
+ mockSupabase.rpc.mockResolvedValue({
861
+ data: [{ has_permission: false, role_name: null }],
862
+ error: null
863
+ });
864
+
865
+ // Mock app config
866
+ mockSupabase.from.mockImplementation((table: string) => {
867
+ if (table === 'rbac_apps') {
868
+ return {
869
+ select: vi.fn().mockReturnThis(),
870
+ eq: vi.fn().mockReturnThis(),
871
+ single: vi.fn().mockResolvedValue({
872
+ data: { requires_event: false },
873
+ error: null
874
+ })
875
+ };
876
+ }
877
+ if (table === 'rbac_app_pages') {
878
+ return {
879
+ select: vi.fn().mockReturnThis(),
880
+ eq: vi.fn().mockReturnThis(),
881
+ single: vi.fn().mockResolvedValue({
882
+ data: { id: 'page-uuid-123', page_name: 'users' },
883
+ error: null
884
+ })
885
+ };
886
+ }
887
+ return {
888
+ select: vi.fn().mockReturnThis(),
889
+ eq: vi.fn().mockReturnThis(),
890
+ data: [],
891
+ error: null
892
+ };
893
+ });
894
+
895
+ const permissionCheck: PermissionCheck = {
896
+ userId: 'user-123' as UUID,
897
+ scope: {
898
+ organisationId: 'org-123' as UUID,
899
+ appId: 'app-123' as UUID
900
+ },
901
+ permission: 'read:page.users' as Permission,
902
+ pageId: 'users' // Page name, not UUID
903
+ };
904
+
905
+ const result = await engine.isPermitted(permissionCheck);
906
+ expect(result).toBe(false); // Should fail due to no permissions granted
907
+ });
908
+ });
909
+
910
+ describe('Security Context Integration', () => {
911
+ it('validates security context when provided', async () => {
912
+ // Mock super admin check to return false
913
+ mockSupabase.rpc.mockResolvedValue({
914
+ data: [{ has_permission: false, role_name: null }],
915
+ error: null
916
+ });
917
+
918
+ // Mock app config
919
+ mockSupabase.from.mockImplementation((table: string) => {
920
+ if (table === 'rbac_apps') {
921
+ return {
922
+ select: vi.fn().mockReturnThis(),
923
+ eq: vi.fn().mockReturnThis(),
924
+ single: vi.fn().mockResolvedValue({
925
+ data: { requires_event: false },
926
+ error: null
927
+ })
928
+ };
929
+ }
930
+ return {
931
+ select: vi.fn().mockReturnThis(),
932
+ eq: vi.fn().mockReturnThis(),
933
+ data: [],
934
+ error: null
935
+ };
936
+ });
937
+
938
+ const permissionCheck: PermissionCheck = {
939
+ userId: 'user-123' as UUID,
940
+ scope: { organisationId: 'org-123' as UUID },
941
+ permission: 'read:users' as Permission
942
+ };
943
+
944
+ const securityContext = {
945
+ userId: 'user-123',
946
+ ipAddress: '192.168.1.1',
947
+ userAgent: 'test-agent'
948
+ };
949
+
950
+ const result = await engine.isPermitted(permissionCheck, securityContext);
951
+ expect(result).toBe(false);
952
+ });
953
+ });
954
+ });