From e1a12c569292f617394bf19bbaa822057142afb0 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 11 Dec 2025 18:47:57 -0300 Subject: [PATCH] test(realtime): add tests for optional refs handling in message serialization Add comprehensive tests for nil ref scenarios to verify and document that optional ref and joinRef fields are properly handled in JSON encoding/decoding. The Swift SDK already has the correct implementation with optional types, but these tests prevent regressions and ensure SDK parity with other language implementations. - Added tests for messages with nil refs - Added tests for heartbeat messages with nil joinRef - Added tests for JSON encoding/decoding with nil values - Added documentation comments to RealtimeMessageV2 struct Closes SDK-532 --- Sources/Realtime/RealtimeMessageV2.swift | 6 ++ .../RealtimeMessageV2Tests.swift | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/Sources/Realtime/RealtimeMessageV2.swift b/Sources/Realtime/RealtimeMessageV2.swift index ae111ef55..1be076eaa 100644 --- a/Sources/Realtime/RealtimeMessageV2.swift +++ b/Sources/Realtime/RealtimeMessageV2.swift @@ -1,7 +1,13 @@ import Foundation +/// A message sent over the Realtime WebSocket connection. +/// +/// Both `joinRef` and `ref` are optional because certain messages like heartbeats +/// don't require a join reference as they don't refer to a specific channel. public struct RealtimeMessageV2: Hashable, Codable, Sendable { + /// Optional join reference. Nil for messages like heartbeats that don't belong to a specific channel. public let joinRef: String? + /// Optional message reference. Can be nil for certain message types. public let ref: String? public let topic: String public let event: String diff --git a/Tests/RealtimeTests/RealtimeMessageV2Tests.swift b/Tests/RealtimeTests/RealtimeMessageV2Tests.swift index 0df944a6e..f95304e10 100644 --- a/Tests/RealtimeTests/RealtimeMessageV2Tests.swift +++ b/Tests/RealtimeTests/RealtimeMessageV2Tests.swift @@ -76,4 +76,80 @@ final class RealtimeMessageV2Tests: XCTestCase { joinRef: nil, ref: nil, topic: "topic", event: "unknown_event", payload: payloadWithNoStatus) XCTAssertNil(unknownEventMessage._eventType) } + + func testMessageWithNilRefs() throws { + let message = RealtimeMessageV2( + joinRef: nil, + ref: nil, + topic: "phoenix", + event: "heartbeat", + payload: [:] + ) + // Verify JSON encoding works + let encoded = try JSONEncoder().encode(message) + let decoded = try JSONDecoder().decode(RealtimeMessageV2.self, from: encoded) + XCTAssertNil(decoded.joinRef) + XCTAssertNil(decoded.ref) + XCTAssertEqual(decoded.topic, "phoenix") + XCTAssertEqual(decoded.event, "heartbeat") + } + + func testHeartbeatMessageWithNilJoinRef() throws { + let message = RealtimeMessageV2( + joinRef: nil, // Heartbeats don't have joinRef + ref: "123", + topic: "phoenix", + event: "heartbeat", + payload: [:] + ) + let encoded = try JSONEncoder().encode(message) + let decoded = try JSONDecoder().decode(RealtimeMessageV2.self, from: encoded) + XCTAssertNil(decoded.joinRef) + XCTAssertEqual(decoded.ref, "123") + } + + func testMessageJSONEncodingWithNilRefs() throws { + let message = RealtimeMessageV2( + joinRef: nil, + ref: nil, + topic: "test", + event: "custom", + payload: ["key": "value"] + ) + let encoded = try JSONEncoder().encode(message) + let jsonString = String(data: encoded, encoding: .utf8)! + // Verify nil values are encoded as null in JSON + XCTAssertTrue(jsonString.contains("\"join_ref\":null") || !jsonString.contains("join_ref")) + XCTAssertTrue(jsonString.contains("\"ref\":null") || !jsonString.contains("ref")) + } + + func testMessageWithBothRefAndJoinRef() throws { + let message = RealtimeMessageV2( + joinRef: "join-456", + ref: "ref-789", + topic: "room:lobby", + event: "join", + payload: ["user_id": "123"] + ) + let encoded = try JSONEncoder().encode(message) + let decoded = try JSONDecoder().decode(RealtimeMessageV2.self, from: encoded) + XCTAssertEqual(decoded.joinRef, "join-456") + XCTAssertEqual(decoded.ref, "ref-789") + XCTAssertEqual(decoded.topic, "room:lobby") + } + + func testMessageWithRefButNilJoinRef() throws { + let message = RealtimeMessageV2( + joinRef: nil, + ref: "ref-999", + topic: "room:lobby", + event: "leave", + payload: [:] + ) + let encoded = try JSONEncoder().encode(message) + let decoded = try JSONDecoder().decode(RealtimeMessageV2.self, from: encoded) + XCTAssertNil(decoded.joinRef) + XCTAssertEqual(decoded.ref, "ref-999") + XCTAssertEqual(decoded.topic, "room:lobby") + } }