Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 83d8bceb authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

fix(source): report accurate rollback state in update exception

parent b33223db
Loading
Loading
Loading
Loading
+17 −5
Original line number Diff line number Diff line
@@ -70,24 +70,35 @@ class UpdateSourceSelectionCoordinator @Inject constructor(
        previousSelection: SourceSelection,
        originalFailure: Exception,
    ): Nothing {
        rollbackSourceSelection(previousSelection, originalFailure)
        val restoredSelection = rollbackSourceSelection(
            requestedSelection = requestedSelection,
            previousSelection = previousSelection,
            originalFailure = originalFailure,
        )
        throw SourceSelectionUpdateException(
            requestedSelection = requestedSelection,
            restoredSelection = previousSelection,
            restoredSelection = restoredSelection,
            cause = originalFailure,
        )
    }

    private suspend fun rollbackSourceSelection(
        requestedSelection: SourceSelection,
        previousSelection: SourceSelection,
        originalFailure: Exception,
    ) {
        try {
    ): SourceSelection {
        // The new selection was already persisted before this rollback ran (otherwise
        // applySourceSelectionSideEffects would not have been entered), so a failure to
        // re-write the previous selection leaves the requested selection persisted.
        // restoredSelection must reflect what is actually persisted on disk so callers do not
        // misreport the post-failure state to the user.
        val restoredSelection = try {
            sourceSelectionRepository.saveSourceSelection(previousSelection)
            previousSelection
        } catch (exception: IllegalStateException) {
            originalFailure.addSuppressed(exception)
            Timber.w(exception, "Failed to restore previous source selection")
            return
            return requestedSelection
        }

        runRollbackStep(
@@ -105,6 +116,7 @@ class UpdateSourceSelectionCoordinator @Inject constructor(
            sessionStateController.clearLoadedSessions()
            sessionStateController.refreshSessions()
        }
        return restoredSelection
    }

    private suspend fun runRollbackStep(
+36 −0
Original line number Diff line number Diff line
@@ -136,6 +136,38 @@ class UpdateSourceSelectionCoordinatorTest {
        }
    }

    @Test
    fun `invoke reports requested selection as restored when rollback save fails`() = runTest {
        val pendingUpdatesRepository = FakePendingUpdatesRepository()
        val sourceSelectionRepository = FakeSourceSelectionRepository(
            initialSourceSelection = SourceSelection.DEFAULT,
            saveFailureOnSelection = SourceSelection.DEFAULT,
        )
        val periodicUpdatesScheduler = mockk<PeriodicUpdatesScheduler>()
        val sessionStateController = FakeSessionStateController()
        val coordinator = UpdateSourceSelectionCoordinator(
            pendingUpdatesRepository = pendingUpdatesRepository,
            periodicUpdatesScheduler = periodicUpdatesScheduler,
            sessionStateController = sessionStateController,
            sourceSelectionRepository = sourceSelectionRepository,
        )
        val updatedSelection = SourceSelection.OPEN_SOURCE_AND_PWA
        coEvery {
            periodicUpdatesScheduler.syncPeriodicUpdates(
                existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
            )
        } throws IOException("schedule failed")

        val exception = kotlin.test.assertFailsWith<SourceSelectionUpdateException> {
            coordinator(updatedSelection)
        }

        assertThat(exception.requestedSelection).isEqualTo(updatedSelection)
        assertThat(exception.restoredSelection).isEqualTo(updatedSelection)
        assertThat(sourceSelectionRepository.currentSourceSelection()).isEqualTo(updatedSelection)
        assertThat(exception.cause?.suppressed?.toList()).hasSize(1)
    }

    private class FakePendingUpdatesRepository : PendingUpdatesRepository {
        var clearPendingUpdatesCallCount = 0

@@ -146,6 +178,7 @@ class UpdateSourceSelectionCoordinatorTest {

    private class FakeSourceSelectionRepository(
        initialSourceSelection: SourceSelection,
        private val saveFailureOnSelection: SourceSelection? = null,
    ) : SourceSelectionRepository {
        private val sourceSelectionState = MutableStateFlow(initialSourceSelection)

@@ -154,6 +187,9 @@ class UpdateSourceSelectionCoordinatorTest {
        override fun currentSourceSelection(): SourceSelection = sourceSelectionState.value

        override fun saveSourceSelection(sourceSelection: SourceSelection) {
            if (sourceSelection == saveFailureOnSelection) {
                throw IllegalStateException("save failed")
            }
            sourceSelectionState.value = sourceSelection
        }
    }