Skip to content

Commit fd1d2cf

Browse files
committed
[ECO-5457] Added integration tests LiveCounter accessors
1 parent 6667b4d commit fd1d2cf

4 files changed

Lines changed: 273 additions & 7 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package io.ably.lib.objects.integration
2+
3+
import io.ably.lib.objects.LiveCounter
4+
import io.ably.lib.objects.LiveMap
5+
import io.ably.lib.objects.assertWaiter
6+
import io.ably.lib.objects.integration.helpers.fixtures.createUserMapWithCountersObject
7+
import io.ably.lib.objects.integration.setup.IntegrationTest
8+
import kotlinx.coroutines.test.runTest
9+
import org.junit.Test
10+
import kotlin.test.assertEquals
11+
import kotlin.test.assertNotNull
12+
13+
class DefaultLiveCounterTest: IntegrationTest() {
14+
/**
15+
* Tests the synchronization process when a user map object with counters is initialized before channel attach.
16+
* This includes checking the initial values of all counter objects and nested maps in the
17+
* comprehensive user engagement counter structure.
18+
*/
19+
@Test
20+
fun testLiveCounterSync() = runTest {
21+
val channelName = generateChannelName()
22+
val userMapObjectId = restObjects.createUserMapWithCountersObject(channelName)
23+
restObjects.setMapRef(channelName, "root", "user", userMapObjectId)
24+
25+
val channel = getRealtimeChannel(channelName)
26+
val rootMap = channel.objects.root
27+
28+
// Get the user map object from the root map
29+
val userMap = rootMap.get("user") as LiveMap
30+
assertNotNull(userMap, "User map should be synchronized")
31+
assertEquals(7L, userMap.size(), "User map should contain 7 top-level entries")
32+
33+
// Assert direct counter objects at the top level of the user map
34+
// Test profileViews counter - should have initial value of 127
35+
val profileViewsCounter = userMap.get("profileViews") as LiveCounter
36+
assertNotNull(profileViewsCounter, "Profile views counter should exist")
37+
assertEquals(127L, profileViewsCounter.value(), "Profile views counter should have initial value of 127")
38+
39+
// Test postLikes counter - should have initial value of 45
40+
val postLikesCounter = userMap.get("postLikes") as LiveCounter
41+
assertNotNull(postLikesCounter, "Post likes counter should exist")
42+
assertEquals(45L, postLikesCounter.value(), "Post likes counter should have initial value of 45")
43+
44+
// Test commentCount counter - should have initial value of 23
45+
val commentCountCounter = userMap.get("commentCount") as LiveCounter
46+
assertNotNull(commentCountCounter, "Comment count counter should exist")
47+
assertEquals(23L, commentCountCounter.value(), "Comment count counter should have initial value of 23")
48+
49+
// Test followingCount counter - should have initial value of 89
50+
val followingCountCounter = userMap.get("followingCount") as LiveCounter
51+
assertNotNull(followingCountCounter, "Following count counter should exist")
52+
assertEquals(89L, followingCountCounter.value(), "Following count counter should have initial value of 89")
53+
54+
// Test followersCount counter - should have initial value of 156
55+
val followersCountCounter = userMap.get("followersCount") as LiveCounter
56+
assertNotNull(followersCountCounter, "Followers count counter should exist")
57+
assertEquals(156L, followersCountCounter.value(), "Followers count counter should have initial value of 156")
58+
59+
// Test loginStreak counter - should have initial value of 7
60+
val loginStreakCounter = userMap.get("loginStreak") as LiveCounter
61+
assertNotNull(loginStreakCounter, "Login streak counter should exist")
62+
assertEquals(7L, loginStreakCounter.value(), "Login streak counter should have initial value of 7")
63+
64+
// Assert the nested engagement metrics map
65+
val engagementMetrics = userMap.get("engagementMetrics") as LiveMap
66+
assertNotNull(engagementMetrics, "Engagement metrics map should exist")
67+
assertEquals(4L, engagementMetrics.size(), "Engagement metrics map should contain 4 counter entries")
68+
69+
// Assert counter objects within the engagement metrics map
70+
// Test totalShares counter - should have initial value of 34
71+
val totalSharesCounter = engagementMetrics.get("totalShares") as LiveCounter
72+
assertNotNull(totalSharesCounter, "Total shares counter should exist")
73+
assertEquals(34L, totalSharesCounter.value(), "Total shares counter should have initial value of 34")
74+
75+
// Test totalBookmarks counter - should have initial value of 67
76+
val totalBookmarksCounter = engagementMetrics.get("totalBookmarks") as LiveCounter
77+
assertNotNull(totalBookmarksCounter, "Total bookmarks counter should exist")
78+
assertEquals(67L, totalBookmarksCounter.value(), "Total bookmarks counter should have initial value of 67")
79+
80+
// Test totalReactions counter - should have initial value of 189
81+
val totalReactionsCounter = engagementMetrics.get("totalReactions") as LiveCounter
82+
assertNotNull(totalReactionsCounter, "Total reactions counter should exist")
83+
assertEquals(189L, totalReactionsCounter.value(), "Total reactions counter should have initial value of 189")
84+
85+
// Test dailyActiveStreak counter - should have initial value of 12
86+
val dailyActiveStreakCounter = engagementMetrics.get("dailyActiveStreak") as LiveCounter
87+
assertNotNull(dailyActiveStreakCounter, "Daily active streak counter should exist")
88+
assertEquals(12L, dailyActiveStreakCounter.value(), "Daily active streak counter should have initial value of 12")
89+
90+
// Verify that all expected counter keys exist at the top level
91+
val topLevelKeys = userMap.keys().toSet()
92+
val expectedTopLevelKeys = setOf(
93+
"profileViews", "postLikes", "commentCount", "followingCount",
94+
"followersCount", "loginStreak", "engagementMetrics"
95+
)
96+
assertEquals(expectedTopLevelKeys, topLevelKeys, "Top-level keys should match expected counter keys")
97+
98+
// Verify that all expected counter keys exist in the engagement metrics map
99+
val engagementKeys = engagementMetrics.keys().toSet()
100+
val expectedEngagementKeys = setOf(
101+
"totalShares", "totalBookmarks", "totalReactions", "dailyActiveStreak"
102+
)
103+
assertEquals(expectedEngagementKeys, engagementKeys, "Engagement metrics keys should match expected counter keys")
104+
105+
// Verify total counter values match expectations (useful for integration testing)
106+
val totalUserCounterValues = listOf(127L, 45L, 23L, 89L, 156L, 7L).sum()
107+
val totalEngagementCounterValues = listOf(34L, 67L, 189L, 12L).sum()
108+
assertEquals(447L, totalUserCounterValues, "Sum of user counter values should be 447")
109+
assertEquals(302L, totalEngagementCounterValues, "Sum of engagement counter values should be 302")
110+
}
111+
112+
/**
113+
* Tests sequential counter operations including creation with initial value, incrementing by various amounts,
114+
* decrementing by various amounts, and validates the resulting counter value after each operation.
115+
*/
116+
@Test
117+
fun testLiveCounterOperations() = runTest {
118+
val channelName = generateChannelName()
119+
val channel = getRealtimeChannel(channelName)
120+
val rootMap = channel.objects.root
121+
122+
// Step 1: Create a new counter with initial value of 10
123+
val testCounterObjectId = restObjects.createCounter(channelName, initialValue = 10L)
124+
restObjects.setMapRef(channelName, "root", "testCounter", testCounterObjectId)
125+
126+
// Wait for updated testCounter to be available in the root map
127+
assertWaiter { rootMap.get("testCounter") != null }
128+
129+
// Assert initial state after creation
130+
val testCounter = rootMap.get("testCounter") as LiveCounter
131+
assertNotNull(testCounter, "Test counter should be created and accessible")
132+
assertEquals(10L, testCounter.value(), "Counter should have initial value of 10")
133+
134+
// Step 2: Increment counter by 5 (10 + 5 = 15)
135+
restObjects.incrementCounter(channelName, testCounterObjectId, 5L)
136+
// Wait for the counter to be updated
137+
assertWaiter { testCounter.value() == 15L }
138+
139+
// Assert after first increment
140+
assertEquals(15L, testCounter.value(), "Counter should be incremented to 15")
141+
142+
// Step 3: Increment counter by 3 (15 + 3 = 18)
143+
restObjects.incrementCounter(channelName, testCounterObjectId, 3L)
144+
// Wait for the counter to be updated
145+
assertWaiter { testCounter.value() == 18L }
146+
147+
// Assert after second increment
148+
assertEquals(18L, testCounter.value(), "Counter should be incremented to 18")
149+
150+
// Step 4: Increment counter by a larger amount: 12 (18 + 12 = 30)
151+
restObjects.incrementCounter(channelName, testCounterObjectId, 12L)
152+
// Wait for the counter to be updated
153+
assertWaiter { testCounter.value() == 30L }
154+
155+
// Assert after third increment
156+
assertEquals(30L, testCounter.value(), "Counter should be incremented to 30")
157+
158+
// Step 5: Decrement counter by 7 (30 - 7 = 23)
159+
restObjects.decrementCounter(channelName, testCounterObjectId, 7L)
160+
// Wait for the counter to be updated
161+
assertWaiter { testCounter.value() == 23L }
162+
163+
// Assert after first decrement
164+
assertEquals(23L, testCounter.value(), "Counter should be decremented to 23")
165+
166+
// Step 6: Decrement counter by 4 (23 - 4 = 19)
167+
restObjects.decrementCounter(channelName, testCounterObjectId, 4L)
168+
// Wait for the counter to be updated
169+
assertWaiter { testCounter.value() == 19L }
170+
171+
// Assert after second decrement
172+
assertEquals(19L, testCounter.value(), "Counter should be decremented to 19")
173+
174+
// Step 7: Increment counter by 1 (19 + 1 = 20)
175+
restObjects.incrementCounter(channelName, testCounterObjectId, 1L)
176+
// Wait for the counter to be updated
177+
assertWaiter { testCounter.value() == 20L }
178+
179+
// Assert after final increment
180+
assertEquals(20L, testCounter.value(), "Counter should be incremented to 20")
181+
182+
// Step 8: Decrement counter by a larger amount: 15 (20 - 15 = 5)
183+
restObjects.decrementCounter(channelName, testCounterObjectId, 15L)
184+
// Wait for the counter to be updated
185+
assertWaiter { testCounter.value() == 5L }
186+
187+
// Assert after large decrement
188+
assertEquals(5L, testCounter.value(), "Counter should be decremented to 5")
189+
190+
// Final verification - test final increment to ensure counter still works
191+
restObjects.incrementCounter(channelName, testCounterObjectId, 25L)
192+
assertWaiter { testCounter.value() == 30L }
193+
194+
// Assert final state
195+
assertEquals(30L, testCounter.value(), "Counter should have final value of 30")
196+
197+
// Verify the counter object is still accessible and functioning
198+
assertNotNull(testCounter, "Counter should still be accessible at the end")
199+
200+
// Verify we can still access it from the root map
201+
val finalCounterCheck = rootMap.get("testCounter") as LiveCounter
202+
assertNotNull(finalCounterCheck, "Counter should still be accessible from root map")
203+
assertEquals(30L, finalCounterCheck.value(), "Final counter value should be 30 when accessed from root map")
204+
}
205+
}

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,10 @@ internal object PayloadBuilder {
126126
val opBody = JsonObject().apply {
127127
addProperty("operation", ACTION_STRINGS[ObjectOperationAction.CounterInc])
128128
addProperty("objectId", objectId)
129+
add("data", JsonObject().apply {
130+
addProperty("number", number)
131+
})
129132
}
130-
131-
val dataObj = JsonObject().apply {
132-
addProperty("number", number)
133-
}
134-
opBody.add("data", dataObj)
135-
136133
return opBody
137134
}
138135
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,3 @@ internal class RestObjects(options: ClientOptions) {
117117
val success: Boolean = true
118118
)
119119
}
120-
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.ably.lib.objects.integration.helpers.fixtures
2+
3+
import io.ably.lib.objects.integration.helpers.RestObjects
4+
5+
/**
6+
* Creates a comprehensive test fixture object tree focused on user-context counters.
7+
*
8+
* This method establishes a hierarchical structure of live counter objects for testing
9+
* counter operations in a realistic user engagement context, creating various types of
10+
* counters and establishing references between them through nested maps.
11+
*
12+
* **Object Tree Structure:**
13+
* ```
14+
* userMap (Map)
15+
* ├── profileViews → Counter(value=127)
16+
* ├── postLikes → Counter(value=45)
17+
* ├── commentCount → Counter(value=23)
18+
* ├── followingCount → Counter(value=89)
19+
* ├── followersCount → Counter(value=156)
20+
* ├── loginStreak → Counter(value=7)
21+
* └── engagementMetrics → Map{
22+
* ├── "totalShares" → Counter(value=34)
23+
* ├── "totalBookmarks" → Counter(value=67)
24+
* ├── "totalReactions" → Counter(value=189)
25+
* └── "dailyActiveStreak" → Counter(value=12)
26+
* }
27+
* ```
28+
*
29+
* @param channelName The channel where the counter object tree will be created
30+
* @return The object ID of the root test map containing all counter references
31+
*/
32+
internal fun RestObjects.createUserMapWithCountersObject(channelName: String): String {
33+
// Create the main test map first
34+
val testMapObjectId = createMap(channelName)
35+
36+
// Create various user-context relevant counters
37+
val profileViewsCounterObjectId = createCounter(channelName, 127)
38+
val postLikesCounterObjectId = createCounter(channelName, 45)
39+
val commentCountCounterObjectId = createCounter(channelName, 23)
40+
val followingCountCounterObjectId = createCounter(channelName, 89)
41+
val followersCountCounterObjectId = createCounter(channelName, 156)
42+
val loginStreakCounterObjectId = createCounter(channelName, 7)
43+
44+
// Create engagement metrics nested map with counters
45+
val engagementMetricsMapObjectId = createMap(
46+
channelName,
47+
data = mapOf(
48+
"totalShares" to DataFixtures.mapRef(createCounter(channelName, 34)),
49+
"totalBookmarks" to DataFixtures.mapRef(createCounter(channelName, 67)),
50+
"totalReactions" to DataFixtures.mapRef(createCounter(channelName, 189)),
51+
"dailyActiveStreak" to DataFixtures.mapRef(createCounter(channelName, 12))
52+
)
53+
)
54+
55+
// Set up the main test map structure with references to all created counters
56+
setMapRef(channelName, testMapObjectId, "profileViews", profileViewsCounterObjectId)
57+
setMapRef(channelName, testMapObjectId, "postLikes", postLikesCounterObjectId)
58+
setMapRef(channelName, testMapObjectId, "commentCount", commentCountCounterObjectId)
59+
setMapRef(channelName, testMapObjectId, "followingCount", followingCountCounterObjectId)
60+
setMapRef(channelName, testMapObjectId, "followersCount", followersCountCounterObjectId)
61+
setMapRef(channelName, testMapObjectId, "loginStreak", loginStreakCounterObjectId)
62+
setMapRef(channelName, testMapObjectId, "engagementMetrics", engagementMetricsMapObjectId)
63+
64+
return testMapObjectId
65+
}

0 commit comments

Comments
 (0)