diff options
Diffstat (limited to 'test-runner/src/test')
3 files changed, 269 insertions, 0 deletions
diff --git a/test-runner/src/test/kotlin/org/terst/nav/gps/GpsPositionTest.kt b/test-runner/src/test/kotlin/org/terst/nav/gps/GpsPositionTest.kt new file mode 100644 index 0000000..52e8348 --- /dev/null +++ b/test-runner/src/test/kotlin/org/terst/nav/gps/GpsPositionTest.kt @@ -0,0 +1,33 @@ +package org.terst.nav.gps + +import org.junit.Assert.* +import org.junit.Test + +class GpsPositionTest { + + @Test + fun `GpsPosition holds correct values`() { + val pos = GpsPosition( + latitude = 41.5, + longitude = -71.0, + sog = 5.2, + cog = 180.0, + timestampMs = 1_000L + ) + assertEquals(41.5, pos.latitude, 0.0) + assertEquals(-71.0, pos.longitude, 0.0) + assertEquals(5.2, pos.sog, 0.0) + assertEquals(180.0, pos.cog, 0.0) + assertEquals(1_000L, pos.timestampMs) + } + + @Test + fun `GpsPosition equality works as expected for data class`() { + val pos1 = GpsPosition(41.5, -71.0, 5.2, 180.0, 1_000L) + val pos2 = GpsPosition(41.5, -71.0, 5.2, 180.0, 1_000L) + val pos3 = GpsPosition(42.0, -70.0, 3.0, 90.0, 2_000L) + + assertEquals(pos1, pos2) + assertNotEquals(pos1, pos3) + } +} diff --git a/test-runner/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt b/test-runner/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt new file mode 100644 index 0000000..4a03387 --- /dev/null +++ b/test-runner/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt @@ -0,0 +1,133 @@ +package org.terst.nav.gps + +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +// ── Fake implementation (no Android dependencies) ──────────────────────────── + +class FakeGpsProvider : GpsProvider { + var currentPosition: GpsPosition? = null + private val listeners = mutableListOf<GpsListener>() + var started = false + + override fun start() { started = true } + override fun stop() { started = false } + override val position: GpsPosition? get() = currentPosition + override fun addListener(listener: GpsListener) { listeners.add(listener) } + override fun removeListener(listener: GpsListener) { listeners.remove(listener) } + + fun simulatePosition(pos: GpsPosition) { + currentPosition = pos + listeners.forEach { it.onPositionUpdate(pos) } + } + + fun simulateFixLost() { listeners.forEach { it.onFixLost() } } +} + +// ── Test helpers ───────────────────────────────────────────────────────────── + +private fun makePosition(lat: Double = 41.0, lon: Double = -71.0, sog: Double = 5.0) = + GpsPosition(lat, lon, sog, cog = 180.0, timestampMs = 1_000L) + +private class RecordingListener : GpsListener { + val positions = mutableListOf<GpsPosition>() + var fixLostCount = 0 + + override fun onPositionUpdate(position: GpsPosition) { positions.add(position) } + override fun onFixLost() { fixLostCount++ } +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +class GpsProviderTest { + + private lateinit var provider: FakeGpsProvider + + @Before + fun setUp() { + provider = FakeGpsProvider() + } + + @Test + fun `start sets started to true`() { + provider.start() + assertTrue(provider.started) + } + + @Test + fun `stop sets started to false`() { + provider.start() + provider.stop() + assertFalse(provider.started) + } + + @Test + fun `listener receives position update`() { + val listener = RecordingListener() + provider.addListener(listener) + val pos = makePosition() + provider.simulatePosition(pos) + assertEquals(1, listener.positions.size) + assertEquals(pos, listener.positions[0]) + } + + @Test + fun `listener notified of fix lost`() { + val listener = RecordingListener() + provider.addListener(listener) + provider.simulateFixLost() + assertEquals(1, listener.fixLostCount) + } + + @Test + fun `multiple listeners all receive position update`() { + val l1 = RecordingListener() + val l2 = RecordingListener() + val l3 = RecordingListener() + provider.addListener(l1) + provider.addListener(l2) + provider.addListener(l3) + provider.simulatePosition(makePosition()) + assertEquals(1, l1.positions.size) + assertEquals(1, l2.positions.size) + assertEquals(1, l3.positions.size) + } + + @Test + fun `multiple listeners all notified of fix lost`() { + val l1 = RecordingListener() + val l2 = RecordingListener() + provider.addListener(l1) + provider.addListener(l2) + provider.simulateFixLost() + assertEquals(1, l1.fixLostCount) + assertEquals(1, l2.fixLostCount) + } + + @Test + fun `removing listener stops notifications`() { + val listener = RecordingListener() + provider.addListener(listener) + provider.removeListener(listener) + provider.simulatePosition(makePosition()) + provider.simulateFixLost() + assertEquals(0, listener.positions.size) + assertEquals(0, listener.fixLostCount) + } + + @Test + fun `position property reflects last simulated position`() { + assertNull(provider.position) + val pos = makePosition(lat = 42.5, lon = -70.0) + provider.simulatePosition(pos) + assertEquals(pos, provider.position) + } + + @Test + fun `SOG conversion sanity check - 1 mps is approximately 1_94384 knots`() { + // 1 m/s * 1.94384 = 1.94384 knots — validate constant used in DeviceGpsProvider + val knots = 1.0 * 1.94384 + assertEquals(1.94384, knots, 0.00001) + } +} diff --git a/test-runner/src/test/kotlin/org/terst/nav/nmea/NmeaParserTest.kt b/test-runner/src/test/kotlin/org/terst/nav/nmea/NmeaParserTest.kt new file mode 100644 index 0000000..e43b7ab --- /dev/null +++ b/test-runner/src/test/kotlin/org/terst/nav/nmea/NmeaParserTest.kt @@ -0,0 +1,103 @@ +package org.terst.nav.nmea + +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class NmeaParserTest { + + private lateinit var parser: NmeaParser + + @Before + fun setUp() { + parser = NmeaParser() + } + + // $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A + // lat: 48 + 7.038/60 = 48.1173°N, lon: 11 + 31.000/60 = 11.51667°E + // SOG 22.4 kn, COG 84.4° + + @Test + fun `valid RMC sentence parses latitude and longitude`() { + val sentence = "\$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertEquals(48.1173, pos!!.latitude, 0.0001) + assertEquals(11.51667, pos.longitude, 0.0001) + } + + @Test + fun `valid RMC sentence parses SOG and COG`() { + val sentence = "\$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A" + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertEquals(22.4, pos!!.sog, 0.001) + assertEquals(84.4, pos.cog, 0.001) + } + + @Test + fun `void status V returns null`() { + val sentence = "\$GPRMC,123519,V,4807.038,N,01131.000,E,,,230394,003.1,W" + assertNull(parser.parseRmc(sentence)) + } + + @Test + fun `malformed sentence with too few fields returns null`() { + assertNull(parser.parseRmc("\$GPRMC,123519,A")) + } + + @Test + fun `empty string returns null`() { + assertNull(parser.parseRmc("")) + } + + @Test + fun `non-NMEA string returns null`() { + assertNull(parser.parseRmc("NOT_NMEA_DATA")) + } + + @Test + fun `south latitude is negative`() { + // lat: -(42 + 50.5589/60) = -42.84265 + val sentence = "\$GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,," + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertTrue("South latitude must be negative", pos!!.latitude < 0) + assertEquals(-42.84265, pos.latitude, 0.0001) + } + + @Test + fun `west longitude is negative`() { + // lon: -(11 + 31.000/60) = -11.51667 + val sentence = "\$GPRMC,123519,A,4807.038,N,01131.000,W,022.4,084.4,230394,003.1,E" + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertTrue("West longitude must be negative", pos!!.longitude < 0) + assertEquals(-11.51667, pos.longitude, 0.0001) + } + + @Test + fun `SOG and COG parse with decimal precision`() { + // lon: -(118 + 1.5678/60) = -118.02613, lat: 33 + 52.1234/60 = 33.86872 + val sentence = "\$GPRMC,093456,A,3352.1234,N,11801.5678,W,12.345,270.5,140326,," + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertEquals(12.345, pos!!.sog, 0.0001) + assertEquals(270.5, pos.cog, 0.0001) + } + + @Test + fun `empty SOG and COG fields default to zero`() { + val sentence = "\$GPRMC,123519,A,4807.038,N,01131.000,E,,,230394,003.1,W" + val pos = parser.parseRmc(sentence) + assertNotNull(pos) + assertEquals(0.0, pos!!.sog, 0.001) + assertEquals(0.0, pos.cog, 0.001) + } + + @Test + fun `non-RMC sentence returns null`() { + val sentence = "\$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,," + assertNull(parser.parseRmc(sentence)) + } +} |
