Comprehensive testing documentation for the JudgeFinder Platform.
Version: 1.1.0 Last Updated: 2025-11-09 Status: Production
- Overview
- Testing Philosophy
- Test Structure
- Running Tests
- Writing Tests
- Unit Testing
- Integration Testing
- E2E Testing
- Test Patterns
- Mocking Guide
- Test Fixtures
- Test Coverage
- CI/CD Integration
- Best Practices
- Troubleshooting
- ID-Length Strategy
The JudgeFinder testing infrastructure consists of three main types of tests:
- Unit Tests: Test individual functions and business logic in isolation
- Integration Tests: Test API endpoints and database interactions
- E2E Tests: Test complete user flows through the browser
- Vitest: Fast unit and integration testing framework
- Playwright: Browser automation for E2E testing
- Testing Library: React component testing utilities
- Happy DOM: Lightweight DOM implementation for unit tests
Testing is not optional—it's a core part of development at JudgeFinder:
- Confidence: Ship features without fear
- Documentation: Tests serve as executable documentation
- Regression Prevention: Catch bugs before users do
- Refactoring Safety: Change code with confidence
- Design Feedback: Tests reveal design issues early
/\
/ \ E2E Tests (5%)
/ \ - Critical user flows
/------\ - Real browser automation
/ \
/ \ Integration Tests (25%)
/ \ - API endpoint testing
/ \ - Database integration
/----------------\
| | Unit Tests (70%)
| | - Pure functions
| | - Business logic
| | - Helper utilities
------------------
70% Unit Tests: Fast, isolated, abundant 25% Integration Tests: API routes, database, external services 5% E2E Tests: Critical user flows only
- New Features: >80% coverage required
- Bug Fixes: Add test that reproduces bug first
- Critical Paths: >90% coverage (auth, billing, search)
- Overall Project: Maintain >75% coverage
tests/
├── setup/
│ └── test-setup.ts # Global test configuration
├── fixtures/
│ ├── judges.ts # Mock judge data
│ ├── cases.ts # Mock case data
│ └── users.ts # Mock user data
├── unit/
│ ├── auth/
│ │ ├── is-admin.test.ts
│ │ ├── bar-number-validation.test.ts
│ │ └── turnstile.test.ts
│ ├── analytics/
│ │ ├── bias-calculations.test.ts
│ │ └── report-generation.test.ts
│ ├── api/
│ │ ├── judges/
│ │ │ └── search-helpers.test.ts
│ │ └── stripe-webhook.test.ts
│ ├── lib/
│ │ ├── analytics/
│ │ └── utils/
│ └── validation/
│ ├── input-validation.test.ts
│ └── security-validation.test.ts
├── integration/
│ ├── api/
│ │ ├── search-route.test.ts
│ │ ├── chat-route.test.ts
│ │ └── judges-analytics.test.ts
│ └── stripe-flow.test.ts
├── e2e/
│ ├── auth/
│ │ └── sign-up-flow.spec.ts
│ ├── search/
│ │ └── judge-search.spec.ts
│ └── homepage-blur-animation.test.ts
├── migrations/
│ ├── analytics-cache.test.ts
│ ├── data-integrity.test.ts
│ └── security-definer-views.test.ts
└── helpers/
├── auth-helpers.ts
├── db-helpers.ts
└── mock-helpers.ts
File Names:
{module-name}.test.tsfor unit tests{feature}-route.test.tsfor API routes{feature}-workflow.test.tsfor E2E tests
Test Descriptions:
// ✅ Good - Descriptive, behavior-focused
describe('calculateBiasIndicators', () => {
it('should calculate bias indicators within valid ranges', () => {
// ...
})
it('should handle edge case with single case', () => {
// ...
})
})
// ❌ Bad - Vague, implementation-focused
describe('Bias', () => {
it('works', () => {
// ...
})
})All tests should follow the AAA pattern:
it('should calculate settlement preference correctly', () => {
// Arrange - Set up test data and conditions
const cases: CaseRecord[] = Array(10)
.fill(null)
.map(() => ({ outcome: 'Settled' }))
const caseTypePatterns = analyzeCaseTypePatterns(cases)
const outcomeAnalysis = analyzeOutcomes(cases)
// Act - Execute the code under test
const indicators = calculateBiasIndicators(cases, caseTypePatterns, outcomeAnalysis)
// Assert - Verify the results
expect(indicators.settlement_preference).toBeGreaterThan(0)
})# Run all unit and integration tests
npm test
# Run all tests including E2E
npm run test:all# Run unit tests once
npm run test:unit
# Run in watch mode during development
npm run test:watch
# Run specific test file
npm run test:unit tests/unit/auth/is-admin.test.ts# Run all integration tests
npm run test:integration
# Run specific integration test
npm run test:integration tests/integration/api/search.test.ts# Run E2E tests (headless)
npm run test:e2e
# Run with UI mode for debugging
npm run test:e2e:ui
# Run in headed mode (see browser)
npm run test:e2e:headed
# Run specific browser
npm run test:e2e -- --project=chromium# Generate coverage report
npm run test:coverage
# Coverage report will be in coverage/index.html
open coverage/index.html # macOS
start coverage/index.html # WindowsUnit tests should test individual functions in isolation with mocked dependencies.
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { myFunction } from '@/lib/my-module'
// Mock dependencies
vi.mock('@/lib/dependency', () => ({
dependencyFunction: vi.fn(() => 'mocked value'),
}))
describe('MyFunction', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return expected result', () => {
const result = myFunction('input')
expect(result).toBe('expected output')
})
it('should handle edge cases', () => {
expect(myFunction('')).toBe('')
expect(myFunction(null)).toBe(null)
})
})- Pure functions
- Business logic
- Data transformations
- Helper utilities
- Validators
- Calculations
// tests/unit/lib/utils/slug.test.ts
import { describe, it, expect } from 'vitest'
import { generateJudgeSlug, parseJudgeSlug } from '@/lib/utils/slug'
describe('generateJudgeSlug', () => {
it('should generate slug from judge name and ID', () => {
const slug = generateJudgeSlug('John Smith', 123)
expect(slug).toBe('john-smith-123')
})
it('should handle special characters', () => {
const slug = generateJudgeSlug("O'Brien-Smith", 456)
expect(slug).toBe('obrien-smith-456')
})
it('should handle empty name gracefully', () => {
const slug = generateJudgeSlug('', 999)
expect(slug).toBe('judge-999')
})
})// tests/unit/validation/input-validation.test.ts
import { describe, it, expect } from 'vitest'
import { validateSearchParams } from '@/lib/validation/search'
describe('validateSearchParams', () => {
it('should validate correct search parameters', () => {
const result = validateSearchParams({
query: 'judge smith',
limit: 20,
page: 1,
})
expect(result.success).toBe(true)
expect(result.data.query).toBe('judge smith')
})
it('should enforce limit maximum', () => {
const result = validateSearchParams({
query: 'test',
limit: 1000,
})
expect(result.success).toBe(false)
expect(result.error.message).toContain('Limit cannot exceed 500')
})
it('should sanitize SQL injection attempts', () => {
const result = validateSearchParams({
query: "'; DROP TABLE judges; --",
})
expect(result.success).toBe(true)
expect(result.data.query).not.toContain('DROP TABLE')
})
})// tests/unit/analytics/bias-calculations.test.ts
import { describe, it, expect } from 'vitest'
import { calculateBiasIndicators } from '@/lib/analytics/bias-calculations'
describe('calculateBiasIndicators', () => {
it('should calculate bias indicators within valid ranges', () => {
const cases: CaseRecord[] = [
{
case_type: 'Civil',
outcome: 'Settled',
case_value: 100000,
filing_date: '2023-01-01',
decision_date: '2023-03-01',
},
{
case_type: 'Civil',
outcome: 'Settled',
case_value: 150000,
filing_date: '2023-02-01',
decision_date: '2023-04-01',
},
]
const patterns = analyzeCaseTypePatterns(cases)
const analysis = analyzeOutcomes(cases)
const indicators = calculateBiasIndicators(cases, patterns, analysis)
// Verify all metrics are in valid ranges
expect(indicators.consistency_score).toBeGreaterThanOrEqual(0)
expect(indicators.consistency_score).toBeLessThanOrEqual(100)
expect(indicators.settlement_preference).toBeGreaterThanOrEqual(-50)
expect(indicators.settlement_preference).toBeLessThanOrEqual(50)
})
})// tests/unit/api/judges/search-helpers.test.ts
import { describe, it, expect } from 'vitest'
import { parseSearchParams } from '@/app/api/judges/search/helpers'
describe('parseSearchParams', () => {
it('should handle malformed URLs gracefully', () => {
const request = new Request('http://example.com?invalid=%%')
expect(() => parseSearchParams(request)).not.toThrow()
})
it('should provide default values for missing parameters', () => {
const request = new Request('http://example.com')
const params = parseSearchParams(request)
expect(params.query).toBe('')
expect(params.limit).toBe(20)
expect(params.page).toBe(1)
})
})// tests/unit/lib/utils/helpers.test.ts
import { describe, it, expect } from 'vitest'
import { formatCurrency, formatDate } from '@/lib/utils/helpers'
describe('formatCurrency', () => {
it('should format USD currency correctly', () => {
expect(formatCurrency(1234.56)).toBe('$1,234.56')
expect(formatCurrency(1000000)).toBe('$1,000,000.00')
})
it('should handle zero and negative values', () => {
expect(formatCurrency(0)).toBe('$0.00')
expect(formatCurrency(-500)).toBe('-$500.00')
})
it('should handle null/undefined', () => {
expect(formatCurrency(null)).toBe('$0.00')
expect(formatCurrency(undefined)).toBe('$0.00')
})
})- API endpoints
- Database queries
- External service integration
- Authentication flows
- Multi-step processes
Integration tests verify API endpoints and database interactions work together correctly.
import { describe, it, expect, vi } from 'vitest'
import { NextRequest } from 'next/server'
import { GET } from '@/app/api/my-endpoint/route'
vi.mock('@/lib/supabase/server', () => ({
createServerClient: vi.fn(async () => ({
from: vi.fn(() => ({
select: vi.fn(() => ({
eq: vi.fn(() => ({
data: mockData,
error: null,
})),
})),
})),
})),
}))
describe('API /api/my-endpoint', () => {
it('should return data successfully', async () => {
const request = new NextRequest('http://localhost:3000/api/my-endpoint')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data).toBeDefined()
})
})// tests/integration/api/search-route.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { GET } from '@/app/api/judges/search/route'
import { mockAuthenticatedSession, createMockRequest } from '../../helpers/auth-helpers'
describe('GET /api/judges/search', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('should return search results for valid query', async () => {
mockAuthenticatedSession()
const request = createMockRequest('http://localhost:3000/api/judges/search?q=judge+smith')
const response = await GET(request)
const json = await response.json()
expect(response.status).toBe(200)
expect(json.results).toBeDefined()
expect(Array.isArray(json.results)).toBe(true)
})
it('should support pagination', async () => {
mockAuthenticatedSession()
const request = createMockRequest('http://localhost:3000/api/judges/search?page=2&limit=10')
const response = await GET(request)
const json = await response.json()
expect(response.status).toBe(200)
expect(json.page).toBe(2)
expect(json.per_page).toBe(10)
})
})describe('Rate Limiting', () => {
it('should enforce 10 searches per day for anonymous users', async () => {
mockUnauthenticatedSession()
const mockRateLimiter = mockRateLimiterBlock(0)
const request = createMockRequestWithIP(
'http://localhost:3000/api/judges/search?q=judge',
'192.168.1.1'
)
const response = await GET(request)
const json = await response.json()
expect(response.status).toBe(429)
expect(json.error).toContain('Daily search limit reached')
})
it('should include rate limit headers', async () => {
mockAuthenticatedSession()
const request = createMockRequest('http://localhost:3000/api/judges/search?q=judge')
const response = await GET(request)
expect(response.headers.get('X-RateLimit-Limit')).toBeDefined()
expect(response.headers.get('X-RateLimit-Remaining')).toBeDefined()
})
})// tests/integration/api/auth-flow.test.ts
describe('Authentication Flow', () => {
it('should reject unauthenticated requests', async () => {
mockUnauthenticatedSession()
const request = createMockRequest('http://localhost:3000/api/user/profile')
const response = await GET(request)
expect(response.status).toBe(401)
})
it('should allow authenticated requests', async () => {
mockAuthenticatedSession('user-123')
const request = createMockRequest('http://localhost:3000/api/user/profile')
const response = await GET(request)
expect(response.status).toBe(200)
})
})E2E tests should cover critical user flows only:
- User registration and login
- Judge search and viewing
- Subscription purchase
- Profile management
E2E tests verify complete user journeys through the browser.
import { test, expect } from '@playwright/test'
test.describe('User Flow', () => {
test('should complete user journey', async ({ page }) => {
await page.goto('/')
// Interact with the page
await page.getByRole('button', { name: 'Search' }).click()
// Verify results
await expect(page.getByText('Results')).toBeVisible()
})
})// tests/e2e/judge-search-flow.test.ts
import { test, expect } from '@playwright/test'
test.describe('Judge Search Flow', () => {
test('should search for judges and view profile', async ({ page }) => {
await page.goto('/')
// Enter search query
await page.fill('input[placeholder="Search judges..."]', 'Smith')
await page.press('input[placeholder="Search judges..."]', 'Enter')
// Wait for results
await expect(page.getByText(/results/i)).toBeVisible()
// Click first result
const results = page.locator('[data-testid="judge-card"]')
await results.first().click()
// Verify profile page loaded
await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
})
it('should handle empty search results', async ({ page }) => {
await page.goto('/')
await page.fill('input[placeholder="Search judges..."]', 'xyznonexistent')
await page.press('input[placeholder="Search judges..."]', 'Enter')
await expect(page.getByText(/no results found/i)).toBeVisible()
})
})// tests/e2e/auth-flow.test.ts
test.describe('Authentication Flow', () => {
test('should sign up new user', async ({ page }) => {
await page.goto('/sign-up')
await page.fill('input[name="email"]', 'test@example.com')
await page.fill('input[name="password"]', 'SecurePassword123!')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/dashboard')
await expect(page.getByText(/Welcome/i)).toBeVisible()
})
test('should handle invalid credentials', async ({ page }) => {
await page.goto('/sign-in')
await page.fill('input[name="email"]', 'wrong@example.com')
await page.fill('input[name="password"]', 'WrongPassword')
await page.click('button[type="submit"]')
await expect(page.getByText(/invalid credentials/i)).toBeVisible()
})
})// tests/unit/components/JudgeCard.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { JudgeCard } from '@/components/judges/JudgeCard'
describe('JudgeCard', () => {
const mockJudge = {
id: 1,
name: 'Judge Smith',
court_name: 'Superior Court',
total_cases: 1000,
slug: 'judge-smith-1',
}
it('should render judge information', () => {
render(<JudgeCard judge={mockJudge} />)
expect(screen.getByText('Judge Smith')).toBeInTheDocument()
expect(screen.getByText('Superior Court')).toBeInTheDocument()
expect(screen.getByText('1,000 cases')).toBeInTheDocument()
})
it('should link to judge profile', () => {
render(<JudgeCard judge={mockJudge} />)
const link = screen.getByRole('link')
expect(link).toHaveAttribute('href', '/judges/judge-smith-1')
})
})// tests/unit/hooks/useJudgeSearch.test.ts
import { describe, it, expect } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { useJudgeSearch } from '@/hooks/useJudgeSearch'
describe('useJudgeSearch', () => {
it('should fetch search results', async () => {
const { result } = renderHook(() => useJudgeSearch('smith'))
expect(result.current.isLoading).toBe(true)
await waitFor(() => {
expect(result.current.isLoading).toBe(false)
})
expect(result.current.results).toBeDefined()
})
})// tests/unit/lib/analytics/report-builder.test.ts
describe('generateBiasReport', () => {
it('should generate report asynchronously', async () => {
const report = await generateBiasReport(123)
expect(report).toBeDefined()
expect(report.judgeId).toBe(123)
expect(report.metrics).toBeDefined()
})
it('should handle errors gracefully', async () => {
await expect(generateBiasReport(-1)).rejects.toThrow('Invalid judge ID')
})
})Always mock external services (databases, APIs, AI services):
vi.mock('@google/generative-ai', () => ({
GoogleGenerativeAI: vi.fn(() => ({
getGenerativeModel: vi.fn(() => ({
generateContent: vi.fn(),
})),
})),
}))// Mock entire module
vi.mock('@/lib/supabase/server', () => ({
createServerClient: vi.fn().mockResolvedValue({
from: vi.fn().mockReturnValue({
select: vi.fn().mockResolvedValue({ data: [], error: null }),
}),
}),
}))
// Mock specific functions
vi.mock('@/lib/utils/helpers', () => ({
formatCurrency: vi.fn((value) => `$${value}`),
formatDate: vi.fn((date) => date.toISOString()),
}))// Mock global fetch
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve({ data: 'mocked' }),
})
) as any
// Verify fetch was called
expect(fetch).toHaveBeenCalledWith('/api/judges/search?q=smith')// Mock Supabase client
const mockSupabase = {
from: vi.fn().mockReturnThis(),
select: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
single: vi.fn().mockResolvedValue({
data: { id: 1, name: 'Judge Smith' },
error: null,
}),
}
vi.mock('@/lib/supabase/server', () => ({
createServerClient: () => mockSupabase,
}))const mockFn = vi.fn()
mockFn.mockReturnValue('value')
mockFn.mockResolvedValue('async value')
mockFn.mockRejectedValue(new Error('error'))Test fixtures provide consistent mock data across tests:
import { mockJudges, mockJudgesList } from '@/tests/fixtures/judges'
import { mockCases, generateMockCases } from '@/tests/fixtures/cases'
import { mockUsers } from '@/tests/fixtures/users'
// Use in tests
const testJudge = mockJudges.activeJudge
const multipleCases = generateMockCases(50, 'judge-001')// tests/fixtures/judges.ts
export const mockJudge = {
id: 1,
name: 'Judge John Smith',
court_name: 'Superior Court of California, Los Angeles County',
jurisdiction: 'CA',
total_cases: 1234,
slug: 'judge-john-smith-1',
}
export const mockJudgeList = [
mockJudge,
{ ...mockJudge, id: 2, name: 'Judge Jane Doe' },
{ ...mockJudge, id: 3, name: 'Judge Bob Wilson' },
]
// tests/fixtures/cases.ts
export const mockCase = {
id: 1,
judge_id: 1,
case_number: '23-CV-12345',
case_type: 'Civil',
outcome: 'Settled',
filing_date: '2023-01-15',
case_value: 150000,
}// tests/helpers/factories.ts
export const createMockJudge = (overrides = {}) => ({
id: 'judge-1',
name: 'Test Judge',
...overrides,
})The project enforces the following minimum coverage thresholds:
- Lines: 60%
- Functions: 60%
- Branches: 50%
- Statements: 60%
# Generate and view coverage report
npm run test:coverage
open coverage/index.html # macOS
start coverage/index.html # WindowsThe following are excluded from coverage:
node_modules/.next/tests/**/*.d.ts**/*.config.*scripts/ios/
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules/', 'tests/', '*.config.ts', 'scripts/'],
thresholds: {
statements: 75,
branches: 70,
functions: 75,
lines: 75,
},
},
},
})Tests run automatically on GitHub Actions for:
- Pull requests to
mainordevelop - Pushes to
mainordevelop
- Linting: Code style and quality checks
- Type Checking: TypeScript compilation
- Unit Tests: Fast isolated tests
- Integration Tests: API and database tests
- E2E Tests: Browser-based user flows (Chromium, Firefox, WebKit)
- Coverage: Generate and upload coverage reports
# Run all CI checks locally
npm run test:ci# .github/workflows/test.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload coverage
uses: codecov/codecov-action@v3Use descriptive test names that explain the behavior:
// ✅ Good
it('should return 404 when judge does not exist')
// ❌ Bad
it('test judge endpoint')Structure tests clearly:
it('should calculate settlement rate correctly', () => {
// Arrange
const cases = [{ outcome: 'Settled' }, { outcome: 'Dismissed' }]
// Act
const result = analyzeOutcomes(cases)
// Assert
expect(result.overall_settlement_rate).toBe(0.5)
})Include tests for:
- Empty inputs
- Null/undefined values
- Error conditions
- Rate limits
- Invalid data
Each test should be independent:
beforeEach(() => {
vi.clearAllMocks()
// Reset any shared state
})- Write descriptive test names
- Test behavior, not implementation
- Use data-testid for stable selectors
- Mock external dependencies
- Test edge cases and errors
- Keep tests independent
- Use AAA pattern (Arrange, Act, Assert)
- Test implementation details
- Rely on CSS selectors
- Share state between tests
- Make tests dependent on each other
- Skip accessibility tests
- Ignore flaky tests
- Write slow tests
- Ensure you're using the correct Node version (20.x)
- Run
npm ciinstead ofnpm install - Check for environment-specific configurations
# Reinstall browsers
npx playwright install --with-deps
# Clear Playwright cache
npx playwright clean# Try clearing Vitest cache
npx vitest --clearCacheEnsure mocks are defined before imports:
// Mock must come before import
vi.mock('@/lib/module')
import { functionToTest } from '@/lib/module'# Install coverage provider
npm install --save-dev @vitest/coverage-v8
# Run with coverage
npm run test:coverage// Increase timeout
test('slow test', async () => {
// test code
}, 30000) // 30 second timeout// Retry failed tests
test.retry(2)beforeEach(() => {
vi.clearAllMocks()
})screen.debug() // Print DOM
console.log(screen.getByRole('button').textContent)In production code (app/, components/, lib/), strict id-length rules apply:
// ✅ Good - Descriptive identifiers (min-length: 2)
const judgeId = 123
const searchQuery = 'smith'
const caseList = []
// ❌ Bad - Single letter identifiers
const j = 123 // Use judgeId
const q = 'smith' // Use query or searchQuery
const c = [] // Use cases or caseListExceptions in Production:
- Loop iterators in tight scopes:
for (let i = 0; i < 10; i++) - Array methods with obvious context:
cases.map((c) => c.id) - Coordinates:
{ x, y }in graphics code
In test files (tests/**/*.test.ts), we use a hybrid approach:
// ✅ Allowed - Descriptive test data
const mockJudgeData = { id: 1, name: 'Smith' }
const testCases = [...]
// ✅ Allowed - Single-letter in obvious test context
describe('analyzeCaseTypePatterns', () => {
it('should sort by total cases descending', () => {
const c: CaseRecord[] = [
{ case_type: 'Family', outcome: 'Settled' },
{ case_type: 'Civil', outcome: 'Settled' },
]
const p = analyzeCaseTypePatterns(c)
expect(p[0].case_type).toBe('Civil')
})
})// .eslintrc.js
module.exports = {
rules: {
'id-length': [
'error',
{
min: 2,
exceptions: ['i', 'j', 'k', 'x', 'y', 'z'],
properties: 'never',
},
],
},
overrides: [
{
// Relaxed rules for test files
files: ['**/*.test.ts', '**/*.test.tsx', '**/tests/**/*'],
rules: {
'id-length': ['warn', { min: 1 }],
},
},
],
}- Test Strategy - Overall testing strategy
- Test Coverage Report - Current coverage metrics
- Vitest Documentation
- Playwright Documentation
- Testing Library
- Testing Best Practices
- ARCHITECTURE.md - System architecture
- API_DOCUMENTATION.md - API reference
When adding new features:
- Write unit tests for business logic
- Add integration tests for API endpoints
- Create E2E tests for user-facing features
- Ensure coverage meets thresholds
- Update this documentation if needed
For testing questions or issues:
- Check this documentation
- Review existing test examples in
tests/directory - Consult the troubleshooting section
- Review Test Strategy
- Open an issue on GitHub
Last Updated: 2025-11-09 Maintained By: JudgeFinder Development Team