ably-rest 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/SPEC.md +1380 -631
  4. data/ably-rest.gemspec +11 -5
  5. data/lib/submodules/ably-ruby/.travis.yml +1 -1
  6. data/lib/submodules/ably-ruby/CHANGELOG.md +42 -48
  7. data/lib/submodules/ably-ruby/ably.gemspec +7 -1
  8. data/lib/submodules/ably-ruby/lib/ably.rb +2 -0
  9. data/lib/submodules/ably-ruby/lib/ably/auth.rb +155 -47
  10. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +2 -0
  11. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +2 -3
  12. data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +54 -0
  13. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +14 -4
  14. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +13 -7
  15. data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +1 -2
  16. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +3 -2
  17. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +1 -3
  18. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +2 -2
  19. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +6 -0
  20. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +15 -4
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -0
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +10 -3
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +62 -6
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +58 -54
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +18 -5
  27. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +9 -1
  28. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +32 -14
  29. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  30. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  31. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +251 -11
  32. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +12 -2
  33. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +316 -24
  34. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +93 -1
  35. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
  36. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +284 -60
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +45 -6
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +4 -0
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +181 -49
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +13 -0
  41. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +222 -4
  42. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +132 -1
  43. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -28
  44. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +7 -7
  45. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +10 -0
  46. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +41 -17
  47. data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -0
  48. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +16 -0
  49. data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +60 -0
  50. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +45 -0
  51. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -1
  52. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +6 -5
  53. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +5 -1
  54. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +5 -1
  55. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +5 -1
  56. metadata +57 -13
@@ -22,6 +22,19 @@ describe Ably::Realtime::Client, '#time', :event_machine do
22
22
  stop_reactor
23
23
  end
24
24
  end
25
+
26
+ context 'with reconfigured HTTP timeout' do
27
+ let(:client) do
28
+ auto_close Ably::Realtime::Client.new(http_request_timeout: 0.0001, key: api_key, environment: environment, protocol: protocol, log_level: :fatal)
29
+ end
30
+
31
+ it 'should raise a timeout exception' do
32
+ client.time.errback do |error|
33
+ expect(error).to be_a Ably::Exceptions::ConnectionTimeout
34
+ stop_reactor
35
+ end
36
+ end
37
+ end
25
38
  end
26
39
  end
27
40
  end
@@ -197,6 +197,85 @@ describe Ably::Auth do
197
197
  end
198
198
  end
199
199
 
200
+ context 'with :auth_url option merging', :webmock do
201
+ context 'with existing configured auth options' do
202
+ let(:client_id) { random_str }
203
+ let(:auth_url) { "https://www.fictitious.com/#{random_str}" }
204
+ let(:auth_method) { :get }
205
+ let(:auth_params) { { key: 'val', client_id: 'isOverridenByClient' } }
206
+ let(:auth_headers) { { 'Header-X' => 'val1', 'Header-Y' => 'val2' } }
207
+
208
+ let(:base_options) do
209
+ default_options.merge(
210
+ client_id: client_id,
211
+ auth_url: auth_url,
212
+ auth_params: auth_params,
213
+ auth_headers: auth_headers
214
+ )
215
+ end
216
+ let(:client_options) { base_options }
217
+
218
+ let!(:auth_request) do
219
+ stub_request(auth_method, auth_url).to_return(
220
+ :status => 201,
221
+ :body => '123123.12312321321312321', # token string
222
+ :headers => { 'Content-Type' => 'text/plain' }
223
+ )
224
+ end
225
+
226
+ let(:request_token_auth_options) { Hash.new }
227
+ let(:request_token_token_params) { Hash.new }
228
+ after do
229
+ client.auth.request_token(request_token_token_params, request_token_auth_options)
230
+ expect(auth_request).to have_been_requested
231
+ end
232
+
233
+ context 'using unspecified :auth_method' do
234
+ it 'requests a token using a GET request with provided headers, and merges client_id into auth_params' do
235
+ auth_request.with(headers: auth_headers)
236
+ auth_request.with(query: auth_params.merge(client_id: client_id))
237
+ end
238
+
239
+ context 'with provided token_params' do
240
+ let(:request_token_token_params) { { client_id: 'custom', key2: 'val2' } }
241
+
242
+ it 'merges provided token_params with existing auth_params and client_id' do
243
+ auth_request.with(query: auth_params.merge(client_id: client_id).merge(request_token_token_params))
244
+ end
245
+ end
246
+
247
+ context 'with provided auth option auth_params and auth_headers' do
248
+ let(:request_token_auth_options) { { auth_params: {}, auth_headers: {} } }
249
+
250
+ it 'replaces any preconfigured auth_params' do
251
+ auth_request.with(query: {}.merge(client_id: client_id))
252
+ auth_request.with(headers: { 'Accept'=>'*/*' }) # mock library needs at least one header, accept is default
253
+ end
254
+ end
255
+ end
256
+
257
+ context 'using :get :auth_method and query params in the URL' do
258
+ let(:auth_method) { :get }
259
+ let(:client_options) { base_options.merge(auth_method: :get, auth_url: "#{auth_url}?urlparam=true") }
260
+
261
+ it 'requests a token using a GET request with provided headers, and merges client_id into auth_params and existing URL querystring into new URL querystring' do
262
+ auth_request.with(headers: auth_headers)
263
+ auth_request.with(query: auth_params.merge(client_id: client_id).merge(urlparam: 'true'))
264
+ end
265
+ end
266
+
267
+ context 'using :post :auth_method' do
268
+ let(:auth_method) { :post }
269
+ let(:client_options) { base_options.merge(auth_method: :post) }
270
+
271
+ it 'requests a token using a POST request with provided headers, and merges client_id into auth_params as form-encoded post data' do
272
+ auth_request.with(headers: auth_headers)
273
+ auth_request.with(body: auth_params.merge(client_id: client_id))
274
+ end
275
+ end
276
+ end
277
+ end
278
+
200
279
  context 'with :auth_url option', :webmock do
201
280
  let(:auth_url) { 'https://www.fictitious.com/get_token' }
202
281
  let(:auth_url_response) { { keyName: key_name }.to_json }
@@ -365,6 +444,18 @@ describe Ably::Auth do
365
444
  it 'uses the token request returned from the callback when requesting a new token' do
366
445
  expect(request_token.client_id).to eql(client_id)
367
446
  end
447
+
448
+ context 'when authorised' do
449
+ before { auth.authorise(token_params, auth_callback: auth_callback) }
450
+
451
+ it "sets Auth#client_id to the new token's client_id" do
452
+ expect(auth.client_id).to eql(client_id)
453
+ end
454
+
455
+ it "sets Client#client_id to the new token's client_id" do
456
+ expect(client.client_id).to eql(client_id)
457
+ end
458
+ end
368
459
  end
369
460
 
370
461
  context 'that returns a TokenDetails JSON object' do
@@ -377,7 +468,11 @@ describe Ably::Auth do
377
468
  let(:capability_str) { JSON.dump(capability) }
378
469
 
379
470
  let!(:token_details) do
380
- auth.request_token(token_params, auth_callback: Proc.new do |token_params_arg|
471
+ auth.request_token(token_params, auth_callback: auth_callback)
472
+ end
473
+
474
+ let(:auth_callback) do
475
+ Proc.new do |token_params_arg|
381
476
  @block_called = true
382
477
  @block_params = token_params_arg
383
478
  {
@@ -388,7 +483,7 @@ describe Ably::Auth do
388
483
  'expires' => expires.to_i * 1000,
389
484
  'capability'=> capability_str
390
485
  }
391
- end)
486
+ end
392
487
  end
393
488
 
394
489
  it 'calls the Proc when authenticating to obtain the request token' do
@@ -404,6 +499,18 @@ describe Ably::Auth do
404
499
  expect(token_details.issued).to be_within(1).of(issued)
405
500
  expect(token_details.capability).to eql(capability)
406
501
  end
502
+
503
+ context 'when authorised' do
504
+ before { auth.authorise(token_params, auth_callback: auth_callback) }
505
+
506
+ it "sets Auth#client_id to the new token's client_id" do
507
+ expect(auth.client_id).to eql(client_id)
508
+ end
509
+
510
+ it "sets Client#client_id to the new token's client_id" do
511
+ expect(client.client_id).to eql(client_id)
512
+ end
513
+ end
407
514
  end
408
515
 
409
516
  context 'that returns a TokenDetails object' do
@@ -602,6 +709,37 @@ describe Ably::Auth do
602
709
  end
603
710
  end
604
711
  end
712
+
713
+ context 'with an explicit ClientOptions client_id' do
714
+ let(:client_id) { random_str }
715
+ let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id) }
716
+ let(:auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')) }
717
+
718
+ context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
719
+ let(:auth_token_object) { auth_client.auth.request_token }
720
+
721
+ it 'rejects a TokenDetails object with an incompatible client_id and raises an exception' do
722
+ expect { client.auth.authorise({}, force: true) }.to raise_error Ably::Exceptions::IncompatibleClientId
723
+ end
724
+ end
725
+
726
+ context 'and an incompatible client_id in a TokenRequest object passed to the auth callback and raises an exception' do
727
+ let(:auth_token_object) { auth_client.auth.create_token_request }
728
+
729
+ it 'rejects a TokenRequests object with an incompatible client_id and raises an exception' do
730
+ expect { client.auth.authorise({}, force: true) }.to raise_error Ably::Exceptions::IncompatibleClientId
731
+ end
732
+ end
733
+
734
+ context 'and a token string without any retrievable client_id' do
735
+ let(:auth_token_object) { auth_client.auth.request_token(client_id: 'different').token }
736
+
737
+ it 'rejects a TokenRequests object with an incompatible client_id and raises an exception' do
738
+ client.auth.authorise({}, force: true)
739
+ expect(client.client_id).to eql(client_id)
740
+ end
741
+ end
742
+ end
605
743
  end
606
744
 
607
745
  describe '#create_token_request' do
@@ -799,7 +937,7 @@ describe Ably::Auth do
799
937
  end
800
938
  end
801
939
 
802
- context 'when implicit as a result of using :client id' do
940
+ context 'when implicit as a result of using :client_id' do
803
941
  let(:client_id) { '999' }
804
942
  let(:client) do
805
943
  Ably::Rest::Client.new(key: api_key, client_id: client_id, environment: environment, protocol: protocol)
@@ -849,11 +987,91 @@ describe Ably::Auth do
849
987
  expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl))
850
988
  expect(token.client_id).to eq(client_id)
851
989
  end
990
+
991
+ specify '#client_id contains the client_id' do
992
+ expect(client.auth.client_id).to eql(client_id)
993
+ end
994
+ end
995
+ end
996
+
997
+ context 'when :client_id is provided in a token' do
998
+ let(:client_id) { '123' }
999
+ let(:token) do
1000
+ Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol).auth.request_token(client_id: client_id)
1001
+ end
1002
+ let(:client) do
1003
+ Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol)
1004
+ end
1005
+
1006
+ specify '#client_id contains the client_id' do
1007
+ expect(client.auth.client_id).to eql(client_id)
1008
+ end
1009
+ end
1010
+ end
1011
+
1012
+ describe '#client_id_validated?' do
1013
+ let(:auth) { Ably::Rest::Client.new(default_options.merge(key: api_key)).auth }
1014
+
1015
+ context 'when using basic auth' do
1016
+ let(:client_options) { default_options.merge(key: api_key) }
1017
+
1018
+ it 'is false as basic auth users do not have an identity' do
1019
+ expect(client.auth).to_not be_client_id_validated
1020
+ end
1021
+ end
1022
+
1023
+ context 'when using a token auth string for a token with a client_id' do
1024
+ let(:client_options) { default_options.merge(token: auth.request_token(client_id: 'present').token) }
1025
+
1026
+ it 'is false as identification is not possible from an opaque token string' do
1027
+ expect(client.auth).to_not be_client_id_validated
1028
+ end
1029
+ end
1030
+
1031
+ context 'when using a token' do
1032
+ context 'with a client_id' do
1033
+ let(:client_options) { default_options.merge(token: auth.request_token(client_id: 'present')) }
1034
+
1035
+ it 'is true' do
1036
+ expect(client.auth).to be_client_id_validated
1037
+ end
1038
+ end
1039
+
1040
+ context 'with no client_id (anonymous)' do
1041
+ let(:client_options) { default_options.merge(token: auth.request_token(client_id: nil)) }
1042
+
1043
+ it 'is true' do
1044
+ expect(client.auth).to be_client_id_validated
1045
+ end
1046
+ end
1047
+
1048
+ context 'with a wildcard client_id (anonymous)' do
1049
+ let(:client_options) { default_options.merge(token: auth.request_token(client_id: '*')) }
1050
+
1051
+ it 'is false' do
1052
+ expect(client.auth).to be_client_id_validated
1053
+ end
1054
+ end
1055
+ end
1056
+
1057
+ context 'when using a token request with a client_id' do
1058
+ let(:client_options) { default_options.merge(token: auth.create_token_request(client_id: 'present')) }
1059
+
1060
+ it 'is not true as identification is not confirmed until authenticated' do
1061
+ expect(client.auth).to_not be_client_id_validated
1062
+ end
1063
+
1064
+ context 'after authentication' do
1065
+ before { client.channel('test').publish('a') }
1066
+
1067
+ it 'is true as identification is completed during implicit authentication' do
1068
+ expect(client.auth).to be_client_id_validated
1069
+ end
852
1070
  end
853
1071
  end
854
1072
  end
855
1073
 
856
- context 'when using an :key and basic auth' do
1074
+ context 'when using a :key and basic auth' do
857
1075
  specify '#using_token_auth? is false' do
858
1076
  expect(auth).to_not be_using_token_auth
859
1077
  end
@@ -125,6 +125,137 @@ describe Ably::Rest::Channel do
125
125
  end
126
126
  end
127
127
  end
128
+
129
+ context 'identified clients' do
130
+ context 'when authenticated with a wildcard client_id' do
131
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*') }
132
+ let(:client_options) { default_options.merge(key: nil, token: token) }
133
+ let(:client) { Ably::Rest::Client.new(client_options) }
134
+ let(:channel) { client.channels.get(channel_name) }
135
+
136
+ context 'with a valid client_id in the message' do
137
+ it 'succeeds' do
138
+ channel.publish([name: 'event', client_id: 'valid'])
139
+ channel.history do |messages|
140
+ expect(messages.first.client_id).to eql('valid')
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'with a wildcard client_id in the message' do
146
+ it 'throws an exception' do
147
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
148
+ end
149
+ end
150
+
151
+ context 'with an empty client_id in the message' do
152
+ it 'succeeds and publishes without a client_id' do
153
+ channel.publish([name: 'event', client_id: nil])
154
+ channel.history do |messages|
155
+ expect(messages.first.client_id).to eql('valid')
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when authenticated with a Token string with an implicit client_id' do
162
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: 'valid').token }
163
+ let(:client_options) { default_options.merge(key: nil, token: token) }
164
+ let(:client) { Ably::Rest::Client.new(client_options) }
165
+ let(:channel) { client.channels.get(channel_name) }
166
+
167
+ context 'without having a confirmed identity' do
168
+ context 'with a valid client_id in the message' do
169
+ it 'succeeds' do
170
+ channel.publish([name: 'event', client_id: 'valid'])
171
+ channel.history do |messages|
172
+ expect(messages.first.client_id).to eql('valid')
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'with an invalid client_id in the message' do
178
+ it 'succeeds in the client library but then fails when published to Ably' do
179
+ expect { channel.publish([name: 'event', client_id: 'invalid']) }.to raise_error Ably::Exceptions::InvalidRequest, /mismatched clientId/
180
+ end
181
+ end
182
+
183
+ context 'with an empty client_id in the message' do
184
+ it 'succeeds and publishes with an implicit client_id' do
185
+ channel.publish([name: 'event', client_id: nil])
186
+ channel.history do |messages|
187
+ expect(messages.first.client_id).to eql('valid')
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ context 'when authenticated with TokenDetails with a valid client_id' do
195
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: 'valid') }
196
+ let(:client_options) { default_options.merge(key: nil, token: token) }
197
+ let(:client) { Ably::Rest::Client.new(client_options) }
198
+ let(:channel) { client.channels.get(channel_name) }
199
+
200
+ context 'with a valid client_id in the message' do
201
+ it 'succeeds' do
202
+ channel.publish([name: 'event', client_id: 'valid'])
203
+ channel.history do |messages|
204
+ expect(messages.first.client_id).to eql('valid')
205
+ end
206
+ end
207
+ end
208
+
209
+ context 'with a wildcard client_id in the message' do
210
+ it 'throws an exception' do
211
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
212
+ end
213
+ end
214
+
215
+ context 'with an invalid client_id in the message' do
216
+ it 'throws an exception' do
217
+ expect { channel.publish([name: 'event', client_id: 'invalid']) }.to raise_error Ably::Exceptions::IncompatibleClientId
218
+ end
219
+ end
220
+
221
+ context 'with an empty client_id in the message' do
222
+ it 'succeeds and publishes with an implicit client_id' do
223
+ channel.publish([name: 'event', client_id: nil])
224
+ channel.history do |messages|
225
+ expect(messages.first.client_id).to eql('valid')
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'when anonymous and no client_id' do
232
+ let(:token) { Ably::Rest::Client.new(default_options).auth.request_token(client_id: nil) }
233
+ let(:client_options) { default_options.merge(key: nil, token: token) }
234
+ let(:client) { Ably::Rest::Client.new(client_options) }
235
+ let(:channel) { client.channels.get(channel_name) }
236
+
237
+ context 'with a client_id in the message' do
238
+ it 'throws an exception' do
239
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
240
+ end
241
+ end
242
+
243
+ context 'with a wildcard client_id in the message' do
244
+ it 'throws an exception' do
245
+ expect { channel.publish([name: 'event', client_id: '*']) }.to raise_error Ably::Exceptions::IncompatibleClientId
246
+ end
247
+ end
248
+
249
+ context 'with an empty client_id in the message' do
250
+ it 'succeeds and publishes with an implicit client_id' do
251
+ channel.publish([name: 'event', client_id: nil])
252
+ channel.history do |messages|
253
+ expect(messages.first.client_id).to be_nil
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
128
259
  end
129
260
 
130
261
  describe '#history' do
@@ -246,7 +377,7 @@ describe Ably::Rest::Channel do
246
377
  let!(:history_stub) {
247
378
  query_params = default_history_options
248
379
  .merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
249
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{query_params}").
380
+ stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/messages?#{query_params}").
250
381
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
251
382
  }
252
383
 
@@ -8,7 +8,7 @@ describe Ably::Rest::Client do
8
8
 
9
9
  let(:client) { Ably::Rest::Client.new(client_options) }
10
10
 
11
- connection_retry = Ably::Rest::Client::CONNECTION_RETRY
11
+ http_defaults = Ably::Rest::Client::HTTP_DEFAULTS
12
12
 
13
13
  def encode64(text)
14
14
  Base64.encode64(text).gsub("\n", '')
@@ -50,6 +50,12 @@ describe Ably::Rest::Client do
50
50
  end
51
51
  end
52
52
 
53
+ context 'with an invalid wildcard "*" :client_id' do
54
+ it 'raises an exception' do
55
+ expect { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: '*')) }.to raise_error ArgumentError
56
+ end
57
+ end
58
+
53
59
  context 'with an :auth_callback Proc' do
54
60
  let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
55
61
 
@@ -83,7 +89,7 @@ describe Ably::Rest::Client do
83
89
 
84
90
  context 'before any REST request' do
85
91
  before do
86
- expect(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, hash_including(:auth_method => :get)).once do
92
+ expect(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, hash_including(:auth_method => :get), anything).once do
87
93
  client.auth.create_token_request(client_id: client_id)
88
94
  end
89
95
  end
@@ -154,8 +160,10 @@ describe Ably::Rest::Client do
154
160
  send("token_request_#{@request_index > 2 ? 'next' : @request_index}")
155
161
  end))
156
162
  end
157
- let(:token_request_1) { client.auth.create_token_request({}, token_request_options.merge(client_id: random_str)) }
158
- let(:token_request_2) { client.auth.create_token_request({}, token_request_options.merge(client_id: random_str)) }
163
+ let(:client_id) { random_str }
164
+ let(:client_id_2) { client_id }
165
+ let(:token_request_1) { client.auth.create_token_request({}, token_request_options.merge(client_id: client_id)) }
166
+ let(:token_request_2) { client.auth.create_token_request({}, token_request_options.merge(client_id: client_id_2)) }
159
167
 
160
168
  # If token expires against whilst runnig tests in a slower CI environment then use this token
161
169
  let(:token_request_next) { client.auth.create_token_request({}, token_request_options.merge(client_id: random_str)) }
@@ -177,6 +185,16 @@ describe Ably::Rest::Client do
177
185
  expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
178
186
  expect(client.auth.current_token_details.client_id).to eql(token_request_2.client_id)
179
187
  end
188
+
189
+ context 'with a different client_id in the subsequent token' do
190
+ let(:client_id_2) { random_str }
191
+
192
+ it 'fails to authenticate and raises an exception' do
193
+ client.channel('channel_name').publish('event', 'message')
194
+ sleep 1
195
+ expect { client.channel('channel_name').publish('event', 'message') }.to raise_error(Ably::Exceptions::IncompatibleClientId)
196
+ end
197
+ end
180
198
  end
181
199
 
182
200
  context 'when token has not expired' do
@@ -195,25 +213,53 @@ describe Ably::Rest::Client do
195
213
  end
196
214
 
197
215
  context 'connection transport' do
198
- let(:client_options) { default_options.merge(key: api_key) }
216
+ context 'defaults' do
217
+ let(:client_options) { default_options.merge(key: api_key) }
218
+
219
+ context 'for default host' do
220
+ it "is configured to timeout connection opening in #{http_defaults.fetch(:open_timeout)} seconds" do
221
+ expect(client.connection.options.open_timeout).to eql(http_defaults.fetch(:open_timeout))
222
+ end
199
223
 
200
- context 'for default host' do
201
- it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
202
- expect(client.connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
224
+ it "is configured to timeout connection requests in #{http_defaults.fetch(:request_timeout)} seconds" do
225
+ expect(client.connection.options.timeout).to eql(http_defaults.fetch(:request_timeout))
226
+ end
203
227
  end
204
228
 
205
- it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
206
- expect(client.connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
229
+ context 'for the fallback hosts' do
230
+ it "is configured to timeout connection opening in #{http_defaults.fetch(:open_timeout)} seconds" do
231
+ expect(client.fallback_connection.options.open_timeout).to eql(http_defaults.fetch(:open_timeout))
232
+ end
233
+
234
+ it "is configured to timeout connection requests in #{http_defaults.fetch(:request_timeout)} seconds" do
235
+ expect(client.fallback_connection.options.timeout).to eql(http_defaults.fetch(:request_timeout))
236
+ end
207
237
  end
208
238
  end
209
239
 
210
- context 'for the fallback hosts' do
211
- it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
212
- expect(client.fallback_connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
240
+ context 'with custom http_open_timeout and http_request_timeout options' do
241
+ let(:http_open_timeout) { 999 }
242
+ let(:http_request_timeout) { 666 }
243
+ let(:client_options) { default_options.merge(key: api_key, http_open_timeout: http_open_timeout, http_request_timeout: http_request_timeout) }
244
+
245
+ context 'for default host' do
246
+ it 'is configured to use custom open timeout' do
247
+ expect(client.connection.options.open_timeout).to eql(http_open_timeout)
248
+ end
249
+
250
+ it 'is configured to use custom request timeout' do
251
+ expect(client.connection.options.timeout).to eql(http_request_timeout)
252
+ end
213
253
  end
214
254
 
215
- it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
216
- expect(client.fallback_connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
255
+ context 'for the fallback hosts' do
256
+ it "is configured to timeout connection opening in #{http_defaults.fetch(:open_timeout)} seconds" do
257
+ expect(client.fallback_connection.options.open_timeout).to eql(http_open_timeout)
258
+ end
259
+
260
+ it "is configured to timeout connection requests in #{http_defaults.fetch(:request_timeout)} seconds" do
261
+ expect(client.fallback_connection.options.timeout).to eql(http_request_timeout)
262
+ end
217
263
  end
218
264
  end
219
265
  end
@@ -249,19 +295,20 @@ describe Ably::Rest::Client do
249
295
 
250
296
  context 'when environment is production' do
251
297
  let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
252
- let(:max_attempts) { 2 }
253
- let(:cumulative_timeout) { 0.5 }
254
- let(:client_options) { default_options.merge(environment: nil, key: api_key) }
298
+ let(:max_retry_count) { 2 }
299
+ let(:max_retry_duration) { 0.5 }
255
300
  let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
301
+ let(:client_options) do
302
+ default_options.merge(
303
+ environment: nil,
304
+ key: api_key,
305
+ http_max_retry_duration: max_retry_duration,
306
+ http_max_retry_count: max_retry_count
307
+ )
308
+ end
256
309
 
257
310
  before do
258
311
  stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
259
- stub_const 'Ably::Rest::Client::CONNECTION_RETRY', {
260
- single_request_open_timeout: 4,
261
- single_request_timeout: 15,
262
- cumulative_request_open_timeout: cumulative_timeout,
263
- max_retry_attempts: max_attempts
264
- }
265
312
  end
266
313
 
267
314
  let!(:first_fallback_request_stub) do
@@ -279,17 +326,17 @@ describe Ably::Rest::Client do
279
326
  end
280
327
  end
281
328
 
282
- it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
329
+ it "tries fallback hosts #{http_defaults.fetch(:max_retry_count)} times" do
283
330
  expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
284
331
  expect(default_host_request_stub).to have_been_requested
285
332
  expect(first_fallback_request_stub).to have_been_requested
286
333
  expect(second_fallback_request_stub).to have_been_requested
287
334
  end
288
335
 
289
- context "and the total request time exeeds #{connection_retry[:cumulative_request_open_timeout]} seconds" do
336
+ context "and the total request time exeeds #{http_defaults.fetch(:max_retry_duration)} seconds" do
290
337
  let!(:default_host_request_stub) do
291
338
  stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
292
- sleep cumulative_timeout * 1.5
339
+ sleep max_retry_duration * 1.5
293
340
  raise Faraday::TimeoutError.new('timeout error message')
294
341
  end
295
342
  end
@@ -310,7 +357,7 @@ describe Ably::Rest::Client do
310
357
  end
311
358
  end
312
359
 
313
- it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
360
+ it "tries fallback hosts #{http_defaults.fetch(:max_retry_count)} times" do
314
361
  expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
315
362
  expect(default_host_request_stub).to have_been_requested
316
363
  expect(first_fallback_request_stub).to have_been_requested
@@ -429,6 +476,60 @@ describe Ably::Rest::Client do
429
476
  end
430
477
  end
431
478
 
479
+ context 'HTTP configuration options' do
480
+ let(:client_options) { default_options.merge(key: api_key) }
481
+
482
+ context 'defaults' do
483
+ specify '#http_open_timeout is 4s' do
484
+ expect(client.http_defaults[:open_timeout]).to eql(4)
485
+ end
486
+
487
+ specify '#http_request_timeout is 15s' do
488
+ expect(client.http_defaults[:request_timeout]).to eql(15)
489
+ end
490
+
491
+ specify '#http_max_retry_count is 3' do
492
+ expect(client.http_defaults[:max_retry_count]).to eql(3)
493
+ end
494
+
495
+ specify '#http_max_retry_duration is 10s' do
496
+ expect(client.http_defaults[:max_retry_duration]).to eql(10)
497
+ end
498
+ end
499
+
500
+ context 'configured' do
501
+ let(:client_options) do
502
+ default_options.merge(
503
+ key: api_key,
504
+ http_open_timeout: 1,
505
+ http_request_timeout: 2,
506
+ http_max_retry_count: 33,
507
+ http_max_retry_duration: 4
508
+ )
509
+ end
510
+
511
+ specify '#http_open_timeout uses provided value' do
512
+ expect(client.http_defaults[:open_timeout]).to eql(1)
513
+ end
514
+
515
+ specify '#http_request_timeout uses provided value' do
516
+ expect(client.http_defaults[:request_timeout]).to eql(2)
517
+ end
518
+
519
+ specify '#http_max_retry_count uses provided value' do
520
+ expect(client.http_defaults[:max_retry_count]).to eql(33)
521
+ end
522
+
523
+ specify '#http_max_retry_duration uses provided value' do
524
+ expect(client.http_defaults[:max_retry_duration]).to eql(4)
525
+ end
526
+ end
527
+
528
+ it 'is frozen' do
529
+ expect(client.http_defaults).to be_frozen
530
+ end
531
+ end
532
+
432
533
  context '#auth' do
433
534
  let(:dummy_auth_url) { 'http://dummy.url' }
434
535
  let(:unique_ttl) { 1234 }