summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/test
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-06-09 16:43:14 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-06-09 16:43:14 +0200
commit8b0b5ab45c3e0720797bd381d4b02e70cf4043f9 (patch)
tree4d5d5fc018053cf664be5c41040f8755de07c55d /android/app/src/test
parent87e716c551f563b6bf181bcef87a58bee0fb2599 (diff)
parent1c58ad3fc58c1862526d912efc311e06956317fd (diff)
downloadmullvadvpn-8b0b5ab45c3e0720797bd381d4b02e70cf4043f9.tar.xz
mullvadvpn-8b0b5ab45c3e0720797bd381d4b02e70cf4043f9.zip
Merge branch 'implement-payment-screen-with-3-months-droid-1947'
Diffstat (limited to 'android/app/src/test')
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt137
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AddTimeViewModelTest.kt200
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt128
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt71
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt101
6 files changed, 275 insertions, 364 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt
index a07fefabe3..c855b9a473 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt
@@ -78,7 +78,7 @@ class PlayPaymentUseCaseTest {
@Test
fun `resetPurchaseResult call should result in purchaseResult null`() = runTest {
// Arrange
- val completedSuccess = PurchaseResult.Completed.Success
+ val completedSuccess = PurchaseResult.Completed.Success(ProductId("one_month"))
val productId = ProductId("productId")
val paymentRepositoryPurchaseResultFlow = flow { emit(completedSuccess) }
every { mockPaymentRepository.purchaseProduct(any(), any()) } returns
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
index 74a6bbd414..7a6b756bf5 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
@@ -1,34 +1,32 @@
package net.mullvad.mullvadvpn.viewmodel
-import android.app.Activity
import app.cash.turbine.test
import arrow.core.right
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
import java.time.ZonedDateTime
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.compose.state.PaymentState
import net.mullvad.mullvadvpn.data.UUID
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.lib.common.test.assertLists
import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.Device
import net.mullvad.mullvadvpn.lib.model.DeviceId
import net.mullvad.mullvadvpn.lib.model.DeviceState
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus
import net.mullvad.mullvadvpn.lib.payment.model.ProductId
-import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
+import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
+import net.mullvad.mullvadvpn.util.Lc
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -54,17 +52,14 @@ class AccountViewModelTest {
DeviceState.LoggedIn(accountNumber = dummyAccountNumber, device = dummyDevice)
)
private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null)
- private val purchaseResult = MutableStateFlow<PurchaseResult?>(null)
private val accountExpiryState = MutableStateFlow(null)
private lateinit var viewModel: AccountViewModel
@BeforeEach
fun setup() {
- mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
every { mockAccountRepository.accountData } returns accountExpiryState
every { mockDeviceRepository.deviceState } returns deviceState
- coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult
coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailability
coEvery { mockAccountRepository.getAccountData() } returns null
@@ -73,7 +68,6 @@ class AccountViewModelTest {
accountRepository = mockAccountRepository,
deviceRepository = mockDeviceRepository,
paymentUseCase = mockPaymentUseCase,
- isPlayBuild = false,
)
}
@@ -89,7 +83,8 @@ class AccountViewModelTest {
deviceState.value =
DeviceState.LoggedIn(accountNumber = dummyAccountNumber, device = dummyDevice)
val result = awaitItem()
- assertEquals(dummyAccountNumber, result.accountNumber)
+ assertIs<Lc.Content<AccountUiState>>(result)
+ assertEquals(dummyAccountNumber, result.value.accountNumber)
}
}
@@ -106,119 +101,29 @@ class AccountViewModelTest {
}
@Test
- fun `when paymentAvailability emits ProductsUnavailable uiState should be NoPayment`() =
- runTest {
- // Act, Assert
- viewModel.uiState.test {
- awaitItem() // Default state
- paymentAvailability.tryEmit(PaymentAvailability.ProductsUnavailable)
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.NoPayment>(result)
- }
- }
+ fun `when there is a pending purchase, uiState should reflect it`() = runTest {
+ // Arrange
+ paymentAvailability.value =
+ PaymentAvailability.ProductsAvailable(
+ products =
+ listOf(
+ PaymentProduct(
+ productId = ProductId("test_product_id"),
+ price = ProductPrice("9.99"),
+ status = PaymentStatus.PENDING,
+ )
+ )
+ )
- @Test
- fun `when paymentAvailability emits ErrorOther uiState should be ErrorGeneric`() = runTest {
// Act, Assert
viewModel.uiState.test {
- awaitItem() // Default state
- paymentAvailability.tryEmit(PaymentAvailability.Error.Other(mockk()))
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Generic>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ErrorBillingUnavailable uiState should be ErrorBilling`() =
- runTest {
- // Act, Assert
- viewModel.uiState.test {
- awaitItem() // Default state
- paymentAvailability.tryEmit(PaymentAvailability.Error.BillingUnavailable)
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Billing>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ProductsAvailable uiState should be Available with products`() =
- runTest {
- // Arrange
- val mockProduct: PaymentProduct = mockk()
- val expectedProductList = listOf(mockProduct)
-
- // Act, Assert
- viewModel.uiState.test {
- awaitItem() // Default state
- paymentAvailability.tryEmit(
- PaymentAvailability.ProductsAvailable(listOf(mockProduct))
- )
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.PaymentAvailable>(result)
- assertLists(expectedProductList, result.products)
- }
+ val result = awaitItem()
+ assertIs<Lc.Content<AccountUiState>>(result)
+ assertEquals(true, result.value.verificationPending)
}
-
- @Test
- fun `startBillingPayment should invoke purchaseProduct on PaymentUseCase`() {
- // Arrange
- val mockProductId = ProductId("MOCK")
- val mockActivityProvider = mockk<() -> Activity>()
-
- // Act
- viewModel.startBillingPayment(mockProductId, mockActivityProvider)
-
- // Assert
- coVerify { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success should invoke fetchAccountExpiry on AccountRepository`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = true)
-
- // Assert
- coVerify { mockAccountRepository.getAccountData() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success should invoke resetPurchaseResult on PaymentUseCase`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = true)
-
- // Assert
- coVerify { mockPaymentUseCase.resetPurchaseResult() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success false should invoke queryPaymentAvailability on PaymentUseCase`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = false)
-
- // Assert
- coVerify { mockPaymentUseCase.queryPaymentAvailability() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success false should invoke resetPurchaseResult on PaymentUseCase`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = false)
-
- // Assert
- coVerify { mockPaymentUseCase.resetPurchaseResult() }
}
companion object {
- private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
- "net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
private const val DUMMY_DEVICE_NAME = "fake_name"
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AddTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AddTimeViewModelTest.kt
new file mode 100644
index 0000000000..3d4eb07905
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AddTimeViewModelTest.kt
@@ -0,0 +1,200 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import android.app.Activity
+import app.cash.turbine.test
+import arrow.core.right
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.state.AddTimeUiState
+import net.mullvad.mullvadvpn.compose.state.PaymentState
+import net.mullvad.mullvadvpn.compose.state.PurchaseState
+import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
+import net.mullvad.mullvadvpn.lib.payment.model.ProductId
+import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
+import net.mullvad.mullvadvpn.lib.payment.model.VerificationResult
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
+import net.mullvad.mullvadvpn.usecase.PaymentUseCase
+import net.mullvad.mullvadvpn.util.Lc
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class AddTimeViewModelTest {
+
+ private val mockPaymentUseCase: PaymentUseCase = mockk()
+ private val mockAccountRepository: AccountRepository = mockk()
+ private val mockConnectionProxy: ConnectionProxy = mockk()
+
+ private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null)
+ private val purchaseResult = MutableStateFlow<PurchaseResult?>(null)
+ private val tunnelState = MutableStateFlow(TunnelState.Disconnected(null))
+
+ private lateinit var viewModel: AddTimeViewModel
+
+ @BeforeEach
+ fun setUp() {
+ every { mockPaymentUseCase.paymentAvailability } returns paymentAvailability
+ every { mockPaymentUseCase.purchaseResult } returns purchaseResult
+ every { mockConnectionProxy.tunnelState } returns tunnelState
+
+ coEvery { mockPaymentUseCase.verifyPurchases() } returns
+ VerificationResult.NothingToVerify.right()
+ coEvery { mockPaymentUseCase.queryPaymentAvailability() } just Runs
+ coEvery { mockPaymentUseCase.resetPurchaseResult() } just Runs
+ coEvery { mockAccountRepository.getAccountData() } returns null
+
+ viewModel =
+ AddTimeViewModel(
+ paymentUseCase = mockPaymentUseCase,
+ accountRepository = mockAccountRepository,
+ connectionProxy = mockConnectionProxy,
+ isPlayBuild = true,
+ )
+ }
+
+ @Test
+ fun `when paymentAvailability emits ProductsUnavailable uiState should be NoPayment`() =
+ runTest {
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem() // Default state
+ paymentAvailability.tryEmit(PaymentAvailability.ProductsUnavailable)
+ val result = awaitItem()
+ assertIs<Lc.Content<AddTimeUiState>>(result)
+ assertIs<PaymentState.NoPayment>(result.value.billingPaymentState)
+ }
+ }
+
+ @Test
+ fun `when paymentAvailability emits ErrorOther uiState should be null`() = runTest {
+ // Arrange
+ paymentAvailability.tryEmit(PaymentAvailability.Error.Other(mockk()))
+
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem() // Default state
+ val result = awaitItem()
+ assertIs<Lc.Content<AddTimeUiState>>(result)
+ assertIs<PaymentState.Error.Generic>(result.value.billingPaymentState)
+ }
+ }
+
+ @Test
+ fun `when paymentAvailability emits ErrorBillingUnavailable uiState should be ErrorBilling`() =
+ runTest {
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem() // Default state
+ paymentAvailability.tryEmit(PaymentAvailability.Error.BillingUnavailable)
+ val result = awaitItem()
+ assertIs<Lc.Content<AddTimeUiState>>(result)
+ assertIs<PaymentState.Error.Billing>(result.value.billingPaymentState)
+ }
+ }
+
+ @Test
+ fun `when paymentAvailability emits ProductsAvailable uiState should be Available with products`() =
+ runTest {
+ // Arrange
+ val mockProduct: PaymentProduct = mockk()
+ val expectedProductList = listOf(mockProduct)
+
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem() // Default state
+ paymentAvailability.tryEmit(
+ PaymentAvailability.ProductsAvailable(listOf(mockProduct))
+ )
+ val result = awaitItem()
+ assertIs<Lc.Content<AddTimeUiState>>(result)
+ assertIs<PaymentState.PaymentAvailable>(result.value.billingPaymentState)
+ assertLists(expectedProductList, result.value.billingPaymentState.products)
+ }
+ }
+
+ @Test
+ fun `startBillingPayment should invoke purchaseProduct on PaymentUseCase`() {
+ // Arrange
+ val mockProductId = ProductId("MOCK")
+ val mockActivityProvider = mockk<() -> Activity>()
+ coEvery { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) } just
+ Runs
+
+ // Act
+ viewModel.startBillingPayment(mockProductId, mockActivityProvider)
+
+ // Assert
+ coVerify { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) }
+ }
+
+ @Test
+ fun `onClosePurchaseResultDialog with success should invoke fetchAccountExpiry on AccountRepository`() {
+ // Arrange
+
+ // Act
+ viewModel.onClosePurchaseResultDialog(success = true)
+
+ // Assert
+ coVerify { mockAccountRepository.getAccountData() }
+ }
+
+ @Test
+ fun `onClosePurchaseResultDialog with success should invoke resetPurchaseResult on PaymentUseCase`() {
+ // Arrange
+
+ // Act
+ viewModel.onClosePurchaseResultDialog(success = true)
+
+ // Assert
+ coVerify { mockPaymentUseCase.resetPurchaseResult() }
+ }
+
+ @Test
+ fun `onClosePurchaseResultDialog with success false should invoke queryPaymentAvailability on PaymentUseCase`() {
+ // Arrange
+
+ // Act
+ viewModel.onClosePurchaseResultDialog(success = false)
+
+ // Assert
+ coVerify { mockPaymentUseCase.queryPaymentAvailability() }
+ }
+
+ @Test
+ fun `onClosePurchaseResultDialog with success false should invoke resetPurchaseResult on PaymentUseCase`() {
+ // Arrange
+
+ // Act
+ viewModel.onClosePurchaseResultDialog(success = false)
+
+ // Assert
+ coVerify { mockPaymentUseCase.resetPurchaseResult() }
+ }
+
+ @Test
+ fun `purchaseResult emitting Success should result in success dialog state`() = runTest {
+ // Arrange
+ val result = PurchaseState.Success(ProductId("one_month"))
+ val purchaseResultData = PurchaseResult.Completed.Success(ProductId("one_month"))
+
+ // Act, Assert
+ viewModel.uiState.test {
+ awaitItem()
+ purchaseResult.value = purchaseResultData
+ val item = awaitItem()
+ assertIs<Lc.Content<AddTimeUiState>>(item)
+ assertEquals(result, item.value.purchaseState)
+ }
+ }
+}
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 bfaed10629..fab79f19c4 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
@@ -7,22 +7,23 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.compose.state.PaymentState
+import net.mullvad.mullvadvpn.compose.state.OutOfTimeUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.lib.common.test.assertLists
import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.DeviceState
import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus
+import net.mullvad.mullvadvpn.lib.payment.model.ProductId
+import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
@@ -63,8 +64,6 @@ class OutOfTimeViewModelTest {
@BeforeEach
fun setup() {
- mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
-
every { mockServiceConnectionManager.connectionState } returns serviceConnectionStateFlow
every { mockConnectionProxy.tunnelState } returns tunnelState
@@ -151,108 +150,25 @@ class OutOfTimeViewModelTest {
}
@Test
- fun `when paymentAvailability emits ProductsUnavailable uiState should include state NoPayment`() =
- runTest {
- // Arrange
- val productsUnavailable = PaymentAvailability.ProductsUnavailable
- paymentAvailabilityFlow.value = productsUnavailable
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.NoPayment>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ErrorOther uiState should include state ErrorGeneric`() =
- runTest {
- // Arrange
- val paymentAvailabilityError = PaymentAvailability.Error.Other(mockk())
- paymentAvailabilityFlow.value = paymentAvailabilityError
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Generic>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ErrorBillingUnavailable uiState should be ErrorBilling`() =
- runTest {
- // Arrange
- val paymentAvailabilityError = PaymentAvailability.Error.BillingUnavailable
- paymentAvailabilityFlow.value = paymentAvailabilityError
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Billing>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ProductsAvailable uiState should be Available with products`() =
- runTest {
- // Arrange
- val mockProduct: PaymentProduct = mockk()
- val expectedProductList = listOf(mockProduct)
- val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct))
- paymentAvailabilityFlow.value = productsAvailable
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.PaymentAvailable>(result)
- assertLists(expectedProductList, result.products)
- }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success should invoke getAccountData on AccountRepository`() {
- // Act
- viewModel.onClosePurchaseResultDialog(success = true)
-
- // Assert
- coVerify { mockAccountRepository.getAccountData() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success should invoke resetPurchaseResult on PaymentUseCase`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = true)
-
- // Assert
- coVerify { mockPaymentUseCase.resetPurchaseResult() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success false should invoke queryPaymentAvailability on PaymentUseCase`() {
- // Arrange
-
- // Act
- viewModel.onClosePurchaseResultDialog(success = false)
-
- // Assert
- coVerify { mockPaymentUseCase.queryPaymentAvailability() }
- }
-
- @Test
- fun `onClosePurchaseResultDialog with success false should invoke resetPurchaseResult on PaymentUseCase`() {
+ fun `when there is a pending purchase, uiState should reflect it`() = runTest {
// Arrange
+ paymentAvailabilityFlow.value =
+ PaymentAvailability.ProductsAvailable(
+ products =
+ listOf(
+ PaymentProduct(
+ productId = ProductId("test_product_id"),
+ price = ProductPrice("9.99"),
+ status = PaymentStatus.PENDING,
+ )
+ )
+ )
- // Act
- viewModel.onClosePurchaseResultDialog(success = false)
-
- // Assert
- coVerify { mockPaymentUseCase.resetPurchaseResult() }
- }
-
- companion object {
- private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
- "net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
+ // Act, Assert
+ viewModel.uiState.test {
+ val result = awaitItem()
+ assertIs<OutOfTimeUiState>(result)
+ assertEquals(true, result.verificationPending)
+ }
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt
deleted file mode 100644
index 49e98c95e6..0000000000
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/PaymentViewModelTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package net.mullvad.mullvadvpn.viewmodel
-
-import app.cash.turbine.test
-import io.mockk.coEvery
-import io.mockk.mockk
-import io.mockk.unmockkAll
-import kotlin.test.assertEquals
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData
-import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
-import net.mullvad.mullvadvpn.usecase.PaymentUseCase
-import net.mullvad.mullvadvpn.util.toPaymentDialogData
-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 PaymentViewModelTest {
-
- private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
-
- private val purchaseResult = MutableStateFlow<PurchaseResult?>(null)
-
- private lateinit var viewModel: PaymentViewModel
-
- @BeforeEach
- fun setup() {
- coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult
-
- viewModel = PaymentViewModel(paymentUseCase = mockPaymentUseCase)
- }
-
- @AfterEach
- fun tearDown() {
- unmockkAll()
- }
-
- @Test
- fun `given PaymentUseCase purchaseResult emits cancelled uiSideEffect should emit PaymentCancelled`() =
- runTest {
- // Arrange
- val result = PurchaseResult.Completed.Cancelled
- purchaseResult.value = result
-
- // Act, Assert
- viewModel.uiState.test {
- assertEquals(PaymentDialogData(), awaitItem().paymentDialogData)
- purchaseResult.value = result
- }
-
- viewModel.uiSideEffect.test {
- assertEquals(PaymentUiSideEffect.PaymentCancelled, awaitItem())
- }
- }
-
- @Test
- fun `purchaseResult emitting Success should result in success dialog state`() = runTest {
- // Arrange
- val result = PurchaseResult.Completed.Success
-
- // Act, Assert
- viewModel.uiState.test {
- awaitItem()
- purchaseResult.value = result
- assertEquals(result.toPaymentDialogData(), awaitItem().paymentDialogData)
- }
- }
-}
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 b2b59ba69e..a96d59361a 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
@@ -7,7 +7,6 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
import java.time.ZonedDateTime
import kotlin.test.assertEquals
@@ -15,9 +14,8 @@ import kotlin.test.assertIs
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.compose.state.PaymentState
+import net.mullvad.mullvadvpn.compose.state.WelcomeUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.lib.common.test.assertLists
import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.Device
@@ -26,6 +24,9 @@ import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus
+import net.mullvad.mullvadvpn.lib.payment.model.ProductId
+import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
@@ -33,6 +34,7 @@ import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
+import net.mullvad.mullvadvpn.util.Lc
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -63,8 +65,6 @@ class WelcomeViewModelTest {
@BeforeEach
fun setup() {
- mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
-
every { mockDeviceRepository.deviceState } returns deviceStateFlow
every { mockServiceConnectionManager.connectionState } returns serviceConnectionStateFlow
@@ -120,7 +120,8 @@ class WelcomeViewModelTest {
awaitItem()
tunnelState.emit(tunnelUiStateTestItem)
val result = awaitItem()
- assertEquals(tunnelUiStateTestItem, result.tunnelState)
+ assertIs<Lc.Content<WelcomeUiState>>(result)
+ assertEquals(tunnelUiStateTestItem, result.value.tunnelState)
}
}
@@ -139,7 +140,9 @@ class WelcomeViewModelTest {
paymentAvailabilityFlow.value = null
deviceStateFlow.value =
DeviceState.LoggedIn(accountNumber = expectedAccountNumber, device = device)
- assertEquals(expectedAccountNumber, awaitItem().accountNumber)
+ val result = awaitItem()
+ assertIs<Lc.Content<WelcomeUiState>>(result)
+ assertEquals(expectedAccountNumber, result.value.accountNumber)
}
}
@@ -158,66 +161,6 @@ class WelcomeViewModelTest {
}
@Test
- fun `when paymentAvailability emits ProductsUnavailable uiState should include state NoPayment`() =
- runTest {
- // Arrange
- val productsUnavailable = PaymentAvailability.ProductsUnavailable
-
- // Act, Assert
- viewModel.uiState.test {
- // Default item
- awaitItem()
- paymentAvailabilityFlow.tryEmit(productsUnavailable)
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.NoPayment>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ErrorOther uiState should include state ErrorGeneric`() =
- runTest {
- // Arrange
- val paymentOtherError = PaymentAvailability.Error.Other(mockk())
- paymentAvailabilityFlow.tryEmit(paymentOtherError)
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Generic>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ErrorBillingUnavailable uiState should include state ErrorBilling`() =
- runTest { // Arrange
- val paymentBillingError = PaymentAvailability.Error.BillingUnavailable
- paymentAvailabilityFlow.value = paymentBillingError
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.Error.Billing>(result)
- }
- }
-
- @Test
- fun `when paymentAvailability emits ProductsAvailable uiState should include state Available with products`() =
- runTest {
- // Arrange
- val mockProduct: PaymentProduct = mockk()
- val expectedProductList = listOf(mockProduct)
- val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct))
- paymentAvailabilityFlow.value = productsAvailable
-
- // Act, Assert
- viewModel.uiState.test {
- val result = awaitItem().billingPaymentState
- assertIs<PaymentState.PaymentAvailable>(result)
- assertLists(expectedProductList, result.products)
- }
- }
-
- @Test
fun `when on disconnect click is called should call connection proxy disconnect`() = runTest {
// Arrange
coEvery { mockConnectionProxy.disconnect() } returns true.right()
@@ -229,8 +172,26 @@ class WelcomeViewModelTest {
coVerify { mockConnectionProxy.disconnect() }
}
- companion object {
- private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
- "net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
+ @Test
+ fun `when there is a pending purchase, uiState should reflect it`() = runTest {
+ // Arrange
+ paymentAvailabilityFlow.value =
+ PaymentAvailability.ProductsAvailable(
+ products =
+ listOf(
+ PaymentProduct(
+ productId = ProductId("test_product_id"),
+ price = ProductPrice("9.99"),
+ status = PaymentStatus.PENDING,
+ )
+ )
+ )
+
+ // Act, Assert
+ viewModel.uiState.test {
+ val result = awaitItem()
+ assertIs<Lc.Content<WelcomeUiState>>(result)
+ assertEquals(true, result.value.verificationPending)
+ }
}
}