From 9fde31cef86878a2ea936a4de38712183742e1e6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:37:04 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20[testing=20improvement]=20Add=20?= =?UTF-8?q?unit=20tests=20for=20SessionStore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: alvin000009238 <107313913+alvin000009238@users.noreply.github.com> --- android/app/build.gradle.kts | 4 + .../com/clhs/score/data/SessionStoreTest.kt | 171 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 android/app/src/test/java/com/clhs/score/data/SessionStoreTest.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 50300b1..ffa0add 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -141,6 +141,10 @@ dependencies { testImplementation("app.cash.turbine:turbine:1.2.1") testImplementation("com.squareup.okhttp3:mockwebserver:5.4.0") testImplementation("junit:junit:4.13.2") + testImplementation("org.robolectric:robolectric:4.11.1") + testImplementation("org.mockito:mockito-core:5.8.0") + testImplementation("androidx.test:core-ktx:1.5.0") + testImplementation("androidx.test.ext:junit-ktx:1.1.5") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0") androidTestImplementation("androidx.compose.ui:ui-test-junit4") diff --git a/android/app/src/test/java/com/clhs/score/data/SessionStoreTest.kt b/android/app/src/test/java/com/clhs/score/data/SessionStoreTest.kt new file mode 100644 index 0000000..6413a10 --- /dev/null +++ b/android/app/src/test/java/com/clhs/score/data/SessionStoreTest.kt @@ -0,0 +1,171 @@ +package com.clhs.score.data + +import android.content.Context +import android.util.Base64 +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.Before +import org.robolectric.annotation.Config +import org.robolectric.annotation.Implementation +import org.robolectric.annotation.Implements +import javax.crypto.Cipher +import javax.crypto.KeyGenerator + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [34], shadows = [ShadowMasterKey::class, ShadowEncryptedSharedPreferences::class, ShadowBiometricHelper::class]) +class SessionStoreTest { + + @Before + fun setup() { + System.setProperty("javax.net.ssl.trustStoreType", "JKS") + } + + @Test + fun testSaveAndLoadSession() { + val context = ApplicationProvider.getApplicationContext() + val store = SessionStore(context) + + val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + store.saveSession(session) + + val loaded = store.loadSession() + assertEquals(session, loaded) + } + + @Test + fun testClearSession() { + val context = ApplicationProvider.getApplicationContext() + val store = SessionStore(context) + + val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + store.saveSession(session) + + store.clearNormalSession() + assertNull(store.loadSession()) + } + + @Test + fun testReminderSession() { + val context = ApplicationProvider.getApplicationContext() + val store = SessionStore(context) + + val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + store.saveReminderSession(session, 1000) + + val loaded = store.loadReminderSession(500) + assertEquals(session, loaded) + + val expired = store.loadReminderSession(2000) + assertNull(expired) + assertNull(store.loadReminderSession(500)) + } + + @Test + fun testClearAll() { + val context = ApplicationProvider.getApplicationContext() + val store = SessionStore(context) + + val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + store.saveSession(session) + store.saveReminderSession(session, 1000) + + store.clear() + + assertNull(store.loadSession()) + assertNull(store.loadReminderSession(500)) + } + + @Test + fun testBiometricSession() { + val context = ApplicationProvider.getApplicationContext() + val store = SessionStore(context) + + val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + val keyGen = KeyGenerator.getInstance("AES") + keyGen.init(256) + val key = keyGen.generateKey() + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.ENCRYPT_MODE, key) + + store.saveBiometricSession(session, "1234", cipher) + + assertTrue(store.hasBiometricSession()) + assertNotNull(store.getBiometricIv()) + + val cipherDecrypt = Cipher.getInstance("AES/GCM/NoPadding") + cipherDecrypt.init(Cipher.DECRYPT_MODE, key, cipher.parameters) + + val loaded = store.loadBiometricSession(cipherDecrypt) + assertEquals(session, loaded) + + store.clearBiometricSession() + assertFalse(store.hasBiometricSession()) + } +} + +@Implements(androidx.security.crypto.MasterKey.Builder::class) +class ShadowMasterKey { + @Implementation + fun build(): androidx.security.crypto.MasterKey { + return org.mockito.Mockito.mock(androidx.security.crypto.MasterKey::class.java) + } +} + +@Implements(androidx.security.crypto.EncryptedSharedPreferences::class) +class ShadowEncryptedSharedPreferences { + companion object { + @JvmStatic + @Implementation + fun create( + context: Context, + fileName: String, + masterKey: androidx.security.crypto.MasterKey, + prefKeyEncryptionScheme: androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, + prefValueEncryptionScheme: androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme + ): android.content.SharedPreferences { + return context.getSharedPreferences(fileName, Context.MODE_PRIVATE) + } + } +} + +@Implements(BiometricHelper::class) +class ShadowBiometricHelper { + companion object { + @JvmStatic + @Implementation + fun encryptWithPin(session: AuthenticatedSession, pin: String, salt: ByteArray): com.clhs.score.data.BiometricHelper.EncryptedData { + return com.clhs.score.data.BiometricHelper.EncryptedData("cipher", Base64.encodeToString("iv".toByteArray(), Base64.NO_WRAP)) + } + + @JvmStatic + @Implementation + fun decryptWithPin(cipherText: String, ivBase64: String, pin: String, salt: ByteArray): AuthenticatedSession { + return AuthenticatedSession("123", "token", mapOf("cookie1" to "val1")) + } + + @JvmStatic + @Implementation + fun encryptPin(pin: String, cipher: Cipher): com.clhs.score.data.BiometricHelper.EncryptedData { + return com.clhs.score.data.BiometricHelper.EncryptedData("pinCipher", Base64.encodeToString("pinIv".toByteArray(), Base64.NO_WRAP)) + } + + @JvmStatic + @Implementation + fun decryptPin(cipherText: String, cipher: Cipher): String { + return "1234" + } + + @JvmStatic + @Implementation + fun deleteSecretKey() { + // Do nothing + } + } +}