TC-PLAT-001: Platform Event Check-in (Super-Admin)
🆕 New on stage — 2026-06-19 → 2026-07-01. First detailed TC for the Platform Event Check-in feature (Jun 19 release #374–#399/#402, Jun 25 release #427/#428 parity). Supersedes the "use the release page's checklist" placeholder note that used to sit here — see the Platform section banner.
Module: Platform Ops — Super-Admin Event Check-in (handlers/platformEventCheckinHandlers.ts, attendanceCsvReconcileHandlers.ts, attendanceReconcileHandlers.ts)
Primary Test User: superadmin@zeniark.com / SuperAdmin123! (Super Admin — admin portal)
Priority: P1 (cross-tenant security surface — isolation + audit are the gate)
Objectives
- Open a tenant's event Attendees page from the super-admin portal and confirm it lists registrants with attendance status and type (member/guest) but never a dollar amount, invoice number, or other financial figure.
- Mark / undo / bulk-mark check-ins and confirm each write lands both an
EventRegistrationstatus change AND aPlatformAuditLogrow attributed to the operator. - Use QR scan-to-check-in (
checkin-by-code) and confirm it checks the attendee in and writes the same audit trail, taggedvia: "qr". - Run the CSV attendance reconcile flow (preview → commit → finalize) and confirm finalize converts every remaining
CONFIRMED(unscanned) registration toNO_SHOW. - Cross-tenant isolation: a check-in performed against tenant A never touches tenant B's identical event, verified in tenant B's own org portal (not just re-reading the admin-portal UI).
- Access gate: confirm the route-level guard (
requirePlatformSupport) returns403 Forbidden, never a forced logout, for an operator lackingSUPER_ADMIN/PLATFORM_SUPPORT.
Correction vs. the section table (PLAT-CHK-01). The Attendees page is not billing-field-free — it DOES render an invoice status pill (Paid / Unpaid / No invoice) per
EventAttendeesPage.tsx. What is redacted is any amount — no amountCents, invoice number, or currency figure is rendered or returned differently for a platform operator vs. what the backend report payload contains generally. See CM-01 below for the exact assertion.
Scenario summary
| ID | Scenario | Key data assertion |
|---|---|---|
| CM-01 | Attendees page loads + field redaction | GET /api/platform/tenants/:slug/events/:eventId/attendance → 200; each attendee row has status, registrationType, invoice status (if any) — but the rendered table shows NO dollar amount / invoice number cell. |
| CM-02 | Mark / undo single check-in | Mark → registration status=CHECKED_IN, checkedInAt set; a new PlatformAuditLog row with action="EVENT_CHECKIN", adminId=operator. Undo → reverts to CONFIRMED, checkedInAt=null, audit row action="EVENT_CHECKIN_UNDO". |
| CM-03 | Bulk-mark check-in | POST …/attendance/bulk-mark → response {marked, skipped, results}; already-checked-in rows land in skipped with reason ALREADY_CHECKED_IN, not an error; one audit row per bulk call listing all marked ids. |
| CM-04 | QR scan-to-check-in | POST …/attendance/checkin-by-code with a valid displayTicketCode/checkInCode → 200, checkInStatus:"checked_in"; audit row metadata.via="qr". Wrong-event code → 400 WRONG_EVENT with correctEvent info, not a silent check-in. |
| CM-05 | CSV reconcile + finalize | Preview → uploadToken + row-match breakdown; commit consumes the token and marks matched rows checked-in; finalize (POST …/reconcile/finalize) sets every remaining CONFIRMED registration to NO_SHOW and returns {noShowCount, reconciledAt}. |
| CM-06 | Cross-tenant isolation | Check in an attendee for tenant A → tenant B's identical event, re-checked via tenant B's own org portal, shows zero change (registration count, checked-in count, and the specific registration's status all untouched). |
| CM-07 | Access-denied is 403, not logout | A request lacking SUPER_ADMIN/PLATFORM_SUPPORT in its platformRoles claim → 403 {"error":{"code":"FORBIDDEN"}} from requirePlatformRole (rbac.ts); the operator's own valid session is untouched (no token revocation, no forced re-login for other tabs/requests). |
➡️ Full click-by-click steps: TC-PLAT-001 (Detailed).