Files
openclaw/apps/ios/Tests/DesktopHubScreenTests.swift
Peter Steinberger e5fca42952 fix(apps): per-session desktop button opens that session's machine (#123412)
* fix(apps): open session desktop on its machine

* fix(ui): scope the desktop session lookup to the key's own agent

`sessions.list` has no exact-key filter, so the viewer resolves a `session=`
parameter by searching for the key and matching it exactly in the response. A
key that prefixes longer ones — `agent:main:main` alongside user-named sessions
that start the same way — could push the exact row outside a five-row page and
silently fall back to the picker.

Session keys encode their agent and the list API accepts `agentId`, so scope the
search to that agent and widen the page.

* refactor(ui): stop parking a session key in the desktop environment id

Document-mode inventory failures stashed the requested session key in
`environmentId` purely so the Retry button's non-null guard would pass, even
though document-mode retry refreshes the inventory and never reads the value.
A session key only names a machine once the inventory loads, so it now stays
out of `environmentId`, and the retry branch that ignores it runs before the
guard. Adds E2E coverage for recovering a session-preselected desktop.

* fix(ui): resolve the desktop session with an exact-key lookup

The session-preselect path searched `sessions.list` and scoped the search to
the key's own agent, but a bounded search cannot rule a key out: 25 newer
same-agent sessions sharing the requested key's prefix would push the exact row
off the page, and the viewer would report the source as unavailable for a
session that exists. `sessions.describe` is the exact-key operation and already
projects placement, so the panel calls it directly and the app-root resolver
plumbing goes away with it.

* refactor(ui): move the desktop document keyboard bridge into its own controller

`desktop-panel.ts` crossed the 700-line cap. The mobile keyboard bridge — the
padded sentinel, the value diffing that turns composed input into backspaces
and text, and the field focus/reset helpers — is a self-contained concern, so
it moves into a `DesktopMobileKeyboard` controller alongside the existing
fullscreen controller instead of taking a `max-lines` suppression.

* test(ios): drain text fields instead of assuming a delete burst lands

`testReleaseChatScreenshot` typed a 5-character probe, sent 5 deletes in one
`typeText`, then asserted the field was empty. CI dropped one synthetic
keystroke under simulator load and the assertion failed with a leftover "f".
XCUITest makes no lossless-burst guarantee, so clearing now re-sends against
whatever the field actually still holds, bounded. The two gateway-setup fields
that overtyped through the same burst use the helper for the same reason.

* fix(ui): keep the chat placement owner out of the startup chunk

`desktop-document-mode.ts` is imported by bootstrap, so importing the chat
placement owner from it pulled the chat page's dependency tree into the startup
bundle and pushed startup JS past its gzip budget (331075 B against a 330507 B
allowance). The route module now only parses the URL; resolving a session to its
machine moves next to the lazily loaded desktop panel, which is the only caller.
Startup JS is back to 329710 B.
2026-08-13 23:33:19 -07:00

94 lines
3.7 KiB
Swift

import Foundation
import Testing
@testable import OpenClaw
@testable import OpenClawKit
@MainActor
struct DesktopHubScreenTests {
private static func makeConfig(
url: URL,
token: String? = nil,
password: String? = nil) -> GatewayConnectConfig
{
GatewayConnectConfig(
url: url,
stableID: "manual|gateway.example.com|443",
tls: nil,
token: token,
bootstrapToken: nil,
password: password,
nodeOptions: GatewayConnectOptions(
role: "node",
scopes: [],
caps: [],
commands: [],
permissions: [:],
clientId: "ios",
clientMode: "node",
clientDisplayName: "Phone"))
}
@Test func `standalone desktop URL uses document mode without credentials`() throws {
let config = try Self.makeConfig(
url: #require(URL(string: "wss://gateway.example.com:8443/openclaw")),
token: "secret-token",
password: "secret-password")
let url = DesktopHubScreen.desktopURL(config: config, source: nil, session: nil)
#expect(url?.absoluteString == "https://gateway.example.com:8443/openclaw/?view=desktop")
#expect(url?.absoluteString.contains("secret-token") == false)
#expect(url?.absoluteString.contains("secret-password") == false)
}
@Test func `session desktop URL includes the session key`() throws {
let config = try Self.makeConfig(
url: #require(URL(string: "ws://192.168.1.10:18789")),
token: "secret-token")
let url = DesktopHubScreen.desktopURL(
config: config,
source: nil,
session: "agent:main:mobile session")
#expect(url?.absoluteString == "http://192.168.1.10:18789/?view=desktop&session=agent%3Amain%3Amobile%20session")
#expect(url?.absoluteString.contains("secret-token") == false)
}
@Test func `explicit desktop source is retained alongside the session`() throws {
let config = try Self.makeConfig(url: #require(URL(string: "wss://gateway.example.com")))
let url = DesktopHubScreen.desktopURL(
config: config,
source: "node:worker-1",
session: "agent:main:mobile")
#expect(url?.absoluteString == "https://gateway.example.com/?view=desktop&source=node%3Aworker-1&session=agent%3Amain%3Amobile")
}
@Test func `empty desktop source is omitted`() throws {
let config = try Self.makeConfig(url: #require(URL(string: "wss://gateway.example.com")))
let url = DesktopHubScreen.desktopURL(config: config, source: " ", session: " ")
#expect(url?.absoluteString == "https://gateway.example.com/?view=desktop")
}
@Test func `desktop auth script carries credentials outside the URL`() throws {
let config = try Self.makeConfig(
url: #require(URL(string: "wss://gateway.example.com")),
token: " secret-token ",
password: "secret-password")
let url = DesktopHubScreen.desktopURL(config: config, source: "gateway")
let script = DesktopHubScreen.desktopAuthUserScript(config: config, source: "gateway")
#expect(url?.absoluteString == "https://gateway.example.com/?view=desktop&source=gateway")
#expect(url?.absoluteString.contains("secret-token") == false)
#expect(url?.absoluteString.contains("secret-password") == false)
#expect(script?.contains("__OPENCLAW_NATIVE_CONTROL_AUTH__") == true)
#expect(script?.contains("\"token\":\"secret-token\"") == true)
#expect(script?.contains("\"password\":\"secret-password\"") == true)
}
}