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