Skip to content

Commit cd9490b

Browse files
committed
[ECO-5056] Updated test helper getMockObjectsAdapter with static mocck
1. Encouraged use of global mocck for Adapter 2. Updated failing tests accordingly 3. Added unit tests covering all cases for retrieveObjectsGCGracePeriod
1 parent c8c84e7 commit cd9490b

5 files changed

Lines changed: 109 additions & 80 deletions

File tree

liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.ably.lib.objects
33
import io.ably.lib.realtime.ChannelState
44
import io.ably.lib.realtime.CompletionListener
55
import io.ably.lib.realtime.ConnectionEvent
6+
import io.ably.lib.realtime.ConnectionState
67
import io.ably.lib.types.ChannelMode
78
import io.ably.lib.types.ErrorInfo
89
import io.ably.lib.types.ProtocolMessage
@@ -11,12 +12,14 @@ import kotlinx.coroutines.suspendCancellableCoroutine
1112
import kotlin.coroutines.resume
1213
import kotlin.coroutines.resumeWithException
1314

15+
internal val ObjectsAdapter.connectionManager get() = connection.connectionManager
16+
1417
/**
1518
* Spec: RTO15g
1619
*/
1720
internal suspend fun ObjectsAdapter.sendAsync(message: ProtocolMessage) = suspendCancellableCoroutine { continuation ->
1821
try {
19-
connection.connectionManager.send(message, clientOptions.queueMessages, object : CompletionListener {
22+
connectionManager.send(message, clientOptions.queueMessages, object : CompletionListener {
2023
override fun onSuccess() {
2124
continuation.resume(Unit)
2225
}
@@ -47,8 +50,12 @@ internal suspend fun ObjectsAdapter.attachAsync(channelName: String) = suspendCa
4750
}
4851

4952
internal fun ObjectsAdapter.retrieveObjectsGCGracePeriod(block : (Long?) -> Unit) {
50-
val connectionManager = connection.connectionManager
51-
if (connectionManager.objectsGCGracePeriod != null) {
53+
connectionManager.objectsGCGracePeriod?.let {
54+
block(it)
55+
return
56+
}
57+
// If already connected, no further `connected` event is guaranteed; return immediately.
58+
if (connection.state == ConnectionState.connected) {
5259
block(connectionManager.objectsGCGracePeriod)
5360
return
5461
}
@@ -88,7 +95,7 @@ internal fun ObjectsAdapter.getChannelModes(channelName: String): Array<ChannelM
8895
* Spec: RTO15d
8996
*/
9097
internal fun ObjectsAdapter.ensureMessageSizeWithinLimit(objectMessages: Array<ObjectMessage>) {
91-
val maximumAllowedSize = connection.connectionManager.maxMessageSize
98+
val maximumAllowedSize = connectionManager.maxMessageSize
9299
val objectsTotalMessageSize = objectMessages.sumOf { it.size() }
93100
if (objectsTotalMessageSize > maximumAllowedSize) {
94101
throw ablyException("ObjectMessages size $objectsTotalMessageSize exceeds maximum allowed size of $maximumAllowedSize bytes",
@@ -143,8 +150,8 @@ internal fun ObjectsAdapter.throwIfInvalidWriteApiConfiguration(channelName: Str
143150
}
144151

145152
internal fun ObjectsAdapter.throwIfUnpublishableState(channelName: String) {
146-
if (!connection.connectionManager.isActive) {
147-
throw ablyException(connection.connectionManager.stateErrorInfo)
153+
if (!connectionManager.isActive) {
154+
throw ablyException(connectionManager.stateErrorInfo)
148155
}
149156
throwIfInChannelState(channelName, arrayOf(ChannelState.failed, ChannelState.suspended))
150157
}

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt

Lines changed: 79 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ import io.ably.lib.realtime.Channel
55
import io.ably.lib.realtime.ChannelState
66
import io.ably.lib.realtime.ChannelStateListener
77
import io.ably.lib.realtime.CompletionListener
8-
import io.ably.lib.transport.ConnectionManager
8+
import io.ably.lib.realtime.ConnectionEvent
9+
import io.ably.lib.realtime.ConnectionStateListener
910
import io.ably.lib.types.*
10-
import io.mockk.every
11-
import io.mockk.mockk
12-
import io.mockk.slot
13-
import io.mockk.verify
11+
import io.mockk.*
1412
import kotlinx.coroutines.test.runTest
1513
import org.junit.Assert.*
1614
import org.junit.Test
@@ -21,13 +19,10 @@ class HelpersTest {
2119
// sendAsync
2220
@Test
2321
fun testSendAsyncShouldQueueAccordingToClientOptions() = runTest {
24-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
25-
val connManager = mockk<ConnectionManager>(relaxed = true)
22+
val adapter = getMockObjectsAdapter()
23+
val connManager = adapter.connectionManager
2624
val clientOptions = ClientOptions().apply { queueMessages = false }
2725

28-
every { adapter.connection } returns mockk(relaxed = true) {
29-
setPrivateField("connectionManager", connManager)
30-
}
3126
every { adapter.clientOptions } returns clientOptions
3227

3328
every { connManager.send(any(), any(), any()) } answers {
@@ -46,13 +41,10 @@ class HelpersTest {
4641

4742
@Test
4843
fun testSendAsyncErrorPropagatesAblyException() = runTest {
49-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
50-
val connManager = mockk<ConnectionManager>(relaxed = true)
44+
val adapter = getMockObjectsAdapter()
45+
val connManager = adapter.connectionManager
5146
val clientOptions = ClientOptions()
5247

53-
every { adapter.connection } returns mockk(relaxed = true) {
54-
setPrivateField("connectionManager", connManager)
55-
}
5648
every { adapter.clientOptions } returns clientOptions
5749

5850
every { connManager.send(any(), any(), any()) } answers {
@@ -67,15 +59,59 @@ class HelpersTest {
6759
assertEquals(40000, ex.errorInfo.code)
6860
}
6961

62+
@Test
63+
fun testRetrieveObjectsGCGracePeriodImmediateInvokesBlock() {
64+
val adapter = getMockObjectsAdapter()
65+
val connManager = adapter.connectionManager
66+
connManager.setPrivateField("objectsGCGracePeriod", 123L)
67+
68+
var value: Long? = null
69+
adapter.retrieveObjectsGCGracePeriod { v -> value = v }
70+
71+
assertEquals(123L, value)
72+
verify(exactly = 0) { adapter.connection.once(ConnectionEvent.connected, any()) }
73+
}
74+
75+
@Test
76+
fun testRetrieveObjectsGCGracePeriodDeferredInvokesOnConnectedWithValue() {
77+
val adapter = getMockObjectsAdapter()
78+
val connManager = adapter.connectionManager
79+
80+
var value: Long? = null
81+
every { adapter.connection.once(ConnectionEvent.connected, any()) } answers {
82+
val listener = secondArg<ConnectionStateListener>()
83+
connManager.setPrivateField("objectsGCGracePeriod", 456L)
84+
listener.onConnectionStateChanged(mockk(relaxed = true))
85+
}
86+
87+
adapter.retrieveObjectsGCGracePeriod { v -> value = v }
88+
89+
assertEquals(456L, value)
90+
verify(exactly = 1) { adapter.connection.once(ConnectionEvent.connected, any()) }
91+
}
92+
93+
@Test
94+
fun testRetrieveObjectsGCGracePeriodDeferredInvokesOnConnectedWithNull() {
95+
val adapter = getMockObjectsAdapter()
96+
97+
var value: Long? = null
98+
every { adapter.connection.once(ConnectionEvent.connected, any()) } answers {
99+
val listener = secondArg<ConnectionStateListener>()
100+
listener.onConnectionStateChanged(mockk(relaxed = true))
101+
}
102+
103+
adapter.retrieveObjectsGCGracePeriod { v -> value = v }
104+
105+
assertNull(value)
106+
verify(exactly = 1) { adapter.connection.once(ConnectionEvent.connected, any()) }
107+
}
108+
70109
@Test
71110
fun testSendAsyncThrowsWhenConnectionManagerThrows() = runTest {
72-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
73-
val connManager = mockk<ConnectionManager>(relaxed = true)
111+
val adapter = getMockObjectsAdapter()
112+
val connManager = adapter.connectionManager
74113
val clientOptions = ClientOptions()
75114

76-
every { adapter.connection } returns mockk(relaxed = true) {
77-
setPrivateField("connectionManager", connManager)
78-
}
79115
every { adapter.clientOptions } returns clientOptions
80116

81117
every { connManager.send(any(), any(), any()) } throws RuntimeException("send failed hard")
@@ -251,25 +287,25 @@ class HelpersTest {
251287
verify(exactly = 1) { channel.once(any()) }
252288
}
253289

254-
@Test
255-
fun testEnsureAttachedAttachingButReceivesNonAttachedEmitsError() = runTest {
256-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
257-
val channel = mockk<Channel>(relaxed = true)
258-
every { adapter.getChannel("ch") } returns channel
259-
channel.state = ChannelState.attaching
260-
every { channel.once(any()) } answers {
261-
val listener = firstArg<ChannelStateListener>()
262-
val stateChange = mockk<ChannelStateListener.ChannelStateChange>(relaxed = true) {
263-
setPrivateField("current", ChannelState.suspended)
264-
setPrivateField("reason", clientError("Not attached").errorInfo)
265-
}
266-
listener.onChannelStateChanged(stateChange)
267-
}
268-
val ex = assertFailsWith<AblyException> { adapter.ensureAttached("ch") }
269-
assertEquals(ErrorCode.ChannelStateError.code, ex.errorInfo.code)
270-
assertTrue(ex.errorInfo.message.contains("Not attached"))
271-
verify(exactly = 1) { channel.once(any()) }
272-
}
290+
@Test
291+
fun testEnsureAttachedAttachingButReceivesNonAttachedEmitsError() = runTest {
292+
val adapter = mockk<ObjectsAdapter>(relaxed = true)
293+
val channel = mockk<Channel>(relaxed = true)
294+
every { adapter.getChannel("ch") } returns channel
295+
channel.state = ChannelState.attaching
296+
every { channel.once(any()) } answers {
297+
val listener = firstArg<ChannelStateListener>()
298+
val stateChange = mockk<ChannelStateListener.ChannelStateChange>(relaxed = true) {
299+
setPrivateField("current", ChannelState.suspended)
300+
setPrivateField("reason", clientError("Not attached").errorInfo)
301+
}
302+
listener.onChannelStateChanged(stateChange)
303+
}
304+
val ex = assertFailsWith<AblyException> { adapter.ensureAttached("ch") }
305+
assertEquals(ErrorCode.ChannelStateError.code, ex.errorInfo.code)
306+
assertTrue(ex.errorInfo.message.contains("Not attached"))
307+
verify(exactly = 1) { channel.once(any()) }
308+
}
273309

274310
@Test
275311
fun testEnsureAttachedThrowsForInvalidState() = runTest {
@@ -350,11 +386,8 @@ class HelpersTest {
350386
// throwIfUnpublishableState
351387
@Test
352388
fun testThrowIfUnpublishableStateInactiveConnection() {
353-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
354-
val connManager = mockk<ConnectionManager>(relaxed = true)
355-
every { adapter.connection } returns mockk(relaxed = true) {
356-
setPrivateField("connectionManager", connManager)
357-
}
389+
val adapter = getMockObjectsAdapter()
390+
val connManager = adapter.connectionManager
358391
every { connManager.isActive } returns false
359392
every { connManager.stateErrorInfo } returns serverError("not active").errorInfo
360393

@@ -365,11 +398,8 @@ class HelpersTest {
365398

366399
@Test
367400
fun testThrowIfUnpublishableStateChannelFailed() {
368-
val adapter = mockk<ObjectsAdapter>(relaxed = true)
369-
val connManager = mockk<ConnectionManager>(relaxed = true)
370-
every { adapter.connection } returns mockk(relaxed = true) {
371-
setPrivateField("connectionManager", connManager)
372-
}
401+
val adapter = getMockObjectsAdapter()
402+
val connManager = adapter.connectionManager
373403
every { connManager.isActive } returns true
374404
val channel = mockk<Channel>(relaxed = true)
375405
every { adapter.getChannel("ch") } returns channel

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,19 @@ import io.ably.lib.objects.ObjectOperationAction
1010
import io.ably.lib.objects.ObjectValue
1111
import io.ably.lib.objects.ensureMessageSizeWithinLimit
1212
import io.ably.lib.objects.size
13-
import io.ably.lib.transport.ConnectionManager
1413
import io.ably.lib.transport.Defaults
1514
import io.ably.lib.types.AblyException
16-
import io.mockk.every
17-
import io.mockk.mockk
1815
import kotlinx.coroutines.test.runTest
1916
import org.junit.Test
2017
import kotlin.test.assertEquals
2118
import kotlin.test.assertFailsWith
2219

2320
class ObjectMessageSizeTest {
24-
2521
@Test
2622
fun testObjectMessageSizeWithinLimit() = runTest {
27-
val mockAdapter = mockk<ObjectsAdapter>(relaxed = true)
28-
val connManager = mockk<ConnectionManager>(relaxed = true)
29-
every { mockAdapter.connection } returns mockk(relaxed = true) {
30-
setPrivateField("connectionManager", connManager)
31-
}
32-
connManager.maxMessageSize = Defaults.maxMessageSize // 64 kb
33-
assertEquals(65536, connManager.maxMessageSize)
23+
val mockAdapter = getMockObjectsAdapter()
24+
mockAdapter.connectionManager.maxMessageSize = Defaults.maxMessageSize // 64 kb
25+
assertEquals(65536, mockAdapter.connectionManager.maxMessageSize)
3426

3527
// ObjectMessage with all size-contributing fields
3628
val objectMessage = ObjectMessage(
@@ -153,13 +145,9 @@ class ObjectMessageSizeTest {
153145

154146
@Test
155147
fun testObjectMessageSizeAboveLimit() = runTest {
156-
val mockAdapter = mockk<ObjectsAdapter>(relaxed = true)
157-
val connManager = mockk<ConnectionManager>(relaxed = true)
158-
every { mockAdapter.connection } returns mockk(relaxed = true) {
159-
setPrivateField("connectionManager", connManager)
160-
}
161-
connManager.maxMessageSize = Defaults.maxMessageSize // 64 kb
162-
assertEquals(65536, connManager.maxMessageSize)
148+
val mockAdapter = getMockObjectsAdapter()
149+
mockAdapter.connectionManager.maxMessageSize = Defaults.maxMessageSize // 64 kb
150+
assertEquals(65536, mockAdapter.connectionManager.maxMessageSize)
163151

164152
// Create ObjectMessage with dummy data that results in size 60kb
165153
val objectMessage1 = ObjectMessage(

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import io.ably.lib.objects.type.livemap.LiveMapManager
1111
import io.ably.lib.realtime.AblyRealtime
1212
import io.ably.lib.realtime.Channel
1313
import io.ably.lib.realtime.ChannelState
14+
import io.ably.lib.transport.ConnectionManager
1415
import io.ably.lib.types.ChannelMode
1516
import io.ably.lib.types.ChannelOptions
1617
import io.ably.lib.types.ClientOptions
1718
import io.mockk.every
1819
import io.mockk.mockk
20+
import io.mockk.mockkStatic
1921
import io.mockk.spyk
2022

2123
internal fun getMockRealtimeChannel(
@@ -45,9 +47,11 @@ internal fun getMockRealtimeChannel(
4547
}
4648

4749
internal fun getMockObjectsAdapter(): ObjectsAdapter {
48-
val mockkAdapter = mockk<ObjectsAdapter>(relaxed = true)
49-
every { mockkAdapter.getChannel(any()) } returns getMockRealtimeChannel("testChannelName")
50-
return mockkAdapter
50+
mockkStatic("io.ably.lib.objects.HelpersKt")
51+
return mockk<ObjectsAdapter>(relaxed = true) {
52+
every { getChannel(any()) } returns getMockRealtimeChannel("testChannelName")
53+
every { connectionManager } returns mockk<ConnectionManager>(relaxed = true)
54+
}
5155
}
5256

5357
internal fun getMockObjectsPool(): ObjectsPool {

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ObjectsPoolTest {
1919

2020
@Test
2121
fun `(RTO3, RTO3a, RTO3b) An internal ObjectsPool should be used to maintain the list of objects present on a channel`() {
22-
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", mockk(relaxed = true))
22+
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", getMockObjectsAdapter())
2323
val objectsPool = defaultRealtimeObjects.objectsPool
2424
assertNotNull(objectsPool)
2525

@@ -44,7 +44,7 @@ class ObjectsPoolTest {
4444

4545
@Test
4646
fun `(RTO6) ObjectsPool should create zero-value objects if not exists`() {
47-
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", mockk(relaxed = true))
47+
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", getMockObjectsAdapter())
4848
val objectsPool = spyk(defaultRealtimeObjects.objectsPool)
4949
assertEquals(1, objectsPool.size(), "RTO3 - Should only contain the root object initially")
5050

@@ -78,7 +78,7 @@ class ObjectsPoolTest {
7878

7979
@Test
8080
fun `(RTO4b1, RTO4b2) ObjectsPool should reset to initial pool retaining original root map`() {
81-
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", mockk(relaxed = true))
81+
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", getMockObjectsAdapter())
8282
val objectsPool = defaultRealtimeObjects.objectsPool
8383
assertEquals(1, objectsPool.size())
8484
val rootMap = objectsPool.get(ROOT_OBJECT_ID) as DefaultLiveMap
@@ -107,7 +107,7 @@ class ObjectsPoolTest {
107107

108108
@Test
109109
fun `(RTO5c2, RTO5c2a) ObjectsPool should delete extra object IDs`() {
110-
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", mockk(relaxed = true))
110+
val defaultRealtimeObjects = DefaultRealtimeObjects("dummyChannel", getMockObjectsAdapter())
111111
val objectsPool = defaultRealtimeObjects.objectsPool
112112

113113
// Add some objects

0 commit comments

Comments
 (0)