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

feat: observe team app lock config (WPB-4476) #2380

Merged
merged 9 commits into from
Nov 2, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,59 @@
package com.wire.android.feature

import com.wire.android.datastore.GlobalDataStore
import com.wire.android.di.KaliumCoreLogic
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl.Companion.DEFAULT_TIMEOUT
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.session.CurrentSessionUseCase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combineTransform
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Singleton
class ObserveAppLockConfigUseCase @Inject constructor(
private val globalDataStore: GlobalDataStore,
@KaliumCoreLogic private val coreLogic: CoreLogic,
private val currentSession: CurrentSessionUseCase
) {

operator fun invoke(): Flow<AppLockConfig> =
globalDataStore.isAppLockPasscodeSetFlow().map { // TODO: include checking if any logged account does not enforce app-lock
when {
it -> AppLockConfig.Enabled
else -> AppLockConfig.Disabled
operator fun invoke(): Flow<AppLockConfig> = channelFlow {
when (val currentSession = currentSession()) {
is CurrentSessionResult.Failure -> {
send(AppLockConfig.Disabled(DEFAULT_TIMEOUT))
}

is CurrentSessionResult.Success -> {
val userId = currentSession.accountInfo.userId
val appLockTeamFeatureConfigFlow =
coreLogic.getSessionScope(userId).appLockTeamFeatureConfigObserver

appLockTeamFeatureConfigFlow().combineTransform(
globalDataStore.isAppLockPasscodeSetFlow()
) { isAppLockedByTeam, isAppLocked ->
if (isAppLockedByTeam.isEnabled) {
emit(AppLockConfig.EnforcedByTeam(isAppLockedByTeam.timeout))
} else {
if (isAppLocked) {
emit(AppLockConfig.Enabled(isAppLockedByTeam.timeout))
} else {
emit(AppLockConfig.Disabled(isAppLockedByTeam.timeout))
}
}
}.collectLatest {
send(it)
}
}
}
}
}

sealed class AppLockConfig(open val timeout: Duration = DEFAULT_TIMEOUT) {
data object Disabled : AppLockConfig()
data object Enabled : AppLockConfig()
data class Disabled(override val timeout: Duration) : AppLockConfig(timeout)
data class Enabled(override val timeout: Duration) : AppLockConfig(timeout)
data class EnforcedByTeam(override val timeout: Duration) : AppLockConfig(timeout)

companion object {
val DEFAULT_TIMEOUT = 60.seconds
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
package com.wire.android.ui.home.appLock

import androidx.compose.ui.text.input.TextFieldValue
import com.wire.android.feature.AppLockConfig
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl.Companion.DEFAULT_TIMEOUT
import com.wire.kalium.logic.feature.auth.ValidatePasswordResult
import kotlin.time.Duration

data class SetLockCodeViewState(
val continueEnabled: Boolean = false,
val password: TextFieldValue = TextFieldValue(),
val passwordValidation: ValidatePasswordResult = ValidatePasswordResult.Invalid(),
val timeout: Duration = AppLockConfig.DEFAULT_TIMEOUT,
val timeout: Duration = DEFAULT_TIMEOUT,
val done: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,20 @@ fun SettingsScreenContent(
add(SettingsItem.NetworkSettings)
add(SettingsItem.AppLock(
when (settingsState.appLockConfig) {
AppLockConfig.Disabled -> SwitchState.Enabled(false, true, onAppLockSwitchChanged)
AppLockConfig.Enabled -> SwitchState.Enabled(true, true) { turnAppLockOffDialogState.show(Unit) }
is AppLockConfig.EnforcedByTeam -> SwitchState.TextOnly(true)
is AppLockConfig.Disabled -> SwitchState.Enabled(
value = false,
isOnOffVisible = true,
onCheckedChange = onAppLockSwitchChanged
)
is AppLockConfig.Enabled -> SwitchState.Enabled(
value = true,
isOnOffVisible = true
) {
turnAppLockOffDialogState.show(Unit)
}
is AppLockConfig.EnforcedByTeam -> {
SwitchState.TextOnly(true)
}
}
))
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
package com.wire.android.ui.home.settings

import com.wire.android.feature.AppLockConfig
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserverImpl.Companion.DEFAULT_TIMEOUT

data class SettingsState(
val appLockConfig: AppLockConfig = AppLockConfig.Disabled,
val appLockConfig: AppLockConfig = AppLockConfig.Disabled(DEFAULT_TIMEOUT),

Check warning on line 24 in app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsState.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/settings/SettingsState.kt#L24

Added line #L24 was not covered by tests
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,125 @@
*/
package com.wire.android.feature

import app.cash.turbine.test
import com.wire.android.datastore.GlobalDataStore
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.UserSessionScope
import com.wire.kalium.logic.feature.applock.AppLockTeamConfig
import com.wire.kalium.logic.feature.applock.AppLockTeamFeatureConfigObserver
import com.wire.kalium.logic.feature.auth.AccountInfo
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.session.CurrentSessionUseCase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.every
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds

class ObserveAppLockConfigUseCaseTest {

// TODO: include checking if any logged account does not enforce app-lock
@Test
fun givenPasscodeIsSet_whenObservingAppLockConfig_thenReturnEnabled() = runTest {
fun givenNoValidSession_whenObservingAppLock_thenSendDisabledStatus() = runTest {
val (_, useCase) = Arrangement()
.withAppLockPasscodeSet(true)
.withNonValidSession()
.arrange()

val result = useCase.invoke().firstOrNull()
val result = useCase.invoke()

assert(result is AppLockConfig.Enabled)
result.test {
val appLockStatus = awaitItem()

assertEquals(AppLockConfig.Disabled(timeout), appLockStatus)
awaitComplete()
}
}

@Test
fun givenPasscodeIsNotSet_whenObservingAppLockConfig_thenReturnDisabled() = runTest {
val (_, useCase) = Arrangement()
.withAppLockPasscodeSet(false)
.arrange()
fun givenValidSessionAndAppLockedByTeam_whenObservingAppLock_thenSendEnforcedByTeamStatus() =
runTest {
val (_, useCase) = Arrangement()
.withValidSession()
.withTeamAppLockEnabled()
.withAppLockedByCurrentUser()
.arrange()

val result = useCase.invoke().firstOrNull()
val result = useCase.invoke()

assert(result is AppLockConfig.Disabled)
}
result.test {
val appLockStatus = awaitItem()

assertEquals(AppLockConfig.EnforcedByTeam(timeout), appLockStatus)
awaitComplete()
}
}

@Test
fun givenValidSessionAndAppLockedByUserOnly_whenObservingAppLock_thenSendEnabledStatus() =
runTest {
val (_, useCase) = Arrangement()
.withValidSession()
.withTeamAppLockDisabled()
.withAppLockedByCurrentUser()
.arrange()

val result = useCase.invoke()

result.test {
val appLockStatus = awaitItem()

assertEquals(AppLockConfig.Enabled(timeout), appLockStatus)
awaitComplete()
}
}

@Test
fun givenValidSessionAndAppNotLockedByUserNorTeam_whenObservingAppLock_thenSendDisabledStatus() =
runTest {
val (_, useCase) = Arrangement()
.withValidSession()
.withTeamAppLockDisabled()
.withAppNonLockedByCurrentUser()
.arrange()

val result = useCase.invoke()

result.test {
val appLockStatus = awaitItem()

assertEquals(AppLockConfig.Disabled(timeout), appLockStatus)
awaitComplete()
}
}

inner class Arrangement {

@MockK
lateinit var globalDataStore: GlobalDataStore
val useCase by lazy { ObserveAppLockConfigUseCase(globalDataStore) }

@MockK
lateinit var currentSession: CurrentSessionUseCase

@MockK
lateinit var coreLogic: CoreLogic

@MockK
lateinit var userSessionScope: UserSessionScope

@MockK
lateinit var appLockTeamFeatureConfigObserver: AppLockTeamFeatureConfigObserver

val useCase by lazy {
ObserveAppLockConfigUseCase(
globalDataStore = globalDataStore,
coreLogic = coreLogic,
currentSession = currentSession
)
}

init {
MockKAnnotations.init(this, relaxUnitFun = true)
Expand All @@ -65,5 +146,46 @@ class ObserveAppLockConfigUseCaseTest {
}

fun arrange() = this to useCase

fun withNonValidSession() = apply {
coEvery { currentSession() } returns CurrentSessionResult.Failure.SessionNotFound
}

fun withValidSession() = apply {
coEvery { currentSession() } returns CurrentSessionResult.Success(accountInfo)
}

fun withTeamAppLockEnabled() = apply {
every { coreLogic.getSessionScope(any()) } returns userSessionScope
every {
userSessionScope.appLockTeamFeatureConfigObserver
} returns appLockTeamFeatureConfigObserver
every {
appLockTeamFeatureConfigObserver.invoke()
} returns flowOf(AppLockTeamConfig(true, timeout))
}

fun withTeamAppLockDisabled() = apply {
every { coreLogic.getSessionScope(any()) } returns userSessionScope
every {
userSessionScope.appLockTeamFeatureConfigObserver
} returns appLockTeamFeatureConfigObserver
every {
appLockTeamFeatureConfigObserver.invoke()
} returns flowOf(AppLockTeamConfig(false, timeout))
}

fun withAppLockedByCurrentUser() = apply {
every { globalDataStore.isAppLockPasscodeSetFlow() } returns flowOf(true)
}

fun withAppNonLockedByCurrentUser() = apply {
every { globalDataStore.isAppLockPasscodeSetFlow() } returns flowOf(false)
}
}

companion object {
private val accountInfo = AccountInfo.Valid(UserId("userId", "domain"))
private val timeout = 60.seconds
}
}