Files
gpt-engineer-app[bot] a9644c0bee Approve tool use
2025-11-02 21:46:47 +00:00

12 KiB

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:

npm run test:unit -- sanitize

Hook Tests (Future)

Test custom hooks in isolation:

  • useModerationQueue
  • useModerationActions
  • useQueueQuery

Example:

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:

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:

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:

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:

// 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):

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:

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:

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

npm run test

Unit Tests Only

npm run test:unit

Integration Tests Only

npm run test:integration

E2E Tests Only

npm run test:e2e

Specific Test File

npm run test:e2e -- lock-management
npm run test:integration -- moderation-security
npm run test:unit -- sanitize

Watch Mode

npm run test:watch

Coverage Report

npm run test:coverage

Writing New Tests

Unit Test Template

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

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

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:

// 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:

// ❌ 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:

// ❌ Vague
test('test 1', () => { ... });

// ✅ Clear
test('should prevent non-moderator from approving submission', () => { ... });

Debugging Tests

Enable Debug Mode

# 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

// In tests
console.log('Debug info:', variable);

// View logs
npm run test -- --verbose

Screenshots on Failure

// In playwright.config.ts
use: {
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
}

Database Inspection

// 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)

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:

npm run test:coverage
open coverage/index.html

Troubleshooting

Test Timeout

// 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

// 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)