From 1eae4663be54afa7c0b5dfa21f72cd61e83347e8 Mon Sep 17 00:00:00 2001 From: Kitselyuk Egor Date: Thu, 1 Jun 2023 17:30:48 +0300 Subject: [PATCH 1/3] MBX-2529: Add mixer --- .../abtests/FixedCustomerAbMixerWrapper.kt | 17 +++++++++++++++++ .../inapp/data/validators/VariantValidator.kt | 1 - 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt new file mode 100644 index 000000000..4c28d7cd5 --- /dev/null +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt @@ -0,0 +1,17 @@ +package cloud.mindbox.mobile_sdk.abtests + +import cloud.mindbox.mobile_sdk.logger.mindboxLogW +import cloud.mindbox.mobile_sdk.repository.MindboxPreferences + +internal class FixedCustomerAbMixerWrapper(private val fallbackMixer: CustomerAbMixer) : + CustomerAbMixer { + + override fun stringModulusHash(identifier: String, salt: String): Int { + return MindboxPreferences.mixerFixedHash + .takeIf { it in 0..99 } + ?.let { + this@FixedCustomerAbMixerWrapper.mindboxLogW("Mixer use fixed hash $it!") + return it + } ?: fallbackMixer.stringModulusHash(identifier, salt) + } +} diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt index ec672504c..9f37b3d81 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt @@ -32,7 +32,6 @@ internal class VariantValidator : Validator { return false } - if (item.objects.size != 1) { mindboxLogW("The 'objects' field must be only one") return false From b5e410da8101f67fe690ed447b27a405aceed239 Mon Sep 17 00:00:00 2001 From: Kitselyuk Egor Date: Thu, 1 Jun 2023 17:30:48 +0300 Subject: [PATCH 2/3] MBX-2529: Add mixer --- .../abtests/FixedCustomerAbMixerWrapper.kt | 17 - .../mobile_sdk/abtests/InAppABTestLogic.kt | 62 +++ .../mobile_sdk/di/modules/DomainModule.kt | 11 +- .../mobile_sdk/di/modules/MindboxModule.kt | 2 + .../inapp/domain/InAppInteractorImpl.kt | 38 +- .../abtests/InAppABTestLogicTest.kt | 450 ++++++++++++++++++ 6 files changed, 547 insertions(+), 33 deletions(-) delete mode 100644 sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt create mode 100644 sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt create mode 100644 sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt deleted file mode 100644 index 4c28d7cd5..000000000 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/FixedCustomerAbMixerWrapper.kt +++ /dev/null @@ -1,17 +0,0 @@ -package cloud.mindbox.mobile_sdk.abtests - -import cloud.mindbox.mobile_sdk.logger.mindboxLogW -import cloud.mindbox.mobile_sdk.repository.MindboxPreferences - -internal class FixedCustomerAbMixerWrapper(private val fallbackMixer: CustomerAbMixer) : - CustomerAbMixer { - - override fun stringModulusHash(identifier: String, salt: String): Int { - return MindboxPreferences.mixerFixedHash - .takeIf { it in 0..99 } - ?.let { - this@FixedCustomerAbMixerWrapper.mindboxLogW("Mixer use fixed hash $it!") - return it - } ?: fallbackMixer.stringModulusHash(identifier, salt) - } -} diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt new file mode 100644 index 000000000..a8947dc4c --- /dev/null +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt @@ -0,0 +1,62 @@ +package cloud.mindbox.mobile_sdk.abtests + +import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository +import cloud.mindbox.mobile_sdk.inapp.domain.models.ABTest +import cloud.mindbox.mobile_sdk.logger.mindboxLogD +import cloud.mindbox.mobile_sdk.logger.mindboxLogW +import cloud.mindbox.mobile_sdk.repository.MindboxPreferences + +internal class InAppABTestLogic( + private val mixer: CustomerAbMixer, + private val repository: MobileConfigRepository, +) { + + suspend fun getInAppsPool(allInApps: List): Set { + val abtests = repository.getABTests() + val uuid = MindboxPreferences.deviceUuid + + if (abtests.isEmpty()) { + this@InAppABTestLogic.mindboxLogW("Abtests is empty. Skip logic.") + return allInApps.toSet() + } + if (allInApps.isEmpty()) { + this@InAppABTestLogic.mindboxLogW("Config inApps is empty. Skip logic.") + return allInApps.toSet() + } + + val inAppsForAbtest: List> = abtests.map { abtest -> + + val hash = mixer.stringModulusHash( + identifier = uuid, + salt = abtest.salt.uppercase(), + ) + + this@InAppABTestLogic.mindboxLogD("mixer calculate $hash for salt ${abtest.salt}") + + val targetBranchInApps: MutableSet = mutableSetOf() + val otherBranchInApps: MutableSet = mutableSetOf() + + abtest.variants.onEach { variant -> + val inApps: List = when (variant.kind) { + ABTest.Variant.VariantKind.ALL -> allInApps + ABTest.Variant.VariantKind.CONCRETE -> variant.inapps + } + if ((variant.lower until variant.upper).contains(hash)) { + targetBranchInApps.addAll(inApps) + this@InAppABTestLogic.mindboxLogD("Selected variant $variant for ${abtest.id}") + } else { + otherBranchInApps.addAll(inApps) + } + } + + (targetBranchInApps + (allInApps - otherBranchInApps)).also { + this@InAppABTestLogic.mindboxLogD("Selected inapps $it for ${abtest.id}") + } + } + + return inAppsForAbtest.takeIf { it.isNotEmpty() }?.reduce { acc, list -> + acc.intersect(list) + } ?: setOf() + } + +} \ No newline at end of file diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DomainModule.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DomainModule.kt index 614e1e336..2fe99b929 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DomainModule.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DomainModule.kt @@ -1,7 +1,9 @@ package cloud.mindbox.mobile_sdk.di.modules + import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixerImpl +import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic import cloud.mindbox.mobile_sdk.inapp.domain.InAppChoosingManagerImpl import cloud.mindbox.mobile_sdk.inapp.domain.InAppEventManagerImpl import cloud.mindbox.mobile_sdk.inapp.domain.InAppFilteringManagerImpl @@ -26,7 +28,8 @@ internal fun DomainModule( inAppSegmentationRepository = inAppSegmentationRepository, inAppFilteringManager = inAppFilteringManager, inAppEventManager = inAppEventManager, - inAppChoosingManager = inAppChoosingManager + inAppChoosingManager = inAppChoosingManager, + inAppABTestLogic = inAppABTestLogic, ) } @@ -44,6 +47,12 @@ internal fun DomainModule( override val inAppFilteringManager: InAppFilteringManager get() = InAppFilteringManagerImpl(inAppRepository = inAppRepository) + override val inAppABTestLogic: InAppABTestLogic + get() = InAppABTestLogic( + mixer = customerAbMixer, + repository = mobileConfigRepository + ) + override val customerAbMixer: CustomerAbMixer get() = CustomerAbMixerImpl() } \ No newline at end of file diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/MindboxModule.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/MindboxModule.kt index 7d3fbacc2..532ca6dd4 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/MindboxModule.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/MindboxModule.kt @@ -2,6 +2,7 @@ package cloud.mindbox.mobile_sdk.di.modules import android.app.Application import cloud.mindbox.mobile_sdk.abtests.CustomerAbMixer +import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper import cloud.mindbox.mobile_sdk.inapp.data.validators.ABTestValidator @@ -87,6 +88,7 @@ internal interface DomainModule : MindboxModule { val inAppEventManager: InAppEventManager val inAppFilteringManager: InAppFilteringManager val customerAbMixer: CustomerAbMixer + val inAppABTestLogic: InAppABTestLogic } internal interface ApiModule : MindboxModule { diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt index bb15eafac..ebd43c8f3 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt @@ -1,6 +1,7 @@ package cloud.mindbox.mobile_sdk.inapp.domain import cloud.mindbox.mobile_sdk.InitializeLock +import cloud.mindbox.mobile_sdk.abtests.InAppABTestLogic import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppChoosingManager import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppEventManager @@ -22,26 +23,33 @@ internal class InAppInteractorImpl( private val inAppSegmentationRepository: InAppSegmentationRepository, private val inAppFilteringManager: InAppFilteringManager, private val inAppEventManager: InAppEventManager, - private val inAppChoosingManager: InAppChoosingManager + private val inAppChoosingManager: InAppChoosingManager, + private val inAppABTestLogic: InAppABTestLogic, ) : InAppInteractor { override suspend fun processEventAndConfig(): Flow { - val inApps: List = mobileConfigRepository.getInAppsSection().let { inApps -> - inAppFilteringManager.filterNotShownInApps( - inAppRepository.getShownInApps(), - inApps - ) - }.also { unShownInApps -> - MindboxLoggerImpl.d( - this, "Filtered config has ${unShownInApps.size} inapps" - ) - inAppSegmentationRepository.unShownInApps = unShownInApps - for (inApp in unShownInApps) { - for (operation in inApp.targeting.getOperationsSet()) { - inAppRepository.saveOperationalInApp(operation.lowercase(), inApp) + val inApps: List = mobileConfigRepository.getInAppsSection() + .let { inApps -> + val inAppIds = inAppABTestLogic.getInAppsPool(inApps.map { it.id }) + inAppFilteringManager.filterABTestsInApps(inApps, inAppIds).also { filteredinApps -> + this@InAppInteractorImpl.mindboxLogD("InApps after abtest logic ${filteredinApps.map { it.id }}") + } + }.let { inApps -> + inAppFilteringManager.filterNotShownInApps( + inAppRepository.getShownInApps(), + inApps + ) + }.also { unShownInApps -> + MindboxLoggerImpl.d( + this, "Filtered config has ${unShownInApps.size} inapps" + ) + inAppSegmentationRepository.unShownInApps = unShownInApps + for (inApp in unShownInApps) { + for (operation in inApp.targeting.getOperationsSet()) { + inAppRepository.saveOperationalInApp(operation.lowercase(), inApp) + } } } - } return inAppRepository.listenInAppEvents() .filter { event -> inAppEventManager.isValidInAppEvent(event) } diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt new file mode 100644 index 000000000..decb8e083 --- /dev/null +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt @@ -0,0 +1,450 @@ +package cloud.mindbox.mobile_sdk.abtests + +import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository +import cloud.mindbox.mobile_sdk.inapp.domain.models.ABTest +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +internal class InAppABTestLogicTest { + + private val inapps1 = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "6f93e2ef-0615-4e63-9c80-24bcb9e83b83", + "b33ca779-3c99-481f-ad46-91282b0caf04", + ) + private val inapps2 = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "6f93e2ef-0615-4e63-9c80-24bcb9e83b83", + "b33ca779-3c99-481f-ad46-91282b0caf04", + "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", + ) + + private val abtest = ABTest("", null, null, "", listOf()) + private val variant = ABTest.Variant("inapps", ABTest.Variant.VariantKind.ALL, 0, 100, listOf()) + + @Test + fun `abtest logic is empty variants`() = runTest { + assertEquals(inapps1.toSet(), calculateInApps(25, listOf(), inapps1)) + } + + @Test + fun `abtest logic two variants with inapps1`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 50, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 50, + upper = 100, + kind = ABTest.Variant.VariantKind.ALL, + inapps = listOf() + ) + ) + ) + ) + + assertEquals(setOf(), calculateInApps(25, abtests, inapps1)) + assertEquals(inapps1.toSet(), calculateInApps(75, abtests, inapps1)) + + val inapps1withExtra = inapps1 + "test" + assertEquals(setOf(), calculateInApps(25, abtests, inapps1withExtra)) + assertEquals(inapps1.toSet() + "test", calculateInApps(75, abtests, inapps1withExtra)) + } + + @Test + fun `abtest logic two variants with inapps2`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 50, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 50, + upper = 100, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "b33ca779-3c99-481f-ad46-91282b0caf04" + ) + ) + ) + ) + ) + + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), + calculateInApps(0, abtests, inapps2) + ) + assertEquals( + inapps2.toSet(), + calculateInApps(99, abtests, inapps2) + ) + + val inapps2withExtra = inapps2 + "test" + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "test"), + calculateInApps(0, abtests, inapps2withExtra) + ) + assertEquals( + inapps2.toSet() + "test", + calculateInApps(99, abtests, inapps2withExtra) + ) + } + + + @Test + fun `abtest logic three variants with inapps1`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 30, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 30, + upper = 65, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "b33ca779-3c99-481f-ad46-91282b0caf04" + ) + ), + variant.copy( + lower = 65, + upper = 100, + kind = ABTest.Variant.VariantKind.ALL, + inapps = listOf() + ) + ) + ) + ) + + assertEquals( + emptySet(), + calculateInApps(1, abtests, inapps1) + ) + assertEquals( + setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04"), + calculateInApps(30, abtests, inapps1) + ) + assertEquals( + inapps1.toSet(), + calculateInApps(65, abtests, inapps1) + ) + } + + @Test + fun `abtest logic three variants with inapps2`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 27, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 27, + upper = 65, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("655f5ffa-de86-4224-a0bf-229fe208ed0d") + ), + variant.copy( + lower = 65, + upper = 100, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("b33ca779-3c99-481f-ad46-91282b0caf04") + ) + ) + ) + ) + + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), + calculateInApps(10, abtests, inapps2) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "655f5ffa-de86-4224-a0bf-229fe208ed0d"), + calculateInApps(64, abtests, inapps2) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "b33ca779-3c99-481f-ad46-91282b0caf04"), + calculateInApps(65, abtests, inapps2) + ) + + val inapps2withExtra = inapps2 + "!" + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "!"), + calculateInApps(10, abtests, inapps2withExtra) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "655f5ffa-de86-4224-a0bf-229fe208ed0d", "!"), + calculateInApps(64, abtests, inapps2withExtra) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "b33ca779-3c99-481f-ad46-91282b0caf04", "!"), + calculateInApps(65, abtests, inapps2withExtra) + ) + } + + @Test + fun `abtest logic two concrete variants with inapps1`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 99, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "b33ca779-3c99-481f-ad46-91282b0caf04" + ) + ), + variant.copy( + lower = 99, + upper = 100, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83") + ) + ) + ) + ) + + assertEquals( + setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04"), + calculateInApps(98, abtests, inapps1) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83"), + calculateInApps(99, abtests, inapps1) + ) + + val inapps1withExtra = inapps1 + "??" + assertEquals( + setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04", "??"), + calculateInApps(98, abtests, inapps1withExtra) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "??"), + calculateInApps(99, abtests, inapps1withExtra) + ) + } + + @Test + fun `abtest logic two concrete variants without inapps`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 99, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf( + "655f5ffa-de86-4224-a0bf-229fe208ed0d", + "b33ca779-3c99-481f-ad46-91282b0caf04" + ) + ), + variant.copy( + lower = 99, + upper = 100, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83") + ) + ) + ) + ) + + assertEquals( + emptySet(), + calculateInApps(0, abtests, listOf()) + ) + assertEquals( + emptySet(), + calculateInApps(99, abtests, listOf()) + ) + } + + @Test + fun `abtest logic five variants with inapps2`() = runTest { + val abtests = listOf( + abtest.copy( + variants = listOf( + variant.copy( + lower = 0, + upper = 10, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("655f5ffa-de86-4224-a0bf-229fe208ed0d") + ), + variant.copy( + lower = 10, + upper = 20, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83") + ), + variant.copy( + lower = 20, + upper = 30, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("b33ca779-3c99-481f-ad46-91282b0caf04") + ), + variant.copy( + lower = 30, + upper = 70, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("d1b312bd-aa5c-414c-a0d8-8126376a2a9b") + ), + variant.copy( + lower = 70, + upper = 100, + kind = ABTest.Variant.VariantKind.ALL, + inapps = listOf() + ) + ) + ) + ) + + assertEquals( + setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d"), + calculateInApps(5, abtests, inapps2) + ) + assertEquals( + setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83"), + calculateInApps(15, abtests, inapps2) + ) + assertEquals( + setOf("b33ca779-3c99-481f-ad46-91282b0caf04"), + calculateInApps(25, abtests, inapps2) + ) + assertEquals( + setOf("d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), + calculateInApps(35, abtests, inapps2) + ) + assertEquals( + inapps2.toSet(), + calculateInApps(75, abtests, inapps2) + ) + } + + + @Test + fun `two abtests logic with inapps1`() = runTest { + val abtests = listOf( + abtest.copy( + salt = "saLt1", + variants = listOf( + variant.copy( + lower = 0, + upper = 25, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 25, + upper = 100, + kind = ABTest.Variant.VariantKind.ALL, + inapps = listOf() + ) + ) + ), + abtest.copy( + salt = "SALT2", + variants = listOf( + variant.copy( + lower = 0, + upper = 75, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf() + ), + variant.copy( + lower = 75, + upper = 100, + kind = ABTest.Variant.VariantKind.CONCRETE, + inapps = listOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "6f93e2ef-0615-4e63-9c80-24bcb9e83b83") + ) + ) + ) + ) + + val mixer24and74 = mockk { + every { stringModulusHash(any(), "SALT1") } returns (24) + every { stringModulusHash(any(), "SALT2") } returns (74) + } + + assertEquals( + setOf(), + calculateInApps(mixer24and74, abtests, inapps1) + ) + + val mixer24and99 = mockk { + every { stringModulusHash(any(), "SALT1") } returns (24) + every { stringModulusHash(any(), "SALT2") } returns (99) + } + assertEquals( + setOf(), + calculateInApps(mixer24and99, abtests, inapps1) + ) + + val mixer99and74 = mockk { + every { stringModulusHash(any(), "SALT1") } returns (99) + every { stringModulusHash(any(), "SALT2") } returns (74) + } + assertEquals( + setOf("b33ca779-3c99-481f-ad46-91282b0caf04"), + calculateInApps(mixer99and74, abtests, inapps1) + ) + + val mixer99and99 = mockk { + every { stringModulusHash(any(), "SALT1") } returns (99) + every { stringModulusHash(any(), "SALT2") } returns (99) + } + assertEquals( + inapps1.toSet(), + calculateInApps(mixer99and99, abtests, inapps1) + ) + } + + private suspend fun calculateInApps( + hash: Int, + abtests: List, + inapps: List + ): Set { + val repository: MobileConfigRepository = mockk { + coEvery { getABTests() } returns (abtests) + } + val mixer: CustomerAbMixer = mockk { + every { stringModulusHash(any(), any()) } returns (hash) + } + return InAppABTestLogic(mixer, repository).getInAppsPool(inapps) + } + + private suspend fun calculateInApps( + mixer: CustomerAbMixer, + abtests: List, + inapps: List + ): Set { + val repository: MobileConfigRepository = mockk { + coEvery { getABTests() } returns (abtests) + } + return InAppABTestLogic(mixer, repository).getInAppsPool(inapps) + } +} \ No newline at end of file From 9f1ac773bf74f6e506cf3b90b0fc309cd1aee945 Mon Sep 17 00:00:00 2001 From: Kitselyuk Egor Date: Fri, 9 Jun 2023 20:11:03 +0300 Subject: [PATCH 3/3] MBX-2529: Follow code review --- .../mobile_sdk/abtests/InAppABTestLogic.kt | 42 +++++---- .../inapp/data/mapper/InAppMapper.kt | 1 + .../inapp/data/validators/VariantValidator.kt | 5 ++ .../inapp/domain/InAppInteractorImpl.kt | 12 ++- .../inapp/domain/models/InAppConfig.kt | 1 + .../mobile_sdk/logger/MindboxLoggerImpl.kt | 6 ++ .../operation/response/InAppConfigResponse.kt | 2 + .../abtests/InAppABTestLogicTest.kt | 86 +++++++++---------- ...obileConfigSerializationManagerImplTest.kt | 2 + .../data/validators/ABTestValidatorTest.kt | 2 + .../data/validators/VariantValidatorTest.kt | 3 + sdk/src/test/resources/abtests.json | 2 + 12 files changed, 96 insertions(+), 68 deletions(-) diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt index a8947dc4c..2d3019a46 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogic.kt @@ -2,26 +2,24 @@ package cloud.mindbox.mobile_sdk.abtests import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository import cloud.mindbox.mobile_sdk.inapp.domain.models.ABTest -import cloud.mindbox.mobile_sdk.logger.mindboxLogD -import cloud.mindbox.mobile_sdk.logger.mindboxLogW +import cloud.mindbox.mobile_sdk.logger.MindboxLog import cloud.mindbox.mobile_sdk.repository.MindboxPreferences internal class InAppABTestLogic( private val mixer: CustomerAbMixer, private val repository: MobileConfigRepository, -) { +): MindboxLog { suspend fun getInAppsPool(allInApps: List): Set { val abtests = repository.getABTests() val uuid = MindboxPreferences.deviceUuid if (abtests.isEmpty()) { - this@InAppABTestLogic.mindboxLogW("Abtests is empty. Skip logic.") + logI("Abtests is empty. Use all inApps.") return allInApps.toSet() } if (allInApps.isEmpty()) { - this@InAppABTestLogic.mindboxLogW("Config inApps is empty. Skip logic.") - return allInApps.toSet() + return emptySet() } val inAppsForAbtest: List> = abtests.map { abtest -> @@ -31,10 +29,10 @@ internal class InAppABTestLogic( salt = abtest.salt.uppercase(), ) - this@InAppABTestLogic.mindboxLogD("mixer calculate $hash for salt ${abtest.salt}") + logI("Mixer calculate hash $hash for abtest ${abtest.id} with salt ${abtest.salt} and deviceUuid $uuid") - val targetBranchInApps: MutableSet = mutableSetOf() - val otherBranchInApps: MutableSet = mutableSetOf() + val targetVariantInApps: MutableSet = mutableSetOf() + val otherVariantInApps: MutableSet = mutableSetOf() abtest.variants.onEach { variant -> val inApps: List = when (variant.kind) { @@ -42,21 +40,31 @@ internal class InAppABTestLogic( ABTest.Variant.VariantKind.CONCRETE -> variant.inapps } if ((variant.lower until variant.upper).contains(hash)) { - targetBranchInApps.addAll(inApps) - this@InAppABTestLogic.mindboxLogD("Selected variant $variant for ${abtest.id}") + targetVariantInApps.addAll(inApps) + logI("Selected variant $variant for ${abtest.id}") } else { - otherBranchInApps.addAll(inApps) + otherVariantInApps.addAll(inApps) } } - (targetBranchInApps + (allInApps - otherBranchInApps)).also { - this@InAppABTestLogic.mindboxLogD("Selected inapps $it for ${abtest.id}") + (targetVariantInApps + (allInApps - otherVariantInApps)).also { inappsSet -> + logI("For abtest ${abtest.id} determined $inappsSet") } } - return inAppsForAbtest.takeIf { it.isNotEmpty() }?.reduce { acc, list -> - acc.intersect(list) - } ?: setOf() + if (inAppsForAbtest.isEmpty()) { + logW("No inApps after calculation abtests logic. InApp will not be shown.") + return setOf() + } + + return if (inAppsForAbtest.size == 1) { + inAppsForAbtest.first() + } else { + getIntersectionForAllABTests(inAppsForAbtest) + } } + private fun getIntersectionForAllABTests(inAppsForAbtest: List>): Set = + inAppsForAbtest.reduce { acc, list -> acc.intersect(list) } + } \ No newline at end of file diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/mapper/InAppMapper.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/mapper/InAppMapper.kt index f1bb9f069..579874aa7 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/mapper/InAppMapper.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/mapper/InAppMapper.kt @@ -115,6 +115,7 @@ internal class InAppMapper { salt = dto.salt!!, variants = dto.variants?.map { variantDto -> ABTest.Variant( + id = variantDto.id, type = variantDto.objects!!.first().type!!, kind = variantDto.objects.first().kind.enumValue(), inapps = variantDto.objects.first().inapps ?: listOf(), diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt index 9f37b3d81..c16e2a04d 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidator.kt @@ -12,6 +12,11 @@ internal class VariantValidator : Validator { return false } + if (item.id.isBlank()) { + mindboxLogW("The 'id' field can not be null or empty") + return false + } + if (item.modulus == null) { mindboxLogW("The 'modulus' field can not be null") return false diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt index ebd43c8f3..532627a62 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/InAppInteractorImpl.kt @@ -12,7 +12,7 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfi import cloud.mindbox.mobile_sdk.inapp.domain.models.InApp import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppType import cloud.mindbox.mobile_sdk.inapp.domain.models.ProductSegmentationFetchStatus -import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl +import cloud.mindbox.mobile_sdk.logger.MindboxLog import cloud.mindbox.mobile_sdk.logger.mindboxLogD import cloud.mindbox.mobile_sdk.models.InAppEventType import kotlinx.coroutines.flow.* @@ -25,14 +25,14 @@ internal class InAppInteractorImpl( private val inAppEventManager: InAppEventManager, private val inAppChoosingManager: InAppChoosingManager, private val inAppABTestLogic: InAppABTestLogic, -) : InAppInteractor { +) : InAppInteractor, MindboxLog { override suspend fun processEventAndConfig(): Flow { val inApps: List = mobileConfigRepository.getInAppsSection() .let { inApps -> val inAppIds = inAppABTestLogic.getInAppsPool(inApps.map { it.id }) - inAppFilteringManager.filterABTestsInApps(inApps, inAppIds).also { filteredinApps -> - this@InAppInteractorImpl.mindboxLogD("InApps after abtest logic ${filteredinApps.map { it.id }}") + inAppFilteringManager.filterABTestsInApps(inApps, inAppIds).also { filteredInApps -> + logI("InApps after abtest logic ${filteredInApps.map { it.id }}") } }.let { inApps -> inAppFilteringManager.filterNotShownInApps( @@ -40,9 +40,7 @@ internal class InAppInteractorImpl( inApps ) }.also { unShownInApps -> - MindboxLoggerImpl.d( - this, "Filtered config has ${unShownInApps.size} inapps" - ) + logI("Filtered config has ${unShownInApps.size} inapps") inAppSegmentationRepository.unShownInApps = unShownInApps for (inApp in unShownInApps) { for (operation in inApp.targeting.getOperationsSet()) { diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/InAppConfig.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/InAppConfig.kt index 566d24d97..4504e7839 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/InAppConfig.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/InAppConfig.kt @@ -58,6 +58,7 @@ internal data class ABTest( val variants: List, ) { internal data class Variant( + val id: String, val type: String, val kind: VariantKind, val lower: Int, diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/logger/MindboxLoggerImpl.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/logger/MindboxLoggerImpl.kt index c35e552ea..ae1c037c9 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/logger/MindboxLoggerImpl.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/logger/MindboxLoggerImpl.kt @@ -129,3 +129,9 @@ internal fun Any.mindboxLogE(message: String, exception: Throwable? = null) = ex MindboxLoggerImpl.e(this, message, exception) } ?: MindboxLoggerImpl.e(this, message) +internal interface MindboxLog { + fun logD(message: String) = this.mindboxLogD(message) + fun logI(message: String) = this.mindboxLogI(message) + fun logW(message: String, exception: Throwable? = null) = this.mindboxLogW(message, exception) + fun logE(message: String, exception: Throwable? = null) = this.mindboxLogE(message, exception) +} diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/operation/response/InAppConfigResponse.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/operation/response/InAppConfigResponse.kt index 04f2f05eb..848f4e10f 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/operation/response/InAppConfigResponse.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/operation/response/InAppConfigResponse.kt @@ -37,6 +37,8 @@ internal data class ABTestDto( val variants: List?, ) { internal data class VariantDto( + @SerializedName("id") + val id: String, @SerializedName("modulus") val modulus: ModulusDto?, @SerializedName("objects") diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt index decb8e083..bff834b6e 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/abtests/InAppABTestLogicTest.kt @@ -13,12 +13,12 @@ import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) internal class InAppABTestLogicTest { - private val inapps1 = listOf( + private val threeInapps = listOf( "655f5ffa-de86-4224-a0bf-229fe208ed0d", "6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "b33ca779-3c99-481f-ad46-91282b0caf04", ) - private val inapps2 = listOf( + private val fourInapps = listOf( "655f5ffa-de86-4224-a0bf-229fe208ed0d", "6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "b33ca779-3c99-481f-ad46-91282b0caf04", @@ -26,15 +26,15 @@ internal class InAppABTestLogicTest { ) private val abtest = ABTest("", null, null, "", listOf()) - private val variant = ABTest.Variant("inapps", ABTest.Variant.VariantKind.ALL, 0, 100, listOf()) + private val variant = ABTest.Variant("", "inapps", ABTest.Variant.VariantKind.ALL, 0, 100, listOf()) @Test fun `abtest logic is empty variants`() = runTest { - assertEquals(inapps1.toSet(), calculateInApps(25, listOf(), inapps1)) + assertEquals(threeInapps.toSet(), calculateInApps(25, listOf(), threeInapps)) } @Test - fun `abtest logic two variants with inapps1`() = runTest { + fun `abtest logic two variants with config of three inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -54,16 +54,16 @@ internal class InAppABTestLogicTest { ) ) - assertEquals(setOf(), calculateInApps(25, abtests, inapps1)) - assertEquals(inapps1.toSet(), calculateInApps(75, abtests, inapps1)) + assertEquals(setOf(), calculateInApps(25, abtests, threeInapps)) + assertEquals(threeInapps.toSet(), calculateInApps(75, abtests, threeInapps)) - val inapps1withExtra = inapps1 + "test" + val inapps1withExtra = threeInapps + "test" assertEquals(setOf(), calculateInApps(25, abtests, inapps1withExtra)) - assertEquals(inapps1.toSet() + "test", calculateInApps(75, abtests, inapps1withExtra)) + assertEquals(threeInapps.toSet() + "test", calculateInApps(75, abtests, inapps1withExtra)) } @Test - fun `abtest logic two variants with inapps2`() = runTest { + fun `abtest logic two variants with config of four inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -88,27 +88,26 @@ internal class InAppABTestLogicTest { assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), - calculateInApps(0, abtests, inapps2) + calculateInApps(0, abtests, fourInapps) ) assertEquals( - inapps2.toSet(), - calculateInApps(99, abtests, inapps2) + fourInapps.toSet(), + calculateInApps(99, abtests, fourInapps) ) - val inapps2withExtra = inapps2 + "test" + val inapps2withExtra = fourInapps + "test" assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "test"), calculateInApps(0, abtests, inapps2withExtra) ) assertEquals( - inapps2.toSet() + "test", + fourInapps.toSet() + "test", calculateInApps(99, abtests, inapps2withExtra) ) } - @Test - fun `abtest logic three variants with inapps1`() = runTest { + fun `abtest logic three variants config of three inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -139,20 +138,20 @@ internal class InAppABTestLogicTest { assertEquals( emptySet(), - calculateInApps(1, abtests, inapps1) + calculateInApps(1, abtests, threeInapps) ) assertEquals( setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04"), - calculateInApps(30, abtests, inapps1) + calculateInApps(30, abtests, threeInapps) ) assertEquals( - inapps1.toSet(), - calculateInApps(65, abtests, inapps1) + threeInapps.toSet(), + calculateInApps(65, abtests, threeInapps) ) } @Test - fun `abtest logic three variants with inapps2`() = runTest { + fun `abtest logic three variants with config of four inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -180,18 +179,18 @@ internal class InAppABTestLogicTest { assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), - calculateInApps(10, abtests, inapps2) + calculateInApps(10, abtests, fourInapps) ) assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "655f5ffa-de86-4224-a0bf-229fe208ed0d"), - calculateInApps(64, abtests, inapps2) + calculateInApps(64, abtests, fourInapps) ) assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "b33ca779-3c99-481f-ad46-91282b0caf04"), - calculateInApps(65, abtests, inapps2) + calculateInApps(65, abtests, fourInapps) ) - val inapps2withExtra = inapps2 + "!" + val inapps2withExtra = fourInapps + "!" assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83", "d1b312bd-aa5c-414c-a0d8-8126376a2a9b", "!"), calculateInApps(10, abtests, inapps2withExtra) @@ -207,7 +206,7 @@ internal class InAppABTestLogicTest { } @Test - fun `abtest logic two concrete variants with inapps1`() = runTest { + fun `abtest logic two concrete with config of three inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -232,14 +231,14 @@ internal class InAppABTestLogicTest { assertEquals( setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04"), - calculateInApps(98, abtests, inapps1) + calculateInApps(98, abtests, threeInapps) ) assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83"), - calculateInApps(99, abtests, inapps1) + calculateInApps(99, abtests, threeInapps) ) - val inapps1withExtra = inapps1 + "??" + val inapps1withExtra = threeInapps + "??" assertEquals( setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d", "b33ca779-3c99-481f-ad46-91282b0caf04", "??"), calculateInApps(98, abtests, inapps1withExtra) @@ -285,7 +284,7 @@ internal class InAppABTestLogicTest { } @Test - fun `abtest logic five variants with inapps2`() = runTest { + fun `abtest logic five variants with config of four inapps`() = runTest { val abtests = listOf( abtest.copy( variants = listOf( @@ -325,29 +324,28 @@ internal class InAppABTestLogicTest { assertEquals( setOf("655f5ffa-de86-4224-a0bf-229fe208ed0d"), - calculateInApps(5, abtests, inapps2) + calculateInApps(5, abtests, fourInapps) ) assertEquals( setOf("6f93e2ef-0615-4e63-9c80-24bcb9e83b83"), - calculateInApps(15, abtests, inapps2) + calculateInApps(15, abtests, fourInapps) ) assertEquals( setOf("b33ca779-3c99-481f-ad46-91282b0caf04"), - calculateInApps(25, abtests, inapps2) + calculateInApps(25, abtests, fourInapps) ) assertEquals( setOf("d1b312bd-aa5c-414c-a0d8-8126376a2a9b"), - calculateInApps(35, abtests, inapps2) + calculateInApps(35, abtests, fourInapps) ) assertEquals( - inapps2.toSet(), - calculateInApps(75, abtests, inapps2) + fourInapps.toSet(), + calculateInApps(75, abtests, fourInapps) ) } - @Test - fun `two abtests logic with inapps1`() = runTest { + fun `two abtests logic with config of three inapps`() = runTest { val abtests = listOf( abtest.copy( salt = "saLt1", @@ -392,7 +390,7 @@ internal class InAppABTestLogicTest { assertEquals( setOf(), - calculateInApps(mixer24and74, abtests, inapps1) + calculateInApps(mixer24and74, abtests, threeInapps) ) val mixer24and99 = mockk { @@ -401,7 +399,7 @@ internal class InAppABTestLogicTest { } assertEquals( setOf(), - calculateInApps(mixer24and99, abtests, inapps1) + calculateInApps(mixer24and99, abtests, threeInapps) ) val mixer99and74 = mockk { @@ -410,7 +408,7 @@ internal class InAppABTestLogicTest { } assertEquals( setOf("b33ca779-3c99-481f-ad46-91282b0caf04"), - calculateInApps(mixer99and74, abtests, inapps1) + calculateInApps(mixer99and74, abtests, threeInapps) ) val mixer99and99 = mockk { @@ -418,8 +416,8 @@ internal class InAppABTestLogicTest { every { stringModulusHash(any(), "SALT2") } returns (99) } assertEquals( - inapps1.toSet(), - calculateInApps(mixer99and99, abtests, inapps1) + threeInapps.toSet(), + calculateInApps(mixer99and99, abtests, threeInapps) ) } diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/managers/MobileConfigSerializationManagerImplTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/managers/MobileConfigSerializationManagerImplTest.kt index 8ac655fef..82cbd2d03 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/managers/MobileConfigSerializationManagerImplTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/managers/MobileConfigSerializationManagerImplTest.kt @@ -27,6 +27,7 @@ class MobileConfigSerializationManagerImplTest { ), variants = listOf( ABTestDto.VariantDto( + id = "3162b011-b30f-4300-a72b-bd5cac0d6607", modulus = ABTestDto.VariantDto.ModulusDto( lower = 0, upper = 50, @@ -43,6 +44,7 @@ class MobileConfigSerializationManagerImplTest { ), ), ABTestDto.VariantDto( + id = "dbc39dce-db4f-4dc9-9133-378df018233b", modulus = ABTestDto.VariantDto.ModulusDto( lower = 50, upper = 100, diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/ABTestValidatorTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/ABTestValidatorTest.kt index e58164809..95166c1c3 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/ABTestValidatorTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/ABTestValidatorTest.kt @@ -27,10 +27,12 @@ internal class ABTestValidatorTest( inapps = listOf() ) val variant1 = ABTestDto.VariantDto( + id = "1", modulus = modulus.copy(upper = 50), objects = listOf(abObject) ) val variant2 = ABTestDto.VariantDto( + id = "2", modulus = modulus.copy(lower = 50), objects = listOf(abObject.copy(kind = "concrete")) ) diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidatorTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidatorTest.kt index d62ed5c2c..15f9c4894 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidatorTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/validators/VariantValidatorTest.kt @@ -24,6 +24,7 @@ internal class VariantValidatorTest( inapps = listOf() ) val variant = ABTestDto.VariantDto( + id = "dsa", modulus = ABTestDto.VariantDto.ModulusDto( lower = 0, upper = 100 @@ -42,6 +43,8 @@ internal class VariantValidatorTest( variant.copy(objects = listOf(objectDto.copy(inapps = listOf("123")))) to true, null to false, + variant.copy(id = "") to false, + variant.copy(id = " ") to false, variant.copy(modulus = null) to false, variant.copy(objects = null) to false, variant.copy(modulus = variant.modulus.copy(lower = -100)) to false, diff --git a/sdk/src/test/resources/abtests.json b/sdk/src/test/resources/abtests.json index 3639b8d44..9dbfd7daf 100644 --- a/sdk/src/test/resources/abtests.json +++ b/sdk/src/test/resources/abtests.json @@ -9,6 +9,7 @@ "salt": "c0e2682c-3d0f-4291-9308-9e48a16eb3c8", "variants": [ { + "id": "3162b011-b30f-4300-a72b-bd5cac0d6607", "modulus": { "lower": 0, "upper": 50 @@ -22,6 +23,7 @@ ] }, { + "id": "dbc39dce-db4f-4dc9-9133-378df018233b", "modulus": { "lower": 50, "upper": 100