Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add awaitRestore to customEntitlementComputation library #1275

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -74,33 +74,6 @@ suspend fun Purchases.awaitLogOut(): CustomerInfo {
}
}

/**
* Restores purchases made with the current Play Store account for the current user.
* This method will post all purchases associated with the current Play Store account to
* RevenueCat and become associated with the current `appUserID`. If the receipt token is being
* used by an existing user, the current `appUserID` will be aliased together with the
* `appUserID` of the existing user. Going forward, either `appUserID` will be able to reference
* the same user.
*
* You shouldn't use this method if you have your own account system. In that case
* "restoration" is provided by your app passing the same `appUserId` used to purchase originally.
*
* Coroutine friendly version of [Purchases.restorePurchases].
*
* @throws [PurchasesException] with a [PurchasesError] if there's an error login out the user.
* @return The [CustomerInfo] with the restored purchases.
*/
@JvmSynthetic
@Throws(PurchasesTransactionException::class)
suspend fun Purchases.awaitRestore(): CustomerInfo {
return suspendCoroutine { continuation ->
restorePurchasesWith(
onSuccess = { continuation.resume(it) },
onError = { continuation.resumeWithException(PurchasesException(it)) },
)
}
}

/**
* This method will send all the purchases to the RevenueCat backend. Call this when using your own implementation
* for subscriptions anytime a sync is needed, such as when migrating existing users to RevenueCat.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,30 @@ suspend fun Purchases.awaitGetProducts(
)
}
}

/**
* Restores purchases made with the current Play Store account for the current user.
* This method will post all purchases associated with the current Play Store account to
* RevenueCat and become associated with the current `appUserID`. If the receipt token is being
* used by an existing user, the current `appUserID` will be aliased together with the
* `appUserID` of the existing user. Going forward, either `appUserID` will be able to reference
* the same user.
*
* You shouldn't use this method if you have your own account system. In that case
* "restoration" is provided by your app passing the same `appUserId` used to purchase originally.
*
* Coroutine friendly version of [Purchases.restorePurchases].
*
* @throws [PurchasesException] with a [PurchasesError] if there's an error login out the user.
* @return The [CustomerInfo] with the restored purchases.
*/
@JvmSynthetic
@Throws(PurchasesTransactionException::class)
suspend fun Purchases.awaitRestore(): CustomerInfo {
return suspendCoroutine { continuation ->
restorePurchasesWith(
onSuccess = { continuation.resume(it) },
onError = { continuation.resumeWithException(PurchasesException(it)) },
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package com.revenuecat.purchases

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.revenuecat.purchases.models.StoreProduct
import com.revenuecat.purchases.models.StoreTransaction
import com.revenuecat.purchases.utils.STUB_PRODUCT_IDENTIFIER
import com.revenuecat.purchases.utils.stubStoreProduct
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
Expand Down Expand Up @@ -229,4 +231,113 @@ internal class PurchasesCoroutinesCommonTest : BasePurchasesTest() {
}

// endregion

// region awaitRestore
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just moving the tests. No changes.


@Test
fun `restore - Success`() = runTest {
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
captureLambda(),
any()
)
} answers {
lambda<(List<StoreTransaction>) -> Unit>().captured.also {
it.invoke(listOf(mockk(relaxed = true)))
}
}

val result: CustomerInfo = purchases.awaitRestore()

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}
assertThat(result).isNotNull
}

@Test
fun `restore - Success - customer info matches expectations`() = runTest {
val afterRestoreCustomerInfo = mockk<CustomerInfo>()
val storeTransaction = mockk<StoreTransaction>(relaxed = true)
every {
mockPostReceiptHelper.postTransactionAndConsumeIfNeeded(
storeTransaction,
any(),
true,
appUserId,
PostReceiptInitiationSource.RESTORE,
onSuccess = captureLambda(),
any()
)
} answers {
lambda<(StoreTransaction, CustomerInfo) -> Unit>().captured.also {
it.invoke(storeTransaction, afterRestoreCustomerInfo)
}
}
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
captureLambda(),
any()
)
} answers {
lambda<(List<StoreTransaction>) -> Unit>().captured.also {
it.invoke(listOf(storeTransaction))
}
}

val result: CustomerInfo = purchases.awaitRestore()

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}
assertThat(result).isNotNull
assertThat(result).isEqualTo(afterRestoreCustomerInfo)
}

@Test
fun `restore - CustomerInfoError`() = runTest {
val error = PurchasesError(PurchasesErrorCode.CustomerInfoError, "Customer info error")
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
onReceivePurchaseHistoryError = captureLambda(),
)
} answers {
lambda<(PurchasesError?) -> Unit>().captured.invoke(error)
}

var result: CustomerInfo? = null
var exception: Throwable? = null
runCatching {
result = purchases.awaitRestore()
}.onFailure {
exception = it
}

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}

assertThat(result).isNull()
assertThat(exception).isNotNull
assertThat(exception).isInstanceOf(PurchasesException::class.java)
assertThat((exception as PurchasesException).code).isEqualTo(PurchasesErrorCode.CustomerInfoError)
}

// endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,115 +245,6 @@ internal class PurchasesCoroutinesTest : BasePurchasesTest() {

// endregion

// region awaitRestore

@Test
fun `restore - Success`() = runTest {
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
captureLambda(),
any()
)
} answers {
lambda<(List<StoreTransaction>) -> Unit>().captured.also {
it.invoke(listOf(mockk(relaxed = true)))
}
}

val result: CustomerInfo = purchases.awaitRestore()

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}
assertThat(result).isNotNull
}

@Test
fun `restore - Success - customer info matches expectations`() = runTest {
val afterRestoreCustomerInfo = mockk<CustomerInfo>()
val storeTransaction = mockk<StoreTransaction>(relaxed = true)
every {
mockPostReceiptHelper.postTransactionAndConsumeIfNeeded(
storeTransaction,
any(),
true,
appUserId,
PostReceiptInitiationSource.RESTORE,
onSuccess = captureLambda(),
any()
)
} answers {
lambda<(StoreTransaction, CustomerInfo) -> Unit>().captured.also {
it.invoke(storeTransaction, afterRestoreCustomerInfo)
}
}
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
captureLambda(),
any()
)
} answers {
lambda<(List<StoreTransaction>) -> Unit>().captured.also {
it.invoke(listOf(storeTransaction))
}
}

val result: CustomerInfo = purchases.awaitRestore()

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}
assertThat(result).isNotNull
assertThat(result).isEqualTo(afterRestoreCustomerInfo)
}

@Test
fun `restore - CustomerInfoError`() = runTest {
val error = PurchasesError(PurchasesErrorCode.CustomerInfoError, "Customer info error")
every {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
onReceivePurchaseHistoryError = captureLambda(),
)
} answers {
lambda<(PurchasesError?) -> Unit>().captured.invoke(error)
}

var result: CustomerInfo? = null
var exception: Throwable? = null
runCatching {
result = purchases.awaitRestore()
}.onFailure {
exception = it
}

verify(exactly = 1) {
mockBillingAbstract.queryAllPurchases(
appUserId,
any(),
any(),
)
}

assertThat(result).isNull()
assertThat(exception).isNotNull
assertThat(exception).isInstanceOf(PurchasesException::class.java)
assertThat((exception as PurchasesException).code).isEqualTo(PurchasesErrorCode.CustomerInfoError)
}

// endregion

// region awaitSyncPurchases

@Test
Expand Down