Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update api version on app start [WPB-14835] #3788

Open
wants to merge 7 commits into
base: release/cycle-4.6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}
}
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 19 files
+5 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/CustomServerConfigRepository.kt
+18 −6 logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/server/ServerConfigRepository.kt
+3 −3 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/MLSConversationRepository.kt
+71 −0 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListChecker.kt
+7 −6 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+3 −3 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/CheckCrlRevocationListUseCase.kt
+12 −18 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/e2ei/SyncCertificateRevocationListUseCase.kt
+1 −1 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionsUseCase.kt
+12 −12 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/UserScope.kt
+3 −3 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/MLSWelcomeEventHandler.kt
+13 −12 logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/CustomServerConfigRepositoryTest.kt
+4 −4 logic/src/commonTest/kotlin/com/wire/kalium/logic/configuration/ServerConfigRepositoryTest.kt
+288 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListCheckerTest.kt
+36 −44 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/e2ei/CertificateRevocationListCheckWorkerTest.kt
+13 −13 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/server/UpdateApiVersionUseCaseTest.kt
+2 −2 persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq
+5 −3 persistence/src/commonMain/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAO.kt
+34 −7 persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt
+7 −0 persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt
Loading