@jmruthers/pace-core 0.5.114 → 0.5.116

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 (236) hide show
  1. package/dist/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
  2. package/dist/{DataTable-3JRLZXER.js → DataTable-ZOAKQ3SU.js} +10 -9
  3. package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
  4. package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
  5. package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
  6. package/dist/{chunk-4OX5PXHX.js → chunk-2GJ5GL77.js} +4 -5
  7. package/dist/chunk-2GJ5GL77.js.map +1 -0
  8. package/dist/{chunk-5YIZFEUQ.js → chunk-2LM4QQGH.js} +31 -35
  9. package/dist/chunk-2LM4QQGH.js.map +1 -0
  10. package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
  11. package/dist/chunk-3DBFLLLU.js.map +1 -0
  12. package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
  13. package/dist/chunk-ECOVPXYS.js.map +1 -0
  14. package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
  15. package/dist/chunk-KA3PSVNV.js.map +1 -0
  16. package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
  17. package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
  18. package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
  19. package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
  20. package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
  21. package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
  22. package/dist/chunk-P3PUOL6B.js.map +1 -0
  23. package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
  24. package/dist/chunk-PHDAXDHB.js.map +1 -0
  25. package/dist/chunk-UJI6WSMD.js +201 -0
  26. package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
  27. package/dist/{chunk-JHWQNJP3.js → chunk-UKZWNQMB.js} +65 -19
  28. package/dist/{chunk-JHWQNJP3.js.map → chunk-UKZWNQMB.js.map} +1 -1
  29. package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
  30. package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
  31. package/dist/components.d.ts +1 -1
  32. package/dist/components.js +12 -11
  33. package/dist/components.js.map +1 -1
  34. package/dist/hooks.d.ts +1 -1
  35. package/dist/hooks.js +10 -9
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +19 -16
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.d.ts +2 -2
  41. package/dist/providers.js +3 -2
  42. package/dist/rbac/index.d.ts +82 -1
  43. package/dist/rbac/index.js +13 -10
  44. package/dist/{useToast-DRah6K-g.d.ts → useToast-Cs_g32bg.d.ts} +8 -6
  45. package/dist/utils.js +6 -4
  46. package/dist/utils.js.map +1 -1
  47. package/dist/validation.js +3 -1
  48. package/dist/validation.js.map +1 -1
  49. package/docs/README.md +4 -0
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/MissingUserContextError.md +1 -1
  54. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  55. package/docs/api/classes/PermissionDeniedError.md +1 -1
  56. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +35 -12
  58. package/docs/api/classes/RBACCache.md +1 -1
  59. package/docs/api/classes/RBACEngine.md +1 -1
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/interfaces/AggregateConfig.md +1 -1
  66. package/docs/api/interfaces/ButtonProps.md +1 -1
  67. package/docs/api/interfaces/CardProps.md +1 -1
  68. package/docs/api/interfaces/ColorPalette.md +1 -1
  69. package/docs/api/interfaces/ColorShade.md +1 -1
  70. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  71. package/docs/api/interfaces/DataRecord.md +1 -1
  72. package/docs/api/interfaces/DataTableAction.md +1 -1
  73. package/docs/api/interfaces/DataTableColumn.md +1 -1
  74. package/docs/api/interfaces/DataTableProps.md +1 -1
  75. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  76. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  77. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  78. package/docs/api/interfaces/EventAppRoleData.md +71 -0
  79. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  80. package/docs/api/interfaces/FileMetadata.md +1 -1
  81. package/docs/api/interfaces/FileReference.md +1 -1
  82. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  83. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  84. package/docs/api/interfaces/FileUploadProps.md +1 -1
  85. package/docs/api/interfaces/FooterProps.md +1 -1
  86. package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
  87. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  88. package/docs/api/interfaces/InputProps.md +1 -1
  89. package/docs/api/interfaces/LabelProps.md +1 -1
  90. package/docs/api/interfaces/LoginFormProps.md +1 -1
  91. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  92. package/docs/api/interfaces/NavigationContextType.md +1 -1
  93. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  94. package/docs/api/interfaces/NavigationItem.md +1 -1
  95. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  97. package/docs/api/interfaces/Organisation.md +1 -1
  98. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  99. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  100. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  101. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  102. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  103. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  104. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  105. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  106. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  107. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  108. package/docs/api/interfaces/PaletteData.md +1 -1
  109. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  110. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  112. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  113. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  116. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  117. package/docs/api/interfaces/RBACConfig.md +1 -1
  118. package/docs/api/interfaces/RBACLogger.md +1 -1
  119. package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
  120. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  121. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  122. package/docs/api/interfaces/RoleManagementResult.md +52 -0
  123. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  124. package/docs/api/interfaces/RouteConfig.md +1 -1
  125. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  126. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  127. package/docs/api/interfaces/StorageConfig.md +1 -1
  128. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  129. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  130. package/docs/api/interfaces/StorageListOptions.md +1 -1
  131. package/docs/api/interfaces/StorageListResult.md +1 -1
  132. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  133. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  134. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  135. package/docs/api/interfaces/StyleImport.md +1 -1
  136. package/docs/api/interfaces/SwitchProps.md +1 -1
  137. package/docs/api/interfaces/ToastActionElement.md +1 -1
  138. package/docs/api/interfaces/ToastProps.md +1 -1
  139. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  141. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  143. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  145. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  147. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  148. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  150. package/docs/api/interfaces/UserEventAccess.md +1 -1
  151. package/docs/api/interfaces/UserMenuProps.md +1 -1
  152. package/docs/api/interfaces/UserProfile.md +1 -1
  153. package/docs/api/modules.md +43 -16
  154. package/docs/architecture/rpc-function-standards.md +193 -0
  155. package/package.json +1 -1
  156. package/src/__tests__/TEST_STANDARD.md +244 -2
  157. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
  158. package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
  159. package/src/components/DataTable/components/DataTableCore.tsx +32 -17
  160. package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
  161. package/src/components/DataTable/components/EditableRow.tsx +18 -1
  162. package/src/components/DataTable/components/ImportModal.tsx +25 -2
  163. package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
  164. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
  165. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
  166. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
  167. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
  168. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
  169. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
  170. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
  171. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
  172. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
  173. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
  174. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
  175. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
  176. package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
  177. package/src/components/EventSelector/EventSelector.tsx +5 -25
  178. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
  179. package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
  180. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
  181. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
  182. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
  183. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
  184. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
  185. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
  186. package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
  187. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
  188. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
  189. package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
  190. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
  191. package/src/components/Select/Select.tsx +8 -0
  192. package/src/components/Toast/Toast.test.tsx +8 -7
  193. package/src/components/Toast/Toast.tsx +4 -4
  194. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
  195. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
  196. package/src/hooks/useEventTheme.ts +49 -18
  197. package/src/hooks/usePermissionCache.ts +5 -3
  198. package/src/hooks/useSecureDataAccess.ts +11 -1
  199. package/src/hooks/useToast.ts +11 -12
  200. package/src/providers/services/EventServiceProvider.tsx +15 -8
  201. package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
  202. package/src/rbac/audit.test.ts +206 -0
  203. package/src/rbac/audit.ts +37 -2
  204. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
  205. package/src/rbac/errors.test.ts +340 -0
  206. package/src/rbac/hooks/index.ts +9 -0
  207. package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
  208. package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
  209. package/src/rbac/hooks/useRoleManagement.ts +255 -0
  210. package/src/services/AuthService.ts +10 -0
  211. package/src/services/EventService.ts +111 -50
  212. package/src/services/__tests__/AuthService.test.ts +1 -1
  213. package/src/services/__tests__/EventService.test.ts +60 -45
  214. package/src/services/interfaces/IEventService.ts +1 -1
  215. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
  216. package/src/utils/__tests__/logger.unit.test.ts +398 -0
  217. package/src/utils/__tests__/validation.unit.test.ts +225 -1
  218. package/src/utils/file-reference.test.ts +214 -0
  219. package/dist/chunk-3OGQLOJM.js.map +0 -1
  220. package/dist/chunk-4OX5PXHX.js.map +0 -1
  221. package/dist/chunk-5CDJCTOO.js +0 -190
  222. package/dist/chunk-5YIZFEUQ.js.map +0 -1
  223. package/dist/chunk-F6QB26OS.js.map +0 -1
  224. package/dist/chunk-KTHLNIMA.js.map +0 -1
  225. package/dist/chunk-OO3V7W4H.js.map +0 -1
  226. package/dist/chunk-ZPXWJA4H.js.map +0 -1
  227. package/src/rbac/audit-enhanced.ts +0 -351
  228. /package/dist/{DataTable-3JRLZXER.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
  229. /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
  230. /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
  231. /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
  232. /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
  233. /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
  234. /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
  235. /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
  236. /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
@@ -0,0 +1,340 @@
1
+ /**
2
+ * @file RBAC Error Categorization Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/Errors
5
+ * @since 1.0.0
6
+ *
7
+ * Comprehensive tests for error categorization and security event mapping.
8
+ */
9
+
10
+ import { describe, it, expect } from 'vitest';
11
+ import {
12
+ RBACErrorCategory,
13
+ categorizeError,
14
+ mapErrorCategoryToSecurityEventType,
15
+ SecurityEventType,
16
+ } from './errors';
17
+ import {
18
+ RBACError,
19
+ PermissionDeniedError,
20
+ OrganisationContextRequiredError,
21
+ InvalidScopeError,
22
+ MissingUserContextError,
23
+ } from './types';
24
+
25
+ describe('RBAC Error Categorization', () => {
26
+ describe('categorizeError', () => {
27
+ describe('RBAC Error Instances', () => {
28
+ it('categorizes PermissionDeniedError as AUTHORIZATION', () => {
29
+ const error = new PermissionDeniedError('Access denied', 'user-123', 'read:users');
30
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHORIZATION);
31
+ });
32
+
33
+ it('categorizes OrganisationContextRequiredError as VALIDATION', () => {
34
+ const error = new OrganisationContextRequiredError();
35
+ expect(categorizeError(error)).toBe(RBACErrorCategory.VALIDATION);
36
+ });
37
+
38
+ it('categorizes InvalidScopeError as VALIDATION', () => {
39
+ const error = new InvalidScopeError('Invalid scope provided');
40
+ expect(categorizeError(error)).toBe(RBACErrorCategory.VALIDATION);
41
+ });
42
+
43
+ it('categorizes MissingUserContextError as AUTHENTICATION', () => {
44
+ const error = new MissingUserContextError();
45
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHENTICATION);
46
+ });
47
+ });
48
+
49
+ describe('RBACError with Codes', () => {
50
+ it('categorizes PERMISSION_DENIED code as AUTHORIZATION', () => {
51
+ const error = new RBACError('Permission denied', 'PERMISSION_DENIED');
52
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHORIZATION);
53
+ });
54
+
55
+ it('categorizes ORGANISATION_CONTEXT_REQUIRED code as VALIDATION', () => {
56
+ const error = new RBACError('Organisation required', 'ORGANISATION_CONTEXT_REQUIRED');
57
+ expect(categorizeError(error)).toBe(RBACErrorCategory.VALIDATION);
58
+ });
59
+
60
+ it('categorizes INVALID_SCOPE code as VALIDATION', () => {
61
+ const error = new RBACError('Invalid scope', 'INVALID_SCOPE');
62
+ expect(categorizeError(error)).toBe(RBACErrorCategory.VALIDATION);
63
+ });
64
+
65
+ it('categorizes MISSING_USER_CONTEXT code as AUTHENTICATION', () => {
66
+ const error = new RBACError('User context required', 'MISSING_USER_CONTEXT');
67
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHENTICATION);
68
+ });
69
+
70
+ it('returns UNKNOWN for unknown RBACError codes', () => {
71
+ const error = new RBACError('Unknown error', 'UNKNOWN_CODE');
72
+ expect(categorizeError(error)).toBe(RBACErrorCategory.UNKNOWN);
73
+ });
74
+ });
75
+
76
+ describe('HTTP Status Codes', () => {
77
+ it('categorizes 429 status as RATE_LIMIT', () => {
78
+ const error = { status: 429 };
79
+ expect(categorizeError(error)).toBe(RBACErrorCategory.RATE_LIMIT);
80
+ });
81
+
82
+ it('categorizes 401 status as AUTHENTICATION', () => {
83
+ const error = { status: 401 };
84
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHENTICATION);
85
+ });
86
+
87
+ it('categorizes 403 status as AUTHORIZATION', () => {
88
+ const error = { status: 403 };
89
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHORIZATION);
90
+ });
91
+
92
+ it('categorizes 500+ status as DATABASE', () => {
93
+ const error500 = { status: 500 };
94
+ const error503 = { status: 503 };
95
+ const error599 = { status: 599 };
96
+
97
+ expect(categorizeError(error500)).toBe(RBACErrorCategory.DATABASE);
98
+ expect(categorizeError(error503)).toBe(RBACErrorCategory.DATABASE);
99
+ expect(categorizeError(error599)).toBe(RBACErrorCategory.DATABASE);
100
+ });
101
+ });
102
+
103
+ describe('Error Code Strings', () => {
104
+ it('categorizes network-related codes as NETWORK', () => {
105
+ const error1 = { code: 'NETWORK_ERROR' };
106
+ const error2 = { code: 'NetworkTimeout' };
107
+ const error3 = { code: 'network_failure' };
108
+
109
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.NETWORK);
110
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.NETWORK);
111
+ expect(categorizeError(error3)).toBe(RBACErrorCategory.NETWORK);
112
+ });
113
+
114
+ it('categorizes database-related codes as DATABASE', () => {
115
+ const error1 = { code: 'POSTGRES_ERROR' };
116
+ const error2 = { code: 'database_connection_failed' };
117
+ const error3 = { code: 'DB_TIMEOUT' };
118
+
119
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.DATABASE);
120
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.DATABASE);
121
+ expect(categorizeError(error3)).toBe(RBACErrorCategory.DATABASE);
122
+ });
123
+
124
+ it('categorizes rate limit codes as RATE_LIMIT', () => {
125
+ const error1 = { code: 'RATE_LIMIT_EXCEEDED' };
126
+ const error2 = { code: 'rate_limit' };
127
+
128
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.RATE_LIMIT);
129
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.RATE_LIMIT);
130
+ });
131
+
132
+ it('categorizes auth-related codes as AUTHENTICATION', () => {
133
+ const error1 = { code: 'AUTH_ERROR' };
134
+ const error2 = { code: 'authentication_failed' };
135
+
136
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.AUTHENTICATION);
137
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.AUTHENTICATION);
138
+ });
139
+
140
+ it('categorizes permission-related codes as AUTHORIZATION', () => {
141
+ const error1 = { code: 'PERMISSION_DENIED' };
142
+ const error2 = { code: 'permission_error' };
143
+
144
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.AUTHORIZATION);
145
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.AUTHORIZATION);
146
+ });
147
+
148
+ it('categorizes validation-related codes as VALIDATION', () => {
149
+ const error1 = { code: 'INVALID_SCOPE' };
150
+ const error2 = { code: 'invalid_input' };
151
+
152
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.VALIDATION);
153
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.VALIDATION);
154
+ });
155
+ });
156
+
157
+ describe('Error Messages', () => {
158
+ it('categorizes timeout messages as NETWORK', () => {
159
+ const error1 = 'Request timeout';
160
+ const error2 = new Error('Network timeout occurred');
161
+ // Object with message property is handled by code path before normalize
162
+ // So we test it as an Error instance or string
163
+ const error3 = new Error('fetch timeout');
164
+
165
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.NETWORK);
166
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.NETWORK);
167
+ expect(categorizeError(error3)).toBe(RBACErrorCategory.NETWORK);
168
+ });
169
+
170
+ it('categorizes network messages as NETWORK', () => {
171
+ const error1 = 'Network error';
172
+ const error2 = new Error('Network failure');
173
+
174
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.NETWORK);
175
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.NETWORK);
176
+ });
177
+
178
+ it('categorizes database messages as DATABASE', () => {
179
+ const error1 = 'Postgres connection failed';
180
+ const error2 = 'Database error occurred';
181
+ const error3 = 'Connection to database lost';
182
+
183
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.DATABASE);
184
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.DATABASE);
185
+ expect(categorizeError(error3)).toBe(RBACErrorCategory.DATABASE);
186
+ });
187
+
188
+ it('categorizes rate limit messages as RATE_LIMIT', () => {
189
+ const error1 = 'Rate limit exceeded';
190
+ const error2 = 'Too many requests';
191
+
192
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.RATE_LIMIT);
193
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.RATE_LIMIT);
194
+ });
195
+
196
+ it('categorizes permission messages as AUTHORIZATION', () => {
197
+ const error1 = 'Permission denied';
198
+ const error2 = 'Forbidden access';
199
+
200
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.AUTHORIZATION);
201
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.AUTHORIZATION);
202
+ });
203
+
204
+ it('categorizes auth messages as AUTHENTICATION', () => {
205
+ const error1 = 'Auth token expired';
206
+ const error2 = 'Session invalid';
207
+
208
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.AUTHENTICATION);
209
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.AUTHENTICATION);
210
+ });
211
+
212
+ it('categorizes validation messages as VALIDATION', () => {
213
+ const error1 = 'Invalid scope provided';
214
+ const error2 = 'Validation error';
215
+ const error3 = 'Invalid input';
216
+
217
+ expect(categorizeError(error1)).toBe(RBACErrorCategory.VALIDATION);
218
+ expect(categorizeError(error2)).toBe(RBACErrorCategory.VALIDATION);
219
+ expect(categorizeError(error3)).toBe(RBACErrorCategory.VALIDATION);
220
+ });
221
+ });
222
+
223
+ describe('Edge Cases', () => {
224
+ it('returns UNKNOWN for null', () => {
225
+ expect(categorizeError(null)).toBe(RBACErrorCategory.UNKNOWN);
226
+ });
227
+
228
+ it('returns UNKNOWN for undefined', () => {
229
+ expect(categorizeError(undefined)).toBe(RBACErrorCategory.UNKNOWN);
230
+ });
231
+
232
+ it('returns UNKNOWN for empty string', () => {
233
+ expect(categorizeError('')).toBe(RBACErrorCategory.UNKNOWN);
234
+ });
235
+
236
+ it('returns UNKNOWN for unknown error types', () => {
237
+ const error = { someProperty: 'value' };
238
+ expect(categorizeError(error)).toBe(RBACErrorCategory.UNKNOWN);
239
+ });
240
+
241
+ it('handles non-string code values', () => {
242
+ const error = { code: 123 };
243
+ expect(categorizeError(error)).toBe(RBACErrorCategory.UNKNOWN);
244
+ });
245
+
246
+ it('handles non-number status values', () => {
247
+ const error = { status: '500' };
248
+ expect(categorizeError(error)).toBe(RBACErrorCategory.UNKNOWN);
249
+ });
250
+ });
251
+
252
+ describe('Priority Order', () => {
253
+ it('prioritizes RBAC error instances over status codes', () => {
254
+ const error = new PermissionDeniedError('Denied', 'user-123', 'read:users');
255
+ // Even if status is set, RBAC error instance takes priority
256
+ expect(categorizeError(error)).toBe(RBACErrorCategory.AUTHORIZATION);
257
+ });
258
+
259
+ it('prioritizes status codes over message parsing', () => {
260
+ const error = {
261
+ status: 429,
262
+ message: 'Network timeout' // Would normally be NETWORK
263
+ };
264
+ expect(categorizeError(error)).toBe(RBACErrorCategory.RATE_LIMIT);
265
+ });
266
+
267
+ it('prioritizes code parsing over message parsing', () => {
268
+ const error = {
269
+ code: 'NETWORK_ERROR',
270
+ message: 'Database error' // Would normally be DATABASE
271
+ };
272
+ expect(categorizeError(error)).toBe(RBACErrorCategory.NETWORK);
273
+ });
274
+ });
275
+ });
276
+
277
+ describe('mapErrorCategoryToSecurityEventType', () => {
278
+ it('maps AUTHORIZATION to permission_denied', () => {
279
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.AUTHORIZATION))
280
+ .toBe('permission_denied');
281
+ });
282
+
283
+ it('maps NETWORK to network_error', () => {
284
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.NETWORK))
285
+ .toBe('network_error');
286
+ });
287
+
288
+ it('maps DATABASE to database_error', () => {
289
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.DATABASE))
290
+ .toBe('database_error');
291
+ });
292
+
293
+ it('maps VALIDATION to validation_error', () => {
294
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.VALIDATION))
295
+ .toBe('validation_error');
296
+ });
297
+
298
+ it('maps RATE_LIMIT to rate_limit_error', () => {
299
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.RATE_LIMIT))
300
+ .toBe('rate_limit_error');
301
+ });
302
+
303
+ it('maps AUTHENTICATION to authentication_error', () => {
304
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.AUTHENTICATION))
305
+ .toBe('authentication_error');
306
+ });
307
+
308
+ it('maps UNKNOWN to unknown_error', () => {
309
+ expect(mapErrorCategoryToSecurityEventType(RBACErrorCategory.UNKNOWN))
310
+ .toBe('unknown_error');
311
+ });
312
+
313
+ it('maps default case to unknown_error', () => {
314
+ // Test with a value that shouldn't happen but ensures default works
315
+ const result = mapErrorCategoryToSecurityEventType('UNEXPECTED' as RBACErrorCategory);
316
+ expect(result).toBe('unknown_error');
317
+ });
318
+ });
319
+
320
+ describe('Integration', () => {
321
+ it('categorizes and maps error end-to-end', () => {
322
+ const error = new PermissionDeniedError('Access denied', 'user-123', 'read:users');
323
+ const category = categorizeError(error);
324
+ const eventType = mapErrorCategoryToSecurityEventType(category);
325
+
326
+ expect(category).toBe(RBACErrorCategory.AUTHORIZATION);
327
+ expect(eventType).toBe('permission_denied');
328
+ });
329
+
330
+ it('handles network errors through full pipeline', () => {
331
+ const error = { status: 500, message: 'Database connection failed' };
332
+ const category = categorizeError(error);
333
+ const eventType = mapErrorCategoryToSecurityEventType(category);
334
+
335
+ expect(category).toBe(RBACErrorCategory.DATABASE);
336
+ expect(eventType).toBe('database_error');
337
+ });
338
+ });
339
+ });
340
+
@@ -21,3 +21,12 @@ export {
21
21
  useHasAllPermissions,
22
22
  useCachedPermissions,
23
23
  } from './usePermissions';
24
+
25
+ // Export role management hook
26
+ export { useRoleManagement } from './useRoleManagement';
27
+ export type {
28
+ EventAppRoleData,
29
+ RevokeEventAppRoleParams,
30
+ GrantEventAppRoleParams,
31
+ RoleManagementResult,
32
+ } from './useRoleManagement';