diff --git a/app/src/test/java/eu/darken/capod/common/datastore/DataStoreMigrationCompatTest.kt b/app/src/test/java/eu/darken/capod/common/datastore/DataStoreMigrationCompatTest.kt index d7908f0e..b16fd5ed 100644 --- a/app/src/test/java/eu/darken/capod/common/datastore/DataStoreMigrationCompatTest.kt +++ b/app/src/test/java/eu/darken/capod/common/datastore/DataStoreMigrationCompatTest.kt @@ -9,6 +9,7 @@ import eu.darken.capod.main.core.MonitorMode import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesContainer +import eu.darken.capod.common.upgrade.core.FossUpgrade import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition import io.kotest.matchers.shouldBe import kotlinx.serialization.SerialName @@ -16,6 +17,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.serializer import org.junit.jupiter.api.Test import testhelpers.BaseTest +import java.time.Instant /** * Tests that verify @SerialName values match @Json(name=...) values, @@ -68,6 +70,9 @@ class DataStoreMigrationCompatTest : BaseTest() { @Test fun `SerialName matches Json name - PodDevice Model`() = verifyEnumSerialNameParity() + @Test + fun `SerialName matches Json name - FossUpgrade Reason`() = verifyEnumSerialNameParity() + @Test fun `Moshi-serialized ThemeMode string is readable by kotlinx`() { // 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(), 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(), 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(), 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(), moshiJson) + result.upgradedAt shouldBe Instant.ofEpochMilli(1709553600000) + result.reason shouldBe FossUpgrade.Reason.DONATED + } + @Test fun `Moshi-serialized DeviceProfilesContainer JSON is readable by kotlinx`() { // This is what Moshi would produce for a container with one Apple profile diff --git a/app/src/test/java/eu/darken/capod/common/datastore/DataStoreValueSerializationTest.kt b/app/src/test/java/eu/darken/capod/common/datastore/DataStoreValueSerializationTest.kt index 4acf322b..8721c0a0 100644 --- a/app/src/test/java/eu/darken/capod/common/datastore/DataStoreValueSerializationTest.kt +++ b/app/src/test/java/eu/darken/capod/common/datastore/DataStoreValueSerializationTest.kt @@ -1,13 +1,20 @@ package eu.darken.capod.common.datastore 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.InstantEpochMillisSerializer +import eu.darken.capod.common.theming.ThemeColor 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 eu.darken.capod.pods.core.PodDevice import eu.darken.capod.profiles.core.AppleDeviceProfile import eu.darken.capod.profiles.core.DeviceProfile import eu.darken.capod.profiles.core.DeviceProfilesContainer +import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import kotlinx.serialization.json.Json @@ -16,6 +23,7 @@ import org.junit.jupiter.api.io.TempDir import testhelpers.BaseTest import testhelpers.coroutine.runTest2 import java.io.File +import java.time.Instant import eu.darken.capod.common.datastore.value class DataStoreValueSerializationTest : BaseTest() { @@ -143,6 +151,97 @@ class DataStoreValueSerializationTest : BaseTest() { 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("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 fun `sealed interface polymorphic - DeviceProfile`() = runTest2 { val ds = createDataStore()