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