100 lines
8.2 KiB
Markdown
100 lines
8.2 KiB
Markdown
# 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 owner account exists; bootstrap admin is marked as owner.
|
||
- Database uniqueness guard enforces single owner row (`IsOwner=true`) even if writes bypass endpoint-level checks.
|
||
- `/api/auth/options` reports owner presence for registration UI behavior.
|
||
- 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.
|
||
- DB trigger also enforces suggestion cap for non-joker inserts, protecting against concurrent over-limit writes.
|
||
- 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.
|
||
- Concurrent vote upserts are handled with retry logic around unique-key conflicts to avoid server errors.
|
||
- 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.
|
||
- POST /admin/player-admin grants/revokes admin role for non-owner accounts; owner role cannot be changed.
|
||
- DELETE /admin/players/{id}: requires valid admin password; removes player, cascades suggestions, breaks links to their suggestions, deletes related votes, wrapped in transaction.
|
||
- Owner account cannot be deleted.
|
||
- 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.
|