ably-rest 0.8.5 → 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/SPEC.md +1380 -631
- data/ably-rest.gemspec +11 -5
- data/lib/submodules/ably-ruby/.travis.yml +1 -1
- data/lib/submodules/ably-ruby/CHANGELOG.md +42 -48
- data/lib/submodules/ably-ruby/ably.gemspec +7 -1
- data/lib/submodules/ably-ruby/lib/ably.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +155 -47
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +2 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +14 -4
- data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +13 -7
- data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +1 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +3 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +6 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +15 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +10 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +62 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +58 -54
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +18 -5
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +9 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +32 -14
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +251 -11
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +12 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +316 -24
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +93 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +284 -60
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +45 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +4 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +181 -49
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +13 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +222 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +132 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -28
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +7 -7
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +41 -17
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -0
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +16 -0
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +60 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +45 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +6 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +5 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +5 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +5 -1
- 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:
|
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 :
|
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
|
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/#{
|
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
|
-
|
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(:
|
158
|
-
let(:
|
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
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
206
|
-
|
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 '
|
211
|
-
|
212
|
-
|
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
|
-
|
216
|
-
|
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(:
|
253
|
-
let(:
|
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 #{
|
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 #{
|
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
|
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 #{
|
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 }
|