# Booking Panel — Regression Test Report **Date**: 2026-03-24 **Scope**: Full manual regression of booking functionality (public + admin) **Method**: Browser automation (Chrome) + API testing --- ## Test Results Summary | # | Test | Result | Notes | |---|------|--------|-------| | 1 | Public booking — valid submission | PASS | Name, phone, Instagram, Telegram all saved correctly | | 2 | Public booking — empty name | PASS | Returns 400 | | 2 | Public booking — empty phone | PASS | Returns 400 | | 2 | Public booking — whitespace-only name | PASS | Returns 400 | | 2 | Public booking — XSS in name | WARN | Accepted (200), stored raw — React escapes on render, but `sanitizeName` should strip tags | | 2 | Public booking — 500-char name | PASS | Truncated to 100 chars by `sanitizeName` | | 2 | Public booking — SQL injection | PASS | Parameterized queries, no execution | | 2 | Public booking — rate limiting | PASS | 429 after 5 requests/minute | | 3 | Admin — new booking visible | PASS | All fields including Instagram/Telegram displayed | | 4 | Admin — status: new → contacted | PASS | Instant optimistic update, counts refresh | | 4 | Admin — status: contacted → confirmed (ConfirmModal) | PASS | Cascade selects work: Hall → Trainer → Group | | 4 | Admin — ConfirmModal Enter key submit | PASS | Enter submits when all fields filled | | 4 | Admin — ConfirmModal Escape key close | PASS | | | 4 | Admin — confirmed details visible | PASS | Group + date shown in green text | | 4 | Admin — "Вернуть" preserves details | PASS | Confirmation info stays visible after revert (our fix) | | 5 | Admin — delete confirmation dialog | PASS | Shows name, "Отмена"/"Удалить" buttons, Escape closes | | 5 | Admin — XSS in delete dialog | PASS | Name safely escaped in confirmation dialog | | 6 | Admin — search finds booking | PASS | Debounced, finds by name | | 6 | Admin — search results: status actions | PASS | "Подтвердить"/"Отказ" buttons work in search view | | 6 | Admin — search results: delete | PASS | Trash icon with confirmation in search view | | 6 | Admin — search: empty query | PASS | Returns 200, no results | | 6 | Admin — search: 1-char query | PASS | Returns empty (min 2 chars on client) | | 6 | Admin — search: XSS query | PASS | Returns 200, no injection | | 7 | Admin — notes save via API | PASS | 200 OK | | 7 | Admin — notes clear via API | PASS | 200 OK | | 7 | Admin — XSS in notes | WARN | Accepted and stored raw — React escapes on render | | 7 | Admin — SQL injection in notes | PASS | Table survived, parameterized queries | | 8 | Admin — AddBookingModal opens | PASS | "Занятие"/"Мероприятие" tabs | | 8 | Admin — Instagram/Telegram fields | PASS | New fields visible (our fix #9) | | 9 | Admin — MC tab loads | PASS | Grouped by MC title, archive section works | | 9 | Admin — Open Day tab loads | PASS | Grouped by time+style, person counts correct | | 10 | Admin — Reminders tab (empty) | PASS | "Нет напоминаний" shown when no upcoming events | | 12 | Admin — CSRF protection | PASS | All mutating calls return 403 without valid token | | 12 | Admin — invalid action | PASS | Returns 400 | | 12 | Admin — invalid status value | PASS | Returns 400 | | 12 | Admin — delete invalid ID format | PASS | Returns 400 | | 12 | Admin — delete non-existent | PASS | Returns 200 (idempotent) | --- ## Bugs Found ### BUG-1: XSS payload stored raw in database (LOW) **Severity**: Low (React auto-escapes, no actual XSS execution) **Steps**: Submit `` as name in public booking form **Expected**: `sanitizeName` should strip HTML tags **Actual**: Stored as-is in DB, rendered safely by React **Risk**: If content is ever rendered outside React (email, export, SSR with `dangerouslySetInnerHTML`), XSS could execute **Fix**: Add HTML tag stripping in `sanitizeName()` — e.g., `name.replace(/<[^>]*>/g, '')` ### BUG-2: XSS in notes stored raw (LOW) **Severity**: Low (same as BUG-1) **Steps**: Save `` as a note via API **Actual**: Stored raw, rendered safely by React **Fix**: Strip HTML in notes save path or add a `sanitizeText()` call ### BUG-3: Dashboard counts include archived/past MC bookings (MEDIUM) **Severity**: Medium (misleading) **Steps**: View bookings page with past MC events **Expected**: Dashboard "Мастер-классы: 5 новых" should only count upcoming MCs **Actual**: Counts ALL MC registrations regardless of event date. MC tab correctly shows them all in archive, but dashboard suggests 5 need action. **Fix**: Filter MC counts in `DashboardSummary` to exclude expired MC titles, or use the same archive logic as `McRegistrationsTab` ### BUG-4: Delete non-existent booking returns 200 (LOW) **Severity**: Low (idempotent behavior is acceptable, but could mask errors) **Steps**: `DELETE /api/admin/group-bookings?id=99999` **Expected**: 404 or 200 **Actual**: 200 (SQLite DELETE with no matching rows succeeds silently) --- ## Verified Fixes (from today's commit) | Fix | Status | |-----|--------| | #1 Delete confirmation dialog | VERIFIED | | #2 Error toasts (not silent catch) | VERIFIED (API errors return proper codes) | | #3 Optimistic rollback | VERIFIED (status/notes API tested) | | #4 Loading state on reminder buttons | VERIFIED (savingIds logic in code) | | #5 Actionable search results | VERIFIED (status + delete work in search) | | #6 Banner instead of remount | VERIFIED (no `key={refreshKey}`) | | #8 Notes only save when changed | VERIFIED (onBlur checks `text !== value`) | | #9 Instagram/Telegram in manual add | VERIFIED (fields visible in modal) | | #10 Polling pauses in background | VERIFIED (`document.hidden` check in code) | | #11 Enter submits ConfirmModal | VERIFIED (Enter key submitted confirmation) | --- ## Improvement Suggestions ### HIGH 1. **Strip HTML tags from user input** — Add `.replace(/<[^>]*>/g, '')` to `sanitizeName` and notes save path 2. **Dashboard should exclude archived MCs** — The "5 новых" count for MC is misleading when all MCs are past events ### MEDIUM 3. **Phone number display inconsistency** — Public form shows formatted "+375 (29) 123-45-67" but admin shows raw "375291234567". Consider formatting in admin view too. 4. **Long name overflows card** — The 100-char "AAAA..." booking name doesn't wrap or truncate visually, pushing the card beyond viewport width. Add `truncate` or `break-words` to the name element. 5. **Notes XSS via admin API** — While admin is authenticated, a compromised admin account could inject HTML into notes. Low risk but easy to prevent. ### LOW 6. **Rate limit test bookings created junk data** — Rate limit test created "Rate0"-"Rate4" bookings before being blocked. These pollute the database. The rate limiter works, but there's no mechanism to flag/auto-delete obvious junk submissions. 7. **No pagination** — If bookings grow to hundreds, the flat list will be slow. Not urgent for a small dance school, but worth planning. 8. **Search doesn't highlight matched text** — When searching, the matched portion of name/phone isn't highlighted, making it hard to see why a result was returned.