@jmruthers/pace-core 0.5.70 → 0.5.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-OSELOGMA.js → DataTable-RCFGXSPQ.js} +5 -5
- package/dist/{api-A4SUYPPV.js → api-DDMUKIUD.js} +2 -2
- package/dist/{chunk-5NV76BYF.js → chunk-2PDTIGS4.js} +6 -6
- package/dist/{chunk-GCUIIBLB.js → chunk-4CET7YQI.js} +4 -3
- package/dist/chunk-4CET7YQI.js.map +1 -0
- package/dist/{chunk-FOT3WUV6.js → chunk-67FGPOHX.js} +3 -3
- package/dist/{chunk-V2TE7LOF.js → chunk-BUM2ZPYC.js} +12 -12
- package/dist/{chunk-BHBMXMLT.js → chunk-C353TCFY.js} +2 -2
- package/dist/{chunk-NHR52QAQ.js → chunk-FGMFQSHX.js} +8 -7
- package/dist/chunk-FGMFQSHX.js.map +1 -0
- package/dist/{chunk-KWQH4VO3.js → chunk-GDIBOLKV.js} +2 -2
- package/dist/{chunk-OTJUAYBG.js → chunk-MTI7X73I.js} +5 -5
- package/dist/{chunk-4YMVZ76F.js → chunk-WMYLD5WP.js} +71 -66
- package/dist/chunk-WMYLD5WP.js.map +1 -0
- package/dist/{chunk-5G7JA3L5.js → chunk-XC4ZCSO4.js} +2 -2
- package/dist/components.js +7 -7
- package/dist/hooks.js +4 -4
- package/dist/index.js +10 -10
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +4 -0
- package/dist/rbac/index.js +5 -5
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +3 -3
- package/docs/implementation-guides/data-tables.md +191 -0
- package/package.json +1 -1
- package/src/components/DataTable/components/DataTableBody.tsx +38 -27
- package/src/components/DataTable/components/UnifiedTableBody.tsx +72 -64
- package/src/rbac/__tests__/engine.comprehensive.test.ts +150 -78
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +52 -50
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +39 -19
- package/src/rbac/engine.test.ts +4 -0
- package/src/rbac/engine.ts +16 -7
- package/src/rbac/providers/RBACProvider.tsx +15 -4
- package/dist/chunk-4YMVZ76F.js.map +0 -1
- package/dist/chunk-GCUIIBLB.js.map +0 -1
- package/dist/chunk-NHR52QAQ.js.map +0 -1
- /package/dist/{DataTable-OSELOGMA.js.map → DataTable-RCFGXSPQ.js.map} +0 -0
- /package/dist/{api-A4SUYPPV.js.map → api-DDMUKIUD.js.map} +0 -0
- /package/dist/{chunk-5NV76BYF.js.map → chunk-2PDTIGS4.js.map} +0 -0
- /package/dist/{chunk-FOT3WUV6.js.map → chunk-67FGPOHX.js.map} +0 -0
- /package/dist/{chunk-V2TE7LOF.js.map → chunk-BUM2ZPYC.js.map} +0 -0
- /package/dist/{chunk-BHBMXMLT.js.map → chunk-C353TCFY.js.map} +0 -0
- /package/dist/{chunk-KWQH4VO3.js.map → chunk-GDIBOLKV.js.map} +0 -0
- /package/dist/{chunk-OTJUAYBG.js.map → chunk-MTI7X73I.js.map} +0 -0
- /package/dist/{chunk-5G7JA3L5.js.map → chunk-XC4ZCSO4.js.map} +0 -0
|
@@ -134,6 +134,12 @@ const renderEditField = (
|
|
|
134
134
|
) => {
|
|
135
135
|
const columnDef = column.columnDef;
|
|
136
136
|
|
|
137
|
+
// Check if column is editable (default: true)
|
|
138
|
+
if (columnDef.editable === false) {
|
|
139
|
+
// Return the original value as text if column is not editable
|
|
140
|
+
return <span className="text-sm text-gray-600">{value || ''}</span>;
|
|
141
|
+
}
|
|
142
|
+
|
|
137
143
|
// Check for custom field type
|
|
138
144
|
if (columnDef.fieldType === 'select' && columnDef.fieldOptions) {
|
|
139
145
|
// Use editAccessorKey if specified, otherwise use the column id
|
|
@@ -435,39 +441,44 @@ export function DataTableBody<TData extends Record<string, any>>({
|
|
|
435
441
|
// Regular row (non-grouped or when grouping is disabled)
|
|
436
442
|
return (
|
|
437
443
|
<tr key={row.id}>
|
|
438
|
-
{row.getVisibleCells().map((cell) =>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
{row.getVisibleCells().map((cell) => {
|
|
445
|
+
const columnDef = cell.column.columnDef as any;
|
|
446
|
+
const isColumnEditable = columnDef.editable !== false;
|
|
447
|
+
|
|
448
|
+
return (
|
|
449
|
+
<td key={cell.id}>
|
|
450
|
+
{isEditing && cell.column.id !== 'actions' && isColumnEditable ? (
|
|
451
|
+
// Check if column has a custom cell renderer - if so, use it in edit mode
|
|
452
|
+
cell.column.columnDef.cell ? (
|
|
453
|
+
flexRender(cell.column.columnDef.cell, {
|
|
454
|
+
...cell.getContext(),
|
|
455
|
+
getIsEditing: () => true, // Always true in edit mode
|
|
456
|
+
setValue: (value: any) => {
|
|
457
|
+
if (typeof value === 'object' && value !== null) {
|
|
458
|
+
onEditingDataChange({ ...editingData, ...value });
|
|
459
|
+
} else {
|
|
460
|
+
onEditingDataChange({ ...editingData, [cell.column.id]: value });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
) : (
|
|
465
|
+
// Fall back to default edit field rendering when no custom cell renderer
|
|
466
|
+
renderEditField(cell.column, editingData[cell.column.id], (value) => {
|
|
447
467
|
if (typeof value === 'object' && value !== null) {
|
|
468
|
+
// Handle editAccessorKey case
|
|
448
469
|
onEditingDataChange({ ...editingData, ...value });
|
|
449
470
|
} else {
|
|
471
|
+
// Handle simple value case
|
|
450
472
|
onEditingDataChange({ ...editingData, [cell.column.id]: value });
|
|
451
473
|
}
|
|
452
|
-
}
|
|
453
|
-
|
|
474
|
+
}, editingData)
|
|
475
|
+
)
|
|
454
476
|
) : (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
} else {
|
|
461
|
-
// Handle simple value case
|
|
462
|
-
onEditingDataChange({ ...editingData, [cell.column.id]: value });
|
|
463
|
-
}
|
|
464
|
-
}, editingData)
|
|
465
|
-
)
|
|
466
|
-
) : (
|
|
467
|
-
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
468
|
-
)}
|
|
469
|
-
</td>
|
|
470
|
-
))}
|
|
477
|
+
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
478
|
+
)}
|
|
479
|
+
</td>
|
|
480
|
+
);
|
|
481
|
+
})}
|
|
471
482
|
</tr>
|
|
472
483
|
);
|
|
473
484
|
})}
|
|
@@ -494,72 +494,80 @@ const MemoizedRow = ({
|
|
|
494
494
|
|
|
495
495
|
{/* Cell content */}
|
|
496
496
|
<div className={`flex-1 ${cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}`}>
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
497
|
+
{(() => {
|
|
498
|
+
const columnDef = cell.column.columnDef as any;
|
|
499
|
+
const isColumnEditable = columnDef.editable !== false;
|
|
500
|
+
const shouldRenderEditField = isEditing && cell.column.id !== 'actions' && isColumnEditable;
|
|
501
|
+
|
|
502
|
+
if (shouldRenderEditField) {
|
|
503
|
+
// Render edit input fields for editable columns in edit mode
|
|
504
|
+
return cell.column.columnDef.cell ? (
|
|
505
|
+
flexRender(cell.column.columnDef.cell, {
|
|
506
|
+
...cell.getContext(),
|
|
507
|
+
hierarchical: hierarchical,
|
|
508
|
+
isParent: isParent,
|
|
509
|
+
isChild: isChild,
|
|
510
|
+
isHierarchical: isHierarchical,
|
|
511
|
+
rowId: rowId,
|
|
512
|
+
isExpanded: isExpanded,
|
|
513
|
+
hasChildren: hasChildren,
|
|
514
|
+
getIsEditing: () => true, // Always true in edit mode
|
|
515
|
+
setValue: (value: any) => {
|
|
516
|
+
if (typeof value === 'object' && value !== null) {
|
|
517
|
+
onEditingDataChange?.({ ...editingData, ...value });
|
|
518
|
+
} else {
|
|
519
|
+
onEditingDataChange?.({ ...editingData, [cell.column.id]: value });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
) : (
|
|
524
|
+
renderEditField(cell.column, editingData?.[cell.column.id], (value) => {
|
|
511
525
|
if (typeof value === 'object' && value !== null) {
|
|
512
526
|
onEditingDataChange?.({ ...editingData, ...value });
|
|
513
527
|
} else {
|
|
514
528
|
onEditingDataChange?.({ ...editingData, [cell.column.id]: value });
|
|
515
529
|
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
rbac={rbac}
|
|
558
|
-
permissions={permissions}
|
|
559
|
-
/>
|
|
560
|
-
)
|
|
561
|
-
) : (
|
|
562
|
-
flexRender(cell.column.columnDef.cell, {
|
|
530
|
+
}, editingData)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Render normal cell (not in edit mode, or column not editable, or is actions column)
|
|
535
|
+
if (cell.column.id === 'actions') {
|
|
536
|
+
return isEditing ? (
|
|
537
|
+
<div className="flex gap-1">
|
|
538
|
+
<button
|
|
539
|
+
onClick={onSaveEditing}
|
|
540
|
+
className="h-8 w-8 p-0 hover:bg-muted/50 flex items-center justify-center"
|
|
541
|
+
title="Save changes"
|
|
542
|
+
>
|
|
543
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
544
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
545
|
+
</svg>
|
|
546
|
+
</button>
|
|
547
|
+
<button
|
|
548
|
+
onClick={onCancelEditing}
|
|
549
|
+
className="h-8 w-8 p-0 hover:bg-muted/50 flex items-center justify-center"
|
|
550
|
+
title="Cancel changes"
|
|
551
|
+
>
|
|
552
|
+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
553
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
554
|
+
</svg>
|
|
555
|
+
</button>
|
|
556
|
+
</div>
|
|
557
|
+
) : (
|
|
558
|
+
<ActionButtons
|
|
559
|
+
row={row}
|
|
560
|
+
actions={actions}
|
|
561
|
+
isEditing={isEditing}
|
|
562
|
+
isParent={isParent}
|
|
563
|
+
hierarchical={!!hierarchical}
|
|
564
|
+
rbac={rbac}
|
|
565
|
+
permissions={permissions}
|
|
566
|
+
/>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return flexRender(cell.column.columnDef.cell, {
|
|
563
571
|
...cell.getContext(),
|
|
564
572
|
hierarchical: hierarchical,
|
|
565
573
|
isParent: isParent,
|
|
@@ -567,9 +575,9 @@ const MemoizedRow = ({
|
|
|
567
575
|
isHierarchical: isHierarchical,
|
|
568
576
|
rowId: rowId,
|
|
569
577
|
isExpanded: isExpanded,
|
|
570
|
-
hasChildren: hasChildren
|
|
571
|
-
})
|
|
572
|
-
)}
|
|
578
|
+
hasChildren: hasChildren,
|
|
579
|
+
});
|
|
580
|
+
})()}
|
|
573
581
|
</div>
|
|
574
582
|
</div>
|
|
575
583
|
</td>
|
|
@@ -28,6 +28,10 @@ const createMockSupabaseClient = () => ({
|
|
|
28
28
|
is: vi.fn().mockReturnThis(),
|
|
29
29
|
lte: vi.fn().mockReturnThis(),
|
|
30
30
|
or: vi.fn().mockReturnThis(),
|
|
31
|
+
limit: vi.fn().mockResolvedValue({
|
|
32
|
+
data: [],
|
|
33
|
+
error: null
|
|
34
|
+
}),
|
|
31
35
|
single: vi.fn(),
|
|
32
36
|
maybeSingle: vi.fn(),
|
|
33
37
|
})),
|
|
@@ -65,10 +69,16 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
65
69
|
|
|
66
70
|
describe('Permission Checking - Super Admin Bypass', () => {
|
|
67
71
|
it('allows super admin to bypass all permissions', async () => {
|
|
68
|
-
// Mock
|
|
69
|
-
mockSupabase.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
// Mock the global roles query to return super_admin role
|
|
73
|
+
mockSupabase.from.mockReturnValueOnce({
|
|
74
|
+
select: vi.fn().mockReturnThis(),
|
|
75
|
+
eq: vi.fn().mockReturnThis(),
|
|
76
|
+
lte: vi.fn().mockReturnThis(),
|
|
77
|
+
or: vi.fn().mockReturnThis(),
|
|
78
|
+
limit: vi.fn().mockResolvedValue({
|
|
79
|
+
data: [{ role: 'super_admin' }],
|
|
80
|
+
error: null
|
|
81
|
+
})
|
|
72
82
|
});
|
|
73
83
|
|
|
74
84
|
const permissionCheck: PermissionCheck = {
|
|
@@ -332,15 +342,31 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
332
342
|
eq: vi.fn().mockReturnThis(),
|
|
333
343
|
lte: vi.fn().mockReturnThis(),
|
|
334
344
|
or: vi.fn().mockReturnThis(),
|
|
345
|
+
limit: vi.fn().mockResolvedValue({
|
|
346
|
+
data: [{ role: 'org_admin', status: 'active', valid_from: '2023-01-01', valid_to: null }],
|
|
347
|
+
error: null
|
|
348
|
+
}),
|
|
335
349
|
data: [{ role: 'org_admin', status: 'active', valid_from: '2023-01-01', valid_to: null }],
|
|
336
350
|
error: null
|
|
337
351
|
};
|
|
338
352
|
}
|
|
353
|
+
if (table === 'rbac_global_roles') {
|
|
354
|
+
return {
|
|
355
|
+
select: vi.fn().mockReturnThis(),
|
|
356
|
+
eq: vi.fn().mockReturnThis(),
|
|
357
|
+
lte: vi.fn().mockReturnThis(),
|
|
358
|
+
or: vi.fn().mockReturnThis(),
|
|
359
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
360
|
+
data: [],
|
|
361
|
+
error: null
|
|
362
|
+
};
|
|
363
|
+
}
|
|
339
364
|
return {
|
|
340
365
|
select: vi.fn().mockReturnThis(),
|
|
341
366
|
eq: vi.fn().mockReturnThis(),
|
|
342
367
|
lte: vi.fn().mockReturnThis(),
|
|
343
368
|
or: vi.fn().mockReturnThis(),
|
|
369
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
344
370
|
data: [],
|
|
345
371
|
error: null
|
|
346
372
|
};
|
|
@@ -361,12 +387,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
361
387
|
});
|
|
362
388
|
|
|
363
389
|
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
390
|
// Mock app config
|
|
371
391
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
372
392
|
if (table === 'rbac_apps') {
|
|
@@ -385,6 +405,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
385
405
|
eq: vi.fn().mockReturnThis(),
|
|
386
406
|
lte: vi.fn().mockReturnThis(),
|
|
387
407
|
or: vi.fn().mockReturnThis(),
|
|
408
|
+
limit: vi.fn().mockResolvedValue({
|
|
409
|
+
data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
|
|
410
|
+
error: null
|
|
411
|
+
}),
|
|
388
412
|
data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
|
|
389
413
|
error: null
|
|
390
414
|
};
|
|
@@ -394,6 +418,7 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
394
418
|
eq: vi.fn().mockReturnThis(),
|
|
395
419
|
lte: vi.fn().mockReturnThis(),
|
|
396
420
|
or: vi.fn().mockReturnThis(),
|
|
421
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
397
422
|
data: [],
|
|
398
423
|
error: null
|
|
399
424
|
};
|
|
@@ -436,8 +461,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
436
461
|
eq: vi.fn().mockReturnThis(),
|
|
437
462
|
lte: vi.fn().mockReturnThis(),
|
|
438
463
|
or: vi.fn().mockReturnThis(),
|
|
439
|
-
|
|
440
|
-
|
|
464
|
+
limit: vi.fn().mockResolvedValue({
|
|
465
|
+
data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
|
|
466
|
+
error: null
|
|
467
|
+
})
|
|
441
468
|
};
|
|
442
469
|
}
|
|
443
470
|
return {
|
|
@@ -445,8 +472,7 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
445
472
|
eq: vi.fn().mockReturnThis(),
|
|
446
473
|
lte: vi.fn().mockReturnThis(),
|
|
447
474
|
or: vi.fn().mockReturnThis(),
|
|
448
|
-
data: [],
|
|
449
|
-
error: null
|
|
475
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
450
476
|
};
|
|
451
477
|
});
|
|
452
478
|
|
|
@@ -461,12 +487,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
461
487
|
});
|
|
462
488
|
|
|
463
489
|
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
490
|
// Mock app config
|
|
471
491
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
472
492
|
if (table === 'rbac_apps') {
|
|
@@ -485,8 +505,10 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
485
505
|
eq: vi.fn().mockReturnThis(),
|
|
486
506
|
lte: vi.fn().mockReturnThis(),
|
|
487
507
|
or: vi.fn().mockReturnThis(),
|
|
488
|
-
|
|
489
|
-
|
|
508
|
+
limit: vi.fn().mockResolvedValue({
|
|
509
|
+
data: [{ role: 'super_admin', valid_from: '2023-01-01', valid_to: null }],
|
|
510
|
+
error: null
|
|
511
|
+
})
|
|
490
512
|
};
|
|
491
513
|
}
|
|
492
514
|
return {
|
|
@@ -494,8 +516,7 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
494
516
|
eq: vi.fn().mockReturnThis(),
|
|
495
517
|
lte: vi.fn().mockReturnThis(),
|
|
496
518
|
or: vi.fn().mockReturnThis(),
|
|
497
|
-
data: [],
|
|
498
|
-
error: null
|
|
519
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
499
520
|
};
|
|
500
521
|
});
|
|
501
522
|
|
|
@@ -512,10 +533,16 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
512
533
|
|
|
513
534
|
describe('Access Level Resolution', () => {
|
|
514
535
|
it('resolves super admin access level', async () => {
|
|
515
|
-
// Mock
|
|
516
|
-
mockSupabase.
|
|
517
|
-
|
|
518
|
-
|
|
536
|
+
// Mock global roles query to return super admin
|
|
537
|
+
mockSupabase.from.mockReturnValue({
|
|
538
|
+
select: vi.fn().mockReturnThis(),
|
|
539
|
+
eq: vi.fn().mockReturnThis(),
|
|
540
|
+
lte: vi.fn().mockReturnThis(),
|
|
541
|
+
or: vi.fn().mockReturnThis(),
|
|
542
|
+
limit: vi.fn().mockResolvedValue({
|
|
543
|
+
data: [{ role: 'super_admin' }],
|
|
544
|
+
error: null
|
|
545
|
+
})
|
|
519
546
|
});
|
|
520
547
|
|
|
521
548
|
const scope: Scope = { organisationId: 'org-123' as UUID };
|
|
@@ -528,12 +555,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
528
555
|
});
|
|
529
556
|
|
|
530
557
|
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
558
|
// Mock app config
|
|
538
559
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
539
560
|
if (table === 'rbac_apps') {
|
|
@@ -546,6 +567,15 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
546
567
|
})
|
|
547
568
|
};
|
|
548
569
|
}
|
|
570
|
+
if (table === 'rbac_global_roles') {
|
|
571
|
+
return {
|
|
572
|
+
select: vi.fn().mockReturnThis(),
|
|
573
|
+
eq: vi.fn().mockReturnThis(),
|
|
574
|
+
lte: vi.fn().mockReturnThis(),
|
|
575
|
+
or: vi.fn().mockReturnThis(),
|
|
576
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
577
|
+
};
|
|
578
|
+
}
|
|
549
579
|
if (table === 'rbac_organisation_roles') {
|
|
550
580
|
return {
|
|
551
581
|
select: vi.fn().mockReturnThis(),
|
|
@@ -559,8 +589,9 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
559
589
|
return {
|
|
560
590
|
select: vi.fn().mockReturnThis(),
|
|
561
591
|
eq: vi.fn().mockReturnThis(),
|
|
562
|
-
|
|
563
|
-
|
|
592
|
+
lte: vi.fn().mockReturnThis(),
|
|
593
|
+
or: vi.fn().mockReturnThis(),
|
|
594
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
564
595
|
};
|
|
565
596
|
});
|
|
566
597
|
|
|
@@ -574,12 +605,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
574
605
|
});
|
|
575
606
|
|
|
576
607
|
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
608
|
// Mock app config
|
|
584
609
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
585
610
|
if (table === 'rbac_apps') {
|
|
@@ -602,6 +627,25 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
602
627
|
})
|
|
603
628
|
};
|
|
604
629
|
}
|
|
630
|
+
if (table === 'rbac_global_roles') {
|
|
631
|
+
return {
|
|
632
|
+
select: vi.fn().mockReturnThis(),
|
|
633
|
+
eq: vi.fn().mockReturnThis(),
|
|
634
|
+
lte: vi.fn().mockReturnThis(),
|
|
635
|
+
or: vi.fn().mockReturnThis(),
|
|
636
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (table === 'rbac_organisation_roles') {
|
|
640
|
+
return {
|
|
641
|
+
select: vi.fn().mockReturnThis(),
|
|
642
|
+
eq: vi.fn().mockReturnThis(),
|
|
643
|
+
single: vi.fn().mockResolvedValue({
|
|
644
|
+
data: null,
|
|
645
|
+
error: { code: 'PGRST116' }
|
|
646
|
+
})
|
|
647
|
+
};
|
|
648
|
+
}
|
|
605
649
|
if (table === 'rbac_event_app_roles') {
|
|
606
650
|
return {
|
|
607
651
|
select: vi.fn().mockReturnThis(),
|
|
@@ -619,10 +663,7 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
619
663
|
eq: vi.fn().mockReturnThis(),
|
|
620
664
|
lte: vi.fn().mockReturnThis(),
|
|
621
665
|
or: vi.fn().mockReturnThis(),
|
|
622
|
-
|
|
623
|
-
data: null,
|
|
624
|
-
error: { code: 'PGRST116' }
|
|
625
|
-
})
|
|
666
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
626
667
|
};
|
|
627
668
|
});
|
|
628
669
|
|
|
@@ -640,12 +681,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
640
681
|
});
|
|
641
682
|
|
|
642
683
|
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
684
|
// Mock app config
|
|
650
685
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
651
686
|
if (table === 'rbac_apps') {
|
|
@@ -658,13 +693,31 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
658
693
|
})
|
|
659
694
|
};
|
|
660
695
|
}
|
|
696
|
+
if (table === 'rbac_global_roles') {
|
|
697
|
+
return {
|
|
698
|
+
select: vi.fn().mockReturnThis(),
|
|
699
|
+
eq: vi.fn().mockReturnThis(),
|
|
700
|
+
lte: vi.fn().mockReturnThis(),
|
|
701
|
+
or: vi.fn().mockReturnThis(),
|
|
702
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (table === 'rbac_organisation_roles') {
|
|
706
|
+
return {
|
|
707
|
+
select: vi.fn().mockReturnThis(),
|
|
708
|
+
eq: vi.fn().mockReturnThis(),
|
|
709
|
+
single: vi.fn().mockResolvedValue({
|
|
710
|
+
data: null,
|
|
711
|
+
error: { code: 'PGRST116' }
|
|
712
|
+
})
|
|
713
|
+
};
|
|
714
|
+
}
|
|
661
715
|
return {
|
|
662
716
|
select: vi.fn().mockReturnThis(),
|
|
663
717
|
eq: vi.fn().mockReturnThis(),
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
})
|
|
718
|
+
lte: vi.fn().mockReturnThis(),
|
|
719
|
+
or: vi.fn().mockReturnThis(),
|
|
720
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
668
721
|
};
|
|
669
722
|
});
|
|
670
723
|
|
|
@@ -680,10 +733,16 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
680
733
|
|
|
681
734
|
describe('Permission Map Generation', () => {
|
|
682
735
|
it('returns empty map for super admin', async () => {
|
|
683
|
-
// Mock
|
|
684
|
-
mockSupabase.
|
|
685
|
-
|
|
686
|
-
|
|
736
|
+
// Mock global roles query to return super admin
|
|
737
|
+
mockSupabase.from.mockReturnValue({
|
|
738
|
+
select: vi.fn().mockReturnThis(),
|
|
739
|
+
eq: vi.fn().mockReturnThis(),
|
|
740
|
+
lte: vi.fn().mockReturnThis(),
|
|
741
|
+
or: vi.fn().mockReturnThis(),
|
|
742
|
+
limit: vi.fn().mockResolvedValue({
|
|
743
|
+
data: [{ role: 'super_admin' }],
|
|
744
|
+
error: null
|
|
745
|
+
})
|
|
687
746
|
});
|
|
688
747
|
|
|
689
748
|
const scope: Scope = { organisationId: 'org-123' as UUID };
|
|
@@ -696,12 +755,6 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
696
755
|
});
|
|
697
756
|
|
|
698
757
|
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
758
|
// Mock app config
|
|
706
759
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
707
760
|
if (table === 'rbac_apps') {
|
|
@@ -714,6 +767,15 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
714
767
|
})
|
|
715
768
|
};
|
|
716
769
|
}
|
|
770
|
+
if (table === 'rbac_global_roles') {
|
|
771
|
+
return {
|
|
772
|
+
select: vi.fn().mockReturnThis(),
|
|
773
|
+
eq: vi.fn().mockReturnThis(),
|
|
774
|
+
lte: vi.fn().mockReturnThis(),
|
|
775
|
+
or: vi.fn().mockReturnThis(),
|
|
776
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
777
|
+
};
|
|
778
|
+
}
|
|
717
779
|
if (table === 'rbac_app_pages') {
|
|
718
780
|
return {
|
|
719
781
|
select: vi.fn().mockReturnThis(),
|
|
@@ -728,8 +790,9 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
728
790
|
return {
|
|
729
791
|
select: vi.fn().mockReturnThis(),
|
|
730
792
|
eq: vi.fn().mockReturnThis(),
|
|
731
|
-
|
|
732
|
-
|
|
793
|
+
lte: vi.fn().mockReturnThis(),
|
|
794
|
+
or: vi.fn().mockReturnThis(),
|
|
795
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
733
796
|
};
|
|
734
797
|
});
|
|
735
798
|
|
|
@@ -809,13 +872,8 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
809
872
|
|
|
810
873
|
describe('Cache Integration', () => {
|
|
811
874
|
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
875
|
// Mock app config
|
|
876
|
+
let callCount = 0;
|
|
819
877
|
mockSupabase.from.mockImplementation((table: string) => {
|
|
820
878
|
if (table === 'rbac_apps') {
|
|
821
879
|
return {
|
|
@@ -827,11 +885,22 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
827
885
|
})
|
|
828
886
|
};
|
|
829
887
|
}
|
|
888
|
+
if (table === 'rbac_global_roles') {
|
|
889
|
+
callCount++;
|
|
890
|
+
return {
|
|
891
|
+
select: vi.fn().mockReturnThis(),
|
|
892
|
+
eq: vi.fn().mockReturnThis(),
|
|
893
|
+
lte: vi.fn().mockReturnThis(),
|
|
894
|
+
or: vi.fn().mockReturnThis(),
|
|
895
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
896
|
+
};
|
|
897
|
+
}
|
|
830
898
|
return {
|
|
831
899
|
select: vi.fn().mockReturnThis(),
|
|
832
900
|
eq: vi.fn().mockReturnThis(),
|
|
833
|
-
|
|
834
|
-
|
|
901
|
+
lte: vi.fn().mockReturnThis(),
|
|
902
|
+
or: vi.fn().mockReturnThis(),
|
|
903
|
+
limit: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
835
904
|
};
|
|
836
905
|
});
|
|
837
906
|
|
|
@@ -849,8 +918,11 @@ describe('RBACEngine - Comprehensive Tests', () => {
|
|
|
849
918
|
const result2 = await engine.isPermitted(permissionCheck);
|
|
850
919
|
expect(result2).toBe(false);
|
|
851
920
|
|
|
852
|
-
// Verify
|
|
853
|
-
expect(
|
|
921
|
+
// Verify global roles query was called at least once
|
|
922
|
+
expect(callCount).toBeGreaterThanOrEqual(1);
|
|
923
|
+
|
|
924
|
+
// Verify results are the same (caching is working)
|
|
925
|
+
expect(result1).toBe(result2);
|
|
854
926
|
});
|
|
855
927
|
});
|
|
856
928
|
|