spec/acceptance/realtime/presence_spec.rb in ably-1.2.4 vs spec/acceptance/realtime/presence_spec.rb in ably-1.2.6

- old
+ new

@@ -58,17 +58,17 @@ yield end end unless expected_state == :left - it 'raise an exception if the channel is detached' do + it "presence #{method_name} : raise an exception if the channel is detached" do setup_test(method_name, args, options) do channel_client_one.attach do channel_client_one.transition_state_machine :detaching channel_client_one.once(:detached) do presence_client_one.public_send(method_name, args).tap do |deferrable| - deferrable.callback { raise 'Get should not succeed' } + deferrable.callback { raise "presence #{method_name} should not succeed" } deferrable.errback do |error| expect(error).to be_a(Ably::Exceptions::InvalidState) expect(error.message).to match(/Operation is not allowed when channel is in STATE.Detached/) stop_reactor end @@ -76,48 +76,48 @@ end end end end - it 'raise an exception if the channel becomes detached' do + it "presence #{method_name} : raise an exception if the channel becomes detached" do setup_test(method_name, args, options) do channel_client_one.attach do channel_client_one.transition_state_machine :detaching presence_client_one.public_send(method_name, args).tap do |deferrable| - deferrable.callback { raise 'Get should not succeed' } + deferrable.callback { raise "presence #{method_name} should not succeed" } deferrable.errback do |error| expect(error).to be_a(Ably::Exceptions::InvalidState) expect(error.message).to match(/Operation failed as channel transitioned to STATE.Detached/) stop_reactor end end end end end - it 'raise an exception if the channel is failed' do + it "presence #{method_name} : raise an exception if the channel is failed" do setup_test(method_name, args, options) do channel_client_one.attach do channel_client_one.transition_state_machine :failed expect(channel_client_one.state).to eq(:failed) presence_client_one.public_send(method_name, args).tap do |deferrable| - deferrable.callback { raise 'Get should not succeed' } + deferrable.callback { raise "presence #{method_name} : Get should not succeed" } deferrable.errback do |error| expect(error).to be_a(Ably::Exceptions::InvalidState) expect(error.message).to match(/Operation is not allowed when channel is in STATE.Failed/) stop_reactor end end end end end - it 'raise an exception if the channel becomes failed' do + it "presence #{method_name} : raise an exception if the channel becomes failed" do setup_test(method_name, args, options) do channel_client_one.attach do presence_client_one.public_send(method_name, args).tap do |deferrable| - deferrable.callback { raise 'Get should not succeed' } + deferrable.callback { raise "presence #{method_name} : Get should not succeed" } deferrable.errback do |error| expect(error).to be_a(Ably::Exceptions::MessageDeliveryFailed) stop_reactor end end @@ -514,15 +514,15 @@ it 'is not synchronised when initially created' do expect(presence_anonymous_client.members).to_not be_sync_complete stop_reactor end - it 'will emit an :in_sync event when synchronisation is complete' do + it 'will emit an :sync_complete event when synchronisation is complete' do presence_client_one.enter presence_client_two.enter - presence_anonymous_client.members.once(:in_sync) do + presence_anonymous_client.members.once(:sync_complete) do stop_reactor end channel_anonymous_client.attach end @@ -543,11 +543,11 @@ entered = 0 presence_client_one.subscribe(:enter) do entered += 1 next unless entered == 2 - presence_anonymous_client.members.once(:in_sync) do + presence_anonymous_client.members.once(:sync_complete) do expect(presence_anonymous_client.members.count).to eql(2) member_ids = presence_anonymous_client.members.map(&:member_key) expect(member_ids.count).to eql(2) expect(member_ids.uniq.count).to eql(2) stop_reactor @@ -578,11 +578,10 @@ ## Fabricate members action = Ably::Models::ProtocolMessage::ACTION.Presence presence_msg = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: 20, channel: channel_name, presence: presence_data, timestamp: Time.now.to_i * 1000 ) anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, presence_msg @@ -631,22 +630,20 @@ sync_pages_received << protocol_message if sync_pages_received.count == 1 action = Ably::Models::ProtocolMessage::ACTION.Presence presence_msg = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: anonymous_client.connection.serial + 1, channel: channel_name, presence: presence_data, timestamp: Time.now.to_i * 1000 ) anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, presence_msg # Now simulate an end to the sync action = Ably::Models::ProtocolMessage::ACTION.Sync sync_msg = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: anonymous_client.connection.serial + 2, channel: channel_name, channel_serial: 'validserialprefix:', # with no part after the `:` this indicates the end to the SYNC presence: [], timestamp: Time.now.to_i * 1000 ) @@ -705,25 +702,33 @@ end end context '#sync_complete? and SYNC flags (#RTP1)' do context 'when attaching to a channel without any members present' do - xit 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do - flag_checked = false + it 'sync_complete? is true, no members are received and the presence channel is synced (#RTP1)' do + sync_info_received = false anonymous_client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| if protocol_message.action == :attached - flag_checked = true - expect(protocol_message.has_presence_flag?).to eql(false) + if protocol_message.has_presence_flag? + sync_info_received = false + else + sync_info_received = true + end end + if protocol_message.action == Ably::Models::ProtocolMessage::ACTION.Sync + expect(protocol_message.presence).to be_empty + sync_info_received = true + end end channel_anonymous_client.attach do - expect(channel_anonymous_client.presence).to be_sync_complete - EventMachine.next_tick do - expect(flag_checked).to eql(true) - stop_reactor + wait_until(lambda { channel_anonymous_client.presence.sync_complete? and sync_info_received}) do + channel_anonymous_client.presence.get do |members| + expect(members).to be_empty + stop_reactor + end end end end end @@ -849,11 +854,11 @@ expect(present.count).to be < enter_expected_count # Hacky accessing a private method, but absent members are intentionally not exposed to any public APIs expect(presence_anonymous_client.members.send(:absent_members).length).to eql(1) - presence_anonymous_client.members.once(:in_sync) do + presence_anonymous_client.members.once(:sync_complete) do # Check that members count is exact indicating the members with LEAVE action after sync are removed expect(presence_anonymous_client).to be_sync_complete expect(presence_anonymous_client.members.length).to eql(enter_expected_count - 1) presence_anonymous_client.unsubscribe stop_reactor @@ -1005,11 +1010,11 @@ entered << message next unless entered.count == enter_expected_count channel_anonymous_client.attach do presence_anonymous_client.get(wait_for_sync: false) do |members| - expect(presence_anonymous_client.members).to_not be_in_sync + expect(presence_anonymous_client.members).to_not be_sync_complete expect(members.count).to eql(0) stop_reactor end end end @@ -1212,11 +1217,11 @@ channel_client_one.attach do presence_client_one.subscribe(:enter) do presence_client_one.unsubscribe :enter EventMachine.add_timer(0.5) do - expect(presence_client_one.members).to be_in_sync + expect(presence_client_one.members).to be_sync_complete expect(presence_client_one.members.send(:members).count).to eql(1) presence_client_one.leave data end end @@ -1590,18 +1595,20 @@ end end end it 'fails if the connection is DETACHED (#RTP11b)' do - channel_client_one.attach do - channel_client_one.detach do - presence_client_one.get.tap do |deferrable| - deferrable.callback { raise 'Get should not succeed' } - deferrable.errback do |error| - expect(error).to be_a(Ably::Exceptions::InvalidState) - expect(error.message).to match(/Operation is not allowed when channel is in STATE.Detached/) - stop_reactor + client_one.connection.once :connected do + channel_client_one.attach do + channel_client_one.detach do + presence_client_one.get.tap do |deferrable| + deferrable.callback { raise 'Get should not succeed' } + deferrable.errback do |error| + expect(error).to be_a(Ably::Exceptions::InvalidState) + expect(error.message).to match(/Operation is not allowed when channel is in STATE.Detached/) + stop_reactor + end end end end end end @@ -1813,33 +1820,35 @@ 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 - members_per_client.times do |indx| - presence_client_one.enter_client("client_1:#{indx}") - presence_client_two.enter_client("client_2:#{indx}") - end + wait_until(lambda { client_one.connection.state == :connected and client_two.connection.state == :connected }) do + presence_client_one.subscribe(:enter) do + clients_entered[:client_one] += 1 + end - presence_client_one.subscribe(:enter) do - clients_entered[:client_one] += 1 - end + presence_client_two.subscribe(:enter) do + clients_entered[:client_two] += 1 + end - presence_client_two.subscribe(:enter) do - clients_entered[:client_two] += 1 - end + members_per_client.times do |indx| + presence_client_one.enter_client("client_1:#{indx}") + presence_client_two.enter_client("client_2:#{indx}") + end - wait_until(lambda { clients_entered[:client_one] + clients_entered[:client_two] == total_members * 2 }) do - presence_anonymous_client.get(wait_for_sync: true) do |anonymous_members| - expect(anonymous_members.count).to eq(total_members) - expect(anonymous_members.map(&:client_id).uniq.count).to eq(total_members) + wait_until(lambda { clients_entered[:client_one] + clients_entered[:client_two] == total_members * 2 }) do + presence_anonymous_client.get(wait_for_sync: true) do |anonymous_members| + expect(anonymous_members.count).to eq(total_members) + expect(anonymous_members.map(&:client_id).uniq.count).to eq(total_members) - presence_client_one.get(wait_for_sync: true) do |client_one_members| - presence_client_two.get(wait_for_sync: true) do |client_two_members| - expect(client_one_members.count).to eq(total_members) - expect(client_one_members.count).to eq(client_two_members.count) - stop_reactor + presence_client_one.get(wait_for_sync: true) do |client_one_members| + presence_client_two.get(wait_for_sync: true) do |client_two_members| + expect(client_one_members.count).to eq(total_members) + expect(client_one_members.count).to eq(client_two_members.count) + stop_reactor + end end end end end end @@ -2241,21 +2250,19 @@ ## Fabricate server-initiated SYNC in two parts action = Ably::Models::ProtocolMessage::ACTION.Sync sync_message = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: 10, channel_serial: 'sequenceid:cursor', channel: channel_name, presence: presence_sync_1, timestamp: Time.now.to_i * 1000 ) anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message sync_message = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: 11, channel_serial: 'sequenceid:', # indicates SYNC is complete channel: channel_name, presence: presence_sync_2, timestamp: Time.now.to_i * 1000 ) @@ -2292,11 +2299,10 @@ ## Fabricate server-initiated SYNC in two parts action = Ably::Models::ProtocolMessage::ACTION.Sync sync_message = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: 10, channel: channel_name, presence: presence_sync, timestamp: Time.now.to_i * 1000 ) anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message @@ -2346,11 +2352,10 @@ ## Fabricate server-initiated SYNC in two parts action = Ably::Models::ProtocolMessage::ACTION.Sync sync_message = Ably::Models::ProtocolMessage.new( action: action, - connection_serial: 10, channel: channel_name, presence: presence_sync_protocol_message, timestamp: Time.now.to_i * 1000 ) anonymous_client.connection.__incoming_protocol_msgbus__.publish :protocol_message, sync_message @@ -2465,11 +2470,11 @@ expect(presence_client_two.members.local_members).to be_empty stop_reactor end leave_message = Ably::Models::PresenceMessage.new( - 'id' => "#{client_two.connection.id}:#{presence_client_two.client_id}:1", + 'id' => "#{client_two.connection.id}:#{client_two.connection.send(:client_msg_serial)}:1", 'clientId' => presence_client_two.client_id, 'connectionId' => client_two.connection.id, 'timestamp' => as_since_epoch(Time.now), 'action' => leave_action ) @@ -2530,50 +2535,74 @@ context 'and the resume flag is false' do context 'and the presence flag is false' do let(:member_data) { random_str } it 'immediately resends all local presence members (#RTP5c2, #RTP19a)' do - in_sync_confirmed_no_local_members = false - local_member_leave_event_fired = false + member_leave_event_fired = false + local_members_sent = false - presence_client_one.enter(member_data) - presence_client_one.subscribe(:enter) do + presence_client_one.subscribe(:enter) do |entered_member| + expect(entered_member.action).to eq(Ably::Models::PresenceMessage::ACTION.Enter) + expect(entered_member.data).to eq(member_data) + expect(entered_member.client_id).to eq(client_one.auth.client_id) + expect(entered_member.id).to be_truthy + entered_member_id = entered_member.id + presence_client_one.unsubscribe :enter - presence_client_one.subscribe(:leave) do |message| - # The local member will leave the PresenceMap due to the ATTACHED without Presence - local_member_leave_event_fired = true + expect(presence_client_one.members.length).to eql(1) + expect(presence_client_one.members.local_members.length).to eql(1) + + # subscribe to outgoing messages to check for entered local members with id + client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message| + if protocol_message.action == :presence + protocol_message.presence.each do |local_member| + expect(local_member.id).to eq(entered_member_id) + expect(local_member.action).to eq(Ably::Models::PresenceMessage::ACTION.Enter) + expect(local_member.data).to eq(member_data) + expect(local_member.client_id).to eq(client_one.auth.client_id) + local_members_sent = true + end + end end - # Local members re-entered automatically appear as updates due to the - # fabricated ATTACHED message sent and the members already being present - presence_client_one.subscribe(:update) do |message| - expect(local_member_leave_event_fired).to be_truthy + presence_client_one.subscribe(:leave) do |message| + # Member will leave the PresenceMap due to the ATTACHED without Presence expect(message.data).to eq(member_data) expect(message.client_id).to eq(client_one.auth.client_id) - EventMachine.next_tick do - expect(presence_client_one.members.length).to eql(1) - expect(presence_client_one.members.local_members.length).to eql(1) - expect(in_sync_confirmed_no_local_members).to be_truthy - stop_reactor - end + member_leave_event_fired = true end - presence_client_one.members.once(:in_sync) do - # Immediately after SYNC (no sync actually occurred, but this event fires immediately after a channel SYNCs or is not expecting to SYNC) + # Shouldn't receive enter/update message when local_members are entered + # This is due to the fact that, when we enter local member we also send + # member id, and server automatically checks for duplicate id and doesn't + # send presenceEnter or presenceUpdate if id is found. + presence_client_one.subscribe(:enter, :update) do |message| + raise { "client shouldn't receive update event for entered local members" } + end + + presence_client_one.members.once(:sync_complete) do expect(presence_client_one.members.length).to eql(0) - expect(presence_client_one.members.local_members.length).to eql(0) - in_sync_confirmed_no_local_members = true + + # Since, this is a client sent event, local_members are not cleared + # local_members acts a source of truth for server and not vice versa + expect(presence_client_one.members.local_members.length).to eql(1) + + wait_until(lambda { member_leave_event_fired and local_members_sent}) do + stop_reactor + end end # ATTACHED ProtocolMessage with no presence flag will clear the presence set immediately, #RTP19a fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new( action: attached_action, channel: channel_name, flags: 0 # no resume or presence flag ) end + + presence_client_one.enter(member_data) end end end context 'when re-entering a client automatically, if the re-enter fails for any reason' do @@ -2682,26 +2711,28 @@ end end context 'channel transitions to the DETACHED state' do it 'clears the PresenceMap and local member map copy and does not emit any presence events (#RTP5a)' do - presence_client_one.enter - presence_client_one.subscribe(:enter) do - presence_client_one.unsubscribe :enter + wait_until(lambda { client_one.connection.state == :connected and anonymous_client.connection.state == :connected }) do + presence_client_one.enter + presence_client_one.subscribe(:enter) do + presence_client_one.unsubscribe :enter - channel_anonymous_client.attach do - presence_anonymous_client.get do |members| - expect(members.count).to eq(1) + channel_anonymous_client.attach do + presence_anonymous_client.get do |members| + expect(members.count).to eq(1) - presence_anonymous_client.subscribe { raise 'No presence events should be emitted' } - channel_anonymous_client.detach do - expect(presence_anonymous_client.members.length).to eq(0) - expect(channel_anonymous_client).to be_detached + presence_anonymous_client.subscribe { raise 'No presence events should be emitted' } + channel_anonymous_client.detach do + expect(presence_anonymous_client.members.length).to eq(0) + expect(channel_anonymous_client).to be_detached - expect(presence_client_one.members.local_members.count).to eq(1) - channel_client_one.detach do - expect(presence_client_one.members.local_members.count).to eq(0) - stop_reactor + expect(presence_client_one.members.local_members.count).to eq(1) + channel_client_one.detach do + expect(presence_client_one.members.local_members.count).to eq(0) + stop_reactor + end end end end end end