ModelTest.kt
⎇
Raw
1package de.upapp
2
3import org.json.JSONObject
4import org.junit.Assert.assertEquals
5import org.junit.Assert.assertFalse
6import org.junit.Assert.assertNull
7import org.junit.Assert.assertTrue
8import org.junit.Test
9import java.time.DayOfWeek.FRIDAY
10import java.time.DayOfWeek.MONDAY
11import java.time.DayOfWeek.SUNDAY
12import java.time.DayOfWeek.THURSDAY
13import java.time.DayOfWeek.TUESDAY
14import java.time.LocalDate
15import java.time.LocalTime
16import java.time.ZoneId
17import java.time.ZonedDateTime
18
19// Fixtures from the upXlink test suite.
20class 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