/** * Integration Tests for Moderation Security * * Tests backend validation, lock enforcement, and audit logging */ import { test, expect } from '@playwright/test'; import { setupTestUser, supabaseAdmin, cleanupTestData } from '../fixtures/database'; import { createClient } from '@supabase/supabase-js'; const supabaseUrl = 'https://ydvtmnrszybqnbcqbdcy.supabase.co'; const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlkdnRtbnJzenlicW5iY3FiZGN5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgzMjYzNTYsImV4cCI6MjA3MzkwMjM1Nn0.DM3oyapd_omP5ZzIlrT0H9qBsiQBxBRgw2tYuqgXKX4'; test.describe('Moderation Security', () => { test.beforeAll(async () => { await cleanupTestData(); }); test.afterAll(async () => { await cleanupTestData(); }); test('should validate moderator role before allowing actions', async () => { // Create a regular user (not moderator) const { userId, email } = await setupTestUser( 'regular-user@test.com', 'TestPassword123!', 'user' ); // Create authenticated client for regular user const userClient = createClient(supabaseUrl, supabaseAnonKey); await userClient.auth.signInWithPassword({ email, password: 'TestPassword123!', }); // Create a test submission if (!supabaseAdmin) { throw new Error('Admin client not available'); } const { data: submission } = await supabaseAdmin .from('content_submissions') .insert({ submission_type: 'review', status: 'pending', submitted_by: userId, is_test_data: true, }) .select() .single(); expect(submission).toBeTruthy(); // Try to call validation function as regular user (should fail) const { data, error } = await userClient.rpc('validate_moderation_action', { _submission_id: submission!.id, _user_id: userId, _action: 'approve', }); // Should fail with authorization error expect(error).toBeTruthy(); expect(error?.message).toContain('Unauthorized'); await userClient.auth.signOut(); }); test('should enforce lock when another moderator has claimed submission', async () => { // Create two moderators const { userId: mod1Id, email: mod1Email } = await setupTestUser( 'moderator1@test.com', 'TestPassword123!', 'moderator' ); const { userId: mod2Id, email: mod2Email } = await setupTestUser( 'moderator2@test.com', 'TestPassword123!', 'moderator' ); // Create submission if (!supabaseAdmin) { throw new Error('Admin client not available'); } const { data: submission } = await supabaseAdmin .from('content_submissions') .insert({ submission_type: 'review', status: 'pending', submitted_by: mod1Id, is_test_data: true, }) .select() .single(); // Moderator 1 claims the submission const mod1Client = createClient(supabaseUrl, supabaseAnonKey); await mod1Client.auth.signInWithPassword({ email: mod1Email, password: 'TestPassword123!', }); await mod1Client .from('content_submissions') .update({ assigned_to: mod1Id, locked_until: new Date(Date.now() + 15 * 60 * 1000).toISOString(), }) .eq('id', submission!.id); // Moderator 2 tries to validate action (should fail due to lock) const mod2Client = createClient(supabaseUrl, supabaseAnonKey); await mod2Client.auth.signInWithPassword({ email: mod2Email, password: 'TestPassword123!', }); const { data, error } = await mod2Client.rpc('validate_moderation_action', { _submission_id: submission!.id, _user_id: mod2Id, _action: 'approve', }); // Should fail with lock error expect(error).toBeTruthy(); expect(error?.message).toContain('locked by another moderator'); await mod1Client.auth.signOut(); await mod2Client.auth.signOut(); }); test('should create audit log entries for moderation actions', async () => { const { userId, email } = await setupTestUser( 'audit-moderator@test.com', 'TestPassword123!', 'moderator' ); if (!supabaseAdmin) { throw new Error('Admin client not available'); } // Create submission const { data: submission } = await supabaseAdmin .from('content_submissions') .insert({ submission_type: 'review', status: 'pending', submitted_by: userId, is_test_data: true, }) .select() .single(); const modClient = createClient(supabaseUrl, supabaseAnonKey); await modClient.auth.signInWithPassword({ email, password: 'TestPassword123!', }); // Claim submission (should trigger audit log) await modClient .from('content_submissions') .update({ assigned_to: userId, locked_until: new Date(Date.now() + 15 * 60 * 1000).toISOString(), }) .eq('id', submission!.id); // Wait a moment for trigger to fire await new Promise(resolve => setTimeout(resolve, 1000)); // Check audit log const { data: auditLogs } = await supabaseAdmin .from('moderation_audit_log') .select('*') .eq('submission_id', submission!.id) .eq('action', 'claim'); expect(auditLogs).toBeTruthy(); expect(auditLogs!.length).toBeGreaterThan(0); expect(auditLogs![0].moderator_id).toBe(userId); await modClient.auth.signOut(); }); test('should enforce rate limiting (10 actions per minute)', async () => { const { userId, email } = await setupTestUser( 'rate-limit-mod@test.com', 'TestPassword123!', 'moderator' ); if (!supabaseAdmin) { throw new Error('Admin client not available'); } const modClient = createClient(supabaseUrl, supabaseAnonKey); await modClient.auth.signInWithPassword({ email, password: 'TestPassword123!', }); // Create 11 submissions const submissions = []; for (let i = 0; i < 11; i++) { const { data } = await supabaseAdmin .from('content_submissions') .insert({ submission_type: 'review', status: 'pending', submitted_by: userId, is_test_data: true, }) .select() .single(); submissions.push(data); } // Try to validate 11 actions (should fail on 11th) let successCount = 0; let failCount = 0; for (const submission of submissions) { const { error } = await modClient.rpc('validate_moderation_action', { _submission_id: submission!.id, _user_id: userId, _action: 'approve', }); if (error) { failCount++; expect(error.message).toContain('Rate limit exceeded'); } else { successCount++; } } // Should have at least one failure due to rate limiting expect(failCount).toBeGreaterThan(0); expect(successCount).toBeLessThanOrEqual(10); await modClient.auth.signOut(); }); });