Add unit tests for InAppPaymentRecurringContextJob.

This commit is contained in:
Alex Hart
2025-01-27 15:52:54 -04:00
committed by Greyson Parrelli
parent 762c7a6d22
commit b5f323d4af
6 changed files with 345 additions and 34 deletions

View File

@@ -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()

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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);