IMPORTANT: The file content has been truncated. Status: Showing lines 1-100 of 609 total lines. Action: To read more of the file, you can use the 'start_line' and 'end_line' parameters in a subsequent 'read_file' call. For example, to read the next section of the file, use start_line: 101. --- FILE CONTENT (truncated) --- package com.yorvana.data.billing import android.app.Activity import com.android.billingclient.api.AcknowledgePurchaseParams import com.android.billingclient.api.AcknowledgePurchaseResponseListener import com.android.billingclient.api.BillingClient import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingResult import com.android.billingclient.api.ProductDetails import com.android.billingclient.api.ProductDetailsResponseListener import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesResponseListener import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.QueryProductDetailsParams import com.android.billingclient.api.QueryProductDetailsResult import com.android.billingclient.api.QueryPurchasesParams import com.google.common.truth.Truth.assertThat import com.yorvana.LocalOnly import com.yorvana.data.model.OdometerUnit import com.yorvana.data.preferences.AppPreferences import com.yorvana.data.preferences.AppPreferencesStore import com.yorvana.data.preferences.CURRENT_CONSENT_VERSION import com.yorvana.data.preferences.DebugBillingOverrideMode import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.experimental.categories.Category /** * Unit tests for [BillingManagerImpl] and [DebugBillingOverride]. * * These tests mock [BillingClient] via a fake [BillingClientProvider]. * * This class is tagged [LocalOnly] and excluded from the standard Gradle test run because * mocking [BillingClient] (a final Android-library class) can be unreliable in the * headless Robolectric environment. Run it directly from the IDE to exercise billing logic. */ @OptIn(ExperimentalCoroutinesApi::class) @Category(LocalOnly::class) class BillingManagerImplTest { private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) private lateinit var mockBillingClient: BillingClient private lateinit var preferences: FakeAppPreferencesStore private lateinit var capturedPurchasesUpdatedListener: PurchasesUpdatedListener @Before fun setUp() { mockBillingClient = mockk(relaxed = true) preferences = FakeAppPreferencesStore() } private fun createBillingManager(scope: CoroutineScope): BillingManagerImpl { val debugBillingOverride = DebugBillingOverride(preferences, scope) val provider = BillingClientProvider { listener -> capturedPurchasesUpdatedListener = listener mockBillingClient } return BillingManagerImpl( preferences = preferences, coroutineScope = scope, billingClientProvider = provider, debugOverride = debugBillingOverride, ) } // ── Helpers ───────────────────────────────────────────────────────────────── private fun okResult(): BillingResult = BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.OK) .build() private fun errorResult(code: Int = BillingClient.BillingResponseCode.ERROR): BillingResult = BillingResult.newBuilder() .setResponseCode(code) .build() private fun makePurchase( state: Int = Purchase.PurchaseState.PURCHASED, acknowledged: Boolean = false, token: String = "token_abc", ): Purchase { val purchase = mockk(relaxed = true) every { purchase.products } returns listOf(BillingManagerImpl.PRODUCT_ID) every { purchase.purchaseState } returns state every { purchase.isAcknowledged } returns acknowledged