diff options
| author | Kalle Lindström <karl.lindstrom@mullvad.net> | 2024-09-30 18:04:57 +0200 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2024-10-07 08:34:52 +0200 |
| commit | 1fe033aabeb67925955906dabb044c2622d2ccd6 (patch) | |
| tree | f51c3686beb5bc87d8184d812a0dbdd321e44dd4 /android/app/src/test | |
| parent | 6462dd1d00ea924d99deb27b1741e76e88045ec2 (diff) | |
| download | mullvadvpn-1fe033aabeb67925955906dabb044c2622d2ccd6.tar.xz mullvadvpn-1fe033aabeb67925955906dabb044c2622d2ccd6.zip | |
Update in app expiry notifications over time
Diffstat (limited to 'android/app/src/test')
4 files changed, 172 insertions, 84 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt index 84ec047f06..07c6c2ed13 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt @@ -16,11 +16,11 @@ import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.ErrorState import net.mullvad.mullvadvpn.repository.InAppNotification import net.mullvad.mullvadvpn.repository.InAppNotificationController -import net.mullvad.mullvadvpn.usecase.AccountExpiryNotificationUseCase +import net.mullvad.mullvadvpn.usecase.AccountExpiryInAppNotificationUseCase import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase -import org.joda.time.DateTime +import org.joda.time.Period import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -42,11 +42,11 @@ class InAppNotificationControllerTest { fun setup() { MockKAnnotations.init(this) - val accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase = mockk() + val accountExpiryInAppNotificationUseCase: AccountExpiryInAppNotificationUseCase = mockk() val newDeviceNotificationUseCase: NewDeviceNotificationUseCase = mockk() val versionNotificationUseCase: VersionNotificationUseCase = mockk() val tunnelStateNotificationUseCase: TunnelStateNotificationUseCase = mockk() - every { accountExpiryNotificationUseCase.invoke() } returns accountExpiryNotifications + every { accountExpiryInAppNotificationUseCase.invoke() } returns accountExpiryNotifications every { newDeviceNotificationUseCase.invoke() } returns newDeviceNotifications every { versionNotificationUseCase.invoke() } returns versionNotifications every { tunnelStateNotificationUseCase.invoke() } returns tunnelStateNotifications @@ -54,7 +54,7 @@ class InAppNotificationControllerTest { inAppNotificationController = InAppNotificationController( - accountExpiryNotificationUseCase, + accountExpiryInAppNotificationUseCase, newDeviceNotificationUseCase, versionNotificationUseCase, tunnelStateNotificationUseCase, @@ -81,7 +81,7 @@ class InAppNotificationControllerTest { val unsupportedVersion = InAppNotification.UnsupportedVersion(mockk()) versionNotifications.value = listOf(unsupportedVersion) - val accountExpiry = InAppNotification.AccountExpiry(DateTime.now()) + val accountExpiry = InAppNotification.AccountExpiry(Period.ZERO) accountExpiryNotifications.value = listOf(accountExpiry) inAppNotificationController.notifications.test { diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt new file mode 100644 index 0000000000..826c8c4962 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryInAppNotificationUseCaseTest.kt @@ -0,0 +1,163 @@ +package net.mullvad.mullvadvpn.usecase + +import app.cash.turbine.test +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlin.math.roundToInt +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.model.AccountData +import net.mullvad.mullvadvpn.lib.shared.AccountRepository +import net.mullvad.mullvadvpn.repository.InAppNotification +import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD +import net.mullvad.mullvadvpn.service.notifications.accountexpiry.ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL +import org.joda.time.DateTime +import org.joda.time.Duration +import org.joda.time.Period +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +class AccountExpiryInAppNotificationUseCaseTest { + + private val accountExpiry = MutableStateFlow<AccountData?>(null) + private lateinit var accountExpiryInAppNotificationUseCase: + AccountExpiryInAppNotificationUseCase + + private lateinit var notificationThreshold: DateTime + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + val accountRepository = mockk<AccountRepository>() + every { accountRepository.accountData } returns accountExpiry + + accountExpiryInAppNotificationUseCase = + AccountExpiryInAppNotificationUseCase(accountRepository) + + notificationThreshold = DateTime.now().plus(ACCOUNT_EXPIRY_CLOSE_TO_EXPIRY_THRESHOLD) + } + + @AfterEach + fun teardown() { + unmockkAll() + } + + @Test + fun `initial state should be empty`() = runTest { + // Arrange, Act, Assert + accountExpiryInAppNotificationUseCase().test { assertTrue { awaitItem().isEmpty() } } + } + + @Test + fun `account that expires within the threshold should emit a notification`() = runTest { + // Arrange, Act, Assert + accountExpiryInAppNotificationUseCase().test { + assertTrue { awaitItem().isEmpty() } + val expiry = setExpiry(notificationThreshold.minusHours(1)) + assertExpiryNotificationAndPeriod(expiry, expectMostRecentItem()) + expectNoEvents() + } + } + + @Test + fun `account that expires after the threshold should not emit a notification`() = runTest { + // Arrange, Act, Assert + accountExpiryInAppNotificationUseCase().test { + assertTrue { awaitItem().isEmpty() } + setExpiry(notificationThreshold.plusDays(1)) + expectNoEvents() + } + } + + @Test + fun `should emit when the threshold is passed`() = runTest { + // Arrange, Act, Assert + accountExpiryInAppNotificationUseCase().test { + assertTrue { awaitItem().isEmpty() } + val expiry = setExpiry(notificationThreshold.plusMinutes(1)) + expectNoEvents() + + // Advance to before threshold + advanceTimeBy(59.seconds) + expectNoEvents() + + // Advance to after threshold + advanceTimeBy(2.seconds) + assertExpiryNotificationAndPeriod(expiry, expectMostRecentItem()) + expectNoEvents() + } + } + + @Test + fun `should emit remaining time divided by update interval time times`() = runTest { + // Arrange, Act, Assert + accountExpiryInAppNotificationUseCase().test { + assertTrue { awaitItem().isEmpty() } + + // Set expiry to to be 1 second before the end of the first update interval + val beforeUpdate = + notificationThreshold + .minus(ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL) + .plusSeconds(1) + val expiry = setExpiry(beforeUpdate) + + // The expiry time is within the notification threshold so we should have an item + // immediately. + assertExpiryNotificationAndPeriod(expiry, expectMostRecentItem()) + expectNoEvents() + + val expectedEmits = + (Duration(DateTime.now(), beforeUpdate).millis.toDouble() / + ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.millis) + .roundToInt() + + advanceTimeBy(2.seconds) + repeat(expectedEmits) { + expectMostRecentItem() + advanceTimeBy( + ACCOUNT_EXPIRY_IN_APP_NOTIFICATION_UPDATE_INTERVAL.plus( + Duration.standardSeconds(1) + ) + .millis + ) + } + + expectNoEvents() + } + } + + private fun setExpiry(expiryDateTime: DateTime): DateTime { + val expiry = AccountData(mockk(relaxed = true), expiryDateTime) + accountExpiry.value = expiry + return expiryDateTime + } + + // Assert that we go a single AccountExpiry notification and that the period is within + // the expected range (checking exact period values is not possible since we use DateTime.now) + private fun assertExpiryNotificationAndPeriod( + expiry: DateTime, + notifications: List<InAppNotification>, + ) { + assertTrue(notifications.size == 1, "Expected a single notification") + val n = notifications[0] + if (n !is InAppNotification.AccountExpiry) { + error("Expected an AccountExpiry notification") + } + val periodNow = Period(DateTime.now(), expiry) + assertTrue(periodNow.toStandardDuration() <= n.expiry.toStandardDuration()) + assertTrue( + periodNow.toStandardDuration().plus(Duration.standardSeconds(5)) > + n.expiry.toStandardDuration() + ) + } +} diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt deleted file mode 100644 index f8a0e52a3e..0000000000 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -package net.mullvad.mullvadvpn.usecase - -import app.cash.turbine.test -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.test.runTest -import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule -import net.mullvad.mullvadvpn.lib.model.AccountData -import net.mullvad.mullvadvpn.lib.shared.AccountRepository -import net.mullvad.mullvadvpn.repository.InAppNotification -import org.joda.time.DateTime -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith - -@ExtendWith(TestCoroutineRule::class) -class AccountExpiryNotificationUseCaseTest { - - private val accountExpiry = MutableStateFlow<AccountData?>(null) - private lateinit var accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase - - @BeforeEach - fun setup() { - MockKAnnotations.init(this) - - val accountRepository = mockk<AccountRepository>() - every { accountRepository.accountData } returns accountExpiry - - accountExpiryNotificationUseCase = AccountExpiryNotificationUseCase(accountRepository) - } - - @AfterEach - fun teardown() { - unmockkAll() - } - - @Test - fun `initial state should be empty`() = runTest { - // Arrange, Act, Assert - accountExpiryNotificationUseCase().test { assertTrue { awaitItem().isEmpty() } } - } - - @Test - fun `account that expires within 3 days should emit a notification`() = runTest { - // Arrange, Act, Assert - accountExpiryNotificationUseCase().test { - assertTrue { awaitItem().isEmpty() } - val closeToExpiry = AccountData(mockk(relaxed = true), DateTime.now().plusDays(2)) - accountExpiry.value = closeToExpiry - - assertEquals( - listOf(InAppNotification.AccountExpiry(closeToExpiry.expiryDate)), - awaitItem(), - ) - } - } - - @Test - fun `account that expires in 4 days should not emit a notification`() = runTest { - // Arrange, Act, Assert - accountExpiryNotificationUseCase().test { - assertTrue { awaitItem().isEmpty() } - accountExpiry.value = AccountData(mockk(relaxed = true), DateTime.now().plusDays(4)) - expectNoEvents() - } - } -} 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 d8438cafe0..e12c2a1d88 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 @@ -185,14 +185,12 @@ class OutOfTimeUseCaseTest { advanceTimeBy(90.seconds) expectNoEvents() - // User fills up with more time 10 seconds before expiry + // User fills up with more time 10 seconds before expiry. expiry.emit(updatedExpiry) - advanceTimeBy(1.days) + advanceTimeBy(29.days) expectNoEvents() - // Expect no more emissions while user has time. - advanceTimeBy(29.days + 2.minutes) - println(testScheduler.currentTime) + advanceTimeBy(2.days) assertEquals(true, expectMostRecentItem()) expectNoEvents() } |
