From c5c9f7dbe71bda3aa822f61f99675891dc3b73bf Mon Sep 17 00:00:00 2001 From: Melad Raouf Date: Mon, 12 Jan 2026 13:08:56 +0000 Subject: [PATCH] [MS-1292] Refactor feature/login to use Kotlin Serialization instead of Jackson --- feature/login/build.gradle.kts | 2 +- .../login/screens/form/LoginFormViewModel.kt | 10 +++- .../login/screens/qrscanner/QrCodeContent.kt | 6 +- .../screens/form/LoginFormViewModelTest.kt | 57 ++++++++++--------- infra/ui-base/build.gradle.kts | 1 - 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts index c8d261bf8e..e18cba5e66 100644 --- a/feature/login/build.gradle.kts +++ b/feature/login/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("simprints.feature") id("kotlin-parcelize") + id("simprints.library.kotlinSerialization") } android { @@ -19,6 +20,5 @@ dependencies { implementation(libs.androidX.cameraX.core) implementation(libs.androidX.cameraX.lifecycle) implementation(libs.androidX.cameraX.view) - implementation(libs.jackson.core) implementation(libs.playServices.barcode) } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt index 6a4bf69478..e9431359dc 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/form/LoginFormViewModel.kt @@ -62,13 +62,21 @@ internal class LoginFormViewModel @Inject constructor( private fun mapAuthDataResult(result: AuthenticateDataResult): SignInState = when (result) { AuthenticateDataResult.Authenticated -> SignInState.Success + AuthenticateDataResult.BadCredentials -> SignInState.BadCredentials + AuthenticateDataResult.IntegrityException -> SignInState.IntegrityException + AuthenticateDataResult.IntegrityServiceTemporaryDown -> SignInState.IntegrityServiceTemporaryDown + AuthenticateDataResult.MissingOrOutdatedGooglePlayStoreApp -> SignInState.MissingOrOutdatedGooglePlayStoreApp + AuthenticateDataResult.Offline -> SignInState.Offline + AuthenticateDataResult.TechnicalFailure -> SignInState.TechnicalFailure + AuthenticateDataResult.Unknown -> SignInState.Unknown + is AuthenticateDataResult.BackendMaintenanceError -> SignInState.BackendMaintenanceError( result.estimatedOutage?.let { TimeUtils.getFormattedEstimatedOutage(it) }, ) @@ -88,7 +96,7 @@ internal class LoginFormViewModel @Inject constructor( _signInState.send(mapQrError(result.error)) } else if (!result.content.isNullOrEmpty()) { try { - val qrContent = jsonHelper.fromJson(result.content) + val qrContent = jsonHelper.json.decodeFromString(result.content) Simber.i("QR scanning successful", tag = LOGIN) if (projectId != qrContent.projectId) { diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrCodeContent.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrCodeContent.kt index a723a780e8..967ac8a522 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrCodeContent.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrCodeContent.kt @@ -1,11 +1,13 @@ package com.simprints.feature.login.screens.qrscanner import androidx.annotation.Keep -import com.fasterxml.jackson.annotation.JsonProperty +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable @Keep +@Serializable internal data class QrCodeContent( val projectId: String, val projectSecret: String, - @JsonProperty("backend") val apiBaseUrl: String? = null, + @SerialName("backend") val apiBaseUrl: String? = null, ) diff --git a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt index 9b5f98c8df..360a2dfd4b 100644 --- a/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt +++ b/feature/login/src/test/java/com/simprints/feature/login/screens/form/LoginFormViewModelTest.kt @@ -2,11 +2,10 @@ package com.simprints.feature.login.screens.form import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer -import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.* import com.simprints.core.domain.tokenization.asTokenizableRaw import com.simprints.core.tools.json.JsonHelper import com.simprints.feature.login.LoginParams -import com.simprints.feature.login.screens.qrscanner.QrCodeContent import com.simprints.feature.login.screens.qrscanner.QrScannerResult import com.simprints.feature.login.screens.qrscanner.QrScannerResult.QrScannerError import com.simprints.infra.authlogic.AuthManager @@ -14,14 +13,8 @@ import com.simprints.infra.authlogic.model.AuthenticateDataResult import com.simprints.infra.network.SimNetwork import com.simprints.testtools.common.coroutines.TestCoroutineRule import com.simprints.testtools.common.livedata.getOrAwaitValue -import io.mockk.MockKAnnotations -import io.mockk.clearMocks -import io.mockk.coEvery -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.slot -import io.mockk.verify import org.junit.Before import org.junit.Rule import org.junit.Test @@ -40,9 +33,6 @@ internal class LoginFormViewModelTest { @MockK private lateinit var authManager: AuthManager - @MockK - private lateinit var jsonHelper: JsonHelper - private lateinit var viewModel: LoginFormViewModel @Before @@ -53,7 +43,7 @@ internal class LoginFormViewModelTest { DEVICE_ID, simNetwork, authManager, - jsonHelper, + JsonHelper, ) } @@ -170,9 +160,7 @@ internal class LoginFormViewModelTest { @Test fun `returns correct SignInState when QR code parsing fails`() { - every { jsonHelper.fromJson(any()) } throws RuntimeException("parsing fail") - - viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) + viewModel.handleQrResult(PROJECT_ID, QrScannerResult("Invalid json", null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.QrInvalidCode::class.java) @@ -180,9 +168,15 @@ internal class LoginFormViewModelTest { @Test fun `returns correct SignInState when QR contains wrong project ID`() { - every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent("differentProjectId", PROJECT_SECRET) - - viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) + val qrContent = + """ + { + "projectId": "differentProjectId", + "projectSecret": "$PROJECT_SECRET", + "backend": "$URL" + } + """.trimIndent() + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(qrContent, null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.ProjectIdMismatch::class.java) @@ -190,9 +184,15 @@ internal class LoginFormViewModelTest { @Test fun `returns correct SignInState when QR code parsing success`() { - every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent(PROJECT_ID, PROJECT_SECRET) - - viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) + val qrContent = + """ + { + "projectId": "$PROJECT_ID", + "projectSecret": "$PROJECT_SECRET", + "backend": "$URL" + } + """.trimIndent() + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(qrContent, null)) val result = viewModel.signInState.getOrAwaitValue() assertThat(result.getContentIfNotHandled()).isInstanceOf(SignInState.QrCodeValid::class.java) @@ -202,9 +202,15 @@ internal class LoginFormViewModelTest { @Test fun `updates base API url when QR code parsing success`() { - every { jsonHelper.fromJson(eq(QR_CONTENT)) } returns QrCodeContent(PROJECT_ID, PROJECT_SECRET, URL) - - viewModel.handleQrResult(PROJECT_ID, QrScannerResult(QR_CONTENT, null)) + val qrContent = + """ + { + "projectId": "$PROJECT_ID", + "projectSecret": "$PROJECT_SECRET", + "backend": "$URL" + } + """.trimIndent() + viewModel.handleQrResult(PROJECT_ID, QrScannerResult(qrContent, null)) verify { simNetwork.setApiBaseUrl(eq(URL)) } } @@ -236,7 +242,6 @@ internal class LoginFormViewModelTest { private const val PROJECT_ID = "projectId" private val USER_ID = "userId".asTokenizableRaw() - private const val QR_CONTENT = "qrCodeContents" private const val PROJECT_SECRET = "projectSecret" private const val URL = "projectUrl" } diff --git a/infra/ui-base/build.gradle.kts b/infra/ui-base/build.gradle.kts index 98745379d8..486ebc6ad3 100644 --- a/infra/ui-base/build.gradle.kts +++ b/infra/ui-base/build.gradle.kts @@ -39,7 +39,6 @@ dependencies { api(libs.androidX.cameraX.core) api(libs.androidX.cameraX.lifecycle) api(libs.androidX.cameraX.view) - api(libs.jackson.core) api(libs.playServices.barcode) testImplementation(project(":infra:test-tools"))