diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DataModule.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DataModule.kt index 1927b3759..df4510ebd 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DataModule.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DataModule.kt @@ -309,6 +309,9 @@ internal fun DataModule( ).registerSubtype( TreeTargetingDto.ViewProductNodeDto::class.java, TreeTargetingDto.ViewProductNodeDto.VIEW_PRODUCT_ID_JSON_NAME + ).registerSubtype( + TreeTargetingDto.VisitNodeDto::class.java, + TreeTargetingDto.VisitNodeDto.VISIT_JSON_NAME ) ).create() } 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 82166c95d..8096c3693 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 @@ -280,6 +280,12 @@ internal class InAppMapper { ) } + is TreeTargetingDto.VisitNodeDto -> TreeTargeting.VisitNode( + TreeTargetingDto.VisitNodeDto.VISIT_JSON_NAME, + treeTargetingDto.kind.enumValue(), + treeTargetingDto.value!! + ) + is TreeTargetingDto.TrueNodeDto -> TreeTargeting.TrueNode(TreeTargetingDto.TrueNodeDto.TRUE_JSON_NAME) is TreeTargetingDto.IntersectionNodeDto -> TreeTargeting.IntersectionNode( type = TreeTargetingDto.IntersectionNodeDto.AND_JSON_NAME, diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/InAppValidatorImpl.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/InAppValidatorImpl.kt index 9377a1ea8..89c656372 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/InAppValidatorImpl.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/data/validators/InAppValidatorImpl.kt @@ -129,6 +129,12 @@ internal class InAppValidatorImpl( ENDS_WITH ) && !targeting.value.isNullOrBlank() } + + is TreeTargetingDto.VisitNodeDto -> { + !targeting.type.isNullOrBlank() && targeting.kind.equalsAny( + GREATER_OR_EQUALS, LOWER_OR_EQUALS, EQUALS, NOT_EQUALS + ) && (targeting.value?.let { it > 0 } == true) + } } } @@ -182,5 +188,10 @@ internal class InAppValidatorImpl( private const val NOT_SUBSTRING = "notSubstring" private const val STARTS_WITH = "startsWith" private const val ENDS_WITH = "endsWith" + + private const val GREATER_OR_EQUALS = "gte" + private const val LOWER_OR_EQUALS = "lte" + private const val EQUALS = "equals" + private const val NOT_EQUALS = "notEquals" } } \ No newline at end of file diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargeting.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargeting.kt index bb671c630..bbb30ac10 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargeting.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargeting.kt @@ -2,6 +2,7 @@ package cloud.mindbox.mobile_sdk.inapp.domain.models import cloud.mindbox.mobile_sdk.di.mindboxInject import cloud.mindbox.mobile_sdk.logger.mindboxLogD +import cloud.mindbox.mobile_sdk.repository.MindboxPreferences internal interface ITargeting { fun checkTargeting(data: TargetingData): Boolean @@ -34,6 +35,13 @@ internal enum class Kind { NEGATIVE } +internal enum class KindVisit { + GTE, + LTE, + EQUALS, + NOT_EQUALS +} + internal enum class KindAny { ANY, NONE, @@ -338,4 +346,49 @@ internal sealed class TreeTargeting(open val type: String) : return false } } + + internal data class VisitNode(override val type: String, val kind: KindVisit, val value: Long) : + TreeTargeting(type) { + + override fun checkTargeting(data: TargetingData): Boolean { + val userVisitCount = MindboxPreferences.userVisitCount.toLong() + return when (kind) { + KindVisit.GTE -> { + value >= userVisitCount + } + + KindVisit.LTE -> { + value <= userVisitCount + } + + KindVisit.EQUALS -> { + value == userVisitCount + } + + KindVisit.NOT_EQUALS -> { + value != userVisitCount + } + } + } + + override suspend fun fetchTargetingInfo(data: TargetingData) { + return + } + + override fun hasSegmentationNode(): Boolean { + return false + } + + override fun hasGeoNode(): Boolean { + return false + } + + override fun hasOperationNode(): Boolean { + return false + } + + override suspend fun getOperationsSet(): Set { + return emptySet() + } + } } \ No newline at end of file diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/TreeTargetingDto.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/TreeTargetingDto.kt index 80c1bfaae..0c73bc2ae 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/TreeTargetingDto.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/models/TreeTargetingDto.kt @@ -170,5 +170,16 @@ internal sealed class TreeTargetingDto { } } - + internal data class VisitNodeDto( + @SerializedName("${"$"}type") + val type: String?, + @SerializedName("kind") + val kind: String?, + @SerializedName("value") + val value: Long? + ): TreeTargetingDto() { + companion object { + const val VISIT_JSON_NAME = "visit" + } + } } \ No newline at end of file diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/InAppValidatorTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/InAppValidatorTest.kt index ff7fec885..bb65bf646 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/InAppValidatorTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/data/InAppValidatorTest.kt @@ -1651,6 +1651,204 @@ internal class InAppValidatorTest { ) } + @Test + fun `validate targeting dto is visit node and its valid with gte`() { + assertTrue( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "gte", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and its valid with lte`() { + assertTrue( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "lte", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and its valid with equals`() { + assertTrue( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "equals", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and its valid with not equals`() { + assertTrue( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "notEquals", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and type is null`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = null, kind = "notBlank", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and type is blank`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "", kind = "notBlank", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and kind is null`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = null, value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and kind is blank`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and kind is unknown`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "notBlank", value = 1L + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and value is null`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "notBlank", value = null + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + + @Test + fun `validate targeting dto is visit node and value is less than 1`() { + assertFalse( + inAppValidator.validateInApp( + InAppStub.getInAppDto().copy( + targeting = InAppStub.getTargetingVisitNodeDto().copy( + type = "notBlank", kind = "notBlank", value = 0 + ), form = InAppStub.getInAppDto().form?.copy( + variants = listOf( + InAppStub.getModalWindowDto() + .copy(type = "def") + ) + ) + ) + ) + ) + } + @Test fun `in-app version is lower than required`() { val lowInAppVersion = Constants.SDK_VERSION_NUMERIC - 1 diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargetingTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargetingTest.kt index 7a709b80b..481d616a5 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargetingTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/inapp/domain/models/TreeTargetingTest.kt @@ -5,7 +5,10 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.InAppGeoRep import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.InAppSegmentationRepository import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository import cloud.mindbox.mobile_sdk.managers.MindboxEventManager -import cloud.mindbox.mobile_sdk.models.* +import cloud.mindbox.mobile_sdk.models.GeoTargetingStub +import cloud.mindbox.mobile_sdk.models.InAppStub +import cloud.mindbox.mobile_sdk.models.SegmentationCheckInAppStub +import cloud.mindbox.mobile_sdk.repository.MindboxPreferences import com.google.gson.Gson import io.mockk.* import io.mockk.junit4.MockKRule @@ -423,6 +426,157 @@ class TreeTargetingTest { } + + @Test + fun `check targeting visit GTE returns false when user visit count is higher`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.GTE + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 11L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertFalse(result) + } + + @Test + fun `check targeting visit GTE returns true when user visit count is lower`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.GTE + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 9L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + + @Test + fun `check targeting visit GTE returns true when user visit count is equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.GTE + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 10L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + + @Test + fun `check targeting visit LTE returns false when user visit count is lower`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.LTE + val value = 12L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 11L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertFalse(result) + } + + @Test + fun `check targeting visit LTE returns true when user visit count is lower`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.LTE + val value = 8L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 9L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + + @Test + fun `check targeting visit LTE returns true when user visit count is equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.LTE + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 10L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + + @Test + fun `check targeting visit equals returns false when user visit is not equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.EQUALS + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 11L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertFalse(result) + } + + @Test + fun `check targeting visit equals returns true when user visit count is equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.EQUALS + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 10L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + + @Test + fun `check targeting visit not equals returns false when user visit count is equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.NOT_EQUALS + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 10L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertFalse(result) + } + + @Test + fun `check targeting visit not equals returns true when user visit count is not equal`() { + mockkObject(MindboxPreferences) + val kind = KindVisit.NOT_EQUALS + val value = 10L + val targeting = InAppStub.getTargetingVisitNode().copy(kind = kind, value = value) + + val userVisitCount = 9L + every { MindboxPreferences.userVisitCount } returns (userVisitCount.toInt()) + + val result = targeting.checkTargeting(mockk()) + + assertTrue(result) + } + class TestTargetingData( override val triggerEventName: String, override val operationBody: String? = null diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/models/InAppStub.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/models/InAppStub.kt index 7afe5f462..c5e823443 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/models/InAppStub.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/models/InAppStub.kt @@ -250,6 +250,14 @@ internal class InAppStub { return TreeTargeting.CityNode(type = "", kind = Kind.POSITIVE, ids = emptyList()) } + fun getTargetingVisitNodeDto(): TreeTargetingDto.VisitNodeDto { + return TreeTargetingDto.VisitNodeDto(type = null, kind = null, value = null) + } + + fun getTargetingVisitNode(): TreeTargeting.VisitNode { + return TreeTargeting.VisitNode(type = "", kind = KindVisit.GTE, value = 0L) + } + fun getTargetingRegionNode(): TreeTargeting.RegionNode { return TreeTargeting.RegionNode(type = "", kind = Kind.POSITIVE, ids = emptyList()) }