payfrit-beacon-ios/QA_WALKTHROUGH.md
2026-02-01 23:39:29 -08:00

329 lines
12 KiB
Markdown

# Payfrit Beacon iOS — QA Walkthrough Test Document
## Overview
**App:** Payfrit Beacon iOS (SwiftUI)
**Purpose:** BLE beacon management for business locations
**Auth:** Token-based with Keychain storage + biometric
**Environment:** Dev (`dev.payfrit.com`) / Prod (`biz.payfrit.com`) — hardcoded in `APIService.swift:50`
---
## Pre-Test Setup
- [ ] Device has iOS 14+
- [ ] Bluetooth enabled in Settings
- [ ] Location Services enabled in Settings
- [ ] Stable network connection (Wi-Fi or LTE)
- [ ] A physical BLE beacon available for scanner tests
- [ ] Test account with at least 1 business, 1 beacon, 1 service point
---
## 1. Authentication Flow
### 1.1 First-Time Login
| Step | Action | Expected |
|------|--------|----------|
| 1 | Launch app (fresh install) | LoginScreen shown |
| 2 | Leave fields empty, tap "Sign In" | Button disabled (name/password empty) |
| 3 | Enter email only, tap "Sign In" | Error: "Please enter username and password" |
| 4 | Enter valid email + wrong password | Error: "Invalid email/phone or password" |
| 5 | Enter valid credentials, tap "Sign In" | Loading spinner on button, navigates to BusinessSelectionScreen |
| 6 | Kill and relaunch app | Saved auth loaded, skips login |
### 1.2 Biometric Re-Auth (Device Only, Skipped on Simulator)
| Step | Action | Expected |
|------|--------|----------|
| 1 | Relaunch app with saved auth | Face ID / Touch ID prompt appears |
| 2 | Authenticate successfully | Navigates to BusinessSelectionScreen |
| 3 | Relaunch, cancel biometric | Still loads saved auth (fallback) |
### 1.3 Logout
| Step | Action | Expected |
|------|--------|----------|
| 1 | From BusinessSelectionScreen, tap logout (top-right arrow icon) | Clears Keychain + UserDefaults, returns to LoginScreen |
| 2 | Kill and relaunch | LoginScreen shown (no saved auth) |
### 1.4 Session Expiry (401)
| Step | Action | Expected |
|------|--------|----------|
| 1 | Login, wait for token to expire (or invalidate server-side) | Next API call returns 401 |
| 2 | Try any action (list beacons, etc.) | Auto-logout, returns to LoginScreen |
### 1.5 Dev Mode Indicator
| Step | Action | Expected |
|------|--------|----------|
| 1 | Launch in dev environment | LoginScreen shows red "DEV MODE — password: 123456" |
| 2 | After login, check bottom-left of RootView | Orange "DEV" banner visible |
---
## 2. Business Selection
### 2.1 Load Businesses
| Step | Action | Expected |
|------|--------|----------|
| 1 | Login successfully | BusinessSelectionScreen loads with spinner |
| 2 | Wait for load | List of businesses with name + city |
| 3 | Tap a business | Pushes to BeaconDashboard, shows "Loading..." then TabView |
### 2.2 Edge Cases
| Scenario | Expected |
|----------|----------|
| User has no businesses | "No businesses found" message, can only logout |
| Network error during load | Error message + "Retry" button |
| Tap Retry | Re-fetches business list |
| 401 during load | Auto-logout to LoginScreen |
---
## 3. Beacon Management (Beacons Tab)
### 3.1 View Beacon List
| Step | Action | Expected |
|------|--------|----------|
| 1 | Tap "Beacons" tab | List loads with spinner |
| 2 | Verify beacon rows | Each shows: name, formatted UUID (XXXXXXXX-XXXX-...), active badge (green check / red X) |
| 3 | Pull down to refresh | Spinner appears, list refreshes |
| 4 | Empty list | "No beacons yet" + "Tap + to add" message |
### 3.2 Create Beacon
| Step | Action | Expected |
|------|--------|----------|
| 1 | Tap + button | BeaconEditSheet modal appears |
| 2 | Leave fields empty, tap Save | Button disabled |
| 3 | Enter name only | Button disabled (UUID required) |
| 4 | Enter name + valid 32-char hex UUID | Save enabled |
| 5 | Tap Save | Modal dismisses, list refreshes with new beacon |
| 6 | Network error during save | Error shown in sheet, sheet stays open |
### 3.3 Edit Beacon
| Step | Action | Expected |
|------|--------|----------|
| 1 | Tap a beacon row | Pushes to BeaconDetailScreen |
| 2 | Verify pre-filled fields | Name, UUID, Active toggle match beacon data |
| 3 | Verify read-only fields | ID, Business ID, Created, Updated dates shown |
| 4 | Change name, tap "Save Changes" | Returns to list, beacon updated |
| 5 | Toggle Active off, save | Badge changes from green check to red X |
| 6 | Clear name, try save | Button disabled |
### 3.4 Delete Beacon (Detail Screen)
| Step | Action | Expected |
|------|--------|----------|
| 1 | From BeaconDetailScreen, tap "Delete Beacon" | Alert: "Delete Beacon?" with warning about service points |
| 2 | Tap "Cancel" | Nothing happens |
| 3 | Tap "Delete" | API call, returns to list, beacon removed |
| 4 | Network error during delete | Error shown, stays on detail screen |
### 3.5 Delete Beacon (Swipe)
| Step | Action | Expected |
|------|--------|----------|
| 1 | Swipe left on beacon row | Delete action appears |
| 2 | Tap delete | Beacon removed immediately (optimistic) |
| 3 | If API fails | Beacon re-added to list, error shown |
### 3.6 Rapid Delete Stress Test
| Step | Action | Expected |
|------|--------|----------|
| 1 | Swipe-delete 3 beacons quickly | All removed from UI |
| 2 | Wait for API responses | Failed deletes restored, successful ones stay removed |
---
## 4. Service Points (Service Points Tab)
### 4.1 View Service Points
| Step | Action | Expected |
|------|--------|----------|
| 1 | Tap "Service Points" tab | List loads (fetches both service points AND beacons) |
| 2 | Verify rows | Name, type, active indicator (green/red circle), beacon assignment |
| 3 | Pull down to refresh | Refreshes both lists |
| 4 | Empty list | "No service points" message |
### 4.2 Assign Beacon
| Step | Action | Expected |
|------|--------|----------|
| 1 | Find service point with "No beacon assigned" | "Assign" button visible |
| 2 | Tap "Assign" | BeaconPickerSheet opens with all beacons |
| 3 | Tap a beacon | Sheet dismisses, spinner on row, then beacon name in green |
### 4.3 Change Beacon Assignment
| Step | Action | Expected |
|------|--------|----------|
| 1 | Find service point with assigned beacon | "Change" button + X button visible |
| 2 | Tap "Change" | Picker opens, current beacon has checkmark |
| 3 | Select different beacon | Assignment updated |
### 4.4 Unassign Beacon
| Step | Action | Expected |
|------|--------|----------|
| 1 | Find service point with assigned beacon | X button visible |
| 2 | Tap X button | API called with nil beaconId, shows "No beacon assigned" |
### 4.5 Edge Cases
| Scenario | Expected |
|----------|----------|
| No beacons exist | Picker opens with empty list |
| Network error during assign | Error shown, assignment reverted |
| 401 during any operation | Auto-logout |
---
## 5. Scanner (Scanner Tab)
### 5.1 Basic Scanning Flow
| Step | Action | Expected |
|------|--------|----------|
| 1 | Tap "Scanner" tab | Beacon picker loads at top |
| 2 | No beacon selected | "Start Scanning" button disabled |
| 3 | Select a beacon from picker | Button becomes enabled |
| 4 | Tap "Start Scanning" | Status: "Scanning..." (blue antenna icon) |
| 5 | Move close to matching beacon | RSSI value appears, samples count up (X/5) |
| 6 | Stay close for 5+ samples with RSSI >= -75 | "Beacon Detected! (avg -XX dBm)" (green checkmark) |
| 7 | Tap "Stop Scanning" | Status resets to "Select a beacon to scan" |
### 5.2 Signal Strength Visualization
| RSSI Range | Bar Color | Signal Quality |
|------------|-----------|---------------|
| -30 to -51 | Green | Strong |
| -52 to -72 | Yellow | Medium |
| -73 to -100 | Red | Weak |
### 5.3 RSSI Threshold Behavior
| Step | Action | Expected |
|------|--------|----------|
| 1 | Scanning, RSSI >= -75 | Sample appended, count increments |
| 2 | RSSI drops below -75 | All samples cleared, count resets to 0 |
| 3 | RSSI returns above -75 | Samples start accumulating again from 0 |
| 4 | Beacon disappears entirely (RSSI = 0) | Samples cleared, "Searching for beacon signal..." |
### 5.4 Permission Handling
| Scenario | Expected |
|----------|----------|
| Location not determined | System prompt shown, scanning waits |
| Location granted after prompt | Scanning starts automatically |
| Location denied | "Location Permission Denied" (red), scanning stops |
| Location denied in Settings | Same as above on next scan attempt |
| Bluetooth off | "Bluetooth is OFF" (orange), scanning stops |
| Bluetooth turned off mid-scan | Detected within 5 seconds, scanning stops |
### 5.5 Beacon Change During Scan
| Step | Action | Expected |
|------|--------|----------|
| 1 | Start scanning for Beacon A | Scanning active |
| 2 | Change picker to Beacon B | Scan stops immediately |
| 3 | Tap "Start Scanning" | New scan starts for Beacon B |
### 5.6 Screen Lock Prevention
| Step | Action | Expected |
|------|--------|----------|
| 1 | Start scanning | Screen stays awake (idle timer disabled) |
| 2 | Stop scanning | Idle timer re-enabled, screen can lock normally |
### 5.7 Error Cases
| Scenario | Expected |
|----------|----------|
| Invalid UUID format on beacon | Error: "Invalid beacon UUID format" |
| Ranging failure (CLLocationManager error) | Error: "Beacon ranging failed: [description]" |
| Beacon list fails to load | Picker empty, scanner still functional if UUID known |
---
## 6. Cross-Cutting Concerns
### 6.1 Network Error Handling (All Screens)
| Scenario | Expected |
|----------|----------|
| Airplane mode during API call | Network error displayed |
| Server returns 500 | "HTTP 500" error shown |
| Server returns non-JSON | "Decoding error: Non-JSON response" |
| Server returns `{"OK": false, "ERROR": "..."}` | Error message from server shown |
| 401 on any authenticated endpoint | Auto-logout to LoginScreen |
### 6.2 Navigation
| Test | Expected |
|------|----------|
| Back button from BeaconDetailScreen | Returns to BeaconListScreen |
| Back button from BeaconDashboard | Returns to BusinessSelectionScreen |
| Tab switching in BeaconDashboard | Beacons / Service Points / Scanner tabs all functional |
| Deep link: Business > Beacon > Detail > Back > Back | Full nav stack unwinds cleanly |
### 6.3 Data Consistency
| Test | Expected |
|------|----------|
| Add beacon, switch to Service Points tab | New beacon available in picker |
| Delete beacon assigned to service point | Service point shows "No beacon assigned" on refresh |
| Edit beacon name | Updated name shows in list and service point rows |
---
## 7. API Endpoints Reference
| Action | Method | Endpoint |
|--------|--------|----------|
| Login | POST | `/auth/login.cfm` |
| List businesses | POST | `/workers/myBusinesses.cfm` |
| List beacons | POST | `/beacons/list.cfm` |
| Get beacon | POST | `/beacons/get.cfm` |
| Create beacon | POST | `/beacons/create.cfm` |
| Update beacon | POST | `/beacons/update.cfm` |
| Delete beacon | POST | `/beacons/delete.cfm` |
| List service points | POST | `/servicePoints/list.cfm` |
| List SP types | POST | `/servicePoints/types.cfm` |
| Assign beacon to SP | POST | `/servicePoints/assignBeacon.cfm` |
All requests include headers:
- `Content-Type: application/json`
- `X-User-Token: <token>` (after login)
- `X-Business-ID: <id>` (after business selection)
---
## 8. Permissions Checklist
| Permission | Info.plist Key | When Prompted |
|-----------|----------------|---------------|
| Location (When In Use) | `NSLocationWhenInUseUsageDescription` | First beacon scan |
| Location (Always) | `NSLocationAlwaysAndWhenInUseUsageDescription` | Background scanning |
| Bluetooth | `NSBluetoothAlwaysUsageDescription` | First beacon scan |
| Face ID | `NSFaceIDUsageDescription` | App relaunch with saved auth |
---
## 9. Known Limitations
1. **Environment switching** requires code change + recompile (`APIService.swift:50`)
2. **Service points are read-only** — can only assign/unassign beacons, not create/edit/delete SPs
3. **No token refresh** — expired tokens force full re-login
4. **Single scanner** — only one scan at a time, changing beacon stops previous
5. **Background scanning** depends on iOS version and background mode support
6. **Photo URLs** resolved but not cached across sessions