1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
package org.terst.nav.ais
import org.junit.Assert.*
import org.junit.Test
class CpaAlertTest {
// --- CpaCalculator ---
@Test
fun `head-on convergence produces small CPA and positive TCPA`() {
// Own vessel heading north at 10 kt; target heading south at 10 kt, 1 nm ahead
val (cpa, tcpa) = CpaCalculator.compute(
ownLat = 0.0, ownLon = 0.0, ownSog = 10.0, ownCog = 0.0,
tgtLat = 1.0 / 60.0, tgtLon = 0.0, tgtSog = 10.0, tgtCog = 180.0
)
assertTrue("CPA should be near zero, got $cpa", cpa < 0.1)
assertTrue("TCPA should be positive, got $tcpa", tcpa > 0)
assertTrue("TCPA should be < 5 min, got $tcpa", tcpa < 5.0)
}
@Test
fun `diverging vessels produce negative TCPA`() {
// Own heading north, target heading north ahead — both going same direction with target faster
val (_, tcpa) = CpaCalculator.compute(
ownLat = 0.0, ownLon = 0.0, ownSog = 5.0, ownCog = 0.0,
tgtLat = 1.0 / 60.0, tgtLon = 0.0, tgtSog = 10.0, tgtCog = 0.0
)
assertTrue("TCPA should be negative for diverging vessels, got $tcpa", tcpa < 0)
}
@Test
fun `zero relative velocity returns current distance`() {
// Same velocity — parallel course, same speed
val (cpa, tcpa) = CpaCalculator.compute(
ownLat = 0.0, ownLon = 0.0, ownSog = 5.0, ownCog = 90.0,
tgtLat = 1.0 / 60.0, tgtLon = 0.0, tgtSog = 5.0, tgtCog = 90.0
)
assertTrue("CPA should equal current distance, got $cpa", cpa > 0.9 && cpa < 1.1)
assertEquals(0.0, tcpa, 1e-9)
}
@Test
fun `well-separated crossing vessels produce large CPA`() {
// Perpendicular courses but far apart — should not alert
val (cpa, _) = CpaCalculator.compute(
ownLat = 0.0, ownLon = 0.0, ownSog = 8.0, ownCog = 0.0,
tgtLat = 0.0, tgtLon = 5.0, tgtSog = 8.0, tgtCog = 270.0
)
assertTrue("CPA should be > 2 nm for well-separated vessels, got $cpa", cpa > 2.0)
}
// --- Severity classification ---
@Test
fun `DANGER severity for CPA under 0_5 nm`() {
val alert = CpaAlert(123, "TARGET", 0.3, 10.0, CpaSeverity.DANGER)
assertEquals(CpaSeverity.DANGER, alert.severity)
}
@Test
fun `label formats correctly with vessel name`() {
val alert = CpaAlert(123456789, "STENA IMPERO", 0.8, 12.5, CpaSeverity.WARNING)
val lbl = alert.label()
assertTrue(lbl.contains("0.8"))
assertTrue(lbl.contains("13") || lbl.contains("12")) // rounded min
assertTrue(lbl.contains("STENA"))
}
@Test
fun `label uses MMSI prefix when name is blank`() {
val alert = CpaAlert(987654321, "", 1.5, 20.0, CpaSeverity.CAUTION)
// "MMSI 987654321" is 14 chars; label() takes the first 12 -> "MMSI 9876543"
val lbl = alert.label()
assertTrue("Expected MMSI prefix in label, got: $lbl", lbl.contains("MMSI"))
assertTrue("Expected partial MMSI in label, got: $lbl", lbl.contains("987654"))
}
}
|