test(settings): Add missing DataStore serialization and migration compat tests

Cover round-trip tests for ThemeStyle, ThemeColor, MonitorMode, ScannerMode, AutoConnectCondition, FossUpgrade, and InstantEpochMillisSerializer. Add Moshi decode tests for ThemeStyle, ThemeColor, AutoConnectCondition, FossUpgrade, and FossUpgrade.Reason annotation parity.
This commit is contained in:
darken
2026-03-04 15:23:34 +00:00
committed by Matthias Urhahn
parent a92e364ca6
commit 8ada7b071d
2 changed files with 134 additions and 0 deletions
@@ -9,6 +9,7 @@ import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer import eu.darken.capod.profiles.core.DeviceProfilesContainer
import eu.darken.capod.common.upgrade.core.FossUpgrade
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@@ -16,6 +17,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import testhelpers.BaseTest import testhelpers.BaseTest
import java.time.Instant
/** /**
* Tests that verify @SerialName values match @Json(name=...) values, * Tests that verify @SerialName values match @Json(name=...) values,
@@ -68,6 +70,9 @@ class DataStoreMigrationCompatTest : BaseTest() {
@Test @Test
fun `SerialName matches Json name - PodDevice Model`() = verifyEnumSerialNameParity<PodDevice.Model>() fun `SerialName matches Json name - PodDevice Model`() = verifyEnumSerialNameParity<PodDevice.Model>()
@Test
fun `SerialName matches Json name - FossUpgrade Reason`() = verifyEnumSerialNameParity<FossUpgrade.Reason>()
@Test @Test
fun `Moshi-serialized ThemeMode string is readable by kotlinx`() { fun `Moshi-serialized ThemeMode string is readable by kotlinx`() {
// Moshi stores enums as JSON strings like: "theme.mode.dark" // Moshi stores enums as JSON strings like: "theme.mode.dark"
@@ -103,6 +108,36 @@ class DataStoreMigrationCompatTest : BaseTest() {
} }
} }
@Test
fun `Moshi-serialized ThemeStyle string is readable by kotlinx`() {
val moshiOutput = "\"theme.style.materialyou\""
val result = json.decodeFromString(serializer<ThemeStyle>(), moshiOutput)
result shouldBe ThemeStyle.MATERIAL_YOU
}
@Test
fun `Moshi-serialized ThemeColor string is readable by kotlinx`() {
val moshiOutput = "\"theme.color.green\""
val result = json.decodeFromString(serializer<ThemeColor>(), moshiOutput)
result shouldBe ThemeColor.GREEN
}
@Test
fun `Moshi-serialized AutoConnectCondition string is readable by kotlinx`() {
val moshiOutput = "\"autoconnect.condition.seen\""
val result = json.decodeFromString(serializer<AutoConnectCondition>(), moshiOutput)
result shouldBe AutoConnectCondition.WHEN_SEEN
}
@Test
fun `Moshi-serialized FossUpgrade JSON is readable by kotlinx`() {
// Moshi with JavaInstantAdapter serializes Instant as epoch millis Long
val moshiJson = """{"upgradedAt":1709553600000,"reason":"foss.upgrade.reason.donated"}"""
val result = json.decodeFromString(serializer<FossUpgrade>(), moshiJson)
result.upgradedAt shouldBe Instant.ofEpochMilli(1709553600000)
result.reason shouldBe FossUpgrade.Reason.DONATED
}
@Test @Test
fun `Moshi-serialized DeviceProfilesContainer JSON is readable by kotlinx`() { fun `Moshi-serialized DeviceProfilesContainer JSON is readable by kotlinx`() {
// This is what Moshi would produce for a container with one Apple profile // This is what Moshi would produce for a container with one Apple profile
@@ -1,13 +1,20 @@
package eu.darken.capod.common.datastore package eu.darken.capod.common.datastore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import eu.darken.capod.common.bluetooth.ScannerMode
import eu.darken.capod.common.serialization.ByteArrayBase64Serializer import eu.darken.capod.common.serialization.ByteArrayBase64Serializer
import eu.darken.capod.common.serialization.InstantEpochMillisSerializer
import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.common.upgrade.core.FossUpgrade
import eu.darken.capod.main.core.MonitorMode
import kotlinx.serialization.builtins.nullable import kotlinx.serialization.builtins.nullable
import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer import eu.darken.capod.profiles.core.DeviceProfilesContainer
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -16,6 +23,7 @@ import org.junit.jupiter.api.io.TempDir
import testhelpers.BaseTest import testhelpers.BaseTest
import testhelpers.coroutine.runTest2 import testhelpers.coroutine.runTest2
import java.io.File import java.io.File
import java.time.Instant
import eu.darken.capod.common.datastore.value import eu.darken.capod.common.datastore.value
class DataStoreValueSerializationTest : BaseTest() { class DataStoreValueSerializationTest : BaseTest() {
@@ -143,6 +151,97 @@ class DataStoreValueSerializationTest : BaseTest() {
result!!.toList() shouldBe testBytes.toList() result!!.toList() shouldBe testBytes.toList()
} }
@Test
fun `enum round-trip - ThemeStyle`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("style", ThemeStyle.DEFAULT, json)
ThemeStyle.entries.forEach { style ->
pref.value(style)
pref.value() shouldBe style
}
}
@Test
fun `enum round-trip - ThemeColor`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("color", ThemeColor.BLUE, json)
ThemeColor.entries.forEach { color ->
pref.value(color)
pref.value() shouldBe color
}
}
@Test
fun `enum round-trip - MonitorMode`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("monitor", MonitorMode.MANUAL, json)
MonitorMode.entries.forEach { mode ->
pref.value(mode)
pref.value() shouldBe mode
}
}
@Test
fun `enum round-trip - ScannerMode`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("scanner", ScannerMode.LOW_POWER, json)
ScannerMode.entries.forEach { mode ->
pref.value(mode)
pref.value() shouldBe mode
}
}
@Test
fun `enum round-trip - AutoConnectCondition`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("autoconnect", AutoConnectCondition.WHEN_SEEN, json)
AutoConnectCondition.entries.forEach { condition ->
pref.value(condition)
pref.value() shouldBe condition
}
}
@Test
fun `data class round-trip - FossUpgrade nullable`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue<FossUpgrade?>("upgrade", null, json)
pref.value() shouldBe null
val upgrade = FossUpgrade(
upgradedAt = Instant.ofEpochMilli(1709553600000),
reason = FossUpgrade.Reason.DONATED,
)
pref.value(upgrade)
val result = pref.value()
result!!.upgradedAt shouldBe Instant.ofEpochMilli(1709553600000)
result.reason shouldBe FossUpgrade.Reason.DONATED
}
@Test
fun `InstantEpochMillisSerializer round-trip`() {
val instant = Instant.ofEpochMilli(1709553600000)
val serialized = json.encodeToString(InstantEpochMillisSerializer, instant)
serialized shouldBe "1709553600000"
val deserialized = json.decodeFromString(InstantEpochMillisSerializer, serialized)
deserialized shouldBe instant
}
@Test
fun `InstantEpochMillisSerializer - epoch zero`() {
val instant = Instant.EPOCH
val serialized = json.encodeToString(InstantEpochMillisSerializer, instant)
serialized shouldBe "0"
val deserialized = json.decodeFromString(InstantEpochMillisSerializer, serialized)
deserialized shouldBe instant
}
@Test @Test
fun `sealed interface polymorphic - DeviceProfile`() = runTest2 { fun `sealed interface polymorphic - DeviceProfile`() = runTest2 {
val ds = createDataStore() val ds = createDataStore()