[PM-19793] Migrate ZonedDateTimeSerializer to core module (#4960)

This commit is contained in:
Patrick Honkonen
2025-04-02 13:42:51 -04:00
committed by GitHub
parent 8e7de92609
commit ce139623d6
20 changed files with 71 additions and 206 deletions

View File

@@ -7,7 +7,6 @@ import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlI
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.RetrofitsImpl
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.ZonedDateTimeSerializer
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigService
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventService
@@ -23,8 +22,6 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import retrofit2.create
import javax.inject.Singleton
@@ -103,23 +100,4 @@ object PlatformNetworkModule {
sslManager = sslManager,
json = json,
)
@Provides
@Singleton
fun providesJson(): Json = Json {
// If there are keys returned by the server not modeled by a serializable class,
// ignore them.
// This makes additive server changes non-breaking.
ignoreUnknownKeys = true
// We allow for nullable values to have keys missing in the JSON response.
explicitNulls = false
serializersModule = SerializersModule {
contextual(ZonedDateTimeSerializer())
}
// Respect model default property values.
coerceInputValues = true
}
}

View File

@@ -1,33 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
/**
* Used to serialize and deserialize [ZonedDateTime].
*/
class ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
private val dateTimeFormatterDeserialization = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.][:][SSSSSSS][SSSSSS][SSSSS][SSSS][SSS][SS][S]X")
private val dateTimeFormatterSerialization =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(serialName = "ZonedDateTime", kind = PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): ZonedDateTime =
decoder.decodeString().let { dateString ->
ZonedDateTime.parse(dateString, dateTimeFormatterDeserialization)
}
override fun serialize(encoder: Encoder, value: ZonedDateTime) {
encoder.encodeString(dateTimeFormatterSerialization.format(value))
}
}

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.authenticatorbridge.util.generateSecretKey
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
@@ -18,7 +19,6 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserD
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockOrganization
import com.x8bit.bitwarden.data.vault.datasource.network.model.createMockPolicy
import io.mockk.every
@@ -44,7 +44,7 @@ class AuthDiskSourceTest {
every { migrateIfNecessary() } just runs
}
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val authDiskSource = AuthDiskSourceImpl(
encryptedSharedPreferences = fakeEncryptedSharedPreferences,

View File

@@ -27,6 +27,7 @@ import androidx.credentials.provider.PublicKeyCredentialEntry
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.di.CoreModule
import com.bitwarden.sdk.Fido2CredentialStore
import com.bitwarden.vault.CipherView
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
@@ -35,7 +36,6 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
@@ -102,7 +102,7 @@ class Fido2ProviderProcessorTest {
}
private val cancellationSignal: CancellationSignal = mockk()
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val clock = FIXED_CLOCK
@BeforeEach

View File

@@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.platform.base
import com.bitwarden.core.di.CoreModule
import com.bitwarden.network.core.NetworkResultCallAdapterFactory
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.mockwebserver.MockWebServer
@@ -14,7 +14,7 @@ import retrofit2.converter.kotlinx.serialization.asConverterFactory
*/
abstract class BaseServiceTest {
protected val json = PlatformNetworkModule.providesJson()
protected val json = CoreModule.providesJson()
protected val server = MockWebServer().apply { start() }

View File

@@ -2,9 +2,9 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.model.ServerConfig
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson.EnvironmentJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson.ServerJson
@@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test
import java.time.Instant
class ConfigDiskSourceTest {
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val fakeSharedPreferences = FakeSharedPreferences()

View File

@@ -1,9 +1,9 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.dao.FakeOrganizationEventDao
import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class EventDiskSourceTest {
private val fakeOrganizationEventDao = FakeOrganizationEventDao()
private val fakeDispatcherManager = FakeDispatcherManager()
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val eventDiskSource: EventDiskSource = EventDiskSourceImpl(
organizationEventDao = fakeOrganizationEventDao,

View File

@@ -3,8 +3,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit
import app.cash.turbine.test
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
import com.x8bit.bitwarden.data.platform.repository.model.ClearClipboardFrequency
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
@@ -23,7 +23,7 @@ import java.time.Instant
@Suppress("LargeClass")
class SettingsDiskSourceTest {
private val fakeSharedPreferences = FakeSharedPreferences()
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val settingsDiskSource = SettingsDiskSourceImpl(
sharedPreferences = fakeSharedPreferences,

View File

@@ -1,99 +0,0 @@
package com.x8bit.bitwarden.data.platform.datasource.network.serializer
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.encodeToJsonElement
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.ZonedDateTime
class ZonedDateTimeSerializerTest {
private val json = PlatformNetworkModule.providesJson()
@Test
fun `properly deserializes raw JSON to ZonedDateTime`() {
assertEquals(
ZonedDateTimeData(
dataAsZonedDateTime = ZonedDateTime.of(
2023,
10,
6,
17,
22,
28,
440000000,
ZoneOffset.UTC,
),
),
json.decodeFromString<ZonedDateTimeData>(
"""
{
"dataAsZonedDateTime": "2023-10-06T17:22:28.44Z"
}
""",
),
)
}
@Test
fun `properly deserializes raw JSON with nano seconds to ZonedDateTime`() {
assertEquals(
ZonedDateTimeData(
dataAsZonedDateTime = ZonedDateTime.of(
2023,
8,
1,
16,
13,
3,
502391000,
ZoneOffset.UTC,
),
),
json.decodeFromString<ZonedDateTimeData>(
"""
{
"dataAsZonedDateTime": "2023-08-01T16:13:03.502391Z"
}
""",
),
)
}
@Test
fun `properly serializes external model back to raw JSON`() {
assertEquals(
json.parseToJsonElement(
"""
{
"dataAsZonedDateTime": "2023-10-06T17:22:28.440Z"
}
""",
),
json.encodeToJsonElement(
ZonedDateTimeData(
dataAsZonedDateTime = ZonedDateTime.of(
2023,
10,
6,
17,
22,
28,
440000000,
ZoneId.of("UTC"),
),
),
),
)
}
}
@Serializable
private data class ZonedDateTimeData(
@Serializable(ZonedDateTimeSerializer::class)
@SerialName("dataAsZonedDateTime")
val dataAsZonedDateTime: ZonedDateTime,
)

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.manager
import app.cash.turbine.test
import com.bitwarden.core.data.util.asFailure
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
@@ -12,7 +13,6 @@ import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
@@ -63,7 +63,7 @@ class PushManagerTest {
pushService = pushService,
dispatcherManager = dispatcherManager,
clock = clock,
json = PlatformNetworkModule.providesJson(),
json = CoreModule.providesJson(),
)
}

View File

@@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.util
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.bitwarden.core.di.CoreModule
import io.mockk.MockKMatcherScope
import io.mockk.every
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -15,7 +15,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
fun assertJsonEquals(
expected: String,
actual: String,
json: Json = PlatformNetworkModule.providesJson(),
json: Json = CoreModule.providesJson(),
) {
assertEquals(
json.parseToJsonElement(expected),

View File

@@ -1,8 +1,8 @@
package com.x8bit.bitwarden.data.vault.datasource.disk
import app.cash.turbine.test
import com.bitwarden.core.di.CoreModule
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.util.assertJsonEquals
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeCiphersDao
import com.x8bit.bitwarden.data.vault.datasource.disk.dao.FakeCollectionsDao
@@ -34,7 +34,7 @@ import java.time.ZonedDateTime
class VaultDiskSourceTest {
private val json = PlatformNetworkModule.providesJson()
private val json = CoreModule.providesJson()
private val dispatcherManager: FakeDispatcherManager = FakeDispatcherManager()
private lateinit var ciphersDao: FakeCiphersDao
private lateinit var collectionsDao: FakeCollectionsDao