diff options
Diffstat (limited to 'android/app/src/test')
4 files changed, 170 insertions, 82 deletions
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 d40e8d8e18..326e183445 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 @@ -3,10 +3,19 @@ package net.mullvad.mullvadvpn.usecase import app.cash.turbine.test import io.mockk.every import io.mockk.mockk +import io.mockk.unmockkAll import kotlin.test.assertEquals -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlin.time.Duration.Companion.days +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain import net.mullvad.mullvadvpn.lib.ipc.Event import net.mullvad.mullvadvpn.lib.ipc.MessageHandler import net.mullvad.mullvadvpn.lib.ipc.events @@ -16,6 +25,7 @@ import net.mullvad.mullvadvpn.repository.AccountRepository import net.mullvad.talpid.tunnel.ErrorState import net.mullvad.talpid.tunnel.ErrorStateCause import org.joda.time.DateTime +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -23,106 +33,194 @@ class OutOfTimeUseCaseTest { private val mockAccountRepository: AccountRepository = mockk() private val mockMessageHandler: MessageHandler = mockk() - private val events = MutableSharedFlow<Event.TunnelStateChange>() - private val expiry = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing) + private lateinit var events: Channel<Event.TunnelStateChange> + private lateinit var expiry: MutableStateFlow<AccountExpiry> - lateinit var outOfTimeUseCase: OutOfTimeUseCase + private val dispatcher = StandardTestDispatcher() + private val scope = TestScope(dispatcher) + + private lateinit var outOfTimeUseCase: OutOfTimeUseCase @BeforeEach fun setup() { + events = Channel() + expiry = MutableStateFlow(AccountExpiry.Missing) every { mockAccountRepository.accountExpiryState } returns expiry - every { mockMessageHandler.events<Event.TunnelStateChange>() } returns events - outOfTimeUseCase = OutOfTimeUseCase(mockAccountRepository, mockMessageHandler) + every { mockMessageHandler.events<Event.TunnelStateChange>() } returns + events.receiveAsFlow() + + Dispatchers.setMain(dispatcher) + + outOfTimeUseCase = + OutOfTimeUseCase(mockAccountRepository, mockMessageHandler, scope.backgroundScope) } - @Test - fun `no events should result in no expiry`() = runTest { - // Arrange - // Act, Assert - outOfTimeUseCase.isOutOfTime().test { assertEquals(null, awaitItem()) } + @AfterEach + fun teardown() { + Dispatchers.resetMain() + unmockkAll() } @Test - fun `tunnel is blocking because out of time should emit true`() = runTest { - // Arrange - // Act, Assert - val errorStateCause = ErrorStateCause.AuthFailed("[EXPIRED_ACCOUNT]") - val tunnelStateError = TunnelState.Error(ErrorState(errorStateCause, true)) - val errorChange = Event.TunnelStateChange(tunnelStateError) + fun `no events should result in no expiry`() = + scope.runTest { + // Arrange + // Act, Assert + outOfTimeUseCase.isOutOfTime.test { assertEquals(null, awaitItem()) } + } - outOfTimeUseCase.isOutOfTime().test { - assertEquals(null, awaitItem()) - events.emit(errorChange) - assertEquals(true, awaitItem()) + @Test + fun `tunnel is blocking because out of time should emit true`() = + scope.runTest { + // Arrange + // Act, Assert + val errorStateCause = ErrorStateCause.AuthFailed("[EXPIRED_ACCOUNT]") + val tunnelStateError = TunnelState.Error(ErrorState(errorStateCause, true)) + val errorChange = Event.TunnelStateChange(tunnelStateError) + + outOfTimeUseCase.isOutOfTime.test { + assertEquals(null, awaitItem()) + events.send(errorChange) + assertEquals(true, awaitItem()) + } } - } @Test - fun `tunnel is connected should emit false`() = runTest { - // Arrange - val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1)) - val tunnelStateChanges = - listOf( - TunnelState.Disconnected(), - TunnelState.Connected(mockk(), null), - TunnelState.Connecting(null, null), - TunnelState.Disconnecting(mockk()), - TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)), - ) - .map(Event::TunnelStateChange) + fun `tunnel is connected should emit false`() = + scope.runTest { + // Arrange + val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1)) + val tunnelStateChanges = + listOf( + TunnelState.Disconnected(), + TunnelState.Connected(mockk(), null), + TunnelState.Connecting(null, null), + TunnelState.Disconnecting(mockk()), + TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)), + ) + .map(Event::TunnelStateChange) - // Act, Assert - outOfTimeUseCase.isOutOfTime().test { - assertEquals(null, awaitItem()) - events.emit(tunnelStateChanges.first()) - expiry.emit(expiredAccountExpiry) - assertEquals(false, awaitItem()) + // Act, Assert + outOfTimeUseCase.isOutOfTime.test { + assertEquals(null, awaitItem()) + events.send(tunnelStateChanges.first()) + expiry.emit(expiredAccountExpiry) + assertEquals(false, awaitItem()) - tunnelStateChanges.forEach { events.emit(it) } + tunnelStateChanges.forEach { events.send(it) } - // Should not emit again - expectNoEvents() + // Should not emit again + expectNoEvents() + } } - } @Test - fun `account expiry that has expired should emit true`() = runTest { - // Arrange - val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().minusDays(1)) - // Act, Assert - outOfTimeUseCase.isOutOfTime().test { - assertEquals(null, awaitItem()) - expiry.emit(expiredAccountExpiry) - assertEquals(true, awaitItem()) + fun `account expiry that has expired should emit true`() = + scope.runTest { + // Arrange + val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().minusDays(1)) + // Act, Assert + outOfTimeUseCase.isOutOfTime.test { + assertEquals(null, awaitItem()) + expiry.emit(expiredAccountExpiry) + assertEquals(true, awaitItem()) + } + } + + @Test + fun `account expiry that has not expired should emit false`() = + scope.runTest { + // Arrange + val notExpiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1)) + + // Act, Assert + outOfTimeUseCase.isOutOfTime.test { + assertEquals(null, awaitItem()) + expiry.emit(notExpiredAccountExpiry) + assertEquals(false, awaitItem()) + } } - } @Test - fun `account expiry that has not expired should emit false`() = runTest { + fun `account that expires without new expiry event should emit true`() = + runTest(dispatcher) { + // Arrange + val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100)) + // Act, Assert + outOfTimeUseCase.isOutOfTime.test { + // Initial event + assertEquals(null, awaitItem()) + + expiry.emit(expiredAccountExpiry) + assertEquals(false, awaitItem()) + + // After 50 seconds we should still not emitted out of time + advanceTimeBy(50_000) + expectNoEvents() + + // After additional 50 seconds we should be out of time since account is now expired + advanceTimeBy(50_000) + assertEquals(true, awaitItem()) + } + } + + @Test + fun `account that is about to expire but is refilled should emit false`() = runTest { // Arrange - val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1)) + val initialAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100)) + val updatedExpiry = + AccountExpiry.Available(initialAccountExpiry.expiryDateTime.plusDays(30)) // Act, Assert - outOfTimeUseCase.isOutOfTime().test { + outOfTimeUseCase.isOutOfTime.test { + // Initial event assertEquals(null, awaitItem()) - expiry.emit(expiredAccountExpiry) + + expiry.emit(initialAccountExpiry) assertEquals(false, awaitItem()) + advanceTimeBy(90_000) + expectNoEvents() + + // User fills up with more time 30 seconds before expiry + expiry.emit(updatedExpiry) + advanceTimeBy(1.days) + expectNoEvents() + + // Expect no more emissions while user has time. + advanceTimeBy(29.days) + assertEquals(true, awaitItem()) + expectNoEvents() } } @Test - fun `account that expires without new expiry event should emit true`() = runTest { + fun `expired account that is refilled should emit false`() = runTest { // Arrange - val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(62)) - + val initialAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100)) + val updatedExpiry = + AccountExpiry.Available(initialAccountExpiry.expiryDateTime.plusDays(30)) // Act, Assert - outOfTimeUseCase.isOutOfTime().test { + outOfTimeUseCase.isOutOfTime.test { // Initial event assertEquals(null, awaitItem()) - expiry.emit(expiredAccountExpiry) + expiry.emit(initialAccountExpiry) assertEquals(false, awaitItem()) + + // After 100 seconds we expire + advanceTimeBy(100_000) assertEquals(true, awaitItem()) + expectNoEvents() + + // We then fill up our account and should no longer be out of time + expiry.emit(updatedExpiry) + assertEquals(false, awaitItem()) + expectNoEvents() + + // Advance the time to the updated expiry + advanceTimeBy(30.days) + assertEquals(true, awaitItem()) + expectNoEvents() } } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt index 545422b6f2..7e207a15a4 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt @@ -132,7 +132,7 @@ class ConnectViewModelTest { // Flows every { mockRelayListUseCase.selectedRelayItem() } returns selectedRelayItemFlow - every { outOfTimeUseCase.isOutOfTime() } returns outOfTimeViewFlow + every { outOfTimeUseCase.isOutOfTime } returns outOfTimeViewFlow viewModel = ConnectViewModel( serviceConnectionManager = mockServiceConnectionManager, diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt index a5171b2ea6..e489c01d41 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt @@ -89,7 +89,7 @@ class OutOfTimeViewModelTest { coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailabilityFlow - coEvery { mockOutOfTimeUseCase.isOutOfTime() } returns outOfTimeFlow + coEvery { mockOutOfTimeUseCase.isOutOfTime } returns outOfTimeFlow viewModel = OutOfTimeViewModel( diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt index 74ea210a60..91554193bc 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt @@ -32,11 +32,9 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache -import net.mullvad.mullvadvpn.usecase.OutOfTimeUseCase import net.mullvad.mullvadvpn.usecase.PaymentUseCase import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime -import org.joda.time.ReadableInstant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -51,7 +49,6 @@ class WelcomeViewModelTest { private val accountExpiryStateFlow = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing) private val purchaseResultFlow = MutableStateFlow<PurchaseResult?>(null) private val paymentAvailabilityFlow = MutableStateFlow<PaymentAvailability?>(null) - private val outOfTimeFlow = MutableStateFlow(true) // Service connections private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -64,7 +61,6 @@ class WelcomeViewModelTest { private val mockDeviceRepository: DeviceRepository = mockk() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) - private val mockOutOfTimeUseCase: OutOfTimeUseCase = mockk(relaxed = true) private lateinit var viewModel: WelcomeViewModel @@ -87,15 +83,12 @@ class WelcomeViewModelTest { coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailabilityFlow - coEvery { mockOutOfTimeUseCase.isOutOfTime() } returns outOfTimeFlow - viewModel = WelcomeViewModel( accountRepository = mockAccountRepository, deviceRepository = mockDeviceRepository, serviceConnectionManager = mockServiceConnectionManager, paymentUseCase = mockPaymentUseCase, - outOfTimeUseCase = mockOutOfTimeUseCase, pollAccountExpiry = false, isPlayBuild = false ) @@ -164,19 +157,16 @@ class WelcomeViewModelTest { } @Test - fun `when OutOfTimeUseCase return false uiSideEffect should emit OpenConnectScreen`() = - runTest { - // Arrange - val mockExpiryDate: DateTime = mockk() - every { mockExpiryDate.isAfter(any<ReadableInstant>()) } returns true + fun `when user has added time then uiSideEffect should emit OpenConnectScreen`() = runTest { + // Arrange + accountExpiryStateFlow.emit(AccountExpiry.Available(DateTime().plusDays(1))) - // Act, Assert - viewModel.uiSideEffect.test { - outOfTimeFlow.value = false - val action = awaitItem() - assertIs<WelcomeViewModel.UiSideEffect.OpenConnectScreen>(action) - } + // Act, Assert + viewModel.uiSideEffect.test { + val action = awaitItem() + assertIs<WelcomeViewModel.UiSideEffect.OpenConnectScreen>(action) } + } @Test fun `when paymentAvailability emits ProductsUnavailable uiState should include state NoPayment`() = |
