From ad8c0719832fcd517221a9c350a93ee35756225d Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Nov 2024 20:03:26 +0530 Subject: [PATCH 1/2] Updated Connection Interfaces in accordance with latest DR/spec changes --- .../src/main/java/com/ably/chat/Connection.kt | 96 +++++++++++++++++ .../java/com/ably/chat/ConnectionStatus.kt | 101 ------------------ 2 files changed, 96 insertions(+), 101 deletions(-) delete mode 100644 chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt diff --git a/chat-android/src/main/java/com/ably/chat/Connection.kt b/chat-android/src/main/java/com/ably/chat/Connection.kt index a1378cc..1f346fa 100644 --- a/chat-android/src/main/java/com/ably/chat/Connection.kt +++ b/chat-android/src/main/java/com/ably/chat/Connection.kt @@ -1,5 +1,73 @@ package com.ably.chat +import io.ably.lib.types.ErrorInfo + +/** + * Default timeout for transient states before we attempt handle them as a state change. + */ +const val TRANSIENT_TIMEOUT = 5000 + +/** + * The different states that the connection can be in through its lifecycle. + */ +enum class ConnectionStatus(val stateName: String) { + /** + * A temporary state for when the library is first initialized. + */ + Initialized("initialized"), + + /** + * The library is currently connecting to Ably. + */ + Connecting("connecting"), + + /** + * The library is currently connected to Ably. + */ + Connected("connected"), + + /** + * The library is currently disconnected from Ably, but will attempt to reconnect. + */ + Disconnected("disconnected"), + + /** + * The library is in an extended state of disconnection, but will attempt to reconnect. + */ + Suspended("suspended"), + + /** + * The library is currently disconnected from Ably and will not attempt to reconnect. + */ + Failed("failed"), +} + +/** + * Represents a change in the status of the connection. + */ +data class ConnectionStatusChange( + /** + * The new status of the connection. + */ + val current: ConnectionStatus, + + /** + * The previous status of the connection. + */ + val previous: ConnectionStatus, + + /** + * An error that provides a reason why the connection has + * entered the new status, if applicable. + */ + val error: ErrorInfo?, + + /** + * The time in milliseconds that the client will wait before attempting to reconnect. + */ + val retryIn: Long?, +) + /** * Represents a connection to Ably. */ @@ -8,4 +76,32 @@ interface Connection { * The current status of the connection. */ val status: ConnectionStatus + + /** + * The current error, if any, that caused the connection to enter the current status. + */ + val error: ErrorInfo? + + /** + * Registers a listener that will be called whenever the connection status changes. + * @param listener The function to call when the status changes. + * @returns An object that can be used to unregister the listener. + */ + fun onStatusChange(listener: Listener): Subscription + + /** + * An interface for listening to changes for the connection status + */ + fun interface Listener { + /** + * A function that can be called when the connection status changes. + * @param change The change in status. + */ + fun connectionStatusChanged(change: ConnectionStatusChange) + } + + /** + * Removes all listeners that were added by the `onStatusChange` method. + */ + fun offAllStatusChange() } diff --git a/chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt b/chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt deleted file mode 100644 index 2822f10..0000000 --- a/chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.ably.chat - -import io.ably.lib.types.ErrorInfo - -/** - * Default timeout for transient states before we attempt handle them as a state change. - */ -const val TRANSIENT_TIMEOUT = 5000 - -/** - * Represents a connection to Ably. - */ -interface ConnectionStatus { - /** - * The current status of the connection. - */ - val current: ConnectionLifecycle - - /** - * The current error, if any, that caused the connection to enter the current status. - */ - val error: ErrorInfo? - - /** - * Registers a listener that will be called whenever the connection status changes. - * @param listener The function to call when the status changes. - */ - fun on(listener: Listener): Subscription - - /** - * An interface for listening to changes for the connection status - */ - fun interface Listener { - /** - * A function that can be called when the connection status changes. - * @param change The change in status. - */ - fun connectionStatusChanged(change: ConnectionStatusChange) - } -} - -/** - * The different states that the connection can be in through its lifecycle. - */ -enum class ConnectionLifecycle(val stateName: String) { - /** - * A temporary state for when the library is first initialized. - */ - Initialized("initialized"), - - /** - * The library is currently connecting to Ably. - */ - Connecting("connecting"), - - /** - * The library is currently connected to Ably. - */ - Connected("connected"), - - /** - * The library is currently disconnected from Ably, but will attempt to reconnect. - */ - Disconnected("disconnected"), - - /** - * The library is in an extended state of disconnection, but will attempt to reconnect. - */ - Suspended("suspended"), - - /** - * The library is currently disconnected from Ably and will not attempt to reconnect. - */ - Failed("failed"), -} - -/** - * Represents a change in the status of the connection. - */ -data class ConnectionStatusChange( - /** - * The new status of the connection. - */ - val current: ConnectionLifecycle, - - /** - * The previous status of the connection. - */ - val previous: ConnectionLifecycle, - - /** - * An error that provides a reason why the connection has - * entered the new status, if applicable. - */ - val error: ErrorInfo?, - - /** - * The time in milliseconds that the client will wait before attempting to reconnect. - */ - val retryIn: Long?, -) From 0fae205baabad4632f93727b7cb9bcc1cdf208c5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Mon, 11 Nov 2024 21:01:33 +0530 Subject: [PATCH 2/2] Refactored RoomLifeCycle and RoomStatus interfaces according to the DR/spec changes --- .../src/main/java/com/ably/chat/Room.kt | 52 ++++- .../com/ably/chat/RoomLifecycleManager.kt | 66 ++++--- .../src/main/java/com/ably/chat/RoomStatus.kt | 178 +++++++++--------- .../com/ably/chat/RoomLifecycleManagerTest.kt | 84 ++++----- 4 files changed, 210 insertions(+), 170 deletions(-) diff --git a/chat-android/src/main/java/com/ably/chat/Room.kt b/chat-android/src/main/java/com/ably/chat/Room.kt index 4c60163..5922e7c 100644 --- a/chat-android/src/main/java/com/ably/chat/Room.kt +++ b/chat-android/src/main/java/com/ably/chat/Room.kt @@ -2,6 +2,7 @@ package com.ably.chat +import io.ably.lib.types.ErrorInfo import io.ably.lib.util.Log.LogHandler import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -56,20 +57,37 @@ interface Room { */ val occupancy: Occupancy + /** + * Returns the room options. + * + * @returns A copy of the options used to create the room. + */ + val options: RoomOptions + /** * (CHA-RS2) - * Returns an object that can be used to observe the status of the room. + * The current status of the room. * - * @returns The status observable. + * @returns The current status. */ val status: RoomStatus /** - * Returns the room options. - * - * @returns A copy of the options used to create the room. + * The current error, if any, that caused the room to enter the current status. */ - val options: RoomOptions + val error: ErrorInfo? + + /** + * Registers a listener that will be called whenever the room status changes. + * @param listener The function to call when the status changes. + * @returns An object that can be used to unregister the listener. + */ + fun onStatusChange(listener: RoomLifecycle.Listener): Subscription + + /** + * Removes all listeners that were added by the `onStatusChange` method. + */ + fun offAllStatusChange() /** * Attaches to the room to receive events in realtime. @@ -98,7 +116,6 @@ internal class DefaultRoom( ) : Room { private val _logger = logger - override val status = DefaultStatus(logger) /** * RoomScope is a crucial part of the Room lifecycle. It manages sequential and atomic operations. @@ -132,9 +149,18 @@ internal class DefaultRoom( override val occupancy = DefaultOccupancy( messages = messages, ) - private var _lifecycleManager: RoomLifecycleManager? = null + private val _statusLifecycle = DefaultRoomLifecycle(logger) + internal val statusLifecycle: DefaultRoomLifecycle + get() = _statusLifecycle + + override val status: RoomStatus + get() = _statusLifecycle.status + + override val error: ErrorInfo? + get() = _statusLifecycle.error + init { /** * TODO @@ -143,7 +169,7 @@ internal class DefaultRoom( * Currently, all features are initialized by default. */ val features = listOf(messages, presence, typing, reactions, occupancy) - _lifecycleManager = RoomLifecycleManager(roomScope, status, features, _logger) + _lifecycleManager = RoomLifecycleManager(roomScope, _statusLifecycle, features, _logger) /** * TODO * Make sure previous release op. for same was a success. @@ -151,7 +177,13 @@ internal class DefaultRoom( * Once this is a success, set room to initialized, if not set it to failed and throw error. * Note that impl. can change based on recent proposed changes to chat-room-lifecycle DR. */ - this.status.setStatus(RoomLifecycle.Initialized) + this._statusLifecycle.setStatus(RoomStatus.Initialized) + } + + override fun onStatusChange(listener: RoomLifecycle.Listener): Subscription = _statusLifecycle.onChange(listener) + + override fun offAllStatusChange() { + _statusLifecycle.offAll() } override suspend fun attach() { diff --git a/chat-android/src/main/java/com/ably/chat/RoomLifecycleManager.kt b/chat-android/src/main/java/com/ably/chat/RoomLifecycleManager.kt index 0591e87..e9e2850 100644 --- a/chat-android/src/main/java/com/ably/chat/RoomLifecycleManager.kt +++ b/chat-android/src/main/java/com/ably/chat/RoomLifecycleManager.kt @@ -94,8 +94,8 @@ interface RoomAttachmentResult : NewRoomStatus { } class DefaultRoomAttachmentResult : RoomAttachmentResult { - internal var statusField: RoomLifecycle = RoomLifecycle.Attached - override val status: RoomLifecycle + internal var statusField: RoomStatus = RoomStatus.Attached + override val status: RoomStatus get() = statusField internal var failedFeatureField: ResolvedContributor? = null @@ -123,13 +123,17 @@ class DefaultRoomAttachmentResult : RoomAttachmentResult { * An implementation of the `Status` interface. * @internal */ -class RoomLifecycleManager -(private val roomScope: CoroutineScope, status: DefaultStatus, contributors: List, logger: LogHandler? = null) { +class RoomLifecycleManager( + private val roomScope: CoroutineScope, + lifecycle: DefaultRoomLifecycle, + contributors: List, + logger: LogHandler? = null, +) { /** * The status of the room. */ - private var _status: DefaultStatus = status + private var _statusLifecycle: DefaultRoomLifecycle = lifecycle /** * The features that contribute to the room status. @@ -177,7 +181,7 @@ class RoomLifecycleManager private val _retryDurationInMs: Long = 250 init { - if (_status.current != RoomLifecycle.Attached) { + if (_statusLifecycle.status != RoomStatus.Attached) { _operationInProgress = true } // TODO - [CHA-RL4] set up room monitoring here @@ -212,7 +216,7 @@ class RoomLifecycleManager var result = kotlin.runCatching { doChannelWindDown(contributor) } while (result.isFailure) { // If in doing the wind down, we've entered failed state, then it's game over anyway - if (this._status.current === RoomLifecycle.Failed) { + if (this._statusLifecycle.status === RoomStatus.Failed) { error("room is in a failed state") } delay(_retryDurationInMs) @@ -222,11 +226,11 @@ class RoomLifecycleManager // A helper that allows us to retry the attach operation val doAttachWithRetry: suspend () -> Unit = { coroutineScope { - _status.setStatus(RoomLifecycle.Attaching) + _statusLifecycle.setStatus(RoomStatus.Attaching) val attachmentResult = doAttach() // If we're in failed, then we should wind down all the channels, eventually - but we're done here - if (attachmentResult.status === RoomLifecycle.Failed) { + if (attachmentResult.status === RoomStatus.Failed) { atomicCoroutineScope.async(LifecycleOperationPrecedence.Internal.priority) { runDownChannelsOnFailedAttach() } @@ -234,7 +238,7 @@ class RoomLifecycleManager } // If we're in suspended, then we should wait for the channel to reattach, but wait for it to do so - if (attachmentResult.status === RoomLifecycle.Suspended) { + if (attachmentResult.status === RoomStatus.Suspended) { val failedFeature = attachmentResult.failedFeature if (failedFeature == null) { AblyException.fromErrorInfo( @@ -264,7 +268,7 @@ class RoomLifecycleManager return doAttachWithRetry() } catch (ex: AblyException) { // Channel attach failed - _status.setStatus(RoomLifecycle.Failed, ex.errorInfo) + _statusLifecycle.setStatus(RoomStatus.Failed, ex.errorInfo) throw ex } } @@ -294,9 +298,9 @@ class RoomLifecycleManager @SuppressWarnings("ThrowsCount") internal suspend fun attach() { val deferredAttach = atomicCoroutineScope.async(LifecycleOperationPrecedence.AttachOrDetach.priority) { // CHA-RL1d - when (_status.current) { - RoomLifecycle.Attached -> return@async // CHA-RL1a - RoomLifecycle.Releasing -> // CHA-RL1b + when (_statusLifecycle.status) { + RoomStatus.Attached -> return@async // CHA-RL1a + RoomStatus.Releasing -> // CHA-RL1b throw AblyException.fromErrorInfo( ErrorInfo( "unable to attach room; room is releasing", @@ -304,7 +308,7 @@ class RoomLifecycleManager ErrorCodes.RoomIsReleasing.errorCode, ), ) - RoomLifecycle.Released -> // CHA-RL1c + RoomStatus.Released -> // CHA-RL1c throw AblyException.fromErrorInfo( ErrorInfo( "unable to attach room; room is released", @@ -318,12 +322,12 @@ class RoomLifecycleManager // At this point, we force the room status to be attaching clearAllTransientDetachTimeouts() _operationInProgress = true - _status.setStatus(RoomLifecycle.Attaching) // CHA-RL1e + _statusLifecycle.setStatus(RoomStatus.Attaching) // CHA-RL1e val attachResult = doAttach() // CHA-RL1h4 - If we're in a failed state, then we should wind down all the channels, eventually - if (attachResult.status === RoomLifecycle.Failed) { + if (attachResult.status === RoomStatus.Failed) { // CHA-RL1h5 - detach all remaining channels atomicCoroutineScope.async(LifecycleOperationPrecedence.Internal.priority) { runDownChannelsOnFailedAttach() @@ -332,7 +336,7 @@ class RoomLifecycleManager } // CHA-RL1h1, CHA-RL1h2 - If we're in suspended, then this attach should fail, but we'll retry after a short delay async - if (attachResult.status === RoomLifecycle.Suspended) { + if (attachResult.status === RoomStatus.Suspended) { if (attachResult.failedFeature == null) { AblyException.fromErrorInfo( ErrorInfo( @@ -382,10 +386,10 @@ class RoomLifecycleManager // If it's in suspended, we wind down the other channels and wait for the reattach // If it's failed, we can fail the entire room when (feature.channel.state) { - ChannelState.suspended -> attachResult.statusField = RoomLifecycle.Suspended - ChannelState.failed -> attachResult.statusField = RoomLifecycle.Failed + ChannelState.suspended -> attachResult.statusField = RoomStatus.Suspended + ChannelState.failed -> attachResult.statusField = RoomStatus.Failed else -> { - attachResult.statusField = RoomLifecycle.Failed + attachResult.statusField = RoomStatus.Failed attachResult.errorField = ErrorInfo( "unexpected channel state in doAttach ${feature.channel.state}${feature.channel.errorMessage}", HttpStatusCodes.InternalServerError, @@ -396,13 +400,13 @@ class RoomLifecycleManager // Regardless of whether we're suspended or failed, run-down the other channels // The wind-down procedure will take Precedence over any user-driven actions - _status.setStatus(attachResult) + _statusLifecycle.setStatus(attachResult) return attachResult } } // CHA-RL1g, We successfully attached all the channels - set our status to attached, start listening changes in channel status - this._status.setStatus(attachResult) + this._statusLifecycle.setStatus(attachResult) this._operationInProgress = false // Iterate the pending discontinuity events and trigger them @@ -444,14 +448,14 @@ class RoomLifecycleManager async { // If its the contributor we want to wait for a conclusion on, then we should not detach it // Unless we're in a failed state, in which case we should detach it - if (contributor === except && _status.current !== RoomLifecycle.Failed) { + if (contributor === except && _statusLifecycle.status !== RoomStatus.Failed) { return@async } // If the room's already in the failed state, or it's releasing, we should not detach a failed channel if (( - _status.current === RoomLifecycle.Failed || - _status.current === RoomLifecycle.Releasing || - _status.current === RoomLifecycle.Released + _statusLifecycle.status === RoomStatus.Failed || + _statusLifecycle.status === RoomStatus.Releasing || + _statusLifecycle.status === RoomStatus.Released ) && contributor.channel.state === ChannelState.failed ) { @@ -464,16 +468,16 @@ class RoomLifecycleManager // If the contributor is in a failed state and we're not ignoring failed states, we should fail the room if ( contributor.channel.state === ChannelState.failed && - _status.current !== RoomLifecycle.Failed && - _status.current !== RoomLifecycle.Releasing && - _status.current !== RoomLifecycle.Released + _statusLifecycle.status !== RoomStatus.Failed && + _statusLifecycle.status !== RoomStatus.Releasing && + _statusLifecycle.status !== RoomStatus.Released ) { val contributorError = ErrorInfo( "failed to detach feature", HttpStatusCodes.InternalServerError, contributor.contributor.detachmentErrorCode.errorCode, ) - _status.setStatus(RoomLifecycle.Failed, contributorError) + _statusLifecycle.setStatus(RoomStatus.Failed, contributorError) throw AblyException.fromErrorInfo(throwable, contributorError) } diff --git a/chat-android/src/main/java/com/ably/chat/RoomStatus.kt b/chat-android/src/main/java/com/ably/chat/RoomStatus.kt index 1f40e5b..bbd93ac 100644 --- a/chat-android/src/main/java/com/ably/chat/RoomStatus.kt +++ b/chat-android/src/main/java/com/ably/chat/RoomStatus.kt @@ -5,82 +5,11 @@ import io.ably.lib.util.EventEmitter import io.ably.lib.util.Log import io.ably.lib.util.Log.LogHandler -/** - * Represents the status of a Room. - */ -interface RoomStatus { - /** - * (CHA-RS2a) - * The current status of the room. - */ - val current: RoomLifecycle - - /** - * (CHA-RS2b) - * The current error, if any, that caused the room to enter the current status. - */ - val error: ErrorInfo? - - /** - * Registers a listener that will be called whenever the room status changes. - * @param listener The function to call when the status changes. - * @returns An object that can be used to unregister the listener. - */ - fun onChange(listener: Listener): Subscription - - /** - * An interface for listening to changes for the room status - */ - fun interface Listener { - /** - * A function that can be called when the room status changes. - * @param change The change in status. - */ - fun roomStatusChanged(change: RoomStatusChange) - } - - /** - * Removes all listeners that were added by the `onChange` method. - */ - fun offAll() -} - -/** - * A new room status that can be set. - */ -interface NewRoomStatus { - /** - * The new status of the room. - */ - val status: RoomLifecycle - - /** - * An error that provides a reason why the room has - * entered the new status, if applicable. - */ - val error: ErrorInfo? -} - -interface InternalRoomStatus : RoomStatus { - /** - * Registers a listener that will be called once when the room status changes. - * @param listener The function to call when the status changes. - */ - fun onChangeOnce(listener: RoomStatus.Listener) - - /** - * Sets the status of the room. - * - * @param params The new status of the room. - */ - fun setStatus(params: NewRoomStatus) -} - /** * (CHA-RS1) * The different states that a room can be in throughout its lifecycle. */ -enum class RoomLifecycle(val stateName: String) { +enum class RoomStatus(val stateName: String) { /** * The library is currently initializing the room. */ @@ -149,12 +78,12 @@ data class RoomStatusChange( /** * The new status of the room. */ - val current: RoomLifecycle, + val current: RoomStatus, /** * The previous status of the room. */ - val previous: RoomLifecycle, + val previous: RoomStatus, /** * An error that provides a reason why the room has @@ -163,9 +92,85 @@ data class RoomStatusChange( val error: ErrorInfo? = null, ) -class RoomStatusEventEmitter : EventEmitter() { +/** + * Represents the status of a Room. + */ +interface RoomLifecycle { + /** + * (CHA-RS2a) + * The current status of the room. + */ + val status: RoomStatus + + /** + * (CHA-RS2b) + * The current error, if any, that caused the room to enter the current status. + */ + val error: ErrorInfo? + + /** + * Registers a listener that will be called whenever the room status changes. + * @param listener The function to call when the status changes. + * @returns An object that can be used to unregister the listener. + */ + fun onChange(listener: Listener): Subscription - override fun apply(listener: RoomStatus.Listener?, event: RoomLifecycle?, vararg args: Any?) { + /** + * An interface for listening to changes for the room status + */ + fun interface Listener { + /** + * A function that can be called when the room status changes. + * @param change The change in status. + */ + fun roomStatusChanged(change: RoomStatusChange) + } + + /** + * Removes all listeners that were added by the `onChange` method. + */ + fun offAll() +} + +/** + * A new room status that can be set. + */ +interface NewRoomStatus { + /** + * The new status of the room. + */ + val status: RoomStatus + + /** + * An error that provides a reason why the room has + * entered the new status, if applicable. + */ + val error: ErrorInfo? +} + +/** + * An internal interface for the status of a room, which can be used to separate critical + * internal functionality from user listeners. + * @internal + */ +interface InternalRoomLifecycle : RoomLifecycle { + /** + * Registers a listener that will be called once when the room status changes. + * @param listener The function to call when the status changes. + */ + fun onChangeOnce(listener: RoomLifecycle.Listener) + + /** + * Sets the status of the room. + * + * @param params The new status of the room. + */ + fun setStatus(params: NewRoomStatus) +} + +class RoomStatusEventEmitter : EventEmitter() { + + override fun apply(listener: RoomLifecycle.Listener?, event: RoomStatus?, vararg args: Any?) { try { listener?.roomStatusChanged(args[0] as RoomStatusChange) } catch (t: Throwable) { @@ -174,13 +179,12 @@ class RoomStatusEventEmitter : EventEmitter( } } -class DefaultStatus(private val logger: LogHandler? = null) : InternalRoomStatus { +class DefaultRoomLifecycle(private val logger: LogHandler? = null) : InternalRoomLifecycle { private val _logger = logger - - private var _state = RoomLifecycle.Initializing - override val current: RoomLifecycle - get() = _state + private var _status = RoomStatus.Initializing + override val status: RoomStatus + get() = _status private var _error: ErrorInfo? = null override val error: ErrorInfo? @@ -189,7 +193,7 @@ class DefaultStatus(private val logger: LogHandler? = null) : InternalRoomStatus private val externalEmitter = RoomStatusEventEmitter() private val internalEmitter = RoomStatusEventEmitter() - override fun onChange(listener: RoomStatus.Listener): Subscription { + override fun onChange(listener: RoomLifecycle.Listener): Subscription { externalEmitter.on(listener) return Subscription { externalEmitter.off(listener) @@ -200,21 +204,21 @@ class DefaultStatus(private val logger: LogHandler? = null) : InternalRoomStatus externalEmitter.off() } - override fun onChangeOnce(listener: RoomStatus.Listener) { + override fun onChangeOnce(listener: RoomLifecycle.Listener) { internalEmitter.once(listener) } override fun setStatus(params: NewRoomStatus) { - val change = RoomStatusChange(params.status, current, params.error) - _state = change.current + val change = RoomStatusChange(params.status, _status, params.error) + _status = change.current _error = change.error internalEmitter.emit(change.current, change) externalEmitter.emit(change.current, change) } - fun setStatus(status: RoomLifecycle, error: ErrorInfo? = null) { + fun setStatus(status: RoomStatus, error: ErrorInfo? = null) { val newStatus = object : NewRoomStatus { - override val status: RoomLifecycle = status + override val status: RoomStatus = status override val error: ErrorInfo? = error } this.setStatus(newStatus) 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 6f6cc73..0b56337 100644 --- a/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt +++ b/chat-android/src/test/java/com/ably/chat/RoomLifecycleManagerTest.kt @@ -35,20 +35,20 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1a) Attach success when channel in already in attached state`() = runTest { - val status = spyk().apply { - setStatus(RoomLifecycle.Attached) + val statusLifecycle = spyk().apply { + setStatus(RoomStatus.Attached) } - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, createRoomFeatureMocks())) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, createRoomFeatureMocks())) val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertTrue(result.isSuccess) } @Test fun `(CHA-RL1b) Attach throws exception when channel in releasing state`() = runTest { - val status = spyk().apply { - setStatus(RoomLifecycle.Releasing) + val statusLifecycle = spyk().apply { + setStatus(RoomStatus.Releasing) } - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, createRoomFeatureMocks())) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, createRoomFeatureMocks())) val exception = Assert.assertThrows(AblyException::class.java) { runBlocking { roomLifecycle.attach() @@ -61,10 +61,10 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1c) Attach throws exception when channel in released state`() = runTest { - val status = spyk().apply { - setStatus(RoomLifecycle.Released) + val statusLifecycle = spyk().apply { + setStatus(RoomStatus.Released) } - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, listOf())) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, listOf())) val exception = Assert.assertThrows(AblyException::class.java) { runBlocking { roomLifecycle.attach() @@ -77,19 +77,19 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1d) Attach op should wait for existing operation as per (CHA-RL7)`() = runTest { - val status = spyk() - Assert.assertEquals(RoomLifecycle.Initializing, status.current) + val statusLifecycle = spyk() + Assert.assertEquals(RoomStatus.Initializing, statusLifecycle.status) - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, createRoomFeatureMocks())) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, createRoomFeatureMocks())) val roomReleased = Channel() coEvery { roomLifecycle.release() } coAnswers { roomLifecycle.atomicCoroutineScope().async { - status.setStatus(RoomLifecycle.Releasing) + statusLifecycle.setStatus(RoomStatus.Releasing) roomReleased.receive() - status.setStatus(RoomLifecycle.Released) + statusLifecycle.setStatus(RoomStatus.Released) } } @@ -97,16 +97,16 @@ class RoomLifecycleManagerTest { launch { roomLifecycle.release() } assertWaiter { !roomLifecycle.atomicCoroutineScope().finishedProcessing } Assert.assertEquals(0, roomLifecycle.atomicCoroutineScope().pendingJobCount) // no queued jobs, one job running - assertWaiter { status.current == RoomLifecycle.Releasing } + assertWaiter { statusLifecycle.status == RoomStatus.Releasing } // Attach op started from separate coroutine val roomAttachOpDeferred = async(SupervisorJob()) { roomLifecycle.attach() } assertWaiter { roomLifecycle.atomicCoroutineScope().pendingJobCount == 1 } // attach op queued - Assert.assertEquals(RoomLifecycle.Releasing, status.current) + Assert.assertEquals(RoomStatus.Releasing, statusLifecycle.status) // Finish release op, so ATTACH op can start roomReleased.send(true) - assertWaiter { status.current == RoomLifecycle.Released } + assertWaiter { statusLifecycle.status == RoomStatus.Released } val result = kotlin.runCatching { roomAttachOpDeferred.await() } Assert.assertTrue(roomLifecycle.atomicCoroutineScope().finishedProcessing) @@ -123,21 +123,21 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1e) Attach op should transition room into ATTACHING state`() = runTest { - val status = spyk() + val statusLifecycle = spyk() val roomStatusChanges = mutableListOf() - status.onChange { + statusLifecycle.onChange { roomStatusChanges.add(it) } - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, emptyList())) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, emptyList())) roomLifecycle.attach() - Assert.assertEquals(RoomLifecycle.Attaching, roomStatusChanges[0].current) - Assert.assertEquals(RoomLifecycle.Attached, roomStatusChanges[1].current) + Assert.assertEquals(RoomStatus.Attaching, roomStatusChanges[0].current) + Assert.assertEquals(RoomStatus.Attached, roomStatusChanges[1].current) assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } } @Test fun `(CHA-RL1f) Attach op should attach each contributor channel sequentially`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) val capturedChannels = mutableListOf() @@ -148,7 +148,7 @@ class RoomLifecycleManagerTest { val contributors = createRoomFeatureMocks() Assert.assertEquals(5, contributors.size) - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors)) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors)) roomLifecycle.attach() val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertTrue(result.isSuccess) @@ -166,7 +166,7 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1g) When all contributor channels ATTACH, op is complete and room should be considered ATTACHED`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } returns Unit @@ -180,7 +180,7 @@ class RoomLifecycleManagerTest { } Assert.assertEquals(5, contributors.size) - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) { + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) { val pendingDiscontinuityEvents = mutableMapOf().apply { for (contributor in contributors) { put(contributor, ErrorInfo("${contributor.channel.name} error", 500)) @@ -195,7 +195,7 @@ class RoomLifecycleManagerTest { // CHA-RL1g1 Assert.assertTrue(result.isSuccess) - Assert.assertEquals(RoomLifecycle.Attached, status.current) + Assert.assertEquals(RoomStatus.Attached, statusLifecycle.status) assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } // CHA-RL1g2 @@ -216,7 +216,7 @@ class RoomLifecycleManagerTest { @Suppress("MaximumLineLength") @Test fun `(CHA-RL1h1, CHA-RL1h2) If a one of the contributors fails to attach (enters suspended state), attach throws related error and room enters suspended state`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } coAnswers { @@ -229,12 +229,12 @@ class RoomLifecycleManagerTest { } val contributors = createRoomFeatureMocks("1234") - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertTrue(result.isFailure) - Assert.assertEquals(RoomLifecycle.Suspended, status.current) + Assert.assertEquals(RoomStatus.Suspended, statusLifecycle.status) val exception = result.exceptionOrNull() as AblyException @@ -246,7 +246,7 @@ class RoomLifecycleManagerTest { @Suppress("MaximumLineLength") @Test fun `(CHA-RL1h1, CHA-RL1h4) If a one of the contributors fails to attach (enters failed state), attach throws related error and room enters failed state`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } coAnswers { @@ -260,12 +260,12 @@ class RoomLifecycleManagerTest { } val contributors = createRoomFeatureMocks("1234") - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertTrue(result.isFailure) - Assert.assertEquals(RoomLifecycle.Failed, status.current) + Assert.assertEquals(RoomStatus.Failed, statusLifecycle.status) val exception = result.exceptionOrNull() as AblyException @@ -279,7 +279,7 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1h3) When room enters suspended state (CHA-RL1h2), it should enter recovery loop as per (CHA-RL5)`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } coAnswers { @@ -292,7 +292,7 @@ class RoomLifecycleManagerTest { } val contributors = createRoomFeatureMocks("1234") - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) val resolvedContributor = slot() @@ -305,7 +305,7 @@ class RoomLifecycleManagerTest { assertWaiter { !roomLifecycle.atomicCoroutineScope().finishedProcessing } // internal attach retry in progress Assert.assertTrue(result.isFailure) - Assert.assertEquals(RoomLifecycle.Suspended, status.current) + Assert.assertEquals(RoomStatus.Suspended, statusLifecycle.status) assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } // Wait for doRetry to finish @@ -317,7 +317,7 @@ class RoomLifecycleManagerTest { @Test fun `(CHA-RL1h5) When room enters failed state (CHA-RL1h4), room detach all channels not in failed state`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } coAnswers { @@ -336,13 +336,13 @@ class RoomLifecycleManagerTest { } val contributors = createRoomFeatureMocks("1234") - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertFalse(roomLifecycle.atomicCoroutineScope().finishedProcessing) // Internal channels detach in progress Assert.assertTrue(result.isFailure) - Assert.assertEquals(RoomLifecycle.Failed, status.current) + Assert.assertEquals(RoomStatus.Failed, statusLifecycle.status) assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } // Wait for channels detach @@ -363,7 +363,7 @@ class RoomLifecycleManagerTest { @Suppress("MaximumLineLength") @Test fun `(CHA-RL1h6) When room enters failed state, when CHA-RL1h5 fails to detach, op will be repeated till all channels are detached`() = runTest { - val status = spyk() + val statusLifecycle = spyk() mockkStatic(io.ably.lib.realtime.Channel::attachCoroutine) coEvery { any().attachCoroutine() } coAnswers { @@ -386,13 +386,13 @@ class RoomLifecycleManagerTest { } val contributors = createRoomFeatureMocks("1234") - val roomLifecycle = spyk(RoomLifecycleManager(roomScope, status, contributors), recordPrivateCalls = true) + val roomLifecycle = spyk(RoomLifecycleManager(roomScope, statusLifecycle, contributors), recordPrivateCalls = true) val result = kotlin.runCatching { roomLifecycle.attach() } Assert.assertFalse(roomLifecycle.atomicCoroutineScope().finishedProcessing) // Internal channels detach in progress Assert.assertTrue(result.isFailure) - Assert.assertEquals(RoomLifecycle.Failed, status.current) + Assert.assertEquals(RoomStatus.Failed, statusLifecycle.status) assertWaiter { roomLifecycle.atomicCoroutineScope().finishedProcessing } // Wait for channels detach