- New ParticipantLimits component in FormField.tsx (reusable) - Used in both Open Day settings and MC editor — identical layout - Open Day: event-level min/max (DB migration 15) - MC: per-event min/max (JSON fields) - Public: waiting list when full, spots counter, amber success modal
7.1 KiB
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
- Strip HTML tags from user input — Add
.replace(/<[^>]*>/g, '')tosanitizeNameand notes save path - Dashboard should exclude archived MCs — The "5 новых" count for MC is misleading when all MCs are past events
MEDIUM
- Phone number display inconsistency — Public form shows formatted "+375 (29) 123-45-67" but admin shows raw "375291234567". Consider formatting in admin view too.
- Long name overflows card — The 100-char "AAAA..." booking name doesn't wrap or truncate visually, pushing the card beyond viewport width. Add
truncateorbreak-wordsto the name element. - 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
- 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.
- No pagination — If bookings grow to hundreds, the flat list will be slow. Not urgent for a small dance school, but worth planning.
- 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.