ably-rest 1.0.5 → 1.1.3

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 (118) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/CHANGELOG.md +1 -1
  4. data/LICENSE +1 -1
  5. data/README.md +26 -7
  6. data/SPEC.md +2003 -1605
  7. data/ably-rest.gemspec +4 -2
  8. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  9. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  10. data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
  11. data/lib/submodules/ably-ruby/LICENSE +1 -3
  12. data/lib/submodules/ably-ruby/README.md +12 -7
  13. data/lib/submodules/ably-ruby/Rakefile +32 -0
  14. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  15. data/lib/submodules/ably-ruby/ably.gemspec +17 -11
  16. data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
  17. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  18. data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
  19. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  20. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  21. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  24. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +12 -12
  25. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
  26. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -4
  27. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  28. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  29. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +2 -2
  30. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
  31. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
  32. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
  33. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  34. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  35. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
  37. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
  38. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  39. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -4
  48. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  49. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +45 -26
  50. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  51. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  59. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +54 -18
  60. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  61. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
  62. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  63. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  64. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  68. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +253 -49
  70. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
  71. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
  72. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
  73. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
  74. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
  75. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
  76. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  77. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
  78. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  79. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +62 -51
  81. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  82. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  83. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +318 -74
  85. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  86. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  90. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -9
  91. data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
  92. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  93. data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
  94. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  95. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  96. data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
  97. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
  98. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  99. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  100. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  101. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  102. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  103. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  104. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -2
  105. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  106. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
  107. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
  108. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +1 -1
  109. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  110. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +2 -2
  111. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +1 -1
  112. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  113. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +30 -1
  114. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  115. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  116. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
  117. data/spec/spec_helper.rb +1 -0
  118. metadata +51 -10
@@ -45,7 +45,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
45
45
  connection.on(:failed) do |connection_state_change|
46
46
  error = connection_state_change.reason
47
47
  expect(connection.state).to eq(:failed)
48
- # TODO: Check error type is a TokenNotFOund exception
48
+ # TODO: Check error type is a TokenNotFound exception
49
49
  expect(error.status).to eq(401)
50
50
  expect(error.code).to eq(40400) # not found
51
51
  stop_reactor
@@ -110,6 +110,72 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
110
110
  end
111
111
  end
112
112
  end
113
+
114
+ context 'request fails due to slow response and subsequent timeout', :webmock, em_timeout: (Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout) + 5) * 2 do
115
+ let(:auth_url) { "http://#{random_str}.domain.will.be.stubbed/path" }
116
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: auth_url, log_level: :fatal) }
117
+
118
+ # Timeout +5 seconds, beyond default allowed timeout
119
+ before do
120
+ stub_request(:get, auth_url).
121
+ to_return do |request|
122
+ sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout) + 5
123
+ { status: [500, "Internal Server Error"] }
124
+ end
125
+ end
126
+
127
+ specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
128
+ states = Hash.new { |hash, key| hash[key] = [] }
129
+
130
+ connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
131
+
132
+ connection.on do |connection_state|
133
+ states[connection_state.current.to_sym] << Time.now
134
+ if states[:disconnected].count == 2 && connection_state.current == :disconnected
135
+ expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
136
+ expect(connection.error_reason.message).to match(/auth_url/)
137
+ EventMachine.add_timer(2) do
138
+ expect(states.keys).to include(:connecting, :disconnected)
139
+ expect(states[:connecting].count).to eql(2)
140
+ expect(states[:connected].count).to eql(0)
141
+ stop_reactor
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'request fails once due to slow response but succeeds the second time' do
149
+ let(:auth_url) { "http://#{random_str}.domain.will.be.stubbed/path" }
150
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: auth_url, log_level: :fatal) }
151
+
152
+ # Timeout +5 seconds, beyond default allowed timeout
153
+ before do
154
+ token_response = Ably::Rest::Client.new(default_options).auth.request_token
155
+ WebMock.enable!
156
+
157
+ stub_request(:get, auth_url).
158
+ to_return do |request|
159
+ sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout)
160
+ { status: [500, "Internal Server Error"] }
161
+ end.then.
162
+ to_return(:status => 201, :body => token_response.to_json, :headers => { 'Content-Type' => 'application/json' })
163
+ end
164
+
165
+ specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
166
+ states = Hash.new { |hash, key| hash[key] = [] }
167
+
168
+ connection.once(:connected) do
169
+ expect(states[:disconnected].count).to eql(1)
170
+ expect(states[:connecting].count).to eql(2)
171
+ stop_reactor
172
+ end
173
+
174
+ connection.on do |connection_state|
175
+ states[connection_state.current.to_sym] << Time.now
176
+ end
177
+ end
178
+ end
113
179
  end
114
180
 
115
181
  context 'existing CONNECTED connection' do
@@ -143,7 +209,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
143
209
  context 'with auth_callback' do
144
210
  context 'opening a new connection' do
145
211
  context 'when callback fails due to an exception' do
146
- let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: Proc.new { raise "Cannot issue token" }, log_level: :fatal) }
212
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: lambda { |token_params| raise "Cannot issue token" }, log_level: :fatal) }
147
213
 
148
214
  it 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
149
215
  states = Hash.new { |hash, key| hash[key] = [] }
@@ -174,7 +240,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
174
240
  use_token_auth: true,
175
241
  log_level: :fatal)
176
242
  }
177
- let(:auth_options) { { auth_callback: Proc.new { sleep 10 }, } }
243
+ let(:auth_options) { { auth_callback: lambda { |token_params| sleep 10 }, } }
178
244
 
179
245
  it 'the authorization request fails as configured in the realtime_request_timeout (#RSA4c, #RSA4c1, #RSA4c3)' do
180
246
  connection.once(:connected) do
@@ -425,7 +491,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
425
491
  let(:client_options) do
426
492
  default_options.merge(
427
493
  log_level: :none,
428
- realtime_request_timeout: timeout
494
+ realtime_request_timeout: timeout,
429
495
  )
430
496
  end
431
497
 
@@ -514,6 +580,216 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
514
580
  end
515
581
  end
516
582
 
583
+ context 'connection state freshness is monitored' do
584
+ it 'resumes connections when disconnected within the connection_state_ttl period (#RTN15g)' do
585
+ connection.once(:connected) do
586
+ connection_id = connection.id
587
+ reconnected_with_resume = false
588
+
589
+ # Make sure the next connect has the resume param
590
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
591
+ url = args[4]
592
+ uri = URI.parse(url)
593
+ expect(CGI::parse(uri.query)['resume'][0]).to_not be_empty
594
+ reconnected_with_resume = true
595
+ original.call(*args, &block)
596
+ end
597
+
598
+ connection.once(:disconnected) do
599
+ disconnected_at = Time.now
600
+
601
+ connection.once(:connecting) do
602
+ expect(Time.now.to_f - disconnected_at.to_f).to be < connection.connection_state_ttl
603
+ connection.once(:connected) do |state_change|
604
+ expect(connection.id).to eql(connection_id)
605
+ expect(reconnected_with_resume).to be_truthy
606
+ stop_reactor
607
+ end
608
+ end
609
+ end
610
+
611
+ connection.transport.unbind
612
+ end
613
+ end
614
+
615
+ context 'when connection_state_ttl period has passed since being disconnected' do
616
+ let(:client_options) do
617
+ default_options.merge(
618
+ disconnected_retry_timeout: 4,
619
+ suspended_retry_timeout: 8,
620
+ max_connection_state_ttl: 2,
621
+ )
622
+ end
623
+
624
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl period has passed (#RTN15g)' do
625
+ connection.once(:connected) do
626
+ connection_id = connection.id
627
+ resumed_with_clean_connection = false
628
+
629
+ connection.once(:disconnected) do
630
+ disconnected_at = Time.now
631
+
632
+ connection.once(:connecting) do
633
+ connection.once(:disconnected) do
634
+ # Make sure the next connect does not have the resume param
635
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
636
+ url = args[4]
637
+ uri = URI.parse(url)
638
+ expect(CGI::parse(uri.query)['resume']).to be_empty
639
+ resumed_with_clean_connection = true
640
+ original.call(*args, &block)
641
+ end
642
+
643
+ allow(connection.details).to receive(:max_idle_interval).and_return(0)
644
+ connection.__incoming_protocol_msgbus__.plugin_listeners
645
+
646
+ connection.once(:connecting) do
647
+ expect(Time.now.to_f - disconnected_at.to_f).to be > connection.connection_state_ttl
648
+ connection.once(:connected) do |state_change|
649
+ expect(connection.id).to_not eql(connection_id)
650
+ expect(resumed_with_clean_connection).to be_truthy
651
+ stop_reactor
652
+ end
653
+ end
654
+ end
655
+
656
+ # Disconnect the transport and trigger a new disconnected state
657
+ wait_until(lambda { connection.transport }) do
658
+ connection.transport.unbind
659
+ end
660
+ end
661
+
662
+ connection.__incoming_protocol_msgbus__.unplug_listeners
663
+ end
664
+
665
+ connection.transport.unbind
666
+ end
667
+ end
668
+ end
669
+
670
+ context 'when connection_state_ttl period has passed since last activity on the connection' do
671
+ let(:client_options) do
672
+ default_options.merge(
673
+ max_connection_state_ttl: 2,
674
+ )
675
+ end
676
+
677
+ it 'does not clear the local connection state when the connection_state_ttl period has passed since last activity, but the idle timeout has not passed (#RTN15g1, #RTN15g2)' do
678
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
679
+
680
+ connection.once(:connected) do
681
+ connection_id = connection.id
682
+ resumed_connection = false
683
+
684
+ connection.once(:disconnected) do
685
+ disconnected_at = Time.now
686
+
687
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(connection.connection_state_ttl + 1)
688
+
689
+ # Make sure the next connect does not have the resume param
690
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
691
+ url = args[4]
692
+ uri = URI.parse(url)
693
+ expect(CGI::parse(uri.query)['resume']).to_not be_empty
694
+ resumed_connection = true
695
+ original.call(*args, &block)
696
+ end
697
+
698
+ connection.once(:connecting) do
699
+ connection.once(:connected) do |state_change|
700
+ expect(connection.id).to eql(connection_id)
701
+ expect(resumed_connection).to be_truthy
702
+ stop_reactor
703
+ end
704
+ end
705
+ end
706
+
707
+ connection.transport.unbind
708
+ end
709
+ end
710
+
711
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl + max_idle_interval period has passed since last activity (#RTN15g1, #RTN15g2)' do
712
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
713
+
714
+ connection.once(:connected) do
715
+ connection_id = connection.id
716
+ resumed_with_clean_connection = false
717
+
718
+ connection.once(:disconnected) do
719
+ disconnected_at = Time.now
720
+
721
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
722
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
723
+
724
+ # Make sure the next connect does not have the resume param
725
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
726
+ url = args[4]
727
+ uri = URI.parse(url)
728
+ expect(CGI::parse(uri.query)['resume']).to be_empty
729
+ resumed_with_clean_connection = true
730
+ original.call(*args, &block)
731
+ end
732
+
733
+ connection.once(:connecting) do
734
+ connection.once(:connected) do |state_change|
735
+ expect(connection.id).to_not eql(connection_id)
736
+ expect(resumed_with_clean_connection).to be_truthy
737
+ stop_reactor
738
+ end
739
+ end
740
+ end
741
+
742
+ connection.transport.unbind
743
+ end
744
+ end
745
+
746
+ it 'still reattaches the channels automatically following a new connection being established (#RTN15g2)' do
747
+ connection.once(:connected) do
748
+ connection_id = connection.id
749
+ resumed_with_clean_connection = false
750
+ channel_emitted_an_attached = false
751
+
752
+ channel.attach do
753
+ channel.once(:attached) do |channel_state_change|
754
+ expect(channel_state_change.resumed).to be_falsey
755
+ channel_emitted_an_attached = true
756
+ end
757
+
758
+ connection.once(:disconnected) do
759
+ disconnected_at = Time.now
760
+
761
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
762
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
763
+
764
+ # Make sure the next connect does not have the resume param
765
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
766
+ url = args[4]
767
+ uri = URI.parse(url)
768
+ expect(CGI::parse(uri.query)['resume']).to be_empty
769
+ resumed_with_clean_connection = true
770
+ original.call(*args, &block)
771
+ end
772
+
773
+ connection.once(:connecting) do
774
+ connection.once(:connected) do |state_change|
775
+ expect(connection.id).to_not eql(connection_id)
776
+ expect(resumed_with_clean_connection).to be_truthy
777
+
778
+ wait_until(lambda { channel.attached? }) do
779
+ expect(channel_emitted_an_attached).to be_truthy
780
+ stop_reactor
781
+ end
782
+ end
783
+ end
784
+ end
785
+
786
+ connection.transport.unbind
787
+ end
788
+ end
789
+ end
790
+ end
791
+ end
792
+
517
793
  context 'and subsequently fails to reconnect' do
518
794
  let(:retry_every) { 1.5 }
519
795
 
@@ -711,11 +987,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
711
987
  end
712
988
  end
713
989
 
714
- it 'retains the client_serial (#RTN15c2, #RTN15c3)' do
990
+ it 'retains the client_msg_serial (#RTN15c2, #RTN15c3)' do
715
991
  last_message = nil
716
992
  channel = client.channels.get("foo")
717
993
 
718
- connection.once(:connected) do
994
+ channel.attach do
719
995
  connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
720
996
  if protocol_message.action == :message
721
997
  last_message = protocol_message
@@ -893,7 +1169,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
893
1169
  end
894
1170
  end
895
1171
 
896
- it 'resets the client_serial (#RTN15c3)' do
1172
+ it 'continues to use the client_msg_serial (#RTN15c3)' do
897
1173
  last_message = nil
898
1174
  channel = client.channels.get("foo")
899
1175
 
@@ -911,7 +1187,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
911
1187
  connection.once(:connected) do
912
1188
  channel.publish("first on new connection") do
913
1189
  # Message serial reset after failed resume
914
- expect(last_message.message_serial).to eql(0)
1190
+ expect(last_message.message_serial).to eql(2)
915
1191
  stop_reactor
916
1192
  end
917
1193
  end
@@ -935,16 +1211,20 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
935
1211
  previous_connection_id = connection.id
936
1212
  previous_connection_key = connection.key
937
1213
 
938
- five_minutes_time = Time.now + 5 * 60
939
- allow(Time).to receive(:now) { five_minutes_time }
940
-
941
1214
  connection.once(:connected) do
942
1215
  expect(connection.key).to_not eql(previous_connection_key)
943
1216
  expect(connection.id).to_not eql(previous_connection_id)
944
1217
  stop_reactor
945
1218
  end
946
1219
 
947
- kill_connection_transport_and_prevent_valid_resume
1220
+ # Wait until next tick before stubbing otherwise liveness test may
1221
+ # record the stubbed last contact time as the future time
1222
+ EventMachine.next_tick do
1223
+ five_minutes_time = Time.now + 5 * 60
1224
+ allow(Time).to receive(:now) { five_minutes_time }
1225
+
1226
+ kill_connection_transport_and_prevent_valid_resume
1227
+ end
948
1228
  end
949
1229
  end
950
1230
  end
@@ -1017,7 +1297,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1017
1297
  }
1018
1298
 
1019
1299
  let(:client_options) do
1020
- default_options.merge(auth_callback: Proc.new do
1300
+ default_options.merge(auth_callback: lambda do |token_params|
1021
1301
  @auth_requests ||= 0
1022
1302
  @auth_requests += 1
1023
1303
 
@@ -27,6 +27,13 @@ describe Ably::Realtime::Connection, :event_machine do
27
27
  end
28
28
  end
29
29
 
30
+ context 'current_host' do
31
+ it 'is available immediately after the client is instanced' do
32
+ expect(connection.current_host.to_s).to match(/\.ably\.io$/)
33
+ stop_reactor
34
+ end
35
+ end
36
+
30
37
  context 'with :auto_connect option set to false' do
31
38
  let(:client) do
32
39
  auto_close Ably::Realtime::Client.new(default_options.merge(auto_connect: false))
@@ -131,7 +138,7 @@ describe Ably::Realtime::Connection, :event_machine do
131
138
  let(:ttl) { 0.001 }
132
139
  let(:auth_requests) { [] }
133
140
  let(:token_callback) do
134
- Proc.new do
141
+ lambda do |token_params|
135
142
  auth_requests << Time.now
136
143
  Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
137
144
  end
@@ -246,7 +253,7 @@ describe Ably::Realtime::Connection, :event_machine do
246
253
  let(:ttl) { 4 }
247
254
  let(:auth_requests) { [] }
248
255
  let(:token_callback) do
249
- Proc.new do
256
+ lambda do |token_params|
250
257
  sleep 2
251
258
  auth_requests << Time.now
252
259
  Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
@@ -264,7 +271,7 @@ describe Ably::Realtime::Connection, :event_machine do
264
271
  channel.subscribe('event') do |message|
265
272
  messages_received << message.data.to_i
266
273
  if messages_received.count == total_expected
267
- expect(messages_received).to match(total_expected.times)
274
+ expect(messages_received).to match(total_expected.times.to_a)
268
275
  expect(auth_requests.count).to eql(iteration + 1)
269
276
  EventMachine.add_timer(1) do
270
277
  channel.unsubscribe 'event'
@@ -298,7 +305,7 @@ describe Ably::Realtime::Connection, :event_machine do
298
305
  context 'and subsequent token is invalid' do
299
306
  let(:ttl) { 2 }
300
307
  let(:token_callback) do
301
- Proc.new do
308
+ lambda do |token_params|
302
309
  if @token_issued
303
310
  "#{app_id}.invalid-token-invalid-token-invalid-token"
304
311
  else
@@ -417,7 +424,7 @@ describe Ably::Realtime::Connection, :event_machine do
417
424
  let(:phases) { [:connecting, :connected] }
418
425
  let(:events_emitted) { [] }
419
426
  let(:test_expectation) do
420
- Proc.new do
427
+ lambda do
421
428
  expect(events_emitted).to eq(phases)
422
429
  stop_reactor
423
430
  end
@@ -466,7 +473,7 @@ describe Ably::Realtime::Connection, :event_machine do
466
473
  been_disconnected = true
467
474
  end
468
475
  connection.once(:connecting) do
469
- close_if_transport_available = proc do
476
+ close_if_transport_available = lambda do
470
477
  EventMachine.add_timer(0.001) do
471
478
  if connection.transport
472
479
  connection.transport.close_connection_after_writing
@@ -649,16 +656,47 @@ describe Ably::Realtime::Connection, :event_machine do
649
656
  end
650
657
 
651
658
  it 'is set to 1 when the second message is received' do
652
- channel.publish('event', 'data') do
653
- channel.publish('event', 'data')
659
+ channel.attach do
660
+ messages = []
661
+ channel.subscribe do |message|
662
+ messages << message
663
+ if messages.length == 2
664
+ expect(connection.serial).to eql(1)
665
+ stop_reactor
666
+ end
667
+ end
668
+
669
+ channel.publish('event', 'data') do
670
+ channel.publish('event', 'data')
671
+ end
654
672
  end
673
+ end
674
+ end
655
675
 
656
- messages = []
657
- channel.subscribe do |message|
658
- messages << message
659
- if messages.length == 2
660
- expect(connection.serial).to eql(1)
661
- stop_reactor
676
+ describe '#msgSerial' do
677
+ context 'when messages are queued for publish before a connection is established' do
678
+ let(:batches) { 6 }
679
+ let(:messages_per_batch) { 10 }
680
+
681
+ let(:publishing_client) { auto_close Ably::Realtime::Client.new(default_options) }
682
+ let(:channel_name) { random_str }
683
+ let(:publishing_channel) { publishing_client.channels.get(channel_name) }
684
+ let(:receiving_channel) { client.channels.get(channel_name) }
685
+
686
+ it 'the msgSerial is always incrementing (and not reset when the new connection is established) ensuring messages are never de-duped by the realtime service' do
687
+ messages = []
688
+
689
+ receiving_channel.attach do
690
+ receiving_channel.subscribe('event') do |message|
691
+ messages << message
692
+ stop_reactor if messages.count == batches * messages_per_batch
693
+ end
694
+
695
+ batches.times do |batch|
696
+ EventMachine.add_timer(batch.to_f / batches.to_f) do
697
+ messages_per_batch.times { |index| publishing_channel.publish('event') }
698
+ end
699
+ end
662
700
  end
663
701
  end
664
702
  end
@@ -796,7 +834,7 @@ describe Ably::Realtime::Connection, :event_machine do
796
834
  end
797
835
  end
798
836
 
799
- ping_block = Proc.new do
837
+ ping_block = lambda do |time|
800
838
  pings_complete << true
801
839
  if pings_complete.length == 3
802
840
  expect(heartbeat_ids.uniq.length).to eql(3)
@@ -980,7 +1018,6 @@ describe Ably::Realtime::Connection, :event_machine do
980
1018
 
981
1019
  context 'transport-level heartbeats are supported in the websocket transport' do
982
1020
  it 'provides the heartbeats argument in the websocket connection params (#RTN23b)' do
983
- skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
984
1021
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
985
1022
  uri = URI.parse(url)
986
1023
  expect(CGI::parse(uri.query)['heartbeats'][0]).to eql('false')
@@ -990,12 +1027,10 @@ describe Ably::Realtime::Connection, :event_machine do
990
1027
  end
991
1028
 
992
1029
  it 'receives websocket heartbeat messages (#RTN23b) [slow test as need to wait for heartbeat]', em_timeout: 45 do
993
- skip "Heartbeats param is missing from realtime implementation, see https://github.com/ably/realtime/issues/656"
994
-
995
1030
  connection.once(:connected) do
996
1031
  connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
997
1032
  if protocol_message.action == :heartbeat
998
- expect(protocol_message.attributes[:source]).to eql('websocket')
1033
+ expect(protocol_message.attributes[:source]).to eql(:websocket)
999
1034
  expect(connection.time_since_connection_confirmed_alive?).to be_within(1).of(0)
1000
1035
  stop_reactor
1001
1036
  end
@@ -1008,10 +1043,9 @@ describe Ably::Realtime::Connection, :event_machine do
1008
1043
  let(:client_options) { default_options.merge(websocket_heartbeats_disabled: true) }
1009
1044
 
1010
1045
  it 'does not provide the heartbeats argument in the websocket connection params (#RTN23b)' do
1011
- skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
1012
1046
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1013
1047
  uri = URI.parse(url)
1014
- expect(CGI::parse(uri.query)['heartbeats'][0]).to be_nil
1048
+ expect(CGI::parse(uri.query)['heartbeats'][0]).to eql('true')
1015
1049
  stop_reactor
1016
1050
  end
1017
1051
  client
@@ -1021,7 +1055,8 @@ describe Ably::Realtime::Connection, :event_machine do
1021
1055
  connection.once(:connected) do
1022
1056
  connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
1023
1057
  if protocol_message.action == :heartbeat
1024
- expect(protocol_message.attributes[:source]).to_not eql('websocket')
1058
+ next if protocol_message.attributes[:source] == :websocket # ignore the native heartbeats
1059
+ expect(protocol_message.attributes[:source]).to_not eql(:websocket)
1025
1060
  expect(connection.time_since_connection_confirmed_alive?).to be_within(1).of(0)
1026
1061
  stop_reactor
1027
1062
  end
@@ -1128,7 +1163,7 @@ describe Ably::Realtime::Connection, :event_machine do
1128
1163
  expected_serial += 1 # attach message received
1129
1164
  expect(connection.serial).to eql(expected_serial)
1130
1165
 
1131
- expect(connection.recovery_key).to eql("#{connection.key}:#{connection.serial}")
1166
+ expect(connection.recovery_key).to eql("#{connection.key}:#{connection.serial}:#{connection.send(:client_msg_serial)}")
1132
1167
  stop_reactor
1133
1168
  end
1134
1169
  end
@@ -1176,24 +1211,18 @@ describe Ably::Realtime::Connection, :event_machine do
1176
1211
  end
1177
1212
 
1178
1213
  context "opening a new connection using a recently disconnected connection's #recovery_key" do
1179
- context 'connection#id and connection#key after recovery' do
1180
- it 'remains the same for id and party for key' do
1181
- connection_key_consistent_part_regex = /.*?!([\w-]{5,})-\w+/
1214
+ context 'connection#id after recovery' do
1215
+ it 'remains the same' do
1182
1216
  previous_connection_id = nil
1183
- previous_connection_key = nil
1184
1217
 
1185
1218
  connection.once(:connected) do
1186
1219
  previous_connection_id = connection.id
1187
- previous_connection_key = connection.key
1188
1220
  connection.transition_state_machine! :failed
1189
1221
  end
1190
1222
 
1191
1223
  connection.once(:failed) do
1192
1224
  recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
1193
1225
  recover_client.connection.on(:connected) do
1194
- expect(recover_client.connection.key[connection_key_consistent_part_regex, 1]).to_not be_nil
1195
- expect(recover_client.connection.key[connection_key_consistent_part_regex, 1]).to eql(
1196
- previous_connection_key[connection_key_consistent_part_regex, 1])
1197
1226
  expect(recover_client.connection.id).to eql(previous_connection_id)
1198
1227
  stop_reactor
1199
1228
  end
@@ -1245,6 +1274,80 @@ describe Ably::Realtime::Connection, :event_machine do
1245
1274
  end
1246
1275
  end
1247
1276
  end
1277
+
1278
+ context 'when messages have been published' do
1279
+ describe 'the new connection' do
1280
+ it 'uses the correct msgSerial from the old connection' do
1281
+ msg_serial, recovery_key, connection_id = nil, nil, nil
1282
+
1283
+ channel.attach do
1284
+ expect(connection.send(:client_msg_serial)).to eql(-1) # no messages published yet
1285
+ connection_id = client.connection.id
1286
+ connection.transport.__incoming_protocol_msgbus__
1287
+ channel.publish('event', 'message') do
1288
+ msg_serial = connection.send(:client_msg_serial)
1289
+ expect(msg_serial).to eql(0)
1290
+ recovery_key = client.connection.recovery_key
1291
+ connection.transition_state_machine! :failed
1292
+ end
1293
+ end
1294
+
1295
+ connection.on(:failed) do
1296
+ recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: recovery_key))
1297
+ recover_client_channel = recover_client.channel(channel_name)
1298
+ recover_client_channel.attach do
1299
+ expect(recover_client.connection.id).to eql(connection_id)
1300
+ expect(recover_client.connection.send(:client_msg_serial)).to eql(msg_serial)
1301
+ stop_reactor
1302
+ end
1303
+ end
1304
+ end
1305
+ end
1306
+ end
1307
+
1308
+ context 'when messages are published before the new connection is recovered' do
1309
+ describe 'the new connection' do
1310
+ it 'uses the correct msgSerial from the old connection for the queued messages' do
1311
+ msg_serial, recovery_key, connection_id = nil, nil, nil
1312
+
1313
+ channel.attach do
1314
+ expect(connection.send(:client_msg_serial)).to eql(-1) # no messages published yet
1315
+ connection_id = client.connection.id
1316
+ connection.transport.__incoming_protocol_msgbus__
1317
+ channel.subscribe('event') do |message|
1318
+ expect(message.data).to eql('message-1')
1319
+ msg_serial = connection.send(:client_msg_serial)
1320
+ expect(msg_serial).to eql(0)
1321
+ recovery_key = client.connection.recovery_key
1322
+ connection.transition_state_machine! :failed
1323
+ end
1324
+ channel.publish('event', 'message-1')
1325
+ end
1326
+
1327
+ connection.on(:failed) do
1328
+ recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: recovery_key))
1329
+ recover_client_channel = recover_client.channel(channel_name)
1330
+ expect(recover_client.connection.send(:client_msg_serial)).to eql(msg_serial)
1331
+
1332
+ recover_client.connection.once(:connecting) do
1333
+ recover_client_channel.publish('event', 'message-2')
1334
+ expect(recover_client.connection.send(:client_msg_serial)).to eql(msg_serial + 1)
1335
+ end
1336
+
1337
+ recover_client_channel.attach do
1338
+ expect(recover_client.connection.id).to eql(connection_id)
1339
+
1340
+ recover_client_channel.subscribe do |message|
1341
+ expect(message.data).to eql('message-2')
1342
+ EventMachine.add_timer(2) do
1343
+ stop_reactor
1344
+ end
1345
+ end
1346
+ end
1347
+ end
1348
+ end
1349
+ end
1350
+ end
1248
1351
  end
1249
1352
 
1250
1353
  context 'with :recover option' do
@@ -1258,7 +1361,7 @@ describe Ably::Realtime::Connection, :event_machine do
1258
1361
  end
1259
1362
 
1260
1363
  context 'with invalid formatted value sent to server' do
1261
- let(:client_options) { default_options.merge(recover: 'not-a-valid-connection-key:1', log_level: :none) }
1364
+ let(:client_options) { default_options.merge(recover: 'not-a-valid-connection-key:1:0', log_level: :none) }
1262
1365
 
1263
1366
  it 'sets the #error_reason and moves the connection to FAILED' do
1264
1367
  connection.once(:failed) do |state_change|
@@ -1273,7 +1376,7 @@ describe Ably::Realtime::Connection, :event_machine do
1273
1376
  end
1274
1377
 
1275
1378
  context 'with expired (missing) value sent to server' do
1276
- let(:client_options) { default_options.merge(recover: 'wVIsgTHAB1UvXh7z-1991d8586:0', log_level: :fatal) }
1379
+ let(:client_options) { default_options.merge(recover: 'wVIsgTHAB1UvXh7z-1991d8586:0:0', log_level: :fatal) }
1277
1380
 
1278
1381
  it 'connects but sets the error reason and includes the reason in the state change' do
1279
1382
  connection.once(:connected) do |state_change|
@@ -1480,12 +1583,12 @@ describe Ably::Realtime::Connection, :event_machine do
1480
1583
  channel.attach do
1481
1584
  channel.once(:suspended) do
1482
1585
  channel.publish('test').errback do |error|
1483
- expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
1586
+ expect(error).to be_a(Ably::Exceptions::ChannelInactive)
1484
1587
  stop_reactor
1485
1588
  end
1486
1589
  end
1487
1590
 
1488
- close_connection_proc = Proc.new do
1591
+ close_connection_proc = lambda do
1489
1592
  EventMachine.add_timer(0.001) do
1490
1593
  if connection.transport.nil?
1491
1594
  close_connection_proc.call
@@ -1523,7 +1626,7 @@ describe Ably::Realtime::Connection, :event_machine do
1523
1626
  # See https://github.com/ably/ably-ruby/issues/103
1524
1627
  it 'emits event to all and single subscribers' do
1525
1628
  connected_emitted = []
1526
- block = Proc.new do |state_change|
1629
+ block = lambda do |state_change|
1527
1630
  if state_change.current == :connected
1528
1631
  connected_emitted << state_change
1529
1632
  EventMachine.add_timer(0.5) do
@@ -1740,7 +1843,7 @@ describe Ably::Realtime::Connection, :event_machine do
1740
1843
  it 'sends the protocol version param v (#G4, #RTN2f)' do
1741
1844
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1742
1845
  uri = URI.parse(url)
1743
- expect(CGI::parse(uri.query)['v'][0]).to eql('1.0')
1846
+ expect(CGI::parse(uri.query)['v'][0]).to eql('1.1')
1744
1847
  stop_reactor
1745
1848
  end
1746
1849
  client
@@ -1749,7 +1852,7 @@ describe Ably::Realtime::Connection, :event_machine do
1749
1852
  it 'sends the lib version param lib (#RTN2g)' do
1750
1853
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1751
1854
  uri = URI.parse(url)
1752
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-1\.0\.\d+(-[\w\.]+)?+$/)
1855
+ expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-1\.1\.\d+(-[\w\.]+)?+$/)
1753
1856
  stop_reactor
1754
1857
  end
1755
1858
  client
@@ -1769,7 +1872,7 @@ describe Ably::Realtime::Connection, :event_machine do
1769
1872
  it 'sends the lib version param lib with the variant (#RTN2g + #RSC7b)' do
1770
1873
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1771
1874
  uri = URI.parse(url)
1772
- expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-#{variant}-1\.0\.\d+(-[\w\.]+)?$/)
1875
+ expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-#{variant}-1\.1\.\d+(-[\w\.]+)?$/)
1773
1876
  stop_reactor
1774
1877
  end
1775
1878
  client