fix(deeplink): stop links that fail for good from replaying on every launch (#27)

* test(deeplink): pin terminal-failure replay and referendum path matching

These fail on main: a link that fails for good is kept as pending and
replayed on every launch, and /open/governance matches the referendum
handler.

* fix(deeplink): drop a pending link once it fails for good

RootDeepLinkHandler cleared the persisted pending link only on success or
when no handler matched. A link that reached a handler and failed with a
DeepLinkHandlingException stayed in preferences and was replayed on
every launch. Clear it on terminal handling failures in both the fresh
and pending paths; this also recovers installs already stuck on update.
Other failures keep the link pending for a retry.

* fix(deeplink): match the referendum path exactly

ReferendumDeepLinkHandler matched any path starting with /open/gov, so
the governance banner link /open/governance went to the referendum
screen and failed with ReferendumIsNotSpecified.
This commit is contained in:
SatoshiQaziMuhammed authored and GitHub committed 2026-10-19 16:46:32 +04:00
1 parent 5a6eb62a1b
commit 2d5d8bec49
6 files changed
+103 -1

No files matched your search

@@ -31,7 +31,9 @@ class ReferendumDeepLinkHandler(
override suspend fun matches(data: Uri): Boolean {
val path = data.path ?: return false
return path.startsWith(ReferendumDetailsDeepLinkConfigurator.PREFIX)
// Exact match, not startsWith: otherwise "/open/governance" (and any "/open/gov*") would wrongly
// match the referendum prefix "/open/gov" and fail with ReferendumIsNotSpecified.
return path == ReferendumDetailsDeepLinkConfigurator.PREFIX
}
override suspend fun handleDeepLink(data: Uri) = runCatching {
@@ -0,0 +1,39 @@
package io.novafoundation.nova.feature_governance_impl.presentation.referenda.details.deeplink
import android.net.Uri
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.test_shared.whenever
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mockito.Mockito.mock
class ReferendumDeepLinkHandlerTest {
private val handler = ReferendumDeepLinkHandler(
router = mock(GovernanceRouter::class.java),
chainRegistry = mock(ChainRegistry::class.java),
mutableGovernanceState = mock(MutableGovernanceState::class.java),
automaticInteractionGate = mock(AutomaticInteractionGate::class.java)
)
private fun linkWithPath(path: String): Uri = mock(Uri::class.java).also {
whenever(it.path).thenReturn(path)
}
@Test
fun `matches a referendum link`() = runBlocking {
assertTrue(handler.matches(linkWithPath("/open/gov")))
}
// "/open/governance" starts with the referendum prefix "/open/gov"; matching it sent the
// governance banner link to the referendum screen, which failed without a referendum id.
@Test
fun `does not match a link that only starts with the referendum path`() = runBlocking {
assertFalse(handler.matches(linkWithPath("/open/governance")))
}
}