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