Skip to main content

Test Cases — SAPOT Mobile App

Revised: 2026-06-20

Execution Type Definitions

TypeDefinitionTools
AUTOMATEDRuns in CI without human or physical deviceJest, RNTL, Pytest
HYBRIDScripted, requires a live device or emulatorMaestro
MANUALRequires a human on a physical device with real hardware or real networkPhysical device

Sections 1–2 use the format: ID | Feature | Scenario | Steps | Expected Result | Type | Execution | Priority | Risk
Sections 3–21 use the format: ID | Feature | Scenario | Preconditions | Steps | Expected Result | Priority | Severity | Automate
— where Automate carries the execution tool. For MANUAL tests the Automate column reads MANUAL (physical device).

Section 21 (Real Device / Real Network) consolidates multi-device and real-network cases and uses the full Sections 3–21 format.

Priority: P0 (must-pass), P1 (high), P2 (medium), P3 (low)


Precondition Setup Reference

For seeded accounts, state-control API calls, OTP test mode, and the Jest login fixture, see test-data-environment.md.

The table below is a quick-reference for precondition states. Full setup procedures (SQL, shell scripts, seed script) are in the appendix above.

StateHow to produce
Logged in (auth user)1. Select Server mode on Getting Started screen 2. Enter valid credentials 3. Tap Login → navigates to Chats tab
Guest user / Guest with messages1. Select LAN mode 2. Enter first + last name 3. Tap Login 4. Open chat with any discovered peer 5. Send or receive at least one message
Conversations exist in DBFollow "Logged in (auth user)" then send or receive at least one message with each peer
5+ prior failed attemptsAttempt login 5 times with an incorrect password, or use the staging admin API: POST /admin/simulate-lockout?username=<x>&count=5
Account bannedUse the staging admin API: POST /admin/ban?userId=<x>
Session > 60 days oldUse the staging admin API: POST /admin/age-session?userId=<x>&days=61
OTP > 10 min oldRequest OTP, then wait 11 minutes before submitting; or use staging admin API: POST /admin/expire-otp?userId=<x>
Token > 30 min oldRequest a reset token, wait 31 minutes; or use staging admin API: POST /admin/expire-reset-token?userId=<x>
Recovery-key cooldown activeGenerate a recovery key (TC-072), then immediately navigate back to Generate Recovery Key; or use POST /admin/set-key-cooldown?userId=<x>&days=N
Peer discoveredBoth devices on the same WiFi in LAN mode; wait up to 10 s for mDNS discovery
WebRTC connectedOpen a chat room with a discovered peer and wait for the "Connected" status indicator
TCP connectedOpen a chat room with a LAN peer; confirm TCP status in the chat room header
Messages queued (offline peer)Put the receiver device in airplane mode; send messages from the sender device
Expired announcementsUse the staging admin API: POST /admin/expire-announcement?id=<x>, or set expiry_date in the past via direct DB edit on staging
Migration completeFollow TC-260 (Guest Migration) through to completion

1. Getting Started / Mode Select

IDFeatureScenarioStepsExpected ResultTypeExecutionPriorityRisk
TC-001Mode SelectServer mode: backend reachable1. Launch app (backend running) 2. Tap "Server" 3. Tap "Proceed"Navigates to Server Login; AppModeStore set to "server"HYBRIDMaestroP0Critical
TC-002Mode SelectServer mode: backend unreachable1. Launch app (backend offline) 2. Tap "Server" 3. Tap "Proceed""Connection Failed" dialog shown with Retry + "Use LAN Mode"HYBRIDMaestroP0Critical
TC-003Mode SelectRetry after connection failure recovers1. Dialog shown (backend restored) 2. Tap "Retry"Health check re-runs; navigates to Server LoginHYBRIDMaestroP1High
TC-004Mode SelectSwitch to LAN from failure dialog1. Dialog shown 2. Tap "Use LAN Mode"AppModeStore set to "lan"; navigates to LAN LoginHYBRIDMaestroP1High
TC-005Mode SelectLAN mode skips backend check1. Tap "LAN" 2. Tap "Proceed"Navigates to LAN Login; no health check madeHYBRIDMaestroP0Critical
TC-006Mode SelectMode persists across restart1. Select Server mode 2. Kill and relaunch appApp skips mode select; goes to appropriate loginHYBRIDMaestroP1High

2. Server Login

IDFeatureScenarioStepsExpected ResultTypeExecutionPriorityRisk
TC-010Server LoginSuccessful login1. Enter valid username + password 2. Tap LoginTokens stored; MainContainer initialized; navigates to Chats tabHYBRIDMaestroP0Critical
TC-011Server LoginWrong password1. Enter valid username, wrong password 2. Tap LoginError shown; AttemptsWarning shows remaining attemptsAUTOMATEDRNTLP0Critical
TC-012Server LoginNon-existent username1. Enter unknown username + any password 2. Tap LoginGeneric error (not "user not found" — enumeration protection)AUTOMATEDRNTLP0Critical
TC-013Server LoginAccount locked out1. (5+ prior failed attempts) 2. Attempt loginLockoutBanner shown with countdown; form disabledAUTOMATEDRNTLP0Critical
TC-014Server LoginAccount banned1. Enter valid credentials for banned account 2. Tap LoginBannedBanner shown with ban messageAUTOMATEDRNTLP0Critical
TC-015Server LoginLogin times out (30 s)1. Enter valid credentials 2. Backend stalls > 30 sTimeout error; loading state clearedAUTOMATEDJestP1High
TC-016Server LoginLogin blocked when server offline1. No network connectivity 2. Attempt loginSubmit button disabled; no network request is madeAUTOMATEDRNTLP1High
TC-017Server LoginNavigate to forgot password1. On login screen 2. Tap "Forgot password?"Navigates to forgot-password method pickerHYBRIDMaestroP1High
TC-018Server LoginEmpty fields validation1. Leave both fields empty 2. Tap LoginValidation errors shown per fieldAUTOMATEDRNTLP2Medium
TC-019Server LoginRefresh token auto-renews expiring session1. Perform any authenticated action with expiring access tokenInterceptor refreshes silently; action succeedsAUTOMATEDJestP0Critical
TC-020Server LoginExpired refresh token forces re-login1. Open app with session > 60 days oldSession cleared; redirected to loginAUTOMATEDJestP0Critical

3. Guest (LAN) Login

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-030LAN LoginSuccessful guest loginLAN mode selected1. Enter first + last name 2. Tap LoginGuest identity created; navigates to Chats; mode = "lan"P0CriticalMaestro
TC-031LAN LoginEmpty first name rejected1. Leave first name empty 2. Tap LoginValidation error for first nameP1HighRNTL
TC-032LAN LoginEmpty last name rejected1. Leave last name empty 2. Tap LoginValidation error for last nameP1HighRNTL
TC-033LAN LoginGuest cannot see Public Chat tabLogged in as guest1. View tabsPublic Chat tab is absentP0CriticalMaestro
TC-034LAN LoginGuest cannot see Map tabLogged in as guest1. View tabsMap tab is absentP0CriticalMaestro
TC-035LAN LoginGuest mode locked to LAN transportLogged in as guest1. Go to Switch ModeOnly "LAN" selectable; Server and Auto disabledP0CriticalMaestro
TC-036LAN LoginGuest logout shows warning modalLogged in as guest1. Open drawer 2. Tap LogoutWarning modal: "Your messages and conversations will be lost"P1HighMaestro
TC-037LAN LoginGuest data cleared on logoutGuest with messages1. Confirm logoutWatermelonDB guest data cleared; returns to Getting StartedP0CriticalMaestro

4. Registration

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-040RegisterSuccessful registrationBackend running, unique username1. Fill all fields 2. Accept T&C 3. Tap RegisterAccount created; tokens received; navigates to ChatsP0CriticalMaestro
TC-041RegisterDuplicate usernameUsername taken1. Enter taken username (debounce fires)"Username already taken" inline errorP0CriticalRNTL
TC-042RegisterT&C must be acceptedForm filled1. Skip T&C 2. Tap RegisterRegistration blockedP0CriticalRNTL
TC-043RegisterPassword too short1. Enter password < 8 charsValidation errorP1HighRNTL
TC-044RegisterPassword mismatch1. Enter mismatched passwords 2. Submit"Passwords do not match" errorP1HighRNTL
TC-045RegisterServer offline blocks submissionNo connectivity1. Fill form 2. Tap RegisterSubmission blocked; offline errorP1HighRNTL
TC-046RegisterUsername check debounced (single API call)1. Type username rapidlyOnly 1 API call fires after typing stopsP2MediumJest
TC-047RegistersetPendingPassword called for key derivation1. Complete registrationsetPendingPassword() called; crypto keys derivableP0CriticalJest

5. Forgot Password

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-050Forgot PW4 reset method options shown1. Navigate to Forgot PasswordEmail, SMS, Security Question, Recovery Key optionsP1HighRNTL
TC-051Forgot PW (Email)Valid email OTP flow resets passwordAccount with email1. Choose Email 2. Enter email 3. Enter OTP 4. Enter new passwordPassword reset; login worksP0CriticalMaestro
TC-052Forgot PW (Email)Unknown email returns generic success1. Enter unknown emailGeneric success message (enumeration protection)P0CriticalJest
TC-053Forgot PW (Email)Expired OTP rejectedOTP > 10 min old1. Enter expired OTP"Code expired" error; must request new codeP1HighJest
TC-054Forgot PW (SMS)Valid phone OTP flow resets passwordAccount with phone1. Choose SMS 2. Enter phone 3. Enter OTP 4. ResetPassword reset successfullyP0CriticalMaestro
TC-055Forgot PW (SMS)3 wrong OTP attempts triggers lock1. Enter wrong OTP 3 times10-minute lock; further attempts blockedP0CriticalJest
TC-056Forgot PW (Security Q)Correct answer resets passwordQuestions set1. Answer correctly 4. ResetPassword reset; question burnedP0CriticalJest
TC-057Forgot PW (Security Q)Wrong answer decrements attempts1. Answer incorrectlyError + remaining attempts shownP0CriticalJest
TC-058Forgot PW (Recovery Key)Valid key file resets passwordKey generated1. Upload key file 2. ResetPassword resetP0CriticalMaestro
TC-059Forgot PW (Recovery Key)Invalid file rejected1. Upload non-key fileError; reset blockedP1HighJest
TC-060Forgot PWNew password < 8 chars rejectedAt reset step1. Enter short passwordValidation errorP1HighJest
TC-061Forgot PWReset token expires after 30 minValid token1. Follow reset link after 30 min"Token expired" errorP1HighJest

6. Change Password & Recovery Key

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-070Change PWCorrect current password changes PWLogged in1. Enter current + new password 2. SubmitPassword changed; session validP0CriticalMaestro
TC-071Change PWWrong current password rejectedLogged in1. Enter wrong current passwordError; password unchangedP0CriticalRNTL
TC-072Recovery KeyGenerate downloads key fileLogged in, not in cooldown1. Enter password 2. Tap GenerateKey file downloaded; old key invalidatedP0CriticalMaestro
TC-073Recovery KeyBlocked during cooldownKey generated recently1. Navigate to Generate Recovery KeyCooldown message with days remainingP0CriticalRNTL

7. Chats Tab

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-080Chats TabDiscovered peers shown as bubblesLAN mode, peer on network1. Navigate to ChatsPeer bubble per discovered deviceP0CriticalMANUAL (2-device rig)
TC-081Chats TabConversations shown in listConversations exist in DB1. Navigate to ChatsChat rows: name, last message, timestampP0CriticalRNTL
TC-082Chats TabPull-to-refresh triggers syncServer mode1. Pull downsyncService.syncNow() calledP1HighJest
TC-083Chats TabTap peer bubble → Chat Room (source=PEER)Peer discovered, no existing conversation1. Tap peer bubble
2. Keep the chat room open
3. Send a message from the peer device
Navigates to /chat/[peerId] with source=PEER, creates the empty direct conversation immediately, and renders the incoming message without reopening the roomP0CriticalMANUAL (2-device rig)
TC-084Chats TabTap chat row → Chat Room (source=CHAT)Conversation exists1. Tap chat rowNavigates to /chat/[id] with source=CHATP0CriticalMaestro
TC-085Chats TabSearch bar tap → Search screen1. Tap search barNavigates to /searchP1HighMaestro
TC-086Chats TabQR icon → QR Scanner1. Tap QR iconNavigates to /scan-qrP1HighMaestro
TC-087Chats TabSMS FAB hidden for guestsGuest user1. View ChatsSMS FAB not visibleP0CriticalMaestro
TC-088Chats TabSMS FAB disabled without verified phoneAuth user, no phone1. View ChatsSMS FAB visible but disabledP1HighRNTL
TC-089Chats TabSMS FAB opens contact dialogAuth, phone verified, SMS healthy1. Tap SMS FAB"Contact by Phone" dialog with phone inputP1HighMaestro
TC-090Chats TabmDNS discovery starts on mount1. Navigate to ChatsZeroconf scan + publish activeP0CriticalJest
TC-091Chats TabTCP server starts on mount1. Navigate to ChatsconnectionService.startTcpServer() calledP0CriticalJest
TC-092Chats TabEmpty state for new userNo conversations1. Navigate to ChatsEmpty state message shownP2MediumRNTL

8. Chat Room

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-100Chat RoomSend via WebRTC data channelWebRTC connected1. Type + send messageSENDING → SENT → DELIVEREDP0CriticalMANUAL (2-device rig)
TC-101Chat RoomSend via TCPTCP connected1. Type + send messageSENDING → SENT → DELIVEREDP0CriticalMANUAL (2-device rig)
TC-102Chat RoomACK timeout (12 s) marks NOT_SENTPeer disconnects after send1. Send message 2. Wait 12 sMessage status → NOT_SENTP0CriticalJest
TC-103Chat RoomNOT_SENT message retryableNOT_SENT message exists1. Tap "Tap to retry"Message re-queuedP1HighRNTL
TC-104Chat RoomMessages encrypted at restMessage saved1. Inspect DB contentmessages.content is encrypted blob, not plaintextP0CriticalJest
TC-105Chat RoomReceive message while screen activePeer sends message1. Keep chat room openMessage appears; "seen" receipt sentP0CriticalMANUAL (2-device rig)
TC-106Chat RoomSeen receipt sent when focusedUnread messages1. Open chat room"seen" message sent to peerP1HighJest
TC-107Chat RoomConnecting state on mountPeer not yet connected1. Open chat"Connecting…" indicator shownP1HighRNTL
TC-108Chat RoomFailed state after 5 retriesPeer unreachable1. Open chat 2. Wait for retries"Tap to retry" shown; auto-reconnect stopsP0CriticalJest
TC-109Chat RoomExponential backoff between retriesPeer unreachable1. Open chatEach successive delay is ≥1.5× the previous; total wait before 5th retry is between 10 s and 30 sP1HighJest
TC-110Chat RoomAuto-reconnect on mDNS rediscoveryPeer IP changes1. Peer reconnects with new IPConnection re-established automaticallyP0CriticalJest
TC-111Chat RoomAuto-reconnect on network regainWiFi drops then restores1. Lose and regain WiFiNetInfo triggers reconnectP0CriticalJest
TC-112Chat RoomAudio call icon navigates to Call RoomConnected1. Tap audio callNavigates to /call/[id]?type=audioP0CriticalMaestro
TC-113Chat RoomVideo call icon navigates to Call RoomConnected1. Tap video callNavigates to /call/[id]?type=videoP0CriticalMaestro
TC-114Chat RoomSMS mode when eligibleAuth, phones verified, SMS healthy1. Tap "SMS" chipSMS mode activeP1HighMaestro
TC-115Chat RoomSMS mode hidden for guestsGuest user1. Open chatSMS chip not shownP0CriticalRNTL
TC-116Chat RoomSMS warning banner in SMS modeSMS mode active1. Switch to SMS mode"Not E2E encrypted" banner shownP0CriticalRNTL
TC-117Chat RoomSelf-chat always "connected"Own peer ID1. Open self-chatNo TCP/WebRTC; messages save directly as DELIVEREDP1HighJest
TC-118Chat RoomAdmin role badge shownPeer is admin1. Open chat with adminPurple "Admin" badge shownP1HighRNTL
TC-119Chat RoomRescuer role badge shownPeer is rescuer1. Open chat with rescuerGreen "Rescuer" badge shownP1HighRNTL
TC-120Chat RoomLAN mode blocks WS fallbackLAN mode, no TCP/data channel1. Send message without transportError thrown; no silent failureP0CriticalJest
TC-121Chat RoomMessage deduplication by messageIdWS delivers duplicate1. Duplicate message arrivesOnly one message appearsP0CriticalJest
TC-122Chat RoomUp to 5 past keys for decryptionKey rotated multiple times1. Receive message with old keyDecrypted using historical keyP0CriticalJest

9. Incoming Call

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-130Incoming CallAccept audio callPeer calls1. Tap AcceptMic permission; call answered; Call RoomP0CriticalMANUAL (physical device)
TC-131Incoming CallAccept video callPeer video-calls1. Tap AcceptMic + camera permission; Call RoomP0CriticalMANUAL (physical device)
TC-132Incoming CallReject call1. Tap RejectReturns to Chats; call ended; no active call stateP0CriticalMaestro
TC-133Incoming CallAuto-dismiss after 30 sNo action1. Wait 30 secondsMissed call recorded; screen dismissesP0CriticalJest
TC-134Incoming CallCaller hangs up before answerCaller cancels1. Receive call-endedScreen dismisses immediatelyP0CriticalJest
TC-135Incoming CallMic permission denied blocks acceptPermission denied1. Deny mic permission on real device 2. Tap AcceptBlocked; permission denied message shownP0CriticalMANUAL (physical device)
TC-136Incoming CallSimultaneous call tie-breakerBoth call each other1. Both tap call simultaneouslyOne side busy; other proceedsP0CriticalJest
TC-137Incoming CallNotification deduplication prevents double navDuplicate notification1. Receive two notifications for same callNavigation fires once onlyP0CriticalJest

10. Call Room

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-140Call RoomOutgoing shows "Calling…"1. Initiate callCalling state shownP1HighRNTL
TC-141Call RoomNo-answer after 30 sPeer does not answer1. Wait 30 s"No answer" state; call endsP0CriticalJest
TC-142Call RoomTransition to connected on answerPeer answers1. Peer answersConnected state; timer starts; controls shownP0CriticalMaestro
TC-143Call RoomMute/unmute micConnected, real call1. Tap mute 2. Tap unmuteMic track toggled; icon reflects state; peer hears silence when mutedP0CriticalMANUAL (physical device)
TC-144Call RoomToggle camera on during audio callConnected, audio, real device1. Tap camera iconVideo track acquired from real camera; activatesP1HighMANUAL (physical device)
TC-145Call RoomToggle camera offCamera on, real device1. Tap camera iconVideo track disabled; peer sees blankP1HighMANUAL (physical device)
TC-146Call RoomSwitch front/back cameraCamera active, real device1. Tap flipCamera switches between front and rearP1HighMANUAL (physical device)
TC-147Call RoomToggle earpiece/speakerConnected, real device1. Tap speaker iconAudio route changes; audible on speakerP1HighMANUAL (physical device)
TC-148Call RoomEnd callConnected1. Tap EndBoth disconnected; call log saved; navigate backP0CriticalMaestro
TC-149Call RoomMinimize continues callConnected1. Press backReturns to previous screen; CallBanner shown; call activeP0CriticalMaestro
TC-150Call RoomControls auto-hide after 5 sConnected1. Wait 5 sControls fadeP2LowMaestro
TC-151Call RoomTap screen shows hidden controlsControls hidden1. Tap screenControls appearP2LowMaestro
TC-152Call RoomReconnecting overlay on ICE disruptionReal device, real network1. Drop WiFi mid-call 2. Restore WiFi"Reconnecting…" overlay appears; dismissed on ICE restoreP0CriticalMANUAL (physical device + real network)
TC-153Call RoomCall log saved on end1. End callCALL_LOG message saved with durationP0CriticalJest
TC-154Call RoomBusy state if peer in another call1. Call busy peer"Busy" shown immediatelyP0CriticalJest

11. Public Chat

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-160Public ChatTab hidden for guestsGuest1. View tabsPublic Chat absentP0CriticalMaestro
TC-161Public ChatUnavailable in LAN modeLAN mode1. Navigate to Public Chat"Unavailable" screen shownP0CriticalRNTL
TC-162Public ChatMessages load on mountAuth, server mode1. Navigate to Public ChatRecent messages shownP1HighMaestro
TC-163Public ChatSend message broadcasts to allConnected1. Send messageAll connected users receive itP0CriticalMANUAL (2-device rig)
TC-164Public ChatSend disabled when disconnectedWS disconnected1. Open while disconnectedSend button disabledP1HighRNTL
TC-165Public ChatLoad earlier messagesHistory exists1. Tap "Load earlier messages"Older messages loadedP1HighMaestro
TC-166Public ChatConnection status indicator1. Observe headerGreen = connected; amber + spinner = connectingP2MediumRNTL

12. GPS / Map

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-170MapTab visible for rescuersUser is rescuer1. View tabsMap tab visibleP0CriticalMaestro
TC-171MapTab hidden for non-rescuersRegular user1. View tabsMap tab absentP0CriticalMaestro
TC-172MapLocation denied shows error statePermission denied1. Deny locationError state + "Open Settings" buttonP0CriticalMaestro
TC-173MapUser markers from pollGPS data exists1. Open MapPins at user locationsP1HighMaestro
TC-174MapPoll refreshes every 5 sMap open1. Wait 5 sPositions updatedP1HighJest
TC-175MapTap marker opens sheetMarker visible1. Tap markerUserMarkerSheet opensP1HighMaestro
TC-176MapSheet Message button → Chat RoomSheet open1. Tap "Message"Navigates to Chat RoomP1HighMaestro
TC-177MapView Path fetches and renders historySheet open1. Tap "View Path"Last 50 points rendered as red gradient pathP1HighMaestro
TC-178MapEmpty state when no one sharingNo GPS data1. Open MapEmpty state messageP2MediumRNTL
TC-179Map"No history" for <2 points1 GPS point1. Tap "View Path""No history" bannerP2MediumRNTL
TC-180GPS StreamingStarts when enabled + auth + permissionSharing=true, auth, granted1. Enable sharingWS opens; coordinates streamP0CriticalJest
TC-181GPS StreamingDoes not start for guestsGuest1. Enable GPSStreaming does not startP0CriticalJest
TC-182GPS StreamingStops when sharing disabledStreaming active1. Toggle offWS closedP0CriticalJest
TC-183GPS StreamingAuto-reconnects after 3 sWS drops1. Simulate WS disconnectReconnects after 3 sP1HighJest
TC-184GPS SettingsToggle persists across restartGPS enabled1. Kill + reopen appGPS preference retainedP1HighMaestro

13. QR Scanner

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-190QR ScannerCamera permission requested on open1. Navigate to QR ScannerCamera permission promptP0CriticalMaestro
TC-191QR ScannerCamera denied shows messageDenied1. Deny cameraPermission denied message in scannerP0CriticalMaestro
TC-192QR ScannerValid QR → Chat Room (server mode)Peer's QR, server mode1. Scan valid QRPeer upserted; navigates to Chat RoomP0CriticalMaestro
TC-193QR ScannerValid QR in LAN requires ip/portLAN mode1. Scan QR without ip/port"Invalid QR code" badge for 4 sP0CriticalRNTL
TC-194QR ScannerInvalid QR payload shows error1. Scan non-SAPOT QR"Invalid QR code" badge 4 s then resetP1HighRNTL
TC-195QR ScannerPick image with QR from library1. Tap "Pick Image" 2. Select valid imageQR decoded; navigates to Chat RoomP1HighMaestro
TC-196QR Code (own)QR contains correct payload in server modeServer mode1. Navigate to QR Code settingsQR has {id, firstName, username}P1HighMaestro
TC-197QR Code (own)QR contains ip/port in LAN modeLAN mode, IP assigned1. Navigate to QR Code settingsQR has {id, firstName, ip, port}P1HighMaestro

14. Announcements

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-200AnnouncementsLoads role-filtered listAuth user1. Navigate to AnnouncementsRole-appropriate announcements shownP1HighMaestro
TC-201AnnouncementsRescuers see more than regular usersCompare countsRescuer sees rescuer+user; user sees user onlyP1HighJest
TC-202AnnouncementsExpired announcements hiddenSome expired1. View listOnly active announcementsP0CriticalJest
TC-203AnnouncementsFilter by priority works1. Tap High filterOnly high-priority shownP2MediumMaestro
TC-204Announcements"N new" badge shownUnread announcements1. View headerBadge shows new countP2MediumRNTL
TC-205AnnouncementsmarkAllSeen called on focus1. Navigate to screenBadge cleared on next visitP2MediumJest

15. Profile & Settings

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-210Manage ProfileUpload photo via cameraAuth user1. Tap Change Photo → Take PhotoPhoto uploaded; shown in appP1HighMANUAL (physical device)
TC-211Manage ProfileUpload photo from libraryAuth user1. Tap Change Photo → UploadPicker opens; photo uploadedP1HighMANUAL (physical device)
TC-212Manage ProfileGuest cannot upload photoGuest1. Navigate to Manage ProfilePhoto upload option absentP0CriticalRNTL
TC-213Manage ProfileRe-auth required before changing emailAuth1. Tap Change EmailRe-auth modal shownP0CriticalMaestro
TC-214Manage ProfileRe-auth required before changing phoneAuth1. Tap Change PhoneRe-auth modal shownP0CriticalMaestro
TC-215SettingsGuest sees "Authenticate" not "Password & Security"Guest1. Open Settings"Authenticate" shown; "Password & Security" absentP0CriticalRNTL
TC-216Switch ModeChange from server to LANAuth, server mode1. Select LANAppModeStore updated; transport restartedP1HighMaestro
TC-217Switch ModeGuest cannot select server/autoGuest1. Open Switch ModeServer and Auto disabledP0CriticalRNTL
TC-218ThemeSelection persists across restart1. Select dark 2. Kill + reopenDark theme appliedP2LowMaestro

16. Background Connectivity & Notifications

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-220Foreground ServiceStarts when app is backgroundedAndroid app active1. Send app to backgroundForeground-service notification appears; existing transports remain connectedP0CriticalJest + MANUAL
TC-221Foreground ServiceStops when app becomes activeForeground service running1. Return to appForeground-service notification disappears; transports remain owned by MainContainerP0CriticalJest + MANUAL
TC-222Foreground ServiceStops when drawer layout unmountsForeground service running1. Log outForeground service stopsP0CriticalJest
TC-223NotificationsIncoming call notification fires while process is aliveApp backgrounded on real device1. Peer sends audio-call messageincoming-call notification appears with ringtoneP0CriticalMANUAL (physical device)
TC-224NotificationsNotification dismissed on call-endedNotification shown, real device1. Leave notification 2. Caller hangs upNotification dismissed automaticallyP0CriticalMANUAL (physical device)
TC-225NotificationsTap call notification → Incoming Call screenApp in background, real device1. Receive call notification 2. Tap itOpens /call/incoming with caller infoP0CriticalMANUAL (physical device)
TC-226NotificationsTap message notification → Chat RoomApp in background, real device1. Receive message notification 2. Tap itOpens /chat/[id]P1HighMANUAL (physical device)
TC-227NotificationsCold start from existing notificationCall notification already shown1. Force-close app 2. Tap the existing notificationgetLastNotificationResponseAsync() used; Incoming Call shownP0CriticalMANUAL (physical device)
TC-228NotificationsDeduplication prevents double navigationDuplicate notification1. Both foreground + background arriveNavigation fires once onlyP0CriticalJest

17. Offline / Network Interruption

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-230OfflineChat message queued for offline WS peerPeer offline1. Send messageServer queues; delivers on peer reconnectP0CriticalJest
TC-231OfflineServer-ack sent to sender when queuedSender online, receiver offline1. Send messageSender receives server-ackP0CriticalJest
TC-232OfflineQueued messages drained on peer reconnectMessages queued1. Target reconnectsAll messages delivered in orderP0CriticalJest
TC-233OfflineACK deletes queue entry1. Receiver sends ackQueue entry deletedP0CriticalJest
TC-234OfflineSync retries with exponential backoffServer unreachable1. Trigger sync offlineUp to 5 retries; max 30 s delayP0CriticalJest
TC-235OfflineServerStatusBanner in server/auto modeServer down1. Lose server connectivityBanner shownP1HighRNTL
TC-236OfflineNo banner in LAN modeLAN mode, server down1. Navigate to ChatsNo server-offline bannerP1HighRNTL
TC-237OfflineLAN chat works when server downLAN mode1. Lose server 2. Chat with LAN peerChat works via TCP; no errorsP0CriticalMANUAL (2-device rig)

18. Encryption & Security

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-240EncryptionTCP traffic is encryptedTCP connection1. Inspect TCP streamBytes; no plaintext contentP0CriticalJest
TC-241EncryptionWS relay payloads encrypted E2EServer relay1. Inspect WS payload on serverEncrypted blob; server cannot readP0CriticalJest
TC-242EncryptionMessages encrypted at rest in DBMessage saved1. Query DBcontent field is encryptedP0CriticalJest
TC-243EncryptionKey history decrypts old messagesKey rotated1. Receive message encrypted with old keyDecrypted successfullyP0CriticalJest
TC-244Key RecoveryPBKDF2 is deterministicKnown inputs1. Derive key twiceSame key both timesP0CriticalJest
TC-245Key RecoveryWrong recovery key rejected1. Provide wrong keyRecovery blockedP0CriticalJest
TC-246SecurityTesting endpoints require admin authAdmin token required1. POST /testing/test-make-admin without token 2. POST with non-admin token 3. POST with admin tokenSteps 1–2 return 401/403; step 3 returns 200P0CriticalPytest
TC-247SecurityGPS monitor WS requires valid rescuer token1. Connect to /gps/ws/monitor/rescuers/{id} without token 2. Connect with non-rescuer token 3. Connect with mismatched rescuer ID 4. Connect with valid rescuer token and matching IDSteps 1–3 close with code 1008; step 4 connects successfullyP0CriticalPytest
TC-248Security/auth/exists rate-limited1. Send 100 req/minRate limit appliedP0CriticalPytest
TC-249SecurityServer Host Override not in production buildProd build1. Open drawerURL override field absentP0CriticalMaestro
TC-250SecurityJWT uses environment secretProduction1. Check envJWT_SECRET_KEY set; no hardcoded fallbackP0CriticalPytest

19. Guest Migration

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-260Guest MigrationGuest can initiate authenticate flowGuest, has conversations1. Settings → AuthenticateRegistration flow shownP0CriticalMaestro
TC-261Guest MigrationMessages re-encrypted with new auth keysGuest with messages1. Complete authenticateAll messages re-encrypted with new ECDH keysP0CriticalJest
TC-262Guest MigrationskipEncryptedMessageUpdatesOnNextSync calledMigration complete1. Complete authenticateNext sync skips re-upload of migrated messagesP0CriticalJest
TC-263Guest MigrationConversations preserved after migrationConversations exist1. Complete authenticateAll conversations and messages visible after loginP0CriticalMaestro

20. App Lifecycle

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-270App LifecycleForeground service stops on layout cleanupApp running in background1. Log outForeground service stopsP0CriticalJest
TC-271App LifecycleIP change updates in-memory configWiFi changes1. Switch networksNetworkConfig.ipAddress updates and transports rebind after debounceP0CriticalJest
TC-272App LifecycleDebounced IP callback fires onceMultiple rapid IP events1. Simulate rapid eventsCallback fires once after debounceP1HighJest
TC-273App LifecycleSafe-area insets on all screensPhysical notched device1. Navigate all screens on a device with display cutoutNo content clipped behind notch or home barP1HighMANUAL (physical notched device)
TC-274App LifecycleDark mode uses theme colors throughoutDark mode enabled1. Enable dark mode 2. Navigate all screensNo hardcoded white/black colors visible; all colors from themeP1HighMANUAL (physical device)
TC-275App LifecycleSensitive data in secure-store onlyRooted or ADB-enabled device1. ADB pull app data directory 2. Inspect filesTokens, keys, config only in expo-secure-store; not in AsyncStorage or plaintext filesP0CriticalMANUAL (rooted/ADB device)
TC-276App LifecyclePermissions: distinct UI for not-asked/denied/grantedFresh install, real device1. Trigger camera, mic, location, notification permission flows 2. Verify each stateDistinct UI for not-asked vs denied vs granted; no state collapsedP0CriticalMANUAL (physical device, fresh install)

21. Real Device / Real Network (Multi-Peer)

All cases require two physical devices (Device A and Device B) unless noted. LAN cases also require both devices on the same WiFi network. Classify as MANUAL until a scripted mock-peer harness is documented and available.

IDFeatureScenarioPreconditionsStepsExpected ResultPrioritySeverityAutomate
TC-280P2P Messaging (LAN)End-to-end message delivery over TCPBoth devices in LAN mode; peer discovered on Device B1. Device A opens chat with Device B 2. Device A types and sends a messageMessage appears on Device B within 3 s; Device A status → DELIVEREDP0CriticalMANUAL (2-device rig)
TC-281P2P Messaging (Server)End-to-end message delivery via server relayBoth devices authenticated in server mode; connection established1. Device A sends message to Device BMessage appears on Device B; Device A status → DELIVEREDP0CriticalMANUAL (2-device rig)
TC-282P2P ReconnectQueued message delivered after peer reconnectsDevice A and B previously connected; Device B goes offline1. Device A sends message while Device B is offline 2. Device B reconnects to networkMessage delivered to Device B; Device A status → DELIVEREDP0CriticalMANUAL (2-device rig)
TC-283Audio CallEnd-to-end audio call between two physical devicesBoth devices authenticated; mic permission granted on both1. Device A initiates audio call to Device B 2. Device B taps AcceptCall connects; both parties hear each other; call timer incrementsP0CriticalMANUAL (2-device rig)
TC-284Video CallEnd-to-end video call between two physical devicesBoth devices authenticated; mic and camera permission granted on both1. Device A initiates video call to Device B 2. Device B taps AcceptCall connects; video streams in both directions; no freeze within first 30 sP0CriticalMANUAL (2-device rig)
TC-285Call ReconnectICE reconnects after real network disruption mid-callAudio call active between Device A and Device B1. Disable WiFi on Device A for 5 s 2. Re-enable WiFi"Reconnecting…" overlay appears on Device A; call resumes and overlay dismisses after ICE re-negotiationP0CriticalMANUAL (2-device rig + real network)
TC-286Public ChatBroadcast message reaches all connected usersTwo authenticated users; both on Public Chat screen in server mode1. Device A sends a public messageMessage appears on Device B in real timeP0CriticalMANUAL (2-device rig)
TC-287Simultaneous CallTie-breaker resolves when both devices call each other at the same instantBoth devices authenticated and mutually connected1. Device A and Device B both tap Call on each other simultaneouslyExactly one side is designated caller; exactly one side is callee; one call proceeds cleanly; no stuck or double-call stateP0CriticalMANUAL (2-device rig)
TC-288Call RejectionBoth parties reject simultaneously with no stuck stateDevice A calling Device B; incoming screen shown on Device B1. Device A and Device B both tap Reject at the same momentCall ends cleanly on both devices; both return to Chats; no zombie call stateP1HighMANUAL (2-device rig)
TC-289Guest MigrationPeer sees continuity after guest authenticatesGuest Device A has an existing conversation with Device B1. Device A completes the Authenticate flow (TC-260) 2. Device A sends a new message to Device BDevice B receives the message; contact identity matches the pre-migration guestP0CriticalMANUAL (2-device rig)