diff --git a/apps/macos/Package.swift b/apps/macos/Package.swift index 7f020f4cb..ee9aef66f 100644 --- a/apps/macos/Package.swift +++ b/apps/macos/Package.swift @@ -62,7 +62,7 @@ let package = Package( ]), .testTarget( name: "ClawdisIPCTests", - dependencies: ["ClawdisIPC", "Clawdis"], + dependencies: ["ClawdisIPC", "Clawdis", "ClawdisProtocol"], swiftSettings: [ .enableUpcomingFeature("StrictConcurrency"), .enableExperimentalFeature("SwiftTesting"), diff --git a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift index a9651511f..e1737d652 100644 --- a/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift +++ b/apps/macos/Sources/ClawdisProtocol/GatewayModels.swift @@ -500,7 +500,12 @@ public enum GatewayFrame: Codable { } private static func decodePayload(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T { - let data = try JSONSerialization.data(withJSONObject: raw) + // Re-encode the already-decoded map using `JSONEncoder` instead of + // `JSONSerialization` because `AnyCodable` values are not bridged to + // Objective-C types and `JSONSerialization` throws an ObjC exception, + // crashing the app (seen on macOS 26.1). `JSONEncoder` understands + // `Encodable` values and stays in Swift land. + let data = try JSONEncoder().encode(raw) let decoder = JSONDecoder() return try decoder.decode(T.self, from: data) } diff --git a/apps/macos/Tests/ClawdisIPCTests/GatewayFrameDecodeTests.swift b/apps/macos/Tests/ClawdisIPCTests/GatewayFrameDecodeTests.swift new file mode 100644 index 000000000..40405f459 --- /dev/null +++ b/apps/macos/Tests/ClawdisIPCTests/GatewayFrameDecodeTests.swift @@ -0,0 +1,31 @@ +import Foundation +import Testing +import ClawdisProtocol + +@Suite struct GatewayFrameDecodeTests { + @Test func decodesEventFrameWithAnyCodablePayload() throws { + let json = """ + { + "type": "event", + "event": "presence", + "payload": { "foo": "bar", "count": 1 }, + "seq": 7 + } + """ + + let frame = try JSONDecoder().decode(GatewayFrame.self, from: Data(json.utf8)) + + #expect({ + if case .event = frame { true } else { false } + }(), "expected .event frame") + + guard case let .event(evt) = frame else { + return + } + + let payload = evt.payload?.value as? [String: AnyCodable] + #expect(payload?["foo"]?.value as? String == "bar") + #expect(payload?["count"]?.value as? Int == 1) + #expect(evt.seq == 7) + } +}