From 52c3165af9fef8f1c43f7bcd22d33ef54a4a56c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20G=C3=B6ransson?= Date: Fri, 8 Dec 2023 14:48:59 +0100 Subject: [PATCH] Rework out of time --- .../mullvadvpn/usecase/OutOfTimeUseCase.kt | 20 ++++++++++++++----- .../mullvadvpn/viewmodel/ConnectViewModel.kt | 3 +-- .../viewmodel/OutOfTimeViewModel.kt | 2 +- .../mullvadvpn/viewmodel/WelcomeViewModel.kt | 2 +- .../usecase/OutOfTimeUseCaseTest.kt | 20 +++++++++---------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt index 97cecab15e9e..43c206051a91 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCase.kt @@ -23,14 +23,24 @@ class OutOfTimeUseCase( private val messageHandler: MessageHandler ) { - fun isOutOfTime() = + fun isOutOfTime(): Flow = combine(pastAccountExpiry(), isTunnelBlockedBecauseOutOfTime()) { accountExpiryHasPast, tunnelOutOfTime -> - accountExpiryHasPast or tunnelOutOfTime + reduce(accountExpiryHasPast, tunnelOutOfTime) } .distinctUntilChanged() + private fun reduce(vararg outOfTimeProperty: Boolean?): Boolean? = + when { + // If any advertises as out of time + outOfTimeProperty.any { it == true } -> true + // If all advertise as not out of time + outOfTimeProperty.all { it == false } -> false + // If some are unknown + else -> null + } + private fun isTunnelBlockedBecauseOutOfTime() = messageHandler .events() @@ -43,7 +53,7 @@ class OutOfTimeUseCase( ?: false } - private fun pastAccountExpiry(): Flow = + private fun pastAccountExpiry(): Flow = combine( repository.accountExpiryState.map { if (it is AccountExpiry.Available) { @@ -54,12 +64,12 @@ class OutOfTimeUseCase( }, timeFlow() ) { expiryDate, time -> - expiryDate?.isBefore(time) ?: false + expiryDate?.isBefore(time) } private fun timeFlow() = flow { while (true) { - emit(DateTime.now().plusMinutes(1)) + emit(DateTime.now()) delay(accountRefreshInterval) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt index f10f6c11915d..694c396928d9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -138,7 +137,7 @@ class ConnectViewModel( init { viewModelScope.launch { // This once we get isOutOfTime true we will navigate to OutOfTime view. - outOfTimeUseCase.isOutOfTime().filter { it }.first() + outOfTimeUseCase.isOutOfTime().first { it == true } _uiSideEffect.emit(UiSideEffect.OutOfTime) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt index 402a76c8a40e..220a4b92dbbe 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt @@ -77,7 +77,7 @@ class OutOfTimeViewModel( fun start() { jobScope = CoroutineScope(Job() + Dispatchers.IO) jobScope.launch { - outOfTimeUseCase.isOutOfTime().first { !it } + outOfTimeUseCase.isOutOfTime().first { it == false } paymentUseCase.resetPurchaseResult() _uiSideEffect.tryEmit(UiSideEffect.OpenConnectScreen) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt index a6c800f3a81c..9125024f6bef 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt @@ -92,7 +92,7 @@ class WelcomeViewModel( } } launch { - outOfTimeUseCase.isOutOfTime().first { !it } + outOfTimeUseCase.isOutOfTime().first { it == false } paymentUseCase.resetPurchaseResult() _uiSideEffect.emit(UiSideEffect.OpenConnectScreen) } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt index 5726017d83b1..f29eec05cb2d 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt @@ -20,7 +20,6 @@ import org.junit.Before import org.junit.Test class OutOfTimeUseCaseTest { - private val mockAccountRepository: AccountRepository = mockk() private val mockMessageHandler: MessageHandler = mockk() @@ -40,7 +39,7 @@ class OutOfTimeUseCaseTest { fun `No events should result in no expiry`() = runTest { // Arrange // Act, Assert - outOfTimeUseCase.isOutOfTime().test { assertEquals(false, awaitItem()) } + outOfTimeUseCase.isOutOfTime().test { assertEquals(null, awaitItem()) } } @Test @@ -52,7 +51,7 @@ class OutOfTimeUseCaseTest { val errorChange = Event.TunnelStateChange(tunnelStateError) outOfTimeUseCase.isOutOfTime().test { - assertEquals(false, awaitItem()) + assertEquals(null, awaitItem()) events.emit(errorChange) assertEquals(true, awaitItem()) } @@ -64,22 +63,22 @@ class OutOfTimeUseCaseTest { val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().minusDays(1)) // Act, Assert outOfTimeUseCase.isOutOfTime().test { - assertEquals(false, awaitItem()) + assertEquals(null, awaitItem()) expiry.emit(expiredAccountExpiry) assertEquals(true, awaitItem()) } } @Test - fun `Account expiry that has not expired should emit nothing`() = runTest { + fun `Account expiry that has not expired should emit false`() = runTest { // Arrange val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1)) // Act, Assert outOfTimeUseCase.isOutOfTime().test { - assertEquals(false, awaitItem()) + assertEquals(null, awaitItem()) expiry.emit(expiredAccountExpiry) - expectNoEvents() + assertEquals(false, awaitItem()) } } @@ -90,10 +89,11 @@ class OutOfTimeUseCaseTest { // Act, Assert outOfTimeUseCase.isOutOfTime().test { - // Initial event that doesn't change - assertEquals(false, awaitItem()) - expiry.emit(expiredAccountExpiry) + // Initial event + assertEquals(null, awaitItem()) + expiry.emit(expiredAccountExpiry) + assertEquals(false, awaitItem()) assertEquals(true, awaitItem()) } }