@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.
Files changed (209) hide show
  1. package/dist/{DataTable-K3RJRSOX.js → DataTable-GUFUNZ3N.js} +5 -5
  2. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-B8HaLe69.d.ts} +47 -17
  3. package/dist/{UnifiedAuthProvider-B76OWOAT.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
  4. package/dist/{chunk-FMTK4XNN.js → chunk-2UUZZJFT.js} +3 -3
  5. package/dist/{chunk-3IC5WCMO.js → chunk-3GOZZZYH.js} +3 -3
  6. package/dist/{chunk-ULX5FYEM.js → chunk-DDM4CCYT.js} +3 -3
  7. package/dist/{chunk-K2JGDXGU.js → chunk-E7UAOUMY.js} +2 -2
  8. package/dist/{chunk-T6ZJVI3A.js → chunk-IM4QE42D.js} +4 -4
  9. package/dist/{chunk-3NFNJOO7.js → chunk-MX64ZF6I.js} +4 -4
  10. package/dist/{chunk-C4OYJOV4.js → chunk-UCQSRW7Z.js} +829 -829
  11. package/dist/chunk-UCQSRW7Z.js.map +1 -0
  12. package/dist/{chunk-WK2Y6TGA.js → chunk-VGZZXKBR.js} +3 -3
  13. package/dist/chunk-VGZZXKBR.js.map +1 -0
  14. package/dist/{chunk-LBBUPSSC.js → chunk-YGPFYGA6.js} +3760 -3692
  15. package/dist/chunk-YGPFYGA6.js.map +1 -0
  16. package/dist/components.d.ts +1 -2
  17. package/dist/components.js +6 -10
  18. package/dist/components.js.map +1 -1
  19. package/dist/hooks.js +5 -5
  20. package/dist/index.d.ts +1 -2
  21. package/dist/index.js +9 -13
  22. package/dist/index.js.map +1 -1
  23. package/dist/providers.js +1 -1
  24. package/dist/rbac/index.js +5 -5
  25. package/dist/utils.js +1 -1
  26. package/docs/api/classes/ColumnFactory.md +1 -1
  27. package/docs/api/classes/ErrorBoundary.md +1 -1
  28. package/docs/api/classes/InvalidScopeError.md +1 -1
  29. package/docs/api/classes/Logger.md +1 -1
  30. package/docs/api/classes/MissingUserContextError.md +1 -1
  31. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  32. package/docs/api/classes/PermissionDeniedError.md +1 -1
  33. package/docs/api/classes/RBACAuditManager.md +1 -1
  34. package/docs/api/classes/RBACCache.md +1 -1
  35. package/docs/api/classes/RBACEngine.md +1 -1
  36. package/docs/api/classes/RBACError.md +1 -1
  37. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  38. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  39. package/docs/api/classes/StorageUtils.md +1 -1
  40. package/docs/api/enums/FileCategory.md +1 -1
  41. package/docs/api/enums/LogLevel.md +1 -1
  42. package/docs/api/enums/RBACErrorCode.md +1 -1
  43. package/docs/api/enums/RPCFunction.md +1 -1
  44. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  45. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  46. package/docs/api/interfaces/AggregateConfig.md +1 -1
  47. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  48. package/docs/api/interfaces/AvatarProps.md +128 -0
  49. package/docs/api/interfaces/BadgeProps.md +1 -1
  50. package/docs/api/interfaces/ButtonProps.md +1 -1
  51. package/docs/api/interfaces/CalendarProps.md +1 -1
  52. package/docs/api/interfaces/CardProps.md +1 -1
  53. package/docs/api/interfaces/ColorPalette.md +1 -1
  54. package/docs/api/interfaces/ColorShade.md +1 -1
  55. package/docs/api/interfaces/ComplianceResult.md +1 -1
  56. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  57. package/docs/api/interfaces/DataRecord.md +1 -1
  58. package/docs/api/interfaces/DataTableAction.md +1 -1
  59. package/docs/api/interfaces/DataTableColumn.md +1 -1
  60. package/docs/api/interfaces/DataTableProps.md +1 -1
  61. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  62. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  63. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  64. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  65. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  66. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  67. package/docs/api/interfaces/ExportColumn.md +1 -1
  68. package/docs/api/interfaces/ExportOptions.md +1 -1
  69. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  70. package/docs/api/interfaces/FileMetadata.md +1 -1
  71. package/docs/api/interfaces/FileReference.md +1 -1
  72. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  73. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  74. package/docs/api/interfaces/FileUploadProps.md +1 -1
  75. package/docs/api/interfaces/FooterProps.md +1 -1
  76. package/docs/api/interfaces/FormFieldProps.md +1 -1
  77. package/docs/api/interfaces/FormProps.md +1 -1
  78. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  79. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  80. package/docs/api/interfaces/InputProps.md +1 -1
  81. package/docs/api/interfaces/LabelProps.md +1 -1
  82. package/docs/api/interfaces/LoggerConfig.md +1 -1
  83. package/docs/api/interfaces/LoginFormProps.md +1 -1
  84. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  85. package/docs/api/interfaces/NavigationContextType.md +1 -1
  86. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  87. package/docs/api/interfaces/NavigationItem.md +1 -1
  88. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  90. package/docs/api/interfaces/Organisation.md +1 -1
  91. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  92. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  93. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  94. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  95. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  96. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  97. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  98. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  99. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  100. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  101. package/docs/api/interfaces/PaletteData.md +1 -1
  102. package/docs/api/interfaces/ParsedAddress.md +1 -1
  103. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  104. package/docs/api/interfaces/ProgressProps.md +1 -1
  105. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  106. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  107. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  109. package/docs/api/interfaces/QuickFix.md +1 -1
  110. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  111. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  112. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  113. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  114. package/docs/api/interfaces/RBACConfig.md +1 -1
  115. package/docs/api/interfaces/RBACContext.md +1 -1
  116. package/docs/api/interfaces/RBACLogger.md +1 -1
  117. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  118. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  119. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  120. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  121. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  122. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  123. package/docs/api/interfaces/RBACResult.md +1 -1
  124. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  125. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  126. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  127. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  128. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  129. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  130. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  131. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  132. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  133. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  134. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  135. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  136. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  137. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  138. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  139. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  140. package/docs/api/interfaces/RouteConfig.md +1 -1
  141. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  142. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  143. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  144. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  145. package/docs/api/interfaces/SetupIssue.md +1 -1
  146. package/docs/api/interfaces/StorageConfig.md +1 -1
  147. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  148. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  149. package/docs/api/interfaces/StorageListOptions.md +1 -1
  150. package/docs/api/interfaces/StorageListResult.md +1 -1
  151. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  152. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  153. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  154. package/docs/api/interfaces/StyleImport.md +1 -1
  155. package/docs/api/interfaces/SwitchProps.md +1 -1
  156. package/docs/api/interfaces/TabsContentProps.md +1 -1
  157. package/docs/api/interfaces/TabsListProps.md +1 -1
  158. package/docs/api/interfaces/TabsProps.md +1 -1
  159. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  160. package/docs/api/interfaces/TextareaProps.md +1 -1
  161. package/docs/api/interfaces/ToastActionElement.md +1 -1
  162. package/docs/api/interfaces/ToastProps.md +1 -1
  163. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  164. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  165. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  166. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  167. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  168. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  169. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  170. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  171. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  172. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  173. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  174. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  175. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  176. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  177. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  178. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  179. package/docs/api/interfaces/UserEventAccess.md +1 -1
  180. package/docs/api/interfaces/UserMenuProps.md +1 -1
  181. package/docs/api/interfaces/UserProfile.md +1 -1
  182. package/docs/api/modules.md +6 -45
  183. package/docs/api-reference/components.md +57 -22
  184. package/docs/getting-started/examples/README.md +2 -2
  185. package/docs/implementation-guides/public-pages.md +140 -1230
  186. package/docs/standards/05-security-standard.md +3 -1
  187. package/docs/standards/07-rbac-and-rls-standard.md +356 -0
  188. package/package.json +1 -2
  189. package/src/__tests__/public-recipe-view.test.ts +199 -0
  190. package/src/__tests__/rls-policies.test.ts +333 -0
  191. package/src/components/Avatar/Avatar.test.tsx +183 -209
  192. package/src/components/Avatar/Avatar.tsx +179 -53
  193. package/src/components/Avatar/index.ts +1 -1
  194. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  195. package/src/components/UserMenu/UserMenu.tsx +7 -5
  196. package/src/components/index.ts +2 -1
  197. package/src/index.ts +2 -1
  198. package/src/services/OrganisationService.ts +5 -4
  199. package/dist/chunk-C4OYJOV4.js.map +0 -1
  200. package/dist/chunk-LBBUPSSC.js.map +0 -1
  201. package/dist/chunk-WK2Y6TGA.js.map +0 -1
  202. /package/dist/{DataTable-K3RJRSOX.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
  203. /package/dist/{UnifiedAuthProvider-B76OWOAT.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
  204. /package/dist/{chunk-FMTK4XNN.js.map → chunk-2UUZZJFT.js.map} +0 -0
  205. /package/dist/{chunk-3IC5WCMO.js.map → chunk-3GOZZZYH.js.map} +0 -0
  206. /package/dist/{chunk-ULX5FYEM.js.map → chunk-DDM4CCYT.js.map} +0 -0
  207. /package/dist/{chunk-K2JGDXGU.js.map → chunk-E7UAOUMY.js.map} +0 -0
  208. /package/dist/{chunk-T6ZJVI3A.js.map → chunk-IM4QE42D.js.map} +0 -0
  209. /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
+