Sync notifications using WorkManager (#5545)

* Initial implementation of notification sync using `WorkManager`

* Use custom `MetroWorkerFactory` to allow assisted injection in WorkManager Workers

* Add tests for `FetchNotificationWorker`. Create `FakeNotificationResolverQueue` to help testing.

* Add more tests, fix Konsist checks

* Add tests for `SyncNotificationWorkManagerRequest`

* Simplify `FakeNotificationResolverQueue`
This commit is contained in:
Jorge Martin Espinosa
2025-10-17 11:51:27 +02:00
committed by GitHub
parent f3d75ee85c
commit ebe94f873e
38 changed files with 968 additions and 98 deletions
+2
View File
@@ -34,10 +34,12 @@ dependencies {
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api)
implementation(projects.libraries.workmanager.api)
api(projects.features.logout.api)
testCommonDependencies(libs, true)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.libraries.workmanager.test)
}
@@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.workmanager.api.WorkManagerScheduler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -34,6 +35,7 @@ import kotlinx.coroutines.launch
class LogoutPresenter(
private val matrixClient: MatrixClient,
private val encryptionService: EncryptionService,
private val workManagerScheduler: WorkManagerScheduler,
) : Presenter<LogoutState> {
@Composable
override fun present(): LogoutState {
@@ -109,6 +111,9 @@ class LogoutPresenter(
ignoreSdkError: Boolean,
) = launch {
suspend {
// Cancel any pending work (e.g. notification sync)
workManagerScheduler.cancel(matrixClient.sessionId)
matrixClient.logout(userInitiated = true, ignoreSdkError)
}.runCatchingUpdatingState(logoutAction)
}
@@ -14,6 +14,7 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EncryptionService
@@ -21,7 +22,9 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest
@@ -145,7 +148,9 @@ class LogoutPresenterTest {
@Test
fun `present - logout then confirm`() = runTest {
val presenter = createLogoutPresenter()
val cancelWorkManagerJobsLambda = lambdaRecorder<SessionId, Unit> {}
val workManagerScheduler = FakeWorkManagerScheduler(cancelLambda = cancelWorkManagerJobsLambda)
val presenter = createLogoutPresenter(workManagerScheduler = workManagerScheduler)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
@@ -158,6 +163,8 @@ class LogoutPresenterTest {
assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java)
val successState = awaitItem()
assertThat(successState.logoutAction).isInstanceOf(AsyncAction.Success::class.java)
cancelWorkManagerJobsLambda.assertions().isCalledOnce()
}
}
@@ -230,7 +237,9 @@ class LogoutPresenterTest {
internal fun createLogoutPresenter(
matrixClient: MatrixClient = FakeMatrixClient(),
encryptionService: EncryptionService = FakeEncryptionService(),
workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(cancelLambda = {}),
): LogoutPresenter = LogoutPresenter(
matrixClient = matrixClient,
encryptionService = encryptionService,
workManagerScheduler = workManagerScheduler,
)
@@ -163,7 +163,7 @@ class DefaultBugReporterTest {
assertThat(foundValues["file"]).contains(fakePushRules)
// device_key now added given they are not null
// so is the push_rules value
// so is the file value for the included push_rules
assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE + 2)
server.shutdown()