🧪 [testing improvement] Add unit tests for SessionStore - #197
🧪 [testing improvement] Add unit tests for SessionStore#197alvin000009238 wants to merge 1 commit into
Conversation
Co-authored-by: alvin000009238 <107313913+alvin000009238@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
There was a problem hiding this comment.
Code Review
This pull request adds unit tests for SessionStore using Robolectric, along with the necessary test dependencies in build.gradle.kts. The review feedback identifies a binding issue in ShadowBiometricHelper where companion object methods should be converted to instance methods to match Kotlin's singleton compilation, and suggests expanding testClearAll to ensure biometric sessions are also verified as cleared.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| @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 | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
In Kotlin, object declarations compile to classes with instance methods (on the INSTANCE singleton) rather than static methods, unless they are explicitly annotated with @JvmStatic. Since BiometricHelper's methods are not annotated with @JvmStatic, Robolectric expects the shadow methods to be instance methods on ShadowBiometricHelper rather than static methods in a companion object. Defining them as static companion methods will cause Robolectric to fail to bind the shadow, which will result in the real BiometricHelper being executed (potentially causing AndroidKeyStore errors or slow PBKDF2 key derivation in tests).
Remove the companion object and @JvmStatic annotations to make them instance methods.
@Implements(BiometricHelper::class)
class ShadowBiometricHelper {
@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))
}
@Implementation
fun decryptWithPin(cipherText: String, ivBase64: String, pin: String, salt: ByteArray): AuthenticatedSession {
return AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
}
@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))
}
@Implementation
fun decryptPin(cipherText: String, cipher: Cipher): String {
return "1234"
}
@Implementation
fun deleteSecretKey() {
// Do nothing
}
}| fun testClearAll() { | ||
| val context = ApplicationProvider.getApplicationContext<Context>() | ||
| 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)) | ||
| } | ||
|
|
There was a problem hiding this comment.
The clear() method in SessionStore clears all preferences, which includes standard, reminder, and biometric sessions. To ensure comprehensive test coverage, the testClearAll test should also save a biometric session and assert that it is successfully cleared after calling store.clear().
@Test
fun testClearAll() {
val context = ApplicationProvider.getApplicationContext<Context>()
val store = SessionStore(context)
val session = AuthenticatedSession("123", "token", mapOf("cookie1" to "val1"))
store.saveSession(session)
store.saveReminderSession(session, 1000)
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)
store.clear()
assertNull(store.loadSession())
assertNull(store.loadReminderSession(500))
assertFalse(store.hasBiometricSession())
}There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9fde31cef8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 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") |
There was a problem hiding this comment.
Use a Robolectric version that supports targetSdk 37
This project sets targetSdk = 37, but the newly added JVM test runner depends on Robolectric 4.11.1, whose supported SDK ceiling is much older; when ./gradlew test reaches SessionStoreTest, Robolectric validates the generated manifest target SDK before running the @Config(sdk = [34]) test and rejects apps whose targetSdkVersion is above its max SDK. That makes the release workflow's existing ./gradlew --no-daemon test assembleRelease step fail as soon as this test is included, so the dependency needs to be updated to a Robolectric release that supports the app target SDK or the test should avoid Robolectric.
Useful? React with 👍 / 👎.
🎯 What: Added unit tests for
SessionStore.ktwhich was missing tests.📊 Coverage: Covered
saveSession,loadSession,clearNormalSession,saveReminderSession,loadReminderSession,clearand biometric session saving and loading usingRobolectricand mockedSharedPreferences.✨ Result: Improved test coverage by validating standard, reminder, and biometric session functionality in
SessionStore.PR created automatically by Jules for task 10704719854318725426 started by @alvin000009238