mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 19:00:26 +01:00
Add unit tests for InAppPaymentRecurringContextJob.
This commit is contained in:
committed by
Greyson Parrelli
parent
762c7a6d22
commit
b5f323d4af
@@ -17,24 +17,35 @@ import org.junit.rules.ExternalResource
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatValue
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentMethodType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Common setup between different tests that rely on donations infrastructure.
|
||||
*/
|
||||
class DonationsTestRule : ExternalResource() {
|
||||
class InAppPaymentsTestRule : ExternalResource() {
|
||||
|
||||
private var nextId = 1L
|
||||
private val inAppPaymentCache = mutableMapOf<InAppPaymentTable.InAppPaymentId, InAppPaymentTable.InAppPayment>()
|
||||
|
||||
private val configuration: SubscriptionsConfiguration by lazy {
|
||||
val testConfigJsonData = javaClass.classLoader!!.getResourceAsStream("donations_configuration_test_data.json").bufferedReader().readText()
|
||||
@@ -58,14 +69,48 @@ class DonationsTestRule : ExternalResource() {
|
||||
every { InAppDonations.isIDEALAvailable() } returns true
|
||||
|
||||
mockkObject(SignalDatabase.Companion)
|
||||
every { SignalDatabase.Companion.donationReceipts } returns mockk {
|
||||
every { SignalDatabase.Companion.donationReceipts.addReceipt(any()) } returns Unit
|
||||
}
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments } returns mockk {
|
||||
every { SignalDatabase.Companion.inAppPayments.update(any()) } returns Unit
|
||||
every { SignalDatabase.Companion.inAppPayments.insert(any(), any(), any(), any(), any()) } answers {
|
||||
val inAppPaymentData: InAppPaymentData = arg(4)
|
||||
val iap = createInAppPayment(firstArg(), inAppPaymentData.paymentMethodType.toPaymentSourceType())
|
||||
val id = InAppPaymentTable.InAppPaymentId(nextId)
|
||||
nextId++
|
||||
|
||||
inAppPaymentCache[id] = iap.copy(
|
||||
id = id,
|
||||
state = secondArg(),
|
||||
subscriberId = thirdArg(),
|
||||
endOfPeriod = arg(3) ?: 0.seconds,
|
||||
data = inAppPaymentData
|
||||
)
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.update(any()) } answers {
|
||||
val inAppPayment = firstArg<InAppPaymentTable.InAppPayment>()
|
||||
inAppPaymentCache[inAppPayment.id] = inAppPayment
|
||||
}
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(any()) } answers {
|
||||
val inAppPaymentId = firstArg<InAppPaymentTable.InAppPaymentId>()
|
||||
inAppPaymentCache[inAppPaymentId]
|
||||
}
|
||||
}
|
||||
|
||||
mockkObject(SignalStore.Companion)
|
||||
every { SignalStore.Companion.inAppPayments } returns mockk {
|
||||
every { setLastEndOfPeriod(any()) } returns Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun after() {
|
||||
unmockkStatic(RemoteConfig::class, InAppPaymentsRepository::class)
|
||||
unmockkObject(InAppDonations, SignalDatabase.Companion)
|
||||
unmockkObject(InAppDonations, SignalDatabase.Companion, SignalStore.Companion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,6 +120,38 @@ class DonationsTestRule : ExternalResource() {
|
||||
every { AppDependencies.donationsService.getDonationsConfiguration(any()) } returns ServiceResponse(200, "", configuration, null, null)
|
||||
}
|
||||
|
||||
fun initializeActiveSubscriptionMock(
|
||||
activeSubscription: ActiveSubscription? = null,
|
||||
executionError: Throwable? = null,
|
||||
applicationError: Throwable? = null
|
||||
) {
|
||||
every { AppDependencies.donationsService.getSubscription(any()) } returns ServiceResponse(200, "", activeSubscription, null, null)
|
||||
}
|
||||
|
||||
fun initializeSubmitReceiptCredentialRequestSync() {
|
||||
val receiptCredentialResponse = mockk<ReceiptCredentialResponse>()
|
||||
every { AppDependencies.donationsService.submitReceiptCredentialRequestSync(any(), any()) } returns ServiceResponse(200, "", receiptCredentialResponse, null, null)
|
||||
}
|
||||
|
||||
fun createActiveSubscription(): ActiveSubscription {
|
||||
return ActiveSubscription(
|
||||
ActiveSubscription.Subscription(
|
||||
2000,
|
||||
"USD",
|
||||
BigDecimal.ONE,
|
||||
System.currentTimeMillis().milliseconds.inWholeSeconds + 45.days.inWholeSeconds,
|
||||
true,
|
||||
System.currentTimeMillis().milliseconds.inWholeSeconds + 45.days.inWholeSeconds,
|
||||
false,
|
||||
"active",
|
||||
"STRIPE",
|
||||
"CARD",
|
||||
false
|
||||
),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
fun createInAppPayment(
|
||||
type: InAppPaymentType,
|
||||
paymentSourceType: PaymentSourceType
|
||||
@@ -96,4 +173,14 @@ class DonationsTestRule : ExternalResource() {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun mockLocalSubscriberAccess(initialSubscriber: InAppPaymentSubscriberRecord? = null): AtomicReference<InAppPaymentSubscriberRecord?> {
|
||||
val ref = AtomicReference(initialSubscriber)
|
||||
every { InAppPaymentsRepository.getSubscriber(any()) } answers { ref.get() }
|
||||
every { InAppPaymentsRepository.setSubscriber(any()) } answers { ref.set(firstArg()) }
|
||||
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
val appDependencies = MockAppDependenciesRule()
|
||||
|
||||
@get:Rule
|
||||
val donationsTestRule = DonationsTestRule()
|
||||
val inAppPaymentsTestRule = InAppPaymentsTestRule()
|
||||
|
||||
@Test
|
||||
fun `Given a throwable and self, when I handleCreatePaymentIntentError, then I expect a ONE_TIME error`() {
|
||||
@@ -174,7 +174,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `When I getBoosts, then I expect a filtered set of boost objects`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
inAppPaymentsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getBoosts().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
@@ -188,7 +188,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `When I getBoostBadge, then I expect a boost badge`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
inAppPaymentsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getBoostBadge().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
@@ -200,7 +200,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `When I getMinimumDonationAmounts, then I expect a map of 3 currencies`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
inAppPaymentsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getMinimumDonationAmounts().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
@@ -212,7 +212,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `Given a long running transaction, when I waitForOneTimeRedemption, then I expect DonationPending`() {
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.SEPADebit)
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.SEPADebit)
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(inAppPayment.id) } returns inAppPayment
|
||||
|
||||
@@ -230,7 +230,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `Given a non long running transaction, when I waitForOneTimeRedemption, then I expect TimeoutWaitingForTokenError`() {
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.CreditCard)
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.CreditCard)
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(inAppPayment.id) } returns inAppPayment
|
||||
|
||||
@@ -248,7 +248,7 @@ class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `Given no delays, when I waitForOneTimeRedemption, then I expect happy path`() {
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.CreditCard)
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.ONE_TIME_DONATION, PaymentSourceType.Stripe.CreditCard)
|
||||
|
||||
every { InAppPaymentsRepository.observeUpdates(inAppPayment.id) } returns Flowable.just(inAppPayment.copy(state = InAppPaymentTable.State.END))
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(inAppPayment.id) } returns inAppPayment
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AtomicReference
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isNotEqualTo
|
||||
import io.mockk.every
|
||||
@@ -52,10 +51,12 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
val appDependencies = MockAppDependenciesRule()
|
||||
|
||||
@get:Rule
|
||||
val donationsTestRule = DonationsTestRule()
|
||||
val inAppPaymentsTestRule = InAppPaymentsTestRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
InAppPaymentsTestRule.mockLocalSubscriberAccess()
|
||||
|
||||
mockkObject(SignalStore.Companion)
|
||||
every { SignalStore.Companion.inAppPayments } returns mockk {
|
||||
every { SignalStore.Companion.inAppPayments.getRecurringDonationCurrency() } returns Currency.getInstance("USD")
|
||||
@@ -81,7 +82,7 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `when I getDonationsConfiguration then I expect a set of three Subscription objects`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
inAppPaymentsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.getSubscriptions().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
@@ -95,7 +96,7 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `Given I do not need to rotate my subscriber id, when I ensureSubscriberId, then I use the same subscriber id`() {
|
||||
val initialSubscriber = createSubscriber()
|
||||
val ref = mockLocalSubscriberAccess(initialSubscriber)
|
||||
val ref = InAppPaymentsTestRule.mockLocalSubscriberAccess(initialSubscriber)
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.ensureSubscriberId(
|
||||
subscriberType = InAppPaymentSubscriberRecord.Type.DONATION,
|
||||
@@ -113,7 +114,7 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `Given I need to rotate my subscriber id, when I ensureSubscriberId, then I generate and set a new subscriber id`() {
|
||||
val initialSubscriber = createSubscriber()
|
||||
val ref = mockLocalSubscriberAccess(initialSubscriber)
|
||||
val ref = InAppPaymentsTestRule.mockLocalSubscriberAccess(initialSubscriber)
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.ensureSubscriberId(
|
||||
subscriberType = InAppPaymentSubscriberRecord.Type.DONATION,
|
||||
@@ -130,7 +131,7 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `Given no current subscriber, when I rotateSubscriberId, then I do not try to cancel subscription`() {
|
||||
val ref = mockLocalSubscriberAccess()
|
||||
val ref = InAppPaymentsTestRule.mockLocalSubscriberAccess()
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.rotateSubscriberId(InAppPaymentSubscriberRecord.Type.DONATION).test()
|
||||
|
||||
@@ -146,7 +147,7 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `Given current subscriber, when I rotateSubscriberId, then I do not try to cancel subscription`() {
|
||||
val initialSubscriber = createSubscriber()
|
||||
val ref = mockLocalSubscriberAccess(initialSubscriber)
|
||||
val ref = InAppPaymentsTestRule.mockLocalSubscriberAccess(initialSubscriber)
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.rotateSubscriberId(InAppPaymentSubscriberRecord.Type.DONATION).test()
|
||||
|
||||
@@ -162,8 +163,8 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `given no delays, when I setSubscriptionLevel, then I expect happy path`() {
|
||||
val paymentSourceType = PaymentSourceType.Stripe.CreditCard
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
InAppPaymentsTestRule.mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
every { SignalDatabase.inAppPayments.getById(any()) } returns inAppPayment
|
||||
@@ -182,8 +183,8 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `given 10s delay, when I setSubscriptionLevel, then I expect timeout`() {
|
||||
val paymentSourceType = PaymentSourceType.Stripe.CreditCard
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
InAppPaymentsTestRule.mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
every { SignalDatabase.inAppPayments.getById(any()) } returns inAppPayment
|
||||
@@ -206,8 +207,8 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
@Test
|
||||
fun `given long running payment type with 10s delay, when I setSubscriptionLevel, then I expect pending`() {
|
||||
val paymentSourceType = PaymentSourceType.Stripe.SEPADebit
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
InAppPaymentsTestRule.mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
every { SignalDatabase.inAppPayments.getById(any()) } returns inAppPayment
|
||||
@@ -231,8 +232,8 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
fun `given an execution error, when I setSubscriptionLevel, then I expect the same error`() {
|
||||
val expected = NonSuccessfulResponseCodeException(404)
|
||||
val paymentSourceType = PaymentSourceType.Stripe.SEPADebit
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
val inAppPayment = inAppPaymentsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
InAppPaymentsTestRule.mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
every { SignalDatabase.inAppPayments.getById(any()) } returns inAppPayment
|
||||
@@ -261,12 +262,4 @@ class RecurringInAppPaymentRepositoryTest {
|
||||
iapSubscriptionId = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun mockLocalSubscriberAccess(initialSubscriber: InAppPaymentSubscriberRecord? = null): AtomicReference<InAppPaymentSubscriberRecord?> {
|
||||
val ref = AtomicReference(initialSubscriber)
|
||||
every { InAppPaymentsRepository.getSubscriber(any()) } answers { ref.get() }
|
||||
every { InAppPaymentsRepository.setSubscriber(any()) } answers { ref.set(firstArg()) }
|
||||
|
||||
return ref
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user