begin; select plan(19); -- Total number of tests -- ============================================================================ -- RLS Enabled Test -- ============================================================================ SELECT is( (SELECT relrowsecurity FROM pg_class WHERE relname = 'notifications' AND relnamespace = 'public'::regnamespace), true, 'RLS should be enabled on notifications table' ); -- ============================================================================ -- Notifications Table RLS Policies -- ============================================================================ SELECT ok( (SELECT COUNT(*) FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can view their own notifications') > 0, 'Policy for viewing own notifications should exist' ); SELECT ok( (SELECT COUNT(*) FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can mark their own notifications as read') > 0, 'Policy for marking notifications as read should exist' ); -- Test policy commands SELECT is( (SELECT cmd FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can view their own notifications' LIMIT 1), 'SELECT', 'View notifications policy should be for SELECT' ); SELECT is( (SELECT cmd FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can mark their own notifications as read' LIMIT 1), 'UPDATE', 'Mark as read policy should be for UPDATE' ); -- Test policy uses auth.uid() for isolation SELECT ok( (SELECT qual::text FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can view their own notifications' LIMIT 1) LIKE '%auth.uid()%', 'SELECT policy should use auth.uid() for user isolation' ); -- Test UPDATE policy allows updating read_at SELECT ok( (SELECT with_check::text FROM pg_policies WHERE tablename = 'notifications' AND policyname = 'Users can mark their own notifications as read' LIMIT 1) LIKE '%read_at%', 'UPDATE policy should check read_at field' ); -- ============================================================================ -- Notifications Behavior Tests with Mock Data -- ============================================================================ -- Create test users and generate some notifications DO $$ DECLARE user1_id uuid := gen_random_uuid(); user2_id uuid := gen_random_uuid(); test_tablo_id text; BEGIN -- Insert test users INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at) VALUES (user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notifrlsuser1_' || user1_id::text || '@test.com', 'encrypted', now(), now(), now()), (user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notifrlsuser2_' || user2_id::text || '@test.com', 'encrypted', now(), now(), now()) ON CONFLICT DO NOTHING; -- Insert test profiles INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id) VALUES (user1_id, 'notifrlsuser1_' || user1_id::text || '@test.com', 'NotifRLS', 'User1', substring(user1_id::text from 1 for 8)), (user2_id, 'notifrlsuser2_' || user2_id::text || '@test.com', 'NotifRLS', 'User2', substring(user2_id::text from 1 for 8)) ON CONFLICT DO NOTHING; -- Set auth context to user1 for creating test data PERFORM set_config('request.jwt.claims', json_build_object('sub', user1_id::text)::text, true); -- Create a tablo as user1 INSERT INTO public.tablos (owner_id, name, status, position) VALUES (user1_id, 'RLS Test Tablo', 'todo', 0) RETURNING id INTO test_tablo_id; -- Grant access to user2 INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin) VALUES (test_tablo_id, user2_id, user1_id, true, false); -- Update tablo to trigger notification for user2 UPDATE public.tablos SET name = 'RLS Test Tablo Updated' WHERE id = test_tablo_id; -- Store test IDs PERFORM set_config('test.rls_user1_id', user1_id::text, true); PERFORM set_config('test.rls_user2_id', user2_id::text, true); PERFORM set_config('test.rls_tablo_id', test_tablo_id, true); END $$; -- Test: Notifications exist for test users SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid) > 0, 'Notifications should exist for test user2' ); -- Test: User2 received notification about tablo update SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid AND entity_type = 'tablos' AND entity_id = current_setting('test.rls_tablo_id')) > 0, 'User2 should have notification for tablo update' ); -- Test: Actor (user1) did not receive notification for their own action SELECT is( (SELECT COUNT(*)::integer FROM public.notifications WHERE user_id = current_setting('test.rls_user1_id')::uuid AND actor_id = current_setting('test.rls_user1_id')::uuid AND entity_id = current_setting('test.rls_tablo_id')), 0, 'User should not receive notifications for their own actions' ); -- Test: Notifications have proper structure SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid AND message IS NOT NULL AND length(message->>'en') > 0 AND length(message->>'fr') > 0) > 0, 'Notifications should have non-empty messages in both en and fr' ); SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid AND metadata IS NOT NULL AND jsonb_typeof(metadata) = 'object') > 0, 'Notifications should have valid JSONB metadata' ); SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid AND action_type IN ('created', 'updated')) > 0, 'Notifications should have valid action_type' ); -- ============================================================================ -- Test Notification Updates (Mark as Read) -- ============================================================================ -- Test: Update read_at on a notification DO $$ DECLARE test_notif_id uuid; BEGIN -- Get an unread notification for user2 SELECT id INTO test_notif_id FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid AND read_at IS NULL LIMIT 1; IF test_notif_id IS NOT NULL THEN -- Update the notification UPDATE public.notifications SET read_at = now() WHERE id = test_notif_id; PERFORM set_config('test.updated_notif_id', test_notif_id::text, true); PERFORM set_config('test.notif_was_updated', 'true', true); ELSE PERFORM set_config('test.notif_was_updated', 'false', true); PERFORM set_config('test.updated_notif_id', '00000000-0000-0000-0000-000000000000', true); END IF; END $$; -- Verify the update worked SELECT ok( CASE WHEN current_setting('test.notif_was_updated', true) = 'true' THEN (SELECT read_at IS NOT NULL FROM public.notifications WHERE id = current_setting('test.updated_notif_id', true)::uuid LIMIT 1) ELSE true -- Skip if no notification was found END, 'Notification should be updatable with read_at timestamp' ); -- ============================================================================ -- Test Data Isolation -- ============================================================================ -- Test: Count notifications for user1 vs user2 SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user1_id')::uuid) >= 0, 'User1 notifications count should be valid' ); SELECT ok( (SELECT COUNT(*) FROM public.notifications WHERE user_id = current_setting('test.rls_user2_id')::uuid) > 0, 'User2 should have at least one notification' ); -- ============================================================================ -- Foreign Key Constraints Tests -- ============================================================================ SELECT has_fk('public', 'notifications', 'notifications should have foreign key constraints'); -- Test that notifications.user_id references auth.users.id SELECT fk_ok( 'public', 'notifications', 'user_id', 'auth', 'users', 'id', 'notifications.user_id should reference auth.users.id' ); -- Test that notifications.actor_id references auth.users.id SELECT fk_ok( 'public', 'notifications', 'actor_id', 'auth', 'users', 'id', 'notifications.actor_id should reference auth.users.id' ); select * from finish(); rollback;