Skip to content

Conversation

@Nagarjuna0033
Copy link
Contributor

@Nagarjuna0033 Nagarjuna0033 commented Dec 27, 2025

  • Introduce DataManagerProvider interface and implement it in DataManager.
  • Refactor all repositories to depend on DataManagerProvider for better abstraction.
  • Remove legacy Mockito-based JVM unit tests.
  • Add KMP-compatible tests using Fakes (FakeDataManager, FakeClientService) and Turbine.
  • Implement JsonLoader and TestResourceReader utilities for loading test fixtures across platforms.
  • Update build configuration to include test resources and KMP test dependencies.

Fixes - Jira-#477
image

Summary by CodeRabbit

  • Chores

    • Updated test/build configuration to improve multiplatform test support and switched network binding to a provider-based service.
  • Refactor

    • Rewired repositories to use provider-backed service access with clearer non-null checks and safer error handling.
  • Tests

    • Added multiplatform test utilities, fixtures, fake services and a JSON test resource; introduced new multiplatform test suites and removed legacy JVM-only tests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

📝 Walkthrough

Walkthrough

Repositories were refactored to depend on a new DataManagerProvider interface; DataManager now implements it and DI exposes DataManagerProvider. Multiplatform test utilities, fixtures, and fakes were added (JSON fixtures, JsonLoader, FakeDataManager, FakeClientService, tests). Numerous JVM Mockito test files were removed.

Changes

Cohort / File(s) Summary
Core network & DI
core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt, .../DataManager.kt, .../di/NetworkModule.kt
Added DataManagerProvider interface; DataManager now implements it (service props marked override val); Koin binding changed to single<DataManagerProvider> { DataManager(...) }.
Repository implementations
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/*RepositoryImp.kt (Accounts, Beneficiary, ClientCharge, Client, Guarantor, Home, Loan, RecentTransaction, ReviewLoanApplication, SavingsAccount, ShareAccount, ThirdPartyTransfer, Transfer, UserAuth, UserDetail)
Constructor parameter type changed DataManagerDataManagerProvider; repositories initialize non-null API properties via requireNotNull(dataManager.<api>) { "... must be provided" } and route API calls through those properties. Some methods added/expanded error mapping and ensured flows use flowOn(ioDispatcher).
Multiplatform test resource reader
core/data/src/commonTest/.../TestResourceReader.kt, .../androidUnitTest/...TestResourceReader.android.kt, .../desktopTest/...TestResourceReader.desktop.kt, .../jsCommonTest/...TestResourceReader.jsCommon.kt, .../nativeTest/...TestResourceReader.native.kt
Added expect fun readResource(path: String): String and platform actual implementations for Android, Desktop, Native (iOS). JS implementation is a TODO placeholder.
Test utilities & fixtures
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt, .../TestFixtures.kt, core/data/src/commonTest/resources/json/ClientAccounts.json
New JsonLoader with configured Json and fromResource<T>; TestFixtures.clientAccounts loads fixture via JsonLoader; added JSON fixture json/ClientAccounts.json.
Test fakes & new tests
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt, .../FakeClientService.kt, .../repoTests/AccountsRepositoryTest.kt
Added FakeDataManager implementing DataManagerProvider (nullable service props), FakeClientService (configurable success/failure), and a multiplatform AccountsRepositoryTest leveraging fakes, Turbine, and fixtures.
Removed legacy JVM tests & resources
core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/* (many files), core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
Deleted many existing JVM Mockito-based repository test suites and the mockito-extensions resource.
Android test config & deps
core/data/build.gradle.kts, gradle/libs.versions.toml
Added src/commonTest/resources to Android test resources and extended commonTest dependencies (kotlin.test, kotlin-test-annotations-common, kotlinx.coroutines.test, turbine, koin.test). Added kotlin-test-annotations_common entry in version catalog.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Repo as Repository
    participant Provider as DataManagerProvider
    participant API as Service API
    participant Network as KtorfitClient/Network
    Note over Repo,Provider: Repository now resolves APIs via DataManagerProvider
    Repo->>Provider: requireNotNull(provider.<api>) 
    alt API present
        Provider-->>Repo: returns API
        Repo->>API: call endpoint (suspend / returns Flow)
        API->>Network: perform HTTP request
        Network-->>API: HTTP response
        API-->>Repo: Flow/response
        Repo-->>Caller: emit asDataStateFlow / map / flowOn
    else API missing
        Provider-->>Repo: null
        Repo-->>Caller: throw / emit DataState.Error ("... must be provided")
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • revanthkumarJ
  • WizCoderr

Poem

🐰 I nibbled through providers, clean and bright,

Swapped managers for interfaces under night light.
Fakes and JSON fixtures tucked in neat beds,
Old Mockito tests waved soft heads.
Hoppity-hop — modular trails ahead!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: abstracting the data manager and migrating to Kotlin Multiplatform tests, which are the primary objectives of the PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

42-47: Bug: catch block does not emit the error state.

The catch block returns DataState.Error(...) but doesn't actually emit it. The catch operator's lambda should call emit() to propagate the error state downstream. As written, exceptions are caught but the flow terminates without emitting any error value.

🔎 Proposed fix
     override fun getShareProducts(clientId: Long?): Flow<DataState<Page<ShareProduct>>> {
         return shareAccountApi.getShareProducts(clientId)
-            .map { response -> DataState.Success(response) }
-            .catch { exception -> DataState.Error(exception, exception.message) }
+            .asDataStateFlow()
             .flowOn(ioDispatcher)
     }

Alternatively, if you want custom error handling:

     override fun getShareProducts(clientId: Long?): Flow<DataState<Page<ShareProduct>>> {
         return shareAccountApi.getShareProducts(clientId)
             .map { response -> DataState.Success(response) }
-            .catch { exception -> DataState.Error(exception, exception.message) }
+            .catch { exception -> emit(DataState.Error(exception, exception.message)) }
             .flowOn(ioDispatcher)
     }
🧹 Nitpick comments (14)
core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt (1)

12-17: Add error handling for missing resources.

The non-null assertions on lines 14-15 will throw NPEs without descriptive error messages if the classLoader is null or the resource doesn't exist. This makes debugging test failures more difficult.

🔎 Proposed fix with descriptive error handling
 actual fun readResource(path: String): String {
-    return object {}.javaClass
-        .classLoader!!
-        .getResource(path)!!
-        .readText()
+    val classLoader = object {}.javaClass.classLoader
+        ?: error("Failed to get class loader")
+    val resource = classLoader.getResource(path)
+        ?: error("Resource not found: $path")
+    return resource.readText()
 }
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt (1)

14-20: Consider lazy initialization to avoid repeated JSON parsing.

The get() accessor causes the JSON to be parsed every time clientAccounts is accessed. If tests access this property multiple times, consider using lazy initialization for better performance.

🔎 Proposed refactor using lazy initialization
 object TestFixtures {
 
-    val clientAccounts
-        get() = JsonLoader.fromResource<ClientAccounts>(
-            "json/ClientAccounts.json",
-        )
+    val clientAccounts by lazy {
+        JsonLoader.fromResource<ClientAccounts>(
+            "json/ClientAccounts.json",
+        )
+    }
 }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt (1)

27-29: Consider making clientsApi private.

The clientsApi property is public by default (no visibility modifier). Since it's only used internally within the repository, exposing it publicly breaks encapsulation and leaks implementation details. This pattern appears across multiple repositories in this PR.

🔎 Proposed fix
-    val clientsApi = requireNotNull(dataManager.clientsApi) {
+    private val clientsApi = requireNotNull(dataManager.clientsApi) {
         "ClientService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt (1)

31-33: Consider making clientsApi private.

Same encapsulation concern as in ClientRepositoryImp - this property is used only internally and should not be part of the public API.

🔎 Proposed fix
-    val clientsApi = requireNotNull(dataManager.clientsApi) {
+    private val clientsApi = requireNotNull(dataManager.clientsApi) {
         "ClientService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

38-40: Consider making shareAccountApi private.

Same encapsulation concern as in other repositories - this property is used only internally.

🔎 Proposed fix
-    val shareAccountApi = requireNotNull(dataManager.shareAccountApi) {
+    private val shareAccountApi = requireNotNull(dataManager.shareAccountApi) {
         "ShareAccountService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt (1)

32-38: Consider making API properties private.

Both savingAccountsListApi and thirdPartyTransferApi are internal implementation details and should not be exposed publicly.

🔎 Proposed fix
-    val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
+    private val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
         "SavingAccountsListService must be provided"
     }

-    val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
+    private val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
         "ThirdPartyTransferService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt (1)

43-43: Replace debug println statements with proper logging.

The println statements should be replaced with a proper logging framework for better observability and production readiness. Debug output to stdout is not suitable for production code.

💡 Consider using a logging framework
-                        println("response $response")
+                        // Log response if needed using proper logger

Also applies to: 48-48

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt (1)

28-28: Consider removing or tracking commented-out database code.

The commented-out code for chargeDao and local database operations appears to be legacy code. Consider either:

  1. Removing it if the database layer is no longer planned
  2. Creating a tracking issue if this functionality will be implemented later
  3. Adding a comment explaining why it's temporarily disabled

Also applies to: 54-67

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt (1)

34-37: Consider making beneficiaryApi private to maintain encapsulation.

The beneficiaryApi property is declared as val (public), which exposes an internal implementation detail. This could allow external callers to bypass repository methods and access the API directly.

Suggested fix
-    val beneficiaryApi = requireNotNull(dataManager.beneficiaryApi) {
+    private val beneficiaryApi = requireNotNull(dataManager.beneficiaryApi) {
         "BeneficiaryService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt (1)

25-28: Consider making thirdPartyTransferApi private.

Same encapsulation concern as noted in other repositories—exposing internal API access publicly.

Suggested fix
-    val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
+    private val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
         "ThirdPartyTransferService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt (2)

33-43: Consider making API properties private for encapsulation.

All three API properties (registrationApi, authenticationApi, userDetailsApi) are public. Consider making them private to prevent external access to internal services.

Suggested fix
-    val registrationApi = requireNotNull(dataManager.registrationApi) {
+    private val registrationApi = requireNotNull(dataManager.registrationApi) {
         "RegistrationService must be provided"
     }

-    val authenticationApi = requireNotNull(dataManager.authenticationApi) {
+    private val authenticationApi = requireNotNull(dataManager.authenticationApi) {
         "AuthenticationService must be provided"
     }

-    val userDetailsApi = requireNotNull(dataManager.userDetailsApi) {
+    private val userDetailsApi = requireNotNull(dataManager.userDetailsApi) {
         "UserDetailsService must be provided"
     }

63-85: Inconsistent try-catch placement compared to other methods.

In login(), the try-catch wraps withContext, while in registerUser(), verifyUser(), and updateAccountPassword(), the try-catch is inside withContext. This inconsistency could lead to subtle differences in exception propagation behavior and makes the code harder to maintain.

Suggested fix for consistency
     override suspend fun login(username: String, password: String): DataState<User> {
         val loginPayload = LoginPayload(
             username = username,
             password = password,
         )
-        return try {
-            withContext(ioDispatcher) {
+        return withContext(ioDispatcher) {
+            try {
                 val user = authenticationApi.authenticate(loginPayload)
                 if (user.base64EncodedAuthenticationKey != null) {
                     DataState.Success(user)
                 } else {
                     DataState.Error(Exception("Invalid Credentials"), null)
                 }
+            } catch (e: ClientRequestException) {
+                val errorMessage = extractErrorMessage(e.response)
+                DataState.Error(Exception(errorMessage), null)
+            } catch (e: IOException) {
+                DataState.Error(Exception("Network error: ${e.message ?: "Please check your connection"}"), null)
+            } catch (e: ServerResponseException) {
+                DataState.Error(Exception("Server error: ${e.message}"), null)
             }
-        } catch (e: ClientRequestException) {
-            val errorMessage = extractErrorMessage(e.response)
-            DataState.Error(Exception(errorMessage), null)
-        } catch (e: IOException) {
-            DataState.Error(Exception("Network error: ${e.message ?: "Please check your connection"}"), null)
-        } catch (e: ServerResponseException) {
-            DataState.Error(Exception("Server error: ${e.message}"), null)
         }
     }
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt (1)

109-111: Inconsistent assertion style for checking empty lists.

Line 109 uses assertEquals(...?.isEmpty(), true) while lines 110-111 use assertTrue(...isEmpty()). The nullable-safe approach on line 109 suggests savingsAccounts might be nullable, but the assertion style is inconsistent.

Suggested fix for consistency
-                assertEquals(data.savingsAccounts?.isEmpty(), true)
-                assertTrue(data.loanAccounts.isEmpty())
-                assertTrue(data.shareAccounts.isEmpty())
+                assertTrue(data.savingsAccounts?.isEmpty() == true)
+                assertTrue(data.loanAccounts.isEmpty())
+                assertTrue(data.shareAccounts.isEmpty())
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

38-40: Consider making savingAccountsListApi private.

Same encapsulation concern as other repositories.

Suggested fix
-    val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
+    private val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
         "SavingAccountsListService must be provided"
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97c3695 and b0d5a9b.

📒 Files selected for processing (46)
  • core/data/build.gradle.kts
  • core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt
  • core/data/src/commonTest/resources/json/ClientAccounts.json
  • core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt
  • core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt
  • core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (15)
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
🧰 Additional context used
🧬 Code graph analysis (1)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt (5)
core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt (1)
  • readResource (12-19)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt (1)
  • readResource (12-12)
core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt (1)
  • readResource (12-17)
core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt (1)
  • readResource (12-14)
core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt (1)
  • readResource (18-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: PR Checks KMP / Static Analysis Check
🔇 Additional comments (36)
core/data/build.gradle.kts (2)

26-28: LGTM! Proper test resource configuration for KMP.

The configuration correctly points the Android test resources to the commonTest resources directory, enabling resource sharing across platforms.


50-57: LGTM! Standard KMP test dependencies.

The commonTest dependencies are well-chosen for KMP testing:

  • kotlin.test and kotlin.test.annotations.common for cross-platform test APIs
  • kotlinx.coroutines.test for coroutine testing utilities
  • turbine for Flow testing
  • koin.test for dependency injection testing
gradle/libs.versions.toml (1)

245-245: LGTM! Version catalog entry for KMP test annotations.

The library entry correctly references the Kotlin version and follows the established naming pattern in the catalog.

core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt (1)

27-41: LGTM! Clean test fake implementation.

The FakeDataManager properly implements DataManagerProvider with nullable service properties that default to null. This enables flexible test setup where only required services need to be provided.

core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt (1)

12-12: LGTM! Clean multiplatform expect declaration.

The expect function provides a clear contract for platform-specific resource reading implementations.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt (2)

26-35: LGTM! Clean migration to DataManagerProvider.

The refactoring properly introduces the DataManagerProvider abstraction with a clear null-safety check. The requireNotNull provides a descriptive error message that will help diagnose configuration issues during testing or runtime.


37-79: LGTM! Consistent usage of local API property.

All method implementations correctly use the local guarantorApi property instead of accessing through the data manager. This improves readability and ensures the null-check is only performed once during initialization.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt (1)

31-34: LGTM!

The loadClient() implementation correctly uses the new clientsApi property and maintains the existing flow behavior with asDataStateFlow() and flowOn(ioDispatcher).

core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt (1)

14-23: LGTM!

The JsonLoader utility is well-structured for test fixtures. The JSON configuration (ignoreUnknownKeys = true, explicitNulls = false) is appropriate for test data that may evolve independently. The inline reified function provides type-safe deserialization.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt (1)

44-59: LGTM!

The clientImage implementation correctly handles the base64 data URI scheme by extracting the pure base64 content after the comma. The exhaustive when mapping of all DataState variants ensures proper error and loading state propagation.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt (1)

43-45: LGTM!

The DI binding correctly exposes DataManager as DataManagerProvider, enabling interface-based dependency injection. This is the key change that allows repositories to depend on the abstraction and enables test fakes to be substituted.

core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt (1)

12-18: LGTM!

The Android implementation provides proper error handling with a descriptive message and uses use {} for safe resource cleanup. This serves as a good reference for the native implementation which should follow the same error-handling pattern.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt (1)

40-62: LGTM!

The transfer routing logic correctly directs SELF transfers through savingAccountsListApi and other types through thirdPartyTransferApi. The generic error message for ServerResponseException appropriately avoids exposing internal server details to users.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt (2)

19-19: LGTM! Improved abstraction with DataManagerProvider.

The migration from DataManager to DataManagerProvider improves abstraction and aligns with the PR's architectural goals of introducing a provider interface for better dependency injection.

Also applies to: 22-22


30-37: Verify null-safety: potential NPE from force-unwrapping clientId.

The implementation correctly uses requireNotNull for early API validation. However, clientId!! on Line 35 will throw an NPE if clientId is null. Since the method signature accepts clientId as nullable (Long?), consider either:

  1. Making clientId non-nullable in the signature if it's always required, or
  2. Validating clientId with requireNotNull and providing a descriptive error message
🔎 Suggested improvement for null-safety
         val clientsApi = requireNotNull(dataManager.clientsApi) {
             "ClientService must be provided"
         }
+        val safeClientId = requireNotNull(clientId) {
+            "Client ID must be provided"
+        }

         return clientsApi
-            .getAccounts(clientId!!, accountType)
+            .getAccounts(safeClientId, accountType)
             .asDataStateFlow()
             .flowOn(ioDispatcher)
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt (2)

22-22: LGTM! Consistent migration to DataManagerProvider.

The constructor update aligns with the provider-based abstraction pattern introduced in this PR.

Also applies to: 25-25


29-31: LGTM! Appropriate use of requireNotNull for API initialization.

The property-level initialization with requireNotNull provides early failure detection with a clear error message, ensuring the service is available before use.

core/data/src/commonTest/resources/json/ClientAccounts.json (2)

1-554: LGTM! Comprehensive test fixture for KMP testing.

The test resource provides comprehensive account data covering savings, loan, and share accounts with varied statuses and transaction histories. This aligns well with the PR's objective to introduce KMP-compatible tests using JSON-based test fixtures.


194-194: Verify: Unusually large account balance for testing.

Line 194 contains an extremely large accountBalance value (3117780552329.610000). If this is intentional for edge-case testing (e.g., testing large number handling or overflow scenarios), consider adding a comment in the test that uses this fixture to explain the rationale.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt (4)

29-29: LGTM! Consistent provider-based dependency injection.

The migration to DataManagerProvider maintains consistency with the architectural refactoring across repository implementations.

Also applies to: 32-32


36-38: LGTM! Property-level API initialization with clear error message.

The requireNotNull check at the property level ensures early failure with a descriptive error message, improving debuggability.


44-51: Good addition of try-catch for error handling.

Wrapping the API call in a try-catch block and emitting DataState.Error improves resilience compared to letting exceptions propagate. However, note that loanId!! on Line 45 will throw an NPE if loanId is null.

Consider validating loanId explicitly:

     ): Flow<DataState<LoanWithAssociations?>> = flow {
         try {
-            loanAccountsListApi.getLoanWithAssociations(loanId!!, associationType)
+            val safeLoanId = requireNotNull(loanId) { "Loan ID must be provided" }
+            loanAccountsListApi.getLoanWithAssociations(safeLoanId, associationType)

64-82: Verify null-safety: potential NPE from loanId!! operator.

Line 71 uses loanId!! which will throw an NPE if loanId is null. Since the method signature accepts nullable Long?, ensure upstream callers always provide non-null values or add explicit validation.

core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt (2)

20-31: LGTM! Well-designed test fake with error scenario support.

The FakeClientService implementation is appropriate for KMP testing. The shouldFail parameter enables testing error scenarios, and the ability to inject custom accounts data supports various test cases.


33-36: Acceptable use of TODO() for unused test fake methods.

Using TODO() for unimplemented methods in a test fake is acceptable, as these methods are not required for the current test scenarios. If future tests need these methods, they can be implemented incrementally.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt (2)

24-24: LGTM! Consistent migration to DataManagerProvider.

The constructor update maintains consistency with the architectural refactoring pattern.

Also applies to: 27-27


32-38: LGTM! Dual API initialization with clear error messages.

Initializing both clientChargeApi and shareAccountApi as properties with requireNotNull ensures early failure detection for both dependencies.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt (3)

21-21: LGTM! Consistent provider-based abstraction.

The migration to DataManagerProvider maintains architectural consistency across repository implementations.

Also applies to: 24-24


28-30: LGTM! Clear API initialization with descriptive error message.

The requireNotNull check ensures the notification service is available before use, with a clear error message for debugging.


31-59: LGTM! Error handling preserved correctly.

The refactoring maintains the existing error handling patterns while using the new notificationApi property, ensuring no regression in error recovery behavior.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt (3)

20-20: LGTM! Consistent architectural refactoring.

The migration to DataManagerProvider aligns with the PR's objective to improve abstraction across repository implementations.

Also applies to: 23-23


27-29: LGTM! Property-level API initialization with clear error message.

The requireNotNull check provides early failure detection with a descriptive error message.


31-41: Verify null-safety: potential NPE from clientId!! operator.

Line 37 uses clientId!! which will throw an NPE if clientId is null. Since the method signature accepts clientId as nullable (Long?), consider adding explicit validation:

🔎 Suggested improvement
     ): Flow<DataState<Page<Transaction>>> {
+        val safeClientId = requireNotNull(clientId) { "Client ID must be provided" }
         return recentTransactionsApi.getRecentTransactionsList(
-            clientId!!,
+            safeClientId,
             offset,
             limit,
         ).asDataStateFlow().flowOn(ioDispatcher)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt (1)

29-142: Good test structure with comprehensive coverage.

The test class covers success, empty data, and error scenarios using Turbine for Flow testing. The use of fakes (FakeDataManager, FakeClientService) aligns well with the KMP testing strategy.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt (1)

12-41: Clean implementation of DataManagerProvider interface.

The DataManager correctly implements DataManagerProvider, overriding all properties with lazy initialization from ktorfitClient. This maintains the existing lazy-loading behavior while providing the abstraction needed for testing.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt (1)

26-40: Nullable API design is well-implemented with consistent consumer safety checks.

All 13 service properties are nullable, enabling flexible testing where FakeDataManager provides only required services. Every repository properly validates this at initialization via requireNotNull() with descriptive error messages (e.g., "ClientService must be provided"), eliminating the need for scattered null checks throughout method code. The production DataManager provides non-null lazy properties.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (5)
core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt (1)

23-30: Address the error handling issues flagged in the previous review.

The force unwrap on resourcePath!! and the error = null parameter remain unaddressed from the previous review. These will cause test failures with unclear error messages when resources are missing or cannot be read, making debugging difficult.

Please implement the error handling suggested in the previous review to ensure tests fail with actionable error messages.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt (1)

27-28: Dead code should be removed.

The commented-out implementation is no longer needed and should be removed to keep the codebase clean.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

46-49: Validate nullable accountId before force-unwrap.

Using accountId!! on line 47 can cause NPE if accountId is null. Consider validating upfront and returning an error state.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt (2)

73-73: Force-unwrap on nullable parameter remains unaddressed.

As noted in previous reviews, using beneficiaryId!! will throw NullPointerException if null is passed. Consider validating upfront and returning DataState.Error with a descriptive message instead.


89-89: Same force-unwrap issue as in updateBeneficiary.

Apply the same null-safety handling as suggested for updateBeneficiary.

🧹 Nitpick comments (3)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt (1)

109-111: Inconsistent nullable handling in assertions.

Line 109 uses safe-call operator (savingsAccounts?.isEmpty()) treating the property as nullable, while lines 110-111 directly access loanAccounts and shareAccounts without null-safety. According to the ClientAccounts model, these properties should have consistent nullability handling.

🔎 Suggested fix for consistency
-                assertEquals(data.savingsAccounts?.isEmpty(), true)
-                assertTrue(data.loanAccounts.isEmpty())
-                assertTrue(data.shareAccounts.isEmpty())
+                assertTrue(data.savingsAccounts?.isEmpty() == true)
+                assertTrue(data.loanAccounts?.isEmpty() == true)
+                assertTrue(data.shareAccounts?.isEmpty() == true)
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt (1)

43-52: Consider consistent error handling pattern.

The getLoanWithAssociations method uses a generic catch (exception: Exception) block, while other methods in this repository use specific exception types (ClientRequestException, IOException, ServerResponseException) with tailored error messages.

Consider aligning the error handling pattern for consistency and better error diagnostics.

🔎 Suggested pattern alignment
 override fun getLoanWithAssociations(
     associationType: String?,
     loanId: Long?,
 ): Flow<DataState<LoanWithAssociations?>> = flow {
+    emit(DataState.Loading)
     try {
-        loanAccountsListApi.getLoanWithAssociations(loanId!!, associationType)
-            .collect { response ->
-                emit(DataState.Success(response))
-            }
-    } catch (exception: Exception) {
-        emit(DataState.Error(exception))
+        val response = loanAccountsListApi.getLoanWithAssociations(loanId!!, associationType)
+        emit(DataState.Success(response))
+    } catch (e: ClientRequestException) {
+        val errorMessage = extractErrorMessage(e.response)
+        emit(DataState.Error(Exception(errorMessage)))
+    } catch (e: IOException) {
+        emit(DataState.Error(Exception("Network error: ${e.message ?: "Please check your connection"}")))
+    } catch (e: ServerResponseException) {
+        emit(DataState.Error(Exception("Server error: ${e.message}")))
     }
 }.flowOn(ioDispatcher)
core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt (1)

27-39: All service properties are nullable with pervasive requireNotNull checks.

The nullable design is intentional for test flexibility—FakeDataManager allows selective service injection. However, every repository implementation uses requireNotNull to enforce initialization at runtime (e.g., val clientsApi = requireNotNull(dataManager.clientsApi) { "Service must be provided" }). If all services are mandatory, consider making them non-nullable in the interface to catch missing dependencies at compile-time rather than requiring runtime checks across 11+ repositories.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0d5a9b and 93073df.

📒 Files selected for processing (46)
  • core/data/build.gradle.kts
  • core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt
  • core/data/src/commonTest/resources/json/ClientAccounts.json
  • core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt
  • core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt
  • core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (15)
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
🚧 Files skipped from review as they are similar to previous changes (17)
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt
  • gradle/libs.versions.toml
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt
  • core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt
  • core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt
  • core/data/src/commonTest/resources/json/ClientAccounts.json
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt
  • core/data/build.gradle.kts
  • core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt
🔇 Additional comments (16)
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt (2)

31-33: LGTM - Fail-fast initialization pattern.

The requireNotNull check at property initialization ensures the repository fails immediately during construction if ClientService is unavailable, rather than deferring the error until first use. This provides clear feedback and is consistent with the refactoring pattern applied across all repositories.


45-58: LGTM - Response mapping preserves error handling.

The clientImage method correctly maps the response to extract Base64 data while preserving all DataState variants (Success, Error, Loading) and maintaining the flowOn(ioDispatcher) control.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt (1)

43-45: LGTM - Proper DI abstraction.

Binding DataManager as DataManagerProvider correctly exposes the interface type for dependency injection while constructing the concrete implementation. This follows standard DI best practices and enables repositories to depend on the abstraction.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt (1)

30-37: LGTM - Consistent API access pattern.

The requireNotNull guard and local clientsApi reference follow the established pattern for accessing services via DataManagerProvider. The flow operations correctly preserve the original behavior with asDataStateFlow and flowOn(ioDispatcher).

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt (2)

29-31: LGTM - Service initialization follows pattern.

The requireNotNull guard ensures LoanAccountsListService is available at construction time, consistent with the refactoring pattern applied across all repositories.


40-51: LGTM - API usage migrated correctly.

Both CREATE and UPDATE branches correctly route through the loanAccountsListApi property while preserving the existing error handling and response processing logic.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt (1)

36-38: LGTM - Service initialization follows pattern.

The requireNotNull guard ensures LoanAccountsListService is available at construction time, consistent with the refactoring applied across repositories.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

38-40: LGTM - Service initialization follows pattern.

The requireNotNull guard ensures SavingAccountsListService is available at construction time, consistent with the refactoring pattern applied across all repositories.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt (2)

27-27: LGTM—Constructor refactored to use DataManagerProvider.

The migration from DataManager to DataManagerProvider aligns with the PR's abstraction goals.


40-50: LGTM—API usage correctly updated.

The methods now correctly use the local API properties, maintaining the same functional behavior.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt (1)

39-48: LGTM—Template method correctly refactored.

The migration to use beneficiaryApi is correct and error handling is appropriate.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt (2)

46-49: LGTM—Transfer routing correctly implemented.

The conditional routing between SELF and third-party transfers using the appropriate APIs is correct.


53-60: LGTM—Error handling is comprehensive and user-friendly.

The exception handling covers the main failure scenarios with appropriate user-facing messages.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt (2)

45-61: LGTM—Registration flow correctly refactored with robust error handling.

The migration to registrationApi is correct, and the error handling covers all relevant exception types with user-friendly messages.


63-85: LGTM—Login implementation correctly refactored.

The authentication flow properly validates the response and includes comprehensive error handling.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

42-78: LGTM—Share account operations correctly refactored.

All methods properly use the shareAccountApi property with appropriate error handling and flow operators.

- Introduce `DataManagerProvider` interface and implement it in `DataManager`.
- Refactor all repositories to depend on `DataManagerProvider` for better abstraction.
- Remove legacy Mockito-based JVM unit tests.
- Add KMP-compatible tests using Fakes (`FakeDataManager`, `FakeClientService`) and `Turbine`.
- Implement `JsonLoader` and `TestResourceReader` utilities for loading test fixtures across platforms.
- Update build configuration to include test resources and KMP test dependencies.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt (2)

44-51: Force unwrap on nullable loanId may throw NPE.

The parameter loanId: Long? is nullable, but you force-unwrap it with loanId!! on line 45. If a caller passes null, this will throw a NullPointerException at runtime instead of a meaningful error.

Consider either making the parameter non-nullable if null is never valid, or handling the null case explicitly.

🔎 Proposed fix (option 1: make non-nullable)
     override fun getLoanWithAssociations(
         associationType: String?,
-        loanId: Long?,
+        loanId: Long,
     ): Flow<DataState<LoanWithAssociations?>> = flow {
         try {
-            loanAccountsListApi.getLoanWithAssociations(loanId!!, associationType)
+            loanAccountsListApi.getLoanWithAssociations(loanId, associationType)
🔎 Proposed fix (option 2: handle null explicitly)
     ): Flow<DataState<LoanWithAssociations?>> = flow {
+        val id = loanId ?: run {
+            emit(DataState.Error(IllegalArgumentException("loanId cannot be null")))
+            return@flow
+        }
         try {
-            loanAccountsListApi.getLoanWithAssociations(loanId!!, associationType)
+            loanAccountsListApi.getLoanWithAssociations(id, associationType)

64-81: Same force unwrap issue on loanId in withdrawLoanAccount.

Line 71 uses loanId!! on a nullable parameter. Apply the same fix as suggested for getLoanWithAssociations.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

42-47: Error not emitted in getShareProducts.

The catch block creates a DataState.Error but doesn't emit it - the error is silently discarded.

🔎 Proposed fix
     override fun getShareProducts(clientId: Long?): Flow<DataState<Page<ShareProduct>>> {
         return shareAccountApi.getShareProducts(clientId)
             .map { response -> DataState.Success(response) }
-            .catch { exception -> DataState.Error(exception, exception.message) }
+            .catch { exception -> emit(DataState.Error(exception, exception.message)) }
             .flowOn(ioDispatcher)
     }
♻️ Duplicate comments (12)
core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt (1)

12-14: Incomplete implementation blocks JS/Common testing.

The TODO placeholder will cause runtime failures if tests run on the JS/Common platform and attempt to load resources.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt (1)

33-35: Make API property private to preserve encapsulation.

The guarantorApi property is an implementation detail and should not be part of the public API surface. Making it private prevents external code from bypassing the repository interface.

🔎 Proposed fix
-    val guarantorApi = requireNotNull(dataManager.guarantorApi) {
+    private val guarantorApi = requireNotNull(dataManager.guarantorApi) {
         "GuarantorService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt (1)

27-30: Make API property private to preserve encapsulation.

The clientsApi property is an implementation detail and should not be part of the public API surface. Making it private prevents external code from bypassing the repository interface.

🔎 Proposed fix
-    val clientsApi = requireNotNull(dataManager.clientsApi) {
+    private val clientsApi = requireNotNull(dataManager.clientsApi) {
         "ClientService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt (1)

27-29: Make API property private to preserve encapsulation.

The recentTransactionsApi property is an implementation detail and should not be part of the public API surface. Making it private prevents external code from bypassing the repository interface.

🔎 Proposed fix
-    val recentTransactionsApi = requireNotNull(dataManager.recentTransactionsApi) {
+    private val recentTransactionsApi = requireNotNull(dataManager.recentTransactionsApi) {
         "RecentTransactionsService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt (1)

31-33: Make API property private to preserve encapsulation.

The clientsApi property is an implementation detail and should not be part of the public API surface. Making it private prevents external code from bypassing the repository interface.

🔎 Proposed fix
-    val clientsApi = requireNotNull(dataManager.clientsApi) {
+    private val clientsApi = requireNotNull(dataManager.clientsApi) {
         "ClientService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt (1)

28-30: Make API property private to preserve encapsulation.

The notificationApi property is an implementation detail and should not be part of the public API surface. Making it private prevents external code from bypassing the repository interface.

🔎 Proposed fix
-    val notificationApi = requireNotNull(dataManager.notificationApi) {
+    private val notificationApi = requireNotNull(dataManager.notificationApi) {
         "NotificationService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt (1)

32-38: Make API properties private to preserve encapsulation.

As noted in a previous review, both savingAccountsListApi and thirdPartyTransferApi should be private implementation details.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

46-49: Force-unwrap (!!) on nullable accountId risks NPE.

As noted in a previous review, multiple methods use accountId!! (lines 47, 105, 125, 142). Since parameters are declared nullable, validate upfront and return an error state rather than crashing.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt (2)

35-37: Make beneficiaryApi private to preserve encapsulation.

As noted in a previous review, the API property should not be exposed as part of the public API surface.


73-73: Force-unwrap (!!) on nullable beneficiaryId risks NPE.

As noted in previous reviews, this applies to both updateBeneficiary (line 73) and deleteBeneficiary (line 89). Validate upfront and return a descriptive error state rather than crashing.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt (1)

33-43: Make all API properties private to preserve encapsulation.

As noted in a previous review, the three API properties (registrationApi, authenticationApi, userDetailsApi) should not be exposed publicly.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

38-40: Encapsulation issue already flagged.

This was previously identified - shareAccountApi should be private val instead of val.

🧹 Nitpick comments (8)
gradle/libs.versions.toml (1)

245-245: Fix naming inconsistency in the library key.

The key kotlin-test-annotations_common uses an underscore before "common", which is inconsistent with the naming convention used throughout this file. Other entries like kotlin-test, kotlin-stdlib, and kotlin-reflect use hyphens consistently.

🔎 Proposed fix
-kotlin-test-annotations_common = { group = "org.jetbrains.kotlin", name = "kotlin-test-annotations-common", version.ref = "kotlin" }
+kotlin-test-annotations-common = { group = "org.jetbrains.kotlin", name = "kotlin-test-annotations-common", version.ref = "kotlin" }
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt (1)

16-19: Consider lazy initialization for test fixtures.

The custom getter deserializes the JSON resource on every access. For immutable test data, consider using by lazy to cache the result and avoid repeated deserialization.

🔎 Proposed optimization
-    val clientAccounts
-        get() = JsonLoader.fromResource<ClientAccounts>(
-            "json/ClientAccounts.json",
-        )
+    val clientAccounts by lazy {
+        JsonLoader.fromResource<ClientAccounts>("json/ClientAccounts.json")
+    }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt (1)

29-31: Make loanAccountsListApi private to preserve encapsulation.

The API property should not be exposed as part of the public API surface. This aligns with the encapsulation pattern expected across repository implementations.

🔎 Proposed fix
-    val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
+    private val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
         "LoanAccountsListService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt (1)

26-28: Make thirdPartyTransferApi private to preserve encapsulation.

Consistent with other repository implementations, the API property should not be exposed publicly.

🔎 Proposed fix
-    val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
+    private val thirdPartyTransferApi = requireNotNull(dataManager.thirdPartyTransferApi) {
         "ThirdPartyTransferService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

38-40: Make savingAccountsListApi private to preserve encapsulation.

The API property should not be exposed as part of the public API surface.

🔎 Proposed fix
-    val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
+    private val savingAccountsListApi = requireNotNull(dataManager.savingAccountsListApi) {
         "SavingAccountsListService must be provided"
     }
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt (1)

39-48: Consider simplifying the flow pattern.

The nested collect inside flow { } could potentially be replaced with a more direct transformation if the upstream API already returns a Flow. However, if the current pattern is intentional for error boundary handling, this is acceptable.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt (1)

63-85: Minor: Inconsistent try-catch placement in login.

The login method places the try-catch outside of withContext, while other methods (registerUser, verifyUser, updateAccountPassword) place it inside. Both work correctly, but consistency would improve maintainability.

🔎 Proposed fix for consistency
     override suspend fun login(username: String, password: String): DataState<User> {
         val loginPayload = LoginPayload(
             username = username,
             password = password,
         )
-        return try {
-            withContext(ioDispatcher) {
+        return withContext(ioDispatcher) {
+            try {
                 val user = authenticationApi.authenticate(loginPayload)
                 if (user.base64EncodedAuthenticationKey != null) {
                     DataState.Success(user)
                 } else {
                     DataState.Error(Exception("Invalid Credentials"), null)
                 }
+            } catch (e: ClientRequestException) {
+                val errorMessage = extractErrorMessage(e.response)
+                DataState.Error(Exception(errorMessage), null)
+            } catch (e: IOException) {
+                DataState.Error(Exception("Network error: ${e.message ?: "Please check your connection"}"), null)
+            } catch (e: ServerResponseException) {
+                DataState.Error(Exception("Server error: ${e.message}"), null)
             }
-        } catch (e: ClientRequestException) {
-            val errorMessage = extractErrorMessage(e.response)
-            DataState.Error(Exception(errorMessage), null)
-        } catch (e: IOException) {
-            DataState.Error(Exception("Network error: ${e.message ?: "Please check your connection"}"), null)
-        } catch (e: ServerResponseException) {
-            DataState.Error(Exception("Server error: ${e.message}"), null)
         }
     }
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt (1)

106-111: Inconsistent null handling for account lists.

savingsAccounts is checked with safe call (?.isEmpty()), while loanAccounts and shareAccounts are accessed directly. This suggests either:

  1. savingsAccounts is nullable while others are not, or
  2. The null handling is inconsistent

If savingsAccounts can be null, consider using assertTrue(data.savingsAccounts.isNullOrEmpty()) for clarity. If it shouldn't be null, use the same pattern as the other assertions.

🔎 Proposed fix (if savingsAccounts should be treated like others)
-                assertEquals(data.savingsAccounts?.isEmpty(), true)
+                assertTrue(data.savingsAccounts?.isEmpty() == true)
                 assertTrue(data.loanAccounts.isEmpty())
                 assertTrue(data.shareAccounts.isEmpty())
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00bffd2 and 935b042.

📒 Files selected for processing (46)
  • core/data/build.gradle.kts
  • core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/BeneficiaryRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientChargeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ClientRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/RecentTransactionRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserAuthRepositoryImp.kt
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/UserDetailRepositoryImp.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestFixtures.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt
  • core/data/src/commonTest/resources/json/ClientAccounts.json
  • core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt
  • core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt
  • core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (15)
  • core/data/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientChargeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ThirdPartyTransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/GuarantorRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserAuthRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/NotificationRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/TransferRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/SavingsAccountRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/BeneficiaryRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/HomeRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/UserDetailRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/LoanRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/AccountsRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/RecentTransactionRepositoryImpTest.kt
  • core/data/src/test/kotlin/org/mifos/mobile/core/data/repositories/ClientRepositoryImpTest.kt
🚧 Files skipped from review as they are similar to previous changes (7)
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt
  • core/data/build.gradle.kts
  • core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/AccountsRepositoryImp.kt
  • core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt
  • core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManagerProvider.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeDataManager.kt
  • core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/fakeServices/FakeClientService.kt
🧰 Additional context used
🧬 Code graph analysis (1)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt (5)
core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt (1)
  • readResource (12-19)
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.kt (1)
  • readResource (12-12)
core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt (1)
  • readResource (12-17)
core/data/src/jsCommonTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.jsCommon.kt (1)
  • readResource (12-14)
core/data/src/nativeTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.native.kt (1)
  • readResource (18-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: PR Checks KMP / Static Analysis Check
🔇 Additional comments (15)
core/data/src/androidUnitTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.android.kt (1)

12-18: Excellent error handling and resource management!

The use of requireNotNull with a descriptive error message and proper resource cleanup via bufferedReader().use demonstrates solid defensive programming. This implementation should serve as the reference pattern for other platform implementations.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/di/NetworkModule.kt (1)

43-45: LGTM!

The binding change correctly abstracts the DataManager behind the DataManagerProvider interface, enabling better testability and adherence to the dependency inversion principle.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/GuarantorRepositoryImp.kt (1)

88-92: Verify that demo data usage is intentional.

The method returns hardcoded demo data while the actual API call is commented out. Please confirm whether this is intentional (e.g., API not yet implemented) or if the real API call should be enabled.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/HomeRepositoryImp.kt (1)

44-59: LGTM!

The response mapping correctly extracts the pure base64 string from the data URI format, and properly preserves error and loading states throughout the flow.

core/data/src/commonTest/resources/json/ClientAccounts.json (1)

1-554: Well-structured test fixture covering multiple account types and statuses.

The JSON fixture provides comprehensive test data for savings, loan, and share accounts with various statuses (active, overpaid) and complete metadata. The large balance value (line 194) appears intentional for testing edge cases with high precision numbers.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/TransferRepositoryImp.kt (1)

40-62: LGTM - Transfer logic and error handling look good.

The routing based on TransferType is clear, and the generic server error message improves user experience by not exposing internal details.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ThirdPartyTransferRepositoryImp.kt (1)

30-33: LGTM - Clean flow-based implementation.

The method correctly wraps the API call with asDataStateFlow() and applies flowOn(ioDispatcher) for proper dispatcher context.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/SavingsAccountRepositoryImp.kt (1)

52-60: LGTM - New transaction details method.

Clean implementation following the established pattern with proper flow composition.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt (1)

29-34: Refactoring to DataManagerProvider looks good.

The abstraction allows for better testability by enabling injection of fake implementations.

core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/repoTests/AccountsRepositoryTest.kt (3)

29-53: Well-structured test setup with fakes.

Good use of FakeDataManager and FakeClientService for test isolation. The UnconfinedTestDispatcher is appropriate for synchronous test execution.


55-79: Comprehensive success case test.

Good coverage of the happy path with proper Turbine flow testing and assertions on all account types.


117-141: Error scenario test is appropriate.

Validates that the repository correctly propagates errors from the underlying service.

core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ShareAccountRepositoryImp.kt (1)

31-36: Refactoring to DataManagerProvider is correct.

The abstraction pattern is consistent with other repository implementations.

core/network/src/commonMain/kotlin/org/mifos/mobile/core/network/DataManager.kt (2)

12-14: Clean interface implementation.

DataManager now properly implements DataManagerProvider, enabling dependency injection of the interface for better testability.


16-40: Lazy initialization with interface overrides looks good.

All API properties correctly override the interface declarations while preserving lazy initialization for deferred instantiation.

Comment on lines +36 to +38
val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
"LoanAccountsListService must be provided"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Make loanAccountsListApi private to preserve encapsulation.

This property is an internal implementation detail and should not be exposed publicly. Same issue applies to other repository implementations in this PR.

🔎 Proposed fix
-    val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
+    private val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
         "LoanAccountsListService must be provided"
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
"LoanAccountsListService must be provided"
}
private val loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
"LoanAccountsListService must be provided"
}
🤖 Prompt for AI Agents
In
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/LoanRepositoryImp.kt
around lines 36 to 38, the loanAccountsListApi property is currently declared as
a public val; make it a private val to preserve encapsulation so implementation
details aren't exposed. Update the declaration to private val
loanAccountsListApi = requireNotNull(dataManager.loanAccountsListApi) {
"LoanAccountsListService must be provided" } and run a quick search for
similarly exposed API properties in other repository implementations in this PR
and change them to private as well.

Comment on lines +42 to 49
val response = loanAccountsListApi.createLoansAccount(loansPayload)
println("response $response")
return@withContext DataState.Success("Loan Created Successfully")
}
LoanState.UPDATE -> {
val response = dataManager.loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
val response = loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
println("response $response")
return@withContext DataState.Success("Loan Updated Successfully")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug println statements.

Debug print statements should not remain in production code. Consider using structured logging if runtime diagnostics are needed, or remove these statements entirely.

🔎 Proposed fix
                     LoanState.CREATE -> {
                         val response = loanAccountsListApi.createLoansAccount(loansPayload)
-                        println("response $response")
                         return@withContext DataState.Success("Loan Created Successfully")
                     }
                     LoanState.UPDATE -> {
                         val response = loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
-                        println("response $response")
                         return@withContext DataState.Success("Loan Updated Successfully")
                     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val response = loanAccountsListApi.createLoansAccount(loansPayload)
println("response $response")
return@withContext DataState.Success("Loan Created Successfully")
}
LoanState.UPDATE -> {
val response = dataManager.loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
val response = loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
println("response $response")
return@withContext DataState.Success("Loan Updated Successfully")
val response = loanAccountsListApi.createLoansAccount(loansPayload)
return@withContext DataState.Success("Loan Created Successfully")
}
LoanState.UPDATE -> {
val response = loanAccountsListApi.updateLoanAccount(loanId, loansPayload)
return@withContext DataState.Success("Loan Updated Successfully")
🤖 Prompt for AI Agents
In
core/data/src/commonMain/kotlin/org/mifos/mobile/core/data/repositoryImpl/ReviewLoanApplicationRepositoryImpl.kt
around lines 42 to 49, remove the debug println("response $response") statements
left in both the CREATE and UPDATE branches; either delete them or replace them
with a structured logger call (e.g., logger.debug or equivalent) that does not
print to stdout, and add the appropriate logger import/initialization if you opt
to log instead.

Comment on lines +14 to +24
object JsonLoader {

val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
}

inline fun <reified T> fromResource(path: String): T {
return json.decodeFromString(readResource(path))
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find TestResourceReader files (expect/actual pattern)
find . -name "*TestResourceReader*" -type f | head -20

Repository: openMF/mifos-mobile

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Search for the readResource function and its implementations
rg "readResource" -A 3 -B 1 --type kotlin | head -80

Repository: openMF/mifos-mobile

Length of output: 3127


🏁 Script executed:

#!/bin/bash
# Check for JS test configurations
rg -n "jsTest|jsCommonTest" --type gradle,kts | head -30

Repository: openMF/mifos-mobile

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Search for gradle files and check for JS test configurations
find . -name "*.gradle.kts" -o -name "*.gradle" | head -10

Repository: openMF/mifos-mobile

Length of output: 381


🏁 Script executed:

#!/bin/bash
# Check core/data build configuration for JS platform tests
grep -r "js" build.gradle.kts 2>/dev/null | head -20

Repository: openMF/mifos-mobile

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Look at the build.gradle.kts in core/data directory
cat core/data/build.gradle.kts | grep -A 5 -B 5 "js\|jsTest" | head -50

Repository: openMF/mifos-mobile

Length of output: 518


🏁 Script executed:

#!/bin/bash
# Get the full build.gradle.kts file for core/data to see test configuration
cat core/data/build.gradle.kts

Repository: openMF/mifos-mobile

Length of output: 1680


🏁 Script executed:

#!/bin/bash
# Also check if there's any CI configuration that runs JS tests
find . -name ".github" -o -name ".gitlab-ci.yml" -o -name "*.yaml" 2>/dev/null | grep -i "ci\|github\|gitlab" | head -10

Repository: openMF/mifos-mobile

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Check CI workflows to see which platforms are tested
cat .github/workflows/upload-demo-app-on-firebase-kmp.yaml

Repository: openMF/mifos-mobile

Length of output: 1421


🏁 Script executed:

#!/bin/bash
# Also check the kmp.library.convention plugin to see which platforms are enabled by default
find . -path "*/convention/*" -name "*.gradle.kts" | xargs grep -l "js\|platform" | head -5

Repository: openMF/mifos-mobile

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for kmp.library.convention to see which platforms are enabled
find . -path "*/convention/*" -name "*.gradle.kts" | xargs cat | grep -A 20 -B 5 "kotlin {" | head -100

Repository: openMF/mifos-mobile

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check which modules actually have JS targets enabled
rg "js\(\)" . --type kotlin --type gradle-kts | head -20

Repository: openMF/mifos-mobile

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Find and examine the kmp.library.convention plugin
find . -path "*/build-logic/*" -name "*.gradle.kts" | xargs ls -la

Repository: openMF/mifos-mobile

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Search for any JS platform definitions in gradle files
rg "js\(\)|jsTest|@OptIn.*JS" . --type kts | head -30

Repository: openMF/mifos-mobile

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Check what the kmp library convention file contains
cat build-logic/convention/src/main/kotlin/kmp.library.convention.gradle.kts 2>/dev/null || find build-logic -name "*kmp*library*" -type f

Repository: openMF/mifos-mobile

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search more broadly for gradle convention files
find build-logic -name "*.gradle.kts" -exec cat {} \; | head -200

Repository: openMF/mifos-mobile

Length of output: 4900


🏁 Script executed:

#!/bin/bash
# Look for actual platform configuration in any gradle file
grep -r "androidTarget\|jsTarget\|iosTarget" . --include="*.gradle.kts" | head -20

Repository: openMF/mifos-mobile

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Check if there are any references to JS platform tests in the codebase
rg "jsTest|jsCommon" . --type yaml --type toml --type md | head -20

Repository: openMF/mifos-mobile

Length of output: 827


🏁 Script executed:

#!/bin/bash
# Find the KMPLibraryConventionPlugin implementation
find build-logic -name "*KMPLibrary*" -type f

Repository: openMF/mifos-mobile

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Search for the plugin source files
find build-logic/convention/src -type f -name "*.kt" | head -20

Repository: openMF/mifos-mobile

Length of output: 1531


🏁 Script executed:

#!/bin/bash
# Look for kotlin multiplatform platform definitions
cat build-logic/convention/src/main/kotlin/KMPLibraryConventionPlugin.kt 2>/dev/null || find build-logic -name "*.kt" -exec grep -l "kotlin.*multiplatform\|androidTarget\|jsTarget" {} \;

Repository: openMF/mifos-mobile

Length of output: 1742


🏁 Script executed:

#!/bin/bash
# Find the configureKotlinMultiplatform function
grep -r "fun configureKotlinMultiplatform" build-logic --include="*.kt"

Repository: openMF/mifos-mobile

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for the multiplatform configuration file
find build-logic -name "*.kt" -exec grep -l "configureKotlinMultiplatform\|androidTarget\|iosTarget\|jsTarget" {} \;

Repository: openMF/mifos-mobile

Length of output: 210


🏁 Script executed:

#!/bin/bash
# Examine the KotlinMultiplatform.kt configuration file
cat build-logic/convention/src/main/kotlin/org/mifos/mobile/KotlinMultiplatform.kt

Repository: openMF/mifos-mobile

Length of output: 952


The readResource implementation for JS platform must be completed before JS tests can run.

The JSON loader utility is clean, but the associated readResource function in TestResourceReader.jsCommon.kt contains TODO("Not yet implemented"). Since the Kotlin multiplatform configuration explicitly includes the JS target (js(IR) { nodejs(); binaries.executable() }), any JS tests attempting to use JsonLoader.fromResource() will fail at runtime with a NotImplementedError.

🤖 Prompt for AI Agents
In
core/data/src/commonTest/kotlin/org/mifos/mobile/core/data/utils/JsonLoader.kt
(lines 14-24) the JSON loader calls readResource which is unimplemented for the
JS target; implement the JS platform function in TestResourceReader.jsCommon.kt
to synchronously read the test resource file and return its contents as a String
(use Node APIs since the JS target is nodejs):
require("fs").readFileSync(resolvedPath, "utf8") where resolvedPath is built
from process.cwd() or the known test resources folder (e.g. process.cwd() +
"/src/commonTest/resources/" + path), handle any file-not-found I/O errors by
rethrowing or converting to a descriptive exception, and ensure the function
signature returns String so JsonLoader.fromResource works in JS tests.

Comment on lines +12 to +17
actual fun readResource(path: String): String {
return object {}.javaClass
.classLoader!!
.getResource(path)!!
.readText()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Improve error handling to match Android implementation.

The multiple non-null assertions (!!) will throw unhelpful NPEs when resources are missing. The Android implementation uses requireNotNull with a descriptive error message, which provides better debugging context.

🔎 Proposed fix to align with Android's error handling
 actual fun readResource(path: String): String {
-    return object {}.javaClass
-        .classLoader!!
-        .getResource(path)!!
-        .readText()
+    return requireNotNull(
+        object {}.javaClass.classLoader
+            ?.getResourceAsStream(path),
+    ) {
+        "Test resource not found: $path"
+    }.bufferedReader().use { it.readText() }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
actual fun readResource(path: String): String {
return object {}.javaClass
.classLoader!!
.getResource(path)!!
.readText()
}
actual fun readResource(path: String): String {
return requireNotNull(
object {}.javaClass.classLoader
?.getResourceAsStream(path),
) {
"Test resource not found: $path"
}.bufferedReader().use { it.readText() }
}
🤖 Prompt for AI Agents
In
core/data/src/desktopTest/kotlin/org/mifos/mobile/core/data/utils/TestResourceReader.desktop.kt
around lines 12 to 17, replace the chained non-null assertions with
requireNotNull checks that provide descriptive error messages: requireNotNull
for the classLoader with a message like "ClassLoader is null while reading test
resource", requireNotNull for the result of getResource(path) with "Resource not
found: <path>", then call readText() on the non-null resource; this mirrors the
Android implementation's safer, more debuggable error handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant