ably 1.1.4.rc → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +41 -0
  3. data/CHANGELOG.md +85 -0
  4. data/COPYRIGHT +1 -0
  5. data/LICENSE +173 -10
  6. data/MAINTAINERS.md +1 -0
  7. data/README.md +24 -18
  8. data/SPEC.md +1020 -922
  9. data/ably.gemspec +13 -8
  10. data/lib/ably.rb +1 -0
  11. data/lib/ably/agent.rb +3 -0
  12. data/lib/ably/auth.rb +12 -2
  13. data/lib/ably/exceptions.rb +6 -0
  14. data/lib/ably/models/connection_details.rb +2 -0
  15. data/lib/ably/models/message.rb +14 -0
  16. data/lib/ably/models/presence_message.rb +14 -0
  17. data/lib/ably/models/protocol_message.rb +8 -0
  18. data/lib/ably/modules/ably.rb +11 -1
  19. data/lib/ably/realtime/channel.rb +7 -11
  20. data/lib/ably/realtime/channel/channel_manager.rb +3 -3
  21. data/lib/ably/realtime/channel/channel_properties.rb +24 -0
  22. data/lib/ably/realtime/channel/publisher.rb +5 -0
  23. data/lib/ably/realtime/client.rb +9 -0
  24. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  25. data/lib/ably/realtime/connection.rb +9 -5
  26. data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  27. data/lib/ably/realtime/presence.rb +0 -14
  28. data/lib/ably/rest/channel.rb +10 -3
  29. data/lib/ably/rest/client.rb +22 -21
  30. data/lib/ably/version.rb +1 -13
  31. data/spec/acceptance/realtime/auth_spec.rb +1 -1
  32. data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  33. data/spec/acceptance/realtime/channel_spec.rb +24 -0
  34. data/spec/acceptance/realtime/client_spec.rb +72 -16
  35. data/spec/acceptance/realtime/connection_failures_spec.rb +29 -12
  36. data/spec/acceptance/realtime/connection_spec.rb +31 -33
  37. data/spec/acceptance/realtime/presence_history_spec.rb +3 -59
  38. data/spec/acceptance/realtime/presence_spec.rb +66 -157
  39. data/spec/acceptance/realtime/push_admin_spec.rb +3 -19
  40. data/spec/acceptance/rest/auth_spec.rb +6 -75
  41. data/spec/acceptance/rest/base_spec.rb +8 -4
  42. data/spec/acceptance/rest/channel_spec.rb +13 -0
  43. data/spec/acceptance/rest/client_spec.rb +144 -45
  44. data/spec/acceptance/rest/push_admin_spec.rb +3 -19
  45. data/spec/shared/client_initializer_behaviour.rb +131 -8
  46. data/spec/shared/model_behaviour.rb +1 -1
  47. data/spec/spec_helper.rb +12 -2
  48. data/spec/support/serialization_helper.rb +21 -0
  49. data/spec/unit/models/message_spec.rb +59 -0
  50. data/spec/unit/models/presence_message_spec.rb +49 -0
  51. data/spec/unit/models/protocol_message_spec.rb +48 -0
  52. data/spec/unit/realtime/channel_spec.rb +1 -1
  53. data/spec/unit/realtime/client_spec.rb +19 -6
  54. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  55. data/spec/unit/rest/channel_spec.rb +10 -0
  56. data/spec/unit/rest/client_spec.rb +20 -0
  57. metadata +52 -32
  58. data/.travis.yml +0 -18
@@ -77,18 +77,6 @@ describe Ably::Realtime::Connection, :event_machine do
77
77
  end
78
78
  end
79
79
  end
80
-
81
- context 'with implicit authorisation' do
82
- let(:client_options) { default_options.merge(client_id: 'force_token_auth') }
83
-
84
- it 'uses the token created by the implicit authorisation' do
85
- expect(client.rest_client.auth).to receive(:request_token).once.and_call_original
86
-
87
- connection.once(:connected) do
88
- stop_reactor
89
- end
90
- end
91
- end
92
80
  end
93
81
 
94
82
  context 'that expire' do
@@ -134,7 +122,7 @@ describe Ably::Realtime::Connection, :event_machine do
134
122
  end
135
123
  end
136
124
 
137
- context 'with immediately expired token' do
125
+ context 'with immediately expired token and no fallback hosts' do
138
126
  let(:ttl) { 0.001 }
139
127
  let(:auth_requests) { [] }
140
128
  let(:token_callback) do
@@ -143,7 +131,7 @@ describe Ably::Realtime::Connection, :event_machine do
143
131
  Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
144
132
  end
145
133
  end
146
- let(:client_options) { default_options.merge(auth_callback: token_callback) }
134
+ let(:client_options) { default_options.merge(auth_callback: token_callback, fallback_hosts: []) }
147
135
 
148
136
  it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token (#RSA4b)' do
149
137
  started_at = Time.now.to_f
@@ -158,7 +146,7 @@ describe Ably::Realtime::Connection, :event_machine do
158
146
  end
159
147
 
160
148
  context 'when disconnected_retry_timeout is 0.5 seconds' do
161
- let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback) }
149
+ let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback, fallback_hosts: []) }
162
150
 
163
151
  it 'renews the token on connect, and continues to attempt renew based on the retry schedule' do
164
152
  disconnect_count = 0
@@ -184,7 +172,7 @@ describe Ably::Realtime::Connection, :event_machine do
184
172
  end
185
173
 
186
174
  context 'using implicit token auth' do
187
- let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }) }
175
+ let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }, fallback_hosts: []) }
188
176
 
189
177
  before do
190
178
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', -10 # ensure client lib thinks token is still valid
@@ -453,7 +441,9 @@ describe Ably::Realtime::Connection, :event_machine do
453
441
  end
454
442
  end
455
443
 
456
- context '#connect' do
444
+ context '#connect with no fallbacks' do
445
+ let(:client_options) { default_options.merge(fallback_hosts: []) }
446
+
457
447
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
458
448
  expect(connection.connect).to be_a(Ably::Util::SafeDeferrable)
459
449
  stop_reactor
@@ -1179,6 +1169,7 @@ describe Ably::Realtime::Connection, :event_machine do
1179
1169
  host: 'this.host.does.not.exist.com'
1180
1170
  )
1181
1171
  )
1172
+ allow(client).to receive(:fallback_hosts).and_return([])
1182
1173
 
1183
1174
  connection.transition_state_machine! :disconnected
1184
1175
  end
@@ -1463,7 +1454,7 @@ describe Ably::Realtime::Connection, :event_machine do
1463
1454
  let(:client_options) { default_options.merge(tls: true) }
1464
1455
 
1465
1456
  it 'uses TLS for the Internet check to https://internet-up.ably-realtime.com/is-the-internet-up.txt' do
1466
- expect(EventMachine::HttpRequest).to receive(:new).with('https://internet-up.ably-realtime.com/is-the-internet-up.txt').and_return(http_request)
1457
+ expect(EventMachine::HttpRequest).to receive(:new).with('https://internet-up.ably-realtime.com/is-the-internet-up.txt', { tls: { verify_peer: true } }).and_return(http_request)
1467
1458
  connection.internet_up?
1468
1459
  stop_reactor
1469
1460
  end
@@ -1473,7 +1464,7 @@ describe Ably::Realtime::Connection, :event_machine do
1473
1464
  let(:client_options) { default_options.merge(tls: false, use_token_auth: true) }
1474
1465
 
1475
1466
  it 'uses TLS for the Internet check to http://internet-up.ably-realtime.com/is-the-internet-up.txt' do
1476
- expect(EventMachine::HttpRequest).to receive(:new).with('http://internet-up.ably-realtime.com/is-the-internet-up.txt').and_return(http_request)
1467
+ expect(EventMachine::HttpRequest).to receive(:new).with('http://internet-up.ably-realtime.com/is-the-internet-up.txt', { tls: { verify_peer: true } }).and_return(http_request)
1477
1468
  connection.internet_up?
1478
1469
  stop_reactor
1479
1470
  end
@@ -1487,7 +1478,7 @@ describe Ably::Realtime::Connection, :event_machine do
1487
1478
  let(:client_options) { default_options.merge(tls: true) }
1488
1479
 
1489
1480
  it 'checks the Internet up URL over TLS' do
1490
- expect(EventMachine::HttpRequest).to receive(:new).with("https:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1481
+ expect(EventMachine::HttpRequest).to receive(:new).with("https:#{Ably::INTERNET_CHECK.fetch(:url)}", { tls: { verify_peer: true } }).and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1491
1482
  connection.internet_up?
1492
1483
  stop_reactor
1493
1484
  end
@@ -1497,7 +1488,7 @@ describe Ably::Realtime::Connection, :event_machine do
1497
1488
  let(:client_options) { default_options.merge(tls: false, use_token_auth: true) }
1498
1489
 
1499
1490
  it 'checks the Internet up URL over TLS' do
1500
- expect(EventMachine::HttpRequest).to receive(:new).with("http:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1491
+ expect(EventMachine::HttpRequest).to receive(:new).with("http:#{Ably::INTERNET_CHECK.fetch(:url)}", { tls: { verify_peer: true } }).and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1501
1492
  connection.internet_up?
1502
1493
  stop_reactor
1503
1494
  end
@@ -1849,32 +1840,39 @@ describe Ably::Realtime::Connection, :event_machine do
1849
1840
  client
1850
1841
  end
1851
1842
 
1852
- it 'sends the lib version param lib (#RTN2g)' do
1843
+ it 'sends the lib version param agent (#RCS7d)' do
1853
1844
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1854
1845
  uri = URI.parse(url)
1855
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-1\.1\.\d+(-[\w\.]+)?+$/)
1846
+ expect(CGI::parse(uri.query)['agent'][0]).to match(/^ably-ruby\/\d\.\d\.\d ruby\/\d\.\d\.\d$/)
1856
1847
  stop_reactor
1857
1848
  end
1858
1849
  client
1859
1850
  end
1851
+ end
1860
1852
 
1861
- context 'with variant' do
1862
- let(:variant) { 'foo' }
1853
+ context 'transport_params (#RTC1f)' do
1854
+ let(:client_options) { default_options.merge(transport_params: { 'extra_param' => 'extra_param' }) }
1863
1855
 
1864
- before do
1865
- Ably.lib_variant = variant
1866
- end
1856
+ it 'pases transport_params to query' do
1857
+ expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1858
+ uri = URI.parse(url)
1859
+ expect(CGI::parse(uri.query)['extra_param'][0]).to eq('extra_param')
1860
+ stop_reactor
1861
+ end
1867
1862
 
1868
- after do
1869
- Ably.lib_variant = nil
1870
- end
1863
+ client
1864
+ end
1871
1865
 
1872
- it 'sends the lib version param lib with the variant (#RTN2g + #RSC7b)' do
1866
+ context 'when changing default param' do
1867
+ let(:client_options) { default_options.merge(transport_params: { v: '1.0' }) }
1868
+
1869
+ it 'overrides default param (#RTC1f1)' do
1873
1870
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1874
1871
  uri = URI.parse(url)
1875
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-#{variant}-1\.1\.\d+(-[\w\.]+)?$/)
1872
+ expect(CGI::parse(uri.query)['v'][0]).to eq('1.0')
1876
1873
  stop_reactor
1877
1874
  end
1875
+
1878
1876
  client
1879
1877
  end
1880
1878
  end
@@ -20,7 +20,7 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
20
20
 
21
21
  it 'provides up to the moment presence history' do
22
22
  presence_client_one.enter(data) do
23
- presence_client_one.leave(leave_data) do
23
+ presence_client_one.subscribe(:leave) do
24
24
  presence_client_one.history do |history_page|
25
25
  expect(history_page).to be_a(Ably::Models::PaginatedResult)
26
26
  expect(history_page.items.count).to eql(2)
@@ -36,6 +36,8 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
36
36
  stop_reactor
37
37
  end
38
38
  end
39
+
40
+ presence_client_one.leave(leave_data)
39
41
  end
40
42
  end
41
43
 
@@ -52,63 +54,5 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
52
54
 
53
55
  presence_client_one.enter(data)
54
56
  end
55
-
56
- context 'with option until_attach: true' do
57
- let(:event) { random_str }
58
- let(:presence_data_before_attach) { random_str }
59
- let(:presence_data_after_attach) { random_str }
60
-
61
- it 'retrieves all presence messages before channel was attached' do
62
- presence_client_two.enter(presence_data_before_attach) do
63
- presence_client_one.enter(presence_data_after_attach) do
64
- presence_client_one.history(until_attach: true) do |presence_page|
65
- expect(presence_page.items.count).to eql(1)
66
- expect(presence_page.items.first.data).to eql(presence_data_before_attach)
67
- stop_reactor
68
- end
69
- end
70
- end
71
- end
72
-
73
- context 'and two pages of messages' do
74
- let(:wildcard_token) { lambda { |token_params| Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*') } }
75
- let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(auth_callback: wildcard_token)) }
76
- let(:client_two) { auto_close Ably::Realtime::Client.new(default_options.merge(auth_callback: wildcard_token)) }
77
-
78
- # TODO: Remove retry logic when presence history regression fixed
79
- # https://github.com/ably/realtime/issues/1707
80
- #
81
- it 'retrieves two pages of messages before channel was attached', retry: 10, :retry_wait => 5 do
82
- when_all(*10.times.map { |i| presence_client_two.enter_client("client:#{i}", presence_data_before_attach) }) do
83
- when_all(*10.times.map { |i| presence_client_one.enter_client("client:#{i}", presence_data_after_attach) }) do
84
- presence_client_one.history(until_attach: true, limit: 5) do |presence_page|
85
- expect(presence_page.items.count).to eql(5)
86
- expect(presence_page.items.map(&:data).uniq.first).to eql(presence_data_before_attach)
87
-
88
- presence_page.next do |presence_next_page|
89
- expect(presence_next_page.items.count).to eql(5)
90
- expect(presence_next_page.items.map(&:data).uniq.first).to eql(presence_data_before_attach)
91
- if presence_next_page.has_next?
92
- presence_next_page.next do |last|
93
- expect(last.items.count).to eql(0)
94
- end
95
- else
96
- expect(presence_next_page).to be_last
97
- end
98
- stop_reactor
99
- end
100
- end
101
- end
102
- end
103
- end
104
- end
105
-
106
- it 'fails with an exception unless state is attached' do
107
- presence_client_one.history(until_attach: true).errback do |error|
108
- expect(error.message).to match(/not attached/)
109
- stop_reactor
110
- end
111
- end
112
- end
113
57
  end
114
58
  end
@@ -498,7 +498,9 @@ describe Ably::Realtime::Presence, :event_machine do
498
498
  end
499
499
 
500
500
  presence_client_one.enter do
501
- presence_client_one.leave
501
+ EventMachine.add_timer(0.5) do
502
+ presence_client_one.leave
503
+ end
502
504
  end
503
505
  end
504
506
  end
@@ -705,7 +707,7 @@ describe Ably::Realtime::Presence, :event_machine do
705
707
 
706
708
  context '#sync_complete? and SYNC flags (#RTP1)' do
707
709
  context 'when attaching to a channel without any members present' do
708
- it 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do
710
+ xit 'sync_complete? is true, there is no presence flag, and the presence channel is considered synced immediately (#RTP1)' do
709
711
  flag_checked = false
710
712
 
711
713
  anonymous_client.connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
@@ -1211,10 +1213,11 @@ describe Ably::Realtime::Presence, :event_machine do
1211
1213
  channel_client_one.attach do
1212
1214
  presence_client_one.subscribe(:enter) do
1213
1215
  presence_client_one.unsubscribe :enter
1214
-
1215
- expect(presence_client_one.members).to be_in_sync
1216
- expect(presence_client_one.members.send(:members).count).to eql(1)
1217
- presence_client_one.leave data
1216
+ EventMachine.add_timer(0.5) do
1217
+ expect(presence_client_one.members).to be_in_sync
1218
+ expect(presence_client_one.members.send(:members).count).to eql(1)
1219
+ presence_client_one.leave data
1220
+ end
1218
1221
  end
1219
1222
 
1220
1223
  presence_client_one.enter(enter_data) do
@@ -2137,16 +2140,18 @@ describe Ably::Realtime::Presence, :event_machine do
2137
2140
  end
2138
2141
 
2139
2142
  it 'emits an error when cipher does not match and presence data cannot be decoded' do
2140
- incompatible_encrypted_channel.attach do
2143
+ incompatible_encrypted_channel.once(:attached) do
2141
2144
  expect(client_two.logger).to receive(:error) do |*args, &block|
2142
2145
  expect(args.concat([block ? block.call : nil]).join(',')).to match(/Cipher algorithm AES-128-CBC does not match/)
2143
2146
  stop_reactor
2144
- end
2147
+ end.at_least(:once)
2145
2148
 
2146
2149
  encrypted_channel.attach do
2147
2150
  encrypted_channel.presence.enter data
2148
2151
  end
2149
2152
  end
2153
+
2154
+ incompatible_encrypted_channel.attach
2150
2155
  end
2151
2156
  end
2152
2157
  end
@@ -2449,173 +2454,77 @@ describe Ably::Realtime::Presence, :event_machine do
2449
2454
  end
2450
2455
  end
2451
2456
 
2452
- context 'when a channel becomes attached again' do
2453
- let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached.to_i }
2454
- let(:sync_action) { Ably::Models::ProtocolMessage::ACTION.Sync.to_i }
2455
- let(:presence_action) { Ably::Models::ProtocolMessage::ACTION.Presence.to_i }
2456
- let(:present_action) { Ably::Models::PresenceMessage::ACTION.Present.to_i }
2457
- let(:resume_flag) { 4 }
2458
- let(:presence_flag) { 1 }
2459
-
2460
- def fabricate_incoming_protocol_message(protocol_message)
2461
- client_one.connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
2462
- end
2463
-
2464
- # Prevents any messages from the WebSocket transport being sent / received
2465
- # Connection protocol message subscriptions are still active, but nothing reaches or comes from the WebSocket transport
2466
- def cripple_websocket_transport
2467
- client_one.connection.transport.__incoming_protocol_msgbus__.unsubscribe
2468
- client_one.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
2469
- end
2470
-
2471
- context 'and the resume flag is true' do
2472
- context 'and the presence flag is false' do
2473
- it 'does not send any presence events as the PresenceMap is in sync (#RTP5c1)' do
2474
- presence_client_one.enter
2475
- presence_client_one.subscribe(:enter) do
2476
- presence_client_one.unsubscribe :enter
2457
+ describe '#RTP17b' do
2458
+ let(:leave_action) { Ably::Models::PresenceMessage::ACTION.Leave }
2477
2459
 
2478
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2479
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2460
+ it 'updates presence members on leave' do
2461
+ presence_client_two.subscribe(:enter) do
2462
+ channel_anonymous_client.attach do
2463
+ channel_anonymous_client.presence.get do |members|
2464
+ presence_client_two.subscribe(:leave) do
2465
+ expect(presence_client_two.members.local_members).to be_empty
2466
+ stop_reactor
2480
2467
  end
2481
2468
 
2482
- cripple_websocket_transport
2483
-
2484
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2485
- action: attached_action,
2486
- channel: channel_name,
2487
- flags: resume_flag
2469
+ leave_message = Ably::Models::PresenceMessage.new(
2470
+ 'id' => "#{client_two.connection.id}:#{presence_client_two.client_id}:1",
2471
+ 'clientId' => presence_client_two.client_id,
2472
+ 'connectionId' => client_two.connection.id,
2473
+ 'timestamp' => as_since_epoch(Time.now),
2474
+ 'action' => leave_action
2488
2475
  )
2489
2476
 
2490
- EventMachine.add_timer(1) do
2491
- presence_client_one.get do |members|
2492
- expect(members.length).to eql(1)
2493
- expect(presence_client_one.members.local_members.length).to eql(1)
2494
- stop_reactor
2495
- end
2496
- end
2477
+ presence_client_two.__incoming_msgbus__.publish :presence, leave_message
2497
2478
  end
2498
2479
  end
2499
2480
  end
2500
2481
 
2501
- context 'and the presence flag is true' do
2502
- context 'and following the SYNC all local MemberMap members are present in the PresenceMap' do
2503
- it 'does nothing as MemberMap is in sync (#RTP5c2)' do
2504
- presence_client_one.enter
2505
- presence_client_one.subscribe(:enter) do
2506
- presence_client_one.unsubscribe :enter
2507
-
2508
- expect(presence_client_one.members.length).to eql(1)
2509
- expect(presence_client_one.members.local_members.length).to eql(1)
2510
-
2511
- presence_client_one.members.once(:in_sync) do
2512
- presence_client_one.get do |members|
2513
- expect(members.length).to eql(1)
2514
- expect(presence_client_one.members.local_members.length).to eql(1)
2515
- stop_reactor
2516
- end
2517
- end
2518
-
2519
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2520
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2521
- end
2482
+ presence_client_two.enter
2483
+ end
2522
2484
 
2523
- cripple_websocket_transport
2485
+ it 'does no update presence members on fabricated leave' do
2486
+ presence_client_two.subscribe(:enter) do
2487
+ channel_anonymous_client.attach do
2488
+ channel_anonymous_client.presence.get do |members|
2489
+ presence_client_two.subscribe(:leave) do
2490
+ expect(presence_client_two.members.local_members).to_not be_empty
2491
+ stop_reactor
2492
+ end
2524
2493
 
2525
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2526
- action: attached_action,
2527
- channel: channel_name,
2528
- flags: resume_flag + presence_flag
2529
- )
2494
+ fabricated_leave_message = Ably::Models::PresenceMessage.new(
2495
+ 'id' => "#{client_two.connection.id}:#{presence_client_two.client_id}:1",
2496
+ 'clientId' => presence_client_two.client_id,
2497
+ 'connectionId' => "fabricated:#{presence_client_two.client_id}:0",
2498
+ 'timestamp' => as_since_epoch(Time.now),
2499
+ 'action' => leave_action
2500
+ )
2530
2501
 
2531
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2532
- action: sync_action,
2533
- channel: channel_name,
2534
- presence: presence_client_one.members.map(&:shallow_clone).map(&:as_json),
2535
- channelSerial: nil # no further SYNC messages expected
2536
- )
2537
- end
2502
+ presence_client_two.__incoming_msgbus__.publish :presence, fabricated_leave_message
2538
2503
  end
2539
2504
  end
2505
+ end
2540
2506
 
2541
- context 'and following the SYNC a local MemberMap member is not present in the PresenceMap' do
2542
- it 're-enters the missing members automatically (#RTP5c2)' do
2543
- sync_check_completed = false
2544
-
2545
- presence_client_one.enter
2546
- presence_client_one.subscribe(:enter) do
2547
- presence_client_one.unsubscribe :enter
2548
-
2549
- expect(presence_client_one.members.length).to eql(1)
2550
- expect(presence_client_one.members.local_members.length).to eql(1)
2551
-
2552
- client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |message|
2553
- next if message.action == :close # ignore finalization of connection
2554
-
2555
- expect(message.action).to eq(:presence)
2556
- presence_message = message.presence.first
2557
- expect(presence_message.action).to eq(:enter)
2558
- expect(presence_message.client_id).to eq(client_one.auth.client_id)
2559
-
2560
- presence_client_one.subscribe(:enter) do |message|
2561
- expect(message.connection_id).to eql(client_one.connection.id)
2562
- expect(message.client_id).to eq(client_one.auth.client_id)
2563
-
2564
- EventMachine.next_tick do
2565
- expect(presence_client_one.members.length).to eql(2)
2566
- expect(presence_client_one.members.local_members.length).to eql(1)
2567
- expect(sync_check_completed).to be_truthy
2568
- stop_reactor
2569
- end
2570
- end
2571
-
2572
- # Fabricate Ably sending back the Enter PresenceMessage to the client a short while after
2573
- # ensuring the PresenceMap for a short period does not have this member as to be expected in reality
2574
- EventMachine.add_timer(0.2) do
2575
- connection_id = random_str
2576
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2577
- action: presence_action,
2578
- channel: channel_name,
2579
- connectionId: client_one.connection.id,
2580
- connectionSerial: 50,
2581
- timestamp: as_since_epoch(Time.now),
2582
- presence: [presence_message.shallow_clone(id: "#{client_one.connection.id}:0:0", timestamp: as_since_epoch(Time.now)).as_json]
2583
- )
2584
- end
2585
- end
2586
-
2587
- presence_client_one.members.once(:in_sync) do
2588
- # For a brief period, the client will have re-entered the missing members from the local_members
2589
- # but the enter from Ably will have not been received, so at this point the local_members will be empty
2590
- presence_client_one.get do |members|
2591
- expect(members.length).to eql(1)
2592
- expect(members.first.connection_id).to_not eql(client_one.connection.id)
2593
- expect(presence_client_one.members.local_members.length).to eql(0)
2594
- sync_check_completed = true
2595
- end
2596
- end
2507
+ presence_client_two.enter
2508
+ end
2509
+ end
2597
2510
 
2598
- cripple_websocket_transport
2511
+ context 'when a channel becomes attached again' do
2512
+ let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached.to_i }
2513
+ let(:sync_action) { Ably::Models::ProtocolMessage::ACTION.Sync.to_i }
2514
+ let(:presence_action) { Ably::Models::ProtocolMessage::ACTION.Presence.to_i }
2515
+ let(:present_action) { Ably::Models::PresenceMessage::ACTION.Present.to_i }
2516
+ let(:resume_flag) { 4 }
2517
+ let(:presence_flag) { 1 }
2599
2518
 
2600
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2601
- action: attached_action,
2602
- channel: channel_name,
2603
- flags: resume_flag + presence_flag
2604
- )
2519
+ def fabricate_incoming_protocol_message(protocol_message)
2520
+ client_one.connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
2521
+ end
2605
2522
 
2606
- # Complete the SYNC but without the member who was entered by this client
2607
- connection_id = random_str
2608
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2609
- action: sync_action,
2610
- channel: channel_name,
2611
- timestamp: as_since_epoch(Time.now),
2612
- presence: [{ id: "#{connection_id}:0:0", action: present_action, connection_id: connection_id, client_id: random_str }],
2613
- chanenlSerial: nil # no further SYNC messages expected
2614
- )
2615
- end
2616
- end
2617
- end
2618
- end
2523
+ # Prevents any messages from the WebSocket transport being sent / received
2524
+ # Connection protocol message subscriptions are still active, but nothing reaches or comes from the WebSocket transport
2525
+ def cripple_websocket_transport
2526
+ client_one.connection.transport.__incoming_protocol_msgbus__.unsubscribe
2527
+ client_one.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
2619
2528
  end
2620
2529
 
2621
2530
  context 'and the resume flag is false' do