Fix BackupSubscriptionCheckJob tests.

This commit is contained in:
Alex Hart
2025-10-03 15:43:25 -03:00
committed by Michelle Tang
parent 1d403d3dee
commit 14cc0f12a6
9 changed files with 194 additions and 100 deletions

View File

@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.SignalServiceDataStore
import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.account.AccountApi
import org.whispersystems.signalservice.api.archive.ArchiveApi
import org.whispersystems.signalservice.api.attachment.AttachmentApi
import org.whispersystems.signalservice.api.donations.DonationsApi
@@ -56,6 +57,7 @@ class InstrumentationApplicationDependencyProvider(val application: Application,
private val recipientCache: LiveRecipientCache
private var signalServiceMessageSender: SignalServiceMessageSender? = null
private var billingApi: BillingApi = mockk()
private var accountApi: AccountApi = mockk()
init {
runSync {
@@ -118,6 +120,8 @@ class InstrumentationApplicationDependencyProvider(val application: Application,
override fun provideBillingApi(): BillingApi = billingApi
override fun provideAccountApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): AccountApi = accountApi
override fun provideSignalServiceNetworkAccess(): SignalServiceNetworkAccess {
return serviceNetworkAccessMock
}

View File

@@ -12,10 +12,10 @@ import assertk.assertions.isTrue
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import okio.IOException
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -30,6 +30,7 @@ import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.backup.DeletionState
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.components.settings.app.backups.BackupStateObserver
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.database.SignalDatabase
@@ -38,6 +39,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.NetworkResult
@@ -47,6 +49,7 @@ import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.ChargeFailure
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import java.math.BigDecimal
import java.util.Currency
import kotlin.time.Duration.Companion.days
@@ -68,13 +71,21 @@ class BackupSubscriptionCheckJobTest {
every { RemoteConfig.internalUser } returns true
coEvery { AppDependencies.billingApi.getApiAvailability() } returns BillingResponseCode.OK
coEvery { AppDependencies.billingApi.queryPurchases() } returns mockk()
coEvery { AppDependencies.billingApi.queryProduct() } returns null
coEvery { AppDependencies.billingApi.queryPurchases() } returns BillingPurchaseResult.Success(
purchaseState = BillingPurchaseState.PURCHASED,
purchaseToken = "test-token",
isAcknowledged = true,
isAutoRenewing = true,
purchaseTime = System.currentTimeMillis()
)
coEvery { AppDependencies.billingApi.queryProduct() } returns BillingProduct(price = FiatMoney(BigDecimal.ONE, Currency.getInstance("USD")))
SignalStore.backup.backupTier = MessageBackupTier.PAID
mockkObject(RecurringInAppPaymentRepository)
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription()
)
@@ -97,6 +108,18 @@ class BackupSubscriptionCheckJobTest {
}
}
every { BackupRepository.resetInitializedStateAndAuthCredentials() } returns Unit
mockkObject(BackupStateObserver)
every { BackupStateObserver.notifyBackupStateChanged() } returns Unit
mockkObject(SignalNetwork)
every { AppDependencies.accountApi.whoAmI() } returns NetworkResult.Success(
WhoAmIResponse(
number = "+1234567890"
)
)
every { AppDependencies.donationsApi.putSubscription(any()) } returns NetworkResult.Success(Unit)
insertSubscriber()
@@ -173,9 +196,18 @@ class BackupSubscriptionCheckJobTest {
assertEarlyExit(result)
}
@Test
fun givenPrePendingRecurringTransaction_whenIRun_thenIExpectSuccessAndEarlyExit() {
insertPrePendingInAppPayment()
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
assertEarlyExit(result)
}
@Test
fun givenAPendingPayment_whenIRun_thenIExpectSuccessAndEarlyExit() {
mockProduct()
insertPendingInAppPayment()
val job = BackupSubscriptionCheckJob.create()
@@ -187,9 +219,7 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenInactiveSubscription_whenIRun_thenIExpectStateMismatchDetected() {
mockProduct()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = false)
)
@@ -201,9 +231,9 @@ class BackupSubscriptionCheckJobTest {
}
@Test
fun givenRepositoryFailure_whenIRun_thenIExpectFailureResult() {
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.failure(
RuntimeException("Network error")
fun givenAnApplicationErrorWhenAccessingTheActiveSubscription_whenIRun_thenIExpectAFailure() {
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.ApplicationError(
RuntimeException("Application Error")
)
val job = BackupSubscriptionCheckJob.create()
@@ -213,8 +243,10 @@ class BackupSubscriptionCheckJobTest {
}
@Test
fun givenBillingApiReturnsAFailure_whenIRun_thenIExpectFailureResult() {
coEvery { AppDependencies.billingApi.queryPurchases() } returns BillingPurchaseResult.BillingUnavailable
fun givenANetworkErrorWhenAccessingTheActiveSubscription_whenIRun_thenIExpectAFailure() {
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.NetworkError(
IOException()
)
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
@@ -223,10 +255,32 @@ class BackupSubscriptionCheckJobTest {
}
@Test
fun givenPastDueSubscription_whenIRun_thenIExpectStateMismatchDetected() {
mockProduct()
fun givenAStatusCodeErrorWhenAccessingTheActiveSubscription_whenIRun_thenIExpectAMismatch() {
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.StatusCodeError(
NonSuccessfulResponseCodeException(404)
)
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
assertThat(result.isSuccess).isTrue()
assertThat(SignalStore.backup.subscriptionStateMismatchDetected).isTrue()
}
@Test
fun givenBillingApiReturnsAFailure_whenIRun_thenIExpectSuccessAndEarlyExit() {
coEvery { AppDependencies.billingApi.queryPurchases() } returns BillingPurchaseResult.BillingUnavailable
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
assertThat(result.isSuccess).isTrue()
assertThat(SignalStore.backup.subscriptionStateMismatchDetected).isFalse()
}
@Test
fun givenPastDueSubscription_whenIRun_thenIExpectStateMismatchDetected() {
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(
isActive = false,
billingPeriodEndSeconds = System.currentTimeMillis().milliseconds.inWholeSeconds - 1.days.inWholeSeconds,
@@ -243,9 +297,7 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenCancelledSubscription_whenIRun_thenIExpectStateMismatchDetected() {
mockProduct()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(
isActive = false,
status = "canceled",
@@ -262,9 +314,7 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenFreeBackupTier_whenIRun_thenIExpectSuccessAndEarlyExit() {
mockProduct()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
ActiveSubscription.EMPTY
)
@@ -277,25 +327,12 @@ class BackupSubscriptionCheckJobTest {
assertThat(SignalStore.backup.subscriptionStateMismatchDetected).isFalse()
}
@Test
fun givenFailedInAppPayment_whenIRun_thenIExpectStateMismatchDetected() {
mockProduct()
insertFailedInAppPayment()
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
assertThat(result.isSuccess).isTrue()
assertThat(SignalStore.backup.subscriptionStateMismatchDetected).isTrue()
}
@Test
fun givenActiveSignalSubscriptionWithTokenMismatch_whenIRun_thenIExpectTokenRedemption() {
mockProduct()
mockActivePurchase()
insertSubscriber("mismatch")
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -315,10 +352,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenActiveSubscriptionAndPurchaseWithoutEntitlement_whenIRun_thenIExpectRedemption() {
mockProduct()
mockActivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -342,10 +378,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenValidActiveState_whenIRun_thenIExpectSuccessAndNoMismatch() {
mockProduct()
mockActivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -360,10 +395,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenValidInactiveState_whenIRun_thenIExpectSuccessAndNoMismatch() {
mockProduct()
mockInactivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = false)
)
@@ -379,10 +413,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenGooglePlayBillingCanceledWithoutActiveSignalSubscription_whenIRun_thenIExpectValidCancelState() {
mockProduct()
mockCanceledPurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = false)
)
@@ -395,10 +428,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenGooglePlayBillingCanceledWithFailedSignalSubscription_whenIRun_thenIExpectValidCancelState() {
mockProduct()
mockCanceledPurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true, status = "past_due", chargeFailure = ChargeFailure("test", "", "", "", ""))
)
@@ -411,11 +443,10 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenInvalidStateConfiguration_whenIRun_thenIExpectStateMismatchDetected() {
mockProduct()
mockActivePurchase()
// Create invalid state: active purchase but no active subscription, with paid tier
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = false)
)
@@ -430,10 +461,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenActiveSubscriptionWithMismatchedZkCredentials_whenIRun_thenIExpectCredentialRefresh() {
mockProduct()
mockActivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -452,10 +482,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenActiveSubscriptionWithSyncedZkCredentials_whenIRun_thenIExpectNoCredentialRefresh() {
mockProduct()
mockActivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -472,10 +501,9 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenActiveSubscriptionWithZkCredentialFailure_whenIRun_thenIExpectCredentialRefresh() {
mockProduct()
mockActivePurchase()
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true)
)
@@ -493,11 +521,10 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenSubscriptionWillCancelAtPeriodEnd_whenIRun_thenIExpectValidCancelState() {
mockProduct()
mockCanceledPurchase()
// Create subscription that will cancel at period end
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true, cancelled = true) // cancelled = true means willCancelAtPeriodEnd
)
@@ -510,11 +537,10 @@ class BackupSubscriptionCheckJobTest {
@Test
fun givenActiveSubscriptionNotWillCancelAtPeriodEnd_whenIRun_thenIExpectZkSynchronization() {
mockProduct()
mockActivePurchase()
// Create active subscription that won't cancel at period end
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns Result.success(
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(isActive = true, cancelled = false)
)
@@ -528,6 +554,37 @@ class BackupSubscriptionCheckJobTest {
verify { BackupRepository.getBackupTierWithoutDowngrade() }
}
@Test
fun givenSubscriptionWillCancelWithValidEntitlement_whenIRun_thenIExpectBackupStateNotification() {
mockCanceledPurchase()
every { AppDependencies.accountApi.whoAmI() } returns NetworkResult.Success(
WhoAmIResponse(
number = "+1234567890",
entitlements = WhoAmIResponse.Entitlements(
backup = WhoAmIResponse.BackupEntitlement(
backupLevel = 201,
expirationSeconds = System.currentTimeMillis() / 1000 + 3600 // 1 hour from now
)
)
)
)
every { RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP) } returns NetworkResult.Success(
createActiveSubscription(
isActive = true,
cancelled = true,
chargeFailure = ChargeFailure("test", "", "", "", "")
)
)
val job = BackupSubscriptionCheckJob.create()
val result = job.run()
assertThat(result.isSuccess).isTrue()
verify { BackupStateObserver.notifyBackupStateChanged() }
}
private fun createActiveSubscription(
isActive: Boolean = true,
billingPeriodEndSeconds: Long = 2147472000,
@@ -553,15 +610,6 @@ class BackupSubscriptionCheckJobTest {
)
}
private fun mockProduct() {
coEvery { AppDependencies.billingApi.queryProduct() } returns BillingProduct(
price = FiatMoney(
BigDecimal.ONE,
Currency.getInstance("USD")
)
)
}
private fun insertSubscriber(token: String = IAP_TOKEN) {
SignalDatabase.inAppPaymentSubscribers.insertOrReplace(
InAppPaymentSubscriberRecord(
@@ -575,6 +623,16 @@ class BackupSubscriptionCheckJobTest {
)
}
private fun insertPrePendingInAppPayment() {
SignalDatabase.inAppPayments.insert(
type = InAppPaymentType.RECURRING_BACKUP,
state = InAppPaymentTable.State.TRANSACTING,
subscriberId = null,
endOfPeriod = null,
inAppPaymentData = InAppPaymentData()
)
}
private fun insertPendingInAppPayment() {
SignalDatabase.inAppPayments.insert(
type = InAppPaymentType.RECURRING_BACKUP,
@@ -593,20 +651,6 @@ class BackupSubscriptionCheckJobTest {
}
}
private fun insertFailedInAppPayment() {
SignalDatabase.inAppPayments.insert(
type = InAppPaymentType.RECURRING_BACKUP,
state = InAppPaymentTable.State.END,
subscriberId = null,
endOfPeriod = null,
inAppPaymentData = InAppPaymentData(
error = InAppPaymentData.Error(
type = InAppPaymentData.Error.Type.PAYMENT_SETUP
)
)
)
}
private fun mockActivePurchase() {
coEvery { AppDependencies.billingApi.queryPurchases() } returns BillingPurchaseResult.Success(
purchaseState = BillingPurchaseState.PURCHASED,

View File

@@ -1938,7 +1938,7 @@ object BackupRepository {
suspend fun getPaidType(): NetworkResult<MessageBackupsType.Paid> {
val productPrice: FiatMoney? = if (SignalStore.backup.backupTierInternalOverride == MessageBackupTier.PAID) {
Log.d(TAG, "Accessing price via mock subscription.")
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).getOrNull()?.activeSubscription?.let {
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).successOrNull()?.activeSubscription?.let {
FiatMoney.fromSignalNetworkAmount(it.amount, Currency.getInstance(it.currency))
}
} else if (AppDependencies.billingApi.getApiAvailability().isSuccess) {

View File

@@ -181,7 +181,7 @@ class MessageBackupsFlowViewModel(
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP)
}
activeSubscription.onSuccess { subscription ->
activeSubscription.runIfSuccessful { subscription ->
if (subscription.willCancelAtPeriodEnd()) {
Log.d(TAG, "Active subscription is cancelled. Clearing tier.")
internalStateFlow.update {

View File

@@ -269,8 +269,27 @@ class BackupStateObserver(
Log.d(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] hasActiveGooglePlayBillingSubscription: $hasActiveGooglePlayBillingSubscription")
val activeSubscription = withContext(Dispatchers.IO) {
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).getOrNull()
val activeSubscriptionResult = withContext(Dispatchers.IO) {
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP)
}
val activeSubscription: ActiveSubscription? = when (activeSubscriptionResult) {
is NetworkResult.ApplicationError<ActiveSubscription> -> {
Log.w(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] Failed to load active subscription due to an application error.", activeSubscriptionResult.getCause(), true)
return getStateOnError()
}
is NetworkResult.NetworkError<ActiveSubscription> -> {
Log.w(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] Failed to load active subscription due to a network error.", activeSubscriptionResult.getCause(), true)
return getStateOnError()
}
is NetworkResult.StatusCodeError<ActiveSubscription> -> {
Log.i(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] Failed to load active subscription due to a status code error.", activeSubscriptionResult.getCause(), true)
null
}
is NetworkResult.Success<ActiveSubscription> -> {
Log.i(TAG, "[getNetworkBackupState][subscriptionStateMismatchDetected] Successfully loaded active subscription.", true)
activeSubscriptionResult.result
}
}
val hasActiveSignalSubscription = activeSubscription?.isActive == true
@@ -358,10 +377,10 @@ class BackupStateObserver(
RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP)
}
return if (activeSubscription.isSuccess) {
return if (activeSubscription is NetworkResult.Success) {
Log.d(TAG, "[getPaidBackupState] Retrieved subscription details.")
val subscription = activeSubscription.getOrThrow().activeSubscription
val subscription = activeSubscription.successOrThrow().activeSubscription
if (subscription != null) {
Log.d(TAG, "[getPaidBackupState] Subscription found. Updating UI state with subscription details. Status: ${subscription.status}")

View File

@@ -11,6 +11,10 @@ import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.badges.Badges
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.RecurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessarySync
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository.cancelActiveSubscriptionSync
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository.getActiveSubscriptionSync
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository.rotateSubscriberIdSync
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
@@ -25,6 +29,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.subscription.LevelUpdate
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
import org.thoughtcrime.securesms.subscription.Subscription
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.storage.IAPSubscriptionId
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey
@@ -51,7 +56,7 @@ object RecurringInAppPaymentRepository {
@CheckResult
fun getActiveSubscription(type: InAppPaymentSubscriberRecord.Type): Single<ActiveSubscription> {
return Single.fromCallable {
getActiveSubscriptionSync(type).getOrThrow()
getActiveSubscriptionSync(type).successOrThrow()
}.subscribeOn(Schedulers.io())
}
@@ -77,26 +82,23 @@ object RecurringInAppPaymentRepository {
* Gets the active subscription if it exists for the given [InAppPaymentSubscriberRecord.Type]
*/
@WorkerThread
fun getActiveSubscriptionSync(type: InAppPaymentSubscriberRecord.Type): Result<ActiveSubscription> {
fun getActiveSubscriptionSync(type: InAppPaymentSubscriberRecord.Type): NetworkResult<ActiveSubscription> {
if (type == InAppPaymentSubscriberRecord.Type.BACKUP && SignalStore.backup.backupTierInternalOverride == MessageBackupTier.PAID) {
Log.d(TAG, "Returning mock paid subscription.")
return Result.success(MOCK_PAID_SUBSCRIPTION)
return NetworkResult.Success(MOCK_PAID_SUBSCRIPTION)
}
val response = InAppPaymentsRepository.getSubscriber(type)?.let {
donationsService.getSubscription(it.subscriberId)
} ?: return Result.success(ActiveSubscription.EMPTY)
} ?: return NetworkResult.Success(ActiveSubscription.EMPTY)
return try {
val result = response.resultOrThrow
response.result.ifPresent { result ->
if (result.isActive && result.activeSubscription.endOfCurrentPeriod > SignalStore.inAppPayments.getLastEndOfPeriod()) {
InAppPaymentKeepAliveJob.enqueueAndTrackTime(System.currentTimeMillis().milliseconds)
}
Result.success(result)
} catch (e: Exception) {
Result.failure(e)
}
return response.toNetworkResult()
}
/**

View File

@@ -141,7 +141,22 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
return Result.success()
}
val activeSubscription = RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).getOrNull()
val activeSubscriptionResult = RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP)
val activeSubscription: ActiveSubscription? = when (activeSubscriptionResult) {
is NetworkResult.ApplicationError<ActiveSubscription>, is NetworkResult.NetworkError<ActiveSubscription> -> {
Log.w(TAG, "Encountered an app-level or network-level error. Failing.", activeSubscriptionResult.getCause(), true)
return Result.failure()
}
is NetworkResult.StatusCodeError<ActiveSubscription> -> {
Log.w(TAG, "Encountered a status-code error.", activeSubscriptionResult.getCause(), true)
null
}
is NetworkResult.Success<ActiveSubscription> -> {
Log.i(TAG, "Successfully retrieved the user's active subscription object.", true)
activeSubscriptionResult.result
}
}
val hasActiveSignalSubscription = activeSubscription?.isActive == true
checkForFailedOrCanceledSubscriptionState(activeSubscription)
@@ -234,7 +249,7 @@ class BackupSubscriptionCheckJob private constructor(parameters: Parameters) : C
* the "download your data" notifier sheet.
*/
private fun checkForFailedOrCanceledSubscriptionState(activeSubscription: ActiveSubscription?) {
if (activeSubscription?.willCancelAtPeriodEnd() == true && activeSubscription?.activeSubscription != null) {
if (activeSubscription?.willCancelAtPeriodEnd() == true && activeSubscription.activeSubscription != null) {
Log.i(TAG, "Subscription either has a payment failure or has been canceled.")
val response = SignalNetwork.account.whoAmI()

View File

@@ -92,7 +92,7 @@ class PostRegistrationBackupRedemptionJob : CoroutineJob {
}
info("Attempting to grab price information for records...")
val subscription = RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).getOrNull()?.activeSubscription
val subscription = RecurringInAppPaymentRepository.getActiveSubscriptionSync(InAppPaymentSubscriberRecord.Type.BACKUP).successOrNull()?.activeSubscription
val emptyPrice = FiatMoney(BigDecimal.ZERO, Currency.getInstance(Locale.getDefault()))
val price: FiatMoney = if (subscription != null) {

View File

@@ -246,6 +246,16 @@ sealed class NetworkResult<T>(
}
}
/**
* Returns the result if successful, otherwise null.
*/
fun successOrNull(): T? {
return when (this) {
is Success -> result
else -> null
}
}
/**
* Returns the [Throwable] associated with the result, or null if the result is successful.
*/