From 6e3610c86004e89009e7cfa99ad95e6f6cd85b0f Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Fri, 22 Sep 2023 15:37:37 +0200 Subject: [PATCH] Add payment unit tests for welcome view model --- .../viewmodel/WelcomeViewModelTest.kt | 183 +++++++++++++++++- 1 file changed, 180 insertions(+), 3 deletions(-) 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 52713c0b0d31..f39f4c7da86d 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 @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.viewModelScope import app.cash.turbine.test import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -10,10 +11,19 @@ import io.mockk.unmockkAll import kotlin.test.assertEquals import kotlin.test.assertIs import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.PaymentProvider +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.payment.PaymentRepository +import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability +import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct +import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult +import net.mullvad.mullvadvpn.lib.payment.model.VerificationResult import net.mullvad.mullvadvpn.model.AccountAndDevice import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.Device @@ -42,6 +52,12 @@ class WelcomeViewModelTest { MutableStateFlow(ServiceConnectionState.Disconnected) private val deviceState = MutableStateFlow(DeviceState.Initial) private val accountExpiryState = MutableStateFlow(AccountExpiry.Missing) + private val purchaseResult = + MutableSharedFlow(extraBufferCapacity = 1, replay = 1) + private val verifyPurchases = + MutableSharedFlow(extraBufferCapacity = 1, replay = 1) + private val paymentAvailability = + MutableSharedFlow(extraBufferCapacity = 1, replay = 1) // Service connections private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -52,6 +68,8 @@ class WelcomeViewModelTest { private val mockAccountRepository: AccountRepository = mockk() private val mockDeviceRepository: DeviceRepository = mockk() + private val mockPaymentRepository: PaymentRepository = mockk() + private val mockPaymentProvider: PaymentProvider = mockk() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private lateinit var viewModel: WelcomeViewModel @@ -70,11 +88,20 @@ class WelcomeViewModelTest { every { mockAccountRepository.accountExpiryState } returns accountExpiryState + coEvery { mockPaymentRepository.verifyPurchases() } returns verifyPurchases + + coEvery { mockPaymentRepository.purchaseBillingProduct(any()) } returns purchaseResult + + coEvery { mockPaymentRepository.queryPaymentAvailability() } returns paymentAvailability + + every { mockPaymentProvider.paymentRepository } returns mockPaymentRepository + viewModel = WelcomeViewModel( accountRepository = mockAccountRepository, deviceRepository = mockDeviceRepository, serviceConnectionManager = mockServiceConnectionManager, + paymentProvider = mockPaymentProvider, pollAccountExpiry = false ) } @@ -112,9 +139,9 @@ class WelcomeViewModelTest { // Act, Assert viewModel.uiState.test { assertEquals(WelcomeUiState(), awaitItem()) + eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) serviceConnectionState.value = ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - eventNotifierTunnelUiState.notify(tunnelUiStateTestItem) val result = awaitItem() assertEquals(tunnelUiStateTestItem, result.tunnelState) } @@ -131,13 +158,13 @@ class WelcomeViewModelTest { // Act, Assert viewModel.uiState.test { assertEquals(WelcomeUiState(), awaitItem()) - serviceConnectionState.value = - ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) deviceState.value = DeviceState.LoggedIn( accountAndDevice = AccountAndDevice(account_token = expectedAccountNumber, device = device) ) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) val result = awaitItem() assertEquals(expectedAccountNumber, result.accountNumber) } @@ -158,6 +185,156 @@ class WelcomeViewModelTest { } } + @Test + fun testVerifyPurchases() = runTest { + // Act + viewModel.verifyPurchases() + + // Assert + coVerify { mockPaymentRepository.verifyPurchases() } + } + + @Test + fun testBillingProductsUnavailableState() = runTest { + // Arrange + val mockPaymentAvailability = PaymentAvailability.ProductsUnavailable + + // Act, Assert + viewModel.uiState.test { + // Default item + awaitItem() + paymentAvailability.tryEmit(mockPaymentAvailability) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().billingPaymentState + assertIs(result) + } + } + + @Test + fun testBillingProductsGenericErrorState() = runTest { + // Arrange + val mockPaymentAvailability = PaymentAvailability.Error.Other(mockk()) + + // Act, Assert + viewModel.uiState.test { + // Default item + assertIs(awaitItem().billingPaymentState) + paymentAvailability.tryEmit(mockPaymentAvailability) + viewModel.fetchPaymentAvailability() + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().billingPaymentState + assertIs(result) + } + } + + @Test + fun testBillingProductsBillingErrorState() = runTest { + // Arrange + val mockPaymentAvailability = PaymentAvailability.Error.BillingUnavailable + + // Act, Assert + viewModel.uiState.test { + // Default item + assertIs(awaitItem().billingPaymentState) + paymentAvailability.tryEmit(mockPaymentAvailability) + viewModel.fetchPaymentAvailability() + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().billingPaymentState + assertIs(result) + } + } + + @Test + fun testBillingProductsPaymentAvailableState() = runTest { + // Arrange + val mockProduct: PaymentProduct = mockk() + val expectedProductList = listOf(mockProduct) + val mockPaymentAvailability = PaymentAvailability.ProductsAvailable(listOf(mockProduct)) + + // Act, Assert + viewModel.uiState.test { + // Default item + assertIs(awaitItem().billingPaymentState) + paymentAvailability.tryEmit(mockPaymentAvailability) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().billingPaymentState + assertIs(result) + assertLists(expectedProductList, result.products) + } + } + + @Test + fun testBillingVerificationError() = runTest { + // Arrange + val mockPurchaseResult = PurchaseResult.Error.VerificationError(null) + + // Act, Assert + viewModel.uiState.test { + // Default item + awaitItem() + purchaseResult.tryEmit(mockPurchaseResult) + viewModel.startBillingPayment(productId = "mockId") + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().purchaseResult + assertIs(result) + } + } + + @Test + fun testBillingUserCancelled() = runTest { + // Arrange + val mockPurchaseResult = PurchaseResult.PurchaseCancelled + + // Act, Assert + viewModel.uiState.test { + // Default item + awaitItem() + purchaseResult.tryEmit(mockPurchaseResult) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + viewModel.startBillingPayment(productId = "mockId") + assertIs(awaitItem().purchaseResult) + ensureAllEventsConsumed() + } + } + + @Test + fun testBillingPurchaseCompleted() = runTest { + // Arrange + val mockPurchaseResult = PurchaseResult.PurchaseCompleted + + // Act, Assert + viewModel.uiState.test { + // Default item + awaitItem() + purchaseResult.tryEmit(mockPurchaseResult) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + viewModel.startBillingPayment(productId = "mockId") + val result = awaitItem().purchaseResult + assertIs(result) + } + } + + @Test + fun testStartBillingPayment() { + // Arrange + val mockProductId = "MOCK" + coEvery { mockPaymentRepository.purchaseBillingProduct(mockProductId) } returns + mockk(relaxed = true) + + // Act + viewModel.startBillingPayment(mockProductId) + + // Assert + coVerify { mockPaymentRepository.purchaseBillingProduct(mockProductId) } + } + companion object { private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS = "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"