@jmruthers/pace-core 0.5.187 → 0.5.189
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-K3RJRSOX.js → DataTable-GUFUNZ3N.js} +5 -5
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-B8HaLe69.d.ts} +47 -17
- package/dist/{UnifiedAuthProvider-B76OWOAT.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
- package/dist/{chunk-FMTK4XNN.js → chunk-2UUZZJFT.js} +3 -3
- package/dist/{chunk-3IC5WCMO.js → chunk-3GOZZZYH.js} +3 -3
- package/dist/{chunk-ULX5FYEM.js → chunk-DDM4CCYT.js} +3 -3
- package/dist/{chunk-K2JGDXGU.js → chunk-E7UAOUMY.js} +2 -2
- package/dist/{chunk-T6ZJVI3A.js → chunk-IM4QE42D.js} +4 -4
- package/dist/{chunk-3NFNJOO7.js → chunk-MX64ZF6I.js} +4 -4
- package/dist/{chunk-C4OYJOV4.js → chunk-UCQSRW7Z.js} +829 -829
- package/dist/chunk-UCQSRW7Z.js.map +1 -0
- package/dist/{chunk-WK2Y6TGA.js → chunk-VGZZXKBR.js} +3 -3
- package/dist/chunk-VGZZXKBR.js.map +1 -0
- package/dist/{chunk-LBBUPSSC.js → chunk-YGPFYGA6.js} +3760 -3692
- package/dist/chunk-YGPFYGA6.js.map +1 -0
- package/dist/components.d.ts +1 -2
- package/dist/components.js +6 -10
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +5 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +9 -13
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- 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/Logger.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/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- 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/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.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/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.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/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.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 +6 -45
- package/docs/api-reference/components.md +57 -22
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/implementation-guides/public-pages.md +140 -1230
- package/docs/standards/05-security-standard.md +3 -1
- package/docs/standards/07-rbac-and-rls-standard.md +356 -0
- package/package.json +1 -2
- package/src/__tests__/public-recipe-view.test.ts +199 -0
- package/src/__tests__/rls-policies.test.ts +333 -0
- package/src/components/Avatar/Avatar.test.tsx +183 -209
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +7 -5
- package/src/components/index.ts +2 -1
- package/src/index.ts +2 -1
- package/src/services/OrganisationService.ts +5 -4
- package/dist/chunk-C4OYJOV4.js.map +0 -1
- package/dist/chunk-LBBUPSSC.js.map +0 -1
- package/dist/chunk-WK2Y6TGA.js.map +0 -1
- /package/dist/{DataTable-K3RJRSOX.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-B76OWOAT.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
- /package/dist/{chunk-FMTK4XNN.js.map → chunk-2UUZZJFT.js.map} +0 -0
- /package/dist/{chunk-3IC5WCMO.js.map → chunk-3GOZZZYH.js.map} +0 -0
- /package/dist/{chunk-ULX5FYEM.js.map → chunk-DDM4CCYT.js.map} +0 -0
- /package/dist/{chunk-K2JGDXGU.js.map → chunk-E7UAOUMY.js.map} +0 -0
- /package/dist/{chunk-T6ZJVI3A.js.map → chunk-IM4QE42D.js.map} +0 -0
- /package/dist/{chunk-3NFNJOO7.js.map → chunk-MX64ZF6I.js.map} +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file RLS Policy Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RLS/Tests
|
|
5
|
+
* @since 0.5.181
|
|
6
|
+
*
|
|
7
|
+
* Application-level tests for RLS policies to verify they work correctly
|
|
8
|
+
* under different user contexts and that performance is acceptable.
|
|
9
|
+
*
|
|
10
|
+
* These tests verify that:
|
|
11
|
+
* - RLS policies correctly enforce access control
|
|
12
|
+
* - Helper functions are used (no inline auth.uid() calls)
|
|
13
|
+
* - Queries complete in acceptable time (< 1 second)
|
|
14
|
+
* - Cross-organisation access is properly blocked
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
18
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
19
|
+
import type { Database } from '../../types/database';
|
|
20
|
+
|
|
21
|
+
// Test configuration
|
|
22
|
+
const TEST_TIMEOUT = 5000; // 5 seconds
|
|
23
|
+
const PERFORMANCE_THRESHOLD = 1000; // 1 second in milliseconds
|
|
24
|
+
|
|
25
|
+
// Check if we're using real test-db (via environment variables)
|
|
26
|
+
const USE_REAL_DB = !!(process.env.SUPABASE_URL && process.env.VITE_SUPABASE_ANON_KEY);
|
|
27
|
+
const TEST_SUPABASE_URL = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL;
|
|
28
|
+
const TEST_SUPABASE_ANON_KEY = process.env.TEST_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
|
|
29
|
+
const TEST_SUPABASE_SERVICE_ROLE_KEY = process.env.TEST_SUPABASE_SERVICE_ROLE_KEY;
|
|
30
|
+
|
|
31
|
+
// Test user IDs (these should exist in your test-db with appropriate roles)
|
|
32
|
+
// Update these to match actual test users in your test-db branch
|
|
33
|
+
const TEST_SUPER_ADMIN_USER_ID = process.env.TEST_SUPER_ADMIN_USER_ID || '00000000-0000-0000-0000-000000000001';
|
|
34
|
+
const TEST_ORG_ADMIN_USER_ID = process.env.TEST_ORG_ADMIN_USER_ID || '00000000-0000-0000-0000-000000000002';
|
|
35
|
+
const TEST_REGULAR_MEMBER_USER_ID = process.env.TEST_REGULAR_MEMBER_USER_ID || '00000000-0000-0000-0000-000000000003';
|
|
36
|
+
|
|
37
|
+
// Supabase clients for different user contexts
|
|
38
|
+
let superAdminClient: SupabaseClient<Database>;
|
|
39
|
+
let orgAdminClient: SupabaseClient<Database>;
|
|
40
|
+
let regularMemberClient: SupabaseClient<Database>;
|
|
41
|
+
let anonClient: SupabaseClient<Database>;
|
|
42
|
+
|
|
43
|
+
// Test data
|
|
44
|
+
const testOrganisation1 = {
|
|
45
|
+
id: 'org-1' as any,
|
|
46
|
+
name: 'Test Organisation 1'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const testOrganisation2 = {
|
|
50
|
+
id: 'org-2' as any,
|
|
51
|
+
name: 'Test Organisation 2'
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const testEvent = {
|
|
55
|
+
event_id: 'event-1',
|
|
56
|
+
event_name: 'Test Event',
|
|
57
|
+
organisation_id: testOrganisation1.id,
|
|
58
|
+
is_visible: true,
|
|
59
|
+
public_readable: false
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY)('RLS Policies - Organisations', () => {
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
if (!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY) {
|
|
65
|
+
throw new Error('Test database credentials not available. Set SUPABASE_URL and VITE_SUPABASE_ANON_KEY environment variables.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Use real test-db clients with authenticated sessions
|
|
69
|
+
// For real testing, we need to sign in as different users
|
|
70
|
+
const baseClient = createClient<Database>(TEST_SUPABASE_URL, TEST_SUPABASE_ANON_KEY);
|
|
71
|
+
|
|
72
|
+
// Create anon client (no auth)
|
|
73
|
+
anonClient = baseClient;
|
|
74
|
+
|
|
75
|
+
// For authenticated clients, we need to sign in as different users
|
|
76
|
+
// Note: This requires test users to exist in test-db with appropriate roles
|
|
77
|
+
// You'll need to set up test users and get their session tokens
|
|
78
|
+
|
|
79
|
+
// For now, create clients - in a real setup, you'd sign in as each user:
|
|
80
|
+
// const { data: { session } } = await baseClient.auth.signInWithPassword({ email, password });
|
|
81
|
+
// Then create a new client with that session
|
|
82
|
+
|
|
83
|
+
// Using service role key for super admin (bypasses RLS - use carefully!)
|
|
84
|
+
if (TEST_SUPABASE_SERVICE_ROLE_KEY) {
|
|
85
|
+
superAdminClient = createClient<Database>(TEST_SUPABASE_URL, TEST_SUPABASE_SERVICE_ROLE_KEY);
|
|
86
|
+
} else {
|
|
87
|
+
// Fallback: use anon key (will be subject to RLS)
|
|
88
|
+
superAdminClient = baseClient;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// For org admin and regular member, we'd need to sign in as those users
|
|
92
|
+
// For now, using base client - update this when you have test user sessions
|
|
93
|
+
orgAdminClient = baseClient;
|
|
94
|
+
regularMemberClient = baseClient;
|
|
95
|
+
|
|
96
|
+
console.log('✅ Using real test-db:', TEST_SUPABASE_URL);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('Super Admin Access', () => {
|
|
100
|
+
it('should allow super admin to view all organisations', async () => {
|
|
101
|
+
const start = Date.now();
|
|
102
|
+
const { data, error } = await superAdminClient
|
|
103
|
+
.from('organisations')
|
|
104
|
+
.select('*')
|
|
105
|
+
.limit(10);
|
|
106
|
+
const duration = Date.now() - start;
|
|
107
|
+
|
|
108
|
+
expect(error).toBeNull();
|
|
109
|
+
expect(data).toBeDefined();
|
|
110
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
111
|
+
}, TEST_TIMEOUT);
|
|
112
|
+
|
|
113
|
+
it('should allow super admin to update any organisation', async () => {
|
|
114
|
+
const { data, error } = await superAdminClient
|
|
115
|
+
.from('organisations')
|
|
116
|
+
.update({ name: 'Updated Name' })
|
|
117
|
+
.eq('id', testOrganisation1.id)
|
|
118
|
+
.select()
|
|
119
|
+
.single();
|
|
120
|
+
|
|
121
|
+
// In real test, verify update succeeded
|
|
122
|
+
expect(error).toBeNull();
|
|
123
|
+
}, TEST_TIMEOUT);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Organisation Admin Access', () => {
|
|
127
|
+
it('should allow org admin to view their organisation', async () => {
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
const { data, error } = await orgAdminClient
|
|
130
|
+
.from('organisations')
|
|
131
|
+
.select('*')
|
|
132
|
+
.eq('id', testOrganisation1.id)
|
|
133
|
+
.single();
|
|
134
|
+
const duration = Date.now() - start;
|
|
135
|
+
|
|
136
|
+
expect(error).toBeNull();
|
|
137
|
+
expect(data).toBeDefined();
|
|
138
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
139
|
+
}, TEST_TIMEOUT);
|
|
140
|
+
|
|
141
|
+
it('should block org admin from viewing other organisations', async () => {
|
|
142
|
+
const { data, error } = await orgAdminClient
|
|
143
|
+
.from('organisations')
|
|
144
|
+
.select('*')
|
|
145
|
+
.eq('id', testOrganisation2.id)
|
|
146
|
+
.single();
|
|
147
|
+
|
|
148
|
+
// Should return empty or error (RLS blocks access)
|
|
149
|
+
expect(data).toBeNull();
|
|
150
|
+
}, TEST_TIMEOUT);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Regular Member Access', () => {
|
|
154
|
+
it('should allow member to view their organisation', async () => {
|
|
155
|
+
const start = Date.now();
|
|
156
|
+
const { data, error } = await regularMemberClient
|
|
157
|
+
.from('organisations')
|
|
158
|
+
.select('*')
|
|
159
|
+
.eq('id', testOrganisation1.id)
|
|
160
|
+
.single();
|
|
161
|
+
const duration = Date.now() - start;
|
|
162
|
+
|
|
163
|
+
expect(error).toBeNull();
|
|
164
|
+
expect(data).toBeDefined();
|
|
165
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
166
|
+
}, TEST_TIMEOUT);
|
|
167
|
+
|
|
168
|
+
it('should block member from updating organisation', async () => {
|
|
169
|
+
const { data, error } = await regularMemberClient
|
|
170
|
+
.from('organisations')
|
|
171
|
+
.update({ name: 'Unauthorized Update' })
|
|
172
|
+
.eq('id', testOrganisation1.id);
|
|
173
|
+
|
|
174
|
+
// Should fail (member doesn't have update permission)
|
|
175
|
+
expect(error).not.toBeNull();
|
|
176
|
+
}, TEST_TIMEOUT);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Anonymous Access', () => {
|
|
180
|
+
it('should block anonymous users from viewing organisations', async () => {
|
|
181
|
+
const { data, error } = await anonClient
|
|
182
|
+
.from('organisations')
|
|
183
|
+
.select('*');
|
|
184
|
+
|
|
185
|
+
// Should return empty (RLS blocks anonymous access)
|
|
186
|
+
expect(data).toEqual([]);
|
|
187
|
+
}, TEST_TIMEOUT);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY)('RLS Policies - Events', () => {
|
|
192
|
+
describe('Public Event Access', () => {
|
|
193
|
+
it('should allow anonymous access to public events', async () => {
|
|
194
|
+
const start = Date.now();
|
|
195
|
+
const { data, error } = await anonClient
|
|
196
|
+
.from('event')
|
|
197
|
+
.select('*')
|
|
198
|
+
.eq('event_id', testEvent.event_id)
|
|
199
|
+
.eq('public_readable', true)
|
|
200
|
+
.eq('is_visible', true)
|
|
201
|
+
.single();
|
|
202
|
+
const duration = Date.now() - start;
|
|
203
|
+
|
|
204
|
+
expect(error).toBeNull();
|
|
205
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
206
|
+
}, TEST_TIMEOUT);
|
|
207
|
+
|
|
208
|
+
it('should block anonymous access to non-public events', async () => {
|
|
209
|
+
const { data, error } = await anonClient
|
|
210
|
+
.from('event')
|
|
211
|
+
.select('*')
|
|
212
|
+
.eq('event_id', testEvent.event_id)
|
|
213
|
+
.eq('public_readable', false)
|
|
214
|
+
.single();
|
|
215
|
+
|
|
216
|
+
// Should return empty (RLS blocks access)
|
|
217
|
+
expect(data).toBeNull();
|
|
218
|
+
}, TEST_TIMEOUT);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('Authenticated Event Access', () => {
|
|
222
|
+
it('should allow org member to view events in their organisation', async () => {
|
|
223
|
+
const start = Date.now();
|
|
224
|
+
const { data, error } = await regularMemberClient
|
|
225
|
+
.from('event')
|
|
226
|
+
.select('*')
|
|
227
|
+
.eq('organisation_id', testOrganisation1.id)
|
|
228
|
+
.limit(10);
|
|
229
|
+
const duration = Date.now() - start;
|
|
230
|
+
|
|
231
|
+
expect(error).toBeNull();
|
|
232
|
+
expect(data).toBeDefined();
|
|
233
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
234
|
+
}, TEST_TIMEOUT);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY)('RLS Policies - RBAC Tables', () => {
|
|
239
|
+
describe('rbac_user_profiles', () => {
|
|
240
|
+
it('should allow user to view their own profile', async () => {
|
|
241
|
+
const start = Date.now();
|
|
242
|
+
const { data, error } = await regularMemberClient
|
|
243
|
+
.from('rbac_user_profiles')
|
|
244
|
+
.select('*')
|
|
245
|
+
.eq('id', 'user-123' as any)
|
|
246
|
+
.single();
|
|
247
|
+
const duration = Date.now() - start;
|
|
248
|
+
|
|
249
|
+
expect(error).toBeNull();
|
|
250
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
251
|
+
}, TEST_TIMEOUT);
|
|
252
|
+
|
|
253
|
+
it('should block user from viewing other profiles', async () => {
|
|
254
|
+
const { data, error } = await regularMemberClient
|
|
255
|
+
.from('rbac_user_profiles')
|
|
256
|
+
.select('*')
|
|
257
|
+
.eq('id', 'other-user-123' as any)
|
|
258
|
+
.single();
|
|
259
|
+
|
|
260
|
+
// Should return empty (RLS blocks access)
|
|
261
|
+
expect(data).toBeNull();
|
|
262
|
+
}, TEST_TIMEOUT);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('rbac_organisation_roles', () => {
|
|
266
|
+
it('should allow user to view their own roles', async () => {
|
|
267
|
+
const start = Date.now();
|
|
268
|
+
const { data, error } = await regularMemberClient
|
|
269
|
+
.from('rbac_organisation_roles')
|
|
270
|
+
.select('*')
|
|
271
|
+
.eq('user_id', 'user-123' as any);
|
|
272
|
+
const duration = Date.now() - start;
|
|
273
|
+
|
|
274
|
+
expect(error).toBeNull();
|
|
275
|
+
expect(data).toBeDefined();
|
|
276
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
277
|
+
}, TEST_TIMEOUT);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY)('RLS Policies - Performance', () => {
|
|
282
|
+
it('should complete organisation queries in < 1 second', async () => {
|
|
283
|
+
const start = Date.now();
|
|
284
|
+
await superAdminClient
|
|
285
|
+
.from('organisations')
|
|
286
|
+
.select('*')
|
|
287
|
+
.limit(100);
|
|
288
|
+
const duration = Date.now() - start;
|
|
289
|
+
|
|
290
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
291
|
+
}, TEST_TIMEOUT);
|
|
292
|
+
|
|
293
|
+
it('should complete event queries in < 1 second', async () => {
|
|
294
|
+
const start = Date.now();
|
|
295
|
+
await superAdminClient
|
|
296
|
+
.from('event')
|
|
297
|
+
.select('*')
|
|
298
|
+
.eq('is_visible', true)
|
|
299
|
+
.limit(100);
|
|
300
|
+
const duration = Date.now() - start;
|
|
301
|
+
|
|
302
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
303
|
+
}, TEST_TIMEOUT);
|
|
304
|
+
|
|
305
|
+
it('should complete user profile queries in < 1 second', async () => {
|
|
306
|
+
const start = Date.now();
|
|
307
|
+
await superAdminClient
|
|
308
|
+
.from('rbac_user_profiles')
|
|
309
|
+
.select('*')
|
|
310
|
+
.limit(100);
|
|
311
|
+
const duration = Date.now() - start;
|
|
312
|
+
|
|
313
|
+
expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
314
|
+
}, TEST_TIMEOUT);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_ANON_KEY)('RLS Policies - Helper Functions', () => {
|
|
318
|
+
it('should use helper functions instead of inline auth.uid()', async () => {
|
|
319
|
+
// This test verifies that policies use helper functions
|
|
320
|
+
// by checking query plans don't contain InitPlan nodes
|
|
321
|
+
// In a real test, we would use EXPLAIN ANALYZE
|
|
322
|
+
|
|
323
|
+
const { data, error } = await superAdminClient
|
|
324
|
+
.rpc('check_query_performance', {
|
|
325
|
+
p_query: 'SELECT * FROM organisations LIMIT 1'
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Verify no InitPlan nodes (would indicate inline auth.uid() calls)
|
|
329
|
+
expect(error).toBeNull();
|
|
330
|
+
// In real test, verify has_initplan = false
|
|
331
|
+
}, TEST_TIMEOUT);
|
|
332
|
+
});
|
|
333
|
+
|