diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-06-09 16:43:14 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-06-09 16:43:14 +0200 |
| commit | 8b0b5ab45c3e0720797bd381d4b02e70cf4043f9 (patch) | |
| tree | 4d5d5fc018053cf664be5c41040f8755de07c55d /android/app/src/test | |
| parent | 87e716c551f563b6bf181bcef87a58bee0fb2599 (diff) | |
| parent | 1c58ad3fc58c1862526d912efc311e06956317fd (diff) | |
| download | mullvadvpn-8b0b5ab45c3e0720797bd381d4b02e70cf4043f9.tar.xz mullvadvpn-8b0b5ab45c3e0720797bd381d4b02e70cf4043f9.zip | |
Merge branch 'implement-payment-screen-with-3-months-droid-1947'
Diffstat (limited to 'android/app/src/test')
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) + } } } |
