# Moderation Queue Testing Guide ## Overview Comprehensive testing strategy for the moderation queue component covering unit tests, integration tests, and end-to-end tests. ## Test Structure ``` tests/ ├── unit/ # Fast, isolated tests │ └── sanitize.test.ts # Input sanitization ├── integration/ # Database + API tests │ └── moderation-security.test.ts ├── e2e/ # Browser-based tests │ └── moderation/ │ └── lock-management.spec.ts ├── fixtures/ # Shared test utilities │ ├── auth.ts # Authentication helpers │ └── database.ts # Database setup/teardown └── setup/ ├── global-setup.ts # Runs before all tests └── global-teardown.ts # Runs after all tests ``` ## Unit Tests ### Sanitization Tests **File:** `tests/unit/sanitize.test.ts` Tests XSS protection utilities: - URL validation (block `javascript:`, `data:` protocols) - HTML entity escaping - Plain text sanitization - Suspicious content detection **Run:** ```bash npm run test:unit -- sanitize ``` ### Hook Tests (Future) Test custom hooks in isolation: - `useModerationQueue` - `useModerationActions` - `useQueueQuery` **Example:** ```typescript import { renderHook } from '@testing-library/react'; import { useModerationQueue } from '@/hooks/useModerationQueue'; test('should claim submission', async () => { const { result } = renderHook(() => useModerationQueue()); const success = await result.current.claimSubmission('test-id'); expect(success).toBe(true); expect(result.current.currentLock).toBeTruthy(); }); ``` ## Integration Tests ### Moderation Security Tests **File:** `tests/integration/moderation-security.test.ts` Tests backend security enforcement: 1. **Role Validation** - Regular users cannot perform moderation actions - Only moderators/admins/superusers can validate actions 2. **Lock Enforcement** - Cannot modify submission locked by another moderator - Lock must be claimed before approve/reject - Expired locks are automatically released 3. **Audit Logging** - All actions logged in `moderation_audit_log` - Logs include metadata (notes, status changes) - Logs are immutable (cannot be modified) 4. **Rate Limiting** - Maximum 10 actions per minute per user - 11th action within minute fails with rate limit error **Run:** ```bash npm run test:integration -- moderation-security ``` ### Test Data Management **Setup:** - Uses service role key to create test users and data - All test data marked with `is_test_data: true` - Isolated from production data **Cleanup:** - Global teardown removes all test data - Query `moderation_audit_log` to verify cleanup - Check `getTestDataStats()` for remaining records **Example:** ```typescript import { setupTestUser, cleanupTestData } from '../fixtures/database'; test.beforeAll(async () => { await cleanupTestData(); await setupTestUser('test@example.com', 'password', 'moderator'); }); test.afterAll(async () => { await cleanupTestData(); }); ``` ## End-to-End Tests ### Lock Management E2E **File:** `tests/e2e/moderation/lock-management.spec.ts` Browser-based tests using Playwright: 1. **Claim Submission** - Click "Claim Submission" button - Verify lock badge appears ("Claimed by you") - Verify approve/reject buttons enabled 2. **Lock Timer** - Verify countdown displays (14:XX format) - Verify lock status badge visible 3. **Extend Lock** - Wait for timer to reach < 5 minutes - Verify "Extend Lock" button appears - Click extend, verify timer resets 4. **Release Lock** - Click "Release Lock" button - Verify "Claim Submission" button reappears - Verify approve/reject buttons disabled 5. **Locked by Another** - Verify lock badge for items locked by others - Verify actions disabled **Run:** ```bash npm run test:e2e -- lock-management ``` ### Authentication in E2E Tests **Global Setup** (`tests/setup/global-setup.ts`): - Creates test users for all roles (user, moderator, admin, superuser) - Logs in each user and saves auth state to `.auth/` directory - Auth states reused across all tests (faster execution) **Test Usage:** ```typescript // Use saved auth state test.use({ storageState: '.auth/moderator.json' }); test('moderator can access queue', async ({ page }) => { await page.goto('/moderation/queue'); // Already authenticated as moderator }); ``` **Manual Login (if needed):** ```typescript import { loginAsUser } from '../fixtures/auth'; const { userId, accessToken } = await loginAsUser( 'test@example.com', 'password' ); ``` ## Test Fixtures ### Database Fixtures **File:** `tests/fixtures/database.ts` **Functions:** - `setupTestUser()` - Create test user with specific role - `cleanupTestData()` - Remove all test data - `queryDatabase()` - Direct database queries for assertions - `waitForVersion()` - Wait for version record to be created - `approveSubmissionDirect()` - Bypass UI for test setup - `getTestDataStats()` - Get count of test records **Example:** ```typescript import { setupTestUser, supabaseAdmin } from '../fixtures/database'; // Create moderator const { userId } = await setupTestUser( 'mod@test.com', 'password', 'moderator' ); // Create test submission const { data } = await supabaseAdmin .from('content_submissions') .insert({ submission_type: 'review', status: 'pending', submitted_by: userId, is_test_data: true, }) .select() .single(); ``` ### Auth Fixtures **File:** `tests/fixtures/auth.ts` **Functions:** - `setupAuthStates()` - Create auth states for all roles - `getTestUserCredentials()` - Get email/password for role - `loginAsUser()` - Programmatic login - `logout()` - Programmatic logout **Test Users:** ```typescript const TEST_USERS = { user: 'test-user@thrillwiki.test', moderator: 'test-moderator@thrillwiki.test', admin: 'test-admin@thrillwiki.test', superuser: 'test-superuser@thrillwiki.test', }; ``` ## Running Tests ### All Tests ```bash npm run test ``` ### Unit Tests Only ```bash npm run test:unit ``` ### Integration Tests Only ```bash npm run test:integration ``` ### E2E Tests Only ```bash npm run test:e2e ``` ### Specific Test File ```bash npm run test:e2e -- lock-management npm run test:integration -- moderation-security npm run test:unit -- sanitize ``` ### Watch Mode ```bash npm run test:watch ``` ### Coverage Report ```bash npm run test:coverage ``` ## Writing New Tests ### Unit Test Template ```typescript import { describe, it, expect } from '@playwright/test'; import { functionToTest } from '@/lib/module'; describe('functionToTest', () => { it('should handle valid input', () => { const result = functionToTest('valid input'); expect(result).toBe('expected output'); }); it('should handle edge case', () => { const result = functionToTest(''); expect(result).toBe('default value'); }); it('should throw on invalid input', () => { expect(() => functionToTest(null)).toThrow(); }); }); ``` ### Integration Test Template ```typescript import { test, expect } from '@playwright/test'; import { setupTestUser, supabaseAdmin, cleanupTestData } from '../fixtures/database'; test.describe('Feature Name', () => { test.beforeAll(async () => { await cleanupTestData(); }); test.afterAll(async () => { await cleanupTestData(); }); test('should perform action', async () => { // Setup const { userId } = await setupTestUser( 'test@example.com', 'password', 'moderator' ); // Action const { data, error } = await supabaseAdmin .from('table_name') .insert({ ... }); // Assert expect(error).toBeNull(); expect(data).toBeTruthy(); }); }); ``` ### E2E Test Template ```typescript import { test, expect } from '@playwright/test'; test.use({ storageState: '.auth/moderator.json' }); test.describe('Feature Name', () => { test.beforeEach(async ({ page }) => { await page.goto('/moderation/queue'); await page.waitForLoadState('networkidle'); }); test('should interact with UI', async ({ page }) => { // Find element const button = page.locator('button:has-text("Action")'); // Assert initial state await expect(button).toBeVisible(); await expect(button).toBeEnabled(); // Perform action await button.click(); // Assert result await expect(page.locator('text=Success')).toBeVisible(); }); }); ``` ## Best Practices ### 1. Test Isolation Each test should be independent: - ✅ Clean up test data in `afterEach` or `afterAll` - ✅ Use unique identifiers for test records - ❌ Don't rely on data from previous tests ### 2. Realistic Test Data Use realistic data patterns: - ✅ Valid email formats - ✅ Appropriate string lengths - ✅ Realistic timestamps - ❌ Don't use `test123` everywhere ### 3. Error Handling Test both success and failure cases: ```typescript // Test success test('should approve valid submission', async () => { const { error } = await approveSubmission(validId); expect(error).toBeNull(); }); // Test failure test('should reject invalid submission', async () => { const { error } = await approveSubmission(invalidId); expect(error).toBeTruthy(); }); ``` ### 4. Async Handling Always await async operations: ```typescript // ❌ WRONG test('test name', () => { asyncFunction(); // Not awaited expect(result).toBe(value); // May run before async completes }); // ✅ CORRECT test('test name', async () => { await asyncFunction(); expect(result).toBe(value); }); ``` ### 5. Descriptive Test Names Use clear, descriptive names: ```typescript // ❌ Vague test('test 1', () => { ... }); // ✅ Clear test('should prevent non-moderator from approving submission', () => { ... }); ``` ## Debugging Tests ### Enable Debug Mode ```bash # Playwright debug mode (E2E) PWDEBUG=1 npm run test:e2e -- lock-management # Show browser during E2E tests npm run test:e2e -- --headed # Slow down actions for visibility npm run test:e2e -- --slow-mo=1000 ``` ### Console Logging ```typescript // In tests console.log('Debug info:', variable); // View logs npm run test -- --verbose ``` ### Screenshots on Failure ```typescript // In playwright.config.ts use: { screenshot: 'only-on-failure', video: 'retain-on-failure', } ``` ### Database Inspection ```typescript // Query database during test const { data } = await supabaseAdmin .from('content_submissions') .select('*') .eq('id', testId); console.log('Submission state:', data); ``` ## Continuous Integration ### GitHub Actions (Example) ```yaml name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run unit tests run: npm run test:unit - name: Run integration tests run: npm run test:integration env: SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} - name: Run E2E tests run: npm run test:e2e env: BASE_URL: http://localhost:8080 ``` ## Coverage Goals - **Unit Tests:** 90%+ coverage - **Integration Tests:** All critical paths covered - **E2E Tests:** Happy paths + key error scenarios **Generate Coverage Report:** ```bash npm run test:coverage open coverage/index.html ``` ## Troubleshooting ### Test Timeout ```typescript // Increase timeout for slow operations test('slow test', async () => { test.setTimeout(60000); // 60 seconds await slowOperation(); }); ``` ### Flaky Tests Common causes and fixes: - **Race conditions:** Add `waitFor` or `waitForSelector` - **Network delays:** Increase timeout, add retries - **Test data conflicts:** Ensure unique IDs ### Database Connection Issues ```typescript // Check connection if (!supabaseAdmin) { throw new Error('Service role key not configured'); } ``` ## Future Test Coverage - [ ] Unit tests for all custom hooks - [ ] Component snapshot tests - [ ] Accessibility tests (axe-core) - [ ] Performance tests (lighthouse) - [ ] Load testing (k6 or similar) - [ ] Visual regression tests (Percy/Chromatic)