Skip to content

Commit

Permalink
fix: update api version on app start [WPB-14835] (#3788)
Browse files Browse the repository at this point in the history
Co-authored-by: Jakub Żerko <[email protected]>
  • Loading branch information
MohamadJaara and Garzas authored Jan 27, 2025
1 parent 038dc1b commit 8827eac
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCase
import com.wire.kalium.logic.feature.asset.GetAvatarAssetUseCase
import com.wire.kalium.logic.feature.client.FinalizeMLSClientAfterE2EIEnrollment
import com.wire.kalium.logic.feature.conversation.GetAllContactsNotInConversationUseCase
import com.wire.kalium.logic.feature.e2ei.CertificateRevocationListCheckWorker
import com.wire.kalium.logic.feature.e2ei.SyncCertificateRevocationListUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetMLSClientIdentityUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.IsOtherUserE2EIVerifiedUseCase
Expand Down Expand Up @@ -232,8 +232,8 @@ class UserModule {

@ViewModelScoped
@Provides
fun provideCertificateRevocationListCheckWorker(userScope: UserScope): CertificateRevocationListCheckWorker =
userScope.certificateRevocationListCheckWorker
fun provideCertificateRevocationListCheckWorker(userScope: UserScope): SyncCertificateRevocationListUseCase =
userScope.syncCertificateRevocationListUseCase

@ViewModelScoped
@Provides
Expand Down
68 changes: 54 additions & 14 deletions app/src/main/kotlin/com/wire/android/ui/home/AppSyncViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,73 @@
*/
package com.wire.android.ui.home

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.navigation.SavedStateViewModel
import com.wire.kalium.logic.feature.e2ei.CertificateRevocationListCheckWorker
import com.wire.android.appLogger
import com.wire.kalium.logic.feature.e2ei.SyncCertificateRevocationListUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.ObserveCertificateRevocationForSelfClientUseCase
import com.wire.kalium.logic.feature.featureConfig.FeatureFlagsSyncWorker
import com.wire.kalium.logic.feature.server.UpdateApiVersionsUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

@HiltViewModel
class AppSyncViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val certificateRevocationListCheckWorker: CertificateRevocationListCheckWorker,
private val syncCertificateRevocationListUseCase: SyncCertificateRevocationListUseCase,
private val observeCertificateRevocationForSelfClient: ObserveCertificateRevocationForSelfClientUseCase,
private val featureFlagsSyncWorker: FeatureFlagsSyncWorker
) : SavedStateViewModel(savedStateHandle) {
private val featureFlagsSyncWorker: FeatureFlagsSyncWorker,
private val updateApiVersions: UpdateApiVersionsUseCase
) : ViewModel() {

private val minIntervalBetweenPulls: Duration = MIN_INTERVAL_BETWEEN_PULLS

private var lastPullInstant: Instant? = null
private var syncDataJob: Job? = null

fun startSyncingAppConfig() {
viewModelScope.launch {
certificateRevocationListCheckWorker.execute()
}
viewModelScope.launch {
observeCertificateRevocationForSelfClient.invoke()
if (isSyncing()) return

val now = Clock.System.now()
if (isPullTooRecent(now)) return

lastPullInstant = now
syncDataJob = viewModelScope.launch {
runSyncTasks()
}
viewModelScope.launch {
featureFlagsSyncWorker.execute()
}

private fun isSyncing(): Boolean {
return syncDataJob?.isActive == true
}

private fun isPullTooRecent(now: Instant): Boolean {
return lastPullInstant?.let { lastPull ->
lastPull + minIntervalBetweenPulls > now
} ?: false
}

@Suppress("TooGenericExceptionCaught")
private suspend fun runSyncTasks() {
try {
listOf(
viewModelScope.launch { syncCertificateRevocationListUseCase() },
viewModelScope.launch { featureFlagsSyncWorker.execute() },
viewModelScope.launch { observeCertificateRevocationForSelfClient.invoke() },
viewModelScope.launch { updateApiVersions() },
).joinAll()
} catch (e: Exception) {
appLogger.e("Error while syncing app config", e)
}
}

companion object {
val MIN_INTERVAL_BETWEEN_PULLS = 60.minutes
}
}
5 changes: 0 additions & 5 deletions app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import com.wire.android.model.ImageAsset.UserAvatarAsset
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.util.ui.WireSessionImageLoader
import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase
import com.wire.kalium.logic.feature.e2ei.CertificateRevocationListCheckWorker
import com.wire.kalium.logic.feature.user.GetSelfUserUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
Expand All @@ -45,17 +44,13 @@ class HomeViewModel @Inject constructor(
private val needsToRegisterClient: NeedsToRegisterClientUseCase,
private val wireSessionImageLoader: WireSessionImageLoader,
private val shouldTriggerMigrationForUser: ShouldTriggerMigrationForUserUserCase,
private val certificateRevocationListCheckWorker: CertificateRevocationListCheckWorker
) : SavedStateViewModel(savedStateHandle) {

var homeState by mutableStateOf(HomeState())
private set

init {
loadUserAvatar()
viewModelScope.launch {
certificateRevocationListCheckWorker.execute()
}
}

fun checkRequirements(onRequirement: (HomeRequirement) -> Unit) {
Expand Down
130 changes: 130 additions & 0 deletions app/src/test/kotlin/com/wire/android/ui/home/AppSyncViewModelTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home

import com.wire.android.config.CoroutineTestExtension
import com.wire.kalium.logic.feature.e2ei.SyncCertificateRevocationListUseCase
import com.wire.kalium.logic.feature.e2ei.usecase.ObserveCertificateRevocationForSelfClientUseCase
import com.wire.kalium.logic.feature.featureConfig.FeatureFlagsSyncWorker
import com.wire.kalium.logic.feature.server.UpdateApiVersionsUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(CoroutineTestExtension::class)
class AppSyncViewModelTest {
@Test
fun `when startSyncingAppConfig is called then it should call the use case`() = runTest {
val (arrangement, viewModel) = Arrangement().arrange {
withObserveCertificateRevocationForSelfClient()
withFeatureFlagsSyncWorker()
withSyncCertificateRevocationListUseCase()
withUpdateApiVersions()
}

viewModel.startSyncingAppConfig()
advanceUntilIdle()

coVerify { arrangement.observeCertificateRevocationForSelfClient.invoke() }
coVerify { arrangement.syncCertificateRevocationListUseCase.invoke() }
coVerify { arrangement.featureFlagsSyncWorker.execute() }
coVerify { arrangement.updateApiVersions() }
}

@Test
fun `when startSyncingAppConfig is called multiple times then it should call the use case with delay`() = runTest {
val (arrangement, viewModel) = Arrangement().arrange {
withObserveCertificateRevocationForSelfClient(1000)
withFeatureFlagsSyncWorker(1000)
withSyncCertificateRevocationListUseCase(1000)
withUpdateApiVersions(1000)
}

viewModel.startSyncingAppConfig()
viewModel.startSyncingAppConfig()
viewModel.startSyncingAppConfig()
advanceUntilIdle()

coVerify(exactly = 1) { arrangement.observeCertificateRevocationForSelfClient.invoke() }
coVerify(exactly = 1) { arrangement.syncCertificateRevocationListUseCase.invoke() }
coVerify(exactly = 1) { arrangement.featureFlagsSyncWorker.execute() }
coVerify(exactly = 1) { arrangement.updateApiVersions() }
}

private class Arrangement {

@MockK
lateinit var syncCertificateRevocationListUseCase: SyncCertificateRevocationListUseCase

@MockK
lateinit var observeCertificateRevocationForSelfClient: ObserveCertificateRevocationForSelfClientUseCase

@MockK
lateinit var featureFlagsSyncWorker: FeatureFlagsSyncWorker

@MockK
lateinit var updateApiVersions: UpdateApiVersionsUseCase

init {
MockKAnnotations.init(this)
}

private val viewModel = AppSyncViewModel(
syncCertificateRevocationListUseCase,
observeCertificateRevocationForSelfClient,
featureFlagsSyncWorker,
updateApiVersions
)

@OptIn(InternalCoroutinesApi::class)
fun withObserveCertificateRevocationForSelfClient(delayMs: Long = 0) {
coEvery { observeCertificateRevocationForSelfClient.invoke() } coAnswers {
delay(delayMs)
}
}

fun withSyncCertificateRevocationListUseCase(delayMs: Long = 0) {
coEvery { syncCertificateRevocationListUseCase.invoke() } coAnswers {
delay(delayMs)
}
}

fun withFeatureFlagsSyncWorker(delayMs: Long = 0) {
coEvery { featureFlagsSyncWorker.execute() } coAnswers {
delay(delayMs)
}
}

fun withUpdateApiVersions(delayMs: Long = 0) {
coEvery { updateApiVersions() } coAnswers {
delay(delayMs)
}
}

fun arrange(block: Arrangement.() -> Unit) = apply(block).let {
this to viewModel
}
}
}

0 comments on commit 8827eac

Please sign in to comment.