|
| 1 | +package io.ably.lib.objects.integration |
| 2 | + |
| 3 | +import com.google.gson.JsonArray |
| 4 | +import com.google.gson.JsonObject |
| 5 | +import io.ably.lib.objects.* |
| 6 | +import io.ably.lib.objects.Binary |
| 7 | +import io.ably.lib.objects.integration.helpers.State |
| 8 | +import io.ably.lib.objects.integration.helpers.fixtures.initializeRootMap |
| 9 | +import io.ably.lib.objects.integration.setup.IntegrationTest |
| 10 | +import io.ably.lib.objects.size |
| 11 | +import io.ably.lib.objects.state.ObjectsStateEvent |
| 12 | +import kotlinx.coroutines.test.runTest |
| 13 | +import org.junit.Test |
| 14 | +import kotlin.test.assertEquals |
| 15 | +import kotlin.test.assertNotNull |
| 16 | +import kotlin.test.assertNull |
| 17 | +import kotlin.text.toByteArray |
| 18 | + |
| 19 | +class DefaultLiveObjectsTest : IntegrationTest() { |
| 20 | + |
| 21 | + @Test |
| 22 | + fun testChannelObjects() = runTest { |
| 23 | + val channelName = generateChannelName() |
| 24 | + val channel = getRealtimeChannel(channelName) |
| 25 | + val objects = channel.objects |
| 26 | + assertNotNull(objects) |
| 27 | + } |
| 28 | + |
| 29 | + @Test |
| 30 | + fun testObjectsSyncEvents() = runTest { |
| 31 | + val channelName = generateChannelName() |
| 32 | + // Initialize the root map on the channel with initial data |
| 33 | + restObjects.initializeRootMap(channelName) |
| 34 | + |
| 35 | + val channel = getRealtimeChannel(channelName, autoAttach = false) |
| 36 | + val objects = channel.objects |
| 37 | + assertNotNull(objects) |
| 38 | + |
| 39 | + assertEquals(ObjectsState.Initialized, objects.State, "Initial state should be INITIALIZED") |
| 40 | + |
| 41 | + val syncStates = mutableListOf<ObjectsStateEvent>() |
| 42 | + objects.on(ObjectsStateEvent.SYNCING) { |
| 43 | + syncStates.add(it) |
| 44 | + } |
| 45 | + objects.on(ObjectsStateEvent.SYNCED) { |
| 46 | + syncStates.add(it) |
| 47 | + } |
| 48 | + |
| 49 | + channel.attach() |
| 50 | + |
| 51 | + assertWaiter { syncStates.size == 2 } // Wait for both SYNCING and SYNCED events |
| 52 | + |
| 53 | + assertEquals(ObjectsStateEvent.SYNCING, syncStates[0], "First event should be SYNCING") |
| 54 | + assertEquals(ObjectsStateEvent.SYNCED, syncStates[1], "Second event should be SYNCED") |
| 55 | + |
| 56 | + val rootMap = objects.root |
| 57 | + assertEquals(6, rootMap.size(), "Root map should have 6 entries after sync") |
| 58 | + } |
| 59 | + |
| 60 | + /** |
| 61 | + * This will test objects sync process when the root map is initialized before channel attach. |
| 62 | + * This includes checking the initial values of counters, maps, and other data types. |
| 63 | + */ |
| 64 | + @Test |
| 65 | + fun testObjectsSync() = runTest { |
| 66 | + val channelName = generateChannelName() |
| 67 | + // Initialize the root map on the channel with initial data |
| 68 | + restObjects.initializeRootMap(channelName) |
| 69 | + |
| 70 | + val channel = getRealtimeChannel(channelName) |
| 71 | + val rootMap = channel.objects.root |
| 72 | + assertNotNull(rootMap) |
| 73 | + |
| 74 | + // Assert Counter Objects |
| 75 | + // Test emptyCounter - should have initial value of 0 |
| 76 | + val emptyCounter = rootMap.get("emptyCounter") as LiveCounter |
| 77 | + assertNotNull(emptyCounter) |
| 78 | + assertEquals(0.0, emptyCounter.value()) |
| 79 | + |
| 80 | + // Test initialValueCounter - should have initial value of 10 |
| 81 | + val initialValueCounter = rootMap.get("initialValueCounter") as LiveCounter |
| 82 | + assertNotNull(initialValueCounter) |
| 83 | + assertEquals(10.0, initialValueCounter.value()) |
| 84 | + |
| 85 | + // Test referencedCounter - should have initial value of 20 |
| 86 | + val referencedCounter = rootMap.get("referencedCounter") as LiveCounter |
| 87 | + assertNotNull(referencedCounter) |
| 88 | + assertEquals(20.0, referencedCounter.value()) |
| 89 | + |
| 90 | + // Assert Map Objects |
| 91 | + // Test emptyMap - should be an empty map |
| 92 | + val emptyMap = rootMap.get("emptyMap") as LiveMap |
| 93 | + assertNotNull(emptyMap) |
| 94 | + assertEquals(0L, emptyMap.size()) |
| 95 | + |
| 96 | + // Test referencedMap - should contain one key "counterKey" pointing to referencedCounter |
| 97 | + val referencedMap = rootMap.get("referencedMap") as LiveMap |
| 98 | + assertNotNull(referencedMap) |
| 99 | + assertEquals(1L, referencedMap.size()) |
| 100 | + val referencedMapCounter = referencedMap.get("counterKey") as LiveCounter |
| 101 | + assertNotNull(referencedMapCounter) |
| 102 | + assertEquals(20.0, referencedMapCounter.value()) // Should point to the same counter with value 20 |
| 103 | + |
| 104 | + // Test valuesMap - should contain all primitive data types and one map reference |
| 105 | + val valuesMap = rootMap.get("valuesMap") as LiveMap |
| 106 | + assertNotNull(valuesMap) |
| 107 | + assertEquals(13L, valuesMap.size()) // Should have 13 entries |
| 108 | + |
| 109 | + // Assert string values |
| 110 | + assertEquals("stringValue", valuesMap.get("string")) |
| 111 | + assertEquals("", valuesMap.get("emptyString")) |
| 112 | + |
| 113 | + // Assert binary values |
| 114 | + val bytesValue = valuesMap.get("bytes") as Binary |
| 115 | + assertNotNull(bytesValue) |
| 116 | + val expectedBinary = Binary("eyJwcm9kdWN0SWQiOiAiMDAxIiwgInByb2R1Y3ROYW1lIjogImNhciJ9".toByteArray()) |
| 117 | + assertEquals(expectedBinary, bytesValue) // Should contain encoded JSON data |
| 118 | + |
| 119 | + val emptyBytesValue = valuesMap.get("emptyBytes") as Binary |
| 120 | + assertNotNull(emptyBytesValue) |
| 121 | + assertEquals(0, emptyBytesValue.size()) // Should be empty byte array |
| 122 | + |
| 123 | + // Assert numeric values |
| 124 | + assertEquals(99999999.0, valuesMap.get("maxSafeNumber")) |
| 125 | + assertEquals(-99999999.0, valuesMap.get("negativeMaxSafeNumber")) |
| 126 | + assertEquals(1.0, valuesMap.get("number")) |
| 127 | + assertEquals(0.0, valuesMap.get("zero")) |
| 128 | + |
| 129 | + // Assert boolean values |
| 130 | + assertEquals(true, valuesMap.get("true")) |
| 131 | + assertEquals(false, valuesMap.get("false")) |
| 132 | + |
| 133 | + // Assert JSON object value - should contain {"foo": "bar"} |
| 134 | + val jsonObjectValue = valuesMap.get("object") as JsonObject |
| 135 | + assertNotNull(jsonObjectValue) |
| 136 | + assertEquals("bar", jsonObjectValue.get("foo").asString) |
| 137 | + |
| 138 | + // Assert JSON array value - should contain ["foo", "bar", "baz"] |
| 139 | + val jsonArrayValue = valuesMap.get("array") as JsonArray |
| 140 | + assertNotNull(jsonArrayValue) |
| 141 | + assertEquals(3, jsonArrayValue.size()) |
| 142 | + assertEquals("foo", jsonArrayValue[0].asString) |
| 143 | + assertEquals("bar", jsonArrayValue[1].asString) |
| 144 | + assertEquals("baz", jsonArrayValue[2].asString) |
| 145 | + |
| 146 | + // Assert map reference - should point to the same referencedMap |
| 147 | + val mapRefValue = valuesMap.get("mapRef") as LiveMap |
| 148 | + assertNotNull(mapRefValue) |
| 149 | + assertEquals(1L, mapRefValue.size()) |
| 150 | + val mapRefCounter = mapRefValue.get("counterKey") as LiveCounter |
| 151 | + assertNotNull(mapRefCounter) |
| 152 | + assertEquals(20.0, mapRefCounter.value()) // Should point to the same counter with value 20 |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Spec: RTLO4e - Tests the removal of objects from the root map. |
| 157 | + * Server runs periodic garbage collection (GC) to remove orphaned objects and will send |
| 158 | + * OBJECT_DELETE events for objects that are no longer referenced. |
| 159 | + * `OBJECT_DELETE` event is not covered in the test and we only check if map entries are removed |
| 160 | + */ |
| 161 | + @Test |
| 162 | + fun testObjectRemovalFromRoot() = runTest { |
| 163 | + val channelName = generateChannelName() |
| 164 | + // Initialize the root map on the channel with initial data |
| 165 | + restObjects.initializeRootMap(channelName) |
| 166 | + |
| 167 | + val channel = getRealtimeChannel(channelName) |
| 168 | + val rootMap = channel.objects.root |
| 169 | + assertEquals(6L, rootMap.size()) // Should have 6 entries initially |
| 170 | + |
| 171 | + // Remove the "referencedCounter" from the root map |
| 172 | + assertNotNull(rootMap.get("referencedCounter")) // Access to ensure it exists before removal |
| 173 | + |
| 174 | + restObjects.removeMapValue(channelName, "root", "referencedCounter") |
| 175 | + |
| 176 | + assertWaiter { rootMap.size() == 5L } // Wait for the removal to complete |
| 177 | + assertNull(rootMap.get("referencedCounter")) // Should be null after removal |
| 178 | + |
| 179 | + // Remove the "referencedMap" from the root map |
| 180 | + assertNotNull(rootMap.get("referencedMap")) // Access to ensure it exists before removal |
| 181 | + |
| 182 | + restObjects.removeMapValue(channelName, "root", "referencedMap") |
| 183 | + |
| 184 | + assertWaiter { rootMap.size() == 4L } // Wait for the removal to complete |
| 185 | + assertNull(rootMap.get("referencedMap")) // Should be null after removal |
| 186 | + } |
| 187 | +} |
0 commit comments