diff options
Diffstat (limited to 'android/app/src/test')
4 files changed, 521 insertions, 8 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 new file mode 100644 index 0000000000..a1d8bee37a --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/PlayPaymentUseCaseTest.kt @@ -0,0 +1,104 @@ +package net.mullvad.mullvadvpn.usecase + +import app.cash.turbine.test +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.lib.payment.PaymentRepository +import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability +import net.mullvad.mullvadvpn.lib.payment.model.ProductId +import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult +import org.junit.Test + +class PlayPaymentUseCaseTest { + + private val mockPaymentRepository: PaymentRepository = mockk(relaxed = true) + + private val playPaymentUseCase = PlayPaymentUseCase(mockPaymentRepository) + + @Test + fun testUpdatePaymentAvailability() = runTest { + // Arrange + val productsUnavailable = PaymentAvailability.ProductsUnavailable + val paymentRepositoryQueryPaymentAvailabilityFlow = flow { emit(productsUnavailable) } + every { mockPaymentRepository.queryPaymentAvailability() } returns + paymentRepositoryQueryPaymentAvailabilityFlow + + // Act, Assert + playPaymentUseCase.paymentAvailability.test { + assertNull(awaitItem()) + playPaymentUseCase.queryPaymentAvailability() + assertEquals(productsUnavailable, awaitItem()) + } + } + + @Test + fun testUpdatePurchaseResult() = runTest { + // Arrange + val fetchingProducts = PurchaseResult.FetchingProducts + val productId = ProductId("productId") + val paymentRepositoryPurchaseResultFlow = flow { emit(fetchingProducts) } + every { mockPaymentRepository.purchaseProduct(any(), any()) } returns + paymentRepositoryPurchaseResultFlow + + // Act, Assert + playPaymentUseCase.purchaseResult.test { + assertNull(awaitItem()) + playPaymentUseCase.purchaseProduct(productId, mockk()) + assertEquals(fetchingProducts, awaitItem()) + } + } + + @Test + fun testPurchaseProduct() = runTest { + // Arrange + val productId = ProductId("productId") + + // Act + playPaymentUseCase.purchaseProduct(productId, mockk()) + + // Assert + coVerify { mockPaymentRepository.purchaseProduct(productId, any()) } + } + + @Test + fun testQueryPaymentAvailability() = runTest { + // Act + playPaymentUseCase.queryPaymentAvailability() + + // Assert + coVerify { mockPaymentRepository.queryPaymentAvailability() } + } + + @Test + fun testResetPurchaseResult() = runTest { + // Arrange + val completedSuccess = PurchaseResult.Completed.Success + val productId = ProductId("productId") + val paymentRepositoryPurchaseResultFlow = flow { emit(completedSuccess) } + every { mockPaymentRepository.purchaseProduct(any(), any()) } returns + paymentRepositoryPurchaseResultFlow + + // Act, Assert + playPaymentUseCase.purchaseResult.test { + assertNull(awaitItem()) + playPaymentUseCase.purchaseProduct(productId, mockk()) + assertEquals(completedSuccess, awaitItem()) + playPaymentUseCase.resetPurchaseResult() + assertNull(awaitItem()) + } + } + + @Test + fun testVerifyPurchases() = runTest { + // Act + playPaymentUseCase.verifyPurchases() + + // Assert + coVerify { mockPaymentRepository.verifyPurchases() } + } +} 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 fc1fd5e99b..c02e755951 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,15 +1,27 @@ package net.mullvad.mullvadvpn.viewmodel +import android.app.Activity 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 import io.mockk.unmockkAll import io.mockk.verify import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData +import net.mullvad.mullvadvpn.compose.state.PaymentState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.common.test.assertLists +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.model.AccountAndDevice import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.Device @@ -19,6 +31,8 @@ import net.mullvad.mullvadvpn.repository.DeviceRepository import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache +import net.mullvad.mullvadvpn.usecase.PaymentUseCase +import net.mullvad.mullvadvpn.util.toPaymentDialogData import org.junit.After import org.junit.Before import org.junit.Rule @@ -31,8 +45,11 @@ class AccountViewModelTest { private val mockServiceConnectionManager: ServiceConnectionManager = mockk() private val mockDeviceRepository: DeviceRepository = mockk() private val mockAuthTokenCache: AuthTokenCache = mockk() + private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) private val deviceState: MutableStateFlow<DeviceState> = MutableStateFlow(DeviceState.Initial) + private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null) + private val purchaseResult = MutableStateFlow<PurchaseResult?>(null) private val accountExpiryState = MutableStateFlow(AccountExpiry.Missing) private val dummyAccountAndDevice: AccountAndDevice = @@ -51,15 +68,19 @@ class AccountViewModelTest { @Before fun setUp() { mockkStatic(CACHE_EXTENSION_CLASS) + mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache every { mockDeviceRepository.deviceState } returns deviceState every { mockAccountRepository.accountExpiryState } returns accountExpiryState + coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult + coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailability viewModel = AccountViewModel( accountRepository = mockAccountRepository, serviceConnectionManager = mockServiceConnectionManager, - deviceRepository = mockDeviceRepository + deviceRepository = mockDeviceRepository, + paymentUseCase = mockPaymentUseCase ) } @@ -72,10 +93,9 @@ class AccountViewModelTest { fun testAccountLoggedInState() = runTest { // Act, Assert viewModel.uiState.test { - var result = awaitItem() - assertEquals(null, result.deviceName) + awaitItem() // Default state deviceState.value = DeviceState.LoggedIn(accountAndDevice = dummyAccountAndDevice) - result = awaitItem() + val result = awaitItem() assertEquals(DUMMY_DEVICE_NAME, result.accountNumber) } } @@ -89,8 +109,121 @@ class AccountViewModelTest { verify { mockAccountRepository.logout() } } + @Test + fun testBillingProductsUnavailableState() = runTest { + // Arrange in setup + + // Act, Assert + viewModel.uiState.test { + awaitItem() // Default state + paymentAvailability.tryEmit(PaymentAvailability.ProductsUnavailable) + val result = awaitItem().billingPaymentState + assertIs<PaymentState.NoPayment>(result) + } + } + + @Test + fun testBillingProductsGenericErrorState() = 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 testBillingProductsBillingErrorState() = 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 testBillingProductsPaymentAvailableState() = 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) + } + } + + @Test + fun testBillingUserCancelled() = runTest { + // Arrange + val result = PurchaseResult.Completed.Cancelled + purchaseResult.value = result + every { result.toPaymentDialogData() } returns null + + // Act, Assert + viewModel.uiState.test { assertNull(awaitItem().paymentDialogData) } + } + + @Test + fun testBillingPurchaseSuccess() = runTest { + // Arrange + val result = PurchaseResult.Completed.Success + val expectedData: PaymentDialogData = mockk() + purchaseResult.value = result + every { result.toPaymentDialogData() } returns expectedData + + // Act, Assert + viewModel.uiState.test { assertEquals(expectedData, awaitItem().paymentDialogData) } + } + + @Test + fun testStartBillingPayment() { + // Arrange + val mockProductId = ProductId("MOCK") + val mockActivityProvider = mockk<() -> Activity>() + + // Act + viewModel.startBillingPayment(mockProductId, mockActivityProvider) + + // Assert + coVerify { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) } + } + + @Test + fun testOnClosePurchaseResultDialogSuccessful() { + // Arrange + + // Act + viewModel.onClosePurchaseResultDialog(success = true) + + // Assert + verify { mockAccountRepository.fetchAccountExpiry() } + coVerify { mockPaymentUseCase.resetPurchaseResult() } + } + + @Test + fun testOnClosePurchaseResultDialogNotSuccessful() { + // Arrange + + // Act + viewModel.onClosePurchaseResultDialog(success = false) + + // Assert + coVerify { mockPaymentUseCase.queryPaymentAvailability() } + coVerify { mockPaymentUseCase.resetPurchaseResult() } + } + companion object { private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt" + 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/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt index 8c1ec10f5a..dad51eab59 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 @@ -1,8 +1,10 @@ package net.mullvad.mullvadvpn.viewmodel +import android.app.Activity 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,11 +12,19 @@ import io.mockk.unmockkAll import io.mockk.verify import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNull import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData import net.mullvad.mullvadvpn.compose.state.OutOfTimeUiState +import net.mullvad.mullvadvpn.compose.state.PaymentState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.common.test.assertLists +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.model.AccountExpiry import net.mullvad.mullvadvpn.model.DeviceState import net.mullvad.mullvadvpn.model.TunnelState @@ -27,6 +37,8 @@ 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.ui.serviceconnection.connectionProxy +import net.mullvad.mullvadvpn.usecase.PaymentUseCase +import net.mullvad.mullvadvpn.util.toPaymentDialogData import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime import org.joda.time.ReadableInstant @@ -42,6 +54,8 @@ class OutOfTimeViewModelTest { MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected) private val accountExpiryState = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing) private val deviceState = MutableStateFlow<DeviceState>(DeviceState.Initial) + private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null) + private val purchaseResult = MutableStateFlow<PurchaseResult?>(null) // Service connections private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -50,15 +64,17 @@ class OutOfTimeViewModelTest { // Event notifiers private val eventNotifierTunnelRealState = EventNotifier<TunnelState>(TunnelState.Disconnected) - private val mockAccountRepository: AccountRepository = mockk() + private val mockAccountRepository: AccountRepository = mockk(relaxed = true) private val mockDeviceRepository: DeviceRepository = mockk() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() + private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) private lateinit var viewModel: OutOfTimeViewModel @Before fun setUp() { mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS) + mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) every { mockServiceConnectionManager.connectionState } returns serviceConnectionState @@ -70,11 +86,16 @@ class OutOfTimeViewModelTest { every { mockDeviceRepository.deviceState } returns deviceState + coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult + + coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailability + viewModel = OutOfTimeViewModel( accountRepository = mockAccountRepository, serviceConnectionManager = mockServiceConnectionManager, deviceRepository = mockDeviceRepository, + paymentUseCase = mockPaymentUseCase, pollAccountExpiry = false ) } @@ -112,9 +133,9 @@ class OutOfTimeViewModelTest { // Act, Assert viewModel.uiState.test { assertEquals(OutOfTimeUiState(deviceName = ""), awaitItem()) + eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) serviceConnectionState.value = ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) - eventNotifierTunnelRealState.notify(tunnelRealStateTestItem) val result = awaitItem() assertEquals(tunnelRealStateTestItem, result.tunnelState) } @@ -149,8 +170,135 @@ class OutOfTimeViewModelTest { verify { mockProxy.disconnect() } } + @Test + fun testBillingProductsUnavailableState() = runTest { + // Arrange + val productsUnavailable = PaymentAvailability.ProductsUnavailable + paymentAvailability.value = productsUnavailable + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.NoPayment>(result) + } + } + + @Test + fun testBillingProductsGenericErrorState() = runTest { + // Arrange + val paymentAvailabilityError = PaymentAvailability.Error.Other(mockk()) + paymentAvailability.value = paymentAvailabilityError + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.Error.Generic>(result) + } + } + + @Test + fun testBillingProductsBillingErrorState() = runTest { + // Arrange + val paymentAvailabilityError = PaymentAvailability.Error.BillingUnavailable + paymentAvailability.value = paymentAvailabilityError + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.Error.Billing>(result) + } + } + + @Test + fun testBillingProductsPaymentAvailableState() = runTest { + // Arrange + val mockProduct: PaymentProduct = mockk() + val expectedProductList = listOf(mockProduct) + val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct)) + paymentAvailability.value = productsAvailable + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.PaymentAvailable>(result) + assertLists(expectedProductList, result.products) + } + } + + @Test + fun testBillingUserCancelled() = runTest { + // Arrange + val result = PurchaseResult.Completed.Cancelled + purchaseResult.value = result + every { result.toPaymentDialogData() } returns null + + // Act, Assert + viewModel.uiState.test { assertNull(awaitItem().paymentDialogData) } + } + + @Test + fun testBillingPurchaseSuccess() = runTest { + // Arrange + val result = PurchaseResult.Completed.Success + val expectedData: PaymentDialogData = mockk() + purchaseResult.value = result + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + every { result.toPaymentDialogData() } returns expectedData + + // Act, Assert + viewModel.uiState.test { assertEquals(expectedData, awaitItem().paymentDialogData) } + } + + @Test + fun testStartBillingPayment() { + // Arrange + val mockProductId = ProductId("MOCK") + val mockActivityProvider = mockk<() -> Activity>() + + // Act + viewModel.startBillingPayment(mockProductId, mockActivityProvider) + + // Assert + coVerify { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) } + } + + @Test + fun testOnClosePurchaseResultDialogSuccessful() { + // Arrange + + // Act + viewModel.onClosePurchaseResultDialog(success = true) + + // Assert + verify { mockAccountRepository.fetchAccountExpiry() } + coVerify { mockPaymentUseCase.resetPurchaseResult() } + } + + @Test + fun testOnClosePurchaseResultDialogNotSuccessful() { + // Arrange + + // Act + viewModel.onClosePurchaseResultDialog(success = false) + + // Assert + coVerify { mockPaymentUseCase.queryPaymentAvailability() } + coVerify { mockPaymentUseCase.resetPurchaseResult() } + } + companion object { private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS = "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt" + private const val PURCHASE_RESULT_EXTENSIONS_CLASS = + "net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt" } } 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 b16eeec2f8..e958df9337 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 @@ -1,19 +1,29 @@ package net.mullvad.mullvadvpn.viewmodel +import android.app.Activity 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 import io.mockk.unmockkAll import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertNull import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.dialog.payment.PaymentDialogData +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.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.model.AccountAndDevice import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.Device @@ -27,6 +37,8 @@ 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.PaymentUseCase +import net.mullvad.mullvadvpn.util.toPaymentDialogData import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime import org.joda.time.ReadableInstant @@ -42,6 +54,8 @@ class WelcomeViewModelTest { MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected) private val deviceState = MutableStateFlow<DeviceState>(DeviceState.Initial) private val accountExpiryState = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing) + private val purchaseResult = MutableStateFlow<PurchaseResult?>(null) + private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null) // Service connections private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk() @@ -50,15 +64,17 @@ class WelcomeViewModelTest { // Event notifiers private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected) - private val mockAccountRepository: AccountRepository = mockk() + private val mockAccountRepository: AccountRepository = mockk(relaxed = true) private val mockDeviceRepository: DeviceRepository = mockk() private val mockServiceConnectionManager: ServiceConnectionManager = mockk() + private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true) private lateinit var viewModel: WelcomeViewModel @Before fun setUp() { mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS) + mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS) every { mockDeviceRepository.deviceState } returns deviceState @@ -70,11 +86,16 @@ class WelcomeViewModelTest { every { mockAccountRepository.accountExpiryState } returns accountExpiryState + coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult + + coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailability + viewModel = WelcomeViewModel( accountRepository = mockAccountRepository, deviceRepository = mockDeviceRepository, serviceConnectionManager = mockServiceConnectionManager, + paymentUseCase = mockPaymentUseCase, pollAccountExpiry = false ) } @@ -112,9 +133,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) } @@ -158,8 +179,115 @@ class WelcomeViewModelTest { } } + @Test + fun testBillingProductsUnavailableState() = runTest { + // Arrange + val productsUnavailable = PaymentAvailability.ProductsUnavailable + + // Act, Assert + viewModel.uiState.test { + // Default item + awaitItem() + paymentAvailability.tryEmit(productsUnavailable) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + val result = awaitItem().billingPaymentState + assertIs<PaymentState.NoPayment>(result) + } + } + + @Test + fun testBillingProductsGenericErrorState() = runTest { + // Arrange + val paymentOtherError = PaymentAvailability.Error.Other(mockk()) + paymentAvailability.tryEmit(paymentOtherError) + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.Error.Generic>(result) + } + } + + @Test + fun testBillingProductsBillingErrorState() = runTest { + // Arrange + val paymentBillingError = PaymentAvailability.Error.BillingUnavailable + paymentAvailability.value = paymentBillingError + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.Error.Billing>(result) + } + } + + @Test + fun testBillingProductsPaymentAvailableState() = runTest { + // Arrange + val mockProduct: PaymentProduct = mockk() + val expectedProductList = listOf(mockProduct) + val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct)) + paymentAvailability.value = productsAvailable + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + + // Act, Assert + viewModel.uiState.test { + val result = awaitItem().billingPaymentState + assertIs<PaymentState.PaymentAvailable>(result) + assertLists(expectedProductList, result.products) + } + } + + @Test + fun testBillingUserCancelled() = runTest { + // Arrange + val result = PurchaseResult.Completed.Cancelled + purchaseResult.value = result + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + every { result.toPaymentDialogData() } returns null + + // Act, Assert + viewModel.uiState.test { assertNull(awaitItem().paymentDialogData) } + } + + @Test + fun testBillingPurchaseSuccess() = runTest { + // Arrange + val result = PurchaseResult.Completed.Success + val expectedData: PaymentDialogData = mockk() + purchaseResult.value = result + serviceConnectionState.value = + ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer) + every { result.toPaymentDialogData() } returns expectedData + + // Act, Assert + viewModel.uiState.test { assertEquals(expectedData, awaitItem().paymentDialogData) } + } + + @Test + fun testStartBillingPayment() { + // Arrange + val mockProductId = ProductId("MOCK") + val mockActivityProvider = mockk<() -> Activity>() + + // Act + viewModel.startBillingPayment(mockProductId, mockActivityProvider) + + // Assert + coVerify { mockPaymentUseCase.purchaseProduct(mockProductId, mockActivityProvider) } + } + companion object { private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS = "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt" + private const val PURCHASE_RESULT_EXTENSIONS_CLASS = + "net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt" } } |
