Skip to content

Commit 9fde31c

Browse files
🧪 [testing improvement] Add unit tests for SessionStore
Co-authored-by: alvin000009238 <107313913+alvin000009238@users.noreply.github.com>
1 parent fd03a1f commit 9fde31c

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

android/app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ dependencies {
141141
testImplementation("app.cash.turbine:turbine:1.2.1")
142142
testImplementation("com.squareup.okhttp3:mockwebserver:5.4.0")
143143
testImplementation("junit:junit:4.13.2")
144+
testImplementation("org.robolectric:robolectric:4.11.1")
145+
testImplementation("org.mockito:mockito-core:5.8.0")
146+
testImplementation("androidx.test:core-ktx:1.5.0")
147+
testImplementation("androidx.test.ext:junit-ktx:1.1.5")
144148
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
145149

146150
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package com.clhs.score.data
2+
3+
import android.content.Context
4+
import android.util.Base64
5+
import androidx.test.core.app.ApplicationProvider
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import org.junit.Assert.assertEquals
8+
import org.junit.Assert.assertNull
9+
import org.junit.Assert.assertTrue
10+
import org.junit.Assert.assertFalse
11+
import org.junit.Assert.assertNotNull
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import org.junit.Before
15+
import org.robolectric.annotation.Config
16+
import org.robolectric.annotation.Implementation
17+
import org.robolectric.annotation.Implements
18+
import javax.crypto.Cipher
19+
import javax.crypto.KeyGenerator
20+
21+
@RunWith(AndroidJUnit4::class)
22+
@Config(sdk = [34], shadows = [ShadowMasterKey::class, ShadowEncryptedSharedPreferences::class, ShadowBiometricHelper::class])
23+
class SessionStoreTest {
24+
25+
@Before
26+
fun setup() {
27+
System.setProperty("javax.net.ssl.trustStoreType", "JKS")
28+
}
29+
30+
@Test
31+
fun testSaveAndLoadSession() {
32+
val context = ApplicationProvider.getApplicationContext<Context>()
33+
val store = SessionStore(context)
34+
35+
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
36+
store.saveSession(session)
37+
38+
val loaded = store.loadSession()
39+
assertEquals(session, loaded)
40+
}
41+
42+
@Test
43+
fun testClearSession() {
44+
val context = ApplicationProvider.getApplicationContext<Context>()
45+
val store = SessionStore(context)
46+
47+
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
48+
store.saveSession(session)
49+
50+
store.clearNormalSession()
51+
assertNull(store.loadSession())
52+
}
53+
54+
@Test
55+
fun testReminderSession() {
56+
val context = ApplicationProvider.getApplicationContext<Context>()
57+
val store = SessionStore(context)
58+
59+
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
60+
store.saveReminderSession(session, 1000)
61+
62+
val loaded = store.loadReminderSession(500)
63+
assertEquals(session, loaded)
64+
65+
val expired = store.loadReminderSession(2000)
66+
assertNull(expired)
67+
assertNull(store.loadReminderSession(500))
68+
}
69+
70+
@Test
71+
fun testClearAll() {
72+
val context = ApplicationProvider.getApplicationContext<Context>()
73+
val store = SessionStore(context)
74+
75+
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
76+
store.saveSession(session)
77+
store.saveReminderSession(session, 1000)
78+
79+
store.clear()
80+
81+
assertNull(store.loadSession())
82+
assertNull(store.loadReminderSession(500))
83+
}
84+
85+
@Test
86+
fun testBiometricSession() {
87+
val context = ApplicationProvider.getApplicationContext<Context>()
88+
val store = SessionStore(context)
89+
90+
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
91+
val keyGen = KeyGenerator.getInstance("AES")
92+
keyGen.init(256)
93+
val key = keyGen.generateKey()
94+
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
95+
cipher.init(Cipher.ENCRYPT_MODE, key)
96+
97+
store.saveBiometricSession(session, "1234", cipher)
98+
99+
assertTrue(store.hasBiometricSession())
100+
assertNotNull(store.getBiometricIv())
101+
102+
val cipherDecrypt = Cipher.getInstance("AES/GCM/NoPadding")
103+
cipherDecrypt.init(Cipher.DECRYPT_MODE, key, cipher.parameters)
104+
105+
val loaded = store.loadBiometricSession(cipherDecrypt)
106+
assertEquals(session, loaded)
107+
108+
store.clearBiometricSession()
109+
assertFalse(store.hasBiometricSession())
110+
}
111+
}
112+
113+
@Implements(androidx.security.crypto.MasterKey.Builder::class)
114+
class ShadowMasterKey {
115+
@Implementation
116+
fun build(): androidx.security.crypto.MasterKey {
117+
return org.mockito.Mockito.mock(androidx.security.crypto.MasterKey::class.java)
118+
}
119+
}
120+
121+
@Implements(androidx.security.crypto.EncryptedSharedPreferences::class)
122+
class ShadowEncryptedSharedPreferences {
123+
companion object {
124+
@JvmStatic
125+
@Implementation
126+
fun create(
127+
context: Context,
128+
fileName: String,
129+
masterKey: androidx.security.crypto.MasterKey,
130+
prefKeyEncryptionScheme: androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme,
131+
prefValueEncryptionScheme: androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme
132+
): android.content.SharedPreferences {
133+
return context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
134+
}
135+
}
136+
}
137+
138+
@Implements(BiometricHelper::class)
139+
class ShadowBiometricHelper {
140+
companion object {
141+
@JvmStatic
142+
@Implementation
143+
fun encryptWithPin(session: AuthenticatedSession, pin: String, salt: ByteArray): com.clhs.score.data.BiometricHelper.EncryptedData {
144+
return com.clhs.score.data.BiometricHelper.EncryptedData("cipher", Base64.encodeToString("iv".toByteArray(), Base64.NO_WRAP))
145+
}
146+
147+
@JvmStatic
148+
@Implementation
149+
fun decryptWithPin(cipherText: String, ivBase64: String, pin: String, salt: ByteArray): AuthenticatedSession {
150+
return AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
151+
}
152+
153+
@JvmStatic
154+
@Implementation
155+
fun encryptPin(pin: String, cipher: Cipher): com.clhs.score.data.BiometricHelper.EncryptedData {
156+
return com.clhs.score.data.BiometricHelper.EncryptedData("pinCipher", Base64.encodeToString("pinIv".toByteArray(), Base64.NO_WRAP))
157+
}
158+
159+
@JvmStatic
160+
@Implementation
161+
fun decryptPin(cipherText: String, cipher: Cipher): String {
162+
return "1234"
163+
}
164+
165+
@JvmStatic
166+
@Implementation
167+
fun deleteSecretKey() {
168+
// Do nothing
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)