diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt index 12a132e987..2de554c97c 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt @@ -179,6 +179,7 @@ internal class PurchasesFactory( offeringsCache, backend, offlineEntitlementsManager, + dispatcher, ) val customerInfoUpdateHandler = CustomerInfoUpdateHandler( @@ -285,6 +286,7 @@ internal class PurchasesFactory( createPaywallEventsManager(application, identityManager, eventsDispatcher, backend), paywallPresentedCache, purchasesStateProvider, + dispatcher = dispatcher, ) return Purchases(purchasesOrchestrator) diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt index b28ecc5e6c..d55831d871 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt @@ -16,6 +16,8 @@ import com.revenuecat.purchases.common.Backend import com.revenuecat.purchases.common.BillingAbstract import com.revenuecat.purchases.common.Config import com.revenuecat.purchases.common.Constants +import com.revenuecat.purchases.common.Delay +import com.revenuecat.purchases.common.Dispatcher import com.revenuecat.purchases.common.LogIntent import com.revenuecat.purchases.common.PlatformInfo import com.revenuecat.purchases.common.ReceiptInfo @@ -73,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlin.time.Duration.Companion.seconds @Suppress("LongParameterList", "LargeClass", "TooManyFunctions") -internal class PurchasesOrchestrator constructor( +internal class PurchasesOrchestrator( private val application: Application, backingFieldAppUserID: String?, private val backend: Backend, @@ -97,6 +99,7 @@ internal class PurchasesOrchestrator constructor( private val purchasesStateCache: PurchasesStateCache, // This is nullable due to: https://github.com/RevenueCat/purchases-flutter/issues/408 private val mainHandler: Handler? = Handler(Looper.getMainLooper()), + private val dispatcher: Dispatcher, ) : LifecycleDelegate, CustomActivityLifecycleHandler { internal var state: PurchasesState @@ -194,22 +197,24 @@ internal class PurchasesOrchestrator constructor( state = state.copy(appInBackground = false, firstTimeInForeground = false) } log(LogIntent.DEBUG, ConfigureStrings.APP_FOREGROUNDED) - if (shouldRefreshCustomerInfo(firstTimeInForeground)) { - log(LogIntent.DEBUG, CustomerInfoStrings.CUSTOMERINFO_STALE_UPDATING_FOREGROUND) - customerInfoHelper.retrieveCustomerInfo( - identityManager.currentAppUserID, - fetchPolicy = CacheFetchPolicy.FETCH_CURRENT, - appInBackground = false, - allowSharingPlayStoreAccount = allowSharingPlayStoreAccount, - ) - } - offeringsManager.onAppForeground(identityManager.currentAppUserID) - postPendingTransactionsHelper.syncPendingPurchaseQueue(allowSharingPlayStoreAccount) - synchronizeSubscriberAttributesIfNeeded() - offlineEntitlementsManager.updateProductEntitlementMappingCacheIfStale() - flushPaywallEvents() - if (firstTimeInForeground && isAndroidNOrNewer()) { - diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() + enqueue { + if (shouldRefreshCustomerInfo(firstTimeInForeground)) { + log(LogIntent.DEBUG, CustomerInfoStrings.CUSTOMERINFO_STALE_UPDATING_FOREGROUND) + customerInfoHelper.retrieveCustomerInfo( + identityManager.currentAppUserID, + fetchPolicy = CacheFetchPolicy.FETCH_CURRENT, + appInBackground = false, + allowSharingPlayStoreAccount = allowSharingPlayStoreAccount, + ) + } + offeringsManager.onAppForeground(identityManager.currentAppUserID) + postPendingTransactionsHelper.syncPendingPurchaseQueue(allowSharingPlayStoreAccount) + synchronizeSubscriberAttributesIfNeeded() + offlineEntitlementsManager.updateProductEntitlementMappingCacheIfStale() + flushPaywallEvents() + if (firstTimeInForeground && isAndroidNOrNewer()) { + diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() + } } } @@ -775,6 +780,9 @@ internal class PurchasesOrchestrator constructor( //endregion // region Private Methods + private fun enqueue(command: () -> Unit) { + dispatcher.enqueue({ command() }, Delay.NONE) + } private fun shouldRefreshCustomerInfo(firstTimeInForeground: Boolean): Boolean { return !appConfig.customEntitlementComputation && diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/common/caching/DeviceCache.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/common/caching/DeviceCache.kt index b12866414d..9afe56728c 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/common/caching/DeviceCache.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/common/caching/DeviceCache.kt @@ -64,6 +64,10 @@ internal open class DeviceCache( private val offeringsResponseCacheKey: String by lazy { "$apiKeyPrefix.offeringsResponse" } + fun startEditing(): SharedPreferences.Editor { + return preferences.edit() + } + // region app user id @Synchronized @@ -74,7 +78,15 @@ internal open class DeviceCache( @Synchronized fun cacheAppUserID(appUserID: String) { - preferences.edit().putString(appUserIDCacheKey, appUserID).apply() + cacheAppUserID(appUserID, preferences.edit()).apply() + } + + @Synchronized + fun cacheAppUserID( + appUserID: String, + cacheEditor: SharedPreferences.Editor, + ): SharedPreferences.Editor { + return cacheEditor.putString(appUserIDCacheKey, appUserID) } @Synchronized @@ -168,9 +180,17 @@ internal open class DeviceCache( @Synchronized fun clearCustomerInfoCache(appUserID: String) { val editor = preferences.edit() + clearCustomerInfoCache(appUserID, editor) + editor.apply() + } + + @Synchronized + fun clearCustomerInfoCache( + appUserID: String, + editor: SharedPreferences.Editor, + ) { editor.clearCustomerInfoCacheTimestamp(appUserID) editor.remove(customerInfoCacheKey(appUserID)) - editor.apply() } @Synchronized diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/identity/IdentityManager.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/identity/IdentityManager.kt index 09919c0c73..0e014cd1e3 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/identity/IdentityManager.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/identity/IdentityManager.kt @@ -1,10 +1,13 @@ package com.revenuecat.purchases.identity +import android.content.SharedPreferences import com.revenuecat.purchases.CustomerInfo import com.revenuecat.purchases.PurchasesError import com.revenuecat.purchases.PurchasesErrorCode import com.revenuecat.purchases.VerificationResult import com.revenuecat.purchases.common.Backend +import com.revenuecat.purchases.common.Delay +import com.revenuecat.purchases.common.Dispatcher import com.revenuecat.purchases.common.LogIntent import com.revenuecat.purchases.common.caching.DeviceCache import com.revenuecat.purchases.common.debugLog @@ -20,7 +23,7 @@ import com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttribute import java.util.Locale import java.util.UUID -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LongParameterList") internal class IdentityManager( private val deviceCache: DeviceCache, private val subscriberAttributesCache: SubscriberAttributesCache, @@ -28,6 +31,7 @@ internal class IdentityManager( private val offeringsCache: OfferingsCache, private val backend: Backend, private val offlineEntitlementsManager: OfflineEntitlementsManager, + private val dispatcher: Dispatcher, ) { val currentAppUserID: String @@ -38,7 +42,9 @@ internal class IdentityManager( // region Public functions @Synchronized - fun configure(appUserID: String?) { + fun configure( + appUserID: String?, + ) { if (appUserID?.isBlank() == true) { log(LogIntent.WARNING, IdentityStrings.EMPTY_APP_USER_ID_WILL_BECOME_ANONYMOUS) } @@ -49,10 +55,16 @@ internal class IdentityManager( ?: deviceCache.getLegacyCachedAppUserID() ?: generateRandomID() log(LogIntent.USER, IdentityStrings.IDENTIFYING_APP_USER_ID.format(appUserIDToUse)) - deviceCache.cacheAppUserID(appUserIDToUse) - subscriberAttributesCache.cleanUpSubscriberAttributeCache(appUserIDToUse) - deviceCache.cleanupOldAttributionData() - invalidateCustomerInfoAndETagCacheIfNeeded(appUserIDToUse) + + val cacheEditor = deviceCache.startEditing() + deviceCache.cacheAppUserID(appUserIDToUse, cacheEditor) + subscriberAttributesCache.cleanUpSubscriberAttributeCache(appUserIDToUse, cacheEditor) + invalidateCustomerInfoAndETagCacheIfNeeded(appUserIDToUse, cacheEditor) + cacheEditor.apply() + + enqueue { + deviceCache.cleanupOldAttributionData() + } } fun logIn( @@ -135,11 +147,17 @@ internal class IdentityManager( } } - private fun invalidateCustomerInfoAndETagCacheIfNeeded(appUserID: String) { + private fun invalidateCustomerInfoAndETagCacheIfNeeded( + appUserID: String, + cacheEditor: SharedPreferences.Editor, + ) { + if (backend.verificationMode == SignatureVerificationMode.Disabled) { + return + } val cachedCustomerInfo = deviceCache.getCachedCustomerInfo(appUserID) if (shouldInvalidateCustomerInfoAndETagCache(cachedCustomerInfo)) { infoLog(IdentityStrings.INVALIDATING_CACHED_CUSTOMER_INFO) - deviceCache.clearCustomerInfoCache(appUserID) + deviceCache.clearCustomerInfoCache(appUserID, cacheEditor) backend.clearCaches() } } @@ -172,5 +190,10 @@ internal class IdentityManager( backend.clearCaches() } + @Synchronized + private fun enqueue(command: () -> Unit) { + dispatcher.enqueue({ command() }, Delay.NONE) + } + // endregion } diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesCache.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesCache.kt index 4b227f73cb..e7e0079c4f 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesCache.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesCache.kt @@ -5,6 +5,7 @@ package com.revenuecat.purchases.subscriberattributes.caching +import android.content.SharedPreferences import com.revenuecat.purchases.common.LogIntent import com.revenuecat.purchases.common.caching.DeviceCache import com.revenuecat.purchases.common.log @@ -76,12 +77,15 @@ internal class SubscriberAttributesCache( } @Synchronized - fun cleanUpSubscriberAttributeCache(currentAppUserID: String) { - migrateSubscriberAttributesIfNeeded() - deleteSyncedSubscriberAttributesForOtherUsers(currentAppUserID) + fun cleanUpSubscriberAttributeCache( + currentAppUserID: String, + cacheEditor: SharedPreferences.Editor, + ) { + migrateSubscriberAttributesIfNeeded(cacheEditor) + deleteSyncedSubscriberAttributesForOtherUsers(currentAppUserID, cacheEditor) } - internal fun DeviceCache.putAttributes( + private fun DeviceCache.putAttributes( updatedSubscriberAttributesForAll: SubscriberAttributesPerAppUserIDMap, ) { return deviceCache.putString( @@ -91,7 +95,10 @@ internal class SubscriberAttributesCache( } @Synchronized - private fun deleteSyncedSubscriberAttributesForOtherUsers(currentAppUserID: String) { + private fun deleteSyncedSubscriberAttributesForOtherUsers( + currentAppUserID: String, + cacheEditor: SharedPreferences.Editor, + ) { log(LogIntent.DEBUG, AttributionStrings.DELETING_ATTRIBUTES_OTHER_USERS.format(currentAppUserID)) val allStoredSubscriberAttributes = getAllStoredSubscriberAttributes() @@ -105,7 +112,10 @@ internal class SubscriberAttributesCache( } }.toMap().filterValues { it.isNotEmpty() } - deviceCache.putAttributes(filteredMap) + cacheEditor.putString( + subscriberAttributesCacheKey, + filteredMap.toJSONObject().toString(), + ) } private fun SubscriberAttributeMap.filterUnsynced(appUserID: AppUserID): SubscriberAttributeMap = diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesMigrationExtensions.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesMigrationExtensions.kt index a64823b7e0..704c161830 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesMigrationExtensions.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/subscriberattributes/caching/SubscriberAttributesMigrationExtensions.kt @@ -1,19 +1,21 @@ package com.revenuecat.purchases.subscriberattributes.caching +import android.content.SharedPreferences import com.revenuecat.purchases.subscriberattributes.buildLegacySubscriberAttributes @Synchronized -internal fun SubscriberAttributesCache.migrateSubscriberAttributesIfNeeded() { +internal fun SubscriberAttributesCache.migrateSubscriberAttributesIfNeeded(cacheEditor: SharedPreferences.Editor) { getAllLegacyStoredSubscriberAttributes() .takeIf { it.isNotEmpty() } ?.let { legacySubscriberAttributes -> - migrateSubscriberAttributes(legacySubscriberAttributes) + migrateSubscriberAttributes(legacySubscriberAttributes, cacheEditor) } } @Synchronized internal fun SubscriberAttributesCache.migrateSubscriberAttributes( legacySubscriberAttributesForAppUserID: SubscriberAttributesPerAppUserIDMap, + cacheEditor: SharedPreferences.Editor, ) { val storedSubscriberAttributesForAll: SubscriberAttributesPerAppUserIDMap = getAllStoredSubscriberAttributes() @@ -25,10 +27,13 @@ internal fun SubscriberAttributesCache.migrateSubscriberAttributes( val current: SubscriberAttributeMap = storedSubscriberAttributesForAll[appUserID] ?: emptyMap() val updated: SubscriberAttributeMap = legacy + current updatedStoredSubscriberAttributesForAll[appUserID] = updated - deviceCache.remove(legacySubscriberAttributesCacheKey(appUserID)) + cacheEditor.remove(legacySubscriberAttributesCacheKey(appUserID)) } - deviceCache.putAttributes(updatedStoredSubscriberAttributesForAll) + cacheEditor.putString( + subscriberAttributesCacheKey, + updatedStoredSubscriberAttributesForAll.toJSONObject().toString(), + ) } internal fun SubscriberAttributesCache.legacySubscriberAttributesCacheKey(appUserID: String) = @@ -39,12 +44,12 @@ internal fun SubscriberAttributesCache.getAllLegacyStoredSubscriberAttributes(): val legacySubscriberAttributesCacheKeyPrefix = legacySubscriberAttributesCacheKey("") val allSubscriberAttributesKeys = deviceCache.findKeysThatStartWith(legacySubscriberAttributesCacheKeyPrefix) - return allSubscriberAttributesKeys.map { preferencesKey -> + return allSubscriberAttributesKeys.associate { preferencesKey -> val appUserIDFromKey: AppUserID = preferencesKey.split(legacySubscriberAttributesCacheKeyPrefix)[1] val subscriberAttributeMap: SubscriberAttributeMap = deviceCache.getJSONObjectOrNull(preferencesKey) ?.buildLegacySubscriberAttributes() ?: emptyMap() appUserIDFromKey to subscriberAttributeMap - }.toMap() + } } diff --git a/purchases/src/test/java/com/revenuecat/purchases/BasePurchasesTest.kt b/purchases/src/test/java/com/revenuecat/purchases/BasePurchasesTest.kt index 723674af0a..8cc1adb6d1 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/BasePurchasesTest.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/BasePurchasesTest.kt @@ -33,6 +33,7 @@ import com.revenuecat.purchases.paywalls.PaywallPresentedCache import com.revenuecat.purchases.paywalls.events.PaywallEventsManager import com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager import com.revenuecat.purchases.utils.STUB_PRODUCT_IDENTIFIER +import com.revenuecat.purchases.utils.SyncDispatcher import com.revenuecat.purchases.utils.createMockOneTimeProductDetails import com.revenuecat.purchases.utils.stubGooglePurchase import com.revenuecat.purchases.utils.stubPurchaseHistoryRecord @@ -406,6 +407,7 @@ internal open class BasePurchasesTest { paywallEventsManager = mockPaywallEventsManager, paywallPresentedCache = paywallPresentedCache, purchasesStateCache = purchasesStateProvider, + dispatcher = SyncDispatcher(), ) purchases = Purchases(purchasesOrchestrator) Purchases.sharedInstance = purchases diff --git a/purchases/src/test/java/com/revenuecat/purchases/identity/IdentityManagerTests.kt b/purchases/src/test/java/com/revenuecat/purchases/identity/IdentityManagerTests.kt index 38cb3c26f4..bcc6cf5dfa 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/identity/IdentityManagerTests.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/identity/IdentityManagerTests.kt @@ -1,5 +1,6 @@ package com.revenuecat.purchases.identity +import android.content.SharedPreferences.Editor import androidx.test.ext.junit.runners.AndroidJUnit4 import com.revenuecat.purchases.CustomerInfo import com.revenuecat.purchases.EntitlementInfos @@ -14,6 +15,7 @@ import com.revenuecat.purchases.common.offlineentitlements.OfflineEntitlementsMa import com.revenuecat.purchases.common.verification.SignatureVerificationMode import com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager import com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttributesCache +import com.revenuecat.purchases.utils.SyncDispatcher import io.mockk.CapturingSlot import io.mockk.Runs import io.mockk.every @@ -40,21 +42,30 @@ class IdentityManagerTests { private lateinit var mockBackend: Backend private lateinit var mockOfflineEntitlementsManager: OfflineEntitlementsManager private lateinit var identityManager: IdentityManager + private lateinit var mockEditor: Editor private val stubAnonymousID = "\$RCAnonymousID:ff68f26e432648369a713849a9f93b58" @Before fun setup() { cachedAppUserIDSlot = slot() + mockEditor = mockk().apply { + every { apply() } just Runs + } mockDeviceCache = mockk().apply { every { cacheAppUserID(capture(cachedAppUserIDSlot)) } answers { every { mockDeviceCache.getCachedAppUserID() } returns cachedAppUserIDSlot.captured } + every { cacheAppUserID(capture(cachedAppUserIDSlot), mockEditor) } answers { + every { mockDeviceCache.getCachedAppUserID() } returns cachedAppUserIDSlot.captured + mockEditor + } every { cleanupOldAttributionData() } just Runs every { getCachedCustomerInfo(any()) } returns null + every { startEditing() } returns mockEditor } mockSubscriberAttributesCache = mockk().apply { every { - cleanUpSubscriberAttributeCache(capture(cachedAppUserIDSlot)) + cleanUpSubscriberAttributeCache(capture(cachedAppUserIDSlot), mockEditor) } just Runs } mockSubscriberAttributesManager = mockk() @@ -62,7 +73,9 @@ class IdentityManagerTests { every { clearCache() } just Runs } - mockBackend = mockk() + mockBackend = mockk().apply { + every { verificationMode } returns SignatureVerificationMode.Disabled + } mockOfflineEntitlementsManager = mockk().apply { every { resetOfflineCustomerInfoCache() } just Runs } @@ -94,7 +107,7 @@ class IdentityManagerTests { fun testConfigureSavesTheIDInTheCache() { every { mockDeviceCache.getCachedAppUserID() } returns null every { mockDeviceCache.getLegacyCachedAppUserID() } returns null - every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar") } just Runs + every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar", any()) } just Runs identityManager.configure("cesar") assertCorrectlyIdentified("cesar") } @@ -425,7 +438,7 @@ class IdentityManagerTests { every { mockDeviceCache.getCachedAppUserID() } returns null every { mockDeviceCache.getLegacyCachedAppUserID() } returns "an_old_random" every { mockDeviceCache.clearCachesForAppUserID("an_old_random") } just Runs - every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("an_old_random") } just Runs + every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("an_old_random", any()) } just Runs identityManager.configure(null) assertCorrectlyIdentifiedWithAnonymous(oldID = "an_old_random") } @@ -435,7 +448,7 @@ class IdentityManagerTests { every { mockDeviceCache.getCachedAppUserID() } returns null every { mockDeviceCache.getLegacyCachedAppUserID() } returns "an_old_random" every { mockDeviceCache.clearCachesForAppUserID("an_old_random") } just Runs - every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar") } just Runs + every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar", any()) } just Runs identityManager.configure("cesar") assertCorrectlyIdentified("cesar") } @@ -452,7 +465,7 @@ class IdentityManagerTests { mockCleanCaches() identityManager.configure("cesar") verify { - mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar") + mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar", any()) } } @@ -462,7 +475,7 @@ class IdentityManagerTests { identityManager.configure(null) assertThat(cachedAppUserIDSlot.captured).isNotNull verify { - mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(cachedAppUserIDSlot.captured) + mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(cachedAppUserIDSlot.captured, any()) } } @@ -471,7 +484,7 @@ class IdentityManagerTests { mockCleanCaches() identityManager.configure("cesar") verify { - mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar") + mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache("cesar", any()) } } @@ -481,7 +494,7 @@ class IdentityManagerTests { identityManager.configure(null) assertThat(cachedAppUserIDSlot.captured).isNotNull verify { - mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(cachedAppUserIDSlot.captured) + mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(cachedAppUserIDSlot.captured, any()) } } @@ -510,7 +523,7 @@ class IdentityManagerTests { ) identityManager.configure(userId) verify(exactly = 1) { - mockDeviceCache.clearCustomerInfoCache(userId) + mockDeviceCache.clearCustomerInfoCache(userId, mockEditor) } verify(exactly = 1) { mockBackend.clearCaches() @@ -528,7 +541,7 @@ class IdentityManagerTests { ) identityManager.configure(userId) verify(exactly = 1) { - mockDeviceCache.clearCustomerInfoCache(userId) + mockDeviceCache.clearCustomerInfoCache(userId, mockEditor) } verify(exactly = 1) { mockBackend.clearCaches() @@ -633,7 +646,7 @@ class IdentityManagerTests { } every { mockDeviceCache.getCachedCustomerInfo(userId) } returns mockCustomerInfo if (shouldClearCustomerInfoAndETagCaches) { - every { mockDeviceCache.clearCustomerInfoCache(userId) } just Runs + every { mockDeviceCache.clearCustomerInfoCache(userId, mockEditor) } just Runs every { mockBackend.clearCaches() } just Runs } every { mockBackend.verificationMode } returns verificationMode @@ -645,7 +658,7 @@ class IdentityManagerTests { every { mockDeviceCache.getLegacyCachedAppUserID() } returns null every { mockDeviceCache.clearCachesForAppUserID(identifiedUserID) } just Runs every { mockSubscriberAttributesCache.clearSubscriberAttributesIfSyncedForSubscriber(identifiedUserID) } just Runs - every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(identifiedUserID) } just Runs + every { mockSubscriberAttributesCache.cleanUpSubscriberAttributeCache(identifiedUserID, any()) } just Runs every { mockBackend.clearCaches() } just Runs } @@ -714,7 +727,8 @@ class IdentityManagerTests { subscriberAttributesManager, offeringsCache, backend, - offlineEntitlementsManager + offlineEntitlementsManager, + SyncDispatcher() ) } diff --git a/purchases/src/test/java/com/revenuecat/purchases/subscriberattributes/SubscriberAttributesDeviceCacheTests.kt b/purchases/src/test/java/com/revenuecat/purchases/subscriberattributes/SubscriberAttributesDeviceCacheTests.kt index cabce5f086..0caaa6ae07 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/subscriberattributes/SubscriberAttributesDeviceCacheTests.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/subscriberattributes/SubscriberAttributesDeviceCacheTests.kt @@ -336,7 +336,7 @@ class SubscriberAttributesDeviceCacheTests { mockNotEmptyCache(expectedAttributes) - underTest.cleanUpSubscriberAttributeCache(appUserID) + underTest.cleanUpSubscriberAttributeCache(appUserID, mockEditor) verify(exactly = 0) { mockEditor.remove(any()) } assertCapturedEqualsExpected(mapOf(appUserID to expectedAttributes)) @@ -368,7 +368,7 @@ class SubscriberAttributesDeviceCacheTests { userThree to createMapOfUnsyncedAttributes() ) mockNotEmptyCacheMultipleUsers(cacheContents) - underTest.cleanUpSubscriberAttributeCache(appUserID) + underTest.cleanUpSubscriberAttributeCache(appUserID, mockEditor) verify(exactly = 1) { mockEditor.remove(underTest.legacySubscriberAttributesCacheKey(userOne)) @@ -417,7 +417,7 @@ class SubscriberAttributesDeviceCacheTests { ) mockNotEmptyCacheMultipleUsers(cacheContents) - underTest.cleanUpSubscriberAttributeCache(appUserID) + underTest.cleanUpSubscriberAttributeCache(appUserID, mockEditor) verify(exactly = 1) { mockEditor.remove(underTest.legacySubscriberAttributesCacheKey(userOne)) @@ -458,7 +458,7 @@ class SubscriberAttributesDeviceCacheTests { ) mockEmptyLegacyCache() mockNotEmptyCacheMultipleUsers(cacheContents) - underTest.cleanUpSubscriberAttributeCache(appUserID) + underTest.cleanUpSubscriberAttributeCache(appUserID, mockEditor) assertCapturedEqualsExpected( mapOf( appUserID to expectedAttributes diff --git a/purchases/src/testDefaults/kotlin/com/revenuecat/purchases/attributes/SubscriberAttributesPurchasesTests.kt b/purchases/src/testDefaults/kotlin/com/revenuecat/purchases/attributes/SubscriberAttributesPurchasesTests.kt index a1949f7972..534cd15812 100644 --- a/purchases/src/testDefaults/kotlin/com/revenuecat/purchases/attributes/SubscriberAttributesPurchasesTests.kt +++ b/purchases/src/testDefaults/kotlin/com/revenuecat/purchases/attributes/SubscriberAttributesPurchasesTests.kt @@ -105,6 +105,7 @@ class SubscriberAttributesPurchasesTests { paywallEventsManager = null, paywallPresentedCache = PaywallPresentedCache(), purchasesStateCache = PurchasesStateCache(PurchasesState()), + dispatcher = SyncDispatcher(), ) underTest = Purchases(purchasesOrchestrator)