140 lines
9.0 KiB
Markdown
140 lines
9.0 KiB
Markdown
# padhle — Frontend Manual Testing Plan
|
||
|
||
> **Goal:** test the application through the browser (frontend only) and verify every feature that was built, changed, or hardened.
|
||
> **Setup:** backend on `:3001`, frontend dev server on `:5173`, local Supabase running (`npx supabase start`).
|
||
> **Recommended browsers:** test in a **normal window** and an **incognito window** side-by-side (incognito = clean anonymous identity).
|
||
|
||
---
|
||
|
||
## 0. Before you start — launch
|
||
|
||
| Step | Do this | Expected |
|
||
|---|---|---|
|
||
| 0.1 | Terminal 1: `cd backend && npm run dev` | Backend on `:3001` |
|
||
| 0.2 | Terminal 2: `cd frontend && npm run dev` | Frontend opens `http://localhost:5173` |
|
||
| 0.3 | Open `http://localhost:5173` in incognito | App loads; **no sign-in wall** — you're in the app (sidebar, selectors, chat input visible) |
|
||
|
||
> ⚠️ **If you don't have a Supabase/backend running**, backend calls for sign-in/chat will fail with a connection error banner. The anonymous flow also needs the backend for chat. If a *real* AI key has no quota, streams may end with an in-chat error (expected, not a code bug).
|
||
|
||
---
|
||
|
||
## 1. Anonymous 5-message trial (FREE-TIER — this is the headline feature)
|
||
|
||
> This is the core new behavior. Everything here works **without** an account.
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 1.1 | In incognito, notice the TopNav + Sidebar | Both show a **"Sign in"** button (no avatar) |
|
||
| 1.2 | Pick a grade → subject → chapter, then send a message | Streaming answer appears; chat thread builds |
|
||
| 1.3 | Send 4 more messages (5 total) | Each works; counter quietly builds up (you see the conversation continue) |
|
||
| 1.4 | Send a **6th** message | Composer is **replaced** by a card: *"You've used your 5 free messages — Sign in to continue"* |
|
||
| 1.5 | If you've closed/reopened between messages: the URL stays on `/` the whole time | Anonymous chat does **NOT** reflect a chat id into the browser URL |
|
||
| 1.6 | Click **"Sign in to continue"** on the card | The sign-in modal opens |
|
||
| 1.7 | Close the modal (× / "Maybe later" / click the dimmer) | Modal closes; you can keep navigating, still capped at 5 |
|
||
| 1.8 | **Refresh the page** after using some messages | Conversation resets to a **fresh** 5-message budget (by design) |
|
||
| 1.9 | **Secure:** open DevTools → Application → Cookies | There is **no** `padhle.anon` cookie; your anonymous state is 100% in-memory (refresh wipes it) |
|
||
|
||
**Cross-browser isolation:**
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 1.10 | Use 2 different browsers (or normal + incognito), both anonymous | Each has its **own separate** budget; their chats are **not** visible to each other |
|
||
|
||
---
|
||
|
||
## 2. Sign-up / Sign-in / Auth
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 2.1 | Click **"Sign in"** (TopNav or Sidebar) | Modal opens cleanly |
|
||
| 2.2 | **Sign up** with a valid email + password (≥6 chars) | Account created; you are now signed in; avatar appears in TopNav; sessions sidebar appears |
|
||
| 2.3 | **Reload the page** (`F5`) | You stay signed in (session restored from the httpOnly cookie) |
|
||
| 2.4 | Try to **sign up with a 5-character password** (via the UI if it lets you, or note): | Server must reject (password ≥ 6). The UI enforces it too — if the UI blocks it, that's fine |
|
||
| 2.5 | **Sign out** (Sidebar → sign out) | You are logged out; back to anonymous app with a **fresh** 5-message budget; "Sign in" buttons return |
|
||
| 2.6 | **Sign back in** with the same email/password | Your previous signed-in conversation is still there in the sidebar |
|
||
|
||
---
|
||
|
||
## 3. Sessions (persisted conversations, signed-in only)
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 3.1 | While signed in, start a new chat and send a few messages | When you send, the URL becomes `/chat/<id>` (signed-in chats ARE deep-linkable) |
|
||
| 3.2 | Open another chat from the sidebar | URL switches to that chat's `/chat/<id>`; back/forward browser buttons work |
|
||
| 3.3 | Copy the `/chat/<id>` URL, open it in **another tab** (signed in there) | The conversation loads directly (deep link works) |
|
||
| 3.4 | **New chat** button | Resets to `/`, clears composer, back to a fresh conversation |
|
||
| 3.5 | **Delete a session** from the sidebar | Removed; no longer in the list |
|
||
| 3.6 | **Clear messages** in a session (if available) | Conversation empties but the session stays |
|
||
| 3.7 | **Secure — ownership:** in browser A (user 1) note a session URL; in incognito (user 2) paste that exact URL | Two possibilities: if you are NOT signed in there → app shows clean "could not load" / stays on `/`; if you ARE signed in as user 2 → NOT user 1's messages (must be 404 — you should NOT see user 1's chat) |
|
||
|
||
---
|
||
|
||
## 4. Routing / URLs (added this session)
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 4.1 | While **anonymous**, send a message | URL **stays `/`** (anonymous trial id is never written to the URL — security decision) |
|
||
| 4.2 | While **signed in**, open a chat | URL is `/chat/<id>` — shareable and refreshable |
|
||
| 4.3 | Type a random path, e.g. `http://localhost:5173/whatever` | App still loads at the root shell (SPA fallback) — no 404 page yet (documented) |
|
||
| 4.4 | Browser **back/forward** buttons between chats | Moves between conversations as expected |
|
||
|
||
---
|
||
|
||
## 5. AI streaming + rendering
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 5.1 | Send a question and watch the reply | Answer **streams** word by word (token by token), typing indicator during |
|
||
| 5.2 | Ask for something with **Markdown** (e.g. "give me a bulleted list, bold text, a code block") | Renders as formatted Markdown (not raw `**` / backticks) |
|
||
| 5.3 | Ask for **math** (e.g. "what is `x^2 + y^2`") | LaTeX renders via KaTeX |
|
||
| 5.4 | **Secure — XSS:** ask the AI to output raw HTML like `<script>alert(1)</script>` (or intentionally craft a message) | It renders as **text**, NOT executable HTML — no alert pops |
|
||
| 5.5 | Long answer | Auto-scrolls to the new content |
|
||
|
||
---
|
||
|
||
## 6. Errors, validation, robustness (secure boundary checks)
|
||
|
||
> These verify the backend still protects you even if the frontend is bypassed. Easiest way: open DevTools Console and run `fetch(...)` from the page (same-origin) — or just reason through the UI where possible.
|
||
|
||
| # | Try | Expected |
|
||
|---|---|---|
|
||
| 6.1 | `fetch('http://localhost:3001/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:123})})` (no session cookie) | **400** with a clean JSON error — NOT a 500 / stack trace |
|
||
| 6.2 | `fetch('http://localhost:3001/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:'hi',chapter:'<script>'})})` (no cookie) | **400** (chapter with `<`/`>` rejected) |
|
||
| 6.3 | `fetch('http://localhost:3001/api/sessions')` (no cookie) | **401** |
|
||
| 6.4 | `fetch('http://localhost:3001/api/chat/sessions')` (no cookie) | **401** |
|
||
| 6.5 | Shut down the backend, then send a message | Frontend shows a "Connection error… make sure backend is running" message — app doesn't hang |
|
||
| 6.6 | Open DevTools → Network, and confirm the chat requests carry cookies with `credentials: include` | Requests are same-origin via the Vite proxy; cookies sent automatically |
|
||
|
||
> The **rate limits** (5 auth/15min, 30 chat/min, 20 sessions/min per IP) won't usually trip you in manual testing — they're per-IP and reset. Don't try to exhaust them in the UI; they're verified server-side.
|
||
|
||
---
|
||
|
||
## 7. Security-review smoke test (quick pass)
|
||
|
||
| # | Do this | Expected |
|
||
|---|---|---|
|
||
| 7.1 | Sign in; open DevTools → Application → Cookies for `localhost` | `padhle.token` and `padhle.user` are marked **HttpOnly**; `SameSite` = Lax |
|
||
| 7.2 | Open any response in DevTools → Network → Headers | Headers include `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, `Cross-Origin-Opener-Policy: same-origin`; **no** `X-Powered-By` |
|
||
| 7.3 | After signing out, note your old chat URL and reload it (signed out) | You can't load a signed-in-only chat while anonymous (clean, no data leak) |
|
||
|
||
---
|
||
|
||
## 8. Final sign-off checklist
|
||
|
||
- [ ] Anonymous user can send **5** messages, then is blocked gracefully with a **Sign in to continue** card.
|
||
- [ ] Anonymous URL stays on `/`.
|
||
- [ ] Sign in/up works; session survives reload.
|
||
- [ ] Signed-in chats are deep-linkable at `/chat/<id>`.
|
||
- [ ] Two identities never see each other's data.
|
||
- [ ] Streamed Markdown + math renders; raw HTML is not executed.
|
||
- [ ] Malformed JSON (non-string `text`) → clean 400, no crash/stack trace.
|
||
- [ ] Unauthenticated calls to `/api/sessions` and `/api/chat/sessions` → 401.
|
||
- [ ] Backend + frontend both build; `cd backend && node --test` → 38 pass.
|
||
|
||
---
|
||
|
||
## Notes / expectations
|
||
- **You will NOT** be able to exceed 5 anonymous messages in normal UI use — the card blocks you. That's correct.
|
||
- **Markdown/LaTeX** demo depends on the AI model actually producing them; a generic question works either way.
|
||
- If a **real AI key** has no quota, streaming may show an in-chat error after a short delay — that's the vendor, not the app. The frontend still handles errors gracefully.
|
||
- This plan tests the **frontend behavior**. The full backend regression suite is `cd backend && node --test` (38 tests) and runs independently.
|