ModelTest.kt
| 1 | package de.upapp |
| 2 | |
| 3 | import org.json.JSONObject |
| 4 | import org.junit.Assert.assertEquals |
| 5 | import org.junit.Assert.assertFalse |
| 6 | import org.junit.Assert.assertNull |
| 7 | import org.junit.Assert.assertTrue |
| 8 | import org.junit.Test |
| 9 | import java.time.DayOfWeek.FRIDAY |
| 10 | import java.time.DayOfWeek.MONDAY |
| 11 | import java.time.DayOfWeek.SUNDAY |
| 12 | import java.time.DayOfWeek.THURSDAY |
| 13 | import java.time.DayOfWeek.TUESDAY |
| 14 | import java.time.LocalDate |
| 15 | import java.time.LocalTime |
| 16 | import java.time.ZoneId |
| 17 | import java.time.ZonedDateTime |
| 18 | |
| 19 | // Fixtures from the upXlink test suite. |
| 20 | class ModelTest { |
| 21 | private val timer = parseTimer(JSONObject(""" |
| 22 | {"id": "20a560c1", "name": "Timer1", "uri": "/chargingmanager/timers/20a560c1", "state": "idle", |
| 23 | "departureTime": "06:35:00", "profile": {"id": "63cf948c", "name": "Ladelimit 80, HVAC", "uri": "/x"}, |
| 24 | "cyclic": true, "weekdays": ["monday", "tuesday", "thursday", "friday"]} |
| 25 | """)) |
| 26 | |
| 27 | @Test |
| 28 | fun parsesTimer() { |
| 29 | assertEquals(1, timer.index) |
| 30 | assertFalse(timer.active) |
| 31 | assertEquals(setOf(MONDAY, TUESDAY, THURSDAY, FRIDAY), timer.weekdays) |
| 32 | assertEquals(LocalTime.of(6, 35), timer.departureTime) |
| 33 | assertNull(timer.departureDate) |
| 34 | assertEquals("63cf948c", timer.profileId) |
| 35 | } |
| 36 | |
| 37 | @Test |
| 38 | fun parsesProfilesAndOptions() { |
| 39 | val p = parseProfile(JSONObject(""" |
| 40 | {"id": "8269b474", "name": "Ladeort 3", "uri": "/p/8269b474", "operations": ["charge", "climate"], "maxCurrent": 16, |
| 41 | "minLevel": 0, "targetLevel": 100, "temperature": 10.0, "powerProvider": {"id": "c8876782", "name": "", "uri": "/x"}} |
| 42 | """)) |
| 43 | assertEquals(Profile("8269b474", "/p/8269b474", "Ladeort 3", 100, 16, listOf("charge", "climate"), "c8876782"), p) |
| 44 | |
| 45 | val o = parseOptions(JSONObject(""" |
| 46 | {"id": "3900b210", "name": "Optionen", "uri": "/p/3900b210", "operations": ["climateExtSupply", "climate"], |
| 47 | "maxCurrent": 32, "minLevel": 100, "targetLevel": 0, "temperature": 30.0} |
| 48 | """)) |
| 49 | assertTrue(o.climateExtSupply) |
| 50 | assertEquals(100, o.minLevel) |
| 51 | assertEquals(30.0, o.temperature, 0.0) |
| 52 | } |
| 53 | |
| 54 | @Test |
| 55 | fun parsesProviderAndCarData() { |
| 56 | val p = parseProvider(JSONObject(""" |
| 57 | {"id": "c8876782", "uri": "/pr/c8876782", "cyclic": false, "preferredTimeStart": "22:00:00", "preferredTimeEnd": "05:00:00", |
| 58 | "weekdays": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]} |
| 59 | """)) |
| 60 | assertEquals(7, p.weekdays.size) |
| 61 | assertEquals(LocalTime.of(22, 0), p.start) |
| 62 | assertEquals(84.0, parseBattery(JSONObject("""{"name": "BEV battery", "soc": 84.0}""")).soc!!, 0.0) |
| 63 | assertNull(parseBattery(JSONObject("""{"soc": ""}""")).soc) |
| 64 | assertEquals("MATSRSECBFQTIXLFYF", parseInfo(JSONObject("""{"vehicleIdenticationNumber": "MATSRSECBFQTIXLFYF"}""")).vin) |
| 65 | assertEquals(LocalDate.of(2025, 11, 1), parseTimer(JSONObject( |
| 66 | """{"id": "a", "name": "Timer2", "uri": "/t", "state": "scheduled", "departureTime": "07:00:00", "departureDate": "01 Nov 2025"}""" |
| 67 | )).departureDate) |
| 68 | } |
| 69 | |
| 70 | @Test |
| 71 | fun shiftsOnlyTheTime() { |
| 72 | val late = timer.copy(departureTime = LocalTime.of(23, 30), weekdays = setOf(FRIDAY, SUNDAY), departureDate = LocalDate.of(2025, 12, 31)) |
| 73 | val local = late.shifted(2 * 3600) |
| 74 | assertEquals(LocalTime.of(1, 30), local.departureTime) |
| 75 | assertEquals(late.weekdays, local.weekdays) |
| 76 | assertEquals(late.departureDate, local.departureDate) |
| 77 | assertEquals(late, local.shifted(-2 * 3600)) |
| 78 | } |
| 79 | |
| 80 | @Test |
| 81 | fun allows32AmpsOnlyForEUp300() { |
| 82 | assertEquals(16, maxCurrents("WVWZZZAAZKD000001").last()) |
| 83 | assertEquals(32, maxCurrents("WVWZZZAAZLD000001").last()) |
| 84 | assertEquals(32, maxCurrents("TMBZZZAAZKD000001").last()) |
| 85 | } |
| 86 | |
| 87 | @Test |
| 88 | fun findsNextDeparture() { |
| 89 | val berlin = ZoneId.of("Europe/Berlin") |
| 90 | // Wednesday 2025-11-12 12:00. Timer runs Mon, Tue, Thu, Fri at 06:35. |
| 91 | val now = ZonedDateTime.of(2025, 11, 12, 12, 0, 0, 0, berlin) |
| 92 | assertNull(nextDeparture(listOf(timer), now)) |
| 93 | val (_, at) = nextDeparture(listOf(timer.copy(state = TIMER_ON)), now)!! |
| 94 | assertEquals(ZonedDateTime.of(2025, 11, 13, 6, 35, 0, 0, berlin), at) |
| 95 | } |
| 96 | |
| 97 | @Test |
| 98 | fun pairsUnlinkedProvidersByPosition() { |
| 99 | fun profile(id: String) = JSONObject("""{"id": "$id", "uri": "/p/$id", "name": "$id", "operations": [], "maxCurrent": 16, "minLevel": 0, "targetLevel": 80, "temperature": 21.0}""") |
| 100 | fun provider(id: String) = JSONObject("""{"id": "$id", "uri": "/pr/$id", "cyclic": false, "preferredTimeStart": "00:00:00", "preferredTimeEnd": "00:00:00"}""") |
| 101 | val cm = chargingManagerOf(listOf(profile("options"), profile("home")), emptyList(), listOf(provider("a"), provider("b"))) |
| 102 | assertEquals("a", cm.options!!.providerId) |
| 103 | assertEquals("b", cm.profiles.single().providerId) |
| 104 | assertNull(chargingManagerOf(listOf(profile("options"), profile("home")), emptyList(), listOf(provider("a"))).profiles.single().providerId) |
| 105 | } |
| 106 | |
| 107 | @Test |
| 108 | fun matchesIgnoresExtraKeysAndNumberTypes() { |
| 109 | val actual = JSONObject("""{"temperature": 24, "profile": {"id": "a", "name": "Home"}, "weekdays": ["monday"]}""") |
| 110 | assertTrue(matches(JSONObject("""{"temperature": 24.0, "profile": {"id": "a"}, "weekdays": ["monday"]}"""), actual)) |
| 111 | assertFalse(matches(JSONObject("""{"temperature": 25.0}"""), actual)) |
| 112 | assertFalse(matches(JSONObject("""{"weekdays": ["monday", "friday"]}"""), actual)) |
| 113 | assertTrue(matches(JSONObject("""{"profile": "a"}"""), actual)) |
| 114 | assertFalse(matches(JSONObject("""{"profile": "b"}"""), actual)) |
| 115 | } |
| 116 | } |
| 117 |