# Gate Pass — End-to-End Test Checklist

Walks the entire app across all three roles (**Owner / Approver / Reception**) against the seed data. Tick boxes as you verify.

---

## 0. Setup

- [ ] API + web servers running (`npm run dev` in `apps/api` and `apps/web`)
- [ ] Database seeded: `cd apps/api && npm run seed`
- [ ] Browser at `https://localhost:3200`
- [ ] `EXPOSE_OTP_IN_RESPONSE=true` in `apps/api/.env` (already set) — OTPs appear in the API response on `/api/auth/send-otp` and in the API server console.

**Seed credentials (re-printed by `npm run seed`):**

| Role              | Email / Mobile        | Notes                            |
|-------------------|------------------------|----------------------------------|
| Owner             | `owner@example.com`    | OTP login                        |
| Approver (full)   | `alice@example.com`    | `canAddVisitors = true`          |
| Approver (read)   | `bob@example.com`      | `canAddVisitors = false`         |
| Approver (inactive)| `carol@example.com`   | login should be **blocked**      |
| Reception #1      | `9000000001` / `scan123` | "Main Reception" checkpoint    |
| Reception #2      | `9000000002` / `scan123` | "South Gate" checkpoint        |

15 visitors are seeded covering EXPECTED, AWAITING_APPROVAL, ARRIVED, REJECTED, CANCELLED, EXPIRED — including approval-required, expiry, and approver-created variants. 4 walk-in requests cover PENDING / APPROVED / REJECTED states.

---

## 1. Owner

### Login
- [ ] `/login` → enter `owner@example.com` → submit
- [ ] OTP is shown in browser DevTools (response JSON) and/or API console
- [ ] `/login/otp` → enter OTP → lands on `/dashboard`
- [ ] Sidebar brand: "Gate Pass" with **ADMIN** (blue) pill
- [ ] Top-right shows email avatar/menu

### Dashboard
- [ ] Date-filterable section and All-time section render
- [ ] Counts match seed: Expected today, Arrived today, Awaiting approval > 0
- [ ] Recent activity table shows visitor rows

### Visitors tab
- [ ] `/visitors` lists 15 visitors
- [ ] Each row shows reason-for-visit (indigo icon + truncated text) below `#shortId`
- [ ] **Pagination bottom bar**: "Showing 1 – 10 of 15", page size selector (10/20/50/100), Prev/Next/First/Last
- [ ] Search: "Priya" → 1 row; "98765 11119" → Tina Roy; "PRIYA" case-insensitive
- [ ] Sort by Visit column toggles asc/desc/off (active key is blue)
- [ ] Sort by Status column groups similar rows
- [ ] Visitor with `requiresApproval` shows a purple pill (approver name or "Owner")
- [ ] Visitor with `expiresAt` shows "Until …" pill (orange) or "Expired" pill (gray)
- [ ] AWAITING_APPROVAL row is highlighted (orange left border)

### Action buttons (per row, in order)
- [ ] **Edit** (pencil) → opens `/visitors/:id/edit` with form pre-filled
  - Change name → Save → returns to list with toast, name updated in row
  - Required validation: clear "Reason for visit" → save fails with red border + error
- [ ] **Preview** (eye) → modal shows photo/name/status/reason/notes/contact/visit/approval policy/expiry/last scan
  - "Edit" footer button in modal navigates to edit page
- [ ] **QR** (qrcode) → modal shows scannable QR + Download button
- [ ] **Delete** (trash) → confirms + removes row

### Add Visitor (`/visitors/add`)
- [ ] **Typeahead in Full Name field**: type "Priya" → dropdown lists matching existing visitor(s)
  - Click match → form prefills (mobile, email, reason, notes, photo, approval policy, approver)
  - Visit date stays today, visit time blank, expiry off — these are fresh
  - Toast: "Loaded Priya Shah's details — adjust and save for a new visit"
- [ ] No typeahead match for "Zzz" → just type a new name, submit creates a new visitor
- [ ] **Reason for visit** title has red `*`; field is required (zod blocks submit if blank)
- [ ] Photo upload works (uploads to `/uploads/visitors/photo/…`)
- [ ] Date picker + Time picker work, smart placement (no overlap at bottom of viewport)
- [ ] Pass expiry toggle exposes date + time inputs; "No expiry" hides them
- [ ] Approval policy: "Auto" vs "Require approval" toggle. When "Require approval" + approver picker → searchable list of active approvers
- [ ] Save → toast `"<name> added"` → redirect to `/visitors`
- [ ] Bottom action bar is fixed; visible after scrolling long form

### Approvals tab
- [ ] Pill filters: All / Pending / Approved / Rejected with correct counts
- [ ] Unified rows show walk-ins (blue "Walk-in" source chip) and pre-registered approval-required visitors (purple chip)
- [ ] Each pending walk-in has Approve + Reject buttons
- [ ] Approve a walk-in → status changes to APPROVED, toast fires
- [ ] Reject a walk-in → REJECTED, optional note saved
- [ ] Inline Approve/Reject on AWAITING_APPROVAL visitors → status flips immediately
- [ ] Sort by Visitor / Reason / Submitted / Status / Source works

### Checkpoints tab (owner-only)
- [ ] Tab visible (3rd tab for owners)
- [ ] 2 seeded checkpoints listed
- [ ] Reveal password (eye toggle) shows `scan123`
- [ ] Copy credentials → clipboard receives "Scanner Login..."
- [ ] View activity → modal shows past scans + requests
- [ ] Add Checkpoint modal → creates new row

### Approvers (`/approvers`)
- [ ] 3 approvers listed (Alice / Bob / Carol)
- [ ] Carol is inactive (visual indicator)
- [ ] Toggle `canAddVisitors` on Bob → success toast
- [ ] View activity for Alice → modal lists her visitors + decisions
- [ ] Add Approver flow: create a new email; verify they appear

### Settings (`/settings`)
- [ ] Email template editor renders with seeded "Visitor invite" template
- [ ] Sample variables substitute in preview
- [ ] Departments management: list HR / Engineering / Sales; add new; delete

### Sidebar / Notifications
- [ ] Side menu badge on **Visitor** shows pending count (awaiting approval + pending walk-ins)
- [ ] Notification bell shows unread count
- [ ] Click bell → list of notifications; mark one read → count decreases
- [ ] "Mark all read" works

---

## 2. Approver — Alice (full permissions)

### Login
- [ ] Logout from owner. `/login` → `alice@example.com` → OTP → `/dashboard`
- [ ] Sidebar brand: "Gate Pass" with **APPROVER** (purple) pill
- [ ] Sidebar items: **Dashboard, Visitor** only (no Approver, no Settings)
- [ ] Manually hitting `/approvers` or `/settings` redirects back to `/dashboard` with toast

### Dashboard
- [ ] Counts reflect only visitors assigned to OR created by Alice (smaller than owner's)

### Visitors (`/visitors`)
- [ ] List shows only Alice-scoped visitors (e.g., Rohan, Aisha, Dev, Neha — at least 4)
- [ ] Visitors not in her scope (e.g., Sneha owner-approved) are absent
- [ ] Tabs: **Visitors, Approvals** (no Checkpoints tab)
- [ ] Pagination, search, sort all work
- [ ] Reason for visit shown per row

### Approvals
- [ ] Walk-in routed to Alice (Sanjay Rao) appears as PENDING; she can Approve/Reject
- [ ] Walk-in routed to owner only (Lara Stevens) is NOT visible to Alice
- [ ] AWAITING_APPROVAL visitors assigned to Alice (Dev Sharma) — she can decide inline

### Add Visitor (`/visitors/add`)
- [ ] Button is visible (canAddVisitors=true)
- [ ] **No approver picker** in Check-in policy — instead a purple note: "You'll be the one approving this visitor at check-in"
- [ ] **Typeahead**: type in name → dropdown only shows visitors in Alice's scope
- [ ] Save with `requiresApproval=true` → the new visitor's `assignedApproverId` is Alice
- [ ] Save with `requiresApproval=false` → just creates EXPECTED
- [ ] Edit on a visitor she created → redirected (only owner can edit) → toast "Only the owner can edit visitors"

---

## 3. Approver — Bob (no add permission)

### Login
- [ ] Logout. `/login` → `bob@example.com` → OTP → `/dashboard`
- [ ] **Add visitor** button is NOT visible on `/visitors`
- [ ] Hitting `/visitors/add` directly → toast "You do not have permission to add visitors" → redirect to `/dashboard`

### Visitors
- [ ] Bob sees only visitors assigned to him OR created by him (Manish Gupta is in his scope)

---

## 4. Approver — Carol (inactive)

- [ ] Logout. `/login` → `carol@example.com` → OTP → verify
- [ ] **Expected**: 401/403 or error — login should fail because `isActive=false`
- [ ] User does NOT reach the dashboard

---

## 5. Reception (Visitor Scanner)

Open in a private window or mobile browser (camera access needs https + permission).

### Login
- [ ] `/visitor-scanner/login` → mobile `9000000001` / password `scan123` → checkpoint dashboard
- [ ] Header shows "Gate Pass · Reception" + "Main Reception" + person name
- [ ] Tabs: **Scanner, Requests, History**

### Scanner tab
- [ ] Stats card: Expected today + Arrived
- [ ] Start QR Scanner → camera prompts → live feed
- [ ] Manual Search: "Priya" → result with Check-In button
- [ ] **Auto check-in flow** (Tina Roy was pre-arrived; pick an EXPECTED no-approval one like Priya):
  - Click Check In → green flash, beep, vibrate (on phone), card shows "Check-in Successful"
  - Reason-for-visit displayed in indigo block; notes (if any) in amber
- [ ] **Approval-required preview flow** (use Rohan Patel — assigned to Alice):
  - Tap Check In (or scan QR) → blue "Approval needed before entry" card with details
  - Cancel button dismisses without changing state
  - Tap **Request Approval** → card flips purple "Awaiting Host Approval"
  - Without leaving page, log in elsewhere as Alice and approve → card auto-flips green via Socket.io
- [ ] **Already arrived** → yellow card "Already Checked In"
- [ ] **Expired pass** (Vikram Singh): scan/check-in → red flash, "Pass has expired" error
- [ ] **Rejected**: try to check in a rejected visitor → red card "Do not let this visitor in"

### Requests tab
- [ ] Pending count badge shows in tab (matches seed pending count after walk-ins)
- [ ] **New Request** form opens
  - **Auto check-in** mode (green): name + reason required; submit creates an ARRIVED visitor
  - **Require approval** mode (orange): Route to picker shows approvers; submit creates VisitorRequest
  - **Reason for visit** required in both modes
  - Optional Notes textarea below Company
- [ ] Pending requests list: each shows reason, contact, time
- [ ] Wait 15 min (or modify seed `createdAt`) → "Mark Complete" button appears
- [ ] When owner/approver acts on a request → list updates live (status badge changes)

### History tab
- [ ] List shows recent scans (at least 5 from seed scan logs)
- [ ] Each row: photo/avatar, name, shortId, mobile, status badge, full timestamp
- [ ] Search box filters list (name, shortId, mobile, status)
- [ ] Refresh button reloads
- [ ] Empty state when no scans

### Notifications (bell, top-right)
- [ ] Unread count visible
- [ ] Click → list with relative time
- [ ] Mark all read → count goes to 0
- [ ] Background tab + new scan → browser OS notification fires (after permission granted)

---

## 6. Cross-role real-time

These verify Socket.io wiring across browsers/devices.

- [ ] Open Owner in tab A, Reception in tab B
- [ ] Reception scans an approval-required visitor → Owner sees the approval row appear in Approvals tab without refresh
- [ ] Owner approves → Reception card flips green
- [ ] Owner rejects → Reception card flips red
- [ ] Walk-in approval request from Reception → routed to Alice → Alice sees it in her Approvals tab
- [ ] Alice approves → Reception sees it move from Pending → Completed (green)

---

## 7. Mobile responsive spot-checks (reception app)

In Chrome DevTools, switch to a narrow viewport (~360px).

- [ ] Header: long checkpoint name + person name truncate; bell + logout stay visible
- [ ] 3-tab bar fits without crowding
- [ ] Scan result card: long status labels truncate next to "Dismiss"
- [ ] Walk-in form: Phone + Email stack vertically
- [ ] History row: name, status, time all fit; no horizontal scroll

---

## 8. Negative / edge cases

- [ ] Submit add-visitor with empty Name → "Name is required" error
- [ ] Submit add-visitor with empty Reason for visit → "Reason for visit is required"
- [ ] Submit walk-in (auto mode) with empty Reason → toast error, no API call
- [ ] Scanner: scan an unknown shortId / non-portal QR → "Visitor not found" error
- [ ] Login with non-allowed email → "This email is not authorized to access the portal"
- [ ] Expire OTP (>10 min old) → "Invalid or expired OTP"

---

## Reset

Re-running `npm run seed` wipes and recreates everything. Safe to use mid-test if state gets messy.
