mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 18:41:37 +00:00
115 lines
5.1 KiB
Swift
115 lines
5.1 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
struct WatchSessionActivationGateTests {
|
|
@Test func `reachable delivery requires an accepted acknowledgment`() throws {
|
|
try requireAcceptedWatchMessageReply(["ok": true])
|
|
|
|
#expect(throws: WatchMessageAcknowledgmentError.self) {
|
|
try requireAcceptedWatchMessageReply(["ok": false, "error": "unsupported_payload"])
|
|
}
|
|
#expect(throws: WatchMessageAcknowledgmentError.self) {
|
|
try requireAcceptedWatchMessageReply(["ok": "true"])
|
|
}
|
|
#expect(throws: WatchMessageAcknowledgmentError.self) {
|
|
try requireAcceptedWatchMessageReply([:])
|
|
}
|
|
}
|
|
|
|
@Test func `startup event buffering is ordered and bounded`() {
|
|
var buffer = WatchMessagingStartupBuffer<String>(maxCount: 3)
|
|
|
|
#expect(buffer.receive("first").isEmpty)
|
|
#expect(buffer.receive("second").isEmpty)
|
|
#expect(buffer.receive("third").isEmpty)
|
|
#expect(buffer.receive("fourth").isEmpty)
|
|
#expect(buffer.markReady() == ["second", "third", "fourth"])
|
|
#expect(buffer.receive("live") == ["live"])
|
|
#expect(buffer.markReady().isEmpty)
|
|
}
|
|
|
|
@Test func `iPhone observes watch pairing and install changes`() throws {
|
|
let sourceURL = URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.appendingPathComponent("Sources/Services/WatchConnectivityTransport.swift")
|
|
let source = try String(contentsOf: sourceURL, encoding: .utf8)
|
|
|
|
#expect(source.contains("func sessionWatchStateDidChange(_ session: WCSession)"))
|
|
#expect(source.contains("paired=\\(session.isPaired) installed=\\(session.isWatchAppInstalled)"))
|
|
}
|
|
|
|
@Test func `concurrent waiters share one activation`() async throws {
|
|
let gate = WatchSessionActivationGate(timeoutNanoseconds: 1_000_000_000)
|
|
|
|
#expect(gate.beginActivation())
|
|
#expect(!gate.beginActivation())
|
|
let first = Task { try await gate.waitUntilActivated() }
|
|
let second = Task { try await gate.waitUntilActivated() }
|
|
|
|
gate.complete(activated: true, errorDescription: nil)
|
|
|
|
try await first.value
|
|
try await second.value
|
|
}
|
|
|
|
@Test func `activation timeout remains retryable`() async throws {
|
|
let gate = WatchSessionActivationGate(timeoutNanoseconds: 1_000_000)
|
|
|
|
#expect(gate.beginActivation())
|
|
await #expect(throws: WatchSessionActivationError.self) {
|
|
try await gate.waitUntilActivated()
|
|
}
|
|
|
|
#expect(gate.beginActivation())
|
|
gate.complete(activated: true, errorDescription: nil)
|
|
try await gate.waitUntilActivated()
|
|
}
|
|
|
|
@Test func `activation errors reach every waiter`() async {
|
|
let gate = WatchSessionActivationGate(timeoutNanoseconds: 1_000_000_000)
|
|
|
|
#expect(gate.beginActivation())
|
|
let first = Task { try await gate.waitUntilActivated() }
|
|
let second = Task { try await gate.waitUntilActivated() }
|
|
gate.complete(activated: false, errorDescription: "not paired")
|
|
|
|
await #expect(throws: WatchSessionActivationError.self) { try await first.value }
|
|
await #expect(throws: WatchSessionActivationError.self) { try await second.value }
|
|
}
|
|
|
|
@Test func `watch receiver acknowledges only accepted payloads and snapshots only after activation`() throws {
|
|
let iosRoot = URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
let receiverSource = try String(
|
|
contentsOf: iosRoot.appendingPathComponent(
|
|
"WatchApp/Sources/WatchConnectivityReceiver.swift"),
|
|
encoding: .utf8)
|
|
let transportSource = try String(
|
|
contentsOf: iosRoot.appendingPathComponent(
|
|
"Sources/Services/WatchConnectivityTransport.swift"),
|
|
encoding: .utf8)
|
|
let serviceSource = try String(
|
|
contentsOf: iosRoot.appendingPathComponent(
|
|
"Sources/Services/WatchMessagingService.swift"),
|
|
encoding: .utf8)
|
|
|
|
#expect(receiverSource.contains(
|
|
"let accepted = self.consumeIncomingPayload(message, transport: \"sendMessage\")"))
|
|
#expect(receiverSource.contains("accepted\n ? [\"ok\": true]"))
|
|
#expect(receiverSource.contains(
|
|
": [\"ok\": false, \"error\": \"unsupported_payload\"]"))
|
|
#expect(receiverSource.contains(
|
|
"private func consumeIncomingPayload(_ payload: [String: Any], transport: String) -> Bool"))
|
|
#expect(receiverSource.contains("guard activationState == .activated else { return }"))
|
|
#expect(receiverSource.contains("try requireAcceptedWatchMessageReply(reply)"))
|
|
#expect(transportSource.contains("try requireAcceptedWatchMessageReply(reply)"))
|
|
let callbackRegistration = try #require(
|
|
serviceSource.range(of: "self.transport.setAppCommandHandler"))
|
|
let activation = try #require(serviceSource.range(of: "self.transport.activate()"))
|
|
#expect(callbackRegistration.lowerBound < activation.lowerBound)
|
|
}
|
|
}
|