From 43d3e44c453a558edc127da42da37b35ed537aac Mon Sep 17 00:00:00 2001 From: Karl Dimla Date: Mon, 24 Jun 2024 18:22:44 +0200 Subject: [PATCH 1/4] Add pprobillingperiod matching attribute --- .../impl/SubscriptionsConstants.kt | 10 ++ .../RMFPProBillingPeriodMatchingAttribute.kt | 70 ++++++++ .../impl/survey/PproSurveyParameters.kt | 9 +- ...FPProBillingPeriodMatchingAttributeTest.kt | 153 ++++++++++++++++++ .../survey/PproSurveyParameterPluginsTest.kt | 28 +++- 5 files changed, 262 insertions(+), 8 deletions(-) create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttribute.kt create mode 100644 subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttributeTest.kt diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsConstants.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsConstants.kt index 179e2fb753a5..3ee363b41de4 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsConstants.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsConstants.kt @@ -46,3 +46,13 @@ object SubscriptionsConstants { const val PRIVACY_PRO_ETLD = "duckduckgo.com" const val PRIVACY_PRO_PATH = "pro" } + +internal fun String.productIdToBillingPeriod(): String? { + return if (this == SubscriptionsConstants.MONTHLY_PLAN) { + "monthly" + } else if (this == SubscriptionsConstants.YEARLY_PLAN) { + "annual" + } else { + null + } +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttribute.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttribute.kt new file mode 100644 index 000000000000..efca56473e9f --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttribute.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.remote.messaging.api.AttributeMatcherPlugin +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.remote.messaging.api.JsonToMatchingAttributeMapper +import com.duckduckgo.remote.messaging.api.MatchingAttribute +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.productIdToBillingPeriod +import com.squareup.anvil.annotations.ContributesMultibinding +import dagger.SingleInstanceIn +import javax.inject.Inject + +@ContributesMultibinding( + scope = AppScope::class, + boundType = JsonToMatchingAttributeMapper::class, +) +@ContributesMultibinding( + scope = AppScope::class, + boundType = AttributeMatcherPlugin::class, +) +@SingleInstanceIn(AppScope::class) +class RMFPProBillingPeriodMatchingAttribute @Inject constructor( + private val subscriptionsManager: SubscriptionsManager, +) : JsonToMatchingAttributeMapper, AttributeMatcherPlugin { + override suspend fun evaluate(matchingAttribute: MatchingAttribute): Boolean? { + if (matchingAttribute is PProBillingPeriodMatchingAttribute) { + val productId = subscriptionsManager.getSubscription()?.productId + return productId != null && matchingAttribute.value == productId.productIdToBillingPeriod() + } + return null + } + + override fun map( + key: String, + jsonMatchingAttribute: JsonMatchingAttribute, + ): MatchingAttribute? { + if (key == PProBillingPeriodMatchingAttribute.KEY) { + val value = jsonMatchingAttribute.value as? String + return value.takeIf { !it.isNullOrEmpty() }?.let { + PProBillingPeriodMatchingAttribute(value = it) + } + } + return null + } +} + +internal data class PProBillingPeriodMatchingAttribute( + val value: String, +) : MatchingAttribute { + companion object { + const val KEY = "privacyProBillingPeriod" + } +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameters.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameters.kt index ba49085e56f7..02eddd77fed0 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameters.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameters.kt @@ -20,6 +20,7 @@ import com.duckduckgo.common.utils.CurrentTimeProvider import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.MONTHLY_PLAN import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.productIdToBillingPeriod import com.duckduckgo.survey.api.SurveyParameterPlugin import com.squareup.anvil.annotations.ContributesMultibinding import java.util.concurrent.TimeUnit @@ -42,13 +43,7 @@ class PproBillingParameterPlugin @Inject constructor( override suspend fun evaluate(): String { val productId = subscriptionsManager.getSubscription()?.productId - return productId?.let { - if (it.isMonthly()) { - "Monthly" - } else { - "Yearly" - } - }?.lowercase() ?: "" + return productId?.productIdToBillingPeriod() ?: "" } private fun String.isMonthly(): Boolean = this == MONTHLY_PLAN diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttributeTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttributeTest.kt new file mode 100644 index 000000000000..8ff990beadb6 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProBillingPeriodMatchingAttributeTest.kt @@ -0,0 +1,153 @@ +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE +import com.duckduckgo.subscriptions.impl.SubscriptionsConstants +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.repository.Entitlement +import com.duckduckgo.subscriptions.impl.repository.Subscription +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever + +class RMFPProBillingPeriodMatchingAttributeTest { + @Mock + private lateinit var subscriptionsManager: SubscriptionsManager + + private lateinit var matcher: RMFPProBillingPeriodMatchingAttribute + private val testSubscription = Subscription( + productId = SubscriptionsConstants.YEARLY_PLAN, + startedAt = 10000L, + expiresOrRenewsAt = 10000L, + status = AUTO_RENEWABLE, + platform = "Google", + entitlements = listOf(Entitlement("name", "product")), + ) + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + matcher = RMFPProBillingPeriodMatchingAttribute(subscriptionsManager) + } + + @Test + fun whenKeyIsNotBillingPeriodThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "annual") + val result = matcher.map("somethingelse", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsBillingPeriodWithInvalidValueTypeThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf("annual")) + val result = matcher.map("privacyProBillingPeriod", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsBillingPeriodWithEmptyValueThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "") + val result = matcher.map("privacyProBillingPeriod", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsBillingPeriodWithNullValueThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = null) + val result = matcher.map("privacyProBillingPeriod", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsBillingPeriodValidThenMappersMapsToAttribute() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "annual") + val result = matcher.map("privacyProBillingPeriod", jsonMatchingAttribute) + + assertEquals(PProBillingPeriodMatchingAttribute("annual"), result) + } + + @Test + fun whenMatchingAttributeHasAnnualValueAndSubscriptionIsAnnualThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("annual")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasMonthlyValueAndSubscriptionIsMonthlyThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(productId = SubscriptionsConstants.MONTHLY_PLAN)) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("monthly")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasMonthlyValueButSubscriptionIsAnnualThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("monthly")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenSubscriptionIsNullAnnualThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(null) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("monthly")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeIsUnsupportedValueThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("quarterly")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenSubscriptionHasInvalidProductIdThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(productId = "invalid")) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute("annual")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatcherHasEmptyValueThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProBillingPeriodMatchingAttribute(value = "")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } +} diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameterPluginsTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameterPluginsTest.kt index f548b8ef618a..a9cdecb4966c 100644 --- a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameterPluginsTest.kt +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/survey/PproSurveyParameterPluginsTest.kt @@ -53,7 +53,7 @@ class PproSurveyParameterPluginTest { } @Test - fun whenSubscriptionIsAvailableThenBillingParamEvaluatesToSubscriptionBilling() = runTest { + fun whenSubscriptionIsMonthlyThenBillingParamEvaluatesToSubscriptionBilling() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) val plugin = PproBillingParameterPlugin(subscriptionsManager) @@ -61,6 +61,32 @@ class PproSurveyParameterPluginTest { assertEquals("monthly", plugin.evaluate()) } + @Test + fun whenSubscriptionIsYearlyThenBillingParamEvaluatesToSubscriptionBilling() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn( + testSubscription.copy( + productId = SubscriptionsConstants.YEARLY_PLAN, + ), + ) + + val plugin = PproBillingParameterPlugin(subscriptionsManager) + + assertEquals("annual", plugin.evaluate()) + } + + @Test + fun whenSubscriptionHasInvalidProductIdThenBillingParamEvaluatesToEmpty() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn( + testSubscription.copy( + productId = "some invalid product id", + ), + ) + + val plugin = PproBillingParameterPlugin(subscriptionsManager) + + assertEquals("", plugin.evaluate()) + } + @Test fun whenSubscriptionIsNotAvailableThenBillingParamEvaluatesToEmpty() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(null) From 5f125b396a1389f8a81998e407789b48a09d9f69 Mon Sep 17 00:00:00 2001 From: Karl Dimla Date: Tue, 25 Jun 2024 13:16:37 +0200 Subject: [PATCH 2/4] Add pproSubscriptionStatus matching attribute --- ...PProSubscriptionStatusMatchingAttribute.kt | 90 +++++++ ...SubscriptionStatusMatchingAttributeTest.kt | 247 ++++++++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt create mode 100644 subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt new file mode 100644 index 000000000000..f9a6694ae348 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.remote.messaging.api.AttributeMatcherPlugin +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.remote.messaging.api.JsonToMatchingAttributeMapper +import com.duckduckgo.remote.messaging.api.MatchingAttribute +import com.duckduckgo.subscriptions.api.SubscriptionStatus +import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.EXPIRED +import com.duckduckgo.subscriptions.api.SubscriptionStatus.GRACE_PERIOD +import com.duckduckgo.subscriptions.api.SubscriptionStatus.INACTIVE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.NOT_AUTO_RENEWABLE +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.squareup.anvil.annotations.ContributesMultibinding +import dagger.SingleInstanceIn +import javax.inject.Inject + +@ContributesMultibinding( + scope = AppScope::class, + boundType = JsonToMatchingAttributeMapper::class, +) +@ContributesMultibinding( + scope = AppScope::class, + boundType = AttributeMatcherPlugin::class, +) +@SingleInstanceIn(AppScope::class) +class RMFPProSubscriptionStatusMatchingAttribute @Inject constructor( + private val subscriptionsManager: SubscriptionsManager, +) : JsonToMatchingAttributeMapper, AttributeMatcherPlugin { + override suspend fun evaluate(matchingAttribute: MatchingAttribute): Boolean? { + if (matchingAttribute is PProSubscriptionStatusMatchingAttribute) { + val status = subscriptionsManager.getSubscription()?.status + return status != null && status.matchesRmfValue(matchingAttribute.value) + } + return null + } + + private fun SubscriptionStatus.matchesRmfValue(value: String): Boolean { + return when (value) { + STATUS_ACTIVE -> this == AUTO_RENEWABLE || this == NOT_AUTO_RENEWABLE || this == GRACE_PERIOD + STATUS_EXPIRING -> this == NOT_AUTO_RENEWABLE + STATUS_EXPIRED -> this == EXPIRED || this == INACTIVE + else -> false + } + } + + override fun map( + key: String, + jsonMatchingAttribute: JsonMatchingAttribute, + ): MatchingAttribute? { + if (key == PProSubscriptionStatusMatchingAttribute.KEY) { + val value = jsonMatchingAttribute.value as? String + return value.takeIf { !it.isNullOrEmpty() }?.let { + PProSubscriptionStatusMatchingAttribute(value = it) + } + } + return null + } + + companion object { + private const val STATUS_ACTIVE = "active" + private const val STATUS_EXPIRING = "expiring" + private const val STATUS_EXPIRED = "expired" + } +} + +internal data class PProSubscriptionStatusMatchingAttribute( + val value: String, +) : MatchingAttribute { + companion object { + const val KEY = "pproSubscriptionStatus" + } +} diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt new file mode 100644 index 000000000000..b37f4a2a1ae1 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt @@ -0,0 +1,247 @@ +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.EXPIRED +import com.duckduckgo.subscriptions.api.SubscriptionStatus.GRACE_PERIOD +import com.duckduckgo.subscriptions.api.SubscriptionStatus.INACTIVE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.NOT_AUTO_RENEWABLE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.UNKNOWN +import com.duckduckgo.subscriptions.api.SubscriptionStatus.WAITING +import com.duckduckgo.subscriptions.impl.SubscriptionsConstants +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.repository.Entitlement +import com.duckduckgo.subscriptions.impl.repository.Subscription +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever + +class RMFPProSubscriptionStatusMatchingAttributeTest { + @Mock + private lateinit var subscriptionsManager: SubscriptionsManager + + private lateinit var matcher: RMFPProSubscriptionStatusMatchingAttribute + private val testSubscription = Subscription( + productId = SubscriptionsConstants.YEARLY_PLAN, + startedAt = 10000L, + expiresOrRenewsAt = 10000L, + status = AUTO_RENEWABLE, + platform = "Google", + entitlements = listOf(Entitlement("name", "product")), + ) + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + matcher = RMFPProSubscriptionStatusMatchingAttribute(subscriptionsManager) + } + + @Test + fun whenKeyIsNotStatusThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "active") + val result = matcher.map("somethingelse", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsStatuseWithInvalidValueTypeThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf("active")) + val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsStatusWithNullThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = null) + val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsStatusWithEmptyValueThenMappersMapsToNull() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "") + val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) + + assertNull(result) + } + + @Test + fun whenKeyIsStatusValidThenMappersMapsToAtttribute() { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "active") + val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) + + assertEquals(PProSubscriptionStatusMatchingAttribute("active"), result) + } + + @Test + fun whenMatchingAttributeHasActiveValueAndSubscriptionIsAutoRenewableThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasActiveValueAndSubscriptionIsNotAutoRenewableThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = NOT_AUTO_RENEWABLE)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasActiveValueAndSubscriptionIsGracePeriodhenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = GRACE_PERIOD)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiringValueAndSubscriptionIsNotAutoRenewableThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = NOT_AUTO_RENEWABLE)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiringValueAndSubscriptionIsAutoRenewableThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsGracePeriodThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = GRACE_PERIOD)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsExpiredThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = EXPIRED)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsInactiveThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = INACTIVE)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsUnknownThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = UNKNOWN)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsWaitingThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = WAITING)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasActiveValueAndSubscriptionIsWaitingThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = WAITING)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasActiveValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(null) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiringValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(null) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasExpiredValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(null) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasInvalidValueThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("invalid")) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } +} From d888e4f670f5eef81e6d83c96582c45e47a1c45e Mon Sep 17 00:00:00 2001 From: Karl Dimla Date: Tue, 25 Jun 2024 14:09:28 +0200 Subject: [PATCH 3/4] Add pproDaysUntilExpiryOrRenewal matching attribute --- ...ProDaysSinceSubscribedMatchingAttribute.kt | 14 +- ...DaysUntilExpiryRenewalMatchingAttribute.kt | 106 ++++++++++ ...UntilExpiryRenewalMatchingAttributeTest.kt | 199 ++++++++++++++++++ 3 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttribute.kt create mode 100644 subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttributeTest.kt diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysSinceSubscribedMatchingAttribute.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysSinceSubscribedMatchingAttribute.kt index 8ab1c414ed43..fa307e4f5523 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysSinceSubscribedMatchingAttribute.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysSinceSubscribedMatchingAttribute.kt @@ -90,13 +90,6 @@ class RMFPProDaysSinceSubscribedMatchingAttribute @Inject constructor( else -> null } } - - private fun Any?.toIntOrDefault(default: Int): Int = when { - this == null -> default - this is Double -> this.toInt() - this is Long -> this.toInt() - else -> this as Int - } } internal data class PProDaysSinceSubscribedMatchingAttribute( @@ -108,3 +101,10 @@ internal data class PProDaysSinceSubscribedMatchingAttribute( const val KEY = "pproDaysSinceSubscribed" } } + +internal fun Any?.toIntOrDefault(default: Int): Int = when { + this == null -> default + this is Double -> this.toInt() + this is Long -> this.toInt() + else -> this as Int +} diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttribute.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttribute.kt new file mode 100644 index 000000000000..19978e00d4ef --- /dev/null +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttribute.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.common.utils.CurrentTimeProvider +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.remote.messaging.api.AttributeMatcherPlugin +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.remote.messaging.api.JsonToMatchingAttributeMapper +import com.duckduckgo.remote.messaging.api.MatchingAttribute +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.repository.isExpired +import com.squareup.anvil.annotations.ContributesMultibinding +import dagger.SingleInstanceIn +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@ContributesMultibinding( + scope = AppScope::class, + boundType = JsonToMatchingAttributeMapper::class, +) +@ContributesMultibinding( + scope = AppScope::class, + boundType = AttributeMatcherPlugin::class, +) +@SingleInstanceIn(AppScope::class) +class RMFPProDaysUntilExpiryRenewalMatchingAttribute @Inject constructor( + private val subscriptionsManager: SubscriptionsManager, + private val currentTimeProvider: CurrentTimeProvider, +) : JsonToMatchingAttributeMapper, AttributeMatcherPlugin { + override suspend fun evaluate(matchingAttribute: MatchingAttribute): Boolean? { + return when (matchingAttribute) { + is PProDaysUntilExpiryRenewalMatchingAttribute -> { + val subscription = subscriptionsManager.getSubscription() + return if (subscription == null || subscription.status.isExpired() || + matchingAttribute == PProDaysUntilExpiryRenewalMatchingAttribute() + ) { + false + } else { + val daysUntilRenewalOrExpiry = daysUntilRenewalOrExpiry(subscription.expiresOrRenewsAt) + if (!matchingAttribute.value.isDefaultValue()) { + matchingAttribute.value == daysUntilRenewalOrExpiry + } else { + (matchingAttribute.min.isDefaultValue() || daysUntilRenewalOrExpiry >= matchingAttribute.min) && + (matchingAttribute.max.isDefaultValue() || daysUntilRenewalOrExpiry <= matchingAttribute.max) + } + } + } + + else -> null + } + } + + private fun Int.isDefaultValue(): Boolean { + return this == -1 + } + + private fun daysUntilRenewalOrExpiry(expiresOrRenewsAt: Long): Int { + return TimeUnit.MILLISECONDS.toDays(expiresOrRenewsAt - currentTimeProvider.currentTimeMillis()).toInt() + } + + override fun map( + key: String, + jsonMatchingAttribute: JsonMatchingAttribute, + ): MatchingAttribute? { + return when (key) { + PProDaysUntilExpiryRenewalMatchingAttribute.KEY -> { + try { + PProDaysUntilExpiryRenewalMatchingAttribute( + min = jsonMatchingAttribute.min.toIntOrDefault(-1), + max = jsonMatchingAttribute.max.toIntOrDefault(-1), + value = jsonMatchingAttribute.value.toIntOrDefault(-1), + ) + } catch (e: Exception) { + null + } + } + + else -> null + } + } +} + +internal data class PProDaysUntilExpiryRenewalMatchingAttribute( + val min: Int = -1, + val max: Int = -1, + val value: Int = -1, +) : MatchingAttribute { + companion object { + const val KEY = "pproDaysUntilExpiryOrRenewal" + } +} diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttributeTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttributeTest.kt new file mode 100644 index 000000000000..bd018507fd11 --- /dev/null +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProDaysUntilExpiryRenewalMatchingAttributeTest.kt @@ -0,0 +1,199 @@ +package com.duckduckgo.subscriptions.impl.rmf + +import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.common.utils.CurrentTimeProvider +import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute +import com.duckduckgo.subscriptions.api.SubscriptionStatus.AUTO_RENEWABLE +import com.duckduckgo.subscriptions.api.SubscriptionStatus.EXPIRED +import com.duckduckgo.subscriptions.impl.SubscriptionsManager +import com.duckduckgo.subscriptions.impl.repository.Entitlement +import com.duckduckgo.subscriptions.impl.repository.Subscription +import java.util.concurrent.TimeUnit.DAYS +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever + +class RMFPProDaysUntilExpiryRenewalMatchingAttributeTest { + @get:Rule + val coroutineTestRule: CoroutineTestRule = CoroutineTestRule() + + @Mock + private lateinit var subscriptionsManager: SubscriptionsManager + + @Mock + private lateinit var currentTimeProvider: CurrentTimeProvider + + private lateinit var matcher: RMFPProDaysUntilExpiryRenewalMatchingAttribute + + private val testSubscription = Subscription( + productId = "productId", + startedAt = DAYS.toMillis(1), + expiresOrRenewsAt = DAYS.toMillis(10), + status = AUTO_RENEWABLE, + platform = "google", + entitlements = listOf(Entitlement("name", "product")), + ) + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + matcher = RMFPProDaysUntilExpiryRenewalMatchingAttribute( + subscriptionsManager, + currentTimeProvider, + ) + } + + @Test + fun whenMapKeyIsDaysTillExpiryRenewalWithMinMaxThenReturnMatchingAttribute() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute(min = 20, max = 30) + val result = matcher.map("pproDaysUntilExpiryOrRenewal", jsonMatchingAttribute) + assertEquals(PProDaysUntilExpiryRenewalMatchingAttribute(min = 20, max = 30), result) + } + + @Test + fun whenMapKeyIsDaysTillExpiryRenewalWithValueThenReturnMatchingAttribute() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute(value = 30) + val result = matcher.map("pproDaysUntilExpiryOrRenewal", jsonMatchingAttribute) + assertEquals(PProDaysUntilExpiryRenewalMatchingAttribute(value = 30), result) + } + + @Test + fun whenMapKeyIsDaysTillExpiryRenewalWithEmptyValuesTheMapReturnAttribute() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute() + val result = matcher.map("pproDaysUntilExpiryOrRenewal", jsonMatchingAttribute) + assertEquals(PProDaysUntilExpiryRenewalMatchingAttribute(), result) + } + + @Test + fun whenMapKeyIsNotDaysTillExpiryRenewalTheMapReturnNull() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute(min = 20, max = 30) + val result = matcher.map("somethingelse", jsonMatchingAttribute) + assertNull(result) + } + + @Test + fun whenMapKeyIsDaysTillExpiryRenewalButValueIsInvalidTheMapReturnNull() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute(value = "20") + val result = matcher.map("pproDaysUntilExpiryOrRenewal", jsonMatchingAttribute) + assertNull(result) + } + + @Test + fun whenMapKeyIsDaysTillExpiryRenewalButValueAreMixedWithInvalidTheMapReturnNull() = runTest { + val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf(20), min = 20, max = 24) + val result = matcher.map("pproDaysUntilExpiryOrRenewal", jsonMatchingAttribute) + assertNull(result) + } + + @Test + fun whenNoPropertiesSetOnMapperThenEvaluateReturnFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute()) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenSubscriptionExpiredThenEvaluateReturnFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = EXPIRED)) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(min = 2, max = 20)) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenSubscriptionIsNullThenReturnEvaluateReturnsFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(null) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(value = 10)) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenValueIsEqualToDaysTillExpiryRenewalThenEvaluateReturnTrue() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(value = 8, max = 13, min = 10)) + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMaxIsGreaterThanDaysTillExpiryRenewalThenEvaluateReturnTrue() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(10))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(max = 15)) + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMaxIsLessThanDaysTillExpiryRenewalThenEvaluateReturnFalse() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(20))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(max = 5)) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMinIsLessThanDaysTillExpiryRenewalThenEvaluateReturnTrue() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(10))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(min = 5)) + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMinIsGreaterThanDaysTillExpiryRenewalThenEvaluateReturnFalse() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(4))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(min = 5)) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenDaysTillExpiryRenewalIsBetweenMinAndMaxThenEvaluateReturnTrue() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(10))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(min = 5, max = 10)) + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenDaysTillExpiryRenewalIsOutOfMinAndMaxThenEvaluateReturnFalse() = runTest { + whenever(currentTimeProvider.currentTimeMillis()).thenReturn(DAYS.toMillis(2)) + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(expiresOrRenewsAt = DAYS.toMillis(20))) + val result = matcher.evaluate(PProDaysUntilExpiryRenewalMatchingAttribute(min = 5, max = 10)) + assertNotNull(result) + result?.let { + assertFalse(it) + } + } +} From 21d3a663c30e1aa757c6500542a5210f0850df80 Mon Sep 17 00:00:00 2001 From: Karl Dimla Date: Wed, 26 Jun 2024 12:21:47 +0200 Subject: [PATCH 4/4] Support list for pproSubscriptionStatus --- ...PProSubscriptionStatusMatchingAttribute.kt | 24 ++++--- ...SubscriptionStatusMatchingAttributeTest.kt | 62 +++++++++++++------ 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt index f9a6694ae348..e498694679d7 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttribute.kt @@ -52,13 +52,19 @@ class RMFPProSubscriptionStatusMatchingAttribute @Inject constructor( return null } - private fun SubscriptionStatus.matchesRmfValue(value: String): Boolean { - return when (value) { - STATUS_ACTIVE -> this == AUTO_RENEWABLE || this == NOT_AUTO_RENEWABLE || this == GRACE_PERIOD - STATUS_EXPIRING -> this == NOT_AUTO_RENEWABLE - STATUS_EXPIRED -> this == EXPIRED || this == INACTIVE - else -> false + private fun SubscriptionStatus.matchesRmfValue(value: List): Boolean { + value.forEach { + val shouldMatch = when (it) { + STATUS_ACTIVE -> this == AUTO_RENEWABLE || this == NOT_AUTO_RENEWABLE || this == GRACE_PERIOD + STATUS_EXPIRING -> this == NOT_AUTO_RENEWABLE + STATUS_EXPIRED -> this == EXPIRED || this == INACTIVE + else -> false + } + + if (shouldMatch) return true } + + return false } override fun map( @@ -66,8 +72,8 @@ class RMFPProSubscriptionStatusMatchingAttribute @Inject constructor( jsonMatchingAttribute: JsonMatchingAttribute, ): MatchingAttribute? { if (key == PProSubscriptionStatusMatchingAttribute.KEY) { - val value = jsonMatchingAttribute.value as? String - return value.takeIf { !it.isNullOrEmpty() }?.let { + val value = jsonMatchingAttribute.value as? List + return value.takeUnless { it.isNullOrEmpty() }?.let { PProSubscriptionStatusMatchingAttribute(value = it) } } @@ -82,7 +88,7 @@ class RMFPProSubscriptionStatusMatchingAttribute @Inject constructor( } internal data class PProSubscriptionStatusMatchingAttribute( - val value: String, + val value: List, ) : MatchingAttribute { companion object { const val KEY = "pproSubscriptionStatus" diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt index b37f4a2a1ae1..3c6144bf77cc 100644 --- a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/rmf/RMFPProSubscriptionStatusMatchingAttributeTest.kt @@ -42,7 +42,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenKeyIsNotStatusThenMappersMapsToNull() { - val jsonMatchingAttribute = JsonMatchingAttribute(value = "active") + val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf("active")) val result = matcher.map("somethingelse", jsonMatchingAttribute) assertNull(result) @@ -50,7 +50,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenKeyIsStatuseWithInvalidValueTypeThenMappersMapsToNull() { - val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf("active")) + val jsonMatchingAttribute = JsonMatchingAttribute(value = "active") val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) assertNull(result) @@ -66,7 +66,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenKeyIsStatusWithEmptyValueThenMappersMapsToNull() { - val jsonMatchingAttribute = JsonMatchingAttribute(value = "") + val jsonMatchingAttribute = JsonMatchingAttribute(value = emptyList()) val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) assertNull(result) @@ -74,16 +74,16 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenKeyIsStatusValidThenMappersMapsToAtttribute() { - val jsonMatchingAttribute = JsonMatchingAttribute(value = "active") + val jsonMatchingAttribute = JsonMatchingAttribute(value = listOf("active")) val result = matcher.map("pproSubscriptionStatus", jsonMatchingAttribute) - assertEquals(PProSubscriptionStatusMatchingAttribute("active"), result) + assertEquals(PProSubscriptionStatusMatchingAttribute(listOf("active")), result) } @Test fun whenMatchingAttributeHasActiveValueAndSubscriptionIsAutoRenewableThenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("active"))) assertNotNull(result) result?.let { @@ -94,7 +94,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasActiveValueAndSubscriptionIsNotAutoRenewableThenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = NOT_AUTO_RENEWABLE)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("active"))) assertNotNull(result) result?.let { @@ -105,7 +105,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasActiveValueAndSubscriptionIsGracePeriodhenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = GRACE_PERIOD)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("active"))) assertNotNull(result) result?.let { @@ -116,7 +116,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiringValueAndSubscriptionIsNotAutoRenewableThenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = NOT_AUTO_RENEWABLE)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expiring"))) assertNotNull(result) result?.let { @@ -127,7 +127,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiringValueAndSubscriptionIsAutoRenewableThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expiring"))) assertNotNull(result) result?.let { @@ -138,7 +138,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsGracePeriodThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = GRACE_PERIOD)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -149,7 +149,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsExpiredThenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = EXPIRED)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -160,7 +160,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsInactiveThenEvaluateToTrue() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = INACTIVE)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -171,7 +171,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsUnknownThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = UNKNOWN)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -182,7 +182,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionIsWaitingThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = WAITING)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -193,7 +193,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasActiveValueAndSubscriptionIsWaitingThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = WAITING)) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("active"))) assertNotNull(result) result?.let { @@ -204,7 +204,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasActiveValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(null) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("active")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("active"))) assertNotNull(result) result?.let { @@ -215,7 +215,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiringValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(null) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expiring")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expiring"))) assertNotNull(result) result?.let { @@ -226,7 +226,7 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasExpiredValueAndSubscriptionNoSubscriptionThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(null) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("expired")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expired"))) assertNotNull(result) result?.let { @@ -237,7 +237,29 @@ class RMFPProSubscriptionStatusMatchingAttributeTest { @Test fun whenMatchingAttributeHasInvalidValueThenEvaluateToFalse() = runTest { whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription) - val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute("invalid")) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("invalid"))) + + assertNotNull(result) + result?.let { + assertFalse(it) + } + } + + @Test + fun whenMatchingAttributeHasMultipleValuesAndMatchesOneThenEvaluateToTrue() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = EXPIRED)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expiring", "expired"))) + + assertNotNull(result) + result?.let { + assertTrue(it) + } + } + + @Test + fun whenMatchingAttributeHasMultipleValuesAndDoesNotMatchThenEvaluateToFalse() = runTest { + whenever(subscriptionsManager.getSubscription()).thenReturn(testSubscription.copy(status = AUTO_RENEWABLE)) + val result = matcher.evaluate(PProSubscriptionStatusMatchingAttribute(listOf("expiring", "expired"))) assertNotNull(result) result?.let {