mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-12 11:37:52 +01:00
Improve remote restore test coverage in regV5.
This commit is contained in:
committed by
Michelle Tang
parent
bd42444e00
commit
a4a6117788
+33
-11
@@ -81,6 +81,7 @@ class RemoteBackupRestoreViewModel(
|
||||
repository.restoreRemoteBackup(_state.value.aep).collect { progress ->
|
||||
when (progress) {
|
||||
is RemoteBackupRestoreProgress.Downloading -> {
|
||||
Log.i(TAG, "[restoreBackup] Restoring...")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.InProgress,
|
||||
restoreProgress = RemoteBackupRestoreState.RestoreProgress(
|
||||
@@ -91,6 +92,7 @@ class RemoteBackupRestoreViewModel(
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.Restoring -> {
|
||||
Log.i(TAG, "[restoreBackup] Restoring...")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.InProgress,
|
||||
restoreProgress = RemoteBackupRestoreState.RestoreProgress(
|
||||
@@ -101,6 +103,7 @@ class RemoteBackupRestoreViewModel(
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.Finalizing -> {
|
||||
Log.i(TAG, "[restoreBackup] Finalizing...")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.InProgress,
|
||||
restoreProgress = RemoteBackupRestoreState.RestoreProgress(
|
||||
@@ -111,7 +114,7 @@ class RemoteBackupRestoreViewModel(
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.Complete -> {
|
||||
Log.i(TAG, "Remote restore completed successfully")
|
||||
Log.i(TAG, "[restoreBackup] Remote restore completed successfully.")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.Restored,
|
||||
restoreProgress = null
|
||||
@@ -121,35 +124,35 @@ class RemoteBackupRestoreViewModel(
|
||||
repository.finishRegistrationOrCreateProfile(parentEventEmitter)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.NetworkError -> {
|
||||
Log.w(TAG, "Remote restore failed with network error", progress.cause)
|
||||
Log.w(TAG, "[restoreBackup] Remote restore failed with network error.", progress.cause)
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.NetworkFailure,
|
||||
restoreProgress = null
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.InvalidBackupVersion -> {
|
||||
Log.w(TAG, "Remote restore failed: invalid backup version")
|
||||
Log.w(TAG, "[restoreBackup] Remote restore failed: invalid backup version.")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.InvalidBackupVersion,
|
||||
restoreProgress = null
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.PermanentSvrBFailure -> {
|
||||
Log.w(TAG, "Remote restore failed: permanent SVR-B failure")
|
||||
Log.w(TAG, "[restoreBackup] Remote restore failed: permanent SVRB failure.")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.PermanentSvrBFailure,
|
||||
restoreProgress = null
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.Canceled -> {
|
||||
Log.w(TAG, "Remote restore was canceled")
|
||||
Log.w(TAG, "[restoreBackup] Remote restore was canceled.")
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.Failed,
|
||||
restoreProgress = null
|
||||
)
|
||||
}
|
||||
is RemoteBackupRestoreProgress.GenericError -> {
|
||||
Log.w(TAG, "Remote restore failed", progress.cause)
|
||||
Log.w(TAG, "[restoreBackup] Remote restore failed.", progress.cause)
|
||||
_state.value = _state.value.copy(
|
||||
restoreState = RemoteBackupRestoreState.RestoreState.Failed,
|
||||
restoreProgress = null
|
||||
@@ -170,10 +173,9 @@ class RemoteBackupRestoreViewModel(
|
||||
|
||||
when (result) {
|
||||
is RequestResult.Success -> {
|
||||
Log.i(TAG, "[loadBackupInfo] Successfully fetched backup info.")
|
||||
val info = result.result
|
||||
|
||||
// parentEventEmitter(RegistrationFlowEvent)
|
||||
|
||||
val lastModifiedResult = withContext(ioDispatcher) {
|
||||
repository.getBackupFileLastModified(_state.value.aep, info)
|
||||
}
|
||||
@@ -193,15 +195,35 @@ class RemoteBackupRestoreViewModel(
|
||||
)
|
||||
}
|
||||
is RequestResult.NonSuccess -> {
|
||||
_state.value = when (result.error) {
|
||||
is NetworkController.GetBackupInfoError.NoBackup -> _state.value.copy(loadState = RemoteBackupRestoreState.LoadState.NotFound)
|
||||
else -> _state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
_state.value = when (val error = result.error) {
|
||||
is NetworkController.GetBackupInfoError.NoBackup -> {
|
||||
Log.w(TAG, "[loadBackupInfo] No backup found.")
|
||||
_state.value.copy(loadState = RemoteBackupRestoreState.LoadState.NotFound)
|
||||
}
|
||||
is NetworkController.GetBackupInfoError.BadArguments -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Failed with bad arguments. Body: ${error.body}")
|
||||
_state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
is NetworkController.GetBackupInfoError.BadAuthCredential -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Bad auth credential. Body: ${error.body}")
|
||||
_state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
is NetworkController.GetBackupInfoError.Forbidden -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Forbidden. Body: ${error.body}")
|
||||
_state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
is NetworkController.GetBackupInfoError.RateLimited -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Rate limited. Try again in: ${error.retryAfter}")
|
||||
_state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
is RequestResult.RetryableNetworkError -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Hit network error.", result.networkError)
|
||||
_state.value = _state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
is RequestResult.ApplicationError -> {
|
||||
Log.w(TAG, "[loadBackupInfo] Hit unexpected error.", result.cause)
|
||||
_state.value = _state.value.copy(loadState = RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
}
|
||||
|
||||
+292
-24
@@ -18,6 +18,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -31,6 +33,8 @@ import org.signal.registration.NetworkController
|
||||
import org.signal.registration.RegistrationFlowEvent
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RestoreDecision
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RemoteBackupRestoreViewModelTest {
|
||||
@@ -72,7 +76,22 @@ class RemoteBackupRestoreViewModelTest {
|
||||
)
|
||||
}
|
||||
|
||||
// ==================== BackupRestoreBackup Tests ====================
|
||||
/** Keeps the [WhileSubscribed] `state` flow hot and records every emission for assertions. */
|
||||
private fun TestScope.collectStatesOf(viewModel: RemoteBackupRestoreViewModel): List<RemoteBackupRestoreState> {
|
||||
val states = mutableListOf<RemoteBackupRestoreState>()
|
||||
backgroundScope.launch { viewModel.state.collect { states.add(it) } }
|
||||
return states
|
||||
}
|
||||
|
||||
private fun backupInfo(usedSpace: Long? = 1024L) = NetworkController.GetBackupInfoResponse(
|
||||
cdn = 3,
|
||||
backupDir = "dir",
|
||||
mediaDir = "media",
|
||||
backupName = "backup",
|
||||
usedSpace = usedSpace
|
||||
)
|
||||
|
||||
// ==================== BackupRestoreBackup ====================
|
||||
|
||||
@Test
|
||||
fun `BackupRestoreBackup emits InProgress state and triggers restore`() = runTest(testDispatcher) {
|
||||
@@ -90,7 +109,7 @@ class RemoteBackupRestoreViewModelTest {
|
||||
coVerify { mockRepository.restoreRemoteBackup(aep) }
|
||||
}
|
||||
|
||||
// ==================== Cancel Tests ====================
|
||||
// ==================== Cancel ====================
|
||||
|
||||
@Test
|
||||
fun `Cancel emits NavigateBack`() = runTest(testDispatcher) {
|
||||
@@ -103,7 +122,30 @@ class RemoteBackupRestoreViewModelTest {
|
||||
assertThat(emittedParentEvents.first()).isEqualTo(RegistrationFlowEvent.NavigateBack)
|
||||
}
|
||||
|
||||
// ==================== DismissError Tests ====================
|
||||
// ==================== Retry ====================
|
||||
|
||||
@Test
|
||||
fun `Retry reloads backup info and re-emits the current state`() = runTest(testDispatcher) {
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
val currentState = RemoteBackupRestoreState(aep = aep)
|
||||
|
||||
viewModel.applyEvent(currentState, RemoteBackupRestoreScreenEvents.Retry, stateEmitter)
|
||||
|
||||
coVerify(exactly = 2) { mockRepository.getRemoteBackupInfo(aep) }
|
||||
assertThat(emittedStates).hasSize(1)
|
||||
assertThat(states.last().loadAttempts).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init records a single load attempt`() = runTest(testDispatcher) {
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadAttempts).isEqualTo(1)
|
||||
}
|
||||
|
||||
// ==================== DismissError ====================
|
||||
|
||||
@Test
|
||||
fun `DismissError resets restoreState to None and clears progress`() = runTest(testDispatcher) {
|
||||
@@ -125,17 +167,11 @@ class RemoteBackupRestoreViewModelTest {
|
||||
assertThat(emittedStates.last().restoreProgress).isNull()
|
||||
}
|
||||
|
||||
// ==================== loadBackupInfo Tests ====================
|
||||
// ==================== loadBackupInfo ====================
|
||||
|
||||
@Test
|
||||
fun `init with successful backup info invokes getRemoteBackupInfo and getBackupFileLastModified`() = runTest(testDispatcher) {
|
||||
val info = NetworkController.GetBackupInfoResponse(
|
||||
cdn = 3,
|
||||
backupDir = "dir",
|
||||
mediaDir = "media",
|
||||
backupName = "backup",
|
||||
usedSpace = 1024L
|
||||
)
|
||||
val info = backupInfo()
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns RequestResult.Success(info)
|
||||
coEvery { mockRepository.getBackupFileLastModified(any(), any()) } returns RequestResult.Success(1234L)
|
||||
|
||||
@@ -146,19 +182,172 @@ class RemoteBackupRestoreViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with NoBackup error invokes getRemoteBackupInfo`() = runTest(testDispatcher) {
|
||||
fun `init with successful backup info moves to Loaded with size and time`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns RequestResult.Success(backupInfo(usedSpace = 2048L))
|
||||
coEvery { mockRepository.getBackupFileLastModified(any(), any()) } returns RequestResult.Success(99999L)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Loaded)
|
||||
assertThat(states.last().backupSize).isEqualTo(2048L)
|
||||
assertThat(states.last().backupTime).isEqualTo(99999L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with null usedSpace defaults backup size to zero`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns RequestResult.Success(backupInfo(usedSpace = null))
|
||||
coEvery { mockRepository.getBackupFileLastModified(any(), any()) } returns RequestResult.Success(1L)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Loaded)
|
||||
assertThat(states.last().backupSize).isEqualTo(0L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with successful info but failed last-modified lookup uses sentinel backup time`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns RequestResult.Success(backupInfo())
|
||||
coEvery { mockRepository.getBackupFileLastModified(any(), any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.NoBackup)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Loaded)
|
||||
assertThat(states.last().backupTime).isEqualTo(-1L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with NoBackup moves to NotFound`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.NoBackup)
|
||||
|
||||
createViewModel()
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
coVerify { mockRepository.getRemoteBackupInfo(aep) }
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.NotFound)
|
||||
}
|
||||
|
||||
// ==================== Restore Progress Tests ====================
|
||||
@Test
|
||||
fun `init with BadArguments moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.BadArguments())
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackupRestoreBackup Complete progress emits UserSuppliedAepVerified and hands off to finishRegistrationOrCreateProfile`() = runTest(testDispatcher) {
|
||||
fun `init with BadAuthCredential moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.BadAuthCredential())
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with Forbidden moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.Forbidden())
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with RateLimited moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.NonSuccess(NetworkController.GetBackupInfoError.RateLimited(30.seconds))
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with retryable network error moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.RetryableNetworkError(IOException("Network error"))
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `init with application error moves to Failure`() = runTest(testDispatcher) {
|
||||
coEvery { mockRepository.getRemoteBackupInfo(any()) } returns
|
||||
RequestResult.ApplicationError(RuntimeException("Unexpected"))
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
assertThat(states.last().loadState).isEqualTo(RemoteBackupRestoreState.LoadState.Failure)
|
||||
}
|
||||
|
||||
// ==================== Restore progress ====================
|
||||
|
||||
@Test
|
||||
fun `Downloading progress maps to InProgress with download bytes`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Downloading(bytesDownloaded = 30, totalBytes = 100)
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
val last = states.last()
|
||||
assertThat(last.restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.InProgress)
|
||||
assertThat(last.restoreProgress?.phase).isEqualTo(RemoteBackupRestoreState.RestoreProgress.Phase.Downloading)
|
||||
assertThat(last.restoreProgress?.bytesCompleted).isEqualTo(30L)
|
||||
assertThat(last.restoreProgress?.totalBytes).isEqualTo(100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Restoring progress maps to InProgress with restore bytes`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Restoring(bytesRead = 75, totalBytes = 100)
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
val last = states.last()
|
||||
assertThat(last.restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.InProgress)
|
||||
assertThat(last.restoreProgress?.phase).isEqualTo(RemoteBackupRestoreState.RestoreProgress.Phase.Restoring)
|
||||
assertThat(last.restoreProgress?.bytesCompleted).isEqualTo(75L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Finalizing progress maps to InProgress with finalizing phase`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Finalizing
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
val last = states.last()
|
||||
assertThat(last.restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.InProgress)
|
||||
assertThat(last.restoreProgress?.phase).isEqualTo(RemoteBackupRestoreState.RestoreProgress.Phase.Finalizing)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Complete progress emits UserSuppliedAepVerified and hands off to finishRegistrationOrCreateProfile`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Complete
|
||||
)
|
||||
@@ -179,21 +368,100 @@ class RemoteBackupRestoreViewModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BackupRestoreBackup NetworkError progress triggers restore and emits no parent events`() = runTest(testDispatcher) {
|
||||
fun `Complete progress moves restore state to Restored and clears progress`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Complete
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.Restored)
|
||||
assertThat(states.last().restoreProgress).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NetworkError progress moves restore state to NetworkFailure and emits no parent events`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.NetworkError()
|
||||
)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val initialState = RemoteBackupRestoreState(aep = aep)
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(
|
||||
initialState,
|
||||
RemoteBackupRestoreScreenEvents.BackupRestoreBackup,
|
||||
stateEmitter
|
||||
)
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
coVerify { mockRepository.restoreRemoteBackup(aep) }
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.NetworkFailure)
|
||||
assertThat(emittedParentEvents).hasSize(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `InvalidBackupVersion progress moves restore state to InvalidBackupVersion`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.InvalidBackupVersion
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.InvalidBackupVersion)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `PermanentSvrBFailure progress moves restore state to PermanentSvrBFailure`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.PermanentSvrBFailure
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.PermanentSvrBFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Canceled progress moves restore state to Failed`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Canceled
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.Failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GenericError progress moves restore state to Failed`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.GenericError()
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.Failed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a multi-phase progress sequence ends in Restored`() = runTest(testDispatcher) {
|
||||
every { mockRepository.restoreRemoteBackup(any()) } returns flowOf(
|
||||
RemoteBackupRestoreProgress.Downloading(bytesDownloaded = 10, totalBytes = 100),
|
||||
RemoteBackupRestoreProgress.Restoring(bytesRead = 60, totalBytes = 100),
|
||||
RemoteBackupRestoreProgress.Finalizing,
|
||||
RemoteBackupRestoreProgress.Complete
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
val states = collectStatesOf(viewModel)
|
||||
|
||||
viewModel.applyEvent(RemoteBackupRestoreState(aep = aep), RemoteBackupRestoreScreenEvents.BackupRestoreBackup, stateEmitter)
|
||||
|
||||
assertThat(states.last().restoreState).isEqualTo(RemoteBackupRestoreState.RestoreState.Restored)
|
||||
coVerify { mockRepository.finishRegistrationOrCreateProfile(parentEventEmitter, any()) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user