spec/acceptance/realtime/connection_spec.rb in ably-0.8.2 vs spec/acceptance/realtime/connection_spec.rb in ably-0.8.3

- old
+ new

@@ -50,13 +50,16 @@ context 'with token auth' do before do # Reduce token expiry buffer to zero so that a token expired? predicate is exact # Normally there is a buffer so that a token expiring soon is considered expired + @original_token_expiry_buffer = Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 end + let(:original_token_expiry_buffer) { @original_token_expiry_buffer } + context 'for renewable tokens' do context 'that are valid for the duration of the test' do context 'with valid pre authorised token expiring in the future' do it 'uses the existing token created by Auth' do client.auth.authorise(ttl: 300) @@ -69,11 +72,11 @@ context 'with implicit authorisation' do let(:client_options) { default_options.merge(client_id: 'force_token_auth') } it 'uses the token created by the implicit authorisation' do - expect(client.auth).to receive(:request_token).once.and_call_original + expect(client.rest_client.auth).to receive(:request_token).once.and_call_original connection.once(:connected) do stop_reactor end end @@ -82,33 +85,49 @@ context 'that expire' do let(:client_options) { default_options.merge(log_level: :none) } before do - client.auth.authorise(ttl: ttl) + expect(client.rest_client.time.to_f).to be_within(2).of(Time.now.to_i), "Local clock is out of sync with Ably" end + before do + # Ensure tokens issued expire immediately after issue + @original_renew_token_buffer = Ably::Auth::TOKEN_DEFAULTS.fetch(:renew_token_buffer) + stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) + + # Authorise synchronously to ensure token has been issued + client.auth.authorise_sync(token_params: { ttl: ttl }) + end + + let(:original_renew_token_buffer) { @original_renew_token_buffer } + context 'opening a new connection' do context 'with recently expired token' do let(:ttl) { 2 } - it 'renews the token on connect' do - sleep ttl + 0.1 - expect(client.auth.current_token_details).to be_expired - expect(client.auth).to receive(:authorise).at_least(:once).and_call_original - connection.once(:connected) do - expect(client.auth.current_token_details).to_not be_expired - stop_reactor + it 'renews the token on connect without changing connection state' do + connection.once(:connecting) do + sleep ttl + 0.1 + expect(client.auth.current_token_details).to be_expired + expect(client.rest_client.auth).to receive(:authorise).at_least(:once).and_call_original + connection.once(:connected) do + expect(client.auth.current_token_details).to_not be_expired + stop_reactor + end + connection.once_state_changed do + raise "Invalid state #{connection.state}" unless connection.state == :connected + end end end end context 'with immediately expiring token' do let(:ttl) { 0.001 } it 'renews the token on connect, and only makes one subsequent attempt to obtain a new token' do - expect(client.auth).to receive(:authorise).at_least(:twice).and_call_original + expect(client.rest_client.auth).to receive(:authorise).at_least(:twice).and_call_original connection.once(:disconnected) do connection.once(:failed) do |error| expect(error.code).to eql(40140) # token expired stop_reactor end @@ -147,20 +166,28 @@ expect(original_token).to_not be_expired connection.once(:connected) do started_at = Time.now connection.once(:disconnected) do |error| + expect(error.code).to eq(40140) # Token expired + + # Token has expired, so now ensure it is not used again + stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', original_token_expiry_buffer + stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: original_renew_token_buffer) + connection.once(:connected) do expect(client.auth.current_token_details).to_not be_expired expect(Time.now - started_at >= ttl) expect(original_token).to be_expired expect(error.code).to eql(40140) # token expired stop_reactor end end end + connection.unsafe_once(:failed) { |error| fail error.inspect } + channel.attach end end skip 'retains connection state' @@ -170,12 +197,17 @@ end end context 'for non-renewable tokens' do context 'that are expired' do + before do + stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) + end + let!(:expired_token_details) do - Ably::Realtime::Client.new(default_options).auth.request_token(ttl: 0.01) + # Request a token synchronously + Ably::Realtime::Client.new(default_options).auth.request_token_sync(token_params: { ttl: 0.01 }) end context 'opening a new connection' do let(:client_options) { default_options.merge(key: nil, token: expired_token_details.token, log_level: :none) } @@ -258,10 +290,24 @@ end end end end + describe 'connection#id' do + it 'is null before connecting' do + expect(connection.id).to be_nil + stop_reactor + end + end + + describe 'connection#key' do + it 'is null before connecting' do + expect(connection.key).to be_nil + stop_reactor + end + end + describe 'once connected' do let(:connection2) { Ably::Realtime::Client.new(client_options).connection } describe 'connection#id' do it 'is a string' do @@ -324,10 +370,22 @@ end end end end end + + context 'when closing' do + it 'raises an exception before the connection is closed' do + connection.connect do + connection.once(:closing) do + expect { connection.connect }.to raise_error Ably::Exceptions::InvalidStateChange + stop_reactor + end + connection.close + end + end + end end describe '#serial connection serial' do let(:channel) { client.channel(random_str) } @@ -337,11 +395,10 @@ stop_reactor end end context 'when a message is sent but the ACK has not yet been received' do - it 'the sent message msgSerial is 0 but the connection serial remains at -1' do channel.attach do connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| connection.__outgoing_protocol_msgbus__.unsubscribe expect(protocol_message['msgSerial']).to eql(0) @@ -730,17 +787,17 @@ end context 'when a state transition is unsupported' do let(:client_options) { default_options.merge(log_level: :none) } # silence FATAL errors - it 'emits a StateChangeError' do + it 'emits a InvalidStateChange' do connection.connect do connection.transition_state_machine :initialized end connection.on(:error) do |error| - expect(error).to be_a(Ably::Exceptions::StateChangeError) + expect(error).to be_a(Ably::Exceptions::InvalidStateChange) stop_reactor end end end @@ -792,21 +849,46 @@ end end end context 'when the Internet is up' do + let(:client_options) { default_options.merge(tls: false, use_token_auth: true) } + + context 'with a TLS connection' do + let(:client_options) { default_options.merge(tls: true) } + + it 'checks the Internet up URL over TLS' do + expect(EventMachine::HttpRequest).to receive(:new).with("https:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new)) + connection.internet_up? + stop_reactor + end + end + + context 'with a non-TLS connection' do + let(:client_options) { default_options.merge(tls: false, use_token_auth: true) } + + it 'checks the Internet up URL over TLS' do + expect(EventMachine::HttpRequest).to receive(:new).with("http:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new)) + connection.internet_up? + stop_reactor + end + end + it 'calls the block with true' do connection.internet_up? do |result| expect(result).to be_truthy - stop_reactor + EventMachine.add_timer(0.2) { stop_reactor } end end it 'calls the success callback of the Deferrable' do connection.internet_up?.callback do - stop_reactor + EventMachine.add_timer(0.2) { stop_reactor } end + connection.internet_up?.errback do |error| + raise "Could not perform the Internet up check. Are you connected to the Internet? #{error}" + end end end context 'when the Internet is down' do before do @@ -822,9 +904,78 @@ it 'calls the failure callback of the Deferrable' do connection.internet_up?.errback do stop_reactor end + end + end + end + end + + describe 'state change side effects' do + let(:channel) { client.channels.get(random_str) } + let(:client_options) { default_options.merge(:log_level => :error) } + + context 'when connection enters the :disconnected state' do + it 'queues messages to be sent and all channels remain attached' do + channel.attach do + connection.once(:disconnected) do + expect(connection.__outgoing_message_queue__).to be_empty + channel.publish 'test' + + EventMachine.add_timer(0.02) do + expect(connection.__outgoing_message_queue__).to_not be_empty + end + + connection.once(:connected) do + EventMachine.add_timer(0.02) do + expect(connection.__outgoing_message_queue__).to be_empty + stop_reactor + end + end + end + + connection.transport.close_connection_after_writing + end + end + end + + context 'when connection enters the :suspended state' do + before do + # Reconfigure client library retry periods so that client stays in suspended state + stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG', + Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge( + disconnected: { retry_every: 0.01, max_time_in_state: 0.05 }, + suspended: { retry_every: 60, max_time_in_state: 60 } + ) + end + + it 'detaches the channels and prevents publishing of messages on those channels' do + channel.attach do + channel.once(:detached) do + expect { channel.publish 'test' }.to raise_error(Ably::Exceptions::ChannelInactive) + stop_reactor + end + + # Keep disconnecting the websocket transport after it attempts reconnection + connection.transport.close_connection_after_writing + connection.on(:connecting) do + EventMachine.add_timer(0.03) do + connection.transport.close_connection_after_writing + end + end + end + end + end + + context 'when connection enters the :failed state' do + let(:client_options) { default_options.merge(:key => 'will.not:authenticate', log_level: :none) } + + it 'sets all channels to failed and prevents publishing of messages on those channels' do + channel.attach + channel.once(:failed) do + expect { channel.publish 'test' }.to raise_error(Ably::Exceptions::ChannelInactive) + stop_reactor end end end end end