From 411bf2ad4b59da9fe3ebcd693d17244136e3fb2d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 9 Jul 2026 13:25:40 +0100 Subject: [PATCH] test(android): cover discovery and startup gates (#102758) * test(android): cover discovery and startup gates Co-authored-by: NianJiuZst <180004567+users.noreply.github.com> * test(android): keep startup gate tests keystore-free --- apps/.i18n/native-source.json | 8 +-- .../java/ai/openclaw/app/MainViewModel.kt | 12 +++- .../openclaw/app/gateway/GatewayDiscovery.kt | 47 +++++++------ .../java/ai/openclaw/app/MainViewModelTest.kt | 35 ++++++++++ .../app/gateway/GatewayDiscoveryTest.kt | 68 +++++++++++++++++++ 5 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 apps/android/app/src/test/java/ai/openclaw/app/MainViewModelTest.kt create mode 100644 apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewayDiscoveryTest.kt diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index 5c26bdbb52f..85ed92fc62f 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -139,7 +139,7 @@ }, { "kind": "ui-state-text", - "line": 138, + "line": 143, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Searching…", "surface": "android", @@ -147,7 +147,7 @@ }, { "kind": "ui-state-text", - "line": 238, + "line": 243, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Mic off", "surface": "android", @@ -155,7 +155,7 @@ }, { "kind": "ui-state-text", - "line": 248, + "line": 253, "path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt", "source": "Off", "surface": "android", @@ -379,7 +379,7 @@ }, { "kind": "ui-state-text", - "line": 86, + "line": 106, "path": "apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt", "source": "Searching…", "surface": "android", diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt index 1d455944100..ff6ac8d2397 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt @@ -46,6 +46,11 @@ data class ChatDraft( val placement: ChatDraftPlacement, ) +internal fun shouldStartRuntimeOnForeground( + foreground: Boolean, + onboardingCompleted: Boolean, +): Boolean = foreground && onboardingCompleted + /** * UI-facing bridge that exposes NodeRuntime and preference state as Compose-friendly StateFlows. */ @@ -298,7 +303,12 @@ class MainViewModel( */ fun setForeground(value: Boolean) { foreground = value - if (value && prefs.onboardingCompleted.value) { + if ( + shouldStartRuntimeOnForeground( + foreground = value, + onboardingCompleted = prefs.onboardingCompleted.value, + ) + ) { queueRuntimeStartup() } runtimeRef.value?.setForeground(value) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt index d57c103085c..f0ec327bef5 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt @@ -62,6 +62,26 @@ private fun createContextDnsResolver(context: Context): DnsResolver = DnsResolve @Suppress("DEPRECATION") private fun createLegacyDnsResolver(): DnsResolver = DnsResolver.getInstance() +internal fun gatewayDiscoveryStatusText( + localCount: Int, + wideAreaRcode: Int?, + wideAreaCount: Int, +): String { + val wide = + when (wideAreaRcode) { + null -> "Wide: ?" + Rcode.NOERROR -> "Wide: $wideAreaCount" + Rcode.NXDOMAIN -> "Wide: NXDOMAIN" + else -> "Wide: ${Rcode.string(wideAreaRcode)}" + } + + return when { + localCount == 0 && wideAreaRcode == null -> "Searching for gateways…" + localCount == 0 -> wide + else -> "Local: $localCount • $wide" + } +} + /** * Watches local DNS-SD and optional wide-area DNS-SD for reachable OpenClaw gateways. */ @@ -306,27 +326,12 @@ class GatewayDiscovery( _gateways.value = // Merge local and wide-area results deterministically for stable UI selection. (localById.values + unicastById.values).sortedBy { it.name.lowercase() } - _statusText.value = buildStatusText() - } - - private fun buildStatusText(): String { - val localCount = localById.size - val wideRcode = lastWideAreaRcode - val wideCount = lastWideAreaCount - - val wide = - when (wideRcode) { - null -> "Wide: ?" - Rcode.NOERROR -> "Wide: $wideCount" - Rcode.NXDOMAIN -> "Wide: NXDOMAIN" - else -> "Wide: ${Rcode.string(wideRcode)}" - } - - return when { - localCount == 0 && wideRcode == null -> "Searching for gateways…" - localCount == 0 -> "$wide" - else -> "Local: $localCount • $wide" - } + _statusText.value = + gatewayDiscoveryStatusText( + localCount = localById.size, + wideAreaRcode = lastWideAreaRcode, + wideAreaCount = lastWideAreaCount, + ) } private fun stableId( diff --git a/apps/android/app/src/test/java/ai/openclaw/app/MainViewModelTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/MainViewModelTest.kt new file mode 100644 index 00000000000..dd03d780043 --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/MainViewModelTest.kt @@ -0,0 +1,35 @@ +package ai.openclaw.app + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class MainViewModelTest { + @Test + fun foregroundStartupRequiresForegroundAndCompletedOnboarding() { + assertFalse( + shouldStartRuntimeOnForeground( + foreground = false, + onboardingCompleted = true, + ), + ) + assertFalse( + shouldStartRuntimeOnForeground( + foreground = true, + onboardingCompleted = false, + ), + ) + assertFalse( + shouldStartRuntimeOnForeground( + foreground = false, + onboardingCompleted = false, + ), + ) + assertTrue( + shouldStartRuntimeOnForeground( + foreground = true, + onboardingCompleted = true, + ), + ) + } +} diff --git a/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewayDiscoveryTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewayDiscoveryTest.kt new file mode 100644 index 00000000000..e8b2f78e598 --- /dev/null +++ b/apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewayDiscoveryTest.kt @@ -0,0 +1,68 @@ +package ai.openclaw.app.gateway + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.xbill.DNS.Rcode + +class GatewayDiscoveryTest { + @Test + fun statusTextFormatsLocalAndWideAreaDiscoveryStates() { + val cases = + listOf( + StatusCase( + localCount = 0, + wideAreaRcode = null, + wideAreaCount = 0, + expected = "Searching for gateways…", + ), + StatusCase( + localCount = 0, + wideAreaRcode = Rcode.NOERROR, + wideAreaCount = 2, + expected = "Wide: 2", + ), + StatusCase( + localCount = 1, + wideAreaRcode = Rcode.NOERROR, + wideAreaCount = 2, + expected = "Local: 1 • Wide: 2", + ), + StatusCase( + localCount = 1, + wideAreaRcode = null, + wideAreaCount = 0, + expected = "Local: 1 • Wide: ?", + ), + StatusCase( + localCount = 0, + wideAreaRcode = Rcode.NXDOMAIN, + wideAreaCount = 0, + expected = "Wide: NXDOMAIN", + ), + StatusCase( + localCount = 2, + wideAreaRcode = Rcode.SERVFAIL, + wideAreaCount = 0, + expected = "Local: 2 • Wide: SERVFAIL", + ), + ) + + for (case in cases) { + assertEquals( + case.expected, + gatewayDiscoveryStatusText( + localCount = case.localCount, + wideAreaRcode = case.wideAreaRcode, + wideAreaCount = case.wideAreaCount, + ), + ) + } + } +} + +private data class StatusCase( + val localCount: Int, + val wideAreaRcode: Int?, + val wideAreaCount: Int, + val expected: String, +)