2025-11-16 11:55:30 +00:00
|
|
|
begin;
|
2025-11-16 19:34:33 +00:00
|
|
|
select plan(71); -- Total number of tests (54 original + 9 multiple notifications tests + 2 invite edge cases + 6 null actor tests)
|
2025-11-16 11:55:30 +00:00
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Table Existence Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
SELECT has_table('public', 'notifications', 'notifications table should exist');
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Notifications Table Structure
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
SELECT has_column('public', 'notifications', 'id', 'notifications should have id column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'user_id', 'notifications should have user_id column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'actor_id', 'notifications should have actor_id column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'entity_type', 'notifications should have entity_type column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'entity_id', 'notifications should have entity_id column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'action_type', 'notifications should have action_type column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'message', 'notifications should have message column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'metadata', 'notifications should have metadata column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'read_at', 'notifications should have read_at column');
|
|
|
|
|
SELECT has_column('public', 'notifications', 'created_at', 'notifications should have created_at column');
|
|
|
|
|
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'id', 'uuid', 'notifications.id should be uuid');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'user_id', 'uuid', 'notifications.user_id should be uuid');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'actor_id', 'uuid', 'notifications.actor_id should be uuid');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'entity_type', 'text', 'notifications.entity_type should be text');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'entity_id', 'text', 'notifications.entity_id should be text');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'action_type', 'text', 'notifications.action_type should be text');
|
2025-11-16 21:28:07 +00:00
|
|
|
SELECT col_type_is('public', 'notifications', 'message', 'jsonb', 'notifications.message should be jsonb');
|
2025-11-16 11:55:30 +00:00
|
|
|
SELECT col_type_is('public', 'notifications', 'metadata', 'jsonb', 'notifications.metadata should be jsonb');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'read_at', 'timestamp with time zone', 'notifications.read_at should be timestamptz');
|
|
|
|
|
SELECT col_type_is('public', 'notifications', 'created_at', 'timestamp with time zone', 'notifications.created_at should be timestamptz');
|
|
|
|
|
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'id', 'notifications.id should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'user_id', 'notifications.user_id should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'entity_type', 'notifications.entity_type should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'entity_id', 'notifications.entity_id should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'action_type', 'notifications.action_type should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'message', 'notifications.message should be NOT NULL');
|
|
|
|
|
SELECT col_not_null('public', 'notifications', 'created_at', 'notifications.created_at should be NOT NULL');
|
|
|
|
|
|
|
|
|
|
SELECT col_has_default('public', 'notifications', 'id', 'notifications.id should have default');
|
|
|
|
|
SELECT col_has_default('public', 'notifications', 'metadata', 'notifications.metadata should have default');
|
|
|
|
|
SELECT col_has_default('public', 'notifications', 'created_at', 'notifications.created_at should have default');
|
|
|
|
|
|
|
|
|
|
-- Check action_type constraint
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM information_schema.check_constraints
|
|
|
|
|
WHERE constraint_schema = 'public'
|
|
|
|
|
AND constraint_name LIKE '%action_type%') > 0,
|
|
|
|
|
'notifications.action_type should have check constraint'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Index Existence Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND tablename = 'notifications'
|
|
|
|
|
AND indexname = 'idx_notifications_user_id') > 0,
|
|
|
|
|
'Index idx_notifications_user_id should exist'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND tablename = 'notifications'
|
|
|
|
|
AND indexname = 'idx_notifications_entity_type') > 0,
|
|
|
|
|
'Index idx_notifications_entity_type should exist'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND tablename = 'notifications'
|
|
|
|
|
AND indexname = 'idx_notifications_entity_id') > 0,
|
|
|
|
|
'Index idx_notifications_entity_id should exist'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND tablename = 'notifications'
|
|
|
|
|
AND indexname = 'idx_notifications_read_at') > 0,
|
|
|
|
|
'Index idx_notifications_read_at should exist'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM pg_indexes
|
|
|
|
|
WHERE schemaname = 'public'
|
|
|
|
|
AND tablename = 'notifications'
|
|
|
|
|
AND indexname = 'idx_notifications_created_at') > 0,
|
|
|
|
|
'Index idx_notifications_created_at should exist'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Trigger Function Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
SELECT has_function('public', 'notify_users', 'Function notify_users should exist');
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Trigger Existence Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'tablos', 'notify_users_on_tablos',
|
|
|
|
|
'Trigger notify_users_on_tablos should exist on tablos table');
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'tasks', 'notify_users_on_tasks',
|
|
|
|
|
'Trigger notify_users_on_tasks should exist on tasks table');
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'events', 'notify_users_on_events',
|
|
|
|
|
'Trigger notify_users_on_events should exist on events table');
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'notes', 'notify_users_on_notes',
|
|
|
|
|
'Trigger notify_users_on_notes should exist on notes table');
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'tablo_access', 'notify_users_on_tablo_access',
|
|
|
|
|
'Trigger notify_users_on_tablo_access should exist on tablo_access table');
|
|
|
|
|
|
|
|
|
|
SELECT has_trigger('public', 'tablo_invites', 'notify_users_on_tablo_invites',
|
|
|
|
|
'Trigger notify_users_on_tablo_invites should exist on tablo_invites table');
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Trigger Behavior Tests - Setup Test Data
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
test_user1_id uuid := gen_random_uuid();
|
|
|
|
|
test_user2_id uuid := gen_random_uuid();
|
|
|
|
|
test_tablo_id text;
|
|
|
|
|
test_task_id text;
|
|
|
|
|
test_event_id text;
|
|
|
|
|
test_note_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
|
|
|
|
|
(test_user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest1_' || test_user1_id::text || '@test.com', 'encrypted', now(), now(), now()),
|
|
|
|
|
(test_user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest2_' || test_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
|
|
|
|
|
(test_user1_id, 'notiftest1_' || test_user1_id::text || '@test.com', 'NotifTest', 'User1', substring(test_user1_id::text from 1 for 8)),
|
|
|
|
|
(test_user2_id, 'notiftest2_' || test_user2_id::text || '@test.com', 'NotifTest', 'User2', substring(test_user2_id::text from 1 for 8))
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
-- Set auth context to user1
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', test_user1_id::text)::text, true);
|
|
|
|
|
|
|
|
|
|
-- Create a tablo as user1
|
|
|
|
|
INSERT INTO public.tablos (owner_id, name, status, position)
|
|
|
|
|
VALUES (test_user1_id, 'Notification 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, test_user2_id, test_user1_id, true, false);
|
|
|
|
|
|
|
|
|
|
-- Create a task
|
|
|
|
|
INSERT INTO public.tasks (tablo_id, title, description, status, assignee_id, position)
|
|
|
|
|
VALUES (test_tablo_id, 'Test Task', 'Test Description', 'todo', test_user2_id, 0)
|
|
|
|
|
RETURNING id INTO test_task_id;
|
|
|
|
|
|
|
|
|
|
-- Create an event
|
|
|
|
|
INSERT INTO public.events (tablo_id, title, description, start_date, start_time, created_by)
|
|
|
|
|
VALUES (test_tablo_id, 'Test Event', 'Test Event Description', CURRENT_DATE, '10:00', test_user1_id)
|
|
|
|
|
RETURNING id INTO test_event_id;
|
|
|
|
|
|
|
|
|
|
-- Create a note
|
|
|
|
|
INSERT INTO public.notes (user_id, title, content)
|
|
|
|
|
VALUES (test_user1_id, 'Test Note', 'Test Note Content')
|
|
|
|
|
RETURNING id INTO test_note_id;
|
|
|
|
|
|
|
|
|
|
-- Share note with user2
|
|
|
|
|
INSERT INTO public.note_access (note_id, user_id, is_active)
|
|
|
|
|
VALUES (test_note_id, test_user2_id, true);
|
|
|
|
|
|
|
|
|
|
-- Store test IDs for use in tests
|
|
|
|
|
PERFORM set_config('test.user1_id', test_user1_id::text, true);
|
|
|
|
|
PERFORM set_config('test.user2_id', test_user2_id::text, true);
|
|
|
|
|
PERFORM set_config('test.tablo_id', test_tablo_id, true);
|
|
|
|
|
PERFORM set_config('test.task_id', test_task_id, true);
|
|
|
|
|
PERFORM set_config('test.event_id', test_event_id, true);
|
|
|
|
|
PERFORM set_config('test.note_id', test_note_id, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Tablo Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Update tablo and check notification for user2
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
UPDATE public.tablos
|
|
|
|
|
SET name = 'Updated Test Tablo'
|
|
|
|
|
WHERE id = current_setting('test.tablo_id');
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
|
|
|
|
AND user_id = current_setting('test.user2_id')::uuid
|
|
|
|
|
AND action_type = 'updated') > 0,
|
|
|
|
|
'Notification should be created for collaborator when tablo is updated'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Task Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Update task status
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
UPDATE public.tasks
|
|
|
|
|
SET status = 'in_progress'
|
|
|
|
|
WHERE id = current_setting('test.task_id');
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tasks'
|
|
|
|
|
AND entity_id = current_setting('test.task_id')
|
|
|
|
|
AND action_type = 'updated') > 0,
|
|
|
|
|
'Notification should be created when task is updated'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Event Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Update event
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
UPDATE public.events
|
|
|
|
|
SET title = 'Updated Test Event'
|
|
|
|
|
WHERE id = current_setting('test.event_id');
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'events'
|
|
|
|
|
AND entity_id = current_setting('test.event_id')
|
|
|
|
|
AND action_type = 'updated') > 0,
|
|
|
|
|
'Notification should be created when event is updated'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Note Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Update note
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
UPDATE public.notes
|
|
|
|
|
SET title = 'Updated Test Note'
|
|
|
|
|
WHERE id = current_setting('test.note_id');
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'notes'
|
|
|
|
|
AND entity_id = current_setting('test.note_id')
|
|
|
|
|
AND action_type = 'updated') > 0,
|
|
|
|
|
'Notification should be created when note is updated'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Tablo Access Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Grant access to a new user and verify notification
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
test_user3_id uuid := gen_random_uuid();
|
|
|
|
|
BEGIN
|
|
|
|
|
-- Insert test user3
|
|
|
|
|
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user3_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest3_' || test_user3_id::text || '@test.com', 'encrypted', now(), now(), now())
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user3_id, 'notiftest3_' || test_user3_id::text || '@test.com', 'NotifTest', 'User3', substring(test_user3_id::text from 1 for 8))
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.user3_id', test_user3_id::text, true);
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Grant access
|
|
|
|
|
INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
|
|
|
|
|
VALUES (current_setting('test.tablo_id'), test_user3_id, current_setting('test.user1_id')::uuid, true, false);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablo_access'
|
|
|
|
|
AND user_id = current_setting('test.user3_id')::uuid
|
|
|
|
|
AND action_type = 'created') > 0,
|
|
|
|
|
'Notification should be created when user is granted tablo access'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Tablo Invite Trigger Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Create an invite
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
|
|
|
|
VALUES (
|
|
|
|
|
current_setting('test.tablo_id'),
|
|
|
|
|
'notiftest2_' || current_setting('test.user2_id') || '@test.com',
|
|
|
|
|
current_setting('test.user1_id')::uuid,
|
|
|
|
|
'test-token-' || gen_random_uuid()::text,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablo_invites'
|
|
|
|
|
AND user_id = current_setting('test.user2_id')::uuid) > 0,
|
2025-11-16 19:34:33 +00:00
|
|
|
'Notification should be created when existing user is invited'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: No notification for non-existent user
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
count_before bigint;
|
|
|
|
|
count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Invite a non-existent user
|
|
|
|
|
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
|
|
|
|
VALUES (
|
|
|
|
|
current_setting('test.tablo_id'),
|
|
|
|
|
'nonexistent@example.com',
|
|
|
|
|
current_setting('test.user1_id')::uuid,
|
|
|
|
|
'test-token-' || gen_random_uuid()::text,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.nonexistent_notif_diff', (count_after - count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.nonexistent_notif_diff', true)::integer,
|
|
|
|
|
0,
|
|
|
|
|
'No notification should be created when inviting non-existent user'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Case-insensitive email matching
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
count_before bigint;
|
|
|
|
|
count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications for user2 before
|
|
|
|
|
SELECT COUNT(*) INTO count_before
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablo_invites'
|
|
|
|
|
AND user_id = current_setting('test.user2_id')::uuid;
|
|
|
|
|
|
|
|
|
|
-- Invite with uppercase email to test case-insensitivity
|
|
|
|
|
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
|
|
|
|
VALUES (
|
|
|
|
|
current_setting('test.tablo_id'),
|
|
|
|
|
UPPER('notiftest2_' || current_setting('test.user2_id') || '@test.com'),
|
|
|
|
|
current_setting('test.user1_id')::uuid,
|
|
|
|
|
'test-token-uppercase-' || gen_random_uuid()::text,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Count after
|
|
|
|
|
SELECT COUNT(*) INTO count_after
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablo_invites'
|
|
|
|
|
AND user_id = current_setting('test.user2_id')::uuid;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.case_insensitive_notif_diff', (count_after - count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.case_insensitive_notif_diff', true)::integer,
|
|
|
|
|
1,
|
|
|
|
|
'Notification should be created with case-insensitive email matching'
|
2025-11-16 11:55:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Multiple Notifications Tests (Exact Count Verification)
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Setup: Create additional users with access to test tablo
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
test_user4_id uuid := gen_random_uuid();
|
|
|
|
|
test_user5_id uuid := gen_random_uuid();
|
|
|
|
|
BEGIN
|
|
|
|
|
-- Insert test users 4 and 5
|
|
|
|
|
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user4_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest4_' || test_user4_id::text || '@test.com', 'encrypted', now(), now(), now()),
|
|
|
|
|
(test_user5_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest5_' || test_user5_id::text || '@test.com', 'encrypted', now(), now(), now())
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user4_id, 'notiftest4_' || test_user4_id::text || '@test.com', 'NotifTest', 'User4', substring(test_user4_id::text from 1 for 8)),
|
|
|
|
|
(test_user5_id, 'notiftest5_' || test_user5_id::text || '@test.com', 'NotifTest', 'User5', substring(test_user5_id::text from 1 for 8))
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Grant access to users 4 and 5 on the test tablo
|
|
|
|
|
INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
|
|
|
|
|
VALUES
|
|
|
|
|
(current_setting('test.tablo_id'), test_user4_id, current_setting('test.user1_id')::uuid, true, false),
|
|
|
|
|
(current_setting('test.tablo_id'), test_user5_id, current_setting('test.user1_id')::uuid, true, false);
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.user4_id', test_user4_id::text, true);
|
|
|
|
|
PERFORM set_config('test.user5_id', test_user5_id::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
-- Test: Multiple users notified when tablo is updated
|
|
|
|
|
-- Expected: user2, user3, user4, user5 should be notified (4 notifications), but NOT user1 (the actor)
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications before update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Update tablo (user1 is the actor)
|
|
|
|
|
UPDATE public.tablos
|
|
|
|
|
SET name = 'Tablo Updated for Multiple Users Test'
|
|
|
|
|
WHERE id = current_setting('test.tablo_id');
|
|
|
|
|
|
|
|
|
|
-- Count notifications after update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.multi_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.multi_notif_count', true)::integer,
|
|
|
|
|
4,
|
|
|
|
|
'Exactly 4 notifications should be created when tablo with 4 collaborators (excluding actor) is updated'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Verify each specific user received the notification
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
|
2025-11-16 11:55:30 +00:00
|
|
|
AND user_id = current_setting('test.user2_id')::uuid),
|
|
|
|
|
1,
|
|
|
|
|
'User2 should receive exactly 1 notification for tablo update'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
|
2025-11-16 11:55:30 +00:00
|
|
|
AND user_id = current_setting('test.user4_id')::uuid),
|
|
|
|
|
1,
|
|
|
|
|
'User4 should receive exactly 1 notification for tablo update'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
|
2025-11-16 11:55:30 +00:00
|
|
|
AND user_id = current_setting('test.user5_id')::uuid),
|
|
|
|
|
1,
|
|
|
|
|
'User5 should receive exactly 1 notification for tablo update'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
|
2025-11-16 11:55:30 +00:00
|
|
|
AND user_id = current_setting('test.user3_id')::uuid),
|
|
|
|
|
1,
|
|
|
|
|
'User3 should receive exactly 1 notification for tablo update'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Actor (user1) should NOT receive a notification
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tablos'
|
|
|
|
|
AND entity_id = current_setting('test.tablo_id')
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' = 'Tablo "Tablo Updated for Multiple Users Test" was updated'
|
2025-11-16 11:55:30 +00:00
|
|
|
AND actor_id = current_setting('test.user1_id')::uuid
|
|
|
|
|
AND user_id = current_setting('test.user1_id')::uuid),
|
|
|
|
|
0,
|
|
|
|
|
'Actor should not receive notification for their own action'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Multiple users notified when new task is created
|
|
|
|
|
-- Expected: user1 (owner), user2, user4, user5 should be notified, but NOT user3 (who created the task)
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
new_task_id text;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user3_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications before creating task
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- User3 creates a new task
|
|
|
|
|
INSERT INTO public.tasks (tablo_id, title, description, status, position)
|
|
|
|
|
VALUES (current_setting('test.tablo_id'), 'Multi-User Test Task', 'Task to test multiple notifications', 'todo', 1)
|
|
|
|
|
RETURNING id INTO new_task_id;
|
|
|
|
|
|
|
|
|
|
-- Count notifications after creating task
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.multi_task_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
PERFORM set_config('test.multi_task_id', new_task_id, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.multi_task_notif_count', true)::integer,
|
|
|
|
|
4,
|
|
|
|
|
'Exactly 4 notifications should be created when task is added to tablo with 4 collaborators (owner + 3 others, excluding actor)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Multiple users notified when task is assigned
|
|
|
|
|
-- Expected: Assignee (user5) + owner (user1) + collaborators (user2, user4) should be notified, but NOT user3 (the actor)
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user3_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications before update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- User3 assigns the task to user5 (without changing status to avoid message priority issues)
|
|
|
|
|
UPDATE public.tasks
|
|
|
|
|
SET assignee_id = current_setting('test.user5_id')::uuid
|
|
|
|
|
WHERE id = current_setting('test.multi_task_id');
|
|
|
|
|
|
|
|
|
|
-- Count notifications after update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.task_assign_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.task_assign_notif_count', true)::integer,
|
|
|
|
|
4,
|
|
|
|
|
'Exactly 4 notifications should be created when task is assigned (owner + assignee + 2 other collaborators, excluding actor)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Assignee (user5) receives notification about being assigned
|
|
|
|
|
SELECT is(
|
|
|
|
|
(SELECT COUNT(*)::integer FROM public.notifications
|
|
|
|
|
WHERE entity_type = 'tasks'
|
|
|
|
|
AND entity_id = current_setting('test.multi_task_id')
|
|
|
|
|
AND user_id = current_setting('test.user5_id')::uuid
|
2025-11-16 21:28:07 +00:00
|
|
|
AND message->>'en' LIKE '%assigned%'),
|
2025-11-16 11:55:30 +00:00
|
|
|
1,
|
|
|
|
|
'Assignee should receive exactly 1 notification about task assignment'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Metadata and Message Tests
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Test: Notification metadata should be valid JSONB
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE jsonb_typeof(metadata) = 'object') > 0,
|
|
|
|
|
'Notification metadata should be valid JSONB objects'
|
|
|
|
|
);
|
|
|
|
|
|
2025-11-16 21:28:07 +00:00
|
|
|
-- Test: Messages should be non-empty and have both en and fr translations
|
2025-11-16 11:55:30 +00:00
|
|
|
SELECT ok(
|
2025-11-16 21:28:07 +00:00
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE length(message->>'en') = 0
|
|
|
|
|
OR length(message->>'fr') = 0
|
|
|
|
|
OR message->>'en' IS NULL
|
|
|
|
|
OR message->>'fr' IS NULL) = 0,
|
|
|
|
|
'All notification messages should have non-empty en and fr translations'
|
2025-11-16 11:55:30 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Action types should be valid
|
|
|
|
|
SELECT ok(
|
|
|
|
|
(SELECT COUNT(*) FROM public.notifications
|
|
|
|
|
WHERE action_type NOT IN ('created', 'updated')) = 0,
|
|
|
|
|
'All notifications should have valid action_type (created or updated)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Edge Cases
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Test: Soft delete should not create notification
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
soft_delete_tablo_id text;
|
|
|
|
|
count_before bigint;
|
|
|
|
|
count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', json_build_object('sub', current_setting('test.user1_id'))::text, true);
|
|
|
|
|
|
|
|
|
|
-- Create a tablo and immediately soft delete it
|
|
|
|
|
INSERT INTO public.tablos (owner_id, name, status, position)
|
|
|
|
|
VALUES (current_setting('test.user1_id')::uuid, 'Soft Delete Test', 'todo', 0)
|
|
|
|
|
RETURNING id INTO soft_delete_tablo_id;
|
|
|
|
|
|
|
|
|
|
-- Count notifications before soft delete
|
|
|
|
|
SELECT COUNT(*) INTO count_before
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE entity_id = soft_delete_tablo_id;
|
|
|
|
|
|
|
|
|
|
-- Soft delete
|
|
|
|
|
UPDATE public.tablos
|
|
|
|
|
SET deleted_at = now()
|
|
|
|
|
WHERE id = soft_delete_tablo_id;
|
|
|
|
|
|
|
|
|
|
-- Count notifications after soft delete
|
|
|
|
|
SELECT COUNT(*) INTO count_after
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE entity_id = soft_delete_tablo_id;
|
|
|
|
|
|
|
|
|
|
-- Store results
|
|
|
|
|
PERFORM set_config('test.notif_count_before', count_before::text, true);
|
|
|
|
|
PERFORM set_config('test.notif_count_after', count_after::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.notif_count_before', true),
|
|
|
|
|
current_setting('test.notif_count_after', true),
|
|
|
|
|
'Soft delete should not create additional notifications'
|
|
|
|
|
);
|
|
|
|
|
|
2025-11-16 19:34:33 +00:00
|
|
|
-- ============================================================================
|
|
|
|
|
-- NULL Actor Tests (Service Role / API Operations)
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- Test: Tablo update with NULL actor should notify all collaborators
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
-- Clear auth context (simulates service role call with no auth.uid())
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications before update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Update tablo without actor context
|
|
|
|
|
UPDATE public.tablos
|
|
|
|
|
SET name = 'Updated by Service Role'
|
|
|
|
|
WHERE id = current_setting('test.tablo_id');
|
|
|
|
|
|
|
|
|
|
-- Count notifications after update
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_notif_count', true)::integer,
|
|
|
|
|
5,
|
|
|
|
|
'With NULL actor, all collaborators (including owner) should be notified on tablo update (5 total: user1-5)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Task creation with NULL actor
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
new_task_id text;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Create task without actor context
|
|
|
|
|
INSERT INTO public.tasks (tablo_id, title, description, status, position)
|
|
|
|
|
VALUES (current_setting('test.tablo_id'), 'Service Role Task', 'Created via service role', 'todo', 2)
|
|
|
|
|
RETURNING id INTO new_task_id;
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_task_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
PERFORM set_config('test.null_actor_task_id', new_task_id, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_task_notif_count', true)::integer,
|
|
|
|
|
5,
|
|
|
|
|
'With NULL actor, all tablo members should be notified on task creation (owner + 4 collaborators)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Tablo access grant with NULL actor
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
test_user6_id uuid := gen_random_uuid();
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
-- Insert test user6
|
|
|
|
|
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user6_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'notiftest6_' || test_user6_id::text || '@test.com', 'encrypted', now(), now(), now())
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
INSERT INTO public.profiles (id, email, first_name, last_name, short_user_id)
|
|
|
|
|
VALUES
|
|
|
|
|
(test_user6_id, 'notiftest6_' || test_user6_id::text || '@test.com', 'NotifTest', 'User6', substring(test_user6_id::text from 1 for 8))
|
|
|
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.user6_id', test_user6_id::text, true);
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE user_id = test_user6_id;
|
|
|
|
|
|
|
|
|
|
-- Grant access without actor context
|
|
|
|
|
INSERT INTO public.tablo_access (tablo_id, user_id, granted_by, is_active, is_admin)
|
|
|
|
|
VALUES (current_setting('test.tablo_id'), test_user6_id, current_setting('test.user1_id')::uuid, true, false);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE user_id = test_user6_id;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_access_notif_diff', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_access_notif_diff', true)::integer,
|
|
|
|
|
1,
|
|
|
|
|
'With NULL actor, user should still be notified when granted tablo access'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Tablo invite with NULL actor (simulating API invite)
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
-- Count notifications for user6 before invite
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE user_id = current_setting('test.user6_id')::uuid
|
|
|
|
|
AND entity_type = 'tablo_invites';
|
|
|
|
|
|
|
|
|
|
-- Invite user6 without actor context (via service role)
|
|
|
|
|
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
|
|
|
|
VALUES (
|
|
|
|
|
current_setting('test.tablo_id'),
|
|
|
|
|
'notiftest6_' || current_setting('test.user6_id') || '@test.com',
|
|
|
|
|
current_setting('test.user1_id')::uuid,
|
|
|
|
|
'test-token-null-actor-' || gen_random_uuid()::text,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Count after
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after
|
|
|
|
|
FROM public.notifications
|
|
|
|
|
WHERE user_id = current_setting('test.user6_id')::uuid
|
|
|
|
|
AND entity_type = 'tablo_invites';
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_invite_notif_diff', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_invite_notif_diff', true)::integer,
|
|
|
|
|
1,
|
|
|
|
|
'With NULL actor, invited user should still receive notification (API-initiated invite)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Event creation with NULL actor
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
new_event_id text;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Create event without actor context
|
|
|
|
|
INSERT INTO public.events (tablo_id, title, description, start_date, start_time, created_by)
|
|
|
|
|
VALUES (current_setting('test.tablo_id'), 'Service Role Event', 'Event via service role', CURRENT_DATE, '14:00', current_setting('test.user1_id')::uuid)
|
|
|
|
|
RETURNING id INTO new_event_id;
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_event_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_event_notif_count', true)::integer,
|
|
|
|
|
6,
|
|
|
|
|
'With NULL actor, all tablo members should be notified on event creation (owner + 5 collaborators including user6)'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- Test: Note update with NULL actor
|
|
|
|
|
DO $$
|
|
|
|
|
DECLARE
|
|
|
|
|
notif_count_before bigint;
|
|
|
|
|
notif_count_after bigint;
|
|
|
|
|
BEGIN
|
|
|
|
|
PERFORM set_config('request.jwt.claims', NULL, true);
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_before FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
-- Update note without actor context
|
|
|
|
|
UPDATE public.notes
|
|
|
|
|
SET content = 'Updated by service role'
|
|
|
|
|
WHERE id = current_setting('test.note_id');
|
|
|
|
|
|
|
|
|
|
SELECT COUNT(*) INTO notif_count_after FROM public.notifications;
|
|
|
|
|
|
|
|
|
|
PERFORM set_config('test.null_actor_note_notif_count', (notif_count_after - notif_count_before)::text, true);
|
|
|
|
|
END $$;
|
|
|
|
|
|
|
|
|
|
SELECT is(
|
|
|
|
|
current_setting('test.null_actor_note_notif_count', true)::integer,
|
|
|
|
|
2,
|
|
|
|
|
'With NULL actor, note owner (user1) and collaborator (user2) should both be notified on note update'
|
|
|
|
|
);
|
|
|
|
|
|
2025-11-16 11:55:30 +00:00
|
|
|
select * from finish();
|
|
|
|
|
rollback;
|