























@@ -10,6 +10,7 @@ import ai.openclaw.app.node.InvokeDispatcher
1010import ai.openclaw.app.protocol.OpenClawTalkCommand
1111import ai.openclaw.app.voice.TalkModeManager
1212import android.Manifest
13+import kotlinx.coroutines.CompletableDeferred
1314import kotlinx.coroutines.flow.MutableStateFlow
1415import kotlinx.coroutines.runBlocking
1516import org.junit.Assert.assertEquals
@@ -153,7 +154,7 @@ class GatewayBootstrapAuthTest {
153154NodeRuntime(
154155 app,
155156 prefs,
156- tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp-1") },
157+ tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "fp:1") },
157158 )
158159val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
159160val explicitAuth =
@@ -169,11 +170,93 @@ class GatewayBootstrapAuthTest {
169170170171 runtime.acceptGatewayTrustPrompt()
171172172- assertEquals("fp-1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
173- assertEquals("setup-bootstrap-token", desiredBootstrapToken(runtime, "nodeSession"))
173+ assertEquals("f1", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
174+ assertEquals("setup-bootstrap-token", waitForDesiredBootstrapToken(runtime, "nodeSession"))
174175 assertNull(desiredBootstrapToken(runtime, "operatorSession"))
175176 }
176177178+ @Test
179+fun connect_promptsBeforeReplacingChangedTlsFingerprint() =
180+ runBlocking {
181+val app = RuntimeEnvironment.getApplication()
182+val securePrefs =
183+ app.getSharedPreferences(
184+"openclaw.node.secure.test.${UUID.randomUUID()}",
185+ android.content.Context.MODE_PRIVATE,
186+ )
187+val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
188+val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
189+ prefs.saveGatewayTlsFingerprint(endpoint.stableId, "sha256:aa:aa:aa:aa")
190+val runtime =
191+NodeRuntime(
192+ app,
193+ prefs,
194+ tlsFingerprintProbe = { _, _ -> GatewayTlsProbeResult(fingerprintSha256 = "sha256:bb:bb:bb:bb") },
195+ )
196+197+ runtime.connect(
198+ endpoint,
199+NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
200+ )
201+202+val prompt = waitForGatewayTrustPrompt(runtime)
203+ assertEquals("aaaaaaaa", prompt.previousFingerprintSha256)
204+ assertEquals("bbbbbbbb", prompt.fingerprintSha256)
205+ assertEquals("sha256:aa:aa:aa:aa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
206+207+ runtime.declineGatewayTrustPrompt()
208+209+ assertEquals("sha256:aa:aa:aa:aa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
210+211+ runtime.connect(
212+ endpoint,
213+NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
214+ )
215+ waitForGatewayTrustPrompt(runtime)
216+ runtime.acceptGatewayTrustPrompt()
217+218+ assertEquals("bbbbbbbb", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
219+ }
220+221+ @Test
222+fun connect_ignoresStaleTlsProbeAfterDisconnect() =
223+ runBlocking {
224+val app = RuntimeEnvironment.getApplication()
225+val securePrefs =
226+ app.getSharedPreferences(
227+"openclaw.node.secure.test.${UUID.randomUUID()}",
228+ android.content.Context.MODE_PRIVATE,
229+ )
230+val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
231+val endpoint = GatewayEndpoint.manual(host = "gateway.example", port = 18789)
232+ prefs.saveGatewayTlsFingerprint(endpoint.stableId, "aaaaaaaa")
233+val probeStarted = CompletableDeferred<Unit>()
234+val probeResult = CompletableDeferred<GatewayTlsProbeResult>()
235+val runtime =
236+NodeRuntime(
237+ app,
238+ prefs,
239+ tlsFingerprintProbe = { _, _ ->
240+ probeStarted.complete(Unit)
241+ probeResult.await()
242+ },
243+ )
244+245+ runtime.connect(
246+ endpoint,
247+NodeRuntime.GatewayConnectAuth(token = "shared-token", bootstrapToken = null, password = null),
248+ )
249+ probeStarted.await()
250+251+ runtime.disconnect()
252+ probeResult.complete(GatewayTlsProbeResult(fingerprintSha256 = "aaaaaaaa"))
253+Thread.sleep(100)
254+255+ assertNull(runtime.pendingGatewayTrust.value)
256+ assertNull(desiredBootstrapToken(runtime, "nodeSession"))
257+ assertEquals("aaaaaaaa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
258+ }
259+177260 @Test
178261fun connect_showsSecureEndpointGuidanceWhenTlsProbeFails() {
179262val app = RuntimeEnvironment.getApplication()
@@ -269,6 +352,21 @@ class GatewayBootstrapAuthTest {
269352return readField(desired, "bootstrapToken")
270353 }
271354355+private fun waitForDesiredBootstrapToken(
356+runtime: NodeRuntime,
357+sessionFieldName: String,
358+ ): String {
359+var lastObserved: String? = null
360+ repeat(50) {
361+ desiredBootstrapToken(runtime, sessionFieldName)?.let { token ->
362+ lastObserved = token
363+return token
364+ }
365+Thread.sleep(10)
366+ }
367+ error("Expected desired bootstrap token for $sessionFieldName; last observed=$lastObserved")
368+ }
369+272370private fun <T> readField(
273371target: Any,
274372name: String,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。