spec/acceptance/realtime/connection_failures_spec.rb in ably-1.0.5 vs spec/acceptance/realtime/connection_failures_spec.rb in ably-1.0.6
- old
+ new
@@ -141,11 +141,11 @@
end
context 'with auth_callback' do
context 'opening a new connection' do
context 'when callback fails due to an exception' do
- let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: Proc.new { raise "Cannot issue token" }, log_level: :fatal) }
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: lambda { |token_params| raise "Cannot issue token" }, log_level: :fatal) }
it 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
states = Hash.new { |hash, key| hash[key] = [] }
connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
@@ -172,11 +172,11 @@
let(:client_options) { default_options.merge(
realtime_request_timeout: request_timeout,
use_token_auth: true,
log_level: :fatal)
}
- let(:auth_options) { { auth_callback: Proc.new { sleep 10 }, } }
+ let(:auth_options) { { auth_callback: lambda { |token_params| sleep 10 }, } }
it 'the authorization request fails as configured in the realtime_request_timeout (#RSA4c, #RSA4c1, #RSA4c3)' do
connection.once(:connected) do
connection.on { raise "State should not change and should stay connected" }
@@ -512,10 +512,220 @@
protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Disconnected.to_i)
connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
end
end
+ context 'connection state freshness is monitored' do
+ it 'resumes connections when disconnected within the connection_state_ttl period (#RTN15g)' do
+ connection.once(:connected) do
+ connection_id = connection.id
+ reconnected_with_resume = false
+
+ # Make sure the next connect has the resume param
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
+ url = args[4]
+ uri = URI.parse(url)
+ expect(CGI::parse(uri.query)['resume'][0]).to_not be_empty
+ reconnected_with_resume = true
+ original.call(*args, &block)
+ end
+
+ connection.once(:disconnected) do
+ disconnected_at = Time.now
+
+ connection.once(:connecting) do
+ expect(Time.now.to_f - disconnected_at.to_f).to be < connection.connection_state_ttl
+ connection.once(:connected) do |state_change|
+ expect(connection.id).to eql(connection_id)
+ expect(reconnected_with_resume).to be_truthy
+ stop_reactor
+ end
+ end
+ end
+
+ connection.transport.unbind
+ end
+ end
+
+ context 'when connection_state_ttl period has passed since being disconnected' do
+ let(:client_options) do
+ default_options.merge(
+ disconnected_retry_timeout: 4,
+ suspended_retry_timeout: 8,
+ max_connection_state_ttl: 2,
+ )
+ end
+
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl period has passed (#RTN15g)' do
+ connection.once(:connected) do
+ connection_id = connection.id
+ resumed_with_clean_connection = false
+
+ connection.once(:disconnected) do
+ disconnected_at = Time.now
+
+ connection.once(:connecting) do
+ connection.once(:disconnected) do
+ # Make sure the next connect does not have the resume param
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
+ url = args[4]
+ uri = URI.parse(url)
+ expect(CGI::parse(uri.query)['resume']).to be_empty
+ resumed_with_clean_connection = true
+ original.call(*args, &block)
+ end
+
+ allow(connection.details).to receive(:max_idle_interval).and_return(0)
+ connection.__incoming_protocol_msgbus__.plugin_listeners
+
+ connection.once(:connecting) do
+ expect(Time.now.to_f - disconnected_at.to_f).to be > connection.connection_state_ttl
+ connection.once(:connected) do |state_change|
+ expect(connection.id).to_not eql(connection_id)
+ expect(resumed_with_clean_connection).to be_truthy
+ stop_reactor
+ end
+ end
+ end
+
+ # Disconnect the transport and trigger a new disconnected state
+ wait_until(lambda { connection.transport }) do
+ connection.transport.unbind
+ end
+ end
+
+ connection.__incoming_protocol_msgbus__.unplug_listeners
+ end
+
+ connection.transport.unbind
+ end
+ end
+ end
+
+ context 'when connection_state_ttl period has passed since last activity on the connection' do
+ let(:client_options) do
+ default_options.merge(
+ max_connection_state_ttl: 2,
+ )
+ end
+
+ it 'does not clear the local connection state when the connection_state_ttl period has passed since last activity, but the idle timeout has not passed (#RTN15g1, #RTN15g2)' do
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
+
+ connection.once(:connected) do
+ connection_id = connection.id
+ resumed_connection = false
+
+ connection.once(:disconnected) do
+ disconnected_at = Time.now
+
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(connection.connection_state_ttl + 1)
+
+ # Make sure the next connect does not have the resume param
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
+ url = args[4]
+ uri = URI.parse(url)
+ expect(CGI::parse(uri.query)['resume']).to_not be_empty
+ resumed_connection = true
+ original.call(*args, &block)
+ end
+
+ connection.once(:connecting) do
+ connection.once(:connected) do |state_change|
+ expect(connection.id).to eql(connection_id)
+ expect(resumed_connection).to be_truthy
+ stop_reactor
+ end
+ end
+ end
+
+ connection.transport.unbind
+ end
+ end
+
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl + max_idle_interval period has passed since last activity (#RTN15g1, #RTN15g2)' do
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
+
+ connection.once(:connected) do
+ connection_id = connection.id
+ resumed_with_clean_connection = false
+
+ connection.once(:disconnected) do
+ disconnected_at = Time.now
+
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
+
+ # Make sure the next connect does not have the resume param
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
+ url = args[4]
+ uri = URI.parse(url)
+ expect(CGI::parse(uri.query)['resume']).to be_empty
+ resumed_with_clean_connection = true
+ original.call(*args, &block)
+ end
+
+ connection.once(:connecting) do
+ connection.once(:connected) do |state_change|
+ expect(connection.id).to_not eql(connection_id)
+ expect(resumed_with_clean_connection).to be_truthy
+ stop_reactor
+ end
+ end
+ end
+
+ connection.transport.unbind
+ end
+ end
+
+ it 'still reattaches the channels automatically following a new connection being established (#RTN15g2)' do
+ connection.once(:connected) do
+ connection_id = connection.id
+ resumed_with_clean_connection = false
+ channel_emitted_an_attached = false
+
+ channel.attach do
+ channel.once(:attached) do |channel_state_change|
+ expect(channel_state_change.resumed).to be_falsey
+ channel_emitted_an_attached = true
+ end
+
+ connection.once(:disconnected) do
+ disconnected_at = Time.now
+
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
+
+ # Make sure the next connect does not have the resume param
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
+ url = args[4]
+ uri = URI.parse(url)
+ expect(CGI::parse(uri.query)['resume']).to be_empty
+ resumed_with_clean_connection = true
+ original.call(*args, &block)
+ end
+
+ connection.once(:connecting) do
+ connection.once(:connected) do |state_change|
+ expect(connection.id).to_not eql(connection_id)
+ expect(resumed_with_clean_connection).to be_truthy
+
+ wait_until(lambda { channel.attached? }) do
+ expect(channel_emitted_an_attached).to be_truthy
+ stop_reactor
+ end
+ end
+ end
+ end
+
+ connection.transport.unbind
+ end
+ end
+ end
+ end
+ end
+
context 'and subsequently fails to reconnect' do
let(:retry_every) { 1.5 }
let(:client_options) do
default_options.merge(
@@ -933,20 +1143,24 @@
it 'starts a new connection automatically and does not try and resume' do
connection.once(:connected) do
previous_connection_id = connection.id
previous_connection_key = connection.key
- five_minutes_time = Time.now + 5 * 60
- allow(Time).to receive(:now) { five_minutes_time }
-
connection.once(:connected) do
expect(connection.key).to_not eql(previous_connection_key)
expect(connection.id).to_not eql(previous_connection_id)
stop_reactor
end
- kill_connection_transport_and_prevent_valid_resume
+ # Wait until next tick before stubbing otherwise liveness test may
+ # record the stubbed last contact time as the future time
+ EventMachine.next_tick do
+ five_minutes_time = Time.now + 5 * 60
+ allow(Time).to receive(:now) { five_minutes_time }
+
+ kill_connection_transport_and_prevent_valid_resume
+ end
end
end
end
end
@@ -1015,10 +1229,10 @@
let!(:normal_token) {
rest_client.auth.request_token.token
}
let(:client_options) do
- default_options.merge(auth_callback: Proc.new do
+ default_options.merge(auth_callback: lambda do |token_params|
@auth_requests ||= 0
@auth_requests += 1
case @auth_requests
when 1