




















@@ -10,6 +10,7 @@ import kotlinx.coroutines.runBlocking
1010import kotlinx.coroutines.withTimeout
1111import kotlinx.coroutines.withTimeoutOrNull
1212import kotlinx.serialization.json.Json
13+import kotlinx.serialization.json.JsonArray
1314import kotlinx.serialization.json.JsonObject
1415import kotlinx.serialization.json.jsonObject
1516import kotlinx.serialization.json.jsonPrimitive
@@ -265,6 +266,126 @@ class GatewaySessionInvokeTest {
265266 }
266267 }
267268269+ @Test
270+fun connect_reusesStoredDeviceTokenScopes() =
271+ runBlocking {
272+val json = testJson()
273+val connected = CompletableDeferred<Unit>()
274+val connectParams = CompletableDeferred<JsonObject>()
275+val lastDisconnect = AtomicReference("")
276+val server =
277+ startGatewayServer(json) { webSocket, id, method, frame ->
278+if (method == "connect") {
279+if (!connectParams.isCompleted) {
280+ connectParams.complete(frame["params"]!!.jsonObject)
281+ }
282+ webSocket.send(connectResponseFrame(id))
283+ webSocket.close(1000, "done")
284+ }
285+ }
286+287+val harness =
288+ createNodeHarness(
289+ connected = connected,
290+ lastDisconnect = lastDisconnect,
291+ ) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }
292+293+try {
294+val deviceId = DeviceIdentityStore(RuntimeEnvironment.getApplication()).loadOrCreate().deviceId
295+ harness.deviceAuthStore.saveToken(
296+ deviceId = deviceId,
297+ role = "operator",
298+ token = "operator-device-token",
299+ scopes = listOf("operator.pairing", "operator.write"),
300+ )
301+302+ connectNodeSession(
303+ session = harness.session,
304+ port = server.port,
305+ token = null,
306+ role = "operator",
307+ scopes = listOf("operator.approvals", "operator.read", "operator.write"),
308+ )
309+ awaitConnectedOrThrow(connected, lastDisconnect, server)
310+311+val params = withTimeout(TEST_TIMEOUT_MS) { connectParams.await() }
312+ assertEquals(
313+"operator-device-token",
314+ params["auth"]
315+ ?.jsonObject
316+ ?.get("token")
317+ ?.jsonPrimitive
318+ ?.content,
319+ )
320+ assertEquals(listOf("operator.pairing", "operator.write"), params.scopes())
321+ } finally {
322+ shutdownHarness(harness, server)
323+ }
324+ }
325+326+ @Test
327+fun bootstrapConnect_filtersOperatorHandoffScopesFromConnectRequest() =
328+ runBlocking {
329+val json = testJson()
330+val connected = CompletableDeferred<Unit>()
331+val connectParams = CompletableDeferred<JsonObject>()
332+val lastDisconnect = AtomicReference("")
333+val server =
334+ startGatewayServer(json) { webSocket, id, method, frame ->
335+if (method == "connect") {
336+if (!connectParams.isCompleted) {
337+ connectParams.complete(frame["params"]!!.jsonObject)
338+ }
339+ webSocket.send(connectResponseFrame(id))
340+ webSocket.close(1000, "done")
341+ }
342+ }
343+344+val harness =
345+ createNodeHarness(
346+ connected = connected,
347+ lastDisconnect = lastDisconnect,
348+ ) { GatewaySession.InvokeResult.ok("""{"handled":true}""") }
349+350+try {
351+ connectNodeSession(
352+ session = harness.session,
353+ port = server.port,
354+ token = null,
355+ bootstrapToken = "setup-bootstrap-token",
356+ role = "operator",
357+ scopes =
358+listOf(
359+"operator.approvals",
360+"operator.pairing",
361+"operator.read",
362+"operator.write",
363+ ),
364+ )
365+ awaitConnectedOrThrow(connected, lastDisconnect, server)
366+367+val params = withTimeout(TEST_TIMEOUT_MS) { connectParams.await() }
368+ assertEquals(
369+"setup-bootstrap-token",
370+ params["auth"]
371+ ?.jsonObject
372+ ?.get("bootstrapToken")
373+ ?.jsonPrimitive
374+ ?.content,
375+ )
376+ assertEquals(
377+listOf(
378+"operator.approvals",
379+"operator.read",
380+"operator.write",
381+ ),
382+ params.scopes(),
383+ )
384+ } finally {
385+ shutdownHarness(harness, server)
386+ }
387+ }
388+268389 @Test
269390fun connect_retriesWithStoredDeviceTokenAfterSharedTokenMismatch() =
270391 runBlocking {
@@ -417,10 +538,7 @@ class GatewaySessionInvokeTest {
417538 assertEquals("bootstrap-node-token", nodeEntry?.token)
418539 assertEquals(emptyList<String>(), nodeEntry?.scopes)
419540 assertEquals("bootstrap-operator-token", operatorEntry?.token)
420- assertEquals(
421-listOf("operator.approvals", "operator.pairing", "operator.read", "operator.write"),
422- operatorEntry?.scopes,
423- )
541+ assertEquals(listOf("operator.approvals", "operator.read", "operator.write"), operatorEntry?.scopes)
424542 } finally {
425543 shutdownHarness(harness, server)
426544 }
@@ -682,6 +800,11 @@ class GatewaySessionInvokeTest {
682800683801private fun testJson(): Json = Json { ignoreUnknownKeys = true }
684802803+private fun JsonObject.scopes(): List<String> =
804+ (this["scopes"] as? JsonArray)
805+ ?.map { it.jsonPrimitive.content }
806+?: emptyList()
807+685808private fun createNodeHarness(
686809connected: CompletableDeferred<Unit>,
687810lastDisconnect: AtomicReference<String>,
@@ -714,6 +837,8 @@ class GatewaySessionInvokeTest {
714837port: Int,
715838token: String? = "test-token",
716839bootstrapToken: String? = null,
840+role: String = "node",
841+scopes: List<String> = listOf("node:invoke"),
717842 ) {
718843 session.connect(
719844 endpoint =
@@ -729,8 +854,8 @@ class GatewaySessionInvokeTest {
729854 password = null,
730855 options =
731856GatewayConnectOptions(
732- role = "node",
733- scopes = listOf("node:invoke"),
857+ role = role,
858+ scopes = scopes,
734859 caps = emptyList(),
735860 commands = emptyList(),
736861 permissions = emptyMap(),
@@ -740,7 +865,7 @@ class GatewaySessionInvokeTest {
740865 displayName = "Android Test",
741866 version = "1.0.0-test",
742867 platform = "android",
743- mode = "node",
868+ mode = role,
744869 instanceId = "android-test-instance",
745870 deviceFamily = "android",
746871 modelIdentifier = "test",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。