# Test Suite Plan — Pick'n'Play Purpose: full coverage of backend + critical UI flows using a mock (in-memory) SQLite DB; validates roles, phases, and data rules described in SPEC.md/API.md. ## Stakeholders & Permissions by Phase | Role | Suggest phase | Vote phase | Results phase | Anytime | | --- | --- | --- | --- | --- | | Unauthenticated visitor | No API access; only static assets | — | — | Health check only | | Player (non-admin) | Create/see own suggestions (≤5), edit all fields, delete own; can advance to Vote; title locks after leaving phase | View all suggestions, vote 0–10, finalize/unfinalize, use joker once to add a game; cannot go backward | Read leaderboard only when resultsOpen=true; no writes | Login/logout, read /state and /me | | Admin (isAdmin=true) | Same as player; may edit/delete any suggestion | All player actions; may grant jokers, link/unlink games, delete players, move a voter back to Suggest | Open/close results; sees leaderboard like player | Toggle results, reset/factory-reset DB, fetch vote status, move self backward | ## Phase/Permission Chart (for tests) ```mermaid stateDiagram-v2 [*] --> Suggest Suggest --> Vote: player POST /api/me/phase/next Vote --> Results: admin opens results OR player next when resultsOpen=true Results --> Vote: admin closes results Admin: Admin-only actions Admin --> Vote: grant joker/link/unlink/delete players/reset/factory-reset Admin --> Results: toggle results ``` ## Test Strategy (mock DB) - Use xUnit + WebApplicationFactory; replace DbContext with in-memory SQLite (shared connection) seeded per test. - Mock IHttpClientFactory with TestHttpMessageHandler to control image URL reachability; avoid real network. - Helper builders: `TestServerClient` to act as specific user (cookie auth), `SeedData` for players/suggestions/votes, `PhaseHelper` to set phases/resultsOpen directly in DB. - Run migrations once per test suite against in-memory DB to mirror schema. ## Test Cases (ordered by feature) ### 1) Authentication & Identity - Register success (player, admin key path) issues cookie, trims fields, stores normalized username, hashes password. - Register rejects missing/long username, weak password policy violations, missing display name, duplicate username, bad admin key, >24 chars username, >16 display name. - Bootstrap-admin key path only works until the first admin account exists. - Login success updates LastLoginAt and sets DisplayName if null; rejects wrong password/username; enforces length limits. - Logout clears cookie. - EnsurePlayerExistsMiddleware: signed cookie for deleted player returns 401 and clears auth. - Cookie contains admin claim; non-admin cookie cannot access admin routes (401/403 via filter). ### 2) State & Phase Alignment (/api/state, /api/me) - /api/state returns player-specific phase, votesFinal, hasJoker, counts; unauthorized returns 401. - GetPhase auto-upgrades legacy Reveal -> Vote and realigns when resultsOpen toggles (to Results and back to Vote clearing votesFinal). - /me/phase/next: moves Suggest->Vote, Vote->Results only when resultsOpen true; clears votesFinal; rejects when results locked. - /me/phase/prev: admin only; moves back one step, clears votesFinal, rejects for player. - Display name is immutable after registration; attempts to change via /api/me/name return 404. ### 3) Suggestions - GET /mine returns only caller’s suggestions ordered by CreatedAt. - POST /: success with valid data; enforces ≤5 per player; trims optional fields; requires display name; rejects bad image URL/ext, unreachable image (mocked), invalid game/youtube URLs, invalid player counts, missing name/too long. - Joker path: when phase=Vote and HasJoker=true allows creation, consumes joker, resets VotesFinal for all players. - Phase gating: non-admin cannot create/update/delete outside Suggest (except joker create); admin bypasses phase checks for update/delete. - PUT /{id}: player can edit own in Suggest; name locked outside Suggest; admin can edit any time; validation mirrors create. - DELETE /{id}: player deletes own in Suggest; admin any time; also breaks child links and deletes related votes. - GET /all: accessible from Vote+, orders by CreatedAt, includes link metadata, enforces phase mismatch before Vote. ### 4) Votes - GET /mine: only in Vote, returns player votes; unauthorized/phase mismatch handled. - POST /: creates or updates vote; rejects score outside 0–10; rejects when VotesFinal=true; enforces display name requirement and phase gating. - Linked votes: when suggestions are linked, a single post updates all linked IDs; invalid suggestionId returns 400; linking root detection works for nested links. - Finalize: POST /finalize toggles VotesFinal flag; allowed only in Vote. ### 5) Results - GET /api/results: requires auth, resultsOpen=true, phase=Results; returns ordered leaderboard with totals/count/avg, caller’s vote, link metadata, and handles empty vote lists (Average=0). - Phase mismatch and locked results return 400; unauthorized 401. ### 6) Admin Operations - POST /admin/results toggles resultsOpen and aligns all player phases (to Results or back to Vote clearing votesFinal); updates UpdatedAt. - GET /admin/vote-status returns list ordered by display/username with suggestion counts, finalized flag, joker flag; ready/waiting derived correctly. - POST /admin/joker grants joker only when target in Vote; resets VotesFinal for target. - POST /admin/player-phase allows Vote->Suggest transitions only; rejects other targets/current phases; clears target VotesFinal. - DELETE /admin/players/{id}: requires valid admin password; removes player, cascades suggestions, breaks links to their suggestions, deletes related votes, wrapped in transaction. - POST /admin/link-suggestions: only in Vote; errors on same ids/already linked/not found; re-parents groups correctly; deletes votes for affected group and unfinalizes affected players. - POST /admin/unlink-suggestions: only in Vote; clears parents for group, deletes votes in group, unfinalizes affected players; no-op safe when missing. - POST /admin/reset: requires valid admin password; wipes suggestions/votes, resets phases to Suggest, clears votesFinal/hasJoker, closes results, updates timestamp. - POST /admin/factory-reset: requires valid admin password; wipes all players/suggestions/votes/state; reseeds AppState with defaults; transactional. ### 7) Infrastructure/Helpers - PasswordHasher: hash+verify roundtrip, rejects empty password, constant-time compare (FixedTimeEquals usage). - EndpointHelpers.IsValidImageUrl/IsValidHttpUrl: accepts empty, http/https; rejects others/invalid ext. - IsReachableImageAsync: with mocked Http responses covers head success, get fallback, redirect rejection, size guard, and private/reserved host range detection (IPv4/IPv6). - BuildLinkRoots/LinkedIdsFor/FindRootId: cover disjoint groups, chains, cycles guard (visited set), non-existent ids. - UpdateIndexMetaBase (Program.cs): rewrites app-base meta when BasePath set; no change when matching/marker missing; safe exceptions swallowed. - Global exception handler returns 500 with JSON body and logs error. - /health returns {status:"ok"}. - Security middleware tests validate response headers and rate-limiting behavior on auth/admin routes. - Frontend regression guard tests assert modal/admin JS no longer interpolate untrusted values in vulnerable patterns. ## Execution Notes - Use named test data builders for players/suggestions to keep cases small and isolated. - Reset in-memory DB per test to avoid cross-contamination; assert timestamps using time providers or approximate windows. - Cover success + failure for every endpoint status path to reach 100% line/branch coverage.