@@ -1583,6 +1583,145 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async
15831583 ) ;
15841584 } ) ;
15851585
1586+ /** @spec RTL3g **/
1587+ it ( 'detaching_channel_when_connection_enters_failed' , async function ( ) {
1588+ // Given: A channel in the DETACHING state
1589+ const helper = this . test . helper ;
1590+ const realtime = helper . AblyRealtime ( { transports : [ helper . bestTransport ] } ) ;
1591+ const channel = realtime . channels . get ( 'detached_channel_when_connection_enters_failed' ) ;
1592+
1593+ await channel . attach ( ) ;
1594+
1595+ helper . recordPrivateApi ( 'call.connectionManager.activeProtocol.getTransport' ) ;
1596+ const transport = realtime . connection . connectionManager . activeProtocol . getTransport ( ) ;
1597+ const onProtocolMessageOriginal = transport . onProtocolMessage ;
1598+
1599+ helper . recordPrivateApi ( 'replace.transport.onProtocolMessage' ) ;
1600+ transport . onProtocolMessage = function ( msg ) {
1601+ if ( msg . action === 13 ) {
1602+ // Drop the incoming DETACHED so that the channel stays in DETACHING
1603+ return ;
1604+ }
1605+
1606+ helper . recordPrivateApi ( 'call.transport.onProtocolMessage' ) ;
1607+ onProtocolMessageOriginal . call ( this , msg ) ;
1608+ } ;
1609+
1610+ const channelDetachPromise = channel . detach ( ) ;
1611+
1612+ expect ( channel . state ) . to . equal ( 'detaching' ) ;
1613+
1614+ // When: The connection enters FAILED
1615+ const channelFailedPromise = channel . whenState ( 'failed' ) ;
1616+
1617+ // Inject a connection-level ERROR to make connection enter FAILED per RTN15i
1618+ helper . recordPrivateApi ( 'call.makeProtocolMessageFromDeserialized' ) ;
1619+ helper . recordPrivateApi ( 'call.transport.onProtocolMessage' ) ;
1620+ transport . onProtocolMessage (
1621+ createPM ( { action : 9 , error : { code : 40000 , statusCode : 400 , message : 'Some error' } } ) ,
1622+ ) ;
1623+
1624+ // Then: The channel transitions to FAILED and the call to `detach()` fails
1625+ await channelFailedPromise ;
1626+
1627+ try {
1628+ await channelDetachPromise ;
1629+ expect . fail ( 'Expected channel.detach() to throw' ) ;
1630+ } catch {
1631+ expect ( channel . errorReason . code ) . to . equal ( 40000 ) ;
1632+ expect ( channel . errorReason . statusCode ) . to . equal ( 400 ) ;
1633+ expect ( channel . errorReason . message ) . to . equal ( 'Some error' ) ;
1634+ }
1635+
1636+ // Teardown
1637+ await helper . closeAndFinishAsync ( realtime ) ;
1638+ } ) ;
1639+
1640+ /** @specpartial RTL3h - Tests the CLOSED case **/
1641+ it ( 'detaching_channel_when_connection_enters_closed' , async function ( ) {
1642+ // Given: A channel in the DETACHING state
1643+ const helper = this . test . helper ;
1644+ const realtime = helper . AblyRealtime ( { transports : [ helper . bestTransport ] } ) ;
1645+ const channel = realtime . channels . get ( 'detached_channel_when_connection_enters_failed' ) ;
1646+
1647+ await channel . attach ( ) ;
1648+
1649+ helper . recordPrivateApi ( 'call.connectionManager.activeProtocol.getTransport' ) ;
1650+ const transport = realtime . connection . connectionManager . activeProtocol . getTransport ( ) ;
1651+ const onProtocolMessageOriginal = transport . onProtocolMessage ;
1652+
1653+ helper . recordPrivateApi ( 'replace.transport.onProtocolMessage' ) ;
1654+ transport . onProtocolMessage = function ( msg ) {
1655+ if ( msg . action === 13 ) {
1656+ // Drop the incoming DETACHED so that the channel stays in DETACHING
1657+ return ;
1658+ }
1659+
1660+ helper . recordPrivateApi ( 'call.transport.onProtocolMessage' ) ;
1661+ onProtocolMessageOriginal . call ( this , msg ) ;
1662+ } ;
1663+
1664+ const channelDetachPromise = channel . detach ( ) ;
1665+
1666+ expect ( channel . state ) . to . equal ( 'detaching' ) ;
1667+
1668+ // When: The connection enters CLOSED
1669+ const channelDetachedPromise = channel . whenState ( 'detached' ) ;
1670+
1671+ realtime . close ( ) ;
1672+
1673+ // Then: The channel transitions to DETACHED and the call to `detach()` succeeds
1674+ await channelDetachedPromise ;
1675+ await channelDetachPromise ;
1676+
1677+ // Teardown
1678+ await helper . closeAndFinishAsync ( realtime ) ;
1679+ } ) ;
1680+
1681+ /** @specpartial RTL3h - Tests the SUSPENDED case **/
1682+ it ( 'detaching_channel_when_connection_enters_suspended' , async function ( ) {
1683+ // Given: A channel in the DETACHING state
1684+ const helper = this . test . helper ;
1685+ const realtime = helper . AblyRealtime ( { transports : [ helper . bestTransport ] } ) ;
1686+ const channel = realtime . channels . get ( 'detached_channel_when_connection_enters_failed' ) ;
1687+
1688+ await channel . attach ( ) ;
1689+
1690+ helper . recordPrivateApi ( 'call.connectionManager.activeProtocol.getTransport' ) ;
1691+ const transport = realtime . connection . connectionManager . activeProtocol . getTransport ( ) ;
1692+ const onProtocolMessageOriginal = transport . onProtocolMessage ;
1693+
1694+ helper . recordPrivateApi ( 'replace.transport.onProtocolMessage' ) ;
1695+ transport . onProtocolMessage = function ( msg ) {
1696+ if ( msg . action === 13 ) {
1697+ // Drop the incoming DETACHED so that the channel stays in DETACHING
1698+ return ;
1699+ }
1700+
1701+ helper . recordPrivateApi ( 'call.transport.onProtocolMessage' ) ;
1702+ onProtocolMessageOriginal . call ( this , msg ) ;
1703+ } ;
1704+
1705+ const channelDetachPromise = channel . detach ( ) ;
1706+
1707+ expect ( channel . state ) . to . equal ( 'detaching' ) ;
1708+
1709+ // When: The connection enters SUSPENDED
1710+ const channelDetachedPromise = channel . whenState ( 'detached' ) ;
1711+
1712+ await new Promise ( ( resolve ) => {
1713+ helper . becomeSuspended ( realtime , resolve ) ;
1714+ } ) ;
1715+
1716+ // Then: The channel transitions to DETACHED and the call to `detach()` succeeds
1717+ await channelDetachedPromise ;
1718+ expect ( channel . errorReason ) . to . be . null ;
1719+ await channelDetachPromise ;
1720+
1721+ // Teardown
1722+ await helper . closeAndFinishAsync ( realtime ) ;
1723+ } ) ;
1724+
15861725 /** @spec RTL5i */
15871726 it ( 'attached_while_detaching' , function ( done ) {
15881727 var helper = this . test . helper ,
0 commit comments