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.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/CHANGELOG.md +1 -1
  4. data/LICENSE +1 -1
  5. data/README.md +26 -7
  6. data/SPEC.md +2003 -1605
  7. data/ably-rest.gemspec +4 -2
  8. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  9. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  10. data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
  11. data/lib/submodules/ably-ruby/LICENSE +1 -3
  12. data/lib/submodules/ably-ruby/README.md +12 -7
  13. data/lib/submodules/ably-ruby/Rakefile +32 -0
  14. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  15. data/lib/submodules/ably-ruby/ably.gemspec +17 -11
  16. data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
  17. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  18. data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
  19. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  20. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  21. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  24. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +12 -12
  25. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
  26. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -4
  27. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  28. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  29. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +2 -2
  30. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
  31. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
  32. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
  33. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  34. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  35. data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +1 -1
  36. data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
  37. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
  38. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  39. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +2 -2
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +9 -4
  48. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  49. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +45 -26
  50. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  51. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  59. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +54 -18
  60. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  61. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
  62. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  63. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  64. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  68. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +253 -49
  70. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
  71. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
  72. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
  73. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
  74. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
  75. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
  76. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  77. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
  78. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  79. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +62 -51
  81. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  82. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  83. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +318 -74
  85. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  86. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  90. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +1 -9
  91. data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
  92. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  93. data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
  94. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
  95. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  96. data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
  97. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
  98. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  99. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  100. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  101. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  102. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  103. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  104. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -2
  105. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  106. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
  107. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
  108. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +1 -1
  109. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  110. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +2 -2
  111. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +1 -1
  112. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  113. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +30 -1
  114. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  115. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  116. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
  117. data/spec/spec_helper.rb +1 -0
  118. 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
- Proc.new do |token_params_arg|
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 Proc when authenticating to obtain the request token' do
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: Proc.new do |block_options|
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: Proc.new do |block_options|
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) { Proc.new do
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: Proc.new { '1231232.12321:12321312' })
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
- expect(auth.options[:authUrl]).to be_nil
762
- auth.authorize({}, authUrl: 'http://foo.com')
763
- expect(auth.options[:authUrl]).to eql('http://foo.com')
761
+ auth_url = "https://echo.ably.io/?type=text&body=#{auth.request_token.token}"
762
+ expect(auth.options[:auth_url]).to be_nil
763
+ auth.authorize({}, auth_url: auth_url)
764
+ expect(auth.options[:auth_url]).to eql(auth_url)
764
765
  end
765
766
 
766
- context 'with a Proc for the :auth_callback option' do
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: Proc.new do
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 Proc' do
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 Proc' do
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 Proc and calls the new #request_token :auth_callback Proc' do
794
- auth.request_token({}, auth_callback: Proc.new { @request_block_called = true; auth.create_token_request })
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 Proc for the :auth_callback option to provide a means to renew the token' do
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: Proc.new do
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 Proc once the token has expired and the new token is used' do
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: Proc.new { auth_token_object }, client_id: client_id) }
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 = Proc.new { subject }
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 = Proc.new { subject }
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 = Proc.new do
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(:custom_logger) do
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 Proc' do
69
- let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
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 Proc to get a new token' do
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 Proc (clientId provided in library options instead of as a token_request param)' do
97
- let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback: Proc.new { token_request })) }
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: Proc.new do
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) { proc { client.channel('test').publish('event', 'data') } }
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) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
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
- Proc.new do
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) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
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
- Proc.new do
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
- @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
551
- if req.header["host"].first.include?(primary_host)
552
- @primary_host_requested = true
553
- sleep request_timeout + 0.5
554
- else
555
- @fallback_request_count ||= 0
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
- res.status = 200
561
- res['Content-Type'] = 'application/json'
562
- res.body = '{}'
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
- max_retry_duration: request_timeout * 3,
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 one of the fallback hosts (#RSC15d)' do
607
+ it 'tries the primary host, then both fallback hosts (#RSC15d)' do
587
608
  client.channel(channel_name).publish('event', 'data')
588
- expect(@primary_host_requested).to be_truthy
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 less than max_retry_duration' do
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
- max_retry_duration: request_timeout / 2,
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 'tries one of the fallback hosts (#RSC15d)' do
609
- client.channel(channel_name).publish('event', 'data')
610
- expect(@primary_host_requested).to be_truthy
611
- expect(@fallback_request_count).to eql(1)
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) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
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
- Proc.new do
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: :error)
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: :error)
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.0')
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 request_id', :webmock do
966
- let(:custom_logger) do
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 'when specifying fallback hosts', :webmock do
1010
- let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true } }
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
- it 'request_id is the same across retries' do
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