Skip to content

Commit 647a5e0

Browse files
committed
[ECO-5426] Updated ObjectMessage Counter specific amount and count to Long
- Fixed serialization for those types, from double to long - Added mocks for LiveMap and LiveCounter to TestHelpers - Added tests for LiveMap and LiveCounter
1 parent e808d38 commit 647a5e0

11 files changed

Lines changed: 222 additions & 19 deletions

File tree

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ internal data class ObjectCounterOp(
102102
* The data value that should be added to the counter
103103
* Spec: OCO2a
104104
*/
105-
val amount: Double? = null
105+
val amount: Long? = null
106106
)
107107

108108
/**
@@ -158,7 +158,7 @@ internal data class ObjectCounter(
158158
* The value of the counter
159159
* Spec: OCN2a
160160
*/
161-
val count: Double? = null
161+
val count: Long? = null
162162
)
163163

164164
/**

live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ private fun ObjectCounterOp.writeMsgpack(packer: MessagePacker) {
425425

426426
if (amount != null) {
427427
packer.packString("amount")
428-
packer.packDouble(amount)
428+
packer.packLong(amount)
429429
}
430430
}
431431

@@ -435,7 +435,7 @@ private fun ObjectCounterOp.writeMsgpack(packer: MessagePacker) {
435435
private fun readObjectCounterOp(unpacker: MessageUnpacker): ObjectCounterOp {
436436
val fieldCount = unpacker.unpackMapHeader()
437437

438-
var amount: Double? = null
438+
var amount: Long? = null
439439

440440
for (i in 0 until fieldCount) {
441441
val fieldName = unpacker.unpackString().intern()
@@ -447,7 +447,7 @@ private fun readObjectCounterOp(unpacker: MessageUnpacker): ObjectCounterOp {
447447
}
448448

449449
when (fieldName) {
450-
"amount" -> amount = unpacker.unpackDouble()
450+
"amount" -> amount = unpacker.unpackLong()
451451
else -> unpacker.skipValue()
452452
}
453453
}
@@ -534,7 +534,7 @@ private fun ObjectCounter.writeMsgpack(packer: MessagePacker) {
534534

535535
if (count != null) {
536536
packer.packString("count")
537-
packer.packDouble(count)
537+
packer.packLong(count)
538538
}
539539
}
540540

@@ -544,7 +544,7 @@ private fun ObjectCounter.writeMsgpack(packer: MessagePacker) {
544544
private fun readObjectCounter(unpacker: MessageUnpacker): ObjectCounter {
545545
val fieldCount = unpacker.unpackMapHeader()
546546

547-
var count: Double? = null
547+
var count: Long? = null
548548

549549
for (i in 0 until fieldCount) {
550550
val fieldName = unpacker.unpackString().intern()
@@ -556,7 +556,7 @@ private fun readObjectCounter(unpacker: MessageUnpacker): ObjectCounter {
556556
}
557557

558558
when (fieldName) {
559-
"count" -> count = unpacker.unpackDouble()
559+
"count" -> count = unpacker.unpackLong()
560560
else -> unpacker.skipValue()
561561
}
562562
}

live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter) {
2323
} else {
2424
// override data for this object with data from the object state
2525
liveCounter.createOperationIsMerged = false // RTLC6b
26-
liveCounter.data = objectState.counter?.count?.toLong() ?: 0 // RTLC6c
26+
liveCounter.data = objectState.counter?.count ?: 0 // RTLC6c
2727

2828
// RTLC6d
2929
objectState.createOp?.let { createOp ->
@@ -77,7 +77,7 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter) {
7777
* @spec RTLC9 - Applies counter increment operation
7878
*/
7979
private fun applyCounterInc(counterOp: ObjectCounterOp): Map<String, Long> {
80-
val amount = counterOp.amount?.toLong() ?: 0
80+
val amount = counterOp.amount ?: 0
8181
liveCounter.data += amount // RTLC9b
8282
return mapOf("amount" to amount)
8383
}
@@ -90,7 +90,7 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter) {
9090
// note that it is intentional to SUM the incoming count from the create op.
9191
// if we got here, it means that current counter instance is missing the initial value in its data reference,
9292
// which we're going to add now.
93-
val count = operation.counter?.count?.toLong() ?: 0
93+
val count = operation.counter?.count ?: 0
9494
liveCounter.data += count // RTLC10a
9595
liveCounter.createOperationIsMerged = true // RTLC10b
9696
return mapOf("amount" to count)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class ObjectMessageSizeTest {
5353

5454
// CounterOp contributes to operation size
5555
counterOp = ObjectCounterOp(
56-
amount = 10.5 // Size: 8 bytes (number is always 8 bytes)
56+
amount = 10 // Size: 8 bytes (number is always 8 bytes)
5757
), // Total ObjectCounterOp size: 8 bytes
5858

5959
// Map contributes to operation size (for MAP_CREATE operations)
@@ -77,7 +77,7 @@ class ObjectMessageSizeTest {
7777

7878
// Counter contributes to operation size (for COUNTER_CREATE operations)
7979
counter = ObjectCounter(
80-
count = 100.0 // Size: 8 bytes (number is always 8 bytes)
80+
count = 100 // Size: 8 bytes (number is always 8 bytes)
8181
), // Total ObjectCounter size: 8 bytes
8282

8383
nonce = "nonce123", // Not counted in operation size
@@ -115,7 +115,7 @@ class ObjectMessageSizeTest {
115115

116116
// counter contributes to state size
117117
counter = ObjectCounter(
118-
count = 50.0 // Size: 8 bytes
118+
count = 50 // Size: 8 bytes
119119
) // Total ObjectCounter size: 8 bytes
120120
), // Total ObjectState size: 20 + 18 + 8 = 46 bytes
121121

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import io.ably.lib.objects.*
44
import io.ably.lib.objects.DefaultLiveObjects
55
import io.ably.lib.objects.ObjectsManager
66
import io.ably.lib.objects.type.BaseLiveObject
7+
import io.ably.lib.objects.type.livecounter.DefaultLiveCounter
8+
import io.ably.lib.objects.type.livecounter.LiveCounterManager
9+
import io.ably.lib.objects.type.livemap.DefaultLiveMap
10+
import io.ably.lib.objects.type.livemap.LiveMapManager
711
import io.ably.lib.realtime.AblyRealtime
812
import io.ably.lib.realtime.Channel
913
import io.ably.lib.realtime.ChannelState
@@ -44,11 +48,20 @@ internal fun getMockLiveObjectsAdapter(): LiveObjectsAdapter {
4448
return mockk<LiveObjectsAdapter>(relaxed = true)
4549
}
4650

51+
internal fun getMockObjectsPool(): ObjectsPool {
52+
return mockk<ObjectsPool>(relaxed = true)
53+
}
54+
4755
internal fun ObjectsPool.size(): Int {
4856
val pool = this.getPrivateField<Map<String, BaseLiveObject>>("pool")
4957
return pool.size
5058
}
5159

60+
/**
61+
* ======================================
62+
* START - DefaultLiveObjects dep mocks
63+
* ======================================
64+
*/
5265
internal val ObjectsManager.SyncObjectsDataPool: Map<String, ObjectState>
5366
get() = this.getPrivateField("syncObjectsDataPool")
5467

@@ -82,3 +95,62 @@ internal fun getDefaultLiveObjectsWithMockedDeps(
8295
}
8396
return defaultLiveObjects
8497
}
98+
/**
99+
* ======================================
100+
* END - DefaultLiveObjects dep mocks
101+
* ======================================
102+
*/
103+
104+
/**
105+
* ======================================
106+
* START - DefaultLiveCounter dep mocks
107+
* ======================================
108+
*/
109+
internal var DefaultLiveCounter.LiveCounterManager: LiveCounterManager
110+
get() = this.getPrivateField("liveCounterManager")
111+
set(value) = this.setPrivateField("liveCounterManager", value)
112+
113+
internal fun getDefaultLiveCounterWithMockedDeps(
114+
objectId: String = "counter:testCounter@1",
115+
relaxed: Boolean = false
116+
): DefaultLiveCounter {
117+
val defaultLiveCounter = DefaultLiveCounter.zeroValue(objectId, getMockLiveObjectsAdapter())
118+
if (relaxed) {
119+
defaultLiveCounter.LiveCounterManager = mockk(relaxed = true)
120+
} else {
121+
defaultLiveCounter.LiveCounterManager = spyk(defaultLiveCounter.LiveCounterManager, recordPrivateCalls = true)
122+
}
123+
return defaultLiveCounter
124+
}
125+
/**
126+
* ======================================
127+
* END - DefaultLiveCounter dep mocks
128+
* ======================================
129+
*/
130+
131+
/**
132+
* ======================================
133+
* START - DefaultLiveMap dep mocks
134+
* ======================================
135+
*/
136+
internal var DefaultLiveMap.LiveMapManager: LiveMapManager
137+
get() = this.getPrivateField("liveMapManager")
138+
set(value) = this.setPrivateField("liveMapManager", value)
139+
140+
internal fun getDefaultLiveMapWithMockedDeps(
141+
objectId: String = "map:testMap@1",
142+
relaxed: Boolean = false
143+
): DefaultLiveMap {
144+
val defaultLiveMap = DefaultLiveMap.zeroValue(objectId, getMockLiveObjectsAdapter(), getMockObjectsPool())
145+
if (relaxed) {
146+
defaultLiveMap.LiveMapManager = mockk(relaxed = true)
147+
} else {
148+
defaultLiveMap.LiveMapManager = spyk(defaultLiveMap.LiveMapManager, recordPrivateCalls = true)
149+
}
150+
return defaultLiveMap
151+
}
152+
/**
153+
* ======================================
154+
* END - DefaultLiveMap dep mocks
155+
* ======================================
156+
*/

live-objects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal val dummyObjectMap = ObjectMap(
3535
)
3636

3737
internal val dummyObjectCounter = ObjectCounter(
38-
count = 123.0
38+
count = 123
3939
)
4040

4141
internal val dummyObjectMapOp = ObjectMapOp(
@@ -44,7 +44,7 @@ internal val dummyObjectMapOp = ObjectMapOp(
4444
)
4545

4646
internal val dummyObjectCounterOp = ObjectCounterOp(
47-
amount = 10.0
47+
amount = 10
4848
)
4949

5050
internal val dummyObjectOperation = ObjectOperation(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class DefaultLiveObjectsTest {
103103
operation = ObjectOperation(
104104
action = ObjectOperationAction.CounterInc,
105105
objectId = "counter:testObject@1",
106-
counterOp = ObjectCounterOp(amount = 5.0)
106+
counterOp = ObjectCounterOp(amount = 5)
107107
),
108108
serial = "serial1",
109109
siteCode = "site1"

live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class ObjectsManagerTest {
4444
objectId = "counter:testObject@2", // Does not exist in pool
4545
tombstone = false,
4646
siteTimeserials = mapOf("site1" to "syncSerial1"),
47-
counter = ObjectCounter(count = 20.0)
47+
counter = ObjectCounter(count = 20)
4848
)
4949
)
5050
val objectMessage3 = ObjectMessage(
@@ -183,7 +183,7 @@ class ObjectsManagerTest {
183183
operation = ObjectOperation(
184184
action = ObjectOperationAction.CounterCreate,
185185
objectId = "counter:testObject@1",
186-
counterOp = ObjectCounterOp(amount = 5.30)
186+
counterOp = ObjectCounterOp(amount = 5)
187187
),
188188
serial = "serial1",
189189
siteCode = "site1"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.ably.lib.objects.unit.type.livecounter
2+
3+
import io.ably.lib.objects.ObjectState
4+
import io.ably.lib.objects.unit.getDefaultLiveCounterWithMockedDeps
5+
import org.junit.Test
6+
import kotlin.test.assertEquals
7+
8+
class DefaultLiveCounterTest {
9+
@Test
10+
fun `(RTLC6, RTLC6a) DefaultLiveCounter should override serials with state serials from sync`() {
11+
val liveCounter = getDefaultLiveCounterWithMockedDeps("counter:testCounter@1")
12+
13+
// Set initial data
14+
liveCounter.siteTimeserials["site1"] = "serial1"
15+
liveCounter.siteTimeserials["site2"] = "serial2"
16+
17+
val objectState = ObjectState(
18+
objectId = "counter:testCounter@1",
19+
siteTimeserials = mapOf("site3" to "serial3", "site4" to "serial4"),
20+
tombstone = false,
21+
)
22+
liveCounter.applyObjectSync(objectState)
23+
assertEquals(mapOf("site3" to "serial3", "site4" to "serial4"), liveCounter.siteTimeserials) // RTLC6a
24+
}
25+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.ably.lib.objects.unit.type.livecounter
2+
3+
import io.ably.lib.objects.*
4+
import io.ably.lib.objects.ObjectCounterOp
5+
import io.ably.lib.objects.ObjectMessage
6+
import io.ably.lib.objects.ObjectOperation
7+
import io.ably.lib.objects.ObjectOperationAction
8+
import io.ably.lib.objects.ObjectState
9+
import io.ably.lib.objects.type.livecounter.DefaultLiveCounter
10+
import io.ably.lib.objects.type.ObjectType
11+
import io.ably.lib.objects.unit.LiveCounterManager
12+
import io.ably.lib.objects.unit.getDefaultLiveCounterWithMockedDeps
13+
import io.mockk.mockk
14+
import io.mockk.spyk
15+
import io.mockk.verify
16+
import org.junit.Test
17+
import kotlin.test.assertEquals
18+
import kotlin.test.assertFalse
19+
import kotlin.test.assertTrue
20+
21+
class DefaultLiveCounterManagerTest {
22+
23+
@Test
24+
fun `(RTLC6, RTLC6b, RTLC6c) DefaultLiveCounter should override counter data with state from sync`() {
25+
val liveCounter = getDefaultLiveCounterWithMockedDeps()
26+
val liveCounterManager = liveCounter.LiveCounterManager
27+
28+
// Set initial data
29+
liveCounter.data = 10L
30+
31+
val objectState = ObjectState(
32+
objectId = "testCounterId",
33+
counter = ObjectCounter(count = 25L),
34+
siteTimeserials = mapOf("site3" to "serial3", "site4" to "serial4"),
35+
tombstone = false,
36+
)
37+
38+
val update = liveCounterManager.applyState(objectState)
39+
40+
assertFalse(liveCounter.createOperationIsMerged) // RTLC6b
41+
assertEquals(25L, liveCounter.data) // RTLC6c
42+
assertEquals(15L, update["amount"]) // Difference between old and new data
43+
}
44+
45+
46+
@Test
47+
fun `(RTLC6, RTLC6d) DefaultLiveCounter should merge initial data from create operation`() {
48+
val liveCounter = getDefaultLiveCounterWithMockedDeps()
49+
val liveCounterManager = liveCounter.LiveCounterManager
50+
51+
// Set initial data
52+
liveCounter.data = 5L
53+
54+
val createOp = ObjectOperation(
55+
action = ObjectOperationAction.CounterCreate,
56+
objectId = "testCounterId",
57+
counter = ObjectCounter(count = 10)
58+
)
59+
60+
val objectState = ObjectState(
61+
objectId = "testCounterId",
62+
counter = ObjectCounter(count = 15),
63+
createOp = createOp,
64+
siteTimeserials = mapOf("site1" to "serial1"),
65+
tombstone = false,
66+
)
67+
68+
// RTLC6d - Merge initial data from create operation
69+
val update = liveCounterManager.applyState(objectState)
70+
71+
assertEquals(25L, liveCounter.data) // 15 from state + 10 from create op
72+
assertEquals(20L, update["amount"]) // Total change
73+
}
74+
75+
76+
}

0 commit comments

Comments
 (0)