From b50f676f2d7071a8b33b02a125a8a32c1565a74a Mon Sep 17 00:00:00 2001 From: Girish Suragani Date: Tue, 28 Jan 2025 12:31:25 +0530 Subject: [PATCH] NTP-30344 | Girish Suragani | Test cases for RefreshAndSyncDataUseCase (#14634) Co-authored-by: abhinav-g_navi Co-authored-by: venkat-praneeth_navi --- .../usecase/RefreshAndSyncDataUseCaseTest.kt | 229 ++++++++++++++++++ .../testsetup/DataSyncHelperTest.kt | 77 ++++++ 2 files changed, 306 insertions(+) create mode 100644 android/navi-money-manager/src/test/java/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCaseTest.kt create mode 100644 android/navi-money-manager/src/test/java/com/navi/moneymanager/testsetup/DataSyncHelperTest.kt diff --git a/android/navi-money-manager/src/test/java/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCaseTest.kt b/android/navi-money-manager/src/test/java/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCaseTest.kt new file mode 100644 index 0000000000..d41eac287b --- /dev/null +++ b/android/navi-money-manager/src/test/java/com/navi/moneymanager/postonboard/dashboard/usecase/RefreshAndSyncDataUseCaseTest.kt @@ -0,0 +1,229 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.postonboard.dashboard.usecase + +import com.navi.common.network.models.RepoResult +import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.dataprovider.data.datastore.DataStoreInfoProvider +import com.navi.moneymanager.common.dataprovider.domain.RemoteDataProvider +import com.navi.moneymanager.common.datasync.DBSyncExecutor +import com.navi.moneymanager.common.datasync.SyncStatus +import com.navi.moneymanager.common.datasync.model.DataSyncState +import com.navi.moneymanager.common.network.di.RoomDataStoreInfoProvider +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.utils.Constants.LAST_REFRESH_SUCCESSFUL_TIMESTAMP +import com.navi.moneymanager.testsetup.DataSyncHelperTest +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkAll +import junit.framework.TestCase.assertFalse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class RefreshAndSyncDataUseCaseTest { + + @RelaxedMockK + @RoomDataStoreInfoProvider + private lateinit var dbDataStoreProvider: DataStoreInfoProvider + + @RelaxedMockK private lateinit var remoteDataProvider: RemoteDataProvider + + @RelaxedMockK private lateinit var dBSyncExecutor: DBSyncExecutor + + private val dataSyncHelperTest = DataSyncHelperTest() + + private val testDispatcher = StandardTestDispatcher() + + private lateinit var refreshAndSyncDataUseCase: RefreshAndSyncDataUseCase + + @Before + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + Dispatchers.setMain(testDispatcher) + mockkObject(DataSyncEventTrackerImpl) + refreshAndSyncDataUseCase = + RefreshAndSyncDataUseCase( + remoteDataProvider = remoteDataProvider, + dbDataStoreProvider = dbDataStoreProvider, + dBSyncExecutor = dBSyncExecutor, + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + unmockkAll() + } + + @Test + fun `execute should refresh data and start polling`() = runBlocking { + val mockResponse = mockk(relaxed = true) + val refreshDataResponse = dataSyncHelperTest.createRefreshDataResponse() + val mockSyncStatusFlow = + dataSyncHelperTest.createMockSyncStatusFlow( + DataSyncState.Completed, + DataSyncState.Completed, + ) + val isRefreshingFlow = refreshAndSyncDataUseCase.isRefreshing + val refreshingStates = mutableListOf() + + coEvery { remoteDataProvider.refreshData() } returns refreshDataResponse + every { dBSyncExecutor.dataSyncStatus } returns mockSyncStatusFlow + every { dBSyncExecutor.initDBSyncExecutor(any()) } just Runs + dataSyncHelperTest.mockEventTrackers() + + val job = launch { refreshAndSyncDataUseCase.execute(this, mockResponse) } + val job2 = launch { isRefreshingFlow.collect { refreshingStates.add(it) } } + + coVerify { remoteDataProvider.refreshData() } + coVerify { dbDataStoreProvider.saveLongData(LAST_REFRESH_SUCCESSFUL_TIMESTAMP, 1000L) } + assertFalse(refreshingStates.last()) + + job.cancelAndJoin() + job2.cancelAndJoin() + } + + @Test + fun `execute should not start polling if response data is null`() = runBlocking { + val mockResponse = mockk(relaxed = true) + coEvery { remoteDataProvider.refreshData() } returns RepoResult(null) + + refreshAndSyncDataUseCase.execute(this, mockResponse) + + coVerify { remoteDataProvider.refreshData() } + coVerify(exactly = 0) { dbDataStoreProvider.saveLongData(any(), any()) } + } + + @Test + fun `execute should stop polling if account sync fails`() = runBlocking { + val mockResponse = dataSyncHelperTest.createMockConfigResponse() + val refreshDataResponse = dataSyncHelperTest.createRefreshDataResponse() + val mockSyncStatusFlow = + dataSyncHelperTest.createMockSyncStatusFlow( + DataSyncState.InProgress, + DataSyncState.InProgress, + ) + val pollingStatusResponse = + dataSyncHelperTest.createPollingStatusResponse( + accountDetailsStatus = SyncStatus.FAILED.name + ) + + coEvery { remoteDataProvider.refreshData() } returns refreshDataResponse + coEvery { remoteDataProvider.pollSyncStatus(any()) } returns pollingStatusResponse + every { dBSyncExecutor.dataSyncStatus } returns mockSyncStatusFlow + every { dBSyncExecutor.initDBSyncExecutor(any()) } just Runs + dataSyncHelperTest.mockEventTrackers() + + val job = launch { refreshAndSyncDataUseCase.execute(this, mockResponse) } + + coVerify { DataSyncEventTrackerImpl.pollingStopped() } + job.cancelAndJoin() + } + + @Test + fun `execute should stop polling if all transactions are synced`() = runBlocking { + val mockResponse = dataSyncHelperTest.createMockConfigResponse() + val refreshDataResponse = dataSyncHelperTest.createRefreshDataResponse() + val mockSyncStatusFlow = + dataSyncHelperTest.createMockSyncStatusFlow( + DataSyncState.InProgress, + DataSyncState.InProgress, + ) + val pollingStatusResponse = + dataSyncHelperTest.createPollingStatusResponse( + oldMonthTxnsStatus = SyncStatus.COMPLETED.name, + currMonthTxnsStatus = SyncStatus.COMPLETED.name, + accountDetailsStatus = SyncStatus.COMPLETED.name, + ) + + coEvery { remoteDataProvider.refreshData() } returns refreshDataResponse + coEvery { remoteDataProvider.pollSyncStatus(any()) } returns pollingStatusResponse + every { dBSyncExecutor.dataSyncStatus } returns mockSyncStatusFlow + every { dBSyncExecutor.initDBSyncExecutor(any()) } just Runs + dataSyncHelperTest.mockEventTrackers() + + val job = launch { refreshAndSyncDataUseCase.execute(this, mockResponse) } + + coVerify { DataSyncEventTrackerImpl.pollingStopped() } + job.cancelAndJoin() + } + + @Test + fun `execute should stop polling when all transactions sync failed`() = runBlocking { + val mockResponse = dataSyncHelperTest.createMockConfigResponse() + val refreshDataResponse = dataSyncHelperTest.createRefreshDataResponse() + val mockSyncStatusFlow = + dataSyncHelperTest.createMockSyncStatusFlow( + DataSyncState.InProgress, + DataSyncState.InProgress, + ) + val pollingStatusResponse = + dataSyncHelperTest.createPollingStatusResponse( + accountDetailsStatus = SyncStatus.COMPLETED.name, + oldMonthTxnsStatus = SyncStatus.FAILED.name, + currMonthTxnsStatus = SyncStatus.FAILED.name, + ) + + coEvery { remoteDataProvider.refreshData() } returns refreshDataResponse + coEvery { remoteDataProvider.pollSyncStatus(any()) } returns pollingStatusResponse + every { dBSyncExecutor.dataSyncStatus } returns mockSyncStatusFlow + every { dBSyncExecutor.initDBSyncExecutor(any()) } just Runs + dataSyncHelperTest.mockEventTrackers() + + val job = launch { refreshAndSyncDataUseCase.execute(this, mockResponse) } + + coVerify { DataSyncEventTrackerImpl.pollingStopped() } + job.cancelAndJoin() + } + + @Test + fun `execute should stop polling when current month sync failed but all months sync completed`() = + runBlocking { + val mockResponse = dataSyncHelperTest.createMockConfigResponse() + val refreshDataResponse = dataSyncHelperTest.createRefreshDataResponse() + val mockSyncStatusFlow = + dataSyncHelperTest.createMockSyncStatusFlow( + DataSyncState.InProgress, + DataSyncState.InProgress, + ) + val pollingStatusResponse = + dataSyncHelperTest.createPollingStatusResponse( + accountDetailsStatus = SyncStatus.COMPLETED.name, + oldMonthTxnsStatus = SyncStatus.COMPLETED.name, + currMonthTxnsStatus = SyncStatus.FAILED.name, + ) + + coEvery { remoteDataProvider.refreshData() } returns refreshDataResponse + coEvery { remoteDataProvider.pollSyncStatus(any()) } returns pollingStatusResponse + every { dBSyncExecutor.dataSyncStatus } returns mockSyncStatusFlow + every { dBSyncExecutor.initDBSyncExecutor(any()) } just Runs + dataSyncHelperTest.mockEventTrackers() + + val job = launch { refreshAndSyncDataUseCase.execute(this, mockResponse) } + + coVerify { DataSyncEventTrackerImpl.pollingStopped() } + job.cancelAndJoin() + } +} diff --git a/android/navi-money-manager/src/test/java/com/navi/moneymanager/testsetup/DataSyncHelperTest.kt b/android/navi-money-manager/src/test/java/com/navi/moneymanager/testsetup/DataSyncHelperTest.kt new file mode 100644 index 0000000000..2a7e65a45e --- /dev/null +++ b/android/navi-money-manager/src/test/java/com/navi/moneymanager/testsetup/DataSyncHelperTest.kt @@ -0,0 +1,77 @@ +/* + * + * * Copyright © 2025 by Navi Technologies Limited + * * All rights reserved. Strictly confidential + * + */ + +package com.navi.moneymanager.testsetup + +import com.navi.common.network.models.RepoResult +import com.navi.moneymanager.common.analytics.DataSyncEventTrackerImpl +import com.navi.moneymanager.common.datasync.model.DataSyncState +import com.navi.moneymanager.common.datasync.model.DataSyncStatus +import com.navi.moneymanager.common.network.model.MMConfigResponse +import com.navi.moneymanager.common.network.model.PollingConfig +import com.navi.moneymanager.common.network.model.PollingStatusResponse +import com.navi.moneymanager.common.network.model.RefreshDataResponse +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +class DataSyncHelperTest @Inject constructor() { + + fun createRefreshDataResponse(): RepoResult { + return RepoResult( + data = + RefreshDataResponse( + requestId = "testRequestId", + lastRefreshSuccessTimestamp = 1000L, + isAARefreshApplicable = true, + ) + ) + } + + fun createMockSyncStatusFlow( + allMonthsStatus: DataSyncState, + currentMonthStatus: DataSyncState, + ): MutableStateFlow { + return MutableStateFlow( + DataSyncStatus( + allMonthsSyncStatus = allMonthsStatus, + currentMonthSyncStatus = currentMonthStatus, + ) + ) + } + + fun createMockConfigResponse(): MMConfigResponse { + return MMConfigResponse(dataSyncPollingConfig = PollingConfig(10, 0, 5)) + } + + fun createPollingStatusResponse( + oldMonthTxnsStatus: String? = null, + currMonthTxnsStatus: String? = null, + accountDetailsStatus: String? = null, + ): RepoResult { + return RepoResult( + data = + PollingStatusResponse( + oldMonthTxnsStatus = oldMonthTxnsStatus, + currMonthTxnsStatus = currMonthTxnsStatus, + accountDetailsStatus = accountDetailsStatus, + ) + ) + } + + fun mockEventTrackers() { + every { + DataSyncEventTrackerImpl.refreshStarted(any(), any()) + DataSyncEventTrackerImpl.periodicTaskSchedulerCreated() + DataSyncEventTrackerImpl.pollingStarted() + DataSyncEventTrackerImpl.pollingTimeout() + DataSyncEventTrackerImpl.pollingStopped() + } just Runs + } +}