Updated on 2026-08-14
This commit is contained in:
parent
9fd13fdc46
commit
2f48642a14
9 changed files with 376 additions and 137 deletions
|
|
@ -366,6 +366,8 @@ dependencies {
|
|||
implementation(deps.googlePlay.review)
|
||||
implementation(deps.googlePlay.review.ktx)
|
||||
implementation(deps.googlePlay.services.wallet)
|
||||
implementation(deps.googlePlay.services)
|
||||
implementation(deps.googlePlay.advertising)
|
||||
coreLibraryDesugaring(deps.desugar)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.reKotlin)
|
||||
|
|
|
|||
|
|
@ -1,36 +1,20 @@
|
|||
package com.tangem.tap.common.analytics.appsflyer
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.appsflyer.deeplink.DeepLink
|
||||
import com.appsflyer.deeplink.DeepLinkListener
|
||||
import com.appsflyer.deeplink.DeepLinkResult
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore
|
||||
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@Singleton
|
||||
class AppsFlyerDeepLinkListener @Inject constructor(
|
||||
private val appsFlyerConversionStore: AppsFlyerConversionStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
private val referralParamsHandler: AppsFlyerReferralParamsHandler,
|
||||
) : DeepLinkListener {
|
||||
|
||||
private val coroutineScope = CoroutineScope(dispatchers.io + SupervisorJob())
|
||||
private val mutex = Mutex()
|
||||
|
||||
override fun onDeepLinking(p0: DeepLinkResult) {
|
||||
when (p0.status) {
|
||||
DeepLinkResult.Status.FOUND -> {
|
||||
onDeepLinkFound(p0.deepLink)
|
||||
referralParamsHandler.handle(deepLink = p0.deepLink)
|
||||
}
|
||||
DeepLinkResult.Status.NOT_FOUND -> {
|
||||
Timber.i("No deep link found")
|
||||
|
|
@ -40,55 +24,4 @@ class AppsFlyerDeepLinkListener @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDeepLinkFound(deepLink: DeepLink) {
|
||||
if (deepLink.deepLinkValue != REFERRAL_DEEP_LINK_VALUE) {
|
||||
Timber.i("Ignoring deep link with value: ${deepLink.deepLinkValue ?: "null"}")
|
||||
return
|
||||
}
|
||||
|
||||
val refcode = deepLink.getStringValue(REFCODE_PARAM_KEY)
|
||||
val campaign = deepLink.getStringValue(CAMPAIGN_PARAM_KEY)
|
||||
|
||||
@Suppress("NullableToStringCall")
|
||||
Timber.i("refcode=$refcode\ncampaign=$campaign")
|
||||
|
||||
if (!isValidParam(refcode)) {
|
||||
Timber.e("Deep link conversion data is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
storeConversionData(refcode = refcode, campaign = campaign)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
private fun isValidParam(value: String?): Boolean {
|
||||
contract {
|
||||
returns(true) implies (value != null)
|
||||
}
|
||||
|
||||
return value != null && value.isNotBlank() && !value.equals("null", ignoreCase = true)
|
||||
}
|
||||
|
||||
private fun storeConversionData(refcode: String, campaign: String?) {
|
||||
coroutineScope.launch {
|
||||
mutex.withLock {
|
||||
appsFlyerConversionStore.storeIfAbsent(
|
||||
value = AppsFlyerConversionData(refcode = refcode, campaign = campaign),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object Companion {
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
const val REFERRAL_DEEP_LINK_VALUE = "referral"
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
const val REFCODE_PARAM_KEY = "deep_link_sub1"
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
|
||||
const val CAMPAIGN_PARAM_KEY = "deep_link_sub2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.tap.common.analytics.appsflyer
|
||||
|
||||
import com.appsflyer.deeplink.DeepLink
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore
|
||||
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@Singleton
|
||||
class AppsFlyerReferralParamsHandler @Inject constructor(
|
||||
private val appsFlyerConversionStore: AppsFlyerConversionStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
private val coroutineScope = CoroutineScope(dispatchers.io + SupervisorJob())
|
||||
private val mutex = Mutex()
|
||||
|
||||
fun handle(deepLink: DeepLink) {
|
||||
handle(
|
||||
deepLinkValue = deepLink.deepLinkValue,
|
||||
deepLinkSub1 = deepLink.getStringValue(DEEP_LINK_SUB_1),
|
||||
deepLinkSub2 = deepLink.getStringValue(DEEP_LINK_SUB_2),
|
||||
)
|
||||
}
|
||||
|
||||
fun handle(params: Map<String?, Any?>) {
|
||||
handle(
|
||||
deepLinkValue = params[DEEP_LINK_VALUE] as? String,
|
||||
deepLinkSub1 = params[DEEP_LINK_SUB_1] as? String,
|
||||
deepLinkSub2 = params[DEEP_LINK_SUB_2] as? String,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handle(deepLinkValue: String?, deepLinkSub1: String?, deepLinkSub2: String?) {
|
||||
if (deepLinkValue != REFERRAL_DEEP_LINK_VALUE) {
|
||||
Timber.i("Ignoring deep link with value: ${deepLinkValue ?: "null"}")
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("NullableToStringCall")
|
||||
Timber.i("refcode=$deepLinkSub1\ncampaign=$deepLinkSub2")
|
||||
|
||||
if (!isValidParam(deepLinkSub1)) {
|
||||
Timber.e("Deeplink conversion data is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
storeConversionData(refcode = deepLinkSub1, campaign = deepLinkSub2)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
private fun isValidParam(value: String?): Boolean {
|
||||
contract {
|
||||
returns(true) implies (value != null)
|
||||
}
|
||||
|
||||
return value != null && value.isNotBlank() && !value.equals("null", ignoreCase = true)
|
||||
}
|
||||
|
||||
private fun storeConversionData(refcode: String, campaign: String?) {
|
||||
coroutineScope.launch {
|
||||
mutex.withLock {
|
||||
appsFlyerConversionStore.storeIfAbsent(
|
||||
value = AppsFlyerConversionData(refcode = refcode, campaign = campaign),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
const val REFERRAL_DEEP_LINK_VALUE = "referral"
|
||||
const val DEEP_LINK_VALUE = "deep_link_value"
|
||||
const val DEEP_LINK_SUB_1 = "deep_link_sub1"
|
||||
const val DEEP_LINK_SUB_2 = "deep_link_sub2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.tap.common.analytics.appsflyer
|
||||
|
||||
import com.appsflyer.AppsFlyerConversionListener
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TangemAFConversionListener @Inject constructor(
|
||||
private val referralParamsHandler: AppsFlyerReferralParamsHandler,
|
||||
) : AppsFlyerConversionListener {
|
||||
|
||||
override fun onConversionDataSuccess(p0: Map<String?, Any?>?) {
|
||||
Timber.i("AppsFlyer conversion data success: ${p0.orEmpty()}")
|
||||
|
||||
if (p0 == null) return
|
||||
|
||||
referralParamsHandler.handle(params = p0)
|
||||
}
|
||||
|
||||
override fun onConversionDataFail(p0: String?) {
|
||||
Timber.e("AppsFlyer conversion data failure: ${p0.orEmpty()}")
|
||||
}
|
||||
|
||||
override fun onAppOpenAttribution(p0: Map<String?, String?>?) {
|
||||
Timber.i("AppsFlyer app open attribution: ${p0.orEmpty()}")
|
||||
}
|
||||
|
||||
override fun onAttributionFailure(p0: String?) {
|
||||
Timber.e("AppsFlyer attribution failure: ${p0.orEmpty()}")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.tap.common.analytics.handlers.appsflyer
|
||||
|
||||
import android.content.Context
|
||||
import com.appsflyer.AppsFlyerConversionListener
|
||||
import com.appsflyer.AppsFlyerLib
|
||||
import com.appsflyer.attribution.AppsFlyerRequestListener
|
||||
import com.tangem.core.analytics.api.EventLogger
|
||||
import com.tangem.core.analytics.api.UserIdHolder
|
||||
import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener
|
||||
import com.tangem.tap.common.analytics.appsflyer.TangemAFConversionListener
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.UnderscoreAnalyticsEventConverter
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -20,6 +20,7 @@ class AppsFlyerClient @AssistedInject constructor(
|
|||
@Assisted apiKey: String,
|
||||
@ApplicationContext private val context: Context,
|
||||
appsFlyerDeepLinkListener: AppsFlyerDeepLinkListener,
|
||||
tangemAFConversionListener: TangemAFConversionListener,
|
||||
) : AppsFlyerAnalyticsClient {
|
||||
|
||||
private val appsFlyerLib: AppsFlyerLib = AppsFlyerLib.getInstance()
|
||||
|
|
@ -32,7 +33,7 @@ class AppsFlyerClient @AssistedInject constructor(
|
|||
|
||||
subscribeForDeepLink(appsFlyerDeepLinkListener)
|
||||
|
||||
init(apiKey, ConversionListener, context)
|
||||
init(apiKey, tangemAFConversionListener, context)
|
||||
|
||||
Timber.i("Starting AppsFlyer SDK")
|
||||
start(context, apiKey, InitializationListener)
|
||||
|
|
@ -58,21 +59,6 @@ class AppsFlyerClient @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private object ConversionListener : AppsFlyerConversionListener {
|
||||
override fun onConversionDataSuccess(p0: Map<String?, Any?>?) {
|
||||
Timber.i("AppsFlyer conversion data success: ${p0.orEmpty()}")
|
||||
}
|
||||
override fun onConversionDataFail(p0: String?) {
|
||||
Timber.e("AppsFlyer conversion data failure: ${p0.orEmpty()}")
|
||||
}
|
||||
override fun onAppOpenAttribution(p0: Map<String?, String?>?) {
|
||||
Timber.i("AppsFlyer app open attribution: ${p0.orEmpty()}")
|
||||
}
|
||||
override fun onAttributionFailure(p0: String?) {
|
||||
Timber.e("AppsFlyer attribution failure: ${p0.orEmpty()}")
|
||||
}
|
||||
}
|
||||
|
||||
private object InitializationListener : AppsFlyerRequestListener {
|
||||
override fun onSuccess() {
|
||||
Timber.d("AppsFlyer initialized successfully")
|
||||
|
|
|
|||
|
|
@ -2,16 +2,8 @@ package com.tangem.tap.common.analytics.appsflyer
|
|||
|
||||
import com.appsflyer.deeplink.DeepLink
|
||||
import com.appsflyer.deeplink.DeepLinkResult
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore
|
||||
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
||||
import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener.Companion.CAMPAIGN_PARAM_KEY
|
||||
import com.tangem.tap.common.analytics.appsflyer.AppsFlyerDeepLinkListener.Companion.REFCODE_PARAM_KEY
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
|
@ -23,27 +15,29 @@ import org.junit.jupiter.params.ParameterizedTest
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AppsFlyerDeepLinkListenerTest {
|
||||
|
||||
private val appsFlyerConversionStore: AppsFlyerConversionStore = mockk(relaxUnitFun = true)
|
||||
private val referralParamsHandler: AppsFlyerReferralParamsHandler = mockk()
|
||||
private val listener = AppsFlyerDeepLinkListener(
|
||||
appsFlyerConversionStore = appsFlyerConversionStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
referralParamsHandler = referralParamsHandler,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(appsFlyerConversionStore)
|
||||
clearMocks(referralParamsHandler)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun onDeepLinking(model: OnDeepLinkingModel) = runTest {
|
||||
if (model.shouldHandle) {
|
||||
every { referralParamsHandler.handle(deepLink = model.deepLinkResult.deepLink) } just Runs
|
||||
}
|
||||
|
||||
listener.onDeepLinking(p0 = model.deepLinkResult)
|
||||
|
||||
if (model.shouldStore) {
|
||||
val value = AppsFlyerConversionData(refcode = SUCCESS_REFCODE, campaign = SUCCESS_CAMPAIGN)
|
||||
coVerify { appsFlyerConversionStore.storeIfAbsent(value = value) }
|
||||
if (model.shouldHandle) {
|
||||
coVerify { referralParamsHandler.handle(deepLink = model.deepLinkResult.deepLink) }
|
||||
} else {
|
||||
coVerify(inverse = true) { appsFlyerConversionStore.storeIfAbsent(value = any()) }
|
||||
coVerify(inverse = true) { referralParamsHandler.handle(deepLink = any()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,48 +46,28 @@ class AppsFlyerDeepLinkListenerTest {
|
|||
// DeepLinkResult.Status.NOT_FOUND
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = DeepLinkResult(null, null),
|
||||
shouldStore = false,
|
||||
shouldHandle = false,
|
||||
),
|
||||
// DeepLinkResult.Status.ERROR
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = DeepLinkResult(null, DeepLinkResult.Error.NETWORK),
|
||||
shouldStore = false,
|
||||
shouldHandle = false,
|
||||
),
|
||||
// DeepLinkResult.Status.FOUND
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = createFoundDeepLink(
|
||||
deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE,
|
||||
refcode = SUCCESS_REFCODE,
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
deepLinkValue = "deep_link_value",
|
||||
refcode = "refcode",
|
||||
campaign = "campaign",
|
||||
),
|
||||
shouldStore = true,
|
||||
),
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = createFoundDeepLink(
|
||||
deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE,
|
||||
refcode = "",
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = createFoundDeepLink(
|
||||
deepLinkValue = AppsFlyerDeepLinkListener.REFERRAL_DEEP_LINK_VALUE,
|
||||
refcode = null,
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
OnDeepLinkingModel(
|
||||
deepLinkResult = createFoundDeepLink(deepLinkValue = "some_other_deep_link_value"),
|
||||
shouldStore = false,
|
||||
shouldHandle = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class OnDeepLinkingModel(
|
||||
val deepLinkResult: DeepLinkResult,
|
||||
val shouldStore: Boolean,
|
||||
val shouldHandle: Boolean,
|
||||
)
|
||||
|
||||
private fun createFoundDeepLink(
|
||||
|
|
@ -103,8 +77,8 @@ class AppsFlyerDeepLinkListenerTest {
|
|||
): DeepLinkResult {
|
||||
val deeplink = mockk<DeepLink> {
|
||||
every { this@mockk.deepLinkValue } returns deepLinkValue
|
||||
every { this@mockk.getStringValue(REFCODE_PARAM_KEY) } returns refcode
|
||||
every { this@mockk.getStringValue(CAMPAIGN_PARAM_KEY) } returns campaign
|
||||
every { this@mockk.getStringValue("deep_link_sub1") } returns refcode
|
||||
every { this@mockk.getStringValue("deep_link_sub2") } returns campaign
|
||||
}
|
||||
|
||||
return mockk<DeepLinkResult> {
|
||||
|
|
@ -112,9 +86,4 @@ class AppsFlyerDeepLinkListenerTest {
|
|||
every { this@mockk.deepLink } returns deeplink
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val SUCCESS_REFCODE = "valid_refcode"
|
||||
const val SUCCESS_CAMPAIGN = "valid_campaign"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package com.tangem.tap.common.analytics.appsflyer
|
||||
|
||||
import com.appsflyer.deeplink.DeepLink
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore
|
||||
import com.tangem.domain.wallets.models.AppsFlyerConversionData
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AppsFlyerReferralParamsHandlerTest {
|
||||
|
||||
private val appsFlyerConversionStore: AppsFlyerConversionStore = mockk(relaxUnitFun = true)
|
||||
private val handler = AppsFlyerReferralParamsHandler(
|
||||
appsFlyerConversionStore = appsFlyerConversionStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(appsFlyerConversionStore)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class HandleDeepLink {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun handle(model: HandleDeepLinkModel) = runTest {
|
||||
handler.handle(deepLink = model.deepLink)
|
||||
|
||||
if (model.shouldStore) {
|
||||
val value = AppsFlyerConversionData(refcode = SUCCESS_REFCODE, campaign = SUCCESS_CAMPAIGN)
|
||||
coVerify { appsFlyerConversionStore.storeIfAbsent(value = value) }
|
||||
} else {
|
||||
coVerify(inverse = true) { appsFlyerConversionStore.storeIfAbsent(value = any()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels(): List<HandleDeepLinkModel> {
|
||||
return listOf(
|
||||
HandleDeepLinkModel(
|
||||
deepLink = createDeepLink(
|
||||
deepLinkValue = "referral",
|
||||
refcode = SUCCESS_REFCODE,
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = true,
|
||||
),
|
||||
HandleDeepLinkModel(
|
||||
deepLink = createDeepLink(deepLinkValue = "some_other_deep_link_value"),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleDeepLinkModel(
|
||||
deepLink = createDeepLink(deepLinkValue = ""),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleDeepLinkModel(
|
||||
deepLink = createDeepLink(
|
||||
deepLinkValue = "referral",
|
||||
refcode = "",
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleDeepLinkModel(
|
||||
deepLink = createDeepLink(
|
||||
deepLinkValue = "referral",
|
||||
refcode = null,
|
||||
campaign = SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDeepLink(
|
||||
deepLinkValue: String,
|
||||
refcode: String? = null,
|
||||
campaign: String? = null,
|
||||
): DeepLink {
|
||||
return mockk<DeepLink> {
|
||||
every { this@mockk.deepLinkValue } returns deepLinkValue
|
||||
every { this@mockk.getStringValue("deep_link_sub1") } returns refcode
|
||||
every { this@mockk.getStringValue("deep_link_sub2") } returns campaign
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class HandleDeepLinkModel(val deepLink: DeepLink, val shouldStore: Boolean)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class HandleParams {
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun handle(model: HandleParamsModel) = runTest {
|
||||
handler.handle(params = model.params)
|
||||
|
||||
if (model.shouldStore) {
|
||||
val value = AppsFlyerConversionData(refcode = SUCCESS_REFCODE, campaign = SUCCESS_CAMPAIGN)
|
||||
coVerify { appsFlyerConversionStore.storeIfAbsent(value = value) }
|
||||
} else {
|
||||
coVerify(inverse = true) { appsFlyerConversionStore.storeIfAbsent(value = any()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels(): List<HandleParamsModel> {
|
||||
return listOf(
|
||||
HandleParamsModel(
|
||||
params = mapOf(
|
||||
"deep_link_value" to "referral",
|
||||
"deep_link_sub1" to SUCCESS_REFCODE,
|
||||
"deep_link_sub2" to SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = true,
|
||||
),
|
||||
HandleParamsModel(
|
||||
params = mapOf("deep_link_value" to "some_other_deep_link_value"),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleParamsModel(
|
||||
params = mapOf("deep_link_value" to ""),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleParamsModel(
|
||||
params = mapOf(
|
||||
"deep_link_value" to "referral",
|
||||
"deep_link_sub1" to "",
|
||||
"deep_link_sub2" to SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
HandleParamsModel(
|
||||
params = mapOf(
|
||||
"deep_link_value" to "referral",
|
||||
"deep_link_sub2" to SUCCESS_CAMPAIGN,
|
||||
),
|
||||
shouldStore = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class HandleParamsModel(val params: Map<String?, Any?>, val shouldStore: Boolean)
|
||||
|
||||
private companion object Companion {
|
||||
const val SUCCESS_REFCODE = "valid_refcode"
|
||||
const val SUCCESS_CAMPAIGN = "valid_campaign"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.tap.common.analytics.appsflyer
|
||||
|
||||
import com.tangem.test.core.ProvideTestModels
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TangemAFConversionListenerTest {
|
||||
|
||||
private val referralParamsHandler: AppsFlyerReferralParamsHandler = mockk(relaxUnitFun = true)
|
||||
private val listener = TangemAFConversionListener(
|
||||
referralParamsHandler = referralParamsHandler,
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(referralParamsHandler)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ProvideTestModels
|
||||
fun onConversionDataSuccess(model: OnConversionDataSuccessModel) = runTest {
|
||||
listener.onConversionDataSuccess(p0 = model.params)
|
||||
|
||||
if (model.shouldHandle) {
|
||||
coVerify { referralParamsHandler.handle(params = model.params!!) }
|
||||
} else {
|
||||
coVerify(inverse = true) { referralParamsHandler.handle(params = any()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels(): List<OnConversionDataSuccessModel> {
|
||||
return listOf(
|
||||
OnConversionDataSuccessModel(
|
||||
params = null,
|
||||
shouldHandle = false,
|
||||
),
|
||||
OnConversionDataSuccessModel(
|
||||
params = emptyMap(),
|
||||
shouldHandle = true,
|
||||
),
|
||||
OnConversionDataSuccessModel(
|
||||
params = mapOf(
|
||||
"deep_link_value" to "referral",
|
||||
),
|
||||
shouldHandle = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class OnConversionDataSuccessModel(
|
||||
val params: Map<String?, Any?>?,
|
||||
val shouldHandle: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@
|
|||
androidGradlePlugin = "8.10.1"
|
||||
firebaseCrashlytics = "3.0.1"
|
||||
googleServices = "4.4.1"
|
||||
playServices = "18.10.0"
|
||||
playServicesAds = "18.2.0"
|
||||
kotlin = "2.1.10"
|
||||
ksp = "2.1.10-1.0.30"
|
||||
firebasePerf = "1.4.2"
|
||||
|
|
@ -254,6 +256,8 @@ desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desuga
|
|||
googlePlay-review = { module = "com.google.android.play:review", version.ref = "googlePlayReview" }
|
||||
googlePlay-review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "googlePlayReviewKtx" }
|
||||
googlePlay-services-wallet = { module = "com.google.android.gms:play-services-wallet", version.ref = "googlePlayServicesWallet" }
|
||||
googlePlay-services = { module = "com.google.android.gms:play-services-base", version.ref = "playServices" }
|
||||
googlePlay-advertising = { module = "com.google.android.gms:play-services-ads-identifier", version.ref = "playServicesAds" }
|
||||
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||
hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" }
|
||||
hilt-kapt = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue