mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 19:26:12 -04:00
test(upgrade): Cover PENDING purchases granting no Pro and no ack
This commit is contained in:
+23
@@ -6,17 +6,40 @@ import io.kotest.assertions.throwables.shouldThrow
|
|||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import testhelpers.BaseTest
|
import testhelpers.BaseTest
|
||||||
|
import testhelpers.coroutine.runTest2
|
||||||
|
|
||||||
class BillingClientConnectionTest : BaseTest() {
|
class BillingClientConnectionTest : BaseTest() {
|
||||||
|
|
||||||
private fun mockPurchase(
|
private fun mockPurchase(
|
||||||
productId: String = CapodSku.Iap.PRO_UPGRADE.id,
|
productId: String = CapodSku.Iap.PRO_UPGRADE.id,
|
||||||
purchaseTime: Long = 1_000,
|
purchaseTime: Long = 1_000,
|
||||||
|
state: Int = Purchase.PurchaseState.PURCHASED,
|
||||||
): Purchase = mockk {
|
): Purchase = mockk {
|
||||||
every { products } returns listOf(productId)
|
every { products } returns listOf(productId)
|
||||||
every { this@mockk.purchaseTime } returns purchaseTime
|
every { this@mockk.purchaseTime } returns purchaseTime
|
||||||
|
every { purchaseState } returns state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `pending purchases are filtered out of the push-based purchases flow`() = runTest2 {
|
||||||
|
// A PENDING purchase (e.g. a slow cash/deferred payment) must never reach the entitlement
|
||||||
|
// layer — only PURCHASED grants Pro. This guards the #628 fix at the connection boundary.
|
||||||
|
val pending = mockPurchase(purchaseTime = 2_000, state = Purchase.PurchaseState.PENDING)
|
||||||
|
val purchased = mockPurchase(purchaseTime = 1_000, state = Purchase.PurchaseState.PURCHASED)
|
||||||
|
|
||||||
|
val connection = BillingClientConnection(
|
||||||
|
client = mockk(relaxed = true),
|
||||||
|
purchasesGlobal = MutableStateFlow(listOf(pending, purchased)),
|
||||||
|
freshObservations = MutableSharedFlow(),
|
||||||
|
purchaseFailuresGlobal = MutableSharedFlow(),
|
||||||
|
)
|
||||||
|
|
||||||
|
connection.purchases.first() shouldBe listOf(purchased)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+38
@@ -2,6 +2,7 @@ package eu.darken.capod.common.upgrade.core.data
|
|||||||
|
|
||||||
import com.android.billingclient.api.BillingClient
|
import com.android.billingclient.api.BillingClient
|
||||||
import com.android.billingclient.api.BillingResult
|
import com.android.billingclient.api.BillingResult
|
||||||
|
import com.android.billingclient.api.Purchase
|
||||||
import eu.darken.capod.common.AppForegroundState
|
import eu.darken.capod.common.AppForegroundState
|
||||||
import eu.darken.capod.common.upgrade.core.client.BillingClientConnection
|
import eu.darken.capod.common.upgrade.core.client.BillingClientConnection
|
||||||
import eu.darken.capod.common.upgrade.core.client.BillingClientConnectionProvider
|
import eu.darken.capod.common.upgrade.core.client.BillingClientConnectionProvider
|
||||||
@@ -212,6 +213,43 @@ class BillingDataRepoTest : BaseTest() {
|
|||||||
mapped shouldBe original
|
mapped shouldBe original
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mockPurchase(state: Int, acknowledged: Boolean): Purchase = mockk {
|
||||||
|
every { purchaseState } returns state
|
||||||
|
every { isAcknowledged } returns acknowledged
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ack pipeline acknowledges only unacknowledged PURCHASED purchases`() = runTest2 {
|
||||||
|
val testScope = TestScope(UnconfinedTestDispatcher(testScheduler))
|
||||||
|
// A PENDING purchase must not be acknowledged (acking it fails and spins the retry loop);
|
||||||
|
// an already-acknowledged one must not be re-acked; only a settled, unacked one is acked.
|
||||||
|
val pending = mockPurchase(Purchase.PurchaseState.PENDING, acknowledged = false)
|
||||||
|
val purchasedUnacked = mockPurchase(Purchase.PurchaseState.PURCHASED, acknowledged = false)
|
||||||
|
val purchasedAcked = mockPurchase(Purchase.PurchaseState.PURCHASED, acknowledged = true)
|
||||||
|
|
||||||
|
val clientConnection = mockk<BillingClientConnection> {
|
||||||
|
every { purchases } returns flowOf(listOf(pending, purchasedUnacked, purchasedAcked))
|
||||||
|
coEvery { acknowledgePurchase(any()) } returns Unit
|
||||||
|
}
|
||||||
|
val provider = mockk<BillingClientConnectionProvider> {
|
||||||
|
every { connection } returns flowOf(clientConnection)
|
||||||
|
}
|
||||||
|
val foregroundState = mockk<AppForegroundState> {
|
||||||
|
every { isForeground } returns MutableStateFlow(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
BillingDataRepo(provider, testScope, foregroundState, TestTimeSource())
|
||||||
|
testScope.testScheduler.runCurrent()
|
||||||
|
|
||||||
|
coVerify(exactly = 1) { clientConnection.acknowledgePurchase(purchasedUnacked) }
|
||||||
|
coVerify(exactly = 0) { clientConnection.acknowledgePurchase(pending) }
|
||||||
|
coVerify(exactly = 0) { clientConnection.acknowledgePurchase(purchasedAcked) }
|
||||||
|
} finally {
|
||||||
|
testScope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ForegroundRefreshHarness(testScope: TestScope) {
|
private class ForegroundRefreshHarness(testScope: TestScope) {
|
||||||
val clientConnection = mockk<BillingClientConnection> {
|
val clientConnection = mockk<BillingClientConnection> {
|
||||||
every { purchases } returns emptyFlow()
|
every { purchases } returns emptyFlow()
|
||||||
|
|||||||
Reference in New Issue
Block a user