# 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: ` (after login) - `X-Business-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