Skip to content

Commit 1cd9657

Browse files
committed
[ECO-5458] Added comprehensive integration tests for subscriptions
- Enhanced DefaultLiveCounterTest with subscription test scenarios - Updated DefaultLiveMapTest with subscription functionality tests - Added subscription tests to DefaultLiveObjectsTest and helper utilities
1 parent 63dad64 commit 1cd9657

5 files changed

Lines changed: 175 additions & 6 deletions

File tree

live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveCounterTest.kt

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package io.ably.lib.objects.integration
22

3-
import io.ably.lib.objects.LiveCounter
4-
import io.ably.lib.objects.LiveMap
3+
import io.ably.lib.objects.type.counter.LiveCounter
4+
import io.ably.lib.objects.type.map.LiveMap
55
import io.ably.lib.objects.assertWaiter
6+
import io.ably.lib.objects.integration.helpers.ObjectId
7+
import io.ably.lib.objects.integration.helpers.fixtures.createUserEngagementMatrixMap
68
import io.ably.lib.objects.integration.helpers.fixtures.createUserMapWithCountersObject
79
import io.ably.lib.objects.integration.setup.IntegrationTest
810
import kotlinx.coroutines.test.runTest
911
import org.junit.Test
1012
import kotlin.test.assertEquals
1113
import kotlin.test.assertNotNull
14+
import kotlin.test.assertTrue
1215

1316
class DefaultLiveCounterTest: IntegrationTest() {
1417
/**
@@ -202,4 +205,73 @@ class DefaultLiveCounterTest: IntegrationTest() {
202205
assertNotNull(finalCounterCheck, "Counter should still be accessible from root map")
203206
assertEquals(30.0, finalCounterCheck.value(), "Final counter value should be 30 when accessed from root map")
204207
}
208+
209+
@Test
210+
fun testLiveCounterChangesUsingSubscription() = runTest {
211+
val channelName = generateChannelName()
212+
val userEngagementMapId = restObjects.createUserEngagementMatrixMap(channelName)
213+
restObjects.setMapRef(channelName, "root", "userMatrix", userEngagementMapId)
214+
215+
val channel = getRealtimeChannel(channelName)
216+
val rootMap = channel.objects.root
217+
218+
val userEngagementMap = rootMap.get("userMatrix") as LiveMap
219+
assertEquals(4L, userEngagementMap.size(), "User engagement map should contain 4 top-level entries")
220+
221+
val totalReactions = userEngagementMap.get("totalReactions") as LiveCounter
222+
assertEquals(189.0, totalReactions.value(), "Total reactions counter should have initial value of 189")
223+
224+
// Subscribe to changes on the totalReactions counter
225+
val counterUpdates = mutableListOf<Double>()
226+
val totalReactionsSubscription = totalReactions.subscribe { update ->
227+
counterUpdates.add(update.update.amount)
228+
}
229+
230+
// Step 1: Increment the totalReactions counter by 10 (189 + 10 = 199)
231+
restObjects.incrementCounter(channelName, totalReactions.ObjectId, 10.0)
232+
233+
// Wait for the update to be received
234+
assertWaiter { counterUpdates.isNotEmpty() }
235+
236+
// Verify the increment update was received
237+
assertEquals(1, counterUpdates.size, "Should receive one update for increment")
238+
assertEquals(10.0, counterUpdates.first(), "Update should contain increment amount of 10")
239+
assertEquals(199.0, totalReactions.value(), "Counter should be incremented to 199")
240+
241+
// Step 2: Decrement the totalReactions counter by 5 (199 - 5 = 194)
242+
counterUpdates.clear()
243+
restObjects.decrementCounter(channelName, totalReactions.ObjectId, 5.0)
244+
245+
// Wait for the second update
246+
assertWaiter { counterUpdates.isNotEmpty() }
247+
248+
// Verify the decrement update was received
249+
assertEquals(1, counterUpdates.size, "Should receive one update for decrement")
250+
assertEquals(-5.0, counterUpdates.first(), "Update should contain decrement amount of -5")
251+
assertEquals(194.0, totalReactions.value(), "Counter should be decremented to 194")
252+
253+
// Step 3: Increment the totalReactions counter by 15 (194 + 15 = 209)
254+
counterUpdates.clear()
255+
restObjects.incrementCounter(channelName, totalReactions.ObjectId, 15.0)
256+
257+
// Wait for the third update
258+
assertWaiter { counterUpdates.isNotEmpty() }
259+
260+
// Verify the third increment update was received
261+
assertEquals(1, counterUpdates.size, "Should receive one update for third increment")
262+
assertEquals(15.0, counterUpdates.first(), "Update should contain increment amount of 15")
263+
assertEquals(209.0, totalReactions.value(), "Counter should be incremented to 209")
264+
265+
// Clean up subscription
266+
counterUpdates.clear()
267+
totalReactionsSubscription.unsubscribe()
268+
269+
// No updates should be received after unsubscribing
270+
restObjects.incrementCounter(channelName, totalReactions.ObjectId, 20.0)
271+
272+
// Wait for a moment to ensure no updates are received
273+
assertWaiter { totalReactions.value() == 229.0 }
274+
275+
assertTrue(counterUpdates.isEmpty(), "No updates should be received after unsubscribing")
276+
}
205277
}

live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import io.ably.lib.objects.*
44
import io.ably.lib.objects.ObjectData
55
import io.ably.lib.objects.ObjectValue
66
import io.ably.lib.objects.integration.helpers.fixtures.createUserMapObject
7+
import io.ably.lib.objects.integration.helpers.fixtures.createUserProfileMapObject
78
import io.ably.lib.objects.integration.setup.IntegrationTest
9+
import io.ably.lib.objects.type.counter.LiveCounter
10+
import io.ably.lib.objects.type.map.LiveMap
11+
import io.ably.lib.objects.type.map.LiveMapUpdate
812
import kotlinx.coroutines.test.runTest
913
import org.junit.Test
1014
import kotlin.test.assertEquals
1115
import kotlin.test.assertNotNull
1216
import kotlin.test.assertNull
17+
import kotlin.test.assertTrue
1318

1419
class DefaultLiveMapTest: IntegrationTest() {
1520
/**
@@ -210,4 +215,94 @@ class DefaultLiveMapTest: IntegrationTest() {
210215
val finalValues = testMap.values().toSet()
211216
assertEquals(setOf("Bob", false, "bob@example.com"), finalValues, "Final values should match expected set")
212217
}
218+
219+
@Test
220+
fun testLiveMapChangesUsingSubscription() = runTest {
221+
val channelName = generateChannelName()
222+
val userProfileObjectId = restObjects.createUserProfileMapObject(channelName)
223+
restObjects.setMapRef(channelName, "root", "userProfile", userProfileObjectId)
224+
225+
val channel = getRealtimeChannel(channelName)
226+
val rootMap = channel.objects.root
227+
228+
// Get the user profile map object from the root map
229+
val userProfile = rootMap.get("userProfile") as LiveMap
230+
assertNotNull(userProfile, "User profile should be synchronized")
231+
assertEquals(4L, userProfile.size(), "User profile should contain 4 entries")
232+
233+
// Verify initial values
234+
assertEquals("user123", userProfile.get("userId"), "Initial userId should be user123")
235+
assertEquals("John Doe", userProfile.get("name"), "Initial name should be John Doe")
236+
assertEquals("john@example.com", userProfile.get("email"), "Initial email should be john@example.com")
237+
assertEquals(true, userProfile.get("isActive"), "Initial isActive should be true")
238+
239+
// Subscribe to changes in the user profile map
240+
val userProfileUpdates = mutableListOf<LiveMapUpdate>()
241+
val userProfileSubscription = userProfile.subscribe { update -> userProfileUpdates.add(update) }
242+
243+
// Step 1: Update an existing field in the user profile map (change the name)
244+
restObjects.setMapValue(channelName, userProfileObjectId, "name", ObjectValue.String("Bob Smith"))
245+
246+
// Wait for the update to be received
247+
assertWaiter { userProfileUpdates.isNotEmpty() }
248+
249+
// Verify the update was received
250+
assertEquals(1, userProfileUpdates.size, "Should receive one update")
251+
val firstUpdateMap = userProfileUpdates.first().update
252+
assertEquals(1, firstUpdateMap.size, "Should have one key change")
253+
assertTrue(firstUpdateMap.containsKey("name"), "Update should contain name key")
254+
assertEquals(LiveMapUpdate.Change.UPDATED, firstUpdateMap["name"], "name should be marked as UPDATED")
255+
256+
// Verify the value was actually updated
257+
assertEquals("Bob Smith", userProfile.get("name"), "Name should be updated to Bob Smith")
258+
259+
// Step 2: Update another field in the user profile map (change the email)
260+
userProfileUpdates.clear()
261+
restObjects.setMapValue(channelName, userProfileObjectId, "email", ObjectValue.String("bob@example.com"))
262+
263+
// Wait for the second update
264+
assertWaiter { userProfileUpdates.isNotEmpty() }
265+
266+
// Verify the second update
267+
assertEquals(1, userProfileUpdates.size, "Should receive one update for the second change")
268+
val secondUpdateMap = userProfileUpdates.first().update
269+
assertEquals(1, secondUpdateMap.size, "Should have one key change")
270+
assertTrue(secondUpdateMap.containsKey("email"), "Update should contain email key")
271+
assertEquals(LiveMapUpdate.Change.UPDATED, secondUpdateMap["email"], "email should be marked as UPDATED")
272+
273+
// Verify the value was actually updated
274+
assertEquals("bob@example.com", userProfile.get("email"), "Email should be updated to bob@example.com")
275+
276+
// Step 3: Remove an existing field from the user profile map (remove isActive)
277+
userProfileUpdates.clear()
278+
restObjects.removeMapValue(channelName, userProfileObjectId, "isActive")
279+
280+
// Wait for the removal update
281+
assertWaiter { userProfileUpdates.isNotEmpty() }
282+
283+
// Verify the removal update
284+
assertEquals(1, userProfileUpdates.size, "Should receive one update for removal")
285+
val removalUpdateMap = userProfileUpdates.first().update
286+
assertEquals(1, removalUpdateMap.size, "Should have one key change")
287+
assertTrue(removalUpdateMap.containsKey("isActive"), "Update should contain isActive key")
288+
assertEquals(LiveMapUpdate.Change.REMOVED, removalUpdateMap["isActive"], "isActive should be marked as REMOVED")
289+
290+
// Verify final state of the user profile map
291+
assertEquals(3L, userProfile.size(), "User profile should have 3 entries after removing isActive")
292+
assertEquals("user123", userProfile.get("userId"), "userId should remain unchanged")
293+
assertEquals("Bob Smith", userProfile.get("name"), "name should remain updated")
294+
assertEquals("bob@example.com", userProfile.get("email"), "email should remain updated")
295+
assertNull(userProfile.get("isActive"), "isActive should be removed")
296+
297+
// Clean up subscription
298+
userProfileUpdates.clear()
299+
userProfileSubscription.unsubscribe()
300+
// No updates should be received after unsubscribing
301+
restObjects.setMapValue(channelName, userProfileObjectId, "country", ObjectValue.String("uk"))
302+
303+
// Wait for a moment to ensure no updates are received
304+
assertWaiter { userProfile.size() == 4L }
305+
306+
assertTrue(userProfileUpdates.isEmpty(), "No updates should be received after unsubscribing")
307+
}
213308
}

live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import io.ably.lib.objects.integration.helpers.fixtures.initializeRootMap
99
import io.ably.lib.objects.integration.setup.IntegrationTest
1010
import io.ably.lib.objects.size
1111
import io.ably.lib.objects.state.ObjectsStateEvent
12+
import io.ably.lib.objects.type.counter.LiveCounter
13+
import io.ably.lib.objects.type.map.LiveMap
1214
import kotlinx.coroutines.test.runTest
1315
import org.junit.Test
1416
import kotlin.test.assertEquals

live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.ably.lib.objects.ObjectData
55
import io.ably.lib.objects.ObjectValue
66
import io.ably.lib.rest.AblyRest
77
import io.ably.lib.http.HttpUtils
8+
import io.ably.lib.objects.integration.helpers.fixtures.DataFixtures
89
import io.ably.lib.types.ClientOptions
910

1011
/**
@@ -37,8 +38,7 @@ internal class RestObjects(options: ClientOptions) {
3738
* Sets an object reference at the specified key in an existing map.
3839
*/
3940
internal fun setMapRef(channelName: String, mapObjectId: String, key: String, refMapObjectId: String) {
40-
val data = ObjectData(objectId = refMapObjectId)
41-
val mapCreateOp = PayloadBuilder.mapSetRestOp(mapObjectId, key, data)
41+
val mapCreateOp = PayloadBuilder.mapSetRestOp(mapObjectId, key, DataFixtures.mapRef(refMapObjectId))
4242
operationRequest(channelName, mapCreateOp)
4343
}
4444

live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.ably.lib.objects.integration.helpers
22

33
import io.ably.lib.objects.DefaultLiveObjects
4-
import io.ably.lib.objects.LiveCounter
5-
import io.ably.lib.objects.LiveMap
4+
import io.ably.lib.objects.type.counter.LiveCounter
5+
import io.ably.lib.objects.type.map.LiveMap
66
import io.ably.lib.objects.LiveObjects
77
import io.ably.lib.objects.type.livecounter.DefaultLiveCounter
88
import io.ably.lib.objects.type.livemap.DefaultLiveMap

0 commit comments

Comments
 (0)