diff options
Diffstat (limited to 'android-app/app/src/test/kotlin/com/example')
| -rw-r--r-- | android-app/app/src/test/kotlin/com/example/androidapp/gps/LocationServiceTest.kt | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/android-app/app/src/test/kotlin/com/example/androidapp/gps/LocationServiceTest.kt b/android-app/app/src/test/kotlin/com/example/androidapp/gps/LocationServiceTest.kt index d9192c6..237004b 100644 --- a/android-app/app/src/test/kotlin/com/example/androidapp/gps/LocationServiceTest.kt +++ b/android-app/app/src/test/kotlin/com/example/androidapp/gps/LocationServiceTest.kt @@ -114,4 +114,114 @@ class LocationServiceTest { assertEquals(data, svc.latestSensor.first()) } + + // ── GPS sensor fusion ───────────────────────────────────────────────────── + + private fun fusionService( + nmeaStalenessThresholdMs: Long = 5_000L, + clockMs: () -> Long = System::currentTimeMillis + ) = LocationService( + nmeaStalenessThresholdMs = nmeaStalenessThresholdMs, + clockMs = clockMs + ) + + private fun pos(lat: Double, lon: Double, timestampMs: Long) = + GpsPosition(lat, lon, sog = 0.0, cog = 0.0, timestampMs = timestampMs) + + @Test + fun noGpsData_bestPositionNullAndSourceNone() = runBlocking { + val svc = fusionService() + assertNull(svc.bestPosition.first()) + assertEquals(GpsSource.NONE, svc.activeGpsSource.first()) + } + + @Test + fun freshNmea_preferredOverAndroid() = runBlocking { + val now = 10_000L + val svc = fusionService(nmeaStalenessThresholdMs = 5_000L, clockMs = { now }) + + val nmeaFix = pos(41.0, -71.0, now) + val androidFix = pos(42.0, -72.0, now - 1_000L) + + svc.updateAndroidGps(androidFix) + svc.updateNmeaGps(nmeaFix) + + assertEquals(GpsSource.NMEA, svc.activeGpsSource.first()) + assertEquals(nmeaFix, svc.bestPosition.first()) + } + + @Test + fun staleNmea_androidFallback() = runBlocking { + val nmeaTime = 0L + val now = 10_000L // 10 s later — NMEA is stale (threshold 5 s) + val svc = fusionService(nmeaStalenessThresholdMs = 5_000L, clockMs = { now }) + + val nmeaFix = pos(41.0, -71.0, nmeaTime) + val androidFix = pos(42.0, -72.0, now) + + svc.updateNmeaGps(nmeaFix) + svc.updateAndroidGps(androidFix) + + assertEquals(GpsSource.ANDROID, svc.activeGpsSource.first()) + assertEquals(androidFix, svc.bestPosition.first()) + } + + @Test + fun onlyNmeaAvailable_usedEvenWhenStale() = runBlocking { + val now = 60_000L // 60 s after fix — very stale + val svc = fusionService(nmeaStalenessThresholdMs = 5_000L, clockMs = { now }) + + val nmeaFix = pos(41.0, -71.0, 0L) + svc.updateNmeaGps(nmeaFix) + + assertEquals(GpsSource.NMEA, svc.activeGpsSource.first()) + assertEquals(nmeaFix, svc.bestPosition.first()) + } + + @Test + fun onlyAndroidAvailable_isUsed() = runBlocking { + val svc = fusionService() + val androidFix = pos(42.0, -72.0, System.currentTimeMillis()) + svc.updateAndroidGps(androidFix) + + assertEquals(GpsSource.ANDROID, svc.activeGpsSource.first()) + assertEquals(androidFix, svc.bestPosition.first()) + } + + @Test + fun nmeaAtExactThreshold_isConsideredFresh() = runBlocking { + val fixTime = 0L + val now = 5_000L // exactly at threshold + val svc = fusionService(nmeaStalenessThresholdMs = 5_000L, clockMs = { now }) + + val nmeaFix = pos(41.0, -71.0, fixTime) + val androidFix = pos(42.0, -72.0, now) + + svc.updateNmeaGps(nmeaFix) + svc.updateAndroidGps(androidFix) + + assertEquals(GpsSource.NMEA, svc.activeGpsSource.first()) + } + + @Test + fun nmeaRecovery_switchesBackFromAndroid() = runBlocking { + var now = 0L + val svc = fusionService(nmeaStalenessThresholdMs = 5_000L, clockMs = { now }) + + // Fresh NMEA + svc.updateNmeaGps(pos(41.0, -71.0, 0L)) + assertEquals(GpsSource.NMEA, svc.activeGpsSource.value) + + // NMEA goes stale; Android takes over + now = 10_000L + val androidFix = pos(42.0, -72.0, 10_000L) + svc.updateAndroidGps(androidFix) + assertEquals(GpsSource.ANDROID, svc.activeGpsSource.value) + + // NMEA recovers with a fresh fix + val freshNmea = pos(41.1, -71.1, 10_000L) + svc.updateNmeaGps(freshNmea) + assertEquals(GpsSource.NMEA, svc.activeGpsSource.value) + assertEquals(freshNmea, svc.bestPosition.value) + } } |
