mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add unit tests for InAppPaymentRecurringContextJob.
This commit is contained in:
committed by
Greyson Parrelli
parent
762c7a6d22
commit
b5f323d4af
@@ -50,7 +50,7 @@ class InAppPaymentRecurringContextJob private constructor(
|
||||
|
||||
const val KEY = "InAppPurchaseRecurringContextJob"
|
||||
|
||||
fun create(inAppPayment: InAppPaymentTable.InAppPayment): Job {
|
||||
fun create(inAppPayment: InAppPaymentTable.InAppPayment): InAppPaymentRecurringContextJob {
|
||||
return InAppPaymentRecurringContextJob(
|
||||
inAppPaymentId = inAppPayment.id,
|
||||
parameters = Parameters.Builder()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.app.Application
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isNotNull
|
||||
import assertk.assertions.isTrue
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsTestRule
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule
|
||||
import org.thoughtcrime.securesms.testutil.SystemOutLogger
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class InAppPaymentRecurringContextJobTest {
|
||||
|
||||
@get:Rule
|
||||
val appDependencies = MockAppDependenciesRule()
|
||||
|
||||
@get:Rule
|
||||
val inAppPaymentsTestRule = InAppPaymentsTestRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
Log.initialize(SystemOutLogger())
|
||||
|
||||
mockkObject(InAppPaymentsRepository)
|
||||
every { InAppPaymentsRepository.generateRequestCredential() } returns mockk {
|
||||
every { serialize() } returns byteArrayOf()
|
||||
every { request } returns mockk()
|
||||
}
|
||||
|
||||
inAppPaymentsTestRule.initializeSubmitReceiptCredentialRequestSync()
|
||||
|
||||
val minimumTime = System.currentTimeMillis().milliseconds + 60.days
|
||||
val minimumTimeS = minimumTime.inWholeSeconds
|
||||
val offset = minimumTimeS % 86400L
|
||||
val actualMinimumTime = minimumTimeS - offset
|
||||
|
||||
every { AppDependencies.clientZkReceiptOperations.receiveReceiptCredential(any(), any()) } returns mockk() {
|
||||
every { receiptLevel } returns 2000
|
||||
every { receiptExpirationTime } returns actualMinimumTime
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a CREATED IAP, when I onAdded, then I expect PENDING`() {
|
||||
val iap = insertInAppPayment()
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
job.onAdded()
|
||||
val updatedIap = SignalDatabase.inAppPayments.getById(iap.id)
|
||||
|
||||
assertThat(updatedIap?.state).isEqualTo(InAppPaymentTable.State.PENDING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an IAP without an error, when I onFailure, then I set error and move to end state`() {
|
||||
val iap = insertInAppPayment()
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
job.onFailure()
|
||||
val updatedIap = SignalDatabase.inAppPayments.getById(iap.id)
|
||||
|
||||
assertThat(updatedIap?.state).isEqualTo(InAppPaymentTable.State.END)
|
||||
assertThat(updatedIap?.data?.error).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a SEPA IAP, when I getNextRunAttemptBackoff, then I expect one day`() {
|
||||
val iap = insertInAppPayment(paymentSourceType = PaymentSourceType.Stripe.SEPADebit)
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.getNextRunAttemptBackoff(1, Exception())
|
||||
|
||||
assertThat(result).isEqualTo(1.days.inWholeMilliseconds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an iDEAL IAP, when I getNextRunAttemptBackoff, then I expect one day`() {
|
||||
val iap = insertInAppPayment(paymentSourceType = PaymentSourceType.Stripe.IDEAL)
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.getNextRunAttemptBackoff(1, Exception())
|
||||
|
||||
assertThat(result).isEqualTo(1.days.inWholeMilliseconds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Test happy path for subscription redemption`() {
|
||||
val activeSubscription = inAppPaymentsTestRule.createActiveSubscription()
|
||||
inAppPaymentsTestRule.initializeActiveSubscriptionMock(activeSubscription)
|
||||
|
||||
val iap = insertInAppPayment(paymentSourceType = PaymentSourceType.Stripe.IDEAL)
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
job.onAdded()
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isSuccess).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given END state, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
state = InAppPaymentTable.State.END
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given non-recurring IAP, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
type = InAppPaymentType.ONE_TIME_GIFT
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no subscriber id, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
subscriberId = null
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no redemption data, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
redemptionState = null
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given redemption started, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
redemptionState = InAppPaymentData.RedemptionState(
|
||||
stage = InAppPaymentData.RedemptionState.Stage.REDEMPTION_STARTED
|
||||
)
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given redeemed, when I run, then I expect failure`() {
|
||||
val iap = insertInAppPayment(
|
||||
redemptionState = InAppPaymentData.RedemptionState(
|
||||
stage = InAppPaymentData.RedemptionState.Stage.REDEEMED
|
||||
)
|
||||
)
|
||||
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no available subscription, when I run, then I expect retry`() {
|
||||
val iap = insertInAppPayment()
|
||||
val job = InAppPaymentRecurringContextJob.create(iap)
|
||||
inAppPaymentsTestRule.initializeActiveSubscriptionMock(ActiveSubscription(null, null))
|
||||
|
||||
val result = job.run()
|
||||
assertThat(result.isRetry).isTrue()
|
||||
}
|
||||
|
||||
private fun insertInAppPayment(
|
||||
type: InAppPaymentType = InAppPaymentType.RECURRING_DONATION,
|
||||
state: InAppPaymentTable.State = InAppPaymentTable.State.CREATED,
|
||||
subscriberId: SubscriberId? = SubscriberId.generate(),
|
||||
paymentSourceType: PaymentSourceType = PaymentSourceType.Stripe.CreditCard,
|
||||
redemptionState: InAppPaymentData.RedemptionState? = InAppPaymentData.RedemptionState(
|
||||
stage = InAppPaymentData.RedemptionState.Stage.INIT
|
||||
)
|
||||
): InAppPaymentTable.InAppPayment {
|
||||
val iap = inAppPaymentsTestRule.createInAppPayment(type, paymentSourceType)
|
||||
SignalDatabase.inAppPayments.insert(
|
||||
type = iap.type,
|
||||
state = state,
|
||||
subscriberId = subscriberId,
|
||||
endOfPeriod = null,
|
||||
inAppPaymentData = iap.data.copy(
|
||||
redemption = redemptionState
|
||||
)
|
||||
)
|
||||
|
||||
return iap
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public final class SystemOutLogger extends Log.Logger {
|
||||
|
||||
private String format(char level, String tag, String message, Throwable t) {
|
||||
if (t != null) {
|
||||
t.printStackTrace();
|
||||
return String.format("%c[%s] %s %s:%s", level, tag, message, t.getClass().getSimpleName(), t.getMessage());
|
||||
} else {
|
||||
return String.format("%c[%s] %s", level, tag, message);
|
||||
|
||||
Reference in New Issue
Block a user