refactor: remove bridge protocol
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite(.serialized)
|
||||
struct BridgeServerTests {
|
||||
@Test func bridgeServerExercisesPaths() async {
|
||||
let server = BridgeServer()
|
||||
await server.exerciseForTesting()
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,13 @@ struct ClawdbotConfigFileTests {
|
||||
ClawdbotConfigFile.saveDict([
|
||||
"gateway": [
|
||||
"remote": [
|
||||
"url": "ws://bridge.ts.net:19999",
|
||||
"url": "ws://gateway.ts.net:19999",
|
||||
],
|
||||
],
|
||||
])
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort() == 19999)
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort(matchingHost: "bridge.ts.net") == 19999)
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort(matchingHost: "bridge") == 19999)
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort(matchingHost: "gateway.ts.net") == 19999)
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort(matchingHost: "gateway") == 19999)
|
||||
#expect(ClawdbotConfigFile.remoteGatewayPort(matchingHost: "other.ts.net") == nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ struct GatewayDiscoveryModelTests {
|
||||
lanHost: "other.local",
|
||||
tailnetDns: "other.tailnet.example",
|
||||
displayName: "Other Mac",
|
||||
serviceName: "other-bridge",
|
||||
serviceName: "other-gateway",
|
||||
local: local))
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ struct GatewayDiscoveryModelTests {
|
||||
lanHost: nil,
|
||||
tailnetDns: nil,
|
||||
displayName: nil,
|
||||
serviceName: "studio-bridge",
|
||||
serviceName: "studio-gateway",
|
||||
local: local))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
import Network
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite struct MacNodeBridgeDiscoveryTests {
|
||||
@MainActor
|
||||
@Test func loopbackBridgePortDefaultsAndOverrides() {
|
||||
withEnv("CLAWDBOT_BRIDGE_PORT", value: nil) {
|
||||
#expect(MacNodeModeCoordinator.loopbackBridgePort() == 18790)
|
||||
}
|
||||
withEnv("CLAWDBOT_BRIDGE_PORT", value: "19991") {
|
||||
#expect(MacNodeModeCoordinator.loopbackBridgePort() == 19991)
|
||||
}
|
||||
withEnv("CLAWDBOT_BRIDGE_PORT", value: "not-a-port") {
|
||||
#expect(MacNodeModeCoordinator.loopbackBridgePort() == 18790)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func probeEndpointSucceedsForOpenPort() async throws {
|
||||
let listener = try NWListener(using: .tcp, on: .any)
|
||||
listener.newConnectionHandler = { connection in
|
||||
connection.cancel()
|
||||
}
|
||||
listener.start(queue: DispatchQueue(label: "com.clawdbot.tests.bridge-listener"))
|
||||
try await waitForListenerReady(listener, timeoutSeconds: 1.0)
|
||||
|
||||
guard let port = listener.port else {
|
||||
listener.cancel()
|
||||
throw TestError(message: "listener port missing")
|
||||
}
|
||||
|
||||
let endpoint = NWEndpoint.hostPort(host: "127.0.0.1", port: port)
|
||||
let ok = await MacNodeModeCoordinator.probeEndpoint(endpoint, timeoutSeconds: 0.6)
|
||||
listener.cancel()
|
||||
#expect(ok == true)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func probeEndpointFailsForClosedPort() async throws {
|
||||
let port = try reserveEphemeralPort()
|
||||
let endpoint = NWEndpoint.hostPort(host: "127.0.0.1", port: port)
|
||||
let ok = await MacNodeModeCoordinator.probeEndpoint(endpoint, timeoutSeconds: 0.4)
|
||||
#expect(ok == false)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func remoteBridgePortUsesMatchingRemoteUrlPort() {
|
||||
let configPath = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-config-\(UUID().uuidString)")
|
||||
.appendingPathComponent("clawdbot.json")
|
||||
.path
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
let prevTarget = defaults.string(forKey: remoteTargetKey)
|
||||
defer {
|
||||
if let prevTarget {
|
||||
defaults.set(prevTarget, forKey: remoteTargetKey)
|
||||
} else {
|
||||
defaults.removeObject(forKey: remoteTargetKey)
|
||||
}
|
||||
}
|
||||
|
||||
withEnv("CLAWDBOT_CONFIG_PATH", value: configPath) {
|
||||
withEnv("CLAWDBOT_GATEWAY_PORT", value: "20000") {
|
||||
defaults.set("user@bridge.ts.net", forKey: remoteTargetKey)
|
||||
ClawdbotConfigFile.saveDict([
|
||||
"gateway": [
|
||||
"remote": [
|
||||
"url": "ws://bridge.ts.net:25000",
|
||||
],
|
||||
],
|
||||
])
|
||||
#expect(MacNodeModeCoordinator.remoteBridgePort() == 25001)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func remoteBridgePortFallsBackWhenRemoteUrlHostMismatch() {
|
||||
let configPath = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clawdbot-config-\(UUID().uuidString)")
|
||||
.appendingPathComponent("clawdbot.json")
|
||||
.path
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
let prevTarget = defaults.string(forKey: remoteTargetKey)
|
||||
defer {
|
||||
if let prevTarget {
|
||||
defaults.set(prevTarget, forKey: remoteTargetKey)
|
||||
} else {
|
||||
defaults.removeObject(forKey: remoteTargetKey)
|
||||
}
|
||||
}
|
||||
|
||||
withEnv("CLAWDBOT_CONFIG_PATH", value: configPath) {
|
||||
withEnv("CLAWDBOT_GATEWAY_PORT", value: "20000") {
|
||||
defaults.set("user@other.ts.net", forKey: remoteTargetKey)
|
||||
ClawdbotConfigFile.saveDict([
|
||||
"gateway": [
|
||||
"remote": [
|
||||
"url": "ws://bridge.ts.net:25000",
|
||||
],
|
||||
],
|
||||
])
|
||||
#expect(MacNodeModeCoordinator.remoteBridgePort() == 20001)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TestError: Error {
|
||||
let message: String
|
||||
}
|
||||
|
||||
private struct ListenerTimeoutError: Error {}
|
||||
|
||||
private func waitForListenerReady(_ listener: NWListener, timeoutSeconds: Double) async throws {
|
||||
try await withThrowingTaskGroup(of: Void.self) { group in
|
||||
group.addTask {
|
||||
try await withCheckedThrowingContinuation { cont in
|
||||
final class ListenerState: @unchecked Sendable {
|
||||
let lock = NSLock()
|
||||
var finished = false
|
||||
}
|
||||
let state = ListenerState()
|
||||
let finish: @Sendable (Result<Void, Error>) -> Void = { result in
|
||||
state.lock.lock()
|
||||
defer { state.lock.unlock() }
|
||||
guard !state.finished else { return }
|
||||
state.finished = true
|
||||
cont.resume(with: result)
|
||||
}
|
||||
|
||||
listener.stateUpdateHandler = { state in
|
||||
switch state {
|
||||
case .ready:
|
||||
finish(.success(()))
|
||||
case let .failed(err):
|
||||
finish(.failure(err))
|
||||
case .cancelled:
|
||||
finish(.failure(ListenerTimeoutError()))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
group.addTask {
|
||||
try await Task.sleep(nanoseconds: UInt64(timeoutSeconds * 1_000_000_000))
|
||||
throw ListenerTimeoutError()
|
||||
}
|
||||
_ = try await group.next()
|
||||
group.cancelAll()
|
||||
}
|
||||
}
|
||||
|
||||
private func withEnv(_ key: String, value: String?, _ body: () -> Void) {
|
||||
let existing = getenv(key).map { String(cString: $0) }
|
||||
if let value {
|
||||
setenv(key, value, 1)
|
||||
} else {
|
||||
unsetenv(key)
|
||||
}
|
||||
defer {
|
||||
if let existing {
|
||||
setenv(key, existing, 1)
|
||||
} else {
|
||||
unsetenv(key)
|
||||
}
|
||||
}
|
||||
body()
|
||||
}
|
||||
|
||||
private func reserveEphemeralPort() throws -> NWEndpoint.Port {
|
||||
let fd = socket(AF_INET, SOCK_STREAM, 0)
|
||||
if fd < 0 {
|
||||
throw TestError(message: "socket failed")
|
||||
}
|
||||
defer { close(fd) }
|
||||
|
||||
var addr = sockaddr_in()
|
||||
addr.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
|
||||
addr.sin_family = sa_family_t(AF_INET)
|
||||
addr.sin_port = in_port_t(0)
|
||||
addr.sin_addr = in_addr(s_addr: inet_addr("127.0.0.1"))
|
||||
|
||||
let bindResult = withUnsafePointer(to: &addr) { pointer -> Int32 in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
Darwin.bind(fd, $0, socklen_t(MemoryLayout<sockaddr_in>.size))
|
||||
}
|
||||
}
|
||||
if bindResult != 0 {
|
||||
throw TestError(message: "bind failed")
|
||||
}
|
||||
|
||||
var resolved = sockaddr_in()
|
||||
var length = socklen_t(MemoryLayout<sockaddr_in>.size)
|
||||
let nameResult = withUnsafeMutablePointer(to: &resolved) { pointer -> Int32 in
|
||||
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
||||
getsockname(fd, $0, &length)
|
||||
}
|
||||
}
|
||||
if nameResult != 0 {
|
||||
throw TestError(message: "getsockname failed")
|
||||
}
|
||||
|
||||
let port = UInt16(bigEndian: resolved.sin_port)
|
||||
guard let endpointPort = NWEndpoint.Port(rawValue: port), endpointPort.rawValue != 0 else {
|
||||
throw TestError(message: "ephemeral port missing")
|
||||
}
|
||||
return endpointPort
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdbot
|
||||
|
||||
@Suite
|
||||
struct MacNodeBridgeSessionTests {
|
||||
@Test func sendEventThrowsWhenNotConnected() async {
|
||||
let session = MacNodeBridgeSession()
|
||||
|
||||
do {
|
||||
try await session.sendEvent(event: "test", payloadJSON: "{}")
|
||||
Issue.record("Expected sendEvent to throw when disconnected")
|
||||
} catch {
|
||||
let ns = error as NSError
|
||||
#expect(ns.domain == "Bridge")
|
||||
#expect(ns.code == 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,15 +20,15 @@ struct WideAreaGatewayDiscoveryTests {
|
||||
let nameserver = args.first(where: { $0.hasPrefix("@") }) ?? ""
|
||||
if recordType == "PTR" {
|
||||
if nameserver == "@100.123.224.76" {
|
||||
return "steipetacstudio-bridge._clawdbot-bridge._tcp.clawdbot.internal.\n"
|
||||
return "steipetacstudio-gateway._clawdbot-gateway._tcp.clawdbot.internal.\n"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if recordType == "SRV" {
|
||||
return "0 0 18790 steipetacstudio.clawdbot.internal."
|
||||
return "0 0 18789 steipetacstudio.clawdbot.internal."
|
||||
}
|
||||
if recordType == "TXT" {
|
||||
return "\"displayName=Peter\\226\\128\\153s Mac Studio (Clawdbot)\" \"transport=bridge\" \"bridgePort=18790\" \"gatewayPort=18789\" \"tailnetDns=peters-mac-studio-1.sheep-coho.ts.net\" \"cliPath=/Users/steipete/clawdbot/src/entry.ts\""
|
||||
return "\"displayName=Peter\\226\\128\\153s Mac Studio (Clawdbot)\" \"gatewayPort=18789\" \"tailnetDns=peters-mac-studio-1.sheep-coho.ts.net\" \"cliPath=/Users/steipete/clawdbot/src/entry.ts\""
|
||||
}
|
||||
return ""
|
||||
})
|
||||
@@ -41,7 +41,7 @@ struct WideAreaGatewayDiscoveryTests {
|
||||
let beacon = beacons[0]
|
||||
let expectedDisplay = "Peter\u{2019}s Mac Studio (Clawdbot)"
|
||||
#expect(beacon.displayName == expectedDisplay)
|
||||
#expect(beacon.bridgePort == 18790)
|
||||
#expect(beacon.port == 18789)
|
||||
#expect(beacon.gatewayPort == 18789)
|
||||
#expect(beacon.tailnetDns == "peters-mac-studio-1.sheep-coho.ts.net")
|
||||
#expect(beacon.cliPath == "/Users/steipete/clawdbot/src/entry.ts")
|
||||
|
||||
Reference in New Issue
Block a user