diff --git a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java index ce15010cc..2ef6a3718 100644 --- a/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java +++ b/lib/src/main/java/io/ably/lib/realtime/ChannelBase.java @@ -221,7 +221,7 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li if(!forceReattach) { /* check preconditions */ switch(state) { - case attaching: + case attaching: //RTL4h if(listener != null) { on(new ChannelStateCompletionListener(listener, ChannelState.attached, ChannelState.failed)); } @@ -229,9 +229,11 @@ private void attachImpl(final boolean forceReattach, final CompletionListener li case detaching: //RTL4h pendingAttachRequest = new AttachRequest(forceReattach,listener); return; - case attached: + case attached: //RTL4a callCompletionListenerSuccess(listener); return; + case failed: //RTL4g + this.reason = null; default: } } @@ -312,12 +314,12 @@ private void detachImpl(CompletionListener listener) throws AblyException { Log.v(TAG, "detach(); channel = " + name); /* check preconditions */ switch(state) { - case initialized: + case initialized: // RTL5a case detached: { callCompletionListenerSuccess(listener); return; } - case detaching: + case detaching: //RTL5i if (listener != null) { on(new ChannelStateCompletionListener(listener, ChannelState.detached, ChannelState.failed)); } @@ -325,6 +327,15 @@ private void detachImpl(CompletionListener listener) throws AblyException { case attaching: //RTL5i pendingDetachRequest = new DetachRequest(listener); return; + case failed: //RTL5b + ErrorInfo error = this.reason != null ? + this.reason : new ErrorInfo("Channel state is failed", 90000); + callCompletionListenerError(listener, error); + return; + case suspended: //RTL5j + setState(ChannelState.detached, null); + callCompletionListenerSuccess(listener); + return; default: } ConnectionManager connectionManager = ably.connection.connectionManager; diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java index 3279e8c7b..d60f7179b 100644 --- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java +++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java @@ -930,7 +930,7 @@ public void attach_success_callback() { Helpers.CompletionWaiter waiter = new Helpers.CompletionWaiter(); channel.attach(waiter); new ChannelWaiter(channel).waitFor(ChannelState.attached); - assertEquals("Verify failed state reached", channel.state, ChannelState.attached); + assertEquals("Verify attached state reached", channel.state, ChannelState.attached); /* Verify onSuccess callback gets called */ waiter.waitFor(); @@ -944,6 +944,59 @@ public void attach_success_callback() { } } + @Test + public void attach_success_callback_for_channel_in_failed_state() { + AblyRealtime ably = null; + try { + ClientOptions opts = createOptions(testVars.keys[0].keyStr); + ably = new AblyRealtime(opts); + + /* wait until connected */ + (new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected); + + /* create a channel and attach */ + final Channel channel = ably.channels.get("attach_success"); + ChannelWaiter channelWaiter = new ChannelWaiter(channel); + channel.attach(); + channelWaiter.waitFor(ChannelState.attached); + + // Simulate connection failure + ably.connection.connectionManager.requestState( + new ConnectionManager.StateIndication( + ConnectionState.failed, + new ErrorInfo("Simulated connection failure", 40000) + ) + ); + + // Wait for the channel to reach the failed state + channelWaiter.waitFor(ChannelState.failed); + + assertNotNull(channel.reason); + assertEquals("Simulated connection failure", channel.reason.message); + + ably.connect(); + + Helpers.CompletionWaiter attachListener = new Helpers.CompletionWaiter(); + channel.attach(attachListener); + + channelWaiter.waitFor(ChannelState.attaching); + assertNull(channel.reason); + channelWaiter.waitFor(ChannelState.attached); + + assertEquals("Verify attached state reached", ChannelState.attached, channel.state); + + /* Verify onSuccess callback gets called */ + attachListener.waitFor(); + assertTrue(attachListener.success); + } catch (AblyException e) { + e.printStackTrace(); + fail("init0: Unexpected exception instantiating library"); + } finally { + if(ably != null) + ably.close(); + } + } + /** * When client failed to attach to a channel, verify * attach {@code CompletionListener#onError(ErrorInfo)} @@ -1015,6 +1068,84 @@ public void detach_success_callback_initialized() { } } + @Test + public void detach_success_callback_on_suspended_state() { + AblyRealtime ably = null; + try { + ClientOptions opts = createOptions(testVars.keys[0].keyStr); + ably = new AblyRealtime(opts); + + /* wait until connected */ + (new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected); + + /* create a channel and attach */ + final Channel channel = ably.channels.get("detach_success"); + ChannelWaiter channelWaiter = new ChannelWaiter(channel); + channel.attach(); + channelWaiter.waitFor(ChannelState.attached); + + ably.connection.connectionManager.requestState(ConnectionState.suspended); + + channelWaiter.waitFor(ChannelState.suspended); + assertEquals("Verify suspended state reached", ChannelState.suspended, channel.state); + + /* detach */ + Helpers.CompletionWaiter detachWaiter = new Helpers.CompletionWaiter(); + channel.detach(detachWaiter); + + /* Verify onSuccess callback gets called */ + detachWaiter.waitFor(); + assertTrue(detachWaiter.success); + } catch (AblyException e) { + e.printStackTrace(); + fail("init0: Unexpected exception instantiating library"); + } finally { + if(ably != null) + ably.close(); + } + } + + @Test + public void detach_failure_callback_on_failed_state() { + AblyRealtime ably = null; + try { + ClientOptions opts = createOptions(testVars.keys[0].keyStr); + ably = new AblyRealtime(opts); + + /* wait until connected */ + (new ConnectionWaiter(ably.connection)).waitFor(ConnectionState.connected); + + /* create a channel and attach */ + final Channel channel = ably.channels.get("detach_failure"); + ChannelWaiter channelWaiter = new ChannelWaiter(channel); + channel.attach(); + channelWaiter.waitFor(ChannelState.attached); + + // Simulate connection failure + ably.connection.connectionManager.requestState(ConnectionState.failed); + + channelWaiter.waitFor(ChannelState.failed); + assertEquals("Verify failed state reached", ChannelState.failed, channel.state); + + /* detach */ + Helpers.CompletionWaiter detachWaiter = new Helpers.CompletionWaiter(); + channel.detach(detachWaiter); + + /* Verify onSuccess callback gets called */ + detachWaiter.waitFor(); + assertFalse(detachWaiter.success); + assertNotNull(detachWaiter.error); + assertEquals("Channel state is failed", detachWaiter.error.message); + assertEquals(90000, detachWaiter.error.code); + } catch (AblyException e) { + e.printStackTrace(); + fail("init0: Unexpected exception instantiating library"); + } finally { + if(ably != null) + ably.close(); + } + } + /** * When client detaches from a channel successfully after attached state, * verify attach {@code CompletionListener#onSuccess()} gets called.