Files
thrilltrack-explorer/docs/moderation/TESTING.md
gpt-engineer-app[bot] a9644c0bee Approve tool use
2025-11-02 21:46:47 +00:00

567 lines
12 KiB
Markdown

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