ably-rest 1.0.5 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +6 -3
- data/CHANGELOG.md +1 -1
- data/LICENSE +1 -1
- data/README.md +26 -7
- data/SPEC.md +2003 -1605
- data/ably-rest.gemspec +4 -2
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -8
- data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
- data/lib/submodules/ably-ruby/LICENSE +1 -3
- data/lib/submodules/ably-ruby/README.md +12 -7
- data/lib/submodules/ably-ruby/Rakefile +32 -0
- data/lib/submodules/ably-ruby/SPEC.md +1277 -835
- data/lib/submodules/ably-ruby/ably.gemspec +17 -11
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +12 -12
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -4
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
- data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +45 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +54 -18
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +253 -49
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +62 -51
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +318 -74
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -9
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
- data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
- data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
- data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
- data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +30 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- metadata +51 -10
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Ably::Realtime::Push, :event_machine do
|
5
|
+
vary_by_protocol do
|
6
|
+
let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
|
7
|
+
let(:client_options) { default_options }
|
8
|
+
let(:client) do
|
9
|
+
Ably::Realtime::Client.new(client_options)
|
10
|
+
end
|
11
|
+
subject { client.push }
|
12
|
+
|
13
|
+
describe '#activate' do
|
14
|
+
it 'raises an unsupported exception' do
|
15
|
+
expect { subject.activate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
|
16
|
+
stop_reactor
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#deactivate' do
|
21
|
+
it 'raises an unsupported exception' do
|
22
|
+
expect { subject.deactivate('foo') }.to raise_error(Ably::Exceptions::PushNotificationsNotSupported)
|
23
|
+
stop_reactor
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -472,7 +472,7 @@ describe Ably::Auth do
|
|
472
472
|
end
|
473
473
|
|
474
474
|
let(:auth_callback) do
|
475
|
-
|
475
|
+
lambda do |token_params_arg|
|
476
476
|
@block_called = true
|
477
477
|
@block_params = token_params_arg
|
478
478
|
{
|
@@ -486,7 +486,7 @@ describe Ably::Auth do
|
|
486
486
|
end
|
487
487
|
end
|
488
488
|
|
489
|
-
it 'calls the
|
489
|
+
it 'calls the lambda when authenticating to obtain the request token' do
|
490
490
|
expect(@block_called).to eql(true)
|
491
491
|
expect(@block_params).to include(token_params)
|
492
492
|
end
|
@@ -517,7 +517,7 @@ describe Ably::Auth do
|
|
517
517
|
let(:client_id) { random_str }
|
518
518
|
|
519
519
|
let!(:token_details) do
|
520
|
-
auth.request_token({}, auth_callback:
|
520
|
+
auth.request_token({}, auth_callback: lambda do |token_params|
|
521
521
|
auth.create_token_request(client_id: client_id)
|
522
522
|
end)
|
523
523
|
end
|
@@ -533,7 +533,7 @@ describe Ably::Auth do
|
|
533
533
|
let(:token) { second_client.auth.request_token.token }
|
534
534
|
|
535
535
|
let!(:token_details) do
|
536
|
-
auth.request_token({}, auth_callback:
|
536
|
+
auth.request_token({}, auth_callback: lambda do |token_params|
|
537
537
|
token
|
538
538
|
end)
|
539
539
|
end
|
@@ -689,7 +689,7 @@ describe Ably::Auth do
|
|
689
689
|
|
690
690
|
context 'AuthOptions argument' do
|
691
691
|
let(:token_ttl) { 2 }
|
692
|
-
let(:auth_callback) {
|
692
|
+
let(:auth_callback) { lambda do |token_params|
|
693
693
|
auth.create_token_request(ttl: token_ttl)
|
694
694
|
end }
|
695
695
|
let(:default_auth_options) { { auth_callback: auth_callback } }
|
@@ -717,7 +717,7 @@ describe Ably::Auth do
|
|
717
717
|
end
|
718
718
|
|
719
719
|
it 'updates Auth#options attribute with an immutable hash' do
|
720
|
-
auth.authorize(nil, auth_callback:
|
720
|
+
auth.authorize(nil, auth_callback: lambda { |token_params| '1231232.12321:12321312' })
|
721
721
|
expect { auth.options['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
|
722
722
|
end
|
723
723
|
|
@@ -758,22 +758,23 @@ describe Ably::Auth do
|
|
758
758
|
end
|
759
759
|
|
760
760
|
it 'updates the persisted auth options that are then used for subsequent authorize requests' do
|
761
|
-
|
762
|
-
auth.
|
763
|
-
|
761
|
+
auth_url = "https://echo.ably.io/?type=text&body=#{auth.request_token.token}"
|
762
|
+
expect(auth.options[:auth_url]).to be_nil
|
763
|
+
auth.authorize({}, auth_url: auth_url)
|
764
|
+
expect(auth.options[:auth_url]).to eql(auth_url)
|
764
765
|
end
|
765
766
|
|
766
|
-
context 'with a
|
767
|
+
context 'with a lambda for the :auth_callback option' do
|
767
768
|
let(:client_id) { random_str }
|
768
769
|
let!(:token) do
|
769
|
-
auth.authorize({}, auth_callback:
|
770
|
+
auth.authorize({}, auth_callback: lambda do |token_params|
|
770
771
|
@block_called ||= 0
|
771
772
|
@block_called += 1
|
772
773
|
auth.create_token_request(client_id: client_id)
|
773
774
|
end)
|
774
775
|
end
|
775
776
|
|
776
|
-
it 'calls the
|
777
|
+
it 'calls the lambda' do
|
777
778
|
expect(@block_called).to eql(1)
|
778
779
|
end
|
779
780
|
|
@@ -782,7 +783,7 @@ describe Ably::Auth do
|
|
782
783
|
end
|
783
784
|
|
784
785
|
context 'for every subsequent #request_token' do
|
785
|
-
context 'without a :auth_callback
|
786
|
+
context 'without a :auth_callback lambda' do
|
786
787
|
it 'calls the originally provided block' do
|
787
788
|
auth.request_token
|
788
789
|
expect(@block_called).to eql(2)
|
@@ -790,8 +791,8 @@ describe Ably::Auth do
|
|
790
791
|
end
|
791
792
|
|
792
793
|
context 'with a provided block' do
|
793
|
-
it 'does not call the originally provided
|
794
|
-
auth.request_token({}, auth_callback:
|
794
|
+
it 'does not call the originally provided lambda and calls the new #request_token :auth_callback lambda' do
|
795
|
+
auth.request_token({}, auth_callback: lambda { |token_params| @request_block_called = true; auth.create_token_request })
|
795
796
|
expect(@block_called).to eql(1)
|
796
797
|
expect(@request_block_called).to eql(true)
|
797
798
|
end
|
@@ -800,7 +801,7 @@ describe Ably::Auth do
|
|
800
801
|
end
|
801
802
|
|
802
803
|
context 'with an explicit token string that expires' do
|
803
|
-
context 'and a
|
804
|
+
context 'and a lambda for the :auth_callback option to provide a means to renew the token' do
|
804
805
|
before do
|
805
806
|
# Ensure a soon to expire token is not treated as expired
|
806
807
|
stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0
|
@@ -811,13 +812,13 @@ describe Ably::Auth do
|
|
811
812
|
|
812
813
|
let(:token_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, default_token_params: { ttl: 3 })) }
|
813
814
|
let(:client_options) {
|
814
|
-
default_options.merge(token: token_client.auth.request_token.token, auth_callback:
|
815
|
+
default_options.merge(token: token_client.auth.request_token.token, auth_callback: lambda do |token_params|
|
815
816
|
@block_called += 1
|
816
817
|
token_client.auth.create_token_request
|
817
818
|
end)
|
818
819
|
}
|
819
820
|
|
820
|
-
it 'calls the
|
821
|
+
it 'calls the lambda once the token has expired and the new token is used' do
|
821
822
|
client.stats
|
822
823
|
expect(@block_called).to eql(0)
|
823
824
|
sleep 3.5
|
@@ -829,7 +830,7 @@ describe Ably::Auth do
|
|
829
830
|
|
830
831
|
context 'with an explicit ClientOptions client_id' do
|
831
832
|
let(:client_id) { random_str }
|
832
|
-
let(:client_options) { default_options.merge(auth_callback:
|
833
|
+
let(:client_options) { default_options.merge(auth_callback: lambda { |token_params| auth_token_object }, client_id: client_id) }
|
833
834
|
let(:auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')) }
|
834
835
|
|
835
836
|
context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
|
@@ -871,7 +872,7 @@ describe Ably::Auth do
|
|
871
872
|
end
|
872
873
|
|
873
874
|
it 'returns a TokenRequest that can be passed to a client that can use it for authentication without an API key' do
|
874
|
-
auth_callback =
|
875
|
+
auth_callback = proc { |token_params| subject }
|
875
876
|
client_without_api_key = Ably::Rest::Client.new(default_options.merge(auth_callback: auth_callback))
|
876
877
|
expect(client_without_api_key.auth).to be_using_token_auth
|
877
878
|
expect { client_without_api_key.auth.authorize }.to_not raise_error
|
@@ -932,7 +933,7 @@ describe Ably::Auth do
|
|
932
933
|
end
|
933
934
|
|
934
935
|
it 'uses these capabilities when Ably issues an actual token' do
|
935
|
-
auth_callback =
|
936
|
+
auth_callback = lambda { |token_params| subject }
|
936
937
|
client_without_api_key = Ably::Rest::Client.new(default_options.merge(auth_callback: auth_callback))
|
937
938
|
client_without_api_key.auth.authorize
|
938
939
|
expect(client_without_api_key.auth.current_token_details.capability).to eql(capability)
|
@@ -1041,7 +1042,7 @@ describe Ably::Auth do
|
|
1041
1042
|
end
|
1042
1043
|
|
1043
1044
|
it 'is valid when used for authentication' do
|
1044
|
-
auth_callback =
|
1045
|
+
auth_callback = lambda do |callback|
|
1045
1046
|
auth.create_token_request(token_attributes)
|
1046
1047
|
end
|
1047
1048
|
client = Ably::Rest::Client.new(auth_callback: auth_callback, environment: environment, protocol: protocol)
|
@@ -1324,36 +1325,9 @@ describe Ably::Auth do
|
|
1324
1325
|
end
|
1325
1326
|
end
|
1326
1327
|
|
1327
|
-
context 'deprecated #authorise' do
|
1328
|
+
context 'deprecated #authorise', :prevent_log_stubbing do
|
1328
1329
|
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) }
|
1329
|
-
let(:
|
1330
|
-
Class.new do
|
1331
|
-
def initialize
|
1332
|
-
@messages = []
|
1333
|
-
end
|
1334
|
-
|
1335
|
-
[:fatal, :error, :warn, :info, :debug].each do |severity|
|
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(' ')]
|
1341
|
-
end
|
1342
|
-
end
|
1343
|
-
|
1344
|
-
def logs
|
1345
|
-
@messages
|
1346
|
-
end
|
1347
|
-
|
1348
|
-
def level
|
1349
|
-
1
|
1350
|
-
end
|
1351
|
-
|
1352
|
-
def level=(new_level)
|
1353
|
-
end
|
1354
|
-
end
|
1355
|
-
end
|
1356
|
-
let(:custom_logger_object) { custom_logger.new }
|
1330
|
+
let(:custom_logger_object) { TestLogger.new }
|
1357
1331
|
|
1358
1332
|
it 'logs a deprecation warning (#RSA10l)' do
|
1359
1333
|
client.auth.authorise
|
@@ -1365,5 +1339,42 @@ describe Ably::Auth do
|
|
1365
1339
|
expect(response).to be_a(Ably::Models::TokenDetails)
|
1366
1340
|
end
|
1367
1341
|
end
|
1342
|
+
|
1343
|
+
# RSC1, RSC1a, RSA3c, RSA3d
|
1344
|
+
context 'when using JWT' do
|
1345
|
+
let(:auth_url) { 'https://echo.ably.io/createJWT' }
|
1346
|
+
let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
|
1347
|
+
let(:client) { Ably::Rest::Client.new(token: token, environment: environment, protocol: protocol) }
|
1348
|
+
|
1349
|
+
it 'authenticates correctly using the JWT token generated by the echo server' do
|
1350
|
+
expect(client.stats).to_not be_nil()
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
context 'when the JWT embeds an Ably token' do
|
1354
|
+
let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded }).body }
|
1355
|
+
|
1356
|
+
it 'authenticates correctly using the embedded token' do
|
1357
|
+
expect(client.stats).to_not be_nil()
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
context 'and the requested token is encrypted' do
|
1361
|
+
let(:token) { Faraday.post(auth_url, { keyName: key_name, keySecret: key_secret, jwtType: :embedded, encrypted: 1 }).body }
|
1362
|
+
|
1363
|
+
it 'authenticates correctly using the embedded token' do
|
1364
|
+
expect(client.stats).to_not be_nil()
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
end
|
1368
|
+
|
1369
|
+
# RSA4f, RSA8c
|
1370
|
+
context 'when the token requested is returned with application/jwt content type' do
|
1371
|
+
let(:auth_rest_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
|
1372
|
+
let(:auth_params) { { keyName: key_name, keySecret: key_secret, returnType: 'jwt' } }
|
1373
|
+
let(:token) { auth_rest_client.auth.request_token({ }, { auth_url: auth_url, auth_params: auth_params }).token }
|
1374
|
+
it 'authenticates correctly and pulls stats' do
|
1375
|
+
expect(client.stats).to_not be_nil()
|
1376
|
+
end
|
1377
|
+
end
|
1378
|
+
end
|
1368
1379
|
end
|
1369
1380
|
end
|
@@ -7,7 +7,7 @@ describe Ably::Rest do
|
|
7
7
|
|
8
8
|
let(:client_options) { {} }
|
9
9
|
let(:client) do
|
10
|
-
Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret'))
|
10
|
+
Ably::Rest::Client.new(client_options.merge(key: 'appid.keyuid:keysecret', log_retries_as_info: true))
|
11
11
|
end
|
12
12
|
|
13
13
|
let(:now) { Time.now - 1000 }
|
@@ -67,7 +67,7 @@ describe Ably::Rest do
|
|
67
67
|
|
68
68
|
vary_by_protocol do
|
69
69
|
let(:client) do
|
70
|
-
Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol)
|
70
|
+
Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true)
|
71
71
|
end
|
72
72
|
|
73
73
|
describe 'failed requests' do
|
@@ -40,7 +40,7 @@ describe Ably::Rest::Channel do
|
|
40
40
|
|
41
41
|
it 'publishes the message without a client_id' do
|
42
42
|
expect(client).to receive(:post).
|
43
|
-
with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id)).
|
43
|
+
with("/channels/#{channel_name}/publish", hash_excluding(client_id: client_id), {}).
|
44
44
|
and_return(double('response', status: 201))
|
45
45
|
|
46
46
|
expect(channel.publish(name, data)).to eql(true)
|
@@ -82,6 +82,44 @@ describe Ably::Rest::Channel do
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
context 'with a Message object' do
|
86
|
+
let(:name) { random_str }
|
87
|
+
|
88
|
+
let(:message) do
|
89
|
+
Ably::Models::Message(name: name, data: data)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'publishes the message' do
|
93
|
+
expect(client).to receive(:post).once.and_call_original
|
94
|
+
expect(channel.publish(message)).to eql(true)
|
95
|
+
expect(channel.history.items.first.name).to eql(name)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'with a Message object and query params' do
|
100
|
+
let(:message) do
|
101
|
+
Ably::Models::Message(name: name, data: data)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should fail to publish the message (RSL1l1)' do
|
105
|
+
expect(client).to receive(:post).once.and_call_original
|
106
|
+
expect { channel.publish(message, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'with Messages and query params' do
|
111
|
+
let(:messages) do
|
112
|
+
10.times.map do |index|
|
113
|
+
{ name: index.to_s, data: { "index" => index + 10 } }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should fail to publish the message (RSL1l1)' do
|
118
|
+
expect(client).to receive(:post).once.and_call_original
|
119
|
+
expect { channel.publish(messages, { _forceNack: 'true' }) }.to raise_error(Ably::Exceptions::InvalidRequest, /40099/)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
85
123
|
context 'without adequate permissions on the channel' do
|
86
124
|
let(:capability) { { onlyChannel: ['subscribe'] } }
|
87
125
|
let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { capability: capability }) }
|
@@ -96,7 +134,7 @@ describe Ably::Rest::Channel do
|
|
96
134
|
let(:data) { random_str }
|
97
135
|
|
98
136
|
it 'publishes the message without a name attribute in the payload' do
|
99
|
-
expect(client).to receive(:post).with(anything, { "data" => data }).once.and_call_original
|
137
|
+
expect(client).to receive(:post).with(anything, { "data" => data }, {}).once.and_call_original
|
100
138
|
expect(channel.publish(nil, data)).to eql(true)
|
101
139
|
expect(channel.history.items.first.name).to be_nil
|
102
140
|
expect(channel.history.items.first.data).to eql(data)
|
@@ -107,7 +145,7 @@ describe Ably::Rest::Channel do
|
|
107
145
|
let(:name) { random_str }
|
108
146
|
|
109
147
|
it 'publishes the message without a data attribute in the payload' do
|
110
|
-
expect(client).to receive(:post).with(anything, { "name" => name }).once.and_call_original
|
148
|
+
expect(client).to receive(:post).with(anything, { "name" => name }, {}).once.and_call_original
|
111
149
|
expect(channel.publish(name)).to eql(true)
|
112
150
|
expect(channel.history.items.first.name).to eql(name)
|
113
151
|
expect(channel.history.items.first.data).to be_nil
|
@@ -118,7 +156,7 @@ describe Ably::Rest::Channel do
|
|
118
156
|
let(:name) { random_str }
|
119
157
|
|
120
158
|
it 'publishes the message without any attributes in the payload' do
|
121
|
-
expect(client).to receive(:post).with(anything, {}).once.and_call_original
|
159
|
+
expect(client).to receive(:post).with(anything, {}, {}).once.and_call_original
|
122
160
|
expect(channel.publish(nil)).to eql(true)
|
123
161
|
expect(channel.history.items.first.name).to be_nil
|
124
162
|
expect(channel.history.items.first.data).to be_nil
|
@@ -275,6 +313,43 @@ describe Ably::Rest::Channel do
|
|
275
313
|
end
|
276
314
|
end
|
277
315
|
end
|
316
|
+
|
317
|
+
context 'with a frozen message event name' do
|
318
|
+
let(:event_name) { random_str.freeze }
|
319
|
+
|
320
|
+
it 'succeeds and publishes with an implicit client_id' do
|
321
|
+
channel.publish([name: event_name])
|
322
|
+
channel.publish(event_name)
|
323
|
+
|
324
|
+
if !(RUBY_VERSION.match(/^1\./) || RUBY_VERSION.match(/^2\.[012]/))
|
325
|
+
channel.publish(+'foo-bar') # new style freeze, see https://github.com/ably/ably-ruby/issues/132
|
326
|
+
else
|
327
|
+
channel.publish('foo-bar'.freeze) # new + style not supported until Ruby 2.3
|
328
|
+
end
|
329
|
+
|
330
|
+
channel.history do |messages|
|
331
|
+
expect(messages.length).to eql(3)
|
332
|
+
expect(messages.first.name).to eql(event_name)
|
333
|
+
expect(messages[1].name).to eql(event_name)
|
334
|
+
expect(messages.last.name).to eql('foo-bar')
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context 'with a frozen payload' do
|
340
|
+
let(:payload) { { foo: random_str.freeze }.freeze }
|
341
|
+
|
342
|
+
it 'succeeds and publishes with an implicit client_id' do
|
343
|
+
channel.publish([data: payload])
|
344
|
+
channel.publish(nil, payload)
|
345
|
+
|
346
|
+
channel.history do |messages|
|
347
|
+
expect(messages.length).to eql(2)
|
348
|
+
expect(messages.first.data).to eql(payload)
|
349
|
+
expect(messages.last.data).to eql(payload)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
278
353
|
end
|
279
354
|
|
280
355
|
describe '#history' do
|
@@ -60,5 +60,11 @@ describe Ably::Rest::Channels do
|
|
60
60
|
let(:channel_with_options) { client.channels[channel_name, options] }
|
61
61
|
it_behaves_like 'a channel'
|
62
62
|
end
|
63
|
+
|
64
|
+
describe 'using a frozen channel name' do
|
65
|
+
let(:channel) { client.channels[channel_name.freeze] }
|
66
|
+
let(:channel_with_options) { client.channels[channel_name.freeze, options] }
|
67
|
+
it_behaves_like 'a channel'
|
68
|
+
end
|
63
69
|
end
|
64
70
|
end
|
@@ -4,7 +4,7 @@ require 'webrick'
|
|
4
4
|
|
5
5
|
describe Ably::Rest::Client do
|
6
6
|
vary_by_protocol do
|
7
|
-
let(:default_options) { { environment: environment, protocol: protocol } }
|
7
|
+
let(:default_options) { { environment: environment, protocol: protocol, log_retries_as_info: true } }
|
8
8
|
let(:client_options) { default_options }
|
9
9
|
|
10
10
|
let(:client) { Ably::Rest::Client.new(client_options) }
|
@@ -27,6 +27,19 @@ describe Ably::Rest::Client do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
context 'with an invalid API key' do
|
31
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(key: 'app.key:secret', log_level: :fatal)) }
|
32
|
+
|
33
|
+
it 'logs an entry with a help href url matching the code #TI5' do
|
34
|
+
begin
|
35
|
+
client.channels.get('foo').publish('test')
|
36
|
+
raise 'Expected Ably::Exceptions::ResourceMissing'
|
37
|
+
rescue Ably::Exceptions::ResourceMissing => err
|
38
|
+
expect err.to_s.match(%r{https://help.ably.io/error/40400})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
30
43
|
context 'with an explicit string :token' do
|
31
44
|
let(:client) { Ably::Rest::Client.new(client_options.merge(token: random_str)) }
|
32
45
|
|
@@ -65,10 +78,10 @@ describe Ably::Rest::Client do
|
|
65
78
|
end
|
66
79
|
end
|
67
80
|
|
68
|
-
context 'with an :auth_callback
|
69
|
-
let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback:
|
81
|
+
context 'with an :auth_callback lambda' do
|
82
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: lambda { |token_params| token_request })) }
|
70
83
|
|
71
|
-
it 'calls the auth
|
84
|
+
it 'calls the auth lambda to get a new token' do
|
72
85
|
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
|
73
86
|
expect(client.auth.current_token_details.client_id).to eql(client_id)
|
74
87
|
end
|
@@ -93,8 +106,8 @@ describe Ably::Rest::Client do
|
|
93
106
|
end
|
94
107
|
end
|
95
108
|
|
96
|
-
context 'with an :auth_callback
|
97
|
-
let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback:
|
109
|
+
context 'with an :auth_callback lambda (clientId provided in library options instead of as a token_request param)' do
|
110
|
+
let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback: lambda { |token_params| token_request })) }
|
98
111
|
let(:token_request) { client.auth.create_token_request({}, key_name: key_name, key_secret: key_secret) }
|
99
112
|
|
100
113
|
it 'correctly sets the clientId on the token' do
|
@@ -178,7 +191,7 @@ describe Ably::Rest::Client do
|
|
178
191
|
|
179
192
|
context 'using tokens' do
|
180
193
|
let(:client) do
|
181
|
-
Ably::Rest::Client.new(client_options.merge(auth_callback:
|
194
|
+
Ably::Rest::Client.new(client_options.merge(auth_callback: lambda do |token_params|
|
182
195
|
@request_index ||= 0
|
183
196
|
@request_index += 1
|
184
197
|
send("token_request_#{@request_index > 2 ? 'next' : @request_index}")
|
@@ -290,7 +303,7 @@ describe Ably::Rest::Client do
|
|
290
303
|
|
291
304
|
context 'fallback hosts', :webmock do
|
292
305
|
let(:path) { '/channels/test/publish' }
|
293
|
-
let(:publish_block) {
|
306
|
+
let(:publish_block) { lambda { client.channel('test').publish('event', 'data') } }
|
294
307
|
|
295
308
|
context 'configured' do
|
296
309
|
let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
|
@@ -321,7 +334,7 @@ describe Ably::Rest::Client do
|
|
321
334
|
let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
|
322
335
|
let(:max_retry_count) { 2 }
|
323
336
|
let(:max_retry_duration) { 0.5 }
|
324
|
-
let(:fallback_block) {
|
337
|
+
let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
|
325
338
|
let(:client_options) do
|
326
339
|
default_options.merge(
|
327
340
|
environment: nil,
|
@@ -454,7 +467,7 @@ describe Ably::Rest::Client do
|
|
454
467
|
context 'and server returns a 50x error' do
|
455
468
|
let(:status) { 502 }
|
456
469
|
let(:fallback_block) do
|
457
|
-
|
470
|
+
proc do
|
458
471
|
{
|
459
472
|
headers: { 'Content-Type' => 'text/html' },
|
460
473
|
status: status
|
@@ -478,7 +491,7 @@ describe Ably::Rest::Client do
|
|
478
491
|
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
|
479
492
|
let(:max_retry_count) { 2 }
|
480
493
|
let(:max_retry_duration) { 0.5 }
|
481
|
-
let(:fallback_block) {
|
494
|
+
let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
|
482
495
|
let(:production_options) do
|
483
496
|
default_options.merge(
|
484
497
|
environment: nil,
|
@@ -490,7 +503,7 @@ describe Ably::Rest::Client do
|
|
490
503
|
|
491
504
|
let(:status) { 502 }
|
492
505
|
let(:fallback_block) do
|
493
|
-
|
506
|
+
proc do
|
494
507
|
{
|
495
508
|
headers: { 'Content-Type' => 'text/html' },
|
496
509
|
status: status
|
@@ -547,28 +560,36 @@ describe Ably::Rest::Client do
|
|
547
560
|
context 'and timing out the primary host' do
|
548
561
|
before do
|
549
562
|
@web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
@fallback_request_count += 1
|
557
|
-
if @fallback_request_count <= fail_fallback_request_count
|
563
|
+
request_handler = lambda do |result_body|
|
564
|
+
lambda do |req, res|
|
565
|
+
host = req.header["host"].first
|
566
|
+
if host.include?(primary_host)
|
567
|
+
@primary_host_request_count ||= 0
|
568
|
+
@primary_host_request_count += 1
|
558
569
|
sleep request_timeout + 0.5
|
559
570
|
else
|
560
|
-
|
561
|
-
|
562
|
-
|
571
|
+
@fallback_request_count ||= 0
|
572
|
+
@fallback_request_count += 1
|
573
|
+
@fallback_hosts_tried ||= []
|
574
|
+
@fallback_hosts_tried.push(host)
|
575
|
+
if @fallback_request_count <= fail_fallback_request_count
|
576
|
+
sleep request_timeout + 0.5
|
577
|
+
else
|
578
|
+
res.status = 200
|
579
|
+
res['Content-Type'] = 'application/json'
|
580
|
+
res.body = result_body
|
581
|
+
end
|
563
582
|
end
|
564
583
|
end
|
565
584
|
end
|
585
|
+
@web_server.mount_proc "/time", &request_handler.call('[1000000000000]')
|
586
|
+
@web_server.mount_proc "/channels/#{channel_name}/publish", &request_handler.call('{}')
|
566
587
|
Thread.new do
|
567
588
|
@web_server.start
|
568
589
|
end
|
569
590
|
end
|
570
591
|
|
571
|
-
context 'with request timeout less than max_retry_duration' do
|
592
|
+
context 'POST with request timeout less than max_retry_duration' do
|
572
593
|
let(:client_options) do
|
573
594
|
default_options.merge(
|
574
595
|
rest_host: primary_host,
|
@@ -577,20 +598,44 @@ describe Ably::Rest::Client do
|
|
577
598
|
port: port,
|
578
599
|
tls: false,
|
579
600
|
http_request_timeout: request_timeout,
|
580
|
-
|
601
|
+
http_max_retry_duration: request_timeout * 2.5,
|
581
602
|
log_level: :error
|
582
603
|
)
|
583
604
|
end
|
584
605
|
let(:fail_fallback_request_count) { 1 }
|
585
606
|
|
586
|
-
it 'tries
|
607
|
+
it 'tries the primary host, then both fallback hosts (#RSC15d)' do
|
587
608
|
client.channel(channel_name).publish('event', 'data')
|
588
|
-
expect(@
|
609
|
+
expect(@primary_host_request_count).to eql(1)
|
610
|
+
expect(@fallback_request_count).to eql(2)
|
611
|
+
expect(@fallback_hosts_tried.uniq.length).to eql(2)
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
context 'GET with request timeout less than max_retry_duration' do
|
616
|
+
let(:client_options) do
|
617
|
+
default_options.merge(
|
618
|
+
rest_host: primary_host,
|
619
|
+
fallback_hosts: fallbacks,
|
620
|
+
token: 'fake.token',
|
621
|
+
port: port,
|
622
|
+
tls: false,
|
623
|
+
http_request_timeout: request_timeout,
|
624
|
+
http_max_retry_duration: request_timeout * 2.5,
|
625
|
+
log_level: :error
|
626
|
+
)
|
627
|
+
end
|
628
|
+
let(:fail_fallback_request_count) { 1 }
|
629
|
+
|
630
|
+
it 'tries the primary host, then both fallback hosts (#RSC15d)' do
|
631
|
+
client.time
|
632
|
+
expect(@primary_host_request_count).to eql(1)
|
589
633
|
expect(@fallback_request_count).to eql(2)
|
634
|
+
expect(@fallback_hosts_tried.uniq.length).to eql(2)
|
590
635
|
end
|
591
636
|
end
|
592
637
|
|
593
|
-
context 'with request timeout
|
638
|
+
context 'POST with request timeout more than max_retry_duration' do
|
594
639
|
let(:client_options) do
|
595
640
|
default_options.merge(
|
596
641
|
rest_host: primary_host,
|
@@ -599,18 +644,41 @@ describe Ably::Rest::Client do
|
|
599
644
|
port: port,
|
600
645
|
tls: false,
|
601
646
|
http_request_timeout: request_timeout,
|
602
|
-
|
647
|
+
http_max_retry_duration: request_timeout / 2,
|
603
648
|
log_level: :error
|
604
649
|
)
|
605
650
|
end
|
606
651
|
let(:fail_fallback_request_count) { 0 }
|
607
652
|
|
608
|
-
it '
|
609
|
-
client.channel(channel_name).publish('event', 'data')
|
610
|
-
expect(@
|
611
|
-
expect(@fallback_request_count).to
|
653
|
+
it 'does not try any fallback hosts (#RSC15d)' do
|
654
|
+
expect { client.channel(channel_name).publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionTimeout
|
655
|
+
expect(@primary_host_request_count).to eql(1)
|
656
|
+
expect(@fallback_request_count).to be_nil
|
612
657
|
end
|
613
658
|
end
|
659
|
+
|
660
|
+
context 'GET with request timeout more than max_retry_duration' do
|
661
|
+
let(:client_options) do
|
662
|
+
default_options.merge(
|
663
|
+
rest_host: primary_host,
|
664
|
+
fallback_hosts: fallbacks,
|
665
|
+
token: 'fake.token',
|
666
|
+
port: port,
|
667
|
+
tls: false,
|
668
|
+
http_request_timeout: request_timeout,
|
669
|
+
http_max_retry_duration: request_timeout / 2,
|
670
|
+
log_level: :error
|
671
|
+
)
|
672
|
+
end
|
673
|
+
let(:fail_fallback_request_count) { 0 }
|
674
|
+
|
675
|
+
it 'does not try any fallback hosts (#RSC15d)' do
|
676
|
+
expect { client.time }.to raise_error Ably::Exceptions::ConnectionTimeout
|
677
|
+
expect(@primary_host_request_count).to eql(1)
|
678
|
+
expect(@fallback_request_count).to be_nil
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
614
682
|
end
|
615
683
|
|
616
684
|
context 'and failing the primary host' do
|
@@ -655,6 +723,109 @@ describe Ably::Rest::Client do
|
|
655
723
|
expect(@fallback_request_count).to eql(2)
|
656
724
|
end
|
657
725
|
end
|
726
|
+
|
727
|
+
context 'to fail the primary host, allow a fallback to succeed, then later trigger a fallback to the primary host (#RSC15f)' do
|
728
|
+
before do
|
729
|
+
@request_count = 0
|
730
|
+
@primary_host_request_count = 0
|
731
|
+
@web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
|
732
|
+
@web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
|
733
|
+
@request_count += 1
|
734
|
+
if req.header["host"].first.include?(primary_host)
|
735
|
+
@primary_host_request_count += 1
|
736
|
+
# Fail all requests to the primary host so that a fallback is used
|
737
|
+
# Except request 6 which should suceed and clear the fallback host preference
|
738
|
+
if @request_count == 6
|
739
|
+
res.status = 200
|
740
|
+
res['Content-Type'] = 'application/json'
|
741
|
+
res.body = '{}'
|
742
|
+
else
|
743
|
+
res.status = 500
|
744
|
+
end
|
745
|
+
else
|
746
|
+
# Fail the second request (first failed fallback of first request)
|
747
|
+
# Fail the third request on the previously succeeded fallback host to trigger an attempt on the primary host
|
748
|
+
if [2, 5].include?(@request_count)
|
749
|
+
res.status = 500
|
750
|
+
else
|
751
|
+
res.status = 200
|
752
|
+
res['Content-Type'] = 'application/json'
|
753
|
+
res.body = '{}'
|
754
|
+
end
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
Thread.new do
|
759
|
+
@web_server.start
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
let(:client_options) do
|
764
|
+
default_options.merge(
|
765
|
+
rest_host: primary_host,
|
766
|
+
fallback_hosts: fallbacks,
|
767
|
+
token: 'fake.token',
|
768
|
+
port: port,
|
769
|
+
tls: false,
|
770
|
+
log_level: :error
|
771
|
+
).merge(additional_client_options)
|
772
|
+
end
|
773
|
+
|
774
|
+
let (:additional_client_options) { {} }
|
775
|
+
|
776
|
+
it 'succeeds and remembers fallback host preferences across requests' do
|
777
|
+
# Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
|
778
|
+
client.channel(channel_name).publish('event', 'data')
|
779
|
+
expect(@request_count).to eql(3)
|
780
|
+
expect(fallbacks).to include(client.using_preferred_fallback_host?)
|
781
|
+
successfull_fallback = client.using_preferred_fallback_host?
|
782
|
+
expect(@primary_host_request_count).to eql(1)
|
783
|
+
|
784
|
+
# Send another request, which should go straight to the fallback as it succeeded previously
|
785
|
+
client.channel(channel_name).publish('event', 'data')
|
786
|
+
expect(@request_count).to eql(4)
|
787
|
+
expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
|
788
|
+
expect(@primary_host_request_count).to eql(1)
|
789
|
+
|
790
|
+
# A subsequent request should fail to the fallback, go the primary host and succeed
|
791
|
+
client.channel(channel_name).publish('event', 'data')
|
792
|
+
expect(@request_count).to eql(6)
|
793
|
+
expect(client.using_preferred_fallback_host?).to be_falsey
|
794
|
+
expect(@primary_host_request_count).to eql(2)
|
795
|
+
|
796
|
+
# A subsequent request will fail on the primary endpoint, and we expect the fallback to be used again
|
797
|
+
client.channel(channel_name).publish('event', 'data')
|
798
|
+
expect(@request_count).to eql(8)
|
799
|
+
expect(fallbacks).to include(client.using_preferred_fallback_host?)
|
800
|
+
successfull_fallback = client.using_preferred_fallback_host?
|
801
|
+
expect(@primary_host_request_count).to eql(3)
|
802
|
+
|
803
|
+
# Send another request, which should go straight to the fallback as it succeeded previously
|
804
|
+
client.channel(channel_name).publish('event', 'data')
|
805
|
+
expect(@request_count).to eql(9)
|
806
|
+
expect(successfull_fallback).to eql(client.using_preferred_fallback_host?)
|
807
|
+
expect(@primary_host_request_count).to eql(3)
|
808
|
+
end
|
809
|
+
|
810
|
+
context 'with custom :fallback_retry_timeout' do
|
811
|
+
let (:additional_client_options) { { fallback_retry_timeout: 5 } }
|
812
|
+
|
813
|
+
it 'stops using the preferred fallback after this time' do
|
814
|
+
# Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed
|
815
|
+
client.channel(channel_name).publish('event', 'data')
|
816
|
+
expect(@request_count).to eql(3)
|
817
|
+
expect(fallbacks).to include(client.using_preferred_fallback_host?)
|
818
|
+
expect(@primary_host_request_count).to eql(1)
|
819
|
+
|
820
|
+
# Wait for the preferred fallback cache to expire
|
821
|
+
sleep 5
|
822
|
+
|
823
|
+
# Send another request, which should go straight to the primary host again as fallback host is expired
|
824
|
+
client.channel(channel_name).publish('event', 'data')
|
825
|
+
expect(@primary_host_request_count).to eql(2)
|
826
|
+
end
|
827
|
+
end
|
828
|
+
end
|
658
829
|
end
|
659
830
|
end
|
660
831
|
|
@@ -662,20 +833,21 @@ describe Ably::Rest::Client do
|
|
662
833
|
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
|
663
834
|
let(:max_retry_count) { 2 }
|
664
835
|
let(:max_retry_duration) { 0.5 }
|
665
|
-
let(:fallback_block) {
|
836
|
+
let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
|
666
837
|
let(:env) { 'custom-env' }
|
667
838
|
let(:production_options) do
|
668
839
|
default_options.merge(
|
669
840
|
environment: env,
|
670
841
|
key: api_key,
|
671
842
|
http_max_retry_duration: max_retry_duration,
|
672
|
-
http_max_retry_count: max_retry_count
|
843
|
+
http_max_retry_count: max_retry_count,
|
844
|
+
log_level: :fatal,
|
673
845
|
)
|
674
846
|
end
|
675
847
|
|
676
848
|
let(:status) { 502 }
|
677
849
|
let(:fallback_block) do
|
678
|
-
|
850
|
+
proc do
|
679
851
|
{
|
680
852
|
headers: { 'Content-Type' => 'text/html' },
|
681
853
|
status: status
|
@@ -696,7 +868,7 @@ describe Ably::Rest::Client do
|
|
696
868
|
end
|
697
869
|
|
698
870
|
let(:client_options) {
|
699
|
-
production_options.merge(fallback_hosts: custom_hosts, log_level: :
|
871
|
+
production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
|
700
872
|
}
|
701
873
|
|
702
874
|
it 'attempts the fallback hosts as this is not an authentication failure' do
|
@@ -709,7 +881,7 @@ describe Ably::Rest::Client do
|
|
709
881
|
|
710
882
|
context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
|
711
883
|
let(:client_options) {
|
712
|
-
production_options.merge(fallback_hosts: [])
|
884
|
+
production_options.merge(fallback_hosts: [], log_level: :fatal)
|
713
885
|
}
|
714
886
|
|
715
887
|
it 'does not attempt the fallback hosts as this is an authentication failure' do
|
@@ -734,7 +906,7 @@ describe Ably::Rest::Client do
|
|
734
906
|
end
|
735
907
|
|
736
908
|
let(:client_options) {
|
737
|
-
production_options.merge(fallback_hosts: custom_hosts, log_level: :
|
909
|
+
production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal)
|
738
910
|
}
|
739
911
|
|
740
912
|
it 'attempts the default fallback hosts as this is an authentication failure' do
|
@@ -911,7 +1083,7 @@ describe Ably::Rest::Client do
|
|
911
1083
|
it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do
|
912
1084
|
client.channels.get('foo').publish("event")
|
913
1085
|
expect(publish_message_stub).to have_been_requested
|
914
|
-
expect(Ably::PROTOCOL_VERSION).to eql('1.
|
1086
|
+
expect(Ably::PROTOCOL_VERSION).to eql('1.1')
|
915
1087
|
end
|
916
1088
|
end
|
917
1089
|
end
|
@@ -962,36 +1134,10 @@ describe Ably::Rest::Client do
|
|
962
1134
|
|
963
1135
|
context 'request_id generation' do
|
964
1136
|
context 'Timeout error' do
|
965
|
-
context 'with
|
966
|
-
let(:
|
967
|
-
Class.new do
|
968
|
-
def initialize
|
969
|
-
@messages = []
|
970
|
-
end
|
971
|
-
|
972
|
-
[:fatal, :error, :warn, :info, :debug].each do |severity|
|
973
|
-
define_method severity do |message, &block|
|
974
|
-
message_val = [message]
|
975
|
-
message_val << block.call if block
|
976
|
-
|
977
|
-
@messages << [severity, message_val.compact.join(' ')]
|
978
|
-
end
|
979
|
-
end
|
980
|
-
|
981
|
-
def logs
|
982
|
-
@messages
|
983
|
-
end
|
984
|
-
|
985
|
-
def level
|
986
|
-
1
|
987
|
-
end
|
988
|
-
|
989
|
-
def level=(new_level)
|
990
|
-
end
|
991
|
-
end
|
992
|
-
end
|
993
|
-
let(:custom_logger_object) { custom_logger.new }
|
1137
|
+
context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
|
1138
|
+
let(:custom_logger_object) { TestLogger.new }
|
994
1139
|
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
|
1140
|
+
|
995
1141
|
before do
|
996
1142
|
@request_id = nil
|
997
1143
|
stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request|
|
@@ -1000,15 +1146,64 @@ describe Ably::Rest::Client do
|
|
1000
1146
|
raise Faraday::TimeoutError.new('timeout error message')
|
1001
1147
|
end
|
1002
1148
|
end
|
1149
|
+
|
1003
1150
|
it 'has an error with the same request_id of the request' do
|
1004
|
-
expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
|
1151
|
+
expect { client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
|
1152
|
+
expect(@request_id).to be_a(String)
|
1153
|
+
expect(@request_id).to_not be_empty
|
1005
1154
|
expect(custom_logger_object.logs.find { |severity, message| message.match(/#{@request_id}/i)} ).to_not be_nil
|
1006
1155
|
end
|
1007
1156
|
end
|
1008
1157
|
|
1009
|
-
context '
|
1010
|
-
let(:client_options) { { key: api_key,
|
1158
|
+
context 'with option add_request_ids: true and REST operations with a message body' do
|
1159
|
+
let(:client_options) { default_options.merge({ key: api_key, add_request_ids: true }) }
|
1160
|
+
let(:channel_name) { random_str }
|
1161
|
+
let(:channel) { client.channels.get(channel_name) }
|
1162
|
+
|
1163
|
+
context 'with mocks to inspect the params', :webmock do
|
1164
|
+
before do
|
1165
|
+
stub_request(:post, Addressable::Template.new("#{client.endpoint}/channels/#{channel_name}/publish{?request_id}")).
|
1166
|
+
with do |request|
|
1167
|
+
@request_id = request.uri.query_values['request_id']
|
1168
|
+
end.to_return(:status => 200, :body => [], :headers => { 'Content-Type' => 'application/json' })
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
context 'with a single publish' do
|
1172
|
+
it 'succeeds and sends the request_id as a param' do
|
1173
|
+
channel.publish('name', { body: random_str })
|
1174
|
+
expect(@request_id.to_s).to_not be_empty
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
context 'with an array publish' do
|
1179
|
+
it 'succeeds and sends the request_id as a param' do
|
1180
|
+
channel.publish([{ body: random_str }, { body: random_str }])
|
1181
|
+
expect(@request_id.to_s).to_not be_empty
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
context 'without mocks to ensure the requests are accepted' do
|
1187
|
+
context 'with a single publish' do
|
1188
|
+
it 'succeeds and sends the request_id as a param' do
|
1189
|
+
channel.publish('name', { body: random_str })
|
1190
|
+
expect(channel.history.items.length).to eql(1)
|
1191
|
+
end
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
context 'with an array publish' do
|
1195
|
+
it 'succeeds and sends the request_id as a param' do
|
1196
|
+
channel.publish([{ body: random_str }, { body: random_str }])
|
1197
|
+
expect(channel.history.items.length).to eql(2)
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
context 'option add_request_ids: true and specified fallback hosts', :webmock do
|
1204
|
+
let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error, log_retries_as_info: true } }
|
1011
1205
|
let(:requests) { [] }
|
1206
|
+
|
1012
1207
|
before do
|
1013
1208
|
@request_id = nil
|
1014
1209
|
hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io']
|
@@ -1021,8 +1216,11 @@ describe Ably::Rest::Client do
|
|
1021
1216
|
end
|
1022
1217
|
end
|
1023
1218
|
end
|
1024
|
-
|
1219
|
+
|
1220
|
+
specify 'request_id is the same across retries' do
|
1025
1221
|
expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
|
1222
|
+
expect(@request_id).to be_a(String)
|
1223
|
+
expect(@request_id).to_not be_empty
|
1026
1224
|
expect(requests.uniq.count).to eql(1)
|
1027
1225
|
expect(requests.uniq.first).to eql(@request_id)
|
1028
1226
|
end
|
@@ -1030,6 +1228,7 @@ describe Ably::Rest::Client do
|
|
1030
1228
|
|
1031
1229
|
context 'without request_id' do
|
1032
1230
|
let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
|
1231
|
+
|
1033
1232
|
it 'does not include request_id in ConnectionTimeout error' do
|
1034
1233
|
begin
|
1035
1234
|
client.stats
|
@@ -1038,10 +1237,11 @@ describe Ably::Rest::Client do
|
|
1038
1237
|
end
|
1039
1238
|
end
|
1040
1239
|
end
|
1041
|
-
end
|
1240
|
+
end
|
1042
1241
|
|
1043
1242
|
context 'UnauthorizedRequest nonce error' do
|
1044
1243
|
let(:token_params) { { nonce: "samenonce_#{protocol}", timestamp: Time.now.to_i } }
|
1244
|
+
|
1045
1245
|
it 'includes request_id in UnauthorizedRequest error due to replayed nonce' do
|
1046
1246
|
client1 = Ably::Rest::Client.new(default_options.merge(key: api_key))
|
1047
1247
|
client2 = Ably::Rest::Client.new(default_options.merge(key: api_key, add_request_ids: true))
|
@@ -1054,5 +1254,49 @@ describe Ably::Rest::Client do
|
|
1054
1254
|
end
|
1055
1255
|
end
|
1056
1256
|
end
|
1257
|
+
|
1258
|
+
context 'failed request logging', :prevent_log_stubbing do
|
1259
|
+
let(:custom_logger) { TestLogger.new }
|
1260
|
+
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger, log_retries_as_info: false) }
|
1261
|
+
|
1262
|
+
it 'is absent when requests do not fail' do
|
1263
|
+
client.time
|
1264
|
+
expect(custom_logger.logs(min_severity: :warn)).to be_empty
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
context 'with the first request failing' do
|
1268
|
+
let(:client_options) do
|
1269
|
+
default_options.merge(
|
1270
|
+
rest_host: 'non.existent.domain.local',
|
1271
|
+
fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')],
|
1272
|
+
key: api_key,
|
1273
|
+
logger: custom_logger,
|
1274
|
+
log_retries_as_info: false)
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
it 'is present with success message when requests do not actually fail' do
|
1278
|
+
client.time
|
1279
|
+
expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to eql(1)
|
1280
|
+
expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/SUCCEEDED/) }.length).to eql(1)
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
context 'with all requests failing' do
|
1285
|
+
let(:client_options) do
|
1286
|
+
default_options.merge(
|
1287
|
+
rest_host: 'non.existent.domain.local',
|
1288
|
+
fallback_hosts: ['non2.existent.domain.local'],
|
1289
|
+
key: api_key,
|
1290
|
+
logger: custom_logger,
|
1291
|
+
log_retries_as_info: false)
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
it 'is present when all requests fail' do
|
1295
|
+
expect { client.time }.to raise_error(Ably::Exceptions::ConnectionError)
|
1296
|
+
expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to be >= 2
|
1297
|
+
expect(custom_logger.logs(min_severity: :error).select { |severity, msg| msg.match(/FAILED/) }.length).to eql(1)
|
1298
|
+
end
|
1299
|
+
end
|
1300
|
+
end
|
1057
1301
|
end
|
1058
1302
|
end
|