Skip to content

Commit c8f95df

Browse files
committed
uts: add inline documentation for fake clocks, HTTP, and WebSocket mocks
- Added KDoc comments to `FakeClock`, `MockHttpClient`, and `MockWebSocket` explaining their purpose and usage.
1 parent 4fe5784 commit c8f95df

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

uts/src/test/kotlin/io/ably/lib/test/mock/FakeClock.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ import io.ably.lib.util.TimerInstance
66
import java.util.TimerTask
77
import kotlin.time.Duration
88

9+
/**
10+
* Virtual clock for deterministic time control in unit tests.
11+
*
12+
* Install via `enableFakeTimers(fakeClock)` inside a `TestRealtimeClient` or `TestRestClient` block.
13+
* Time only advances when [advance] is called; timer callbacks fire synchronously within that call.
14+
*/
915
class FakeClock(initialTimeMs: Long = 0L) : Clock {
1016
@Volatile private var time = initialTimeMs
1117
private val timers = mutableMapOf<String, FakeNamedTimer>()
@@ -18,13 +24,16 @@ class FakeClock(initialTimeMs: Long = 0L) : Clock {
1824
return t
1925
}
2026

27+
/** Advance virtual time by [ms] milliseconds, firing any timers that become due. */
2128
fun advance(ms: Long) {
2229
time += ms
2330
timers.values.forEach { it.fireDue(time) }
2431
}
2532

33+
/** Advance virtual time by [time], firing any timers that become due. */
2634
fun advance(time: Duration) = advance(time.inWholeMilliseconds)
2735

36+
/** Number of tasks currently scheduled on the named timer — useful for asserting retry state. */
2837
fun pendingTaskCount(timerName: String) = timers[timerName]?.pendingCount ?: 0
2938

3039
inner class FakeNamedTimer(val name: String) : NamedTimer {

uts/src/test/kotlin/io/ably/lib/test/mock/MockHttpClient.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,22 @@ import kotlinx.coroutines.withTimeout
99
import kotlin.time.Duration
1010
import kotlin.time.Duration.Companion.seconds
1111

12+
/**
13+
* Callbacks for [MockHttpClient]. Both fields are optional.
14+
*
15+
* When a callback is set it is invoked synchronously; when null the event queues for the
16+
* corresponding `await*` method.
17+
*/
1218
class HttpMockConfig {
19+
/** Called for every TCP connection attempt. Leave null to use [MockHttpClient.awaitConnectionAttempt]. */
1320
var onConnectionAttempt: ((PendingConnection) -> Unit)? = null
21+
/** Called for every HTTP request. Leave null to use [MockHttpClient.awaitRequest]. */
1422
var onRequest: ((PendingRequest) -> Unit)? = null
1523
}
1624

25+
/**
26+
* Fake HTTP engine for SDK unit tests. Install via [installOn] or `TestRestClient`/`TestRealtimeClient`.
27+
*/
1728
class MockHttpClient(private val config: HttpMockConfig = HttpMockConfig()) {
1829
private var _pendingConnections = Channel<PendingConnection>(Channel.UNLIMITED)
1930
private var _pendingRequests = Channel<PendingRequest>(Channel.UNLIMITED)
@@ -29,20 +40,24 @@ class MockHttpClient(private val config: HttpMockConfig = HttpMockConfig()) {
2940
},
3041
)
3142

43+
/** Wire this mock into [options] so the SDK uses it instead of a real HTTP client. */
3244
fun installOn(options: DebugOptions) {
3345
options.httpEngine = engine
3446
}
3547

48+
/** Suspend until the SDK opens a TCP connection. Only usable when [HttpMockConfig.onConnectionAttempt] is null. */
3649
suspend fun awaitConnectionAttempt(timeout: Duration = 5.seconds): PendingConnection =
3750
withContext(Dispatchers.Default.limitedParallelism(1)) {
3851
withTimeout(timeout) { _pendingConnections.receive() }
3952
}
4053

54+
/** Suspend until the SDK makes an HTTP request. Only usable when [HttpMockConfig.onRequest] is null. */
4155
suspend fun awaitRequest(timeout: Duration = 5.seconds): PendingRequest =
4256
withContext(Dispatchers.Default.limitedParallelism(1)) {
4357
withTimeout(timeout) { _pendingRequests.receive() }
4458
}
4559

60+
/** Clear all queued pending connections and requests. Call between tests when reusing a mock instance. */
4661
fun reset() {
4762
_pendingConnections.close()
4863
_pendingRequests.close()

uts/src/test/kotlin/io/ably/lib/test/mock/MockWebSocket.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,36 @@ import java.util.Collections
1515
import kotlin.time.Duration
1616
import 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+
*/
1825
class 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+
*/
2545
class 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

Comments
 (0)