Files
padhle/testing-doc.md
T
2026-09-15 04:08:55 -04:00

140 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.