@@ -7,7 +7,11 @@ import android.os.Build
77import com.android.billingclient.api.BillingClient
88import com.android.billingclient.api.Purchase
99import com.qonversion.android.sdk.dto.QPurchaseOptions
10+ import com.qonversion.android.sdk.dto.QonversionError
11+ import com.qonversion.android.sdk.dto.QonversionErrorCode
12+ import com.qonversion.android.sdk.listeners.QonversionEntitlementsCallback
1013import com.qonversion.android.sdk.listeners.QonversionLaunchCallback
14+ import com.qonversion.android.sdk.internal.api.RequestTrigger
1115import com.qonversion.android.sdk.internal.billing.BillingError
1216import com.qonversion.android.sdk.internal.billing.QonversionBillingService
1317import com.qonversion.android.sdk.internal.dto.QLaunchResult
@@ -153,6 +157,107 @@ internal class QProductCenterManagerTest {
153157 )
154158 }
155159
160+ // User switching on restore tests
161+
162+ @Test
163+ fun `restore with same uid should not trigger user switch` () {
164+ val currentUid = " user_old"
165+ val launchResult = QLaunchResult (currentUid, Date (), offerings = null )
166+
167+ every { mockUserInfoService.obtainUserId() } returns currentUid
168+ mockRestoreFlow(launchResult)
169+
170+ val callback = mockk<QonversionEntitlementsCallback >(relaxed = true )
171+ productCenterManager.restore(RequestTrigger .Restore , callback)
172+
173+ verify(exactly = 0 ) { mockUserInfoService.storeQonversionUserId(any()) }
174+ verify(exactly = 0 ) { mockRemoteConfigManager.onUserUpdate() }
175+ verify(exactly = 0 ) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
176+ verify { callback.onSuccess(any()) }
177+ }
178+
179+ @Test
180+ fun `restore with different uid should trigger user switch` () {
181+ val currentUid = " user_new"
182+ val originalOwnerUid = " user_old"
183+ val launchResult = QLaunchResult (originalOwnerUid, Date (), offerings = null )
184+
185+ every { mockUserInfoService.obtainUserId() } returns currentUid
186+ mockRestoreFlow(launchResult)
187+
188+ val callback = mockk<QonversionEntitlementsCallback >(relaxed = true )
189+ productCenterManager.restore(RequestTrigger .Restore , callback)
190+
191+ verifyOrder {
192+ mockUserInfoService.storeQonversionUserId(originalOwnerUid)
193+ mockConfig.uid = originalOwnerUid
194+ mockRemoteConfigManager.onUserUpdate()
195+ mockLaunchResultCacheWrapper.clearPermissionsCache()
196+ }
197+ verify { callback.onSuccess(any()) }
198+ verify { mockLogger.debug(match { it.contains(" User switch detected" ) }) }
199+ }
200+
201+ @Test
202+ fun `restore with error should not trigger user switch` () {
203+ every { mockBillingService.queryPurchases(any(), captureLambda()) } answers {
204+ lambda< (List <Purchase >) -> Unit > ().captured.invoke(
205+ listOf (mockPurchase(Purchase .PurchaseState .PURCHASED , false ))
206+ )
207+ }
208+ every { mockBillingService.consumePurchases(any()) } just Runs
209+
210+ val callbackSlot = slot<QonversionLaunchCallback >()
211+ every {
212+ mockRepository.restore(any(), any(), any(), capture(callbackSlot))
213+ } answers {
214+ callbackSlot.captured.onError(
215+ QonversionError (QonversionErrorCode .BackendError )
216+ )
217+ }
218+
219+ val callback = mockk<QonversionEntitlementsCallback >(relaxed = true )
220+ productCenterManager.restore(RequestTrigger .Restore , callback)
221+
222+ verify(exactly = 0 ) { mockUserInfoService.storeQonversionUserId(any()) }
223+ verify(exactly = 0 ) { mockRemoteConfigManager.onUserUpdate() }
224+ verify(exactly = 0 ) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
225+ verify { callback.onError(any()) }
226+ }
227+
228+ @Test
229+ fun `restore with empty uid in response should not trigger user switch` () {
230+ val currentUid = " user_current"
231+ val launchResult = QLaunchResult (" " , Date (), offerings = null )
232+
233+ every { mockUserInfoService.obtainUserId() } returns currentUid
234+ mockRestoreFlow(launchResult)
235+
236+ val callback = mockk<QonversionEntitlementsCallback >(relaxed = true )
237+ productCenterManager.restore(RequestTrigger .Restore , callback)
238+
239+ verify(exactly = 0 ) { mockUserInfoService.storeQonversionUserId(any()) }
240+ verify(exactly = 0 ) { mockRemoteConfigManager.onUserUpdate() }
241+ verify(exactly = 0 ) { mockLaunchResultCacheWrapper.clearPermissionsCache() }
242+ verify { callback.onSuccess(any()) }
243+ }
244+
245+ private fun mockRestoreFlow (launchResult : QLaunchResult ) {
246+ every { mockBillingService.queryPurchases(any(), captureLambda()) } answers {
247+ lambda< (List <Purchase >) -> Unit > ().captured.invoke(
248+ listOf (mockPurchase(Purchase .PurchaseState .PURCHASED , false ))
249+ )
250+ }
251+ every { mockBillingService.consumePurchases(any()) } just Runs
252+
253+ val callbackSlot = slot<QonversionLaunchCallback >()
254+ every {
255+ mockRepository.restore(any(), any(), any(), capture(callbackSlot))
256+ } answers {
257+ callbackSlot.captured.onSuccess(launchResult)
258+ }
259+ }
260+
156261 private fun mockPurchase (
157262 @Purchase.PurchaseState purchaseState : Int ,
158263 isAcknowledged : Boolean
0 commit comments