ably 1.1.4 → 1.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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