From b6f4dbdfeddcb8a864134f7f5c02682325900d04 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Wed, 6 Nov 2024 16:04:47 +0530 Subject: [PATCH] Added channel attach room lifecycle spec CHA-RL1f, CHA-RL1g, updated testhelpers for the same --- .../com/ably/chat/RoomLifecycleManagerTest.kt | 76 ++++++++++++++++++- .../src/test/java/com/ably/chat/TestUtils.kt | 13 ++++ .../helpers/RoomLifecycleManagerExtensions.kt | 11 --- .../utils/RoomLifecycleManagerTestHelpers.kt | 32 ++++++++ 4 files changed, 120 insertions(+), 12 deletions(-) delete mode 100644 chat-android/src/test/java/com/ably/helpers/RoomLifecycleManagerExtensions.kt create mode 100644 chat-android/src/test/java/com/ably/utils/RoomLifecycleManagerTestHelpers.kt diff --git a/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt b/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt index ab0ab60e..6b6018b9 100644 --- a/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt +++ b/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt @@ -1,10 +1,16 @@ package com.ably.chat -import com.ably.helpers.atomicCoroutineScope +import com.ably.utils.atomicCoroutineScope +import com.ably.utils.createRoomFeatureMocks import io.ably.lib.types.AblyException +import io.ably.lib.types.ErrorInfo import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockkStatic import io.mockk.spyk +import io.mockk.verify import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -127,10 +133,78 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1f) Attach op should attach each contributor channel sequentially`() = runTest { + val status = spyk() + + mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) + val capturedChannels = mutableListOf() + coEvery { any().attachCoroutine() } coAnswers { + capturedChannels.add(firstArg()) + } + + val contributors = createRoomFeatureMocks() + Assert.assertEquals(5, contributors.size) + + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors)) + roomLifecycle.attach() + val result = kotlin.runCatching { roomLifecycle.attach() } + Assert.assertTrue(result.isSuccess) + Assert.assertEquals(5, capturedChannels.size) + repeat(5) { + Assert.assertEquals(contributors[it].channel.name, capturedChannels[it].name) + } + Assert.assertEquals("1234::\$chat::\$chatMessages", capturedChannels[0].name) + Assert.assertEquals("1234::\$chat::\$chatMessages", capturedChannels[1].name) + Assert.assertEquals("1234::\$chat::\$chatMessages", capturedChannels[2].name) + Assert.assertEquals("1234::\$chat::\$typingIndicators", capturedChannels[3].name) + Assert.assertEquals("1234::\$chat::\$reactions", capturedChannels[4].name) } @Test fun `(CHA-RL1g) When all contributor channels ATTACH, op is complete and room should be considered ATTACHED`() = runTest { + val status = spyk() + + mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) + coEvery { any().attachCoroutine() } returns Unit + + val contributors = createRoomFeatureMocks("1234") + val contributorErrors = mutableListOf() + for (contributor in contributors) { + every { + contributor.contributor.discontinuityDetected(capture(contributorErrors)) + } returns Unit + } + Assert.assertEquals(5, contributors.size) + + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) { + val pendingDiscontinuityEvents = mutableMapOf().apply { + for (contributor in contributors) { + put(contributor, ErrorInfo("${contributor.channel.name} error", 500)) + } + } + this.setPrivateField("_pendingDiscontinuityEvents", pendingDiscontinuityEvents) + } + justRun { roomLifecycle invokeNoArgs "clearAllTransientDetachTimeouts" } + + roomLifecycle.attach() + val result = kotlin.runCatching { roomLifecycle.attach() } + + // CHA-RL1g1 + Assert.assertTrue(result.isSuccess) + Assert.assertEquals(RoomLifecycle.Attached, status.current) + assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } + + // CHA-RL1g2 + verify(exactly = 1) { + for (contributor in contributors) { + contributor.contributor.discontinuityDetected(any()) + } + } + Assert.assertEquals(5, contributorErrors.size) + + // CHA-RL1g3 + verify(exactly = 1) { + roomLifecycle invokeNoArgs "clearAllTransientDetachTimeouts" + } } } diff --git a/chat-android/src/test/java/com/ably/chat/TestUtils.kt b/chat-android/src/test/java/com/ably/chat/TestUtils.kt index 9a77e244..8798791b 100644 --- a/chat-android/src/test/java/com/ably/chat/TestUtils.kt +++ b/chat-android/src/test/java/com/ably/chat/TestUtils.kt @@ -64,3 +64,16 @@ suspend fun assertWaiter(timeoutInMs: Long = 10000, block: () -> Boolean) { } } } + +fun Any.setPrivateField(name: String, value: Any) { + val valueField = javaClass.getDeclaredField(name) + valueField.isAccessible = true + return valueField.set(this, value) +} + +fun Any.getPrivateField(name: String): T { + val valueField = javaClass.getDeclaredField(name) + valueField.isAccessible = true + @Suppress("UNCHECKED_CAST") + return valueField.get(this) as T +} diff --git a/chat-android/src/test/java/com/ably/helpers/RoomLifecycleManagerExtensions.kt b/chat-android/src/test/java/com/ably/helpers/RoomLifecycleManagerExtensions.kt deleted file mode 100644 index 95f5e60d..00000000 --- a/chat-android/src/test/java/com/ably/helpers/RoomLifecycleManagerExtensions.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.ably.helpers - -import com.ably.chat.AtomicCoroutineScope -import com.ably.chat.RoomLifecycleManager - -fun RoomLifecycleManager.atomicCoroutineScope(): AtomicCoroutineScope { - val loadedClass = RoomLifecycleManager::class - val valueField = loadedClass.java.getDeclaredField("atomicCoroutineScope") - valueField.isAccessible = true - return valueField.get(this) as AtomicCoroutineScope -} diff --git a/chat-android/src/test/java/com/ably/utils/RoomLifecycleManagerTestHelpers.kt b/chat-android/src/test/java/com/ably/utils/RoomLifecycleManagerTestHelpers.kt new file mode 100644 index 00000000..079d369f --- /dev/null +++ b/chat-android/src/test/java/com/ably/utils/RoomLifecycleManagerTestHelpers.kt @@ -0,0 +1,32 @@ +package com.ably.utils + +import com.ably.chat.AtomicCoroutineScope +import com.ably.chat.ChatApi +import com.ably.chat.DefaultMessages +import com.ably.chat.DefaultOccupancy +import com.ably.chat.DefaultPresence +import com.ably.chat.DefaultRoomReactions +import com.ably.chat.DefaultTyping +import com.ably.chat.ResolvedContributor +import com.ably.chat.RoomLifecycleManager +import com.ably.chat.getPrivateField +import io.ably.lib.realtime.AblyRealtime +import io.ably.lib.types.ClientOptions +import io.mockk.mockk +import io.mockk.spyk + +fun RoomLifecycleManager.atomicCoroutineScope(): AtomicCoroutineScope { + return getPrivateField("atomicCoroutineScope") +} + +fun createRoomFeatureMocks(roomId: String = "1234"): List { + val realtimeClient = spyk(AblyRealtime(ClientOptions("id:key").apply { autoConnect = false })) + val chatApi = mockk(relaxed = true) + + val messagesContributor = spyk(DefaultMessages(roomId, realtimeClient.channels, chatApi), recordPrivateCalls = true) + val presenceContributor = spyk(DefaultPresence(messagesContributor), recordPrivateCalls = true) + val occupancyContributor = spyk(DefaultOccupancy(messagesContributor), recordPrivateCalls = true) + val typingContributor = spyk(DefaultTyping(roomId, realtimeClient), recordPrivateCalls = true) + val reactionsContributor = spyk(DefaultRoomReactions(roomId, realtimeClient), recordPrivateCalls = true) + return listOf(messagesContributor, presenceContributor, occupancyContributor, typingContributor, reactionsContributor) +}