lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb in ably-rest-1.2.4 vs lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb in ably-rest-1.2.6

- old
+ new

@@ -340,12 +340,14 @@ end channel.attach end - channel.attach do - channel.detach + client.connection.once :connected do + channel.attach do + channel.detach + end end end end context 'with many connections and many channels on each simultaneously' do @@ -428,29 +430,57 @@ end end end context 'with connection state' do + + sent_attach_messages = [] + received_attached_messages = [] + before(:each) do + sent_attach_messages = [] + received_attached_messages = [] + client.connection.__outgoing_protocol_msgbus__.subscribe do |message| + if message.action == :attach + sent_attach_messages << message + end + end + client.connection.__incoming_protocol_msgbus__.subscribe do |message| + if message.action == :attached + received_attached_messages << message + end + end + end + + # Should send/receive attach/attached message only once + # No duplicates should be sent or received + let(:check_for_attach_messages) do + expect(sent_attach_messages.size).to eq(1) + expect(received_attached_messages.size).to eq(1) + end + it 'is initialized (#RTL4i)' do expect(connection).to be_initialized channel.attach do + check_for_attach_messages stop_reactor end end it 'is connecting (#RTL4i)' do connection.once(:connecting) do channel.attach do + check_for_attach_messages stop_reactor end end end it 'is disconnected (#RTL4i)' do connection.once(:connected) do connection.once(:disconnected) do channel.attach do + check_for_attach_messages stop_reactor end end disconnect_transport end @@ -465,11 +495,13 @@ expect(protocol_message.has_attach_resume_flag?).to eq(false) stop_reactor end - channel.attach + client.connection.once :connected do + channel.attach + end end end context "when channel was explicitly detached" do it "doesn't send ATTACH_RESUME" do @@ -486,74 +518,89 @@ channel.once(:attached) do channel.detach end - channel.attach + client.connection.once :connected do + channel.attach + end end end end end describe '#detach' do context 'when state is :attached' do it 'it detaches from a channel (#RTL5d)' do - channel.attach do + channel.once :attached do channel.detach channel.on(:detached) do expect(channel.state).to eq(:detached) stop_reactor end end + connection.once :connected do + channel.attach + end end it 'detaches from a channel and calls the provided block (#RTL5d, #RTL5e)' do - channel.attach do + channel.once :attached do expect(channel.state).to eq(:attached) channel.detach do expect(channel.state).to eq(:detached) stop_reactor end end + connection.once :connected do + channel.attach + end end it 'emits :detaching then :detached events' do channel.once(:detaching) do channel.once(:detached) do stop_reactor end end - channel.attach do - channel.detach + connection.once :connected do + channel.attach do + channel.detach + end end end it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do - channel.attach do + channel.once :attached do expect(channel.detach).to be_a(Ably::Util::SafeDeferrable) stop_reactor end + connection.once :connected do + channel.attach + end end it 'calls the Deferrable callback on success' do - channel.attach do + channel.once :attached do channel.detach.callback do expect(channel).to be_a(Ably::Realtime::Channel) expect(channel.state).to eq(:detached) stop_reactor end end + connection.once :connected do + channel.attach + end end context 'and DETACHED message is not received within realtime request timeout' do let(:request_timeout) { 2 } let(:client_options) { default_options.merge(realtime_request_timeout: request_timeout) } it 'fails the deferrable and returns to the previous state (#RTL5f, #RTL5e)' do channel.attach do - # don't process any incoming ProtocolMessages so the channel never becomes detached connection.__incoming_protocol_msgbus__.unsubscribe detached_requested_at = Time.now.to_i channel.detach do raise "The detach should not succeed if no incoming protocol messages are processed" end.errback do @@ -564,10 +611,11 @@ end end end end + context 'when state is :failed' do let(:client_options) { default_options.merge(log_level: :fatal) } it 'fails the deferrable (#RTL5b)' do channel.attach do @@ -607,19 +655,21 @@ end end channel.detach end - channel.attach do - channel.detach + connection.once :connected do + channel.attach do + channel.detach + end end end end context 'when state is :suspended' do it 'moves the channel state immediately to DETACHED state (#RTL5j)' do - channel.attach do + channel.once :attached do channel.once(:suspended) do channel.on do |channel_state_change| expect(channel_state_change.current).to eq(:detached) expect(channel.state).to eq(:detached) EventMachine.add_timer(1) do @@ -630,10 +680,13 @@ channel.detach end end channel.transition_state_machine :suspended end + connection.once :connected do + channel.attach + end end end context 'when state is :initialized' do it 'does nothing as there is no channel to detach (#RTL5a)' do @@ -653,21 +706,24 @@ end end context 'when state is :detached' do it 'does nothing as the channel is detached (#RTL5a)' do - channel.attach do + channel.once :attached do channel.detach do expect(channel).to be_detached channel.on do raise "Channel state should not change when calling detached if already detached" end channel.detach do EventMachine.add_timer(1) { stop_reactor } end end end + connection.once :connected do + channel.attach + end end end context 'when connection state is' do context 'closing' do @@ -733,12 +789,14 @@ end context 'initialized' do it 'does the detach operation once the connection state is connected (#RTL5h)' do expect(connection).to be_initialized + channel.on :attaching do + channel.detach + end channel.attach - channel.detach connection.once(:connected) do channel.once(:attached) do channel.once(:detached) do stop_reactor end @@ -748,12 +806,14 @@ end context 'connecting' do it 'does the detach operation once the connection state is connected (#RTL5h)' do connection.once(:connecting) do + channel.on :attaching do + channel.detach + end channel.attach - channel.detach connection.once(:connected) do channel.once(:attached) do channel.once(:detached) do stop_reactor end @@ -875,88 +935,96 @@ end describe '#(RTL17)' do context 'when channel is initialized' do it 'sends messages only on attach' do - expect(channel).to be_initialized - channel.publish('event', payload) + connection.once :connected do + expect(channel).to be_initialized + channel.publish('event', payload) - channel.subscribe do |message| - stop_reactor if message.data == payload && channel.attached? - end + channel.subscribe do |message| + stop_reactor if message.data == payload && channel.attached? + end - channel.attach + channel.attach + end end end context 'when channel is attaching' do it 'sends messages only on attach' do - channel.publish('event', payload) + connection.once :connected do + channel.publish('event', payload) - sent_message = nil - channel.subscribe do |message| - return if message.data != payload - sent_message = message + sent_message = nil + channel.subscribe do |message| + return if message.data != payload + sent_message = message - stop_reactor if channel.attached? - end + stop_reactor if channel.attached? + end - channel.on(:attaching) do - expect(channel).to be_attaching - expect(sent_message).to be_nil - end + channel.on(:attaching) do + expect(channel).to be_attaching + expect(sent_message).to be_nil + end - channel.attach + channel.attach + end end end context 'when channel is detaching' do it 'stops sending message' do - sent_message = nil - event_published = false - channel.subscribe do |message| - sent_message = message if message.data == payload - end + connection.once :connected do + sent_message = nil + event_published = false + channel.subscribe do |message| + sent_message = message if message.data == payload + end - channel.on(:detaching) do - channel.publish('event', payload) - event_published = true - end + channel.on(:detaching) do + channel.publish('event', payload) + event_published = true + end - channel.on(:detaching) do - EventMachine.next_tick do - expect(sent_message).to be_nil - stop_reactor if event_published + channel.on(:detaching) do + EventMachine.next_tick do + expect(sent_message).to be_nil + stop_reactor if event_published + end end - end - channel.attach do - channel.detach + channel.attach do + channel.detach + end end end end context 'when channel is detached' do it 'stops sending message' do - sent_message = nil - event_published = false - channel.subscribe do |message| - sent_message = message if message.data == payload - end + connection.once :connected do + sent_message = nil + event_published = false + channel.subscribe do |message| + sent_message = message if message.data == payload + end - channel.on(:detaching) do - channel.publish('event', payload) - event_published = true - end + channel.on(:detaching) do + channel.publish('event', payload) + event_published = true + end - channel.on(:detached) do - expect(sent_message).to be_nil - stop_reactor if event_published - end + channel.on(:detached) do + expect(sent_message).to be_nil + stop_reactor if event_published + end - channel.attach do - channel.detach + channel.attach do + channel.detach + end end end end context 'when channel is failed' do @@ -966,12 +1034,14 @@ expect(error).to be_a(Ably::Exceptions::ChannelInactive) stop_reactor end end - channel.attach do - channel.transition_state_machine(:failed) + connection.once :connected do + channel.attach do + channel.transition_state_machine(:failed) + end end end end end @@ -1017,11 +1087,11 @@ end end context 'when channel is Detaching (#RTL6c1)' do it 'publishes messages immediately (#RTL6c1)' do - sub_channel.attach do + sub_channel.once :attached do channel.attach do channel.once(:detaching) do outgoing_message_count = 0 client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| if protocol_message.action == :message @@ -1039,36 +1109,41 @@ 3.times { channel.publish('event', random_str) } end channel.detach end end + connection.once :connected do + sub_channel.attach + end end end context 'when channel is Detached (#RTL6c1)' do it 'publishes messages immediately (#RTL6c1)' do - sub_channel.attach do - channel.attach - channel.once(:attached) do - channel.once(:detached) do - outgoing_message_count = 0 - client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| - if protocol_message.action == :message - raise "Expected channel state to be attaching when publishing messages, not #{channel.state}" unless channel.detached? - outgoing_message_count += protocol_message.messages.count + connection.once :connected do + sub_channel.attach do + channel.attach + channel.once(:attached) do + channel.once(:detached) do + outgoing_message_count = 0 + client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| + if protocol_message.action == :message + raise "Expected channel state to be attaching when publishing messages, not #{channel.state}" unless channel.detached? + outgoing_message_count += protocol_message.messages.count + end end - end - sub_channel.subscribe do |message| - messages << message if message.name == 'event' - if messages.count == 3 - expect(outgoing_message_count).to eql(3) - stop_reactor + sub_channel.subscribe do |message| + messages << message if message.name == 'event' + if messages.count == 3 + expect(outgoing_message_count).to eql(3) + stop_reactor + end end + 3.times { channel.publish('event', random_str) } end - 3.times { channel.publish('event', random_str) } + channel.detach end - channel.detach end end end end @@ -1427,17 +1502,19 @@ let(:client) { auto_close Ably::Realtime::Client.new(client_options) } let(:channel) { client.channels.get(channel_name) } context 'with a valid client_id in the message' do it 'succeeds' do - channel.publish([name: 'event', client_id: 'validClient']).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: 'validClient']).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to eql('validClient') + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to eql('validClient') - EM.add_timer(0.5) { stop_reactor } - end end end context 'with a wildcard client_id in the message' do it 'throws an exception' do @@ -1453,17 +1530,19 @@ end end context 'with an empty client_id in the message' do it 'succeeds and publishes without a client_id' do - channel.publish([name: 'event', client_id: nil]).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: nil]).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to be_nil + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to be_nil - EM.add_timer(0.5) { stop_reactor } - end end end end context 'when authenticated with a Token string with an implicit client_id' do @@ -1473,41 +1552,48 @@ let(:channel) { client.channels.get(channel_name) } context 'before the client is CONNECTED and the client\'s identity has been obtained' do context 'with a valid client_id in the message' do it 'succeeds' do - channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to eql('valid') + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to eql('valid') - EM.add_timer(0.5) { stop_reactor } - end end end context 'with an invalid client_id in the message' do let(:client_options) { default_options.merge(key: nil, token: token, log_level: :error) } - it 'succeeds in the client library but then fails when delivered to Ably' do + it 'succeeds in the client library ( while connecting ) but then fails when delivered to Ably' do channel.publish([name: 'event', client_id: 'invalid']).tap do |deferrable| + deferrable.errback do |err| + expect(err).to be_truthy + end EM.add_timer(0.5) { stop_reactor } end channel.subscribe('event') do |message| raise 'Message should not have been published' end end end context 'with an empty client_id in the message' do it 'succeeds and publishes with an implicit client_id' do - channel.publish([name: 'event', client_id: nil]).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: nil]).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to eql('valid') + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to eql('valid') - EM.add_timer(0.5) { stop_reactor } - end end end end context 'after the client is CONNECTED and the client\'s identity is known' do @@ -1556,17 +1642,19 @@ let(:client) { auto_close Ably::Realtime::Client.new(client_options) } let(:channel) { client.channels.get(channel_name) } context 'with a valid client_id' do it 'succeeds' do - channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: 'valid']).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to eql('valid') + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to eql('valid') - EM.add_timer(0.5) { stop_reactor } - end end end context 'with a wildcard client_id in the message' do it 'throws an exception' do @@ -1582,17 +1670,19 @@ end end context 'with an empty client_id in the message' do it 'succeeds and publishes with an implicit client_id' do - channel.publish([name: 'event', client_id: nil]).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: nil]).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to eql('valid') + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to eql('valid') - EM.add_timer(0.5) { stop_reactor } - end end end end context 'when anonymous and no client_id' do @@ -1615,17 +1705,19 @@ end end context 'with an empty client_id in the message' do it 'succeeds and publishes with an implicit client_id' do - channel.publish([name: 'event', client_id: nil]).tap do |deferrable| - deferrable.errback { raise 'Should have succeeded' } + connection.once :connected do + channel.publish([name: 'event', client_id: nil]).tap do |deferrable| + deferrable.errback { raise 'Should have succeeded' } + end + channel.subscribe('event') do |message| + expect(message.client_id).to be_nil + EM.add_timer(0.5) { stop_reactor } + end end - channel.subscribe('event') do |message| - expect(message.client_id).to be_nil - EM.add_timer(0.5) { stop_reactor } - end end end end end @@ -1764,71 +1856,79 @@ context 'with a callback that raises an exception' do let(:exception) { StandardError.new("Intentional error") } it 'logs the error and continues' do - emitted_exception = false - expect(client.logger).to receive(:error) do |*args, &block| - expect(args.concat([block ? block.call : nil]).join(',')).to match(/#{exception.message}/) - end - channel.subscribe('click') do |message| - emitted_exception = true - raise exception - end - channel.publish('click', 'data') do - EventMachine.add_timer(1) do - expect(emitted_exception).to eql(true) - stop_reactor + connection.once :connected do + emitted_exception = false + expect(client.logger).to receive(:error) do |*args, &block| + expect(args.concat([block ? block.call : nil]).join(',')).to match(/#{exception.message}/) end + channel.subscribe('click') do |message| + emitted_exception = true + raise exception + end + channel.publish('click', 'data') do + EventMachine.add_timer(1) do + expect(emitted_exception).to eql(true) + stop_reactor + end + end end end end context 'many times with different event names' do it 'filters events accordingly to each callback' do - click_callback = lambda { |message| messages << message } + connection.once :connected do + click_callback = lambda { |message| messages << message } - channel.subscribe('click', &click_callback) - channel.subscribe('move', &click_callback) - channel.subscribe('press', &click_callback) + channel.subscribe('click', &click_callback) + channel.subscribe('move', &click_callback) + channel.subscribe('press', &click_callback) - channel.attach do - channel.publish('click', 'data') - channel.publish('move', 'data') - channel.publish('press', 'data') do - EventMachine.add_timer(2) do - expect(messages.count).to eql(3) - stop_reactor + channel.attach do + channel.publish('click', 'data') + channel.publish('move', 'data') + channel.publish('press', 'data') do + EventMachine.add_timer(2) do + expect(messages.count).to eql(3) + stop_reactor + end end end end end end end describe '#unsubscribe' do context 'with an event argument' do it 'unsubscribes for a single event' do - channel.subscribe('click') { raise 'Should not have been called' } - channel.unsubscribe('click') + connection.once :connected do + channel.subscribe('click') { raise 'Should not have been called' } + channel.unsubscribe('click') - channel.publish('click', 'data') do - EventMachine.add_timer(1) do - stop_reactor + channel.publish('click', 'data') do + EventMachine.add_timer(1) do + stop_reactor + end end end end end context 'with no event argument' do it 'unsubscribes for a single event' do - channel.subscribe { raise 'Should not have been called' } - channel.unsubscribe + connection.once :connected do + channel.subscribe { raise 'Should not have been called' } + channel.unsubscribe - channel.publish('click', 'data') do - EventMachine.add_timer(1) do - stop_reactor + channel.publish('click', 'data') do + EventMachine.add_timer(1) do + stop_reactor + end end end end end end @@ -1859,37 +1959,41 @@ end end context 'an :attached channel' do it 'transitions state to :failed (#RTL3a)' do - channel.attach do - channel.on(:failed) do |connection_state_change| - error = connection_state_change.reason - expect(error).to be_a(Ably::Exceptions::ConnectionFailed) - expect(error.code).to eql(50000) - stop_reactor + connection.once :connected do + channel.attach do + channel.on(:failed) do |connection_state_change| + error = connection_state_change.reason + expect(error).to be_a(Ably::Exceptions::ConnectionFailed) + expect(error.code).to eql(50000) + stop_reactor + end + fake_error connection_error end - fake_error connection_error end end it 'updates the channel error_reason (#RTL3a)' do - channel.attach do - channel.on(:failed) do |connection_state_change| - error = connection_state_change.reason - expect(error).to be_a(Ably::Exceptions::ConnectionFailed) - expect(error.code).to eql(50000) - stop_reactor + connection.once :connected do + channel.attach do + channel.on(:failed) do |connection_state_change| + error = connection_state_change.reason + expect(error).to be_a(Ably::Exceptions::ConnectionFailed) + expect(error.code).to eql(50000) + stop_reactor + end + fake_error connection_error end - fake_error connection_error end end end context 'a :detached channel' do it 'remains in the :detached state (#RTL3a)' do - channel.attach do + channel.once :attached do channel.on(:failed) { raise 'Failed state should not have been reached' } channel.detach do EventMachine.add_timer(1) do expect(channel).to be_detached @@ -1897,31 +2001,37 @@ end fake_error connection_error end end + + connection.once :connected do + channel.attach + end end end context 'a :failed channel' do let(:original_error) { RuntimeError.new } it 'remains in the :failed state and ignores the failure error (#RTL3a)' do - channel.attach do - channel.on(:failed) do - channel.on(:failed) { raise 'Failed state should not have been reached' } + connection.once :connected do + channel.attach do + channel.on(:failed) do + channel.on(:failed) { raise 'Failed state should not have been reached' } - EventMachine.add_timer(1) do - expect(channel).to be_failed - expect(channel.error_reason).to eql(original_error) - stop_reactor + EventMachine.add_timer(1) do + expect(channel).to be_failed + expect(channel.error_reason).to eql(original_error) + stop_reactor + end + + fake_error connection_error end - fake_error connection_error + channel.transition_state_machine :failed, reason: original_error end - - channel.transition_state_machine :failed, reason: original_error end end end context 'a channel ATTACH request' do @@ -1940,15 +2050,17 @@ end context ':closed' do context 'an :attached channel' do it 'transitions state to :detached (#RTL3b)' do - channel.attach do - channel.on(:detached) do - stop_reactor + connection.once :connected do + channel.attach do + channel.on(:detached) do + stop_reactor + end + client.connection.close end - client.connection.close end end end context 'an :attaching channel (#RTL3b)' do @@ -1960,17 +2072,19 @@ client.connection.__incoming_protocol_msgbus__.unsubscribe client.connection.close closed_message = Ably::Models::ProtocolMessage.new(action: 8) # CLOSED client.connection.__incoming_protocol_msgbus__.publish :protocol_message, closed_message end - channel.attach + connection.once :connected do + channel.attach + end end end context 'a :detached channel' do it 'remains in the :detached state (#RTL3b)' do - channel.attach do + channel.once :attached do channel.detach do channel.on(:detached) { raise 'Detached state should not have been reached' } EventMachine.add_timer(1) do expect(channel).to be_detached @@ -1978,32 +2092,37 @@ end client.connection.close end end + connection.once :connected do + channel.attach + end end end context 'a :failed channel' do let(:client_options) { default_options.merge(log_level: :fatal) } let(:original_error) { Ably::Models::ErrorInfo.new(message: 'Error') } it 'remains in the :failed state and retains the error_reason (#RTL3b)' do - channel.attach do - channel.once(:failed) do - channel.on(:detached) { raise 'Detached state should not have been reached' } + connection.on :connected do + channel.attach do + channel.once(:failed) do + channel.on(:detached) { raise 'Detached state should not have been reached' } - EventMachine.add_timer(1) do - expect(channel).to be_failed - expect(channel.error_reason).to eql(original_error) - stop_reactor + EventMachine.add_timer(1) do + expect(channel).to be_failed + expect(channel.error_reason).to eql(original_error) + stop_reactor + end + + client.connection.close end - client.connection.close + channel.transition_state_machine :failed, reason: original_error end - - channel.transition_state_machine :failed, reason: original_error end end end context 'a channel ATTACH request when connection CLOSED' do @@ -2044,39 +2163,45 @@ end client.connection.once_or_if(:connecting) do client.connection.transition_state_machine :suspended end end - channel.attach + channel.attach end end context 'an :attached channel' do it 'transitions state to :suspended (#RTL3c)' do - channel.attach do + channel.once :attached do channel.on(:suspended) do stop_reactor end client.connection.transition_state_machine :suspended end + connection.once :connected do + channel.attach + end end describe 'reattaching (#RTN15c3)' do it 'transitions state automatically to :attaching once the connection is re-established ' do - channel.attach do + channel.once :attached do channel.on(:suspended) do client.connection.connect channel.once(:attached) do stop_reactor end end client.connection.transition_state_machine :suspended end + connection.once :connected do + channel.attach + end end it 'sends ATTACH_RESUME flag when reattaching (RTL4j)' do - channel.attach do + channel.once :attached do channel.on(:suspended) do client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| next if protocol_message.action != :attach expect(protocol_message.has_attach_resume_flag?).to eq(true) @@ -2085,17 +2210,20 @@ client.connection.connect end client.connection.transition_state_machine :suspended end + connection.once :connected do + channel.attach + end end end end context 'a :detached channel' do it 'remains in the :detached state (#RTL3c)' do - channel.attach do + channel.once :attached do channel.detach do channel.on(:detached) { raise 'Detached state should not have been reached' } EventMachine.add_timer(1) do expect(channel).to be_detached @@ -2103,32 +2231,37 @@ end client.connection.transition_state_machine :suspended end end + connection.once :connected do + channel.attach + end end end context 'a :failed channel' do let(:original_error) { RuntimeError.new } let(:client_options) { default_options.merge(log_level: :fatal) } it 'remains in the :failed state and retains the error_reason (#RTL3c)' do - channel.attach do - channel.once(:failed) do - channel.on(:detached) { raise 'Detached state should not have been reached' } + connection.once :connected do + channel.attach do + channel.once(:failed) do + channel.on(:detached) { raise 'Detached state should not have been reached' } - EventMachine.add_timer(1) do - expect(channel).to be_failed - expect(channel.error_reason).to eql(original_error) - stop_reactor + EventMachine.add_timer(1) do + expect(channel).to be_failed + expect(channel.error_reason).to eql(original_error) + stop_reactor + end + + client.connection.transition_state_machine :suspended end - client.connection.transition_state_machine :suspended + channel.transition_state_machine :failed, reason: original_error end - - channel.transition_state_machine :failed, reason: original_error end end end context 'a channel ATTACH request when connection SUSPENDED (#RTL4b)' do @@ -2149,50 +2282,55 @@ end context ':connected' do context 'a :suspended channel' do it 'is automatically reattached (#RTL3d)' do - channel.attach do - channel.once(:suspended) do - client.connection.connect - channel.once(:attached) do - stop_reactor + connection.once :connected do + channel.attach do + channel.once(:suspended) do + client.connection.connect + channel.once(:attached) do + stop_reactor + end end + client.connection.transition_state_machine :suspended end - client.connection.transition_state_machine :suspended end end context 'when re-attach attempt fails' do let(:client_options) do default_options.merge(realtime_request_timeout: 2, log_level: :fatal) end it 'returns to a suspended state (#RTL3d)' do - channel.attach do - channel.once(:attached) do - fail "Channel should not have become attached" - end + connection.once :connected do + channel.attach do + channel.once(:attached) do + fail "Channel should not have become attached" + end - channel.once(:suspended) do - client.connection.connect - channel.once(:attaching) do - # don't process any incoming ProtocolMessages so the connection never opens - client.connection.__incoming_protocol_msgbus__.unsubscribe - channel.once(:suspended) do |state_change| - expect(state_change.reason.code).to eql(90007) - stop_reactor + channel.once(:suspended) do + client.connection.connect + channel.once(:attaching) do + # don't process any incoming ProtocolMessages so the connection never opens + client.connection.__incoming_protocol_msgbus__.unsubscribe + channel.once(:suspended) do |state_change| + expect(state_change.reason.code).to eql(90007) + stop_reactor + end end end + client.connection.transition_state_machine :suspended end - client.connection.transition_state_machine :suspended end end end end end + context ':disconnected' do context 'with an initialized channel' do it 'has no effect on the channel states (#RTL3e)' do connection.once(:connected) do expect(channel).to be_initialized @@ -2220,29 +2358,35 @@ end end context 'with an attached channel' do it 'has no effect on the channel states (#RTL3e)' do - channel.attach do + channel.once :attached do connection.once(:disconnected) do expect(channel).to be_attached stop_reactor end disconnect_transport end + + connection.once :connected do + channel.attach + end end end context 'with a detached channel' do it 'has no effect on the channel states (#RTL3e)' do - channel.attach do - channel.detach do - connection.once(:disconnected) do - expect(channel).to be_detached - stop_reactor + connection.once :connected do + channel.attach do + channel.detach do + connection.once(:disconnected) do + expect(channel).to be_detached + stop_reactor + end + disconnect_transport end - disconnect_transport end end end end @@ -2286,28 +2430,25 @@ flags.map { |flag| Ably::Models::ProtocolMessage::ATTACH_FLAGS_MAPPING[flag] }.reduce(:|) end shared_examples 'an update that sends ATTACH message' do |state, flags| it 'sends an ATTACH message on options change' do - attach_sent = nil + attach_sent_with_flags_set_via_channel_options = nil client.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| if protocol_message.action == :attach && protocol_message.flags.nonzero? - attach_sent = true expect(protocol_message.flags).to eq(flags) + attach_sent_with_flags_set_via_channel_options = true end end channel.once(state) do channel.options = channel_options end channel.on(:attached) do - client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| - next if protocol_message.action != :attached - - expect(attach_sent).to eq(true) + wait_until(lambda { attach_sent_with_flags_set_via_channel_options }) do stop_reactor end end channel.attach @@ -2316,11 +2457,11 @@ context 'when channel is attaching' do it_behaves_like 'an update that sends ATTACH message', :attaching, build_flags(%i[subscribe]) end - context 'when channel is attaching' do + context 'when channel is attached' do it_behaves_like 'an update that sends ATTACH message', :attached, build_flags(%i[resume subscribe]) end context 'when channel is initialized' do it "doesn't send ATTACH message" do @@ -2387,12 +2528,14 @@ it 'has an empty reason when there is no error' do channel.on(:detached) do |channel_state_change| expect(channel_state_change.reason).to be_nil stop_reactor end - channel.attach do - channel.detach + connection.once :connected do + channel.attach do + channel.detach + end end end context 'on failure' do let(:client_options) { default_options.merge(log_level: :none) } @@ -2424,11 +2567,11 @@ channel.attach channel.once(:attached) do |channel_state_change| connection_id = client.connection.id expect(channel_state_change.resumed).to be_falsey - recover_client = auto_close Ably::Realtime::Client.new(client_options.merge(recover: client.connection.recovery_key)) + recover_client = auto_close Ably::Realtime::Client.new(client_options.merge(recover: client.connection.create_recovery_key)) recover_client.connection.once(:connected) do expect(recover_client.connection.id).to eql(connection_id) recover_channel = recover_client.channels.get(channel_name) recover_channel.attach recover_channel.once(:attached) do |recover_channel_state_change| @@ -2439,11 +2582,11 @@ end end it 'is false when a connection fails to recover and the channel is attached' do client.connection.once(:connected) do - recovery_key = client.connection.recovery_key + recovery_key = client.connection.create_recovery_key client.connection.once(:closed) do recover_client = auto_close Ably::Realtime::Client.new(client_options.merge(recover: recovery_key, log_level: :error)) recover_client.connection.once(:connected) do recover_channel = recover_client.channels.get(channel_name) recover_channel.attach @@ -2456,25 +2599,47 @@ client.close end end - context 'when a resume fails' do + context 'when a connection resume fails' do let(:client_options) { default_options.merge(log_level: :error) } - it 'is false when a resume fails to recover and the channel is automatically re-attached' do - channel.attach do + it 'is false when channel_serial goes nil (RTP5a1) and the channel is automatically re-attached' do + channel.once :attached do connection_id = client.connection.id channel.once(:attached) do |channel_state_change| expect(client.connection.id).to_not eql(connection_id) expect(channel_state_change.resumed).to be_falsey stop_reactor end client.connection.transport.close_connection_after_writing - client.connection.configure_new '0123456789abcdef', 'wVIsgTHAB1UvXh7z-1991d8586', -1 # force the resume connection key to be invalid + channel.properties.channel_serial = nil + client.connection.configure_new '0123456789abcdef', 'wVIsgTHAB1UvXh7z-1991d8586' # force the resume connection key to be invalid end + + connection.once :connected do + channel.attach + end end + + it 'is true when channel_serial is intact and the channel is automatically re-attached' do + channel.once :attached do + connection_id = client.connection.id + channel.once(:attached) do |channel_state_change| + expect(client.connection.id).to_not eql(connection_id) + expect(channel_state_change.resumed).to be_truthy + stop_reactor + end + client.connection.transport.close_connection_after_writing + client.connection.configure_new '0123456789abcdef', 'wVIsgTHAB1UvXh7z-1991d8586' # force the resume connection key to be invalid + end + + connection.once :connected do + channel.attach + end + end end end end context 'moves to' do @@ -2599,11 +2764,13 @@ detach_message = Ably::Models::ProtocolMessage.new(action: detached_action, channel: channel_name, error: { code: 50505 }) client.connection.__incoming_protocol_msgbus__.publish :protocol_message, detach_message end - channel.attach + connection.once :connected do + channel.attach + end end end context 'and channel is suspended' do it 'reattaches immediately (#RTL13a) with ATTACH_RESUME flag(RTL4j)' do @@ -2630,30 +2797,34 @@ detach_message = Ably::Models::ProtocolMessage.new(action: detached_action, channel: channel_name, error: { code: 50505 }) client.connection.__incoming_protocol_msgbus__.publish :protocol_message, detach_message end - channel.attach + connection.once :connected do + channel.attach + end end context 'when connection is no longer connected' do it 'will not attempt to reattach (#RTL13c)' do - channel.attach do - connection.once(:closing) do - channel.once(:attaching) do |state_change| - raise 'Channel should not attempt to reattach' + connection.once :connected do + channel.attach do + connection.once(:closing) do + channel.once(:attaching) do |state_change| + raise 'Channel should not attempt to reattach' + end + + channel.transition_state_machine! :suspended end - channel.transition_state_machine! :suspended - end + connection.once(:closed) do + expect(channel).to be_suspended + stop_reactor + end - connection.once(:closed) do - expect(channel).to be_suspended - stop_reactor + connection.close end - - connection.close end end end end @@ -2670,54 +2841,57 @@ else EventMachine.next_tick { prevent_protocol_messages_proc.call } end end prevent_protocol_messages_proc.call - end - channel.once(:attaching) do - attaching_at = Time.now - # First attaching fails during server-initiated ATTACHED received - channel.once(:suspended) do |state_change| - expect(Time.now.to_i - attaching_at.to_i).to be_within(1).of(1) + channel.once(:attaching) do + attaching_at = Time.now + # First attaching fails during server-initiated ATTACHED received + channel.once(:suspended) do |state_change| + expect(Time.now.to_i - attaching_at.to_i).to be_within(1).of(1) - suspended_at = Time.now - # Automatic attach happens at channel_retry_timeout - channel.once(:attaching) do - expect(Time.now.to_i - attaching_at.to_i).to be_within(1).of(2) - channel.once(:suspended) do - channel.once(:attaching) do - channel.once(:attached) do - stop_reactor + suspended_at = Time.now + # Automatic attach happens at channel_retry_timeout + channel.once(:attaching) do + expect(Time.now.to_i - attaching_at.to_i).to be_within(1).of(2) + channel.once(:suspended) do + channel.once(:attaching) do + channel.once(:attached) do + stop_reactor + end + # Simulate ATTACHED from Ably + attached_message = Ably::Models::ProtocolMessage.new(action: 11, channel: channel_name) # ATTACHED + client.connection.__incoming_protocol_msgbus__.publish :protocol_message, attached_message end - # Simulate ATTACHED from Ably - attached_message = Ably::Models::ProtocolMessage.new(action: 11, channel: channel_name) # ATTACHED - client.connection.__incoming_protocol_msgbus__.publish :protocol_message, attached_message end end end + + detach_message = Ably::Models::ProtocolMessage.new(action: detached_action, channel: channel_name) + client.connection.__incoming_protocol_msgbus__.publish :protocol_message, detach_message end - detach_message = Ably::Models::ProtocolMessage.new(action: detached_action, channel: channel_name) - client.connection.__incoming_protocol_msgbus__.publish :protocol_message, detach_message + channel.attach end - channel.attach end end end context 'when it receives an ERROR ProtocolMessage' do let(:client_options) { default_options.merge(log_level: :fatal) } it 'should transition to the failed state and the error_reason should be set (#RTL14)' do - channel.attach do - channel.once(:failed) do |state_change| - expect(state_change.reason.code).to eql(50505) - expect(channel.error_reason.code).to eql(50505) - stop_reactor + connection.once :connected do + channel.attach do + channel.once(:failed) do |state_change| + expect(state_change.reason.code).to eql(50505) + expect(channel.error_reason.code).to eql(50505) + stop_reactor + end + error_message = Ably::Models::ProtocolMessage.new(action: 9, channel: channel_name, error: { code: 50505 }) # ProtocolMessage ERROR type + client.connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message end - error_message = Ably::Models::ProtocolMessage.new(action: 9, channel: channel_name, error: { code: 50505 }) # ProtocolMessage ERROR type - client.connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message end end end end end