# Testing Improvements Design **Date:** 2026-04-03 **Scope:** Unit tests for new server code + Playwright E2E test setup with seeded database ## Part 1: Unit/Integration Tests (Bun test runner) ### tests/lib/params.test.ts Tests for `parseId` helper in `src/server/lib/params.ts`: - Valid positive integers (1, 42, 999) return the number - Zero returns null - Negative numbers (-1, -100) return null - Decimals (1.5, 3.14) return null - Non-numeric strings ("abc", "", "hello") return null - NaN-producing values return null ### tests/middleware/rateLimit.test.ts Tests for rate limiter in `src/server/middleware/rateLimit.ts`: - First request passes through (200) - 5 requests succeed, 6th returns 429 - 429 response includes `Retry-After` header - Different IPs tracked independently - After window expires, requests succeed again Since the rate limiter uses a module-level `Map`, tests need to either: - Reset the store between tests (export a `resetStore` for testing), OR - Use unique paths/IPs per test to avoid interference Recommended: export a `_resetForTesting()` function from rateLimit.ts that clears the store. Only used in tests. ### tests/routes/params.test.ts Route-level integration tests verifying 400 responses for invalid IDs: - `GET /api/items/abc` → 400 - `GET /api/items/-1` → 400 - `GET /api/items/0` → 400 - `DELETE /api/categories/notanumber` → 400 - `GET /api/threads/abc` → 400 - `GET /api/setups/abc` → 400 Uses existing test app pattern with in-memory DB. ## Part 2: Playwright E2E Setup ### Installation - `bun add -d @playwright/test` - `bunx playwright install chromium` (only Chromium needed) ### Configuration: playwright.config.ts ```ts export default defineConfig({ testDir: "./e2e", webServer: { command: "DATABASE_PATH=./e2e/test.db bun run dev:server", port: 3000, reuseExistingServer: !process.env.CI, }, use: { baseURL: "http://localhost:3000", }, projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], }); ``` ### Database Seeding: e2e/seed.ts Script that creates `e2e/test.db` with: - Run Drizzle migrations against the file - Seed data: - 1 user (username: "admin", password: "password123") - 3 categories: Shelter, Sleep System, Cook Kit - 6 items across categories with realistic weights/prices - 1 active thread with 3 candidates (with pros/cons, sort_order) - 1 resolved thread - 1 setup with 4 items (mixed classifications) - Settings: weightUnit=g, currency=USD, onboardingComplete=true Run before E2E tests via `e2e/global-setup.ts` (Playwright globalSetup). ### E2E Test Files **e2e/dashboard.spec.ts** - Dashboard page loads - Summary cards show item count, weight, cost - Navigation links to collection work **e2e/collection.spec.ts** - Gear tab renders items grouped by category - Search input filters items by name - Category filter dropdown works - Tab switching between gear/planning/setups **e2e/threads.spec.ts** - Thread detail page loads with candidates - Comparison view toggle works (shows table) - Rank badges visible on candidates **e2e/auth.spec.ts** - Login page renders - Login with valid credentials succeeds - Login with wrong password shows error - Rate limiting returns error after 5 attempts **e2e/error-boundary.spec.ts** - App doesn't white-screen on unknown routes - Navigating to a non-existent thread/setup shows appropriate error ### Scripts Add to package.json: - `"test:e2e": "bunx playwright test"` - `"test:e2e:ui": "bunx playwright test --ui"` (for debugging) ### Files to .gitignore - `e2e/test.db` - `test-results/` - `playwright-report/` ## Commit Strategy 1. Unit tests for parseId, rate limiter, route params 2. Playwright setup (install, config, seed, global-setup) 3. Playwright E2E test files