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