ably-rest 0.9.3 → 1.0.0

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/ably-rest.gemspec +2 -1
  3. data/lib/submodules/ably-ruby/.travis.yml +6 -4
  4. data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
  5. data/lib/submodules/ably-ruby/README.md +10 -0
  6. data/lib/submodules/ably-ruby/SPEC.md +1473 -852
  7. data/lib/submodules/ably-ruby/ably.gemspec +2 -1
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
  9. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
  10. data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
  11. data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
  12. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
  13. data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
  14. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
  15. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
  17. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
  18. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
  19. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
  21. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
  22. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
  23. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
  24. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
  25. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
  26. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
  35. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
  36. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  37. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  38. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
  39. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  42. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
  43. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
  44. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
  45. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
  46. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  47. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
  48. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
  49. data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
  50. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
  51. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  52. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
  53. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
  54. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  55. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
  56. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
  57. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  58. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
  59. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
  60. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
  61. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
  62. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
  63. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
  64. data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
  65. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
  66. data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
  67. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
  68. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
  69. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
  70. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
  71. data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
  72. data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
  73. data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
  74. data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
  75. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
  76. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
  77. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
  78. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
  79. data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
  80. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
  81. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
  82. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
  83. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
  84. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
  85. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
  86. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  87. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
  88. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
  90. metadata +7 -5
@@ -182,7 +182,7 @@ describe Ably::Auth do
182
182
  context 'with :query_time option' do
183
183
  let(:options) { { query_time: true } }
184
184
 
185
- it 'queries the server for the time' do
185
+ it 'queries the server for the time (#RSA10k)' do
186
186
  expect(client).to receive(:time).and_call_original
187
187
  auth.request_token({}, options)
188
188
  end
@@ -404,7 +404,7 @@ describe Ably::Auth do
404
404
  end
405
405
 
406
406
  it 'raises ServerError' do
407
- expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::ServerError)
407
+ expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::AuthenticationFailed)
408
408
  end
409
409
  end
410
410
 
@@ -415,7 +415,7 @@ describe Ably::Auth do
415
415
  end
416
416
 
417
417
  it 'raises InvalidResponseBody' do
418
- expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::InvalidResponseBody)
418
+ expect { auth.request_token({}, auth_options) }.to raise_error(Ably::Exceptions::AuthenticationFailed, /Content Type.*not supported/)
419
419
  end
420
420
  end
421
421
  end
@@ -612,7 +612,7 @@ describe Ably::Auth do
612
612
  end
613
613
  end
614
614
 
615
- context 'query_time: true' do
615
+ context 'query_time: true with authorize' do
616
616
  let(:local_time) { @now - 60 }
617
617
  let(:server_time) { @now }
618
618
 
@@ -621,7 +621,7 @@ describe Ably::Auth do
621
621
  allow(Time).to receive(:now).and_return(local_time)
622
622
  end
623
623
 
624
- it 'only queries the server time once and then works out the offset, query_time option is never persisted' do
624
+ it 'only queries the server time once and then works out the offset, query_time option is never persisted (#RSA10k)' do
625
625
  expect(client).to receive(:time).once.and_return(server_time)
626
626
 
627
627
  auth.authorize({}, query_time: true)
@@ -630,6 +630,27 @@ describe Ably::Auth do
630
630
  end
631
631
  end
632
632
 
633
+ context 'query_time: true ClientOption when instanced' do
634
+ let(:local_time) { @now - 60 }
635
+ let(:server_time) { @now }
636
+
637
+ let(:client_options) { default_options.merge(key: api_key, query_time: true) }
638
+
639
+ before do
640
+ @now = Time.now
641
+ allow(Time).to receive(:now).and_return(local_time)
642
+ end
643
+
644
+ it 'only queries the server time once and then works out the offset, query_time option is never persisted (#RSA10k)' do
645
+ expect(client).to receive(:time).once.and_return(server_time)
646
+
647
+ auth.authorize({})
648
+ auth.authorize({})
649
+ auth.authorize({})
650
+ expect(auth.auth_options).to_not have_key(:query_time)
651
+ end
652
+ end
653
+
633
654
  context 'TokenParams argument' do
634
655
  let(:default_token_params) { { ttl: 23 } }
635
656
 
@@ -644,11 +665,11 @@ describe Ably::Auth do
644
665
  expect(auth.token_params[:ttl]).to eql(23)
645
666
  end
646
667
 
647
- it 'updates defaults when present and all previous configured TokenParams are discarded' do
668
+ it 'updates defaults when present and all previous configured TokenParams are discarded (#RSA10g)' do
648
669
  old_token = auth.current_token_details
649
670
  auth.authorize({ client_id: 'bob' })
650
671
  expect(old_token).to_not eql(auth.current_token_details)
651
- expect(auth.token_params[:ttl]).to_not eql(23)
672
+ expect(auth.token_params[:ttl]).to_not eq(23)
652
673
  expect(auth.token_params[:client_id]).to eql('bob')
653
674
  end
654
675
 
@@ -656,6 +677,14 @@ describe Ably::Auth do
656
677
  auth.authorize({ client_id: 'bob' })
657
678
  expect { auth.token_params['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
658
679
  end
680
+
681
+ it 'uses TokenParams#timestamp for this request but obtains a new timestamp for subsequence requests (#RSA10g)' do
682
+ timestamp = Time.now.to_i
683
+ expect(auth).to receive(:create_token_request).with({ timestamp: Time.now.to_i }, {}).once.and_call_original
684
+ expect(auth).to receive(:create_token_request).with({}, {}).once.and_call_original
685
+ auth.authorize(timestamp: Time.now.to_i)
686
+ auth.authorize
687
+ end
659
688
  end
660
689
 
661
690
  context 'AuthOptions argument' do
@@ -680,7 +709,7 @@ describe Ably::Auth do
680
709
  expect(auth.options[:auth_callback]).to eql(auth_callback)
681
710
  end
682
711
 
683
- it 'updates defaults when present and all previous configured AuthOptions are discarded' do
712
+ it 'updates defaults when present and all previous configured AuthOptions are discarded (#RSA10g)' do
684
713
  auth.authorize(nil, auth_method: :post)
685
714
  expect(@old_token).to_not eql(auth.current_token_details)
686
715
  expect(auth.options[:auth_callback]).to be_nil
@@ -691,6 +720,18 @@ describe Ably::Auth do
691
720
  auth.authorize(nil, auth_callback: Proc.new { '1231232.12321:12321312' })
692
721
  expect { auth.options['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
693
722
  end
723
+
724
+ it 'uses AuthOptions#query_time for this request and will not query_time for subsequent requests (#RSA10g)' do
725
+ expect(client).to receive(:time).once.and_call_original
726
+ auth.authorize({}, query_time: true)
727
+ auth.authorize
728
+ end
729
+
730
+ it 'uses AuthOptions#query_time for this request and will query_time again if provided subsequently' do
731
+ expect(client).to receive(:time).twice.and_call_original
732
+ auth.authorize({}, query_time: true)
733
+ auth.authorize({}, query_time: true)
734
+ end
694
735
  end
695
736
 
696
737
  context 'with previous authorisation' do
@@ -768,7 +809,7 @@ describe Ably::Auth do
768
809
  @block_called = 0
769
810
  end
770
811
 
771
- let(:token_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, token_params: { ttl: 3 })) }
812
+ let(:token_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, default_token_params: { ttl: 3 })) }
772
813
  let(:client_options) {
773
814
  default_options.merge(token: token_client.auth.request_token.token, auth_callback: Proc.new do
774
815
  @block_called += 1
@@ -840,21 +881,22 @@ describe Ably::Auth do
840
881
  expect(subject['keyName']).to eql(key_name)
841
882
  end
842
883
 
843
- it 'uses the default TTL' do
844
- expect(subject['ttl']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl) * 1000)
884
+ it 'specifies no TTL (#RSA5)' do
885
+ expect(subject['ttl']).to be_nil
845
886
  end
846
887
 
847
888
  context 'with a :ttl option below the Token expiry buffer that ensures tokens are renewed 15s before they expire as they are considered expired' do
848
- let(:ttl) { 1 }
889
+ let(:ttl) { 1 }
890
+ let(:token_params) { { ttl: ttl } }
849
891
 
850
892
  it 'uses the Token expiry buffer default + 10s to allow for a token request in flight' do
851
- expect(subject.ttl).to be > 1
852
- expect(subject.ttl).to be > Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER
893
+ expect(subject['ttl']).to be > 1
894
+ expect(subject['ttl']).to be > Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER
853
895
  end
854
896
  end
855
897
 
856
- it 'uses the default capability' do
857
- expect(subject['capability']).to eql(Ably::Auth::TOKEN_DEFAULTS.fetch(:capability).to_json)
898
+ it 'specifies no capability (#RSA6)' do
899
+ expect(subject['capability']).to be_nil
858
900
  end
859
901
 
860
902
  context 'the nonce' do
@@ -1048,6 +1090,42 @@ describe Ably::Auth do
1048
1090
  it 'cannot be renewed automatically' do
1049
1091
  expect(token_auth_client.auth).to_not be_token_renewable
1050
1092
  end
1093
+
1094
+ context 'and the token expires' do
1095
+ let(:ttl) { 1 }
1096
+
1097
+ before do
1098
+ stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
1099
+ stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
1100
+
1101
+ @token = auth.request_token(ttl: ttl)
1102
+ WebMock.enable!
1103
+ WebMock.disable_net_connect!
1104
+
1105
+ token_expired = {
1106
+ "error" => {
1107
+ "statusCode" => 401,
1108
+ "code" => 40140,
1109
+ "message" => "Token expired"
1110
+ }
1111
+ }
1112
+
1113
+ stub_request(:post, "https://#{environment}-rest.ably.io/channels/foo/publish").
1114
+ to_return(status: 401, body: token_expired.to_json, headers: { 'Content-Type' => 'application/json' })
1115
+ end
1116
+
1117
+ after do
1118
+ WebMock.allow_net_connect!
1119
+ WebMock.disable!
1120
+ end
1121
+
1122
+ let(:token) { @token.token }
1123
+
1124
+ it 'should indicate an error and not retry the request (#RSA4a)' do
1125
+ sleep ttl + 1
1126
+ expect { token_auth_client.channels.get('foo').publish 'event' }.to raise_error(Ably::Exceptions::TokenExpired)
1127
+ end
1128
+ end
1051
1129
  end
1052
1130
 
1053
1131
  context 'when implicit as a result of using :client_id' do
@@ -1090,14 +1168,14 @@ describe Ably::Auth do
1090
1168
  expect(client.channel('foo').publish('event', 'data')).to be_truthy
1091
1169
  end
1092
1170
 
1093
- it 'with capability and TTL defaults' do
1171
+ it 'with capability and TTL defaults (#TK2a, #TK2b)' do
1094
1172
  client.channel('foo').publish('event', 'data')
1095
1173
 
1096
1174
  expect(token).to be_a(Ably::Models::TokenDetails)
1097
- capability_with_str_key = Ably::Auth::TOKEN_DEFAULTS.fetch(:capability)
1175
+ capability_with_str_key = { "*" => ["*"] } # Ably default is all capabilities
1098
1176
  capability = Hash[capability_with_str_key.keys.map(&:to_s).zip(capability_with_str_key.values)]
1099
1177
  expect(token.capability).to eq(capability)
1100
- expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl))
1178
+ expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + 60 * 60) # Ably default is 1hr
1101
1179
  expect(token.client_id).to eq(client_id)
1102
1180
  end
1103
1181
 
@@ -1107,6 +1185,54 @@ describe Ably::Auth do
1107
1185
  end
1108
1186
  end
1109
1187
 
1188
+ context 'when token expires' do
1189
+ before do
1190
+ stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
1191
+ stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
1192
+ end
1193
+
1194
+ after do
1195
+ WebMock.allow_net_connect!
1196
+ WebMock.disable!
1197
+ end
1198
+
1199
+ let(:client_options) { default_options.merge(use_token_auth: true, key: api_key, query_time: true, default_token_params: { ttl: 2 }) }
1200
+ let(:channel) { client.channels.get(random_str) }
1201
+ let(:token_expired_response) do
1202
+ {
1203
+ "error" => {
1204
+ "statusCode" => 401,
1205
+ "code" => 40140,
1206
+ "message" => "Token expired"
1207
+ }
1208
+ }
1209
+ end
1210
+
1211
+ it 'automatically renews the token (#RSA4b)' do
1212
+ expect(auth.current_token_details).to be_nil
1213
+ channel.publish 'event'
1214
+ token = auth.current_token_details
1215
+ expect(token).to_not be_nil
1216
+ sleep 2.5
1217
+ channel.publish 'event'
1218
+ expect(auth.current_token_details).to_not eql(token)
1219
+ end
1220
+
1221
+ it 'fails if the token renewal fails (#RSA4b)' do
1222
+ expect(auth.current_token_details).to be_nil
1223
+ channel.publish 'event'
1224
+ token = auth.current_token_details
1225
+ expect(token).to_not be_nil
1226
+ sleep 2.5
1227
+ WebMock.enable!
1228
+ WebMock.disable_net_connect!
1229
+ stub_request(:post, "https://#{environment}-rest.ably.io/keys/#{TestApp.instance.key_name}/requestToken").
1230
+ to_return(status: 401, body: token_expired_response.to_json, headers: { 'Content-Type' => 'application/json' })
1231
+ expect { channel.publish 'event' }.to raise_error Ably::Exceptions::TokenExpired
1232
+ expect(auth.current_token_details).to eql(token)
1233
+ end
1234
+ end
1235
+
1110
1236
  context 'when :client_id is provided in a token' do
1111
1237
  let(:client_id) { '123' }
1112
1238
  let(:token) do
@@ -1199,7 +1325,7 @@ describe Ably::Auth do
1199
1325
  end
1200
1326
 
1201
1327
  context 'deprecated #authorise' do
1202
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object) }
1328
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) }
1203
1329
  let(:custom_logger) do
1204
1330
  Class.new do
1205
1331
  def initialize
@@ -1207,8 +1333,11 @@ describe Ably::Auth do
1207
1333
  end
1208
1334
 
1209
1335
  [:fatal, :error, :warn, :info, :debug].each do |severity|
1210
- define_method severity do |message|
1211
- @messages << [severity, message]
1336
+ define_method severity do |message, &block|
1337
+ message_val = [message]
1338
+ message_val << block.call if block
1339
+
1340
+ @messages << [severity, message_val.compact.join(' ')]
1212
1341
  end
1213
1342
  end
1214
1343
 
@@ -84,7 +84,7 @@ describe Ably::Rest::Channel do
84
84
 
85
85
  context 'without adequate permissions on the channel' do
86
86
  let(:capability) { { onlyChannel: ['subscribe'] } }
87
- let(:client_options) { default_options.merge(use_token_auth: true, token_params: { capability: capability }) }
87
+ let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { capability: capability }) }
88
88
 
89
89
  it 'raises a permission error when publishing' do
90
90
  expect { channel.publish(name, data) }.to raise_error(Ably::Exceptions::UnauthorizedRequest, /not permitted/)
@@ -51,6 +51,14 @@ describe Ably::Rest::Client do
51
51
  end
52
52
  end
53
53
 
54
+ context 'with a non string :client_id' do
55
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: 1)) }
56
+
57
+ it 'raises an ArgumentError' do
58
+ expect { client.auth }.to raise_error ArgumentError, /client_id.*String/
59
+ end
60
+ end
61
+
54
62
  context 'with an invalid wildcard "*" :client_id' do
55
63
  it 'raises an exception' do
56
64
  expect { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: '*')) }.to raise_error ArgumentError
@@ -70,6 +78,21 @@ describe Ably::Rest::Client do
70
78
  end
71
79
  end
72
80
 
81
+ context 'with :default_token_params' do
82
+ let(:client) do
83
+ Ably::Rest::Client.new(client_options.merge(
84
+ default_token_params: { client_id: 'bob' },
85
+ use_token_auth: true,
86
+ key: api_key
87
+ ))
88
+ end
89
+
90
+ it 'overides the default token params (#TO3j11)' do
91
+ client.auth.authorize
92
+ expect(client.auth.client_id).to eql('bob')
93
+ end
94
+ end
95
+
73
96
  context 'with an :auth_callback Proc (clientId provided in library options instead of as a token_request param)' do
74
97
  let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback: Proc.new { token_request })) }
75
98
  let(:token_request) { client.auth.create_token_request({}, key_name: key_name, key_secret: key_secret) }
@@ -272,7 +295,7 @@ describe Ably::Rest::Client do
272
295
  context 'configured' do
273
296
  let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
274
297
 
275
- it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com' do
298
+ it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com (#RSC15a)' do
276
299
  hosts = []
277
300
  5.times do
278
301
  hosts << client.fallback_connection.host
@@ -281,7 +304,7 @@ describe Ably::Rest::Client do
281
304
  end
282
305
  end
283
306
 
284
- context 'when environment is NOT production' do
307
+ context 'when environment is NOT production (#RSC15b)' do
285
308
  let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) }
286
309
  let!(:default_host_request_stub) do
287
310
  stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
@@ -304,7 +327,8 @@ describe Ably::Rest::Client do
304
327
  environment: nil,
305
328
  key: api_key,
306
329
  http_max_retry_duration: max_retry_duration,
307
- http_max_retry_count: max_retry_count
330
+ http_max_retry_count: max_retry_count,
331
+ log_level: :error
308
332
  )
309
333
  end
310
334
 
@@ -327,7 +351,7 @@ describe Ably::Rest::Client do
327
351
  end
328
352
  end
329
353
 
330
- it "tries fallback hosts #{http_defaults.fetch(:max_retry_count)} times" do
354
+ it "tries fallback hosts #{http_defaults.fetch(:max_retry_count)} times (#RSC15b, #RSC15b)" do
331
355
  expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
332
356
  expect(default_host_request_stub).to have_been_requested
333
357
  expect(first_fallback_request_stub).to have_been_requested
@@ -366,6 +390,43 @@ describe Ably::Rest::Client do
366
390
  end
367
391
  end
368
392
 
393
+ context 'and first request to primary endpoint fails' do
394
+ let(:client_options) do
395
+ default_options.merge(
396
+ environment: nil,
397
+ key: api_key,
398
+ http_max_retry_duration: max_retry_duration,
399
+ http_max_retry_count: max_retry_count,
400
+ log_level: :error
401
+ )
402
+ end
403
+ let(:requests) { [] }
404
+ let!(:default_host_request_stub) do
405
+ stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
406
+ requests << true
407
+ if requests.count == 1
408
+ raise Faraday::ConnectionFailed.new('connection failure error message')
409
+ else
410
+ {
411
+ headers: { 'Content-Type' => 'application/json' },
412
+ status: 200,
413
+ body: {}.to_json
414
+ }
415
+ end
416
+ end
417
+ end
418
+
419
+ it "tries a fallback host, and for the next request tries the primary endpoint again (#RSC15e)" do
420
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
421
+ expect(default_host_request_stub).to have_been_requested
422
+ expect(first_fallback_request_stub).to have_been_requested
423
+ expect(requests.count).to eql(1)
424
+
425
+ publish_block.call
426
+ expect(requests.count).to eql(2)
427
+ end
428
+ end
429
+
369
430
  context 'and basic authentication fails' do
370
431
  let(:status) { 401 }
371
432
  let!(:default_host_request_stub) do
@@ -404,7 +465,7 @@ describe Ably::Rest::Client do
404
465
  stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
405
466
  end
406
467
 
407
- it 'attempts the fallback hosts as this is an authentication failure' do
468
+ it 'attempts the fallback hosts as this is an authentication failure (#RSC15d)' do
408
469
  expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
409
470
  expect(default_host_request_stub).to have_been_requested
410
471
  expect(first_fallback_request_stub).to have_been_requested
@@ -450,10 +511,10 @@ describe Ably::Rest::Client do
450
511
  end
451
512
 
452
513
  let(:client_options) {
453
- production_options.merge(fallback_hosts: custom_hosts)
514
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
454
515
  }
455
516
 
456
- it 'attempts the fallback hosts as this is an authentication failure (#RSC15b, #TO3k6)' do
517
+ it 'attempts the fallback hosts as this is an authentication failure (#RSC15b, #RSC15a, #TO3k6)' do
457
518
  expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
458
519
  expect(default_host_request_stub).to have_been_requested
459
520
  expect(first_fallback_request_stub).to have_been_requested
@@ -461,7 +522,7 @@ describe Ably::Rest::Client do
461
522
  end
462
523
  end
463
524
 
464
- context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
525
+ context 'with an empty array of fallback hosts provided (#RSC15b, #RSC15a, #TO3k6)' do
465
526
  let(:client_options) {
466
527
  production_options.merge(fallback_hosts: [])
467
528
  }
@@ -485,7 +546,7 @@ describe Ably::Rest::Client do
485
546
 
486
547
  context 'and timing out the primary host' do
487
548
  before do
488
- @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false)
549
+ @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
489
550
  @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
490
551
  if req.header["host"].first.include?(primary_host)
491
552
  @primary_host_requested = true
@@ -516,12 +577,13 @@ describe Ably::Rest::Client do
516
577
  port: port,
517
578
  tls: false,
518
579
  http_request_timeout: request_timeout,
519
- max_retry_duration: request_timeout * 3
580
+ max_retry_duration: request_timeout * 3,
581
+ log_level: :error
520
582
  )
521
583
  end
522
584
  let(:fail_fallback_request_count) { 1 }
523
585
 
524
- it 'tries one of the fallback hosts' do
586
+ it 'tries one of the fallback hosts (#RSC15d)' do
525
587
  client.channel(channel_name).publish('event', 'data')
526
588
  expect(@primary_host_requested).to be_truthy
527
589
  expect(@fallback_request_count).to eql(2)
@@ -537,12 +599,13 @@ describe Ably::Rest::Client do
537
599
  port: port,
538
600
  tls: false,
539
601
  http_request_timeout: request_timeout,
540
- max_retry_duration: request_timeout / 2
602
+ max_retry_duration: request_timeout / 2,
603
+ log_level: :error
541
604
  )
542
605
  end
543
606
  let(:fail_fallback_request_count) { 0 }
544
607
 
545
- it 'tries one of the fallback hosts' do
608
+ it 'tries one of the fallback hosts (#RSC15d)' do
546
609
  client.channel(channel_name).publish('event', 'data')
547
610
  expect(@primary_host_requested).to be_truthy
548
611
  expect(@fallback_request_count).to eql(1)
@@ -552,7 +615,7 @@ describe Ably::Rest::Client do
552
615
 
553
616
  context 'and failing the primary host' do
554
617
  before do
555
- @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false)
618
+ @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
556
619
  @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
557
620
  if req.header["host"].first.include?(primary_host)
558
621
  @primary_host_requested = true
@@ -580,7 +643,8 @@ describe Ably::Rest::Client do
580
643
  fallback_hosts: fallbacks,
581
644
  token: 'fake.token',
582
645
  port: port,
583
- tls: false
646
+ tls: false,
647
+ log_level: :error
584
648
  )
585
649
  end
586
650
  let(:fail_fallback_request_count) { 1 }
@@ -632,10 +696,10 @@ describe Ably::Rest::Client do
632
696
  end
633
697
 
634
698
  let(:client_options) {
635
- production_options.merge(fallback_hosts: custom_hosts)
699
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
636
700
  }
637
701
 
638
- it 'attempts the fallback hosts as this is an authentication failure' do
702
+ it 'attempts the fallback hosts as this is not an authentication failure' do
639
703
  expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
640
704
  expect(default_host_request_stub).to have_been_requested
641
705
  expect(first_fallback_request_stub).to have_been_requested
@@ -661,10 +725,6 @@ describe Ably::Rest::Client do
661
725
  stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
662
726
  end
663
727
 
664
- let(:client_options) {
665
- production_options.merge(fallback_hosts_use_default: true)
666
- }
667
-
668
728
  let!(:first_fallback_request_stub) do
669
729
  stub_request(:post, "https://#{Ably::FALLBACK_HOSTS[0]}#{path}").to_return(&fallback_block)
670
730
  end
@@ -674,7 +734,7 @@ describe Ably::Rest::Client do
674
734
  end
675
735
 
676
736
  let(:client_options) {
677
- production_options.merge(fallback_hosts: custom_hosts)
737
+ production_options.merge(fallback_hosts: custom_hosts, log_level: :error)
678
738
  }
679
739
 
680
740
  it 'attempts the default fallback hosts as this is an authentication failure' do
@@ -758,16 +818,16 @@ describe Ably::Rest::Client do
758
818
  expect(client.http_defaults[:open_timeout]).to eql(4)
759
819
  end
760
820
 
761
- specify '#http_request_timeout is 15s' do
762
- expect(client.http_defaults[:request_timeout]).to eql(15)
821
+ specify '#http_request_timeout is 10s' do
822
+ expect(client.http_defaults[:request_timeout]).to eql(10)
763
823
  end
764
824
 
765
825
  specify '#http_max_retry_count is 3' do
766
826
  expect(client.http_defaults[:max_retry_count]).to eql(3)
767
827
  end
768
828
 
769
- specify '#http_max_retry_duration is 10s' do
770
- expect(client.http_defaults[:max_retry_duration]).to eql(10)
829
+ specify '#http_max_retry_duration is 15s' do
830
+ expect(client.http_defaults[:max_retry_duration]).to eql(15)
771
831
  end
772
832
  end
773
833
 
@@ -848,9 +908,10 @@ describe Ably::Rest::Client do
848
908
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
849
909
  end
850
910
 
851
- it 'sends a protocol version and lib version header' do
911
+ it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
852
912
  client.channels.get('foo').publish("event")
853
913
  expect(publish_message_stub).to have_been_requested
914
+ expect(Ably::PROTOCOL_VERSION).to eql('1.0')
854
915
  end
855
916
  end
856
917
  end