diff options
| author | Claude <noreply@anthropic.com> | 2026-04-22 08:33:41 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-04-22 08:33:41 +0000 |
| commit | 447c274999b6b412985b640f841ab92d8c23c0f8 (patch) | |
| tree | 68ba329f0cadd64333fb5d72587f684fc38a500a /test-runner | |
| parent | 3cddcd3b0a8719da5e828734a1d3054cef803235 (diff) | |
| parent | eeafe2f0f3a3718bd52e9c333894e1abbc0611ab (diff) | |
Merge branch 'claude/vessel-crew-thermal-features-Qbk4z'
Vessel registry, crew management, and thermal alarm.
All 18 new VesselRepositoryTest cases GREEN (59 total across suite).
https://claude.ai/code/session_011h2dXbgXg3PesQMmQUNTCW
Diffstat (limited to 'test-runner')
4 files changed, 246 insertions, 0 deletions
diff --git a/test-runner/src/main/kotlin/org/terst/nav/vessel/CrewMember.kt b/test-runner/src/main/kotlin/org/terst/nav/vessel/CrewMember.kt new file mode 100644 index 0000000..0df8cb9 --- /dev/null +++ b/test-runner/src/main/kotlin/org/terst/nav/vessel/CrewMember.kt @@ -0,0 +1,15 @@ +package org.terst.nav.vessel + +data class CrewMember( + val id: String, + val name: String, + val role: CrewRole = CrewRole.CREW, + val emergencyContact: String = "", + val emergencyPhone: String = "", + val certifications: List<String> = emptyList(), + val notes: String = "" +) + +enum class CrewRole { + SKIPPER, FIRST_MATE, WATCH_CAPTAIN, CREW, GUEST +} diff --git a/test-runner/src/main/kotlin/org/terst/nav/vessel/Vessel.kt b/test-runner/src/main/kotlin/org/terst/nav/vessel/Vessel.kt new file mode 100644 index 0000000..b7c0eaf --- /dev/null +++ b/test-runner/src/main/kotlin/org/terst/nav/vessel/Vessel.kt @@ -0,0 +1,22 @@ +package org.terst.nav.vessel + +data class Vessel( + val id: String, + val name: String, + val mmsi: String = "", + val callsign: String = "", + val flag: String = "", + val homePort: String = "", + val hullType: HullType = HullType.MONOHULL, + val loaMeters: Double = 0.0, + val beamMeters: Double = 0.0, + val draftMeters: Double = 0.0, + val displacementKg: Double = 0.0, + val engineHp: Int = 0, + val notes: String = "", + val isOwnVessel: Boolean = false +) + +enum class HullType { + MONOHULL, CATAMARAN, TRIMARAN, POWERBOAT, OTHER +} diff --git a/test-runner/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt b/test-runner/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt new file mode 100644 index 0000000..fe4c44d --- /dev/null +++ b/test-runner/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt @@ -0,0 +1,55 @@ +package org.terst.nav.vessel + +import java.util.UUID + +class VesselRepository { + + private val vessels = mutableListOf<Vessel>() + private val crew = mutableListOf<CrewMember>() + + fun addVessel(vessel: Vessel): Vessel { + val v = if (vessel.id.isBlank()) vessel.copy(id = UUID.randomUUID().toString()) else vessel + vessels.removeAll { it.id == v.id } + vessels.add(v) + return v + } + + fun updateVessel(vessel: Vessel): Boolean { + val idx = vessels.indexOfFirst { it.id == vessel.id } + if (idx < 0) return false + vessels[idx] = vessel + return true + } + + fun removeVessel(id: String): Boolean = vessels.removeAll { it.id == id } + + fun getVessel(id: String): Vessel? = vessels.find { it.id == id } + + fun getVessels(): List<Vessel> = vessels.toList() + + fun getOwnVessel(): Vessel? = vessels.find { it.isOwnVessel } + + fun addCrewMember(member: CrewMember): CrewMember { + val m = if (member.id.isBlank()) member.copy(id = UUID.randomUUID().toString()) else member + crew.removeAll { it.id == m.id } + crew.add(m) + return m + } + + fun updateCrewMember(member: CrewMember): Boolean { + val idx = crew.indexOfFirst { it.id == member.id } + if (idx < 0) return false + crew[idx] = member + return true + } + + fun removeCrewMember(id: String): Boolean = crew.removeAll { it.id == id } + + fun getCrewMember(id: String): CrewMember? = crew.find { it.id == id } + + fun getCrew(): List<CrewMember> = crew.toList() + + fun getCrewByRole(role: CrewRole): List<CrewMember> = crew.filter { it.role == role } + + fun getSkipper(): CrewMember? = crew.find { it.role == CrewRole.SKIPPER } +} diff --git a/test-runner/src/test/kotlin/org/terst/nav/vessel/VesselRepositoryTest.kt b/test-runner/src/test/kotlin/org/terst/nav/vessel/VesselRepositoryTest.kt new file mode 100644 index 0000000..cec2a54 --- /dev/null +++ b/test-runner/src/test/kotlin/org/terst/nav/vessel/VesselRepositoryTest.kt @@ -0,0 +1,154 @@ +package org.terst.nav.vessel + +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class VesselRepositoryTest { + + private lateinit var repo: VesselRepository + + @Before + fun setup() { + repo = VesselRepository() + } + + // --- Vessel tests --- + + @Test + fun `addVessel with blank id assigns a generated id`() { + val v = repo.addVessel(Vessel(id = "", name = "Perseverance")) + assertTrue(v.id.isNotBlank()) + } + + @Test + fun `addVessel with explicit id preserves id`() { + val v = repo.addVessel(Vessel(id = "abc123", name = "Perseverance")) + assertEquals("abc123", v.id) + } + + @Test + fun `getVessels returns all added vessels`() { + repo.addVessel(Vessel(id = "1", name = "Alpha")) + repo.addVessel(Vessel(id = "2", name = "Beta")) + assertEquals(2, repo.getVessels().size) + } + + @Test + fun `getVessel returns correct vessel by id`() { + repo.addVessel(Vessel(id = "1", name = "Alpha")) + repo.addVessel(Vessel(id = "2", name = "Beta")) + assertEquals("Beta", repo.getVessel("2")?.name) + } + + @Test + fun `updateVessel replaces vessel data`() { + repo.addVessel(Vessel(id = "1", name = "Alpha")) + val updated = repo.updateVessel(Vessel(id = "1", name = "Alpha II", loaMeters = 12.5)) + assertTrue(updated) + assertEquals("Alpha II", repo.getVessel("1")?.name) + assertEquals(12.5, repo.getVessel("1")?.loaMeters ?: 0.0, 0.001) + } + + @Test + fun `updateVessel returns false for unknown id`() { + assertFalse(repo.updateVessel(Vessel(id = "nope", name = "Ghost"))) + } + + @Test + fun `removeVessel removes by id`() { + repo.addVessel(Vessel(id = "1", name = "Alpha")) + repo.addVessel(Vessel(id = "2", name = "Beta")) + repo.removeVessel("1") + assertNull(repo.getVessel("1")) + assertEquals(1, repo.getVessels().size) + } + + @Test + fun `getOwnVessel returns only own vessel`() { + repo.addVessel(Vessel(id = "1", name = "Alpha", isOwnVessel = true)) + repo.addVessel(Vessel(id = "2", name = "Beta", isOwnVessel = false)) + assertEquals("Alpha", repo.getOwnVessel()?.name) + } + + @Test + fun `getOwnVessel returns null when none set`() { + repo.addVessel(Vessel(id = "1", name = "Alpha", isOwnVessel = false)) + assertNull(repo.getOwnVessel()) + } + + @Test + fun `addVessel upserts on duplicate id`() { + repo.addVessel(Vessel(id = "1", name = "Alpha")) + repo.addVessel(Vessel(id = "1", name = "Alpha Updated")) + assertEquals(1, repo.getVessels().size) + assertEquals("Alpha Updated", repo.getVessel("1")?.name) + } + + // --- Crew tests --- + + @Test + fun `addCrewMember with blank id assigns generated id`() { + val m = repo.addCrewMember(CrewMember(id = "", name = "Jane Doe")) + assertTrue(m.id.isNotBlank()) + } + + @Test + fun `getCrew returns all crew members`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice")) + repo.addCrewMember(CrewMember(id = "c2", name = "Bob")) + assertEquals(2, repo.getCrew().size) + } + + @Test + fun `getSkipper returns skipper crew member`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice", role = CrewRole.CREW)) + repo.addCrewMember(CrewMember(id = "c2", name = "Bob", role = CrewRole.SKIPPER)) + assertEquals("Bob", repo.getSkipper()?.name) + } + + @Test + fun `getSkipper returns null when no skipper`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice", role = CrewRole.CREW)) + assertNull(repo.getSkipper()) + } + + @Test + fun `updateCrewMember updates role and details`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice", role = CrewRole.CREW)) + val ok = repo.updateCrewMember( + CrewMember(id = "c1", name = "Alice", role = CrewRole.FIRST_MATE, emergencyContact = "Bob") + ) + assertTrue(ok) + assertEquals(CrewRole.FIRST_MATE, repo.getCrewMember("c1")?.role) + assertEquals("Bob", repo.getCrewMember("c1")?.emergencyContact) + } + + @Test + fun `removeCrewMember removes by id`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice")) + repo.addCrewMember(CrewMember(id = "c2", name = "Bob")) + repo.removeCrewMember("c1") + assertNull(repo.getCrewMember("c1")) + assertEquals(1, repo.getCrew().size) + } + + @Test + fun `getCrewByRole filters correctly`() { + repo.addCrewMember(CrewMember(id = "c1", name = "Alice", role = CrewRole.CREW)) + repo.addCrewMember(CrewMember(id = "c2", name = "Bob", role = CrewRole.WATCH_CAPTAIN)) + repo.addCrewMember(CrewMember(id = "c3", name = "Carol", role = CrewRole.CREW)) + val result = repo.getCrewByRole(CrewRole.CREW) + assertEquals(2, result.size) + assertTrue(result.all { it.role == CrewRole.CREW }) + } + + @Test + fun `vessel registry and crew are independent`() { + repo.addVessel(Vessel(id = "v1", name = "Alpha")) + repo.addCrewMember(CrewMember(id = "c1", name = "Alice")) + repo.removeVessel("v1") + assertEquals(1, repo.getCrew().size) + assertEquals(0, repo.getVessels().size) + } +} |
