mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:11:17 -05:00
250 lines
6.9 KiB
TypeScript
250 lines
6.9 KiB
TypeScript
/**
|
|
* 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();
|
|
});
|
|
});
|