mirror of https://github.com/openclaw/openclaw.git
fix: harden mac gateway attach smoke
This commit is contained in:
parent
f9281d1b9d
commit
92fb0caf35
|
|
@ -44,6 +44,7 @@ final class GatewayProcessManager {
|
|||
private var logRefreshTask: Task<Void, Never>?
|
||||
#if DEBUG
|
||||
private var testingConnection: GatewayConnection?
|
||||
private var testingSkipControlChannelRefresh = false
|
||||
#endif
|
||||
private let logger = Logger(subsystem: "ai.openclaw", category: "gateway.process")
|
||||
|
||||
|
|
@ -364,6 +365,11 @@ final class GatewayProcessManager {
|
|||
}
|
||||
|
||||
private func refreshControlChannelIfNeeded(reason: String) {
|
||||
#if DEBUG
|
||||
if self.testingSkipControlChannelRefresh {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
switch ControlChannel.shared.state {
|
||||
case .connected, .connecting:
|
||||
return
|
||||
|
|
@ -421,6 +427,10 @@ extension GatewayProcessManager {
|
|||
self.testingConnection = connection
|
||||
}
|
||||
|
||||
func setTestingSkipControlChannelRefresh(_ skip: Bool) {
|
||||
self.testingSkipControlChannelRefresh = skip
|
||||
}
|
||||
|
||||
func setTestingDesiredActive(_ active: Bool) {
|
||||
self.desiredActive = active
|
||||
}
|
||||
|
|
@ -428,5 +438,9 @@ extension GatewayProcessManager {
|
|||
func setTestingLastFailureReason(_ reason: String?) {
|
||||
self.lastFailureReason = reason
|
||||
}
|
||||
|
||||
func _testAttachExistingGatewayIfAvailable() async -> Bool {
|
||||
await self.attachExistingGatewayIfAvailable()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ actor PortGuardian {
|
|||
|
||||
private var records: [Record] = []
|
||||
private let logger = Logger(subsystem: "ai.openclaw", category: "portguard")
|
||||
#if DEBUG
|
||||
private var testingDescriptors: [Int: Descriptor] = [:]
|
||||
#endif
|
||||
private nonisolated static let appSupportDir: URL = {
|
||||
let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
return base.appendingPathComponent("OpenClaw", isDirectory: true)
|
||||
|
|
@ -130,6 +133,11 @@ actor PortGuardian {
|
|||
}
|
||||
|
||||
func describe(port: Int) async -> Descriptor? {
|
||||
#if DEBUG
|
||||
if let descriptor = self.testingDescriptors[port] {
|
||||
return descriptor
|
||||
}
|
||||
#endif
|
||||
guard let listener = await self.listeners(on: port).first else { return nil }
|
||||
let path = Self.executablePath(for: listener.pid)
|
||||
return Descriptor(pid: listener.pid, command: listener.command, executablePath: path)
|
||||
|
|
@ -406,6 +414,18 @@ actor PortGuardian {
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
extension PortGuardian {
|
||||
func setTestingDescriptor(_ descriptor: Descriptor?, forPort port: Int) {
|
||||
if let descriptor {
|
||||
self.testingDescriptors[port] = descriptor
|
||||
} else {
|
||||
self.testingDescriptors.removeValue(forKey: port)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
extension PortGuardian {
|
||||
static func _testParseListeners(_ text: String) -> [(
|
||||
|
|
|
|||
|
|
@ -35,4 +35,92 @@ struct GatewayProcessManagerTests {
|
|||
#expect(ready)
|
||||
#expect(manager.lastFailureReason == nil)
|
||||
}
|
||||
|
||||
@Test func `attaches to existing gateway without spawning launchd`() async throws {
|
||||
let healthData = Data(
|
||||
"""
|
||||
{
|
||||
"ok": true,
|
||||
"ts": 1,
|
||||
"durationMs": 0,
|
||||
"channels": {
|
||||
"telegram": {
|
||||
"configured": true,
|
||||
"linked": true,
|
||||
"authAgeMs": 60000
|
||||
}
|
||||
},
|
||||
"channelOrder": ["telegram"],
|
||||
"channelLabels": {
|
||||
"telegram": "Telegram"
|
||||
},
|
||||
"heartbeatSeconds": 30,
|
||||
"sessions": {
|
||||
"path": "/tmp/sessions",
|
||||
"count": 1,
|
||||
"recent": []
|
||||
}
|
||||
}
|
||||
""".utf8)
|
||||
let session = GatewayTestWebSocketSession(
|
||||
taskFactory: {
|
||||
GatewayTestWebSocketTask(
|
||||
sendHook: { task, message, sendIndex in
|
||||
guard sendIndex > 0 else { return }
|
||||
guard let id = GatewayWebSocketTestSupport.requestID(from: message) else { return }
|
||||
let json = """
|
||||
{
|
||||
"type": "res",
|
||||
"id": "\(id)",
|
||||
"ok": true,
|
||||
"payload": \(String(decoding: healthData, as: UTF8.self))
|
||||
}
|
||||
"""
|
||||
task.emitReceiveSuccess(.data(Data(json.utf8)))
|
||||
})
|
||||
})
|
||||
let url = try #require(URL(string: "ws://example.invalid"))
|
||||
let connection = GatewayConnection(
|
||||
configProvider: { (url: url, token: nil, password: nil) },
|
||||
sessionBox: WebSocketSessionBox(session: session))
|
||||
let port = GatewayEnvironment.gatewayPort()
|
||||
let descriptor = PortGuardian.Descriptor(
|
||||
pid: 4242,
|
||||
command: "openclaw-gateway",
|
||||
executablePath: "/tmp/openclaw-gateway")
|
||||
|
||||
let manager = GatewayProcessManager.shared
|
||||
await PortGuardian.shared.setTestingDescriptor(descriptor, forPort: port)
|
||||
manager.setTestingConnection(connection)
|
||||
manager.setTestingSkipControlChannelRefresh(true)
|
||||
manager.setTestingLastFailureReason("stale")
|
||||
|
||||
func cleanup() async {
|
||||
await PortGuardian.shared.setTestingDescriptor(nil, forPort: port)
|
||||
manager.setTestingConnection(nil)
|
||||
manager.setTestingSkipControlChannelRefresh(false)
|
||||
manager.setTestingDesiredActive(false)
|
||||
manager.setTestingLastFailureReason(nil)
|
||||
}
|
||||
|
||||
do {
|
||||
let attached = await manager._testAttachExistingGatewayIfAvailable()
|
||||
#expect(attached)
|
||||
#expect(manager.lastFailureReason == nil)
|
||||
guard case let .attachedExisting(statusDetails) = manager.status else {
|
||||
Issue.record("expected attachedExisting status")
|
||||
await cleanup()
|
||||
return
|
||||
}
|
||||
let details = try #require(statusDetails)
|
||||
#expect(details.contains("port \(port)"))
|
||||
#expect(details.contains("Telegram linked"))
|
||||
#expect(details.contains("auth 1m"))
|
||||
#expect(details.contains("pid 4242 openclaw-gateway @ /tmp/openclaw-gateway"))
|
||||
await cleanup()
|
||||
} catch {
|
||||
await cleanup()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,19 @@ import Testing
|
|||
@testable import OpenClaw
|
||||
|
||||
@Suite(.serialized) struct NodeServiceManagerTests {
|
||||
@Test func `builds node service commands with current CLI shape`() throws {
|
||||
let tmp = try makeTempDirForTests()
|
||||
CommandResolver.setProjectRoot(tmp.path)
|
||||
@Test func `builds node service commands with current CLI shape`() async throws {
|
||||
try await TestIsolation.withUserDefaultsValues(["openclaw.gatewayProjectRootPath": nil]) {
|
||||
let tmp = try makeTempDirForTests()
|
||||
CommandResolver.setProjectRoot(tmp.path)
|
||||
|
||||
let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw")
|
||||
try makeExecutableForTests(at: openclawPath)
|
||||
let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw")
|
||||
try makeExecutableForTests(at: openclawPath)
|
||||
|
||||
let start = NodeServiceManager._testServiceCommand(["start"])
|
||||
#expect(start == [openclawPath.path, "node", "start", "--json"])
|
||||
let start = NodeServiceManager._testServiceCommand(["start"])
|
||||
#expect(start == [openclawPath.path, "node", "start", "--json"])
|
||||
|
||||
let stop = NodeServiceManager._testServiceCommand(["stop"])
|
||||
#expect(stop == [openclawPath.path, "node", "stop", "--json"])
|
||||
let stop = NodeServiceManager._testServiceCommand(["stop"])
|
||||
#expect(stop == [openclawPath.path, "node", "stop", "--json"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue