From 95096894d09913e445c4b5ab396d7ed2b3b778c4 Mon Sep 17 00:00:00 2001 From: Rodrigo Gomez Palacio Date: Tue, 24 Sep 2024 15:52:10 -0500 Subject: [PATCH] Update User & Subscription operation executors to set the tokens Motivation: the executors call the respective backend services who's result will include the token value. We then hold in memory via `setRywToken` --- .../SubscriptionOperationExecutor.kt | 14 +- .../executors/UpdateUserOperationExecutor.kt | 50 +- .../SubscriptionOperationExecutorTests.kt | 1360 +++++++++-------- .../UpdateUserOperationExecutorTests.kt | 676 ++++---- 4 files changed, 1131 insertions(+), 969 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt index 73ee000745..39c7c95fb4 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt @@ -6,6 +6,8 @@ import com.onesignal.common.DeviceUtils import com.onesignal.common.NetworkUtils import com.onesignal.common.OneSignalUtils import com.onesignal.common.RootToolsInternalMethods +import com.onesignal.common.consistency.enums.IamFetchRywTokenKey +import com.onesignal.common.consistency.models.IConsistencyManager import com.onesignal.common.exceptions.BackendException import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.core.internal.application.IApplicationService @@ -39,6 +41,7 @@ internal class SubscriptionOperationExecutor( private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, + private val _consistencyManager: IConsistencyManager, ) : IOperationExecutor { override val operations: List get() = listOf(CREATE_SUBSCRIPTION, UPDATE_SUBSCRIPTION, DELETE_SUBSCRIPTION, TRANSFER_SUBSCRIPTION) @@ -101,7 +104,7 @@ internal class SubscriptionOperationExecutor( AndroidUtils.getAppVersion(_applicationService.appContext), ) - val backendSubscriptionId = + val result = _subscriptionBackend.createSubscription( createOperation.appId, IdentityConstants.ONESIGNAL_ID, @@ -109,6 +112,11 @@ internal class SubscriptionOperationExecutor( subscription, ) ?: return ExecutionResponse(ExecutionResult.SUCCESS) + val backendSubscriptionId = result.first + val rywToken = result.second + + _consistencyManager.setRywToken(createOperation.onesignalId, IamFetchRywTokenKey.SUBSCRIPTION, rywToken) + // update the subscription model with the new ID, if it's still active. val subscriptionModel = _subscriptionModelStore.get(createOperation.subscriptionId) subscriptionModel?.setStringProperty( @@ -175,7 +183,8 @@ internal class SubscriptionOperationExecutor( AndroidUtils.getAppVersion(_applicationService.appContext), ) - _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription) + val rywToken = _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription) + _consistencyManager.setRywToken(startingOperation.onesignalId, IamFetchRywTokenKey.SUBSCRIPTION, rywToken) } catch (ex: BackendException) { val responseType = NetworkUtils.getResponseStatusType(ex.statusCode) @@ -216,6 +225,7 @@ internal class SubscriptionOperationExecutor( return ExecutionResponse(ExecutionResult.SUCCESS) } + // TODO: whenever the end-user changes users, we need to add the read-your-write token here, currently no code to handle the re-fetch IAMs private suspend fun transferSubscription(startingOperation: TransferSubscriptionOperation): ExecutionResponse { try { _subscriptionBackend.transferSubscription( diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt index e3d2a7425f..973f3dc08d 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt @@ -1,6 +1,8 @@ package com.onesignal.user.internal.operations.impl.executors import com.onesignal.common.NetworkUtils +import com.onesignal.common.consistency.enums.IamFetchRywTokenKey +import com.onesignal.common.consistency.models.IConsistencyManager import com.onesignal.common.exceptions.BackendException import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.core.internal.operations.ExecutionResponse @@ -31,12 +33,13 @@ internal class UpdateUserOperationExecutor( private val _propertiesModelStore: PropertiesModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, + private val _consistencyManager: IConsistencyManager, ) : IOperationExecutor { override val operations: List get() = listOf(SET_TAG, DELETE_TAG, SET_PROPERTY, TRACK_SESSION_START, TRACK_SESSION_END, TRACK_PURCHASE) - override suspend fun execute(ops: List): ExecutionResponse { - Logging.log(LogLevel.DEBUG, "UpdateUserOperationExecutor(operation: $ops)") + override suspend fun execute(operations: List): ExecutionResponse { + Logging.log(LogLevel.DEBUG, "UpdateUserOperationExecutor(operation: $operations)") var appId: String? = null var onesignalId: String? = null @@ -45,7 +48,7 @@ internal class UpdateUserOperationExecutor( var deltasObject = PropertiesDeltasObject() var refreshDeviceMetadata = false - for (operation in ops) { + for (operation in operations) { when (operation) { is SetTagOperation -> { if (appId == null) { @@ -83,7 +86,8 @@ internal class UpdateUserOperationExecutor( // that exist in this group. val sessionCount = if (deltasObject.sessionCount != null) deltasObject.sessionCount!! + 1 else 1 - deltasObject = PropertiesDeltasObject(deltasObject.sessionTime, sessionCount, deltasObject.amountSpent, deltasObject.purchases) + deltasObject = + PropertiesDeltasObject(deltasObject.sessionTime, sessionCount, deltasObject.amountSpent, deltasObject.purchases) refreshDeviceMetadata = true } is TrackSessionEndOperation -> { @@ -94,9 +98,15 @@ internal class UpdateUserOperationExecutor( // The session time we pass up is the total session time across all `TrackSessionEndOperation` // operations that exist in this group. - val sessionTime = if (deltasObject.sessionTime != null) deltasObject.sessionTime!! + operation.sessionTime else operation.sessionTime + val sessionTime = + if (deltasObject.sessionTime != null) { + deltasObject.sessionTime!! + operation.sessionTime + } else { + operation.sessionTime + } - deltasObject = PropertiesDeltasObject(sessionTime, deltasObject.sessionCount, deltasObject.amountSpent, deltasObject.purchases) + deltasObject = + PropertiesDeltasObject(sessionTime, deltasObject.sessionCount, deltasObject.amountSpent, deltasObject.purchases) } is TrackPurchaseOperation -> { if (appId == null) { @@ -107,7 +117,12 @@ internal class UpdateUserOperationExecutor( // The amount spent we pass up is the total amount spent across all `TrackPurchaseOperation` // operations that exist in this group, while the purchases is the union of all // `TrackPurchaseOperation` operations that exist in this group. - val amountSpent = if (deltasObject.amountSpent != null) deltasObject.amountSpent!! + operation.amountSpent else operation.amountSpent + val amountSpent = + if (deltasObject.amountSpent != null) { + deltasObject.amountSpent!! + operation.amountSpent + } else { + operation.amountSpent + } val purchasesArray = if (deltasObject.purchases != null) deltasObject.purchases!!.toMutableList() else mutableListOf() for (purchase in operation.purchases) { @@ -122,18 +137,21 @@ internal class UpdateUserOperationExecutor( if (appId != null && onesignalId != null) { try { - _userBackend.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - onesignalId, - propertiesObject, - refreshDeviceMetadata, - deltasObject, - ) + val rywToken = + _userBackend.updateUser( + appId, + IdentityConstants.ONESIGNAL_ID, + onesignalId, + propertiesObject, + refreshDeviceMetadata, + deltasObject, + ) + + _consistencyManager.setRywToken(onesignalId, IamFetchRywTokenKey.USER, rywToken) if (_identityModelStore.model.onesignalId == onesignalId) { // go through and make sure any properties are in the correct model state - for (operation in ops) { + for (operation in operations) { when (operation) { is SetTagOperation -> _propertiesModelStore.model.tags.setStringProperty( diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt index 37c290855e..87d5f4a0bd 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt @@ -1,6 +1,8 @@ package com.onesignal.user.internal.operations import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import com.onesignal.common.consistency.enums.IamFetchRywTokenKey +import com.onesignal.common.consistency.models.IConsistencyManager import com.onesignal.common.exceptions.BackendException import com.onesignal.core.internal.operations.ExecutionResult import com.onesignal.core.internal.operations.Operation @@ -18,6 +20,7 @@ import com.onesignal.user.internal.subscriptions.SubscriptionStatus import com.onesignal.user.internal.subscriptions.SubscriptionType import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -27,664 +30,741 @@ import io.mockk.runs import io.mockk.verify @RobolectricTest -class SubscriptionOperationExecutorTests : FunSpec({ - val appId = "appId" - val remoteOneSignalId = "remote-onesignalId" - val localSubscriptionId = "local-subscriptionId1" - val remoteSubscriptionId = "remote-subscriptionId1" - - test("create subscription successfully creates subscription") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns remoteSubscriptionId - - val mockSubscriptionsModelStore = mockk() - val subscriptionModel1 = SubscriptionModel() - subscriptionModel1.id = localSubscriptionId - every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 - - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - CreateSubscriptionOperation( - appId, - remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - subscriptionModel1.id shouldBe remoteSubscriptionId - coVerify(exactly = 1) { - mockSubscriptionBackendService.createSubscription( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) +class SubscriptionOperationExecutorTests : + FunSpec({ + val appId = "appId" + val remoteOneSignalId = "remote-onesignalId" + val localSubscriptionId = "local-subscriptionId1" + val remoteSubscriptionId = "remote-subscriptionId1" + val rywToken = "1" + val mockConsistencyManager = mockk() + + beforeTest { + clearMocks(mockConsistencyManager) + coEvery { mockConsistencyManager.setRywToken(any(), any(), any()) } just runs } - } - - test("create subscription fails with retry when there is a network condition") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(408, retryAfterSeconds = 10) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - CreateSubscriptionOperation( + + test("create subscription successfully creates subscription") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns + Pair(remoteSubscriptionId, rywToken) + + val mockSubscriptionsModelStore = mockk() + val subscriptionModel1 = SubscriptionModel() + subscriptionModel1.id = localSubscriptionId + every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + subscriptionModel1.id shouldBe remoteSubscriptionId + coVerify(exactly = 1) { + mockSubscriptionBackendService.createSubscription( appId, + IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - response.retryAfterSeconds shouldBe 10 - coVerify(exactly = 1) { - mockSubscriptionBackendService.createSubscription( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("create subscription fails without retry when there is a backend error") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } answers { null } - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - CreateSubscriptionOperation( + + test("create subscription fails with retry when there is a network condition") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws + BackendException(408, retryAfterSeconds = 10) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + response.retryAfterSeconds shouldBe 10 + coVerify(exactly = 1) { + mockSubscriptionBackendService.createSubscription( appId, + IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_NORETRY - coVerify(exactly = 1) { - mockSubscriptionBackendService.createSubscription( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("create subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } - val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - newRecordState, - ) - - val operations = - listOf( - CreateSubscriptionOperation( - appId, - remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - } - - test("create subscription then delete subscription is a successful no-op") { - // Given - val mockSubscriptionBackendService = mockk() - - val mockSubscriptionsModelStore = mockk() - val subscriptionModel1 = SubscriptionModel() - subscriptionModel1.id = localSubscriptionId - every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 - - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - CreateSubscriptionOperation( - appId, - remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED, - ), - DeleteSubscriptionOperation(appId, remoteOneSignalId, localSubscriptionId), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - } - - test("create subscription then update subscription successfully creates subscription") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns remoteSubscriptionId - - val mockSubscriptionsModelStore = mockk() - val subscriptionModel1 = SubscriptionModel() - subscriptionModel1.id = localSubscriptionId - every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 - - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - CreateSubscriptionOperation( - appId, - remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken1", - SubscriptionStatus.SUBSCRIBED, - ), - UpdateSubscriptionOperation( + + test("create subscription fails without retry when there is a backend error") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } answers { null } + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_NORETRY + coVerify(exactly = 1) { + mockSubscriptionBackendService.createSubscription( appId, + IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - localSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - subscriptionModel1.id shouldBe remoteSubscriptionId - coVerify(exactly = 1) { - mockSubscriptionBackendService.createSubscription( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken2" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("update subscription successfully updates subscription") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } just runs - - val mockSubscriptionsModelStore = mockk() - val subscriptionModel1 = SubscriptionModel() - subscriptionModel1.id = remoteSubscriptionId - subscriptionModel1.address = "pushToken1" - every { mockSubscriptionsModelStore.get(remoteSubscriptionId) } returns subscriptionModel1 - - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - UpdateSubscriptionOperation( - appId, - remoteOneSignalId, - remoteSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED, - ), - UpdateSubscriptionOperation( + + test("create subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } + val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + newRecordState, + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + } + + test("create subscription then delete subscription is a successful no-op") { + // Given + val mockSubscriptionBackendService = mockk() + + val mockSubscriptionsModelStore = mockk() + val subscriptionModel1 = SubscriptionModel() + subscriptionModel1.id = localSubscriptionId + every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken", + SubscriptionStatus.SUBSCRIBED, + ), + DeleteSubscriptionOperation(appId, remoteOneSignalId, localSubscriptionId), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + } + + test("create subscription then update subscription successfully creates subscription") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns + Pair(remoteSubscriptionId, rywToken) + + val mockSubscriptionsModelStore = mockk() + val subscriptionModel1 = SubscriptionModel() + subscriptionModel1.id = localSubscriptionId + every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + CreateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken1", + SubscriptionStatus.SUBSCRIBED, + ), + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + localSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + subscriptionModel1.id shouldBe remoteSubscriptionId + coVerify(exactly = 1) { + mockSubscriptionBackendService.createSubscription( appId, + IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - remoteSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken3", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockSubscriptionBackendService.updateSubscription( - appId, - remoteSubscriptionId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken3" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("update subscription fails with retry when there is a network condition") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(408) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - UpdateSubscriptionOperation( + + test("update subscription successfully updates subscription") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } returns rywToken + + val mockSubscriptionsModelStore = mockk() + val subscriptionModel1 = + SubscriptionModel().apply { + id = remoteSubscriptionId + address = "pushToken1" + } + every { mockSubscriptionsModelStore.get(remoteSubscriptionId) } returns subscriptionModel1 + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken3", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockSubscriptionBackendService.updateSubscription( appId, - remoteOneSignalId, remoteSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - coVerify(exactly = 1) { - mockSubscriptionBackendService.updateSubscription( - appId, - remoteSubscriptionId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken2" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken3" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("update subscription fails without retry when there is a backend error") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - UpdateSubscriptionOperation( + + test("update subscription fails with retry when there is a network condition") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(408) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + coVerify(exactly = 1) { + mockSubscriptionBackendService.updateSubscription( appId, - remoteOneSignalId, remoteSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_NORETRY - coVerify(exactly = 1) { - mockSubscriptionBackendService.updateSubscription( - appId, - remoteSubscriptionId, - withArg { - it.type shouldBe SubscriptionObjectType.ANDROID_PUSH - it.enabled shouldBe true - it.token shouldBe "pushToken2" - it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value - }, - ) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } } - } - - test("update subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } - val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - newRecordState, - ) - - val operations = - listOf( - UpdateSubscriptionOperation( + + test("update subscription fails without retry when there is a backend error") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_NORETRY + coVerify(exactly = 1) { + mockSubscriptionBackendService.updateSubscription( appId, - remoteOneSignalId, remoteSubscriptionId, - SubscriptionType.PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED, - ), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - } - - test("delete subscription successfully deletes subscription") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } just runs - - val mockSubscriptionsModelStore = mockk() - every { mockSubscriptionsModelStore.remove(any(), any()) } just runs - - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } - verify(exactly = 1) { mockSubscriptionsModelStore.remove(remoteSubscriptionId, any()) } - } - - test("delete subscription fails with retry when there is a network condition") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(408) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } - } - - // If we get a 404 then the subscription has already been deleted, - // so we count it as successful - test("delete subscription is successful if there is a 404") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - getNewRecordState(), - ) - - val operations = - listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } - } - - test("delete subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { - // Given - val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(404) - - val mockSubscriptionsModelStore = mockk() - val mockBuildUserService = mockk() - val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } - val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } - - val subscriptionOperationExecutor = - SubscriptionOperationExecutor( - mockSubscriptionBackendService, - MockHelper.deviceService(), - AndroidMockHelper.applicationService(), - mockSubscriptionsModelStore, - MockHelper.configModelStore(), - mockBuildUserService, - newRecordState, - ) - - val operations = - listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), - ) - - // When - val response = subscriptionOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - } -}) + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + }, + ) + } + } + + test("update subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } + val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + newRecordState, + mockConsistencyManager, + ) + + val operations = + listOf( + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + } + + test("delete subscription successfully deletes subscription") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } just runs + + val mockSubscriptionsModelStore = mockk() + every { mockSubscriptionsModelStore.remove(any(), any()) } just runs + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } + verify(exactly = 1) { mockSubscriptionsModelStore.remove(remoteSubscriptionId, any()) } + } + + test("delete subscription fails with retry when there is a network condition") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(408) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } + } + + // If we get a 404 then the subscription has already been deleted, + // so we count it as successful + test("delete subscription is successful if there is a 404") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { mockSubscriptionBackendService.deleteSubscription(appId, remoteSubscriptionId) } + } + + test("delete subscription fails with retry when the backend returns MISSING, when isInMissingRetryWindow") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(404) + + val mockSubscriptionsModelStore = mockk() + val mockBuildUserService = mockk() + val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } + val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + newRecordState, + mockConsistencyManager, + ) + + val operations = + listOf( + DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + ) + + // When + val response = subscriptionOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + } + + test("setRywToken is called after successful subscription update") { + // Given + val mockSubscriptionBackendService = mockk() + coEvery { + mockSubscriptionBackendService.updateSubscription(any(), any(), any()) + } returns rywToken + + val mockSubscriptionsModelStore = mockk() + val subscriptionModel1 = + SubscriptionModel().apply { + id = remoteSubscriptionId + address = "pushToken1" + } + every { mockSubscriptionsModelStore.get(remoteSubscriptionId) } returns subscriptionModel1 + + val mockBuildUserService = mockk() + + val subscriptionOperationExecutor = + SubscriptionOperationExecutor( + mockSubscriptionBackendService, + MockHelper.deviceService(), + AndroidMockHelper.applicationService(), + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + UpdateSubscriptionOperation( + appId, + remoteOneSignalId, + remoteSubscriptionId, + SubscriptionType.PUSH, + true, + "pushToken2", + SubscriptionStatus.SUBSCRIBED, + ), + ) + + subscriptionOperationExecutor.execute(operations) + + // Then + coVerify(exactly = 1) { + mockConsistencyManager.setRywToken(remoteOneSignalId, IamFetchRywTokenKey.SUBSCRIPTION, rywToken) + } + } + }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt index 529bb22a6f..4c511e3758 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt @@ -1,5 +1,7 @@ package com.onesignal.user.internal.operations +import com.onesignal.common.consistency.enums.IamFetchRywTokenKey +import com.onesignal.common.consistency.models.IConsistencyManager import com.onesignal.common.exceptions.BackendException import com.onesignal.core.internal.operations.ExecutionResult import com.onesignal.core.internal.operations.Operation @@ -13,6 +15,7 @@ import com.onesignal.user.internal.properties.PropertiesModel import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.mockk.clearMocks import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -21,322 +24,373 @@ import io.mockk.mockk import io.mockk.runs import java.math.BigDecimal -class UpdateUserOperationExecutorTests : FunSpec({ - val appId = "appId" - val localOneSignalId = "local-onesignalId" - val remoteOneSignalId = "remote-onesignalId" - - test("update user single operation is successful") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } just runs - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockUserBackendService.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.tags shouldBe mapOf("tagKey1" to "tagValue1") - }, - any(), - any(), - ) +class UpdateUserOperationExecutorTests : + FunSpec({ + val appId = "appId" + val localOneSignalId = "local-onesignalId" + val remoteOneSignalId = "remote-onesignalId" + val rywToken = "1" + val mockConsistencyManager = mockk() + + beforeTest { + clearMocks(mockConsistencyManager) + coEvery { mockConsistencyManager.setRywToken(any(), any(), any()) } just runs } - } - - test("update user multiple property operations are successful") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } just runs - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = - listOf( - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-1"), - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-2"), - SetTagOperation(appId, remoteOneSignalId, "tagKey2", "tagValue2"), - SetTagOperation(appId, remoteOneSignalId, "tagKey3", "tagValue3"), - DeleteTagOperation(appId, remoteOneSignalId, "tagKey3"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang1"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang2"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::timezone.name, "timezone"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::country.name, "country"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLatitude.name, 123.45), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLongitude.name, 678.90), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationType.name, 1), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationAccuracy.name, 0.15), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationBackground.name, true), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationTimestamp.name, 1111L), - ) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockUserBackendService.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.tags shouldBe mapOf("tagKey1" to "tagValue1-2", "tagKey2" to "tagValue2", "tagKey3" to null) - it.country shouldBe "country" - it.language shouldBe "lang2" - it.timezoneId shouldBe "timezone" - it.latitude shouldBe 123.45 - it.longitude shouldBe 678.90 - }, - any(), - any(), - ) + + test("update user single operation is successful") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } returns rywToken + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockUserBackendService.updateUser( + appId, + IdentityConstants.ONESIGNAL_ID, + remoteOneSignalId, + withArg { + it.tags shouldBe mapOf("tagKey1" to "tagValue1") + }, + any(), + any(), + ) + } } - } - - test("update user single property delta operations is successful") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } just runs - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = - listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), - ) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockUserBackendService.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.tags shouldBe null - }, - any(), - withArg { - it.sessionTime shouldBe 1111 - }, - ) + + test("update user multiple property operations are successful") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } returns rywToken + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = + listOf( + SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-1"), + SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-2"), + SetTagOperation(appId, remoteOneSignalId, "tagKey2", "tagValue2"), + SetTagOperation(appId, remoteOneSignalId, "tagKey3", "tagValue3"), + DeleteTagOperation(appId, remoteOneSignalId, "tagKey3"), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang1"), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang2"), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::timezone.name, "timezone"), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::country.name, "country"), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLatitude.name, 123.45), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLongitude.name, 678.90), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationType.name, 1), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationAccuracy.name, 0.15), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationBackground.name, true), + SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationTimestamp.name, 1111L), + ) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockUserBackendService.updateUser( + appId, + IdentityConstants.ONESIGNAL_ID, + remoteOneSignalId, + withArg { + it.tags shouldBe mapOf("tagKey1" to "tagValue1-2", "tagKey2" to "tagValue2", "tagKey3" to null) + it.country shouldBe "country" + it.language shouldBe "lang2" + it.timezoneId shouldBe "timezone" + it.latitude shouldBe 123.45 + it.longitude shouldBe 678.90 + }, + any(), + any(), + ) + } } - } - - test("update user multiple property delta operations are successful") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } just runs - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = - listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), - TrackPurchaseOperation( + + test("update user single property delta operations is successful") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } returns rywToken + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = + listOf( + TrackSessionEndOperation(appId, remoteOneSignalId, 1111), + ) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockUserBackendService.updateUser( appId, + IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - false, - BigDecimal(2222), - listOf( - PurchaseInfo("sku1", "iso1", BigDecimal(1000)), - PurchaseInfo("sku2", "iso2", BigDecimal(1222)), + withArg { + it.tags shouldBe null + }, + any(), + withArg { + it.sessionTime shouldBe 1111 + }, + ) + } + } + + test("update user multiple property delta operations are successful") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } returns rywToken + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = + listOf( + TrackSessionEndOperation(appId, remoteOneSignalId, 1111), + TrackPurchaseOperation( + appId, + remoteOneSignalId, + false, + BigDecimal(2222), + listOf( + PurchaseInfo("sku1", "iso1", BigDecimal(1000)), + PurchaseInfo("sku2", "iso2", BigDecimal(1222)), + ), ), - ), - TrackSessionEndOperation(appId, remoteOneSignalId, 3333), - ) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockUserBackendService.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.tags shouldBe null - }, - any(), - withArg { - it.sessionTime shouldBe (1111 + 3333) - it.amountSpent shouldBe BigDecimal(2222) - it.purchases shouldNotBe null - it.purchases!!.count() shouldBe 2 - it.purchases!![0].sku shouldBe "sku1" - it.purchases!![0].iso shouldBe "iso1" - it.purchases!![0].amount shouldBe BigDecimal(1000) - it.purchases!![1].sku shouldBe "sku2" - it.purchases!![1].iso shouldBe "iso2" - it.purchases!![1].amount shouldBe BigDecimal(1222) - }, - ) + TrackSessionEndOperation(appId, remoteOneSignalId, 3333), + ) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockUserBackendService.updateUser( + appId, + IdentityConstants.ONESIGNAL_ID, + remoteOneSignalId, + withArg { + it.tags shouldBe null + }, + any(), + withArg { + it.sessionTime shouldBe (1111 + 3333) + it.amountSpent shouldBe BigDecimal(2222) + it.purchases shouldNotBe null + it.purchases!!.count() shouldBe 2 + it.purchases!![0].sku shouldBe "sku1" + it.purchases!![0].iso shouldBe "iso1" + it.purchases!![0].amount shouldBe BigDecimal(1000) + it.purchases!![1].sku shouldBe "sku2" + it.purchases!![1].iso shouldBe "iso2" + it.purchases!![1].amount shouldBe BigDecimal(1222) + }, + ) + } + } + + test("update user with both property and property delta operations are successful") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } returns rywToken + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = + listOf( + TrackSessionEndOperation(appId, remoteOneSignalId, 1111), + SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"), + TrackSessionEndOperation(appId, remoteOneSignalId, 3333), + ) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.SUCCESS + coVerify(exactly = 1) { + mockUserBackendService.updateUser( + appId, + IdentityConstants.ONESIGNAL_ID, + remoteOneSignalId, + withArg { + it.tags shouldBe mapOf("tagKey1" to "tagValue1") + }, + any(), + withArg { + it.sessionTime shouldBe (1111 + 3333) + }, + ) + } } - } - - test("update user with both property and property delta operations are successful") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } just runs - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = - listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"), - TrackSessionEndOperation(appId, remoteOneSignalId, 3333), - ) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { - mockUserBackendService.updateUser( - appId, - IdentityConstants.ONESIGNAL_ID, - remoteOneSignalId, - withArg { - it.tags shouldBe mapOf("tagKey1" to "tagValue1") - }, - any(), - withArg { - it.sessionTime shouldBe (1111 + 3333) - }, - ) + + test("update user single operation fails with MISSING") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws BackendException(404) + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } returns null + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_NORETRY + } + + test("update user single operation fails with MISSING, but isInMissingRetryWindow") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws + BackendException(404, retryAfterSeconds = 10) + + // Given + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } returns null + + val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } + val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + newRecordState, + mockConsistencyManager, + ) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + response.result shouldBe ExecutionResult.FAIL_RETRY + response.retryAfterSeconds shouldBe 10 + } + + test("setRywToken is called after successful user update of session count") { + // Given + val mockUserBackendService = mockk() + coEvery { + mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) + } returns rywToken + + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockBuildUserService = mockk() + + val loginUserOperationExecutor = + UpdateUserOperationExecutor( + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockBuildUserService, + getNewRecordState(), + mockConsistencyManager, + ) + + val operations = + listOf( + TrackSessionStartOperation(appId, onesignalId = remoteOneSignalId), + ) + + // When + loginUserOperationExecutor.execute(operations) + + // Then + coVerify(exactly = 1) { + mockConsistencyManager.setRywToken(remoteOneSignalId, IamFetchRywTokenKey.USER, rywToken) + } } - } - - test("update user single operation fails with MISSING") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws BackendException(404) - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } returns null - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - getNewRecordState(), - ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_NORETRY - } - - test("update user single operation fails with MISSING, but isInMissingRetryWindow") { - // Given - val mockUserBackendService = mockk() - coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws BackendException(404, retryAfterSeconds = 10) - - // Given - val mockIdentityModelStore = MockHelper.identityModelStore() - val mockPropertiesModelStore = MockHelper.propertiesModelStore() - val mockBuildUserService = mockk() - every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } returns null - - val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } - val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } - - val loginUserOperationExecutor = - UpdateUserOperationExecutor( - mockUserBackendService, - mockIdentityModelStore, - mockPropertiesModelStore, - mockBuildUserService, - newRecordState, - ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) - - // When - val response = loginUserOperationExecutor.execute(operations) - - // Then - response.result shouldBe ExecutionResult.FAIL_RETRY - response.retryAfterSeconds shouldBe 10 - } -}) + })