ably 1.1.4 → 1.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +15 -1
  3. data/CHANGELOG.md +109 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +23 -9
  6. data/SPEC.md +289 -228
  7. data/ably.gemspec +14 -9
  8. data/lib/ably/agent.rb +3 -0
  9. data/lib/ably/exceptions.rb +6 -0
  10. data/lib/ably/models/connection_details.rb +8 -0
  11. data/lib/ably/models/delta_extras.rb +29 -0
  12. data/lib/ably/models/error_info.rb +6 -2
  13. data/lib/ably/models/message.rb +25 -0
  14. data/lib/ably/models/presence_message.rb +14 -0
  15. data/lib/ably/models/protocol_message.rb +13 -8
  16. data/lib/ably/modules/ably.rb +11 -1
  17. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  18. data/lib/ably/realtime/channel/channel_state_machine.rb +5 -1
  19. data/lib/ably/realtime/channel/publisher.rb +6 -0
  20. data/lib/ably/realtime/channel.rb +2 -0
  21. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  22. data/lib/ably/realtime/client.rb +1 -0
  23. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  24. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  25. data/lib/ably/realtime/connection.rb +2 -2
  26. data/lib/ably/rest/channel.rb +11 -3
  27. data/lib/ably/rest/client.rb +37 -18
  28. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  29. data/lib/ably/version.rb +1 -13
  30. data/lib/ably.rb +1 -0
  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 +220 -1
  34. data/spec/acceptance/realtime/connection_failures_spec.rb +85 -13
  35. data/spec/acceptance/realtime/connection_spec.rb +263 -32
  36. data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
  37. data/spec/acceptance/realtime/presence_spec.rb +31 -159
  38. data/spec/acceptance/rest/base_spec.rb +8 -4
  39. data/spec/acceptance/rest/channel_spec.rb +84 -9
  40. data/spec/acceptance/rest/channels_spec.rb +1 -1
  41. data/spec/acceptance/rest/client_spec.rb +72 -33
  42. data/spec/shared/client_initializer_behaviour.rb +131 -0
  43. data/spec/shared/model_behaviour.rb +1 -1
  44. data/spec/spec_helper.rb +11 -2
  45. data/spec/support/test_app.rb +1 -1
  46. data/spec/unit/models/delta_extras_spec.rb +14 -0
  47. data/spec/unit/models/error_info_spec.rb +17 -1
  48. data/spec/unit/models/message_spec.rb +83 -0
  49. data/spec/unit/models/presence_message_spec.rb +49 -0
  50. data/spec/unit/models/protocol_message_spec.rb +72 -20
  51. data/spec/unit/realtime/channel_spec.rb +3 -2
  52. data/spec/unit/realtime/channels_spec.rb +3 -3
  53. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  54. data/spec/unit/rest/channel_spec.rb +44 -1
  55. data/spec/unit/rest/client_spec.rb +47 -0
  56. metadata +48 -36
@@ -122,7 +122,7 @@ describe Ably::Realtime::Connection, :event_machine do
122
122
  end
123
123
  end
124
124
 
125
- context 'with immediately expired token' do
125
+ context 'with immediately expired token and no fallback hosts' do
126
126
  let(:ttl) { 0.001 }
127
127
  let(:auth_requests) { [] }
128
128
  let(:token_callback) do
@@ -131,7 +131,7 @@ describe Ably::Realtime::Connection, :event_machine do
131
131
  Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
132
132
  end
133
133
  end
134
- let(:client_options) { default_options.merge(auth_callback: token_callback) }
134
+ let(:client_options) { default_options.merge(auth_callback: token_callback, fallback_hosts: []) }
135
135
 
136
136
  it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token (#RSA4b)' do
137
137
  started_at = Time.now.to_f
@@ -146,7 +146,7 @@ describe Ably::Realtime::Connection, :event_machine do
146
146
  end
147
147
 
148
148
  context 'when disconnected_retry_timeout is 0.5 seconds' do
149
- 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: []) }
150
150
 
151
151
  it 'renews the token on connect, and continues to attempt renew based on the retry schedule' do
152
152
  disconnect_count = 0
@@ -172,7 +172,7 @@ describe Ably::Realtime::Connection, :event_machine do
172
172
  end
173
173
 
174
174
  context 'using implicit token auth' do
175
- 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: []) }
176
176
 
177
177
  before do
178
178
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', -10 # ensure client lib thinks token is still valid
@@ -441,7 +441,9 @@ describe Ably::Realtime::Connection, :event_machine do
441
441
  end
442
442
  end
443
443
 
444
- context '#connect' do
444
+ context '#connect with no fallbacks' do
445
+ let(:client_options) { default_options.merge(fallback_hosts: []) }
446
+
445
447
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
446
448
  expect(connection.connect).to be_a(Ably::Util::SafeDeferrable)
447
449
  stop_reactor
@@ -480,6 +482,118 @@ describe Ably::Realtime::Connection, :event_machine do
480
482
  end
481
483
  end
482
484
 
485
+ context 'when explicitly reconnecting disconnected/suspended connection in retry (#RTN11c)' do
486
+ let(:close_connection_proc) do
487
+ lambda do
488
+ EventMachine.add_timer(0.001) do
489
+ if connection.transport.nil?
490
+ close_connection_proc.call
491
+ else
492
+ connection.transport.close_connection_after_writing
493
+ end
494
+ end
495
+ end
496
+ end
497
+
498
+ context 'when suspended' do
499
+ let(:suspended_retry_timeout) { 60 }
500
+ let(:client_options) do
501
+ default_options.merge(
502
+ disconnected_retry_timeout: 0.02,
503
+ suspended_retry_timeout: suspended_retry_timeout,
504
+ connection_state_ttl: 0
505
+ )
506
+ end
507
+
508
+ it 'reconnects immediately' do
509
+ connection.once(:connecting) { close_connection_proc.call }
510
+
511
+ connection.on(:suspended) do |connection_state_change|
512
+ if connection_state_change.retry_in.zero?
513
+ # Exhausting immediate retries
514
+ connection.once(:connecting) { close_connection_proc.call }
515
+ else
516
+ suspended_at = Time.now.to_f
517
+ connection.on(:connected) do
518
+ expect(connection_state_change.retry_in).to eq(suspended_retry_timeout)
519
+ expect(connection.state).to eq(:connected)
520
+ expect(Time.now.to_f).to be_within(4).of(suspended_at)
521
+ stop_reactor
522
+ end
523
+ end
524
+
525
+ connection.connect
526
+ end
527
+
528
+ connection.connect
529
+ end
530
+ end
531
+
532
+ context 'when disconnected' do
533
+ let(:retry_timeout) { 60 }
534
+ let(:client_options) do
535
+ default_options.merge(
536
+ disconnected_retry_timeout: retry_timeout,
537
+ suspended_retry_timeout: retry_timeout,
538
+ connection_state_ttl: 0
539
+ )
540
+ end
541
+
542
+ it 'reconnects immediately' do
543
+ connection.once(:connected) do
544
+ connection.on(:disconnected) do |connection_state_change|
545
+ disconnected_at = Time.now.to_f
546
+ connection.on(:connected) do
547
+ if connection_state_change.retry_in.zero?
548
+ # Exhausting immediate retries
549
+ close_connection_proc.call
550
+ else
551
+ expect(connection_state_change.retry_in).to eq(retry_timeout)
552
+ expect(connection.state).to eq(:connected)
553
+ expect(Time.now.to_f).to be_within(4).of(disconnected_at)
554
+ stop_reactor
555
+ end
556
+ end
557
+ connection.connect
558
+ end
559
+
560
+ close_connection_proc.call
561
+ end
562
+
563
+ connection.connect
564
+ end
565
+ end
566
+ end
567
+
568
+ context 'when reconnecting a failed connection' do
569
+ let(:channel) { client.channel(random_str) }
570
+ let(:client_options) { default_options.merge(log_level: :none) }
571
+
572
+ it 'transitions all channels state to initialized and cleares error_reason' do
573
+ connection.on(:failed) do |connection_state_change|
574
+ expect(connection.error_reason).to be_a(Ably::Exceptions::BaseAblyException)
575
+ expect(channel.error_reason).to be_a(Ably::Exceptions::BaseAblyException)
576
+ expect(channel).to be_failed
577
+
578
+ connection.on(:connected) do
579
+ expect(connection.error_reason).to eq(nil)
580
+ expect(channel).to be_initialized
581
+ expect(channel.error_reason).to eq(nil)
582
+ stop_reactor
583
+ end
584
+
585
+ connection.connect
586
+ end
587
+
588
+ connection.connect do
589
+ channel.attach do
590
+ error = Ably::Exceptions::ConnectionFailed.new('forced failure', 500, 50000)
591
+ client.connection.manager.error_received_from_server error
592
+ end
593
+ end
594
+ end
595
+ end
596
+
483
597
  context 'with invalid auth details' do
484
598
  let(:client_options) { default_options.merge(key: 'this.is:invalid', log_level: :none) }
485
599
 
@@ -691,6 +805,18 @@ describe Ably::Realtime::Connection, :event_machine do
691
805
  end
692
806
 
693
807
  context '#close' do
808
+ let(:close_connection_proc) do
809
+ lambda do
810
+ EventMachine.add_timer(0.001) do
811
+ if connection.transport.nil?
812
+ close_connection_proc.call
813
+ else
814
+ connection.transport.close_connection_after_writing
815
+ end
816
+ end
817
+ end
818
+ end
819
+
694
820
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
695
821
  connection.connect do
696
822
  expect(connection.close).to be_a(Ably::Util::SafeDeferrable)
@@ -752,6 +878,56 @@ describe Ably::Realtime::Connection, :event_machine do
752
878
  end
753
879
  end
754
880
 
881
+ context ':connecting RTN12f' do
882
+ context ":connected does not arrive when trying to close" do
883
+ it 'moves to closed' do
884
+ connection.on(:closed) do |state_change|
885
+ state_changes = connection.state_history.map { |t| t[:state].to_sym }
886
+
887
+ expect(state_changes).to eq([:connecting, :closing, :closed])
888
+ stop_reactor
889
+ end
890
+
891
+ connection.on(:connecting) do
892
+ connection.close
893
+ connection.__outgoing_protocol_msgbus__.unsubscribe
894
+ end
895
+
896
+ connection.connect
897
+ end
898
+ end
899
+
900
+ context ":connected arrive when trying to close" do
901
+ let(:protocol_message_attributes) do
902
+ {
903
+ action: Ably::Models::ProtocolMessage::ACTION.Connected.to_i,
904
+ connection_serial: 55,
905
+ connection_details: {
906
+ max_idle_interval: 2 * 1000
907
+ }
908
+ }
909
+ end
910
+
911
+ it 'moves to connected and then to closed' do
912
+ connection.on(:closed) do |state_change|
913
+ state_changes = connection.state_history.map { |t| t[:state].to_sym }
914
+
915
+ expect(state_changes).to eq([:connecting, :connected, :closing, :closed])
916
+ stop_reactor
917
+ end
918
+
919
+ connection.on(:connecting) do
920
+ connection.__outgoing_protocol_msgbus__.unsubscribe
921
+
922
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
923
+ connection.close
924
+ end
925
+
926
+ connection.connect
927
+ end
928
+ end
929
+ end
930
+
755
931
  context ':connected' do
756
932
  it 'changes the connection state to :closing and waits for the server to confirm connection is :closed with a ProtocolMessage' do
757
933
  connection.on(:connected) do
@@ -798,6 +974,81 @@ describe Ably::Realtime::Connection, :event_machine do
798
974
  end
799
975
  end
800
976
  end
977
+
978
+ context ':suspended RTN12d' do
979
+ let(:suspended_retry_timeout) { 60 }
980
+ let(:client_options) do
981
+ default_options.merge(
982
+ disconnected_retry_timeout: 0.02,
983
+ suspended_retry_timeout: suspended_retry_timeout,
984
+ connection_state_ttl: 0
985
+ )
986
+ end
987
+
988
+ it 'immediatly closes connection' do
989
+ connection.on(:connecting) { close_connection_proc.call }
990
+ connection.on(:suspended) do |connection_state_change|
991
+ if connection_state_change.retry_in.zero?
992
+ # Exhausting immediate retries
993
+ connection.once(:connecting) { close_connection_proc.call }
994
+ else
995
+ suspended_at = Time.now.to_f
996
+ connection.on(:closed) do
997
+ expect(connection_state_change.retry_in).to eq(suspended_retry_timeout)
998
+ expect(connection.state).to eq(:closed)
999
+ expect(Time.now.to_f).to be_within(4).of(suspended_at)
1000
+ stop_reactor
1001
+ end
1002
+
1003
+ connection.close
1004
+ end
1005
+
1006
+ connection.connect
1007
+ end
1008
+
1009
+ connection.connect
1010
+ end
1011
+ end
1012
+
1013
+ context ':disconnected RTN12d' do
1014
+ let(:retry_timeout) { 60 }
1015
+ let(:client_options) do
1016
+ default_options.merge(
1017
+ disconnected_retry_timeout: retry_timeout,
1018
+ suspended_retry_timeout: retry_timeout,
1019
+ connection_state_ttl: 0
1020
+ )
1021
+ end
1022
+
1023
+ it 'immediatly closes connection' do
1024
+ connection.once(:connected) do
1025
+ connection.on(:disconnected) do |connection_state_change|
1026
+ disconnected_at = Time.now.to_f
1027
+ connection.on(:connected) do
1028
+ if connection_state_change.retry_in.zero?
1029
+ # Exhausting immediate retries
1030
+ close_connection_proc.call
1031
+ else
1032
+ connection.once(:closed) do
1033
+ expect(connection_state_change.retry_in).to eq(retry_timeout)
1034
+ expect(connection.state).to eq(:closed)
1035
+ expect(Time.now.to_f).to be_within(4).of(disconnected_at)
1036
+ stop_reactor
1037
+ end
1038
+
1039
+ connection.close
1040
+ end
1041
+ end
1042
+
1043
+ connection.connect
1044
+ end
1045
+
1046
+ close_connection_proc.call
1047
+ end
1048
+
1049
+ connection.connect
1050
+ end
1051
+ end
801
1052
  end
802
1053
  end
803
1054
 
@@ -1167,6 +1418,7 @@ describe Ably::Realtime::Connection, :event_machine do
1167
1418
  host: 'this.host.does.not.exist.com'
1168
1419
  )
1169
1420
  )
1421
+ allow(client).to receive(:fallback_hosts).and_return([])
1170
1422
 
1171
1423
  connection.transition_state_machine! :disconnected
1172
1424
  end
@@ -1451,7 +1703,7 @@ describe Ably::Realtime::Connection, :event_machine do
1451
1703
  let(:client_options) { default_options.merge(tls: true) }
1452
1704
 
1453
1705
  it 'uses TLS for the Internet check to https://internet-up.ably-realtime.com/is-the-internet-up.txt' do
1454
- expect(EventMachine::HttpRequest).to receive(:new).with('https://internet-up.ably-realtime.com/is-the-internet-up.txt').and_return(http_request)
1706
+ 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)
1455
1707
  connection.internet_up?
1456
1708
  stop_reactor
1457
1709
  end
@@ -1461,7 +1713,7 @@ describe Ably::Realtime::Connection, :event_machine do
1461
1713
  let(:client_options) { default_options.merge(tls: false, use_token_auth: true) }
1462
1714
 
1463
1715
  it 'uses TLS for the Internet check to http://internet-up.ably-realtime.com/is-the-internet-up.txt' do
1464
- expect(EventMachine::HttpRequest).to receive(:new).with('http://internet-up.ably-realtime.com/is-the-internet-up.txt').and_return(http_request)
1716
+ 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)
1465
1717
  connection.internet_up?
1466
1718
  stop_reactor
1467
1719
  end
@@ -1475,7 +1727,7 @@ describe Ably::Realtime::Connection, :event_machine do
1475
1727
  let(:client_options) { default_options.merge(tls: true) }
1476
1728
 
1477
1729
  it 'checks the Internet up URL over TLS' do
1478
- expect(EventMachine::HttpRequest).to receive(:new).with("https:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1730
+ 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))
1479
1731
  connection.internet_up?
1480
1732
  stop_reactor
1481
1733
  end
@@ -1485,7 +1737,7 @@ describe Ably::Realtime::Connection, :event_machine do
1485
1737
  let(:client_options) { default_options.merge(tls: false, use_token_auth: true) }
1486
1738
 
1487
1739
  it 'checks the Internet up URL over TLS' do
1488
- expect(EventMachine::HttpRequest).to receive(:new).with("http:#{Ably::INTERNET_CHECK.fetch(:url)}").and_return(double('request', get: EventMachine::DefaultDeferrable.new))
1740
+ 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))
1489
1741
  connection.internet_up?
1490
1742
  stop_reactor
1491
1743
  end
@@ -1837,35 +2089,14 @@ describe Ably::Realtime::Connection, :event_machine do
1837
2089
  client
1838
2090
  end
1839
2091
 
1840
- it 'sends the lib version param lib (#RTN2g)' do
2092
+ it 'sends the lib version param agent (#RCS7d)' do
1841
2093
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1842
2094
  uri = URI.parse(url)
1843
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-1\.1\.\d+(-[\w\.]+)?+$/)
2095
+ expect(CGI::parse(uri.query)['agent'][0]).to match(/^ably-ruby\/\d\.\d\.\d ruby\/\d\.\d\.\d$/)
1844
2096
  stop_reactor
1845
2097
  end
1846
2098
  client
1847
2099
  end
1848
-
1849
- context 'with variant' do
1850
- let(:variant) { 'foo' }
1851
-
1852
- before do
1853
- Ably.lib_variant = variant
1854
- end
1855
-
1856
- after do
1857
- Ably.lib_variant = nil
1858
- end
1859
-
1860
- it 'sends the lib version param lib with the variant (#RTN2g + #RSC7b)' do
1861
- expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1862
- uri = URI.parse(url)
1863
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-#{variant}-1\.1\.\d+(-[\w\.]+)?$/)
1864
- stop_reactor
1865
- end
1866
- client
1867
- end
1868
- end
1869
2100
  end
1870
2101
 
1871
2102
  context 'transport_params (#RTC1f)' do
@@ -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
 
@@ -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
@@ -2522,156 +2527,6 @@ describe Ably::Realtime::Presence, :event_machine do
2522
2527
  client_one.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
2523
2528
  end
2524
2529
 
2525
- context 'and the resume flag is true' do
2526
- context 'and the presence flag is false' do
2527
- it 'does not send any presence events as the PresenceMap is in sync (#RTP5c1)' do
2528
- presence_client_one.enter
2529
- presence_client_one.subscribe(:enter) do
2530
- presence_client_one.unsubscribe :enter
2531
-
2532
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2533
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2534
- end
2535
-
2536
- cripple_websocket_transport
2537
-
2538
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2539
- action: attached_action,
2540
- channel: channel_name,
2541
- flags: resume_flag
2542
- )
2543
-
2544
- EventMachine.add_timer(1) do
2545
- presence_client_one.get do |members|
2546
- expect(members.length).to eql(1)
2547
- expect(presence_client_one.members.local_members.length).to eql(1)
2548
- stop_reactor
2549
- end
2550
- end
2551
- end
2552
- end
2553
- end
2554
-
2555
- context 'and the presence flag is true' do
2556
- context 'and following the SYNC all local MemberMap members are present in the PresenceMap' do
2557
- it 'does nothing as MemberMap is in sync (#RTP5c2)' do
2558
- presence_client_one.enter
2559
- presence_client_one.subscribe(:enter) do
2560
- presence_client_one.unsubscribe :enter
2561
-
2562
- expect(presence_client_one.members.length).to eql(1)
2563
- expect(presence_client_one.members.local_members.length).to eql(1)
2564
-
2565
- presence_client_one.members.once(:in_sync) do
2566
- presence_client_one.get do |members|
2567
- expect(members.length).to eql(1)
2568
- expect(presence_client_one.members.local_members.length).to eql(1)
2569
- stop_reactor
2570
- end
2571
- end
2572
-
2573
- client_one.connection.transport.__outgoing_protocol_msgbus__.subscribe do |message|
2574
- raise "No presence state updates to Ably are expected. Message sent: #{message.to_json}" if client_one.connection.connected?
2575
- end
2576
-
2577
- cripple_websocket_transport
2578
-
2579
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2580
- action: attached_action,
2581
- channel: channel_name,
2582
- flags: resume_flag + presence_flag
2583
- )
2584
-
2585
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2586
- action: sync_action,
2587
- channel: channel_name,
2588
- presence: presence_client_one.members.map(&:shallow_clone).map(&:as_json),
2589
- channelSerial: nil # no further SYNC messages expected
2590
- )
2591
- end
2592
- end
2593
- end
2594
-
2595
- context 'and following the SYNC a local MemberMap member is not present in the PresenceMap' do
2596
- it 're-enters the missing members automatically (#RTP5c2)' do
2597
- sync_check_completed = false
2598
-
2599
- presence_client_one.enter
2600
- presence_client_one.subscribe(:enter) do
2601
- presence_client_one.unsubscribe :enter
2602
-
2603
- expect(presence_client_one.members.length).to eql(1)
2604
- expect(presence_client_one.members.local_members.length).to eql(1)
2605
-
2606
- client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |message|
2607
- next if message.action == :close # ignore finalization of connection
2608
-
2609
- expect(message.action).to eq(:presence)
2610
- presence_message = message.presence.first
2611
- expect(presence_message.action).to eq(:enter)
2612
- expect(presence_message.client_id).to eq(client_one.auth.client_id)
2613
-
2614
- presence_client_one.subscribe(:enter) do |message|
2615
- expect(message.connection_id).to eql(client_one.connection.id)
2616
- expect(message.client_id).to eq(client_one.auth.client_id)
2617
-
2618
- EventMachine.next_tick do
2619
- expect(presence_client_one.members.length).to eql(2)
2620
- expect(presence_client_one.members.local_members.length).to eql(1)
2621
- expect(sync_check_completed).to be_truthy
2622
- stop_reactor
2623
- end
2624
- end
2625
-
2626
- # Fabricate Ably sending back the Enter PresenceMessage to the client a short while after
2627
- # ensuring the PresenceMap for a short period does not have this member as to be expected in reality
2628
- EventMachine.add_timer(0.2) do
2629
- connection_id = random_str
2630
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2631
- action: presence_action,
2632
- channel: channel_name,
2633
- connectionId: client_one.connection.id,
2634
- connectionSerial: 50,
2635
- timestamp: as_since_epoch(Time.now),
2636
- presence: [presence_message.shallow_clone(id: "#{client_one.connection.id}:0:0", timestamp: as_since_epoch(Time.now)).as_json]
2637
- )
2638
- end
2639
- end
2640
-
2641
- presence_client_one.members.once(:in_sync) do
2642
- # For a brief period, the client will have re-entered the missing members from the local_members
2643
- # but the enter from Ably will have not been received, so at this point the local_members will be empty
2644
- presence_client_one.get do |members|
2645
- expect(members.length).to eql(1)
2646
- expect(members.first.connection_id).to_not eql(client_one.connection.id)
2647
- expect(presence_client_one.members.local_members.length).to eql(0)
2648
- sync_check_completed = true
2649
- end
2650
- end
2651
-
2652
- cripple_websocket_transport
2653
-
2654
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2655
- action: attached_action,
2656
- channel: channel_name,
2657
- flags: resume_flag + presence_flag
2658
- )
2659
-
2660
- # Complete the SYNC but without the member who was entered by this client
2661
- connection_id = random_str
2662
- fabricate_incoming_protocol_message Ably::Models::ProtocolMessage.new(
2663
- action: sync_action,
2664
- channel: channel_name,
2665
- timestamp: as_since_epoch(Time.now),
2666
- presence: [{ id: "#{connection_id}:0:0", action: present_action, connection_id: connection_id, client_id: random_str }],
2667
- chanenlSerial: nil # no further SYNC messages expected
2668
- )
2669
- end
2670
- end
2671
- end
2672
- end
2673
- end
2674
-
2675
2530
  context 'and the resume flag is false' do
2676
2531
  context 'and the presence flag is false' do
2677
2532
  let(:member_data) { random_str }
@@ -2779,7 +2634,24 @@ describe Ably::Realtime::Presence, :event_machine do
2779
2634
  end
2780
2635
  end
2781
2636
 
2782
- context 'channel state side effects' do
2637
+ context 'channel state side effects (RTP5)' do
2638
+ context 'channel transitions to the ATTACHED state (RTP5b)' do
2639
+ it 'all queued presence messages are sent' do
2640
+ channel_client_one.on(:attached) do
2641
+ client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
2642
+ if protocol_message.action == :presence
2643
+ expect(protocol_message.action).to eq(:presence)
2644
+ stop_reactor
2645
+ end
2646
+ end
2647
+ end
2648
+
2649
+ presence_client_one.enter do
2650
+ channel_client_one.attach
2651
+ end
2652
+ end
2653
+ end
2654
+
2783
2655
  context 'channel transitions to the FAILED state' do
2784
2656
  let(:anonymous_client) { auto_close Ably::Realtime::Client.new(client_options.merge(log_level: :fatal)) }
2785
2657
  let(:client_one) { auto_close Ably::Realtime::Client.new(client_options.merge(client_id: client_one_id, log_level: :fatal)) }
@@ -87,8 +87,10 @@ describe Ably::Rest do
87
87
  let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
88
88
 
89
89
  before do
90
- stub_request(:get, "#{client.endpoint}/time").
91
- to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
90
+ (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
91
+ stub_request(:get, "#{host}/time")
92
+ .to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
93
+ end
92
94
  end
93
95
 
94
96
  it 'should raise a ServerError exception' do
@@ -98,8 +100,10 @@ describe Ably::Rest do
98
100
 
99
101
  describe '500 server error without a valid JSON response body', :webmock do
100
102
  before do
101
- stub_request(:get, "#{client.endpoint}/time").
102
- to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
103
+ (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
104
+ stub_request(:get, "#{host}/time").
105
+ to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
106
+ end
103
107
  end
104
108
 
105
109
  it 'should raise a ServerError exception' do