spec/acceptance/realtime/connection_failures_spec.rb in ably-0.8.13 vs spec/acceptance/realtime/connection_failures_spec.rb in ably-0.8.14

- old
+ new

@@ -60,11 +60,11 @@ let(:client_failure_options) do default_options.merge( log_level: :none, disconnected_retry_timeout: retry_every_for_tests, suspended_retry_timeout: retry_every_for_tests, - connection_state_ttl: max_time_in_state_for_tests + max_connection_state_ttl: max_time_in_state_for_tests ) end # retry immediately after failure, then one retry every :retry_every_for_tests let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round } @@ -167,11 +167,11 @@ end end context 'for the first time' do let(:client_options) do - default_options.merge(suspended_retry_timeout: 2, connection_state_ttl: 0, log_level: :error) + default_options.merge(suspended_retry_timeout: 2, max_connection_state_ttl: 0, log_level: :error) end it 'waits suspended_retry_timeout before attempting to reconnect' do expect(client.connection.defaults[:suspended_retry_timeout]).to eql(2) connection.once(:connected) do @@ -304,11 +304,11 @@ let(:client_options) do default_options.merge( log_level: :error, disconnected_retry_timeout: 0.1, suspended_retry_timeout: 0.1, - connection_state_ttl: 0.2, + max_connection_state_ttl: 0.2, realtime_host: 'non.existent.host' ) end it 'never calls the provided success block', em_timeout: 10 do @@ -368,11 +368,11 @@ let(:client_options) do default_options.merge( log_level: :none, disconnected_retry_timeout: retry_every, suspended_retry_timeout: retry_every, - connection_state_ttl: 60 + max_connection_state_ttl: 60 ) end it "retries every #{Ably::Realtime::Connection::DEFAULTS.fetch(:disconnected_retry_timeout)} seconds" do fail_if_suspended_or_failed @@ -414,11 +414,11 @@ end end end end - context 'when websocket transport is closed' do + context 'when websocket transport is abruptly disconnected' do it 'reconnects automatically' do fail_if_suspended_or_failed connection.once(:connected) do connection.once(:disconnected) do @@ -429,10 +429,33 @@ end end connection.transport.close_connection_after_writing end end + + context 'hosts used' do + it 'reconnects with the default host' do + fail_if_suspended_or_failed + + connection.once(:connected) do + connection.once(:disconnected) do + hosts = [] + expect(connection).to receive(:create_transport).once.and_wrap_original do |original_method, *args, &block| + hosts << args[0] + original_method.call(*args, &block) + end + connection.once(:connected) do + host = "#{"#{environment}-" if environment && environment.to_s != 'production'}#{Ably::Realtime::Client::DOMAIN}" + expect(hosts.first).to eql(host) + expect(hosts.length).to eql(1) + stop_reactor + end + end + connection.transport.close_connection_after_writing + end + end + end end context 'after successfully reconnecting and resuming' do it 'retains connection_id and updates the connection_key' do connection.once(:connected) do @@ -597,10 +620,36 @@ expect(channel.error_reason).to eql(error) stop_reactor end end end + + context 'as the DISCONNECTED window to resume has passed' do + let(:channel) { client.channel(random_str) } + + def kill_connection_transport_and_prevent_valid_resume + connection.transport.close_connection_after_writing + end + + 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 + end + end + end end end describe 'fallback host feature' do let(:retry_every_for_tests) { 0.2 } @@ -610,11 +659,11 @@ default_options.merge( environment: :production, log_level: :none, disconnected_retry_timeout: retry_every_for_tests, suspended_retry_timeout: retry_every_for_tests, - connection_state_ttl: max_time_in_state_for_tests + max_connection_state_ttl: max_time_in_state_for_tests ) end # Retry immediately and then wait retry_every before every subsequent attempt let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round } @@ -625,11 +674,11 @@ context 'with custom realtime websocket host option' do let(:expected_host) { 'this.host.does.not.exist' } let(:client_options) { timeout_options.merge(realtime_host: expected_host) } it 'never uses a fallback host' do - expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host| + expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host| expect(host).to eql(expected_host) raise EventMachine::ConnectionError end connection.once(:suspended) do @@ -643,11 +692,11 @@ context 'with custom realtime websocket port option' do let(:custom_port) { 666} let(:client_options) { timeout_options.merge(tls_port: custom_port) } it 'never uses a fallback host' do - expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host, port| + expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host, port| expect(port).to eql(custom_port) raise EventMachine::ConnectionError end connection.once(:suspended) do @@ -661,22 +710,71 @@ context 'with non-production environment' do let(:environment) { 'sandbox' } let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" } let(:client_options) { timeout_options.merge(environment: environment) } - it 'never uses a fallback host' do - expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host| + it 'does not use a fallback host by default' do + expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host| expect(host).to eql(expected_host) raise EventMachine::ConnectionError end connection.once(:suspended) do connection.once(:suspended) do stop_reactor end end end + + context ':fallback_hosts_use_default is true' do + let(:max_time_in_state_for_tests) { 4 } + let(:fallback_hosts_used) { Array.new } + let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts_use_default: true) } + + it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k7)' do + request = 0 + allow(connection).to receive(:create_transport) do |host| + if request == 0 + expect(host).to eql(expected_host) + else + fallback_hosts_used << host + end + request += 1 + raise EventMachine::ConnectionError + end + + connection.once(:suspended) do + expect(fallback_hosts_used.uniq).to match_array(Ably::FALLBACK_HOSTS + [expected_host]) + stop_reactor + end + end + end + + context ':fallback_hosts array is provided' do + let(:max_time_in_state_for_tests) { 4 } + let(:fallback_hosts) { %w(a.foo.com b.foo.com) } + let(:fallback_hosts_used) { Array.new } + let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts: fallback_hosts) } + + it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do + request = 0 + allow(connection).to receive(:create_transport) do |host| + if request == 0 + expect(host).to eql(expected_host) + else + fallback_hosts_used << host + end + request += 1 + raise EventMachine::ConnectionError + end + + connection.once(:suspended) do + expect(fallback_hosts_used.uniq).to match_array(fallback_hosts + [expected_host]) + stop_reactor + end + end + end end context 'with production environment' do let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) } before do @@ -692,11 +790,11 @@ before do allow(connection).to receive(:internet_up?).and_yield(false) end it 'never uses a fallback host' do - expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host| + expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host| expect(host).to eql(expected_host) raise EventMachine::ConnectionError end connection.once(:suspended) do @@ -711,46 +809,95 @@ before do allow(connection).to receive(:internet_up?).and_yield(true) @suspended = 0 end - it 'uses a fallback host on every subsequent disconnected attempt until suspended' do - request = 0 - expect(EventMachine).to receive(:connect).exactly(retry_count_for_one_state).times do |host| - if request == 0 - expect(host).to eql(expected_host) - else - fallback_hosts_used << host + context 'and default options' do + let(:max_time_in_state_for_tests) { 2 } # allow time for 3 attempts, 2 configured fallbacks + primary host + + it 'uses a fallback host + the original host once on every subsequent disconnected attempt until suspended' do + request = 0 + expect(connection).to receive(:create_transport).exactly(retry_count_for_one_state).times do |host| + if request == 0 + expect(host).to eql(expected_host) + else + fallback_hosts_used << host + end + request += 1 + raise EventMachine::ConnectionError end - request += 1 - raise EventMachine::ConnectionError + + connection.once(:suspended) do + fallback_hosts_used.pop # remove suspended attempt host + expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host]) + stop_reactor + end end - connection.once(:suspended) do - fallback_hosts_used.pop # remove suspended attempt host - expect(fallback_hosts_used.uniq).to match_array(custom_hosts) - stop_reactor + it 'uses the primary host when suspended, and then every fallback host and the primary host again on every subsequent suspended attempt' do + request = 0 + expect(connection).to receive(:create_transport).at_least(:once) do |host| + if request == 0 || request == expected_retry_attempts + 1 + expect(host).to eql(expected_host) + else + expect(custom_hosts + [expected_host]).to include(host) + fallback_hosts_used << host if @suspended > 0 + end + request += 1 + raise EventMachine::ConnectionError + end + + connection.on(:suspended) do + @suspended += 1 + + if @suspended > 4 + expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host]) + stop_reactor + end + end end end - it 'uses the primary host when suspended, and a fallback host on every subsequent suspended attempt' do - request = 0 - expect(EventMachine).to receive(:connect).at_least(:once) do |host| - if request == 0 || request == expected_retry_attempts + 1 - expect(host).to eql(expected_host) - else - expect(custom_hosts).to include(host) - fallback_hosts_used << host if @suspended > 0 + context ':fallback_hosts array is provided by an empty array' do + let(:max_time_in_state_for_tests) { 3 } + let(:fallback_hosts) { [] } + let(:hosts_used) { Array.new } + let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) } + + it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do + allow(connection).to receive(:create_transport) do |host| + hosts_used << host + raise EventMachine::ConnectionError end - request += 1 - raise EventMachine::ConnectionError + + connection.once(:suspended) do + expect(hosts_used.uniq.length).to eql(1) + expect(hosts_used.uniq.first).to eql(expected_host) + stop_reactor + end end + end - connection.on(:suspended) do - @suspended += 1 + context ':fallback_hosts array is provided' do + let(:max_time_in_state_for_tests) { 3 } + let(:fallback_hosts) { %w(a.foo.com b.foo.com) } + let(:fallback_hosts_used) { Array.new } + let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) } - if @suspended > 3 - expect(fallback_hosts_used.uniq).to match_array(custom_hosts) + it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do + request = 0 + allow(connection).to receive(:create_transport) do |host| + if request == 0 + expect(host).to eql(expected_host) + else + fallback_hosts_used << host + end + request += 1 + raise EventMachine::ConnectionError + end + + connection.once(:suspended) do + expect(fallback_hosts_used.uniq).to match_array(fallback_hosts + [expected_host]) stop_reactor end end end end