summaryrefslogtreecommitdiff
path: root/test-runner/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'test-runner/src/test')
-rw-r--r--test-runner/src/test/kotlin/org/terst/nav/gps/GpsPositionTest.kt33
-rw-r--r--test-runner/src/test/kotlin/org/terst/nav/gps/GpsProviderTest.kt133
-rw-r--r--test-runner/src/test/kotlin/org/terst/nav/nmea/NmeaParserTest.kt103
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))
+ }
+}