From 8a8ef9825cdd940da7e20c3594916a3f381bdf78 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 19 May 2025 18:25:00 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/routing/utils/DeepLinkFactoryTest.kt | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt new file mode 100644 index 0000000000..6bc9b58431 --- /dev/null +++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt @@ -0,0 +1,276 @@ +package com.tangem.tap.routing.utils + +import android.net.Uri +import com.tangem.common.routing.AppRoute +import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler +import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler +import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler +import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.* +import org.junit.After +import org.junit.Before +import org.junit.Test +import timber.log.Timber + +@OptIn(ExperimentalCoroutinesApi::class) +class DeepLinkFactoryTest { + + private val onrampDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + private val sellDeepLinkFactory = mockk(relaxed = true) { + every { create(any(), any()) } returns mockk() + } + private val buyDeepLinkFactory = mockk(relaxed = true) { + every { create(any()) } returns mockk() + } + private val referralDeepLinkFactory = mockk(relaxed = true) { + every { create() } returns mockk() + } + + private val mockedUri = mockk(relaxed = true) + + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + + private val deepLinkFactory = DeepLinkFactory( + onrampDeepLinkFactory, + sellDeepLinkFactory, + buyDeepLinkFactory, + referralDeepLinkFactory, + ) + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setUp() { + testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) + + Dispatchers.setMain(testDispatcher) + + every { mockedUri.path } returns "/path" + every { mockedUri.toString() } returns "https://example.com/path?query=param" + every { mockedUri.authority } returns "example.com" + every { mockedUri.port } returns 443 // Default HTTPS port + every { mockedUri.fragment } returns null // No fragment in this URI + + Timber.uprootAll() // Disable Timber logging for tests + } + + @OptIn(ExperimentalCoroutinesApi::class) + @After + fun tearDown() { + // Reset the main dispatcher + Dispatchers.resetMain() + // Clean up test coroutines + testScope.cancel() + } + + @Test + fun `handleDeeplink stores uri and launches when permitted`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + // Set permittedAppRoute to true + deepLinkFactory.handleDeeplink(mockedUri, testScope) + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + + advanceUntilIdle() + + // Verify onramp handler was called + verify { + onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) + } + } + + @Test + fun `handleDeeplink does not launch when not permitted`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.handleDeeplink(mockedUri, testScope) + deepLinkFactory.checkRoutingReadiness(AppRoute.Initial) + + advanceUntilIdle() + + // Verify no handler was called + verify(inverse = true) { onrampDeepLinkFactory.create(any(), any()) } + } + + @Test + fun `launchDeepLink handles tangem scheme correctly`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + deepLinkFactory.handleDeeplink(mockedUri, testScope) + + advanceUntilIdle() + + verify { + onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) + } + } + + @Test + fun `launchDeepLink ignores unknown scheme`() = runTest { + every { mockedUri.scheme } returns "https" + every { mockedUri.host } returns "example.com" + + deepLinkFactory.handleDeeplink(mockedUri, testScope) + + advanceUntilIdle() + + verify(inverse = true) { + onrampDeepLinkFactory.create(any(), any()) + sellDeepLinkFactory.create(any(), any()) + buyDeepLinkFactory.create(any()) + referralDeepLinkFactory.create() + } + } + + @Test + fun `handleTangemDeepLinks routes to correct handler`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.query } returns "param=value" + every { mockedUri.queryParameterNames } returns setOf("param") + every { mockedUri.getQueryParameter("param") } returns "value" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + + // Test Onramp + every { mockedUri.host } returns "onramp" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { onrampDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Test Sell + every { mockedUri.host } returns "redirect_sell" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { sellDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) } + + // Reset params + every { mockedUri.queryParameterNames } returns emptySet() + every { mockedUri.getQueryParameter(any()) } returns "" + + // Test Buy + every { mockedUri.host } returns "redirect" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { buyDeepLinkFactory.create(eq(testScope)) } + + // Test Referral + every { mockedUri.host } returns "referral" + deepLinkFactory.handleDeeplink(mockedUri, testScope) + advanceUntilIdle() + verify { referralDeepLinkFactory.create() } + } + + @Test + fun `handleTangemDeepLinks incorrect host`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "unknown" + + deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet) + deepLinkFactory.handleDeeplink(mockedUri, testScope) + + advanceUntilIdle() + + verify(inverse = true) { + onrampDeepLinkFactory.create(any(), any()) + sellDeepLinkFactory.create(any(), any()) + buyDeepLinkFactory.create(any()) + referralDeepLinkFactory.create() + } + } + + @Test + fun `getParams filters malicious parameters`() = runTest { + every { mockedUri.scheme } returns "tangem" + every { mockedUri.host } returns "onramp" + every { mockedUri.query } returns "safe=ok&malicious=%3Cscript%3E"e=O%27Brien" + every { mockedUri.queryParameterNames } returns setOf("safe", "malicious", "quote") + every { mockedUri.getQueryParameter("safe") } returns "ok" + every { mockedUri.getQueryParameter("malicious") } returns "