# 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 `<script>alert(1)</script>` 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 `<img src=x onerror=alert(1)>` 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.
