@@ -15,15 +15,36 @@ import java.util.Collections
1515import kotlin.time.Duration
1616import kotlin.time.Duration.Companion.seconds
1717
18+ /* *
19+ * Callbacks for [MockWebSocket]. All fields are optional.
20+ *
21+ * When a callback is set it receives the event immediately (synchronous, on the SDK's thread).
22+ * When a callback is null the event is queued for the corresponding `await*` method instead.
23+ * The two styles cannot be mixed for the same event type.
24+ */
1825class WebSocketMockConfig {
26+ /* * Called for every connection attempt. Set to respond inline; leave null to use [MockWebSocket.awaitConnectionAttempt]. */
1927 var onConnectionAttempt: ((PendingConnection ) -> Unit )? = null
28+ /* * Called for every decoded protocol message from the SDK. Leave null to use [MockWebSocket.awaitNextMessageFromClient]. */
2029 var onMessageFromClient: ((ProtocolMessage ) -> Unit )? = null
30+ /* * Raw text frame callback — rarely needed; prefer [onMessageFromClient]. */
2131 var onTextDataFrame: ((String ) -> Unit )? = null
32+ /* * Raw binary frame callback — rarely needed; prefer [onMessageFromClient]. */
2233 var onBinaryDataFrame: ((ByteArray ) -> Unit )? = null
2334}
2435
36+ /* *
37+ * Fake WebSocket transport for SDK unit tests. Install via [installOn] or `TestRealtimeClient`.
38+ *
39+ * Two usage styles for connection/message handling (cannot mix per event type):
40+ * - **Callback** (`onConnectionAttempt`, `onMessageFromClient` in [WebSocketMockConfig]): handle
41+ * inline, synchronously on the SDK thread. Preferred for single-behaviour setups.
42+ * - **Await** ([awaitConnectionAttempt], [awaitNextMessageFromClient]): suspend until the SDK
43+ * triggers the event. Required when initial and reconnection attempts need different behaviour.
44+ */
2545class MockWebSocket (config : WebSocketMockConfig = WebSocketMockConfig ()) {
2646 private val _events = Collections .synchronizedList(mutableListOf<MockEvent >())
47+ /* * Snapshot of all events recorded since construction (or last [reset]). */
2748 val events: List <MockEvent > get() = _events .toList()
2849
2950 private var _pendingConnections = Channel <PendingConnection >(Channel .UNLIMITED )
@@ -82,31 +103,37 @@ class MockWebSocket(config: WebSocketMockConfig = WebSocketMockConfig()) {
82103 },
83104 )
84105
106+ /* * Wire this mock into [options] so the SDK uses it instead of a real WebSocket. */
85107 fun installOn (options : DebugOptions ) {
86108 options.webSocketEngineFactory = engineFactory
87109 }
88110
111+ /* * Suspend until the SDK opens a new WebSocket connection. Only usable when [WebSocketMockConfig.onConnectionAttempt] is null. */
89112 suspend fun awaitConnectionAttempt (timeout : Duration = 5.seconds): PendingConnection =
90113 withContext(Dispatchers .Default .limitedParallelism(1 )) {
91114 withTimeout(timeout) { _pendingConnections .receive() }
92115 }
93116
117+ /* * Suspend until the SDK sends a protocol message to the server. Only usable when [WebSocketMockConfig.onMessageFromClient] is null. */
94118 suspend fun awaitNextMessageFromClient (timeout : Duration = 5.seconds): ProtocolMessage =
95119 withContext(Dispatchers .Default .limitedParallelism(1 )) {
96120 withTimeout(timeout) { _messagesFromClient .receive() }
97121 }
98122
123+ /* * Suspend until the SDK sends a WebSocket close frame. */
99124 suspend fun awaitClientClose (timeout : Duration = 5.seconds): MockEvent .ClientClose =
100125 withContext(Dispatchers .Default .limitedParallelism(1 )) {
101126 withTimeout(timeout) { _clientCloseEvents .receive() }
102127 }
103128
129+ /* * Deliver [message] from the fake server to the SDK over the active connection. */
104130 fun sendToClient (message : ProtocolMessage ) {
105131 val listener = checkNotNull(activeListener) { " No active WebSocket connection" }
106132 _events .add(MockEvent .SentToClient (message))
107133 listener.onMessage(Serialisation .gson.toJson(message))
108134 }
109135
136+ /* * Deliver [message] to the SDK then immediately close the connection with code 1000. */
110137 fun sendToClientAndClose (message : ProtocolMessage ) {
111138 val listener = checkNotNull(activeListener) { " No active WebSocket connection" }
112139 _events .add(MockEvent .SentToClient (message))
@@ -115,13 +142,15 @@ class MockWebSocket(config: WebSocketMockConfig = WebSocketMockConfig()) {
115142 listener.onClose(1000 , " Normal closure" )
116143 }
117144
145+ /* * Simulate an abnormal network drop (close code 1006). Triggers DISCONNECTED on the SDK. */
118146 fun simulateDisconnect () {
119147 val listener = checkNotNull(activeListener) { " No active WebSocket connection" }
120148 _events .add(MockEvent .Disconnected )
121149 activeListener = null
122150 listener.onClose(1006 , " Abnormal closure" )
123151 }
124152
153+ /* * Clear all queued events and pending channels. Call between tests when reusing a mock instance. */
125154 fun reset () {
126155 _pendingConnections .close()
127156 _messagesFromClient .close()
0 commit comments