Skip to content

Commit

Permalink
Added channel attach room lifecycle spec CHA-RL1f, CHA-RL1g, updated
Browse files Browse the repository at this point in the history
testhelpers for the same
  • Loading branch information
sacOO7 committed Nov 6, 2024
1 parent 52005c0 commit b6f4dbd
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -127,10 +133,78 @@ class RoomLifecycleManagerTest {

@Test
fun `(CHA-RL1f) Attach op should attach each contributor channel sequentially`() = runTest {
val status = spyk<DefaultStatus>()

mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine)
val capturedChannels = mutableListOf<io.ably.lib.realtime.Channel>()
coEvery { any<io.ably.lib.realtime.Channel>().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<DefaultStatus>()

mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine)
coEvery { any<io.ably.lib.realtime.Channel>().attachCoroutine() } returns Unit

val contributors = createRoomFeatureMocks("1234")
val contributorErrors = mutableListOf<ErrorInfo>()
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<ResolvedContributor, ErrorInfo?>().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<ErrorInfo>())
}
}
Assert.assertEquals(5, contributorErrors.size)

// CHA-RL1g3
verify(exactly = 1) {
roomLifecycle invokeNoArgs "clearAllTransientDetachTimeouts"
}
}
}
13 changes: 13 additions & 0 deletions chat-android/src/test/java/com/ably/chat/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T>Any.getPrivateField(name: String): T {
val valueField = javaClass.getDeclaredField(name)
valueField.isAccessible = true
@Suppress("UNCHECKED_CAST")
return valueField.get(this) as T
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<ResolvedContributor> {
val realtimeClient = spyk(AblyRealtime(ClientOptions("id:key").apply { autoConnect = false }))
val chatApi = mockk<ChatApi>(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)
}

0 comments on commit b6f4dbd

Please sign in to comment.