ably-rest 1.0.6 → 1.1.4.rc

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +23 -15
  4. data/ably-rest.gemspec +6 -6
  5. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  6. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  7. data/lib/submodules/ably-ruby/CHANGELOG.md +75 -3
  8. data/lib/submodules/ably-ruby/LICENSE +1 -3
  9. data/lib/submodules/ably-ruby/README.md +12 -7
  10. data/lib/submodules/ably-ruby/Rakefile +32 -0
  11. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  12. data/lib/submodules/ably-ruby/ably.gemspec +15 -10
  13. data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
  14. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  15. data/lib/submodules/ably-ruby/lib/ably/logger.rb +7 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  17. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  18. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  19. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  20. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  21. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  22. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  23. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  24. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
  25. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
  26. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  27. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  28. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  29. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  35. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  36. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  37. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  38. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  39. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +34 -20
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  48. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  49. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +53 -17
  50. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  51. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +162 -35
  52. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  53. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  54. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  55. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  56. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  57. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  59. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  60. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +245 -17
  61. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  62. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +177 -59
  63. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +153 -0
  64. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
  65. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +129 -18
  66. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +36 -34
  67. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +201 -167
  68. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  70. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
  71. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  72. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  73. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  74. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
  75. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  76. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  77. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  78. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  79. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  80. data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -1
  81. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  82. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  83. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
  84. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  85. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  86. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  87. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  88. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  90. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  91. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  92. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +1 -1
  93. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  94. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
  95. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  96. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  97. metadata +46 -21
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Realtime::Push, :event_machine do
5
+ vary_by_protocol do
6
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
7
+ let(:client_options) { default_options }
8
+ let(:client) do
9
+ Ably::Realtime::Client.new(client_options)
10
+ end
11
+ subject { client.push }
12
+
13
+ describe '#activate' do
14
+ it 'raises an unsupported exception' do
15
+ expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
16
+ stop_reactor
17
+ end
18
+ end
19
+
20
+ describe '#deactivate' do
21
+ it 'raises an unsupported exception' do
22
+ expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
23
+ stop_reactor
24
+ end
25
+ end
26
+ end
27
+ end
@@ -758,9 +758,10 @@ describe Ably::Auth do
758
758
  end
759
759
 
760
760
  it 'updates the persisted auth options that are then used for subsequent authorize requests' do
761
- expect(auth.options[:authUrl]).to be_nil
762
- auth.authorize({}, authUrl: 'http://foo.com')
763
- expect(auth.options[:authUrl]).to eql('http://foo.com')
761
+ auth_url = "https://echo.ably.io/?type=text&body=#{auth.request_token.token}"
762
+ expect(auth.options[:auth_url]).to be_nil
763
+ auth.authorize({}, auth_url: auth_url)
764
+ expect(auth.options[:auth_url]).to eql(auth_url)
764
765
  end
765
766
 
766
767
  context 'with a lambda for the :auth_callback option' do
@@ -1338,5 +1339,42 @@ describe Ably::Auth do
1338
1339
  expect(response).to be_a(Ably::Models::TokenDetails)
1339
1340
  end
1340
1341
  end
1342
+
1343
+ # RSC1, RSC1a, RSA3c, RSA3d
1344
+ context 'when using JWT' do
1345
+ let(:auth_url) { 'https://echo.ably.io/createJWT' }
1346
+ let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
1347
+ let(:client) { Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol) }
1348
+
1349
+ it 'authenticates correctly using the JWT token generated by the echo server' do
1350
+ expect(client.stats).to_not be_nil()
1351
+ end
1352
+
1353
+ context 'when the JWT embeds an Ably token' do
1354
+ let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded }).body }
1355
+
1356
+ it 'authenticates correctly using the embedded token' do
1357
+ expect(client.stats).to_not be_nil()
1358
+ end
1359
+
1360
+ context 'and the requested token is encrypted' do
1361
+ let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded, encrypted: 1 }).body }
1362
+
1363
+ it 'authenticates correctly using the embedded token' do
1364
+ expect(client.stats).to_not be_nil()
1365
+ end
1366
+ end
1367
+ end
1368
+
1369
+ # RSA4f, RSA8c
1370
+ context 'when the token requested is returned with application/jwt content type' do
1371
+ let(:auth_rest_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
1372
+ let(:auth_params) { { keyName: key_name, keySecret: key_secret, returnType: 'jwt' } }
1373
+ let(:token) { auth_rest_client.auth.request_token({ }, { auth_url: auth_url, auth_params: auth_params }).token }
1374
+ it 'authenticates correctly and pulls stats' do
1375
+ expect(client.stats).to_not be_nil()
1376
+ end
1377
+ end
1378
+ end
1341
1379
  end
1342
1380
  end
@@ -7,7 +7,7 @@ describe Ably::Rest do
7
7
 
8
8
  let(:client_options) { {} }
9
9
  let(:client) do
10
- Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret'))
10
+ Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret', log_retries_as_info: true))
11
11
  end
12
12
 
13
13
  let(:now) { Time.now - 1000 }
@@ -67,7 +67,7 @@ describe Ably::Rest do
67
67
 
68
68
  vary_by_protocol do
69
69
  let(:client) do
70
- Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol)
70
+ Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true)
71
71
  end
72
72
 
73
73
  describe 'failed requests' do
@@ -40,7 +40,7 @@ describe Ably::Rest::Channel do
40
40
 
41
41
  it 'publishes the message without a client_id' do
42
42
  expect(client).to receive(:post).
43
- with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id)).
43
+ with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id), {}).
44
44
  and_return(double('response', status: 201))
45
45
 
46
46
  expect(channel.publish(name, data)).to eql(true)
@@ -82,6 +82,44 @@ describe Ably::Rest::Channel do
82
82
  end
83
83
  end
84
84
 
85
+ context 'with a Message object' do
86
+ let(:name) { random_str }
87
+
88
+ let(:message) do
89
+ Ably::Models::Message(name: name, data: data)
90
+ end
91
+
92
+ it 'publishes the message' do
93
+ expect(client).to receive(:post).once.and_call_original
94
+ expect(channel.publish(message)).to eql(true)
95
+ expect(channel.history.items.first.name).to eql(name)
96
+ end
97
+ end
98
+
99
+ context 'with a Message object and query params' do
100
+ let(:message) do
101
+ Ably::Models::Message(name: name, data: data)
102
+ end
103
+
104
+ it 'should fail to publish the message (RSL1l1)' do
105
+ expect(client).to receive(:post).once.and_call_original
106
+ expect { channel.publish(message, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
107
+ end
108
+ end
109
+
110
+ context 'with Messages and query params' do
111
+ let(:messages) do
112
+ 10.times.map do |index|
113
+ { name: index.to_s, data: { "index" => index + 10 } }
114
+ end
115
+ end
116
+
117
+ it 'should fail to publish the message (RSL1l1)' do
118
+ expect(client).to receive(:post).once.and_call_original
119
+ expect { channel.publish(messages, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
120
+ end
121
+ end
122
+
85
123
  context 'without adequate permissions on the channel' do
86
124
  let(:capability) { { onlyChannel: ['subscribe'] } }
87
125
  let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { capability: capability }) }
@@ -96,7 +134,7 @@ describe Ably::Rest::Channel do
96
134
  let(:data) { random_str }
97
135
 
98
136
  it 'publishes the message without a name attribute in the payload' do
99
- expect(client).to receive(:post).with(anything, { "data" => data }).once.and_call_original
137
+ expect(client).to receive(:post).with(anything, { "data" => data }, {}).once.and_call_original
100
138
  expect(channel.publish(nil, data)).to eql(true)
101
139
  expect(channel.history.items.first.name).to be_nil
102
140
  expect(channel.history.items.first.data).to eql(data)
@@ -107,7 +145,7 @@ describe Ably::Rest::Channel do
107
145
  let(:name) { random_str }
108
146
 
109
147
  it 'publishes the message without a data attribute in the payload' do
110
- expect(client).to receive(:post).with(anything, { "name" => name }).once.and_call_original
148
+ expect(client).to receive(:post).with(anything, { "name" => name }, {}).once.and_call_original
111
149
  expect(channel.publish(name)).to eql(true)
112
150
  expect(channel.history.items.first.name).to eql(name)
113
151
  expect(channel.history.items.first.data).to be_nil
@@ -118,7 +156,7 @@ describe Ably::Rest::Channel do
118
156
  let(:name) { random_str }
119
157
 
120
158
  it 'publishes the message without any attributes in the payload' do
121
- expect(client).to receive(:post).with(anything, {}).once.and_call_original
159
+ expect(client).to receive(:post).with(anything, {}, {}).once.and_call_original
122
160
  expect(channel.publish(nil)).to eql(true)
123
161
  expect(channel.history.items.first.name).to be_nil
124
162
  expect(channel.history.items.first.data).to be_nil
@@ -275,6 +313,43 @@ describe Ably::Rest::Channel do
275
313
  end
276
314
  end
277
315
  end
316
+
317
+ context 'with a frozen message event name' do
318
+ let(:event_name) { random_str.freeze }
319
+
320
+ it 'succeeds and publishes with an implicit client_id' do
321
+ channel.publish([name: event_name])
322
+ channel.publish(event_name)
323
+
324
+ if !(RUBY_VERSION.match(/^1\./) || RUBY_VERSION.match(/^2\.[012]/))
325
+ channel.publish(+'foo-bar') # new style freeze, see https://github.com/ably/ably-ruby/issues/132
326
+ else
327
+ channel.publish('foo-bar'.freeze) # new + style not supported until Ruby 2.3
328
+ end
329
+
330
+ channel.history do |messages|
331
+ expect(messages.length).to eql(3)
332
+ expect(messages.first.name).to eql(event_name)
333
+ expect(messages[1].name).to eql(event_name)
334
+ expect(messages.last.name).to eql('foo-bar')
335
+ end
336
+ end
337
+ end
338
+
339
+ context 'with a frozen payload' do
340
+ let(:payload) { { foo: random_str.freeze }.freeze }
341
+
342
+ it 'succeeds and publishes with an implicit client_id' do
343
+ channel.publish([data: payload])
344
+ channel.publish(nil, payload)
345
+
346
+ channel.history do |messages|
347
+ expect(messages.length).to eql(2)
348
+ expect(messages.first.data).to eql(payload)
349
+ expect(messages.last.data).to eql(payload)
350
+ end
351
+ end
352
+ end
278
353
  end
279
354
 
280
355
  describe '#history' do
@@ -60,5 +60,11 @@ describe Ably::Rest::Channels do
60
60
  let(:channel_with_options) { client.channels[channel_name, options] }
61
61
  it_behaves_like 'a channel'
62
62
  end
63
+
64
+ describe 'using a frozen channel name' do
65
+ let(:channel) { client.channels[channel_name.freeze] }
66
+ let(:channel_with_options) { client.channels[channel_name.freeze, options] }
67
+ it_behaves_like 'a channel'
68
+ end
63
69
  end
64
70
  end
@@ -4,7 +4,7 @@ require 'webrick'
4
4
 
5
5
  describe Ably::Rest::Client do
6
6
  vary_by_protocol do
7
- let(:default_options) { { environment: environment, protocol: protocol } }
7
+ let(:default_options) { { environment: environment, protocol: protocol, log_retries_as_info: true } }
8
8
  let(:client_options) { default_options }
9
9
 
10
10
  let(:client) { Ably::Rest::Client.new(client_options) }
@@ -27,6 +27,19 @@ describe Ably::Rest::Client do
27
27
  end
28
28
  end
29
29
 
30
+ context 'with an invalid API key' do
31
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: 'app.key:secret', log_level: :fatal)) }
32
+
33
+ it 'logs an entry with a help href url matching the code #TI5' do
34
+ begin
35
+ client.channels.get('foo').publish('test')
36
+ raise 'Expected Ably::Exceptions::ResourceMissing'
37
+ rescue Ably::Exceptions::ResourceMissing => err
38
+ expect err.to_s.match(%r{https://help.ably.io/error/40400})
39
+ end
40
+ end
41
+ end
42
+
30
43
  context 'with an explicit string :token' do
31
44
  let(:client) { Ably::Rest::Client.new(client_options.merge(token: random_str)) }
32
45
 
@@ -710,6 +723,109 @@ describe Ably::Rest::Client do
710
723
  expect(@fallback_request_count).to eql(2)
711
724
  end
712
725
  end
726
+
727
+ context 'to fail the primary host, allow a fallback to succeed, then later trigger a fallback to the primary host (#RSC15f)' do
728
+ before do
729
+ @request_count = 0
730
+ @primary_host_request_count = 0
731
+ @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
732
+ @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
733
+ @request_count += 1
734
+ if req.header["host"].first.include?(primary_host)
735
+ @primary_host_request_count += 1
736
+ # Fail all requests to the primary host so that a fallback is used
737
+ # Except request 6 which should suceed and clear the fallback host preference
738
+ if @request_count == 6
739
+ res.status = 200
740
+ res['Content-Type'] = 'application/json'
741
+ res.body = '{}'
742
+ else
743
+ res.status = 500
744
+ end
745
+ else
746
+ # Fail the second request (first failed fallback of first request)
747
+ # Fail the third request on the previously succeeded fallback host to trigger an attempt on the primary host
748
+ if [2, 5].include?(@request_count)
749
+ res.status = 500
750
+ else
751
+ res.status = 200
752
+ res['Content-Type'] = 'application/json'
753
+ res.body = '{}'
754
+ end
755
+ end
756
+ end
757
+
758
+ Thread.new do
759
+ @web_server.start
760
+ end
761
+ end
762
+
763
+ let(:client_options) do
764
+ default_options.merge(
765
+ rest_host: primary_host,
766
+ fallback_hosts: fallbacks,
767
+ token: 'fake.token',
768
+ port: port,
769
+ tls: false,
770
+ log_level: :error
771
+ ).merge(additional_client_options)
772
+ end
773
+
774
+ let (:additional_client_options) { {} }
775
+
776
+ it 'succeeds and remembers fallback host preferences across requests' do
777
+ # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
778
+ client.channel(channel_name).publish('event', 'data')
779
+ expect(@request_count).to eql(3)
780
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
781
+ successfull_fallback = client.using_preferred_fallback_host?
782
+ expect(@primary_host_request_count).to eql(1)
783
+
784
+ # Send another request, which should go straight to the fallback as it succeeded previously
785
+ client.channel(channel_name).publish('event', 'data')
786
+ expect(@request_count).to eql(4)
787
+ expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
788
+ expect(@primary_host_request_count).to eql(1)
789
+
790
+ # A subsequent request should fail to the fallback, go the primary host and succeed
791
+ client.channel(channel_name).publish('event', 'data')
792
+ expect(@request_count).to eql(6)
793
+ expect(client.using_preferred_fallback_host?).to be_falsey
794
+ expect(@primary_host_request_count).to eql(2)
795
+
796
+ # A subsequent request will fail on the primary endpoint, and we expect the fallback to be used again
797
+ client.channel(channel_name).publish('event', 'data')
798
+ expect(@request_count).to eql(8)
799
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
800
+ successfull_fallback = client.using_preferred_fallback_host?
801
+ expect(@primary_host_request_count).to eql(3)
802
+
803
+ # Send another request, which should go straight to the fallback as it succeeded previously
804
+ client.channel(channel_name).publish('event', 'data')
805
+ expect(@request_count).to eql(9)
806
+ expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
807
+ expect(@primary_host_request_count).to eql(3)
808
+ end
809
+
810
+ context 'with custom :fallback_retry_timeout' do
811
+ let (:additional_client_options) { { fallback_retry_timeout: 5 } }
812
+
813
+ it 'stops using the preferred fallback after this time' do
814
+ # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
815
+ client.channel(channel_name).publish('event', 'data')
816
+ expect(@request_count).to eql(3)
817
+ expect(fallbacks).to include(client.using_preferred_fallback_host?)
818
+ expect(@primary_host_request_count).to eql(1)
819
+
820
+ # Wait for the preferred fallback cache to expire
821
+ sleep 5
822
+
823
+ # Send another request, which should go straight to the primary host again as fallback host is expired
824
+ client.channel(channel_name).publish('event', 'data')
825
+ expect(@primary_host_request_count).to eql(2)
826
+ end
827
+ end
828
+ end
713
829
  end
714
830
  end
715
831
 
@@ -724,7 +840,8 @@ describe Ably::Rest::Client do
724
840
  environment: env,
725
841
  key: api_key,
726
842
  http_max_retry_duration: max_retry_duration,
727
- http_max_retry_count: max_retry_count
843
+ http_max_retry_count: max_retry_count,
844
+ log_level: :fatal,
728
845
  )
729
846
  end
730
847
 
@@ -751,7 +868,7 @@ describe Ably::Rest::Client do
751
868
  end
752
869
 
753
870
  let(:client_options) {
754
- production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
871
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
755
872
  }
756
873
 
757
874
  it 'attempts the fallback hosts as this is not an authentication failure' do
@@ -764,7 +881,7 @@ describe Ably::Rest::Client do
764
881
 
765
882
  context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
766
883
  let(:client_options) {
767
- production_options.merge(fallback_hosts: [])
884
+ production_options.merge(fallback_hosts: [], log_level: :fatal)
768
885
  }
769
886
 
770
887
  it 'does not attempt the fallback hosts as this is an authentication failure' do
@@ -789,7 +906,7 @@ describe Ably::Rest::Client do
789
906
  end
790
907
 
791
908
  let(:client_options) {
792
- production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
909
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
793
910
  }
794
911
 
795
912
  it 'attempts the default fallback hosts as this is an authentication failure' do
@@ -966,7 +1083,7 @@ describe Ably::Rest::Client do
966
1083
  it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
967
1084
  client.channels.get('foo').publish("event")
968
1085
  expect(publish_message_stub).to have_been_requested
969
- expect(Ably::PROTOCOL_VERSION).to eql('1.0')
1086
+ expect(Ably::PROTOCOL_VERSION).to eql('1.1')
970
1087
  end
971
1088
  end
972
1089
  end
@@ -1084,7 +1201,7 @@ describe Ably::Rest::Client do
1084
1201
  end
1085
1202
 
1086
1203
  context 'option add_request_ids: true and specified fallback hosts', :webmock do
1087
- let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error } }
1204
+ let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error, log_retries_as_info: true } }
1088
1205
  let(:requests) { [] }
1089
1206
 
1090
1207
  before do
@@ -1140,7 +1257,7 @@ describe Ably::Rest::Client do
1140
1257
 
1141
1258
  context 'failed request logging', :prevent_log_stubbing do
1142
1259
  let(:custom_logger) { TestLogger.new }
1143
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger) }
1260
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger, log_retries_as_info: false) }
1144
1261
 
1145
1262
  it 'is absent when requests do not fail' do
1146
1263
  client.time
@@ -1153,7 +1270,8 @@ describe Ably::Rest::Client do
1153
1270
  rest_host: 'non.existent.domain.local',
1154
1271
  fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')],
1155
1272
  key: api_key,
1156
- logger: custom_logger)
1273
+ logger: custom_logger,
1274
+ log_retries_as_info: false)
1157
1275
  end
1158
1276
 
1159
1277
  it 'is present with success message when requests do not actually fail' do
@@ -1169,7 +1287,8 @@ describe Ably::Rest::Client do
1169
1287
  rest_host: 'non.existent.domain.local',
1170
1288
  fallback_hosts: ['non2.existent.domain.local'],
1171
1289
  key: api_key,
1172
- logger: custom_logger)
1290
+ logger: custom_logger,
1291
+ log_retries_as_info: false)
1173
1292
  end
1174
1293
 
1175
1294
  it 'is present when all requests fail' do