Isolated tests for OneTimeInAppPaymentRepository.
This commit is contained in:
parent
e650223487
commit
c31780050f
8 changed files with 460 additions and 130 deletions
|
@ -7,7 +7,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.InAppPaymentError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
|
@ -16,52 +16,61 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.errors.Do
|
|||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentReceiptRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.InAppPaymentOneTimeContextJob
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.services.DonationsService
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class OneTimeInAppPaymentRepository(private val donationsService: DonationsService) {
|
||||
object OneTimeInAppPaymentRepository {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(OneTimeInAppPaymentRepository::class.java)
|
||||
private val TAG = Log.tag(OneTimeInAppPaymentRepository::class.java)
|
||||
|
||||
fun <T : Any> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId, paymentSourceType: PaymentSourceType): Single<T> {
|
||||
return if (throwable is DonationError) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
val errorSource = if (recipient.isSelf) DonationErrorSource.ONE_TIME else DonationErrorSource.GIFT
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
|
||||
}
|
||||
}
|
||||
|
||||
fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
|
||||
return Completable.fromAction {
|
||||
Log.d(TAG, "Verifying badge recipient $badgeRecipient", true)
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
|
||||
if (recipient.isSelf) {
|
||||
Log.d(TAG, "Cannot send a gift to self.", true)
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
|
||||
if (recipient.isGroup || recipient.isDistributionList || recipient.registered != RecipientTable.RegisteredState.REGISTERED) {
|
||||
Log.w(TAG, "Invalid badge recipient $badgeRecipient. Verification failed.", true)
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
/**
|
||||
* Translates the given Throwable into a DonationError
|
||||
*
|
||||
* If the throwable is already a DonationError, it's returned as is. Otherwise we will return an adequate payment setup error.
|
||||
*/
|
||||
fun <T : Any> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId, paymentSourceType: PaymentSourceType): Single<T> {
|
||||
return if (throwable is DonationError) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
val errorSource = if (recipient.isSelf) DonationErrorSource.ONE_TIME else DonationErrorSource.GIFT
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the recipient for the given ID is allowed to receive a gift. Returns
|
||||
* normally if they are and emits an error otherwise.
|
||||
*/
|
||||
fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
|
||||
return Completable.fromAction {
|
||||
Log.d(TAG, "Verifying badge recipient $badgeRecipient", true)
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
|
||||
if (recipient.isSelf) {
|
||||
Log.d(TAG, "Cannot send a gift to self.", true)
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
|
||||
if (!recipient.isIndividual || recipient.registered != RecipientTable.RegisteredState.REGISTERED) {
|
||||
Log.w(TAG, "Invalid badge recipient $badgeRecipient. Verification failed.", true)
|
||||
throw DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the donations configuration and returns any boost information from it. Also maps and filters out currencies
|
||||
* based on platform and payment method availability.
|
||||
*/
|
||||
fun getBoosts(): Single<Map<Currency, List<Boost>>> {
|
||||
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
return Single.fromCallable { AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap { it.flattenResult() }
|
||||
.map { config ->
|
||||
|
@ -85,7 +94,7 @@ class OneTimeInAppPaymentRepository(private val donationsService: DonationsServi
|
|||
}
|
||||
|
||||
fun getMinimumDonationAmounts(): Single<Map<Currency, FiatMoney>> {
|
||||
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
return Single.fromCallable { AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault()) }
|
||||
.flatMap { it.flattenResult() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map { it.getMinimumDonationAmounts() }
|
||||
|
@ -93,10 +102,9 @@ class OneTimeInAppPaymentRepository(private val donationsService: DonationsServi
|
|||
|
||||
fun waitForOneTimeRedemption(
|
||||
inAppPayment: InAppPaymentTable.InAppPayment,
|
||||
paymentIntentId: String,
|
||||
paymentSourceType: PaymentSourceType
|
||||
paymentIntentId: String
|
||||
): Completable {
|
||||
val isLongRunning = paymentSourceType == PaymentSourceType.Stripe.SEPADebit
|
||||
val isLongRunning = inAppPayment.data.paymentMethodType.toPaymentSourceType() == PaymentSourceType.Stripe.SEPADebit
|
||||
val isBoost = inAppPayment.data.recipientId?.let { RecipientId.from(it) } == Recipient.self().id
|
||||
val donationErrorSource: DonationErrorSource = if (isBoost) DonationErrorSource.ONE_TIME else DonationErrorSource.GIFT
|
||||
|
||||
|
@ -107,17 +115,7 @@ class OneTimeInAppPaymentRepository(private val donationsService: DonationsServi
|
|||
}
|
||||
|
||||
return Single.fromCallable {
|
||||
val inAppPaymentReceiptRecord = if (isBoost) {
|
||||
InAppPaymentReceiptRecord.createForBoost(inAppPayment.data.amount!!.toFiatMoney())
|
||||
} else {
|
||||
InAppPaymentReceiptRecord.createForGift(inAppPayment.data.amount!!.toFiatMoney())
|
||||
}
|
||||
|
||||
val donationTypeLabel = inAppPaymentReceiptRecord.type.code.replaceFirstChar { c -> if (c.isLowerCase()) c.titlecase(Locale.US) else c.toString() }
|
||||
|
||||
Log.d(TAG, "Confirmed payment intent. Recording $donationTypeLabel receipt and submitting badge reimbursement job chain.", true)
|
||||
SignalDatabase.donationReceipts.addReceipt(inAppPaymentReceiptRecord)
|
||||
|
||||
Log.d(TAG, "Confirmed payment intent. Submitting badge reimbursement job chain.", true)
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment = inAppPayment.copy(
|
||||
data = inAppPayment.data.copy(
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
|||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.database.model.isExpired
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.subscription.LevelUpdate
|
||||
|
@ -51,8 +50,7 @@ import java.util.Optional
|
|||
* only in charge of rendering our "current view of the world."
|
||||
*/
|
||||
class DonateToSignalViewModel(
|
||||
startType: InAppPaymentType,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository
|
||||
startType: InAppPaymentType
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
|
@ -73,7 +71,7 @@ class DonateToSignalViewModel(
|
|||
val inAppPaymentId: Flowable<InAppPaymentTable.InAppPaymentId> = _inAppPaymentId.onBackpressureLatest().distinctUntilChanged()
|
||||
|
||||
init {
|
||||
initializeOneTimeDonationState(oneTimeInAppPaymentRepository)
|
||||
initializeOneTimeDonationState(OneTimeInAppPaymentRepository)
|
||||
initializeMonthlyDonationState(RecurringInAppPaymentRepository)
|
||||
|
||||
networkDisposable += InternetConnectionObserver
|
||||
|
@ -97,7 +95,7 @@ class DonateToSignalViewModel(
|
|||
fun retryOneTimeDonationState() {
|
||||
if (!oneTimeDonationDisposables.isDisposed && store.state.oneTimeDonationState.donationStage == DonateToSignalState.DonationStage.FAILURE) {
|
||||
store.update { it.copy(oneTimeDonationState = it.oneTimeDonationState.copy(donationStage = DonateToSignalState.DonationStage.INIT)) }
|
||||
initializeOneTimeDonationState(oneTimeInAppPaymentRepository)
|
||||
initializeOneTimeDonationState(OneTimeInAppPaymentRepository)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -428,11 +426,10 @@ class DonateToSignalViewModel(
|
|||
}
|
||||
|
||||
class Factory(
|
||||
private val startType: InAppPaymentType,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService)
|
||||
private val startType: InAppPaymentType
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(DonateToSignalViewModel(startType, oneTimeInAppPaymentRepository)) as T
|
||||
return modelClass.cast(DonateToSignalViewModel(startType)) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.signal.donations.PaymentSourceType
|
|||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.PayPalRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
|
@ -35,8 +36,7 @@ import org.whispersystems.signalservice.api.subscriptions.PayPalCreatePaymentMet
|
|||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
|
||||
class PayPalPaymentInProgressViewModel(
|
||||
private val payPalRepository: PayPalRepository,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository
|
||||
private val payPalRepository: PayPalRepository
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
|
@ -122,6 +122,8 @@ class PayPalPaymentInProgressViewModel(
|
|||
inAppPayment: InAppPaymentTable.InAppPayment,
|
||||
routeToPaypalConfirmation: (PayPalCreatePaymentIntentResponse) -> Single<PayPalConfirmationResult>
|
||||
) {
|
||||
check(inAppPayment.data.paymentMethodType.toPaymentSourceType() == PaymentSourceType.PayPal)
|
||||
|
||||
Log.d(TAG, "Proceeding with one-time payment pipeline...", true)
|
||||
store.update { InAppPaymentProcessorStage.PAYMENT_PIPELINE }
|
||||
val verifyUser = if (inAppPayment.type == InAppPaymentType.ONE_TIME_GIFT) {
|
||||
|
@ -148,10 +150,9 @@ class PayPalPaymentInProgressViewModel(
|
|||
)
|
||||
}
|
||||
.flatMapCompletable { response ->
|
||||
oneTimeInAppPaymentRepository.waitForOneTimeRedemption(
|
||||
OneTimeInAppPaymentRepository.waitForOneTimeRedemption(
|
||||
inAppPayment = inAppPayment,
|
||||
paymentIntentId = response.paymentId,
|
||||
paymentSourceType = PaymentSourceType.PayPal
|
||||
paymentIntentId = response.paymentId
|
||||
)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
@ -193,11 +194,10 @@ class PayPalPaymentInProgressViewModel(
|
|||
}
|
||||
|
||||
class Factory(
|
||||
private val payPalRepository: PayPalRepository = PayPalRepository(AppDependencies.donationsService),
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService)
|
||||
private val payPalRepository: PayPalRepository = PayPalRepository(AppDependencies.donationsService)
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(PayPalPaymentInProgressViewModel(payPalRepository, oneTimeInAppPaymentRepository)) as T
|
||||
return modelClass.cast(PayPalPaymentInProgressViewModel(payPalRepository)) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.errors.to
|
|||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
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.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
@ -40,8 +39,7 @@ import org.whispersystems.signalservice.api.util.Preconditions
|
|||
import org.whispersystems.signalservice.internal.push.exceptions.InAppPaymentProcessorError
|
||||
|
||||
class StripePaymentInProgressViewModel(
|
||||
private val stripeRepository: StripeRepository,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository
|
||||
private val stripeRepository: StripeRepository
|
||||
) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
|
@ -200,6 +198,8 @@ class StripePaymentInProgressViewModel(
|
|||
paymentSourceProvider: PaymentSourceProvider,
|
||||
nextActionHandler: StripeNextActionHandler
|
||||
) {
|
||||
check(inAppPayment.data.paymentMethodType.toPaymentSourceType() == paymentSourceProvider.paymentSourceType)
|
||||
|
||||
Log.w(TAG, "Beginning one-time payment pipeline...", true)
|
||||
|
||||
val amount = inAppPayment.data.amount!!.toFiatMoney()
|
||||
|
@ -233,10 +233,9 @@ class StripePaymentInProgressViewModel(
|
|||
.flatMap { stripeRepository.getStatusAndPaymentMethodId(it, action.paymentMethodId) }
|
||||
}
|
||||
.flatMapCompletable {
|
||||
oneTimeInAppPaymentRepository.waitForOneTimeRedemption(
|
||||
OneTimeInAppPaymentRepository.waitForOneTimeRedemption(
|
||||
inAppPayment = inAppPayment,
|
||||
paymentIntentId = paymentIntent.intentId,
|
||||
paymentSourceType = paymentSource.type
|
||||
paymentIntentId = paymentIntent.intentId
|
||||
)
|
||||
}
|
||||
}.subscribeBy(
|
||||
|
@ -304,11 +303,10 @@ class StripePaymentInProgressViewModel(
|
|||
}
|
||||
|
||||
class Factory(
|
||||
private val stripeRepository: StripeRepository,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService)
|
||||
private val stripeRepository: StripeRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(StripePaymentInProgressViewModel(stripeRepository, oneTimeInAppPaymentRepository)) as T
|
||||
return modelClass.cast(StripePaymentInProgressViewModel(stripeRepository)) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ import org.signal.donations.InAppPaymentType
|
|||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredential
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequestContext
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toDonationProcessor
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentReceiptRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
|
@ -138,7 +140,15 @@ class InAppPaymentOneTimeContextJob private constructor(
|
|||
throw InAppPaymentRetryException(e)
|
||||
}
|
||||
|
||||
info("Got presentation. Updating state and completing.")
|
||||
info("Got presentation. Updating state, recording receipt, and completing.")
|
||||
val inAppPaymentReceiptRecord = if (inAppPayment.type == InAppPaymentType.ONE_TIME_DONATION) {
|
||||
InAppPaymentReceiptRecord.createForBoost(inAppPayment.data.amount!!.toFiatMoney())
|
||||
} else {
|
||||
InAppPaymentReceiptRecord.createForGift(inAppPayment.data.amount!!.toFiatMoney())
|
||||
}
|
||||
|
||||
SignalDatabase.donationReceipts.addReceipt(inAppPaymentReceiptRecord)
|
||||
|
||||
SignalDatabase.inAppPayments.update(
|
||||
inAppPayment.copy(
|
||||
data = inAppPayment.data.copy(
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkObject
|
||||
import io.mockk.unmockkStatic
|
||||
import org.junit.rules.ExternalResource
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatValue
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentMethodType
|
||||
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.util.RemoteConfig
|
||||
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 kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Common setup between different tests that rely on donations infrastructure.
|
||||
*/
|
||||
class DonationsTestRule : ExternalResource() {
|
||||
|
||||
private val configuration: SubscriptionsConfiguration by lazy {
|
||||
val testConfigJsonData = javaClass.classLoader!!.getResourceAsStream("donations_configuration_test_data.json").bufferedReader().readText()
|
||||
|
||||
JsonUtil.fromJson(testConfigJsonData, SubscriptionsConfiguration::class.java)
|
||||
}
|
||||
|
||||
override fun before() {
|
||||
mockkStatic(RemoteConfig::class)
|
||||
every { RemoteConfig.init() } just runs
|
||||
|
||||
mockkStatic(InAppPaymentsRepository::class)
|
||||
mockkObject(InAppPaymentsRepository)
|
||||
every { InAppPaymentsRepository.scheduleSyncForAccountRecordChange() } returns Unit
|
||||
|
||||
mockkObject(InAppDonations)
|
||||
every { InAppDonations.isPayPalAvailable() } returns true
|
||||
every { InAppDonations.isGooglePayAvailable() } returns true
|
||||
every { InAppDonations.isSEPADebitAvailable() } returns true
|
||||
every { InAppDonations.isCreditCardAvailable() } returns true
|
||||
every { InAppDonations.isIDEALAvailable() } returns true
|
||||
|
||||
mockkObject(SignalDatabase.Companion)
|
||||
every { SignalDatabase.Companion.inAppPayments } returns mockk {
|
||||
every { SignalDatabase.Companion.inAppPayments.update(any()) } returns Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun after() {
|
||||
unmockkStatic(RemoteConfig::class, InAppPaymentsRepository::class)
|
||||
unmockkObject(InAppDonations, SignalDatabase.Companion)
|
||||
}
|
||||
|
||||
/**
|
||||
* Because this initialisation requires reading from disk, we only want to do it in the exact tests that actually need it.
|
||||
*/
|
||||
fun initializeDonationsConfigurationMock() {
|
||||
every { AppDependencies.donationsService.getDonationsConfiguration(any()) } returns ServiceResponse(200, "", configuration, null, null)
|
||||
}
|
||||
|
||||
fun createInAppPayment(
|
||||
type: InAppPaymentType,
|
||||
paymentSourceType: PaymentSourceType
|
||||
): InAppPaymentTable.InAppPayment {
|
||||
return InAppPaymentTable.InAppPayment(
|
||||
id = InAppPaymentTable.InAppPaymentId(1),
|
||||
state = InAppPaymentTable.State.CREATED,
|
||||
insertedAt = System.currentTimeMillis().milliseconds,
|
||||
updatedAt = System.currentTimeMillis().milliseconds,
|
||||
notified = true,
|
||||
subscriberId = null,
|
||||
endOfPeriod = 0.milliseconds,
|
||||
type = type,
|
||||
data = InAppPaymentData(
|
||||
badge = null,
|
||||
level = 500,
|
||||
paymentMethodType = paymentSourceType.toPaymentMethodType(),
|
||||
amount = FiatMoney(BigDecimal.ONE, Currency.getInstance("USD")).toFiatValue()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import android.app.Application
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
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.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.DistributionListId
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
|
||||
import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule
|
||||
import org.thoughtcrime.securesms.testutil.RxPluginsRule
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class OneTimeInAppPaymentRepositoryTest {
|
||||
|
||||
@get:Rule
|
||||
val rxRule = RxPluginsRule()
|
||||
|
||||
@get:Rule
|
||||
val appDependencies = MockAppDependenciesRule()
|
||||
|
||||
@get:Rule
|
||||
val donationsTestRule = DonationsTestRule()
|
||||
|
||||
@Test
|
||||
fun `Given a throwable and self, when I handleCreatePaymentIntentError, then I expect a ONE_TIME error`() {
|
||||
val throwable = Exception()
|
||||
val selfId = RecipientId.from(1)
|
||||
val self = Recipient(
|
||||
id = selfId,
|
||||
isSelf = true
|
||||
)
|
||||
|
||||
mockkStatic(Recipient::class)
|
||||
every { Recipient.resolved(selfId) } returns self
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.handleCreatePaymentIntentError<Unit>(throwable, selfId, PaymentSourceType.Stripe.CreditCard).test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver.assertError {
|
||||
it is DonationError && it.source == DonationErrorSource.ONE_TIME
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a throwable and not self, when I handleCreatePaymentIntentError, then I expect a GIFT error`() {
|
||||
val throwable = Exception()
|
||||
val otherId = RecipientId.from(1)
|
||||
val other = Recipient(
|
||||
id = otherId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED
|
||||
)
|
||||
|
||||
mockkStatic(Recipient::class)
|
||||
every { Recipient.resolved(otherId) } returns other
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.handleCreatePaymentIntentError<Unit>(throwable, otherId, PaymentSourceType.Stripe.CreditCard).test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver.assertError {
|
||||
it is DonationError && it.source == DonationErrorSource.GIFT
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a registered non-self individual, when I verifyRecipientIsAllowedToReceiveAGift, then I expect completion`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED
|
||||
)
|
||||
|
||||
mockkStatic(Recipient::class)
|
||||
every { Recipient.resolved(recipientId) } returns recipient
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.verifyRecipientIsAllowedToReceiveAGift(recipientId).test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver.assertComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given self, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = true
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an unregistered individual, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.NOT_REGISTERED
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a group, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED,
|
||||
groupIdValue = mockk()
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a call link, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED,
|
||||
callLinkRoomId = CallLinkRoomId.fromBytes(byteArrayOf())
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a distribution list, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED,
|
||||
distributionListIdValue = DistributionListId.from(1L)
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given release notes, when I verifyRecipientIsAllowedToReceiveAGift, then I expect SelectedRecipientIsInvalid`() {
|
||||
val recipientId = RecipientId.from(1L)
|
||||
val recipient = Recipient(
|
||||
id = recipientId,
|
||||
isSelf = false,
|
||||
registeredValue = RecipientTable.RegisteredState.REGISTERED,
|
||||
isReleaseNotes = true
|
||||
)
|
||||
|
||||
verifyRecipientIsNotAllowedToBeGiftedBadges(recipient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getBoosts, then I expect a filtered set of boost objects`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getBoosts().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertValue {
|
||||
it.size == 3
|
||||
}
|
||||
.assertComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getBoostBadge, then I expect a boost badge`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getBoostBadge().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertValue { it.isBoost() }
|
||||
.assertComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getMinimumDonationAmounts, then I expect a map of 3 currencies`() {
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.getMinimumDonationAmounts().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertValue { it.size == 3 }
|
||||
.assertComplete()
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(inAppPayment.id) } returns inAppPayment
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository
|
||||
.waitForOneTimeRedemption(inAppPayment, "test-intent-id")
|
||||
.test()
|
||||
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
rxRule.defaultScheduler.advanceTimeBy(10, TimeUnit.SECONDS)
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertError { it is DonationError.BadgeRedemptionError.DonationPending }
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments.getById(inAppPayment.id) } returns inAppPayment
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository
|
||||
.waitForOneTimeRedemption(inAppPayment, "test-intent-id")
|
||||
.test()
|
||||
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
rxRule.defaultScheduler.advanceTimeBy(10, TimeUnit.SECONDS)
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertError { it is DonationError.BadgeRedemptionError.TimeoutWaitingForTokenError }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given no delays, when I waitForOneTimeRedemption, then I expect happy path`() {
|
||||
val inAppPayment = donationsTestRule.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
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository
|
||||
.waitForOneTimeRedemption(inAppPayment, "test-intent-id")
|
||||
.test()
|
||||
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver
|
||||
.assertComplete()
|
||||
}
|
||||
|
||||
private fun verifyRecipientIsNotAllowedToBeGiftedBadges(recipient: Recipient) {
|
||||
mockkStatic(Recipient::class)
|
||||
every { Recipient.resolved(recipient.id) } returns recipient
|
||||
|
||||
val testObserver = OneTimeInAppPaymentRepository.verifyRecipientIsAllowedToReceiveAGift(recipient.id).test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
||||
testObserver.assertError {
|
||||
it is DonationError.GiftRecipientVerificationError.SelectedRecipientIsInvalid
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,9 @@ package org.thoughtcrime.securesms.components.settings.app.subscription
|
|||
import android.app.Application
|
||||
import androidx.lifecycle.AtomicReference
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
|
@ -21,7 +19,6 @@ import org.robolectric.annotation.Config
|
|||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.assertIsNot
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentMethodType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
|
@ -35,50 +32,29 @@ import org.thoughtcrime.securesms.subscription.LevelUpdate
|
|||
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
|
||||
import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule
|
||||
import org.thoughtcrime.securesms.testutil.RxPluginsRule
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||
import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class RecurringInAppPaymentRepositoryTest {
|
||||
|
||||
private val testConfigData: SubscriptionsConfiguration by lazy {
|
||||
val testConfigJsonData = javaClass.classLoader!!.getResourceAsStream("donations_configuration_test_data.json").bufferedReader().readText()
|
||||
|
||||
JsonUtil.fromJson(testConfigJsonData, SubscriptionsConfiguration::class.java)
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val rxRule = RxPluginsRule()
|
||||
|
||||
@get:Rule
|
||||
val appDependencies = MockAppDependenciesRule()
|
||||
|
||||
@get:Rule
|
||||
val donationsTestRule = DonationsTestRule()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic(RemoteConfig::class)
|
||||
every { RemoteConfig.init() } just runs
|
||||
|
||||
mockkObject(InAppDonations)
|
||||
every { InAppDonations.isPayPalAvailable() } returns true
|
||||
every { InAppDonations.isGooglePayAvailable() } returns true
|
||||
every { InAppDonations.isSEPADebitAvailable() } returns true
|
||||
every { InAppDonations.isCreditCardAvailable() } returns true
|
||||
every { InAppDonations.isIDEALAvailable() } returns true
|
||||
|
||||
mockkStatic(InAppPaymentsRepository::class)
|
||||
mockkObject(InAppPaymentsRepository)
|
||||
every { InAppPaymentsRepository.scheduleSyncForAccountRecordChange() } returns Unit
|
||||
|
||||
mockkObject(SignalStore.Companion)
|
||||
every { SignalStore.Companion.inAppPayments } returns mockk {
|
||||
every { SignalStore.Companion.inAppPayments.getRecurringDonationCurrency() } returns Currency.getInstance("USD")
|
||||
|
@ -86,15 +62,10 @@ class RecurringInAppPaymentRepositoryTest {
|
|||
every { SignalStore.Companion.inAppPayments.updateLocalStateForLocalSubscribe(any()) } returns Unit
|
||||
}
|
||||
|
||||
mockkObject(SignalDatabase.Companion)
|
||||
every { SignalDatabase.Companion.recipients } returns mockk {
|
||||
every { SignalDatabase.Companion.recipients.markNeedsSync(any<RecipientId>()) } returns Unit
|
||||
}
|
||||
|
||||
every { SignalDatabase.Companion.inAppPayments } returns mockk {
|
||||
every { SignalDatabase.Companion.inAppPayments.update(any()) } returns Unit
|
||||
}
|
||||
|
||||
mockkStatic(StorageSyncHelper::class)
|
||||
every { StorageSyncHelper.scheduleSyncForDataChange() } returns Unit
|
||||
|
||||
|
@ -109,7 +80,7 @@ class RecurringInAppPaymentRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `when I getDonationsConfiguration then I expect a set of three Subscription objects`() {
|
||||
every { AppDependencies.donationsService.getDonationsConfiguration(any()) } returns ServiceResponse(200, "", testConfigData, null, null)
|
||||
donationsTestRule.initializeDonationsConfigurationMock()
|
||||
|
||||
val testObserver = RecurringInAppPaymentRepository.getSubscriptions().test()
|
||||
rxRule.defaultScheduler.triggerActions()
|
||||
|
@ -190,7 +161,7 @@ class RecurringInAppPaymentRepositoryTest {
|
|||
@Test
|
||||
fun `given no delays, when I setSubscriptionLevel, then I expect happy path`() {
|
||||
val paymentSourceType = PaymentSourceType.Stripe.CreditCard
|
||||
val inAppPayment = createInAppPayment(paymentSourceType)
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
|
@ -210,7 +181,7 @@ class RecurringInAppPaymentRepositoryTest {
|
|||
@Test
|
||||
fun `given 10s delay, when I setSubscriptionLevel, then I expect timeout`() {
|
||||
val paymentSourceType = PaymentSourceType.Stripe.CreditCard
|
||||
val inAppPayment = createInAppPayment(paymentSourceType)
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
|
@ -234,7 +205,7 @@ 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 = createInAppPayment(paymentSourceType)
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
|
@ -259,7 +230,7 @@ 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 = createInAppPayment(paymentSourceType)
|
||||
val inAppPayment = donationsTestRule.createInAppPayment(InAppPaymentType.RECURRING_DONATION, paymentSourceType)
|
||||
mockLocalSubscriberAccess(createSubscriber())
|
||||
|
||||
every { SignalStore.inAppPayments.getLevelOperation("500") } returns LevelUpdateOperation(IdempotencyKey.generate(), "500")
|
||||
|
@ -290,26 +261,6 @@ class RecurringInAppPaymentRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createInAppPayment(
|
||||
paymentSourceType: PaymentSourceType
|
||||
): InAppPaymentTable.InAppPayment {
|
||||
return InAppPaymentTable.InAppPayment(
|
||||
id = InAppPaymentTable.InAppPaymentId(1),
|
||||
state = InAppPaymentTable.State.CREATED,
|
||||
insertedAt = System.currentTimeMillis().milliseconds,
|
||||
updatedAt = System.currentTimeMillis().milliseconds,
|
||||
notified = true,
|
||||
subscriberId = null,
|
||||
endOfPeriod = 0.milliseconds,
|
||||
type = InAppPaymentType.RECURRING_DONATION,
|
||||
data = InAppPaymentData(
|
||||
badge = null,
|
||||
level = 500,
|
||||
paymentMethodType = paymentSourceType.toPaymentMethodType()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun mockLocalSubscriberAccess(initialSubscriber: InAppPaymentSubscriberRecord? = null): AtomicReference<InAppPaymentSubscriberRecord?> {
|
||||
val ref = AtomicReference(initialSubscriber)
|
||||
every { InAppPaymentsRepository.getSubscriber(any()) } answers { ref.get() }
|
||||
|
|
Loading…
Add table
Reference in a new issue