lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb in ably-rest-0.8.5 vs lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb in ably-rest-0.8.6
- old
+ new
@@ -7,13 +7,16 @@
vary_by_protocol do
let(:default_options) { { key: api_key, environment: environment, protocol: protocol } }
let(:client_options) { default_options }
let(:anonymous_client) { auto_close Ably::Realtime::Client.new(client_options) }
- let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: random_str)) }
- let(:client_two) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: random_str)) }
+ let(:client_one_id) { random_str }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: client_one_id)) }
+ let(:client_two_id) { random_str }
+ let(:client_two) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: client_two_id)) }
+ let(:wildcard_token) { Proc.new { Ably::Rest::Client.new(client_options).auth.request_token(client_id: '*') } }
let(:channel_name) { "presence-#{random_str(4)}" }
let(:channel_anonymous_client) { anonymous_client.channel(channel_name) }
let(:presence_anonymous_client) { channel_anonymous_client.presence }
let(:channel_client_one) { client_one.channel(channel_name) }
let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name) }
@@ -27,10 +30,18 @@
client.connection.transport.send(:driver).remove_all_listeners('message')
client.connection.transport.unbind
end
shared_examples_for 'a public presence method' do |method_name, expected_state, args, options = {}|
+ let(:client_id) do
+ if args.empty?
+ random_str
+ else
+ args
+ end
+ end
+
def setup_test(method_name, args, options)
if options[:enter_first]
presence_client_one.public_send(method_name.to_s.gsub(/leave|update/, 'enter'), args) do
yield
end
@@ -70,11 +81,11 @@
stop_reactor
end
end
context 'when :queue_messages client option is false' do
- let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: random_str)) }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id)) }
context 'and connection state initialized' do
it 'raises an exception' do
expect { presence_client_one.public_send(method_name, args) }.to raise_error Ably::Exceptions::MessageQueueingDisabled
expect(client_one.connection).to be_initialized
@@ -92,11 +103,11 @@
end
end
end
context 'and connection state disconnected' do
- let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: random_str, :log_level => :error)) }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(queue_messages: false, client_id: client_id, :log_level => :error)) }
it 'raises an exception' do
client_one.connection.once(:connected) do
client_one.connection.once(:disconnected) do
expect { presence_client_one.public_send(method_name, args) }.to raise_error Ably::Exceptions::MessageQueueingDisabled
@@ -120,11 +131,11 @@
end
context 'with supported data payload content type' do
def register_presence_and_check_data(method_name, data)
if method_name.to_s.match(/_client/)
- presence_client_one.public_send(method_name, 'client_id', data: data)
+ presence_client_one.public_send(method_name, client_id, data: data)
else
presence_client_one.public_send(method_name, data: data)
end
presence_client_one.subscribe do |presence_message|
@@ -175,11 +186,11 @@
end
context 'with unsupported data payload content type' do
def presence_action(method_name, data)
if method_name.to_s.match(/_client/)
- presence_client_one.public_send(method_name, 'client_id', data: data)
+ presence_client_one.public_send(method_name, client_id, data: data)
else
presence_client_one.public_send(method_name, data: data)
end
end
@@ -253,28 +264,20 @@
presence_client_one.public_send(method_name, args) { raise 'Intentional exception' }
end
end
context 'if connection fails before success' do
- before do
- # Reconfigure client library so that it makes no retry attempts and fails immediately
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
- disconnected: { retry_every: 0.1, max_time_in_state: 0 },
- suspended: { retry_every: 0.1, max_time_in_state: 0 }
- )
- end
-
let(:client_options) { default_options.merge(log_level: :none) }
it 'calls the Deferrable errback if channel is detached' do
setup_test(method_name, args, options) do
channel_client_one.attach do
client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
# Don't allow any messages to reach the server
client_one.connection.__outgoing_protocol_msgbus__.unsubscribe
- force_connection_failure client_one
+ error_message = Ably::Models::ProtocolMessage.new(action: 9, error: { message: 'force failure' })
+ client_one.connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
end
presence_client_one.public_send(method_name, args).tap do |deferrable|
deferrable.callback { raise 'Should not succeed' }
deferrable.errback do |error|
@@ -286,10 +289,127 @@
end
end
end
end
+ shared_examples_for 'a presence on behalf of another client method' do |method_name|
+ context ":#{method_name} when authenticated with a wildcard client_id" do
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*').token }
+ let(:client_options) { default_options.merge(key: nil, token: token) }
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
+ let(:presence_channel) { client.channels.get(channel_name).presence }
+
+ context 'and a valid client_id' do
+ it 'succeeds' do
+ presence_channel.public_send(method_name, 'clientId') do
+ EM.add_timer(0.5) { stop_reactor }
+ end.tap do |deferrable|
+ deferrable.errback { raise 'Should have succeeded' }
+ end
+ end
+ end
+
+ context 'and a wildcard client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, '*') }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+
+ context 'and an empty client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, nil) }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+ end
+
+ context ":#{method_name} when authenticated with a valid client_id" do
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: 'valid').token }
+ let(:client_options) { default_options.merge(key: nil, token: token) }
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options.merge(log_level: :error)) }
+ let(:channel) { client.channels.get(channel_name) }
+ let(:presence_channel) { channel.presence }
+
+ context 'and another invalid client_id' do
+ context 'before authentication' do
+ it 'allows the operation and then Ably rejects the operation' do
+ presence_channel.public_send(method_name, 'invalid').errback do |error|
+ expect(error.code).to eql(40012)
+ stop_reactor
+ end
+ end
+ end
+
+ context 'after authentication' do
+ it 'throws an exception' do
+ channel.attach do
+ expect { presence_channel.public_send(method_name, 'invalid') }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+ end
+ end
+
+ context 'and a wildcard client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, '*') }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+
+ context 'and an empty client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, nil) }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+ end
+
+ context ":#{method_name} when anonymous and no client_id" do
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: nil).token }
+ let(:client_options) { default_options.merge(key: nil, token: token) }
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options.merge(log_level: :error)) }
+ let(:channel) { client.channels.get(channel_name) }
+ let(:presence_channel) { channel.presence }
+
+ context 'and another invalid client_id' do
+ context 'before authentication' do
+ it 'allows the operation and then Ably rejects the operation' do
+ presence_channel.public_send(method_name, 'invalid').errback do |error|
+ expect(error.code).to eql(40012)
+ stop_reactor
+ end
+ end
+ end
+
+ context 'after authentication' do
+ it 'throws an exception' do
+ channel.attach do
+ expect { presence_channel.public_send(method_name, 'invalid') }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+ end
+ end
+
+ context 'and a wildcard client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, '*') }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+
+ context 'and an empty client_id' do
+ it 'throws an exception' do
+ expect { presence_channel.public_send(method_name, nil) }.to raise_error Ably::Exceptions::IncompatibleClientId
+ stop_reactor
+ end
+ end
+ end
+ end
+
context 'when attached (but not present) on a presence channel with an anonymous client (no client ID)' do
it 'maintains state as other clients enter and leave the channel' do
channel_anonymous_client.attach do
presence_anonymous_client.subscribe(:enter) do |presence_message|
expect(presence_message.client_id).to eql(client_one.client_id)
@@ -392,10 +512,11 @@
context 'requires at least 3 SYNC ProtocolMessages' do
let(:enter_expected_count) { 250 }
let(:present) { [] }
let(:entered) { [] }
let(:sync_pages_received) { [] }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
def setup_members_on(presence)
enter_expected_count.times do |index|
presence.enter_client("client:#{index}") do |message|
entered << message
@@ -432,11 +553,14 @@
end
presence_anonymous_client.subscribe(:leave) do |leave_message|
expect(leave_message.client_id).to eql(leave_member.client_id)
expect(present.count).to be < enter_expected_count
- stop_reactor
+ EventMachine.add_timer(1) do
+ presence_anonymous_client.unsubscribe
+ stop_reactor
+ end
end
anonymous_client.connect do
anonymous_client.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
if protocol_message.action == :sync
@@ -469,11 +593,14 @@
leave_member = present_message unless leave_member
if present.count == enter_expected_count
presence_anonymous_client.get do |members|
expect(members.find { |member| member.client_id == leave_member.client_id}.action).to eq(:present)
- stop_reactor
+ EventMachine.add_timer(1) do
+ presence_anonymous_client.unsubscribe
+ stop_reactor
+ end
end
end
end
presence_anonymous_client.subscribe(:leave) do |leave_message|
@@ -521,11 +648,14 @@
presence_anonymous_client.get(wait_for_sync: true) do |members|
expect(members.count).to eql(enter_expected_count - 1)
expect(member_left_emitted).to eql(true)
expect(members.map(&:client_id)).to_not include(left_client_id)
- stop_reactor
+ EventMachine.add_timer(1) do
+ presence_anonymous_client.unsubscribe
+ stop_reactor
+ end
end
channel_anonymous_client.attach do
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
fake_leave_presence_message = Ably::Models::PresenceMessage.new(
@@ -542,11 +672,11 @@
end
end
context '#get' do
context 'with :wait_for_sync option set to true' do
- it 'waits until sync is complete', event_machine: 15 do
+ it 'waits until sync is complete', em_timeout: 15 do
enter_expected_count.times do |index|
presence_client_one.enter_client("client:#{index}") do |message|
entered << message
next unless entered.count == enter_expected_count
@@ -559,11 +689,11 @@
end
end
end
context 'by default' do
- it 'it does not wait for sync', event_machine: 15 do
+ it 'it does not wait for sync', em_timeout: 15 do
enter_expected_count.times do |index|
presence_client_one.enter_client("client:#{index}") do |message|
entered << message
next unless entered.count == enter_expected_count
@@ -654,11 +784,11 @@
end
end
end
it 'raises an exception if client_id is not set' do
- expect { channel_anonymous_client.presence.enter }.to raise_error(Ably::Exceptions::Standard, /without a client_id/)
+ expect { channel_anonymous_client.presence.enter }.to raise_error(Ably::Exceptions::IncompatibleClientId, /without a client_id/)
stop_reactor
end
context 'without necessary capabilities to join presence' do
let(:restricted_client) do
@@ -787,11 +917,11 @@
end
end
end
it 'raises an exception if not entered' do
- expect { channel_anonymous_client.presence.leave }.to raise_error(Ably::Exceptions::Standard, /Unable to leave presence channel that is not entered/)
+ expect { channel_client_one.presence.leave }.to raise_error(Ably::Exceptions::Standard, /Unable to leave presence channel that is not entered/)
stop_reactor
end
it_should_behave_like 'a public presence method', :leave, :left, {}, enter_first: true
end
@@ -822,12 +952,14 @@
end
end
context 'entering/updating/leaving presence state on behalf of another client_id' do
let(:client_count) { 5 }
- let(:clients) { [] }
- let(:data) { random_str }
+ let(:clients) { [] }
+ let(:data) { random_str }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
+ let(:client_two) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
context '#enter_client' do
context 'multiple times on the same channel with different client_ids' do
it "has no affect on the client's presence state and only enters on behalf of the provided client_id" do
client_count.times do |client_id|
@@ -873,12 +1005,10 @@
stop_reactor
end
end
end
- it_should_behave_like 'a public presence method', :enter_client, nil, 'client_id'
-
context 'without necessary capabilities to enter on behalf of another client' do
let(:restricted_client) do
auto_close Ably::Realtime::Client.new(default_options.merge(key: restricted_api_key, log_level: :fatal))
end
let(:restricted_channel) { restricted_client.channel("cansubscribe:channel") }
@@ -889,10 +1019,13 @@
deferrable.callback { raise "Should not succeed" }
deferrable.errback { stop_reactor }
end
end
end
+
+ it_should_behave_like 'a public presence method', :enter_client, nil, 'client_id'
+ it_should_behave_like 'a presence on behalf of another client method', :enter_client
end
context '#update_client' do
context 'multiple times on the same channel with different client_ids' do
it 'updates the data attribute for the member when :data option provided' do
@@ -953,10 +1086,11 @@
end
end
end
it_should_behave_like 'a public presence method', :update_client, nil, 'client_id'
+ it_should_behave_like 'a presence on behalf of another client method', :update_client
end
context '#leave_client' do
context 'leaves a channel' do
context 'multiple times on the same channel with different client_ids' do
@@ -1046,10 +1180,11 @@
end
end
end
it_should_behave_like 'a public presence method', :leave_client, nil, 'client_id'
+ it_should_behave_like 'a presence on behalf of another client method', :leave_client
end
end
context '#get' do
it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
@@ -1089,38 +1224,33 @@
stop_reactor
end
end
context 'during a sync' do
- let(:pages) { 2 }
- let(:members_per_page) { 100 }
+ let(:pages) { 2 }
+ let(:members_per_page) { 100 }
let(:sync_pages_received) { [] }
- let(:client_options) { default_options.merge(log_level: :none) }
+ let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
+ let(:client_options) { default_options.merge(log_level: :none) }
def connect_members_deferrables
(members_per_page * pages + 1).times.map do |index|
presence_client_one.enter_client("client:#{index}")
end
end
- before do
- # Reconfigure client library so that it makes no retry attempts and fails immediately
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
- disconnected: { retry_every: 0.1, max_time_in_state: 0 },
- suspended: { retry_every: 0.1, max_time_in_state: 0 }
- )
- end
-
context 'when :wait_for_sync is true' do
it 'fails if the connection fails' do
when_all(*connect_members_deferrables) do
channel_client_two.attach do
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
if protocol_message.action == :sync
sync_pages_received << protocol_message
- force_connection_failure client_two if sync_pages_received.count == 1
+ if sync_pages_received.count == 1
+ error_message = Ably::Models::ProtocolMessage.new(action: 9, error: { message: 'force failure' })
+ client_two.connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
+ end
end
end
end
presence_client_two.get(wait_for_sync: true).tap do |deferrable|
@@ -1194,35 +1324,35 @@
end
end
end
it 'filters by client_id option if provided' do
- presence_client_one.enter(client_id: 'one') do
- presence_client_two.enter client_id: 'two'
+ presence_client_one.enter do
+ presence_client_two.enter
end
presence_client_one.subscribe(:enter) do |presence_message|
# wait until the client_two enter event has been sent to client_one
- next unless presence_message.client_id == 'two'
+ next unless presence_message.client_id == client_two_id
- presence_client_one.get(client_id: 'one') do |members|
+ presence_client_one.get(client_id: client_one_id) do |members|
expect(members.count).to eq(1)
- expect(members.first.client_id).to eql('one')
+ expect(members.first.client_id).to eql(client_one_id)
expect(members.first.connection_id).to eql(client_one.connection.id)
- presence_client_one.get(client_id: 'two') do |members|
+ presence_client_one.get(client_id: client_two_id) do |members|
expect(members.count).to eq(1)
- expect(members.first.client_id).to eql('two')
+ expect(members.first.client_id).to eql(client_two_id)
expect(members.first.connection_id).to eql(client_two.connection.id)
stop_reactor
end
end
end
end
it 'does not wait for SYNC to complete if :wait_for_sync option is false' do
- presence_client_one.enter(client_id: 'one') do
+ presence_client_one.enter do
presence_client_two.get(wait_for_sync: false) do |members|
expect(members.count).to eql(0)
stop_reactor
end
end
@@ -1240,10 +1370,12 @@
end
end
end
context 'with lots of members on different clients' do
+ let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
+ let(:client_two) { auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: wildcard_token)) }
let(:members_per_client) { 10 }
let(:clients_entered) { Hash.new { |hash, key| hash[key] = 0 } }
let(:total_members) { members_per_client * 2 }
it 'returns a complete list of members on all clients' do
@@ -1586,10 +1718,10 @@
let(:sync_pages_received) { [] }
let(:client_options) { default_options.merge(log_level: :error) }
it 'resumes the SYNC operation', em_timeout: 15 do
when_all(*members_count.times.map do |index|
- presence_client_one.enter_client("client:#{index}")
+ presence_anonymous_client.enter_client("client:#{index}")
end) do
channel_client_two.attach do
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
if protocol_message.action == :sync
sync_pages_received << protocol_message