Skip to main content

GPS Location — Testing

Strategy

LayerToolingScope
UnitJest + React Native Testing LibraryuseGpsStreaming lifecycle, useLatestLocations state updates, permission handling
Integrationpytest + HTTPX + WebSocket test clientgps.py WS handlers, UserLocation INSERT, broadcast, REST endpoints
E2EMaestro (simulator + mock GPS)Stream location → map marker appears; rescuer views history

Coverage Targets

AreaTarget
gps.py WebSocket handlers90%+
REST endpoint scoping / role check100%
useGpsStreaming lifecycle100%
Permission denied path100%
Broadcast fan-out logic90%+
Overall GPS feature coverage≥ 80%

Mocking Rules

  • expo-location — mock requestForegroundPermissionsAsync and watchPositionAsync; never call real GPS API.
  • WebSocket (mobile) — use a mock WebSocket class; intercept send calls.
  • Server DB — pytest fixtures with an async SQLite in-memory database.
  • Server WS clients — use FastAPI TestClient with websocket_connect() context manager.
  • Time — use Jest fake timers for reconnect back-off; use freezegun for server-side timestamp assertions.

Test Cases

Server — WebSocket Streaming (pytest)

ScenarioExpected result
User connects to WS /gps/ws/{user_id} with valid JWTConnection accepted; user added to streaming_connections
User sends { "lat": 14.5995, "lng": 120.9842 }Row inserted in user_locations with correct user_id and timestamp
User sends malformed frame (missing lng)Connection receives error frame; no DB write
User disconnectsRemoved from streaming_connections; no error
Rescuer connects to monitor WS with valid rescuer JWTConnection accepted; added to monitor_connections
Non-rescuer connects to monitor WSConnection rejected with 403
Streamer sends frame while rescuer monitor is connectedRescuer monitor receives { user_id, lat, lng, timestamp } within same request cycle
Multiple rescuer monitors connectedAll monitors receive the broadcast frame
Rescuer monitor disconnects mid-broadcastRemaining monitors still receive frame; no server crash

Server — REST Endpoints (pytest)

ScenarioExpected result
GET /gps/latest as rescuerReturns array with latest location per user; one entry per distinct user
GET /gps/latest as regular userReturns 403
GET /gps/latest with no locations in DBReturns empty array []
GET /gps/history/{user_id} as rescuerReturns up to 50 rows ordered newest first
GET /gps/history/{user_id} with 60 rows in DBReturns exactly 50 rows
GET /gps/history/{user_id} as regular userReturns 403
GET /gps/history/{unknown_user_id}Returns empty array []

Mobile — useGpsStreaming (Jest)

ScenarioExpected result
Hook mounts with location permission grantedwatchPositionAsync called; WebSocket send called on each position update
Hook unmountsWebSocket close() called; locationSub.remove() called
Permission denied on mountpermissionDenied state set to true; WebSocket never opened
WebSocket closes unexpectedlyReconnect attempted after 3 s; exponential back-off applied
Component unmounts before reconnect firesReconnect timer cleared; no further connection attempts
GPS update fires while WS is not yet openFrame queued or skipped; no crash

Mobile — useLatestLocations (Jest)

ScenarioExpected result
Hook mounts as rescuerGET /gps/latest fetched; map state populated
Monitor WS receives location framelocations[user_id] updated with new lat/lng
Hook unmountsMonitor WS closed; no memory leak
Non-rescuer userHook not instantiated; GET /gps/latest never called
Monitor WS unavailableFalls back to polling GET /gps/latest every 10 s

Integration — Full Location Frame Cycle (pytest)

ScenarioExpected result
User streams 3 frames in sequence3 rows inserted in user_locations with increasing timestamps
Rescuer monitor open during streamingReceives all 3 frames in order
GET /gps/latest after 3 framesReturns 1 row per user with the latest timestamp
GET /gps/history after 3 framesReturns all 3 rows ordered newest first

Test File Locations

server/
tests/
test_gps_ws.py
test_gps_rest.py

mobile-app/sapot-mobile-app/
src/
features/gps/
__tests__/
useGpsStreaming.test.ts
useLatestLocations.test.ts