ably 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably/auth.rb +4 -4
  5. data/lib/ably/logger.rb +1 -1
  6. data/lib/ably/models/idiomatic_ruby_wrapper.rb +8 -8
  7. data/lib/ably/models/message.rb +6 -4
  8. data/lib/ably/models/presence_message.rb +6 -4
  9. data/lib/ably/modules/async_wrapper.rb +2 -2
  10. data/lib/ably/modules/conversions.rb +1 -1
  11. data/lib/ably/modules/encodeable.rb +1 -1
  12. data/lib/ably/modules/event_emitter.rb +2 -2
  13. data/lib/ably/modules/safe_deferrable.rb +1 -1
  14. data/lib/ably/modules/safe_yield.rb +1 -1
  15. data/lib/ably/modules/state_emitter.rb +5 -5
  16. data/lib/ably/realtime/auth.rb +1 -1
  17. data/lib/ably/realtime/channel.rb +3 -3
  18. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  19. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +3 -2
  20. data/lib/ably/realtime/connection.rb +11 -6
  21. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  22. data/lib/ably/realtime/presence.rb +3 -3
  23. data/lib/ably/realtime/presence/members_map.rb +6 -6
  24. data/lib/ably/rest/channel.rb +2 -2
  25. data/lib/ably/rest/client.rb +20 -12
  26. data/lib/ably/version.rb +1 -1
  27. data/spec/acceptance/realtime/auth_spec.rb +13 -37
  28. data/spec/acceptance/realtime/channel_history_spec.rb +7 -1
  29. data/spec/acceptance/realtime/channel_spec.rb +3 -3
  30. data/spec/acceptance/realtime/client_spec.rb +2 -2
  31. data/spec/acceptance/realtime/connection_failures_spec.rb +221 -7
  32. data/spec/acceptance/realtime/connection_spec.rb +13 -21
  33. data/spec/acceptance/realtime/message_spec.rb +2 -2
  34. data/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  35. data/spec/acceptance/realtime/presence_spec.rb +10 -10
  36. data/spec/acceptance/rest/auth_spec.rb +21 -48
  37. data/spec/acceptance/rest/client_spec.rb +193 -68
  38. data/spec/shared/client_initializer_behaviour.rb +1 -9
  39. data/spec/spec_helper.rb +2 -0
  40. data/spec/support/event_emitter_helper.rb +31 -0
  41. data/spec/support/event_machine_helper.rb +1 -1
  42. data/spec/support/test_logger_helper.rb +42 -0
  43. data/spec/unit/logger_spec.rb +1 -9
  44. data/spec/unit/modules/async_wrapper_spec.rb +2 -2
  45. data/spec/unit/modules/event_emitter_spec.rb +3 -3
  46. data/spec/unit/modules/state_emitter_spec.rb +10 -10
  47. data/spec/unit/realtime/channel_spec.rb +1 -1
  48. data/spec/unit/realtime/connection_spec.rb +1 -1
  49. data/spec/unit/realtime/presence_spec.rb +1 -1
  50. data/spec/unit/rest/channel_spec.rb +22 -0
  51. data/spec/unit/util/pub_sub_spec.rb +3 -3
  52. metadata +26 -8
@@ -65,10 +65,10 @@ describe Ably::Rest::Client do
65
65
  end
66
66
  end
67
67
 
68
- context 'with an :auth_callback Proc' do
69
- let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
68
+ context 'with an :auth_callback lambda' do
69
+ let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: lambda { |token_params| token_request })) }
70
70
 
71
- it 'calls the auth Proc to get a new token' do
71
+ it 'calls the auth lambda to get a new token' do
72
72
  expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
73
73
  expect(client.auth.current_token_details.client_id).to eql(client_id)
74
74
  end
@@ -93,8 +93,8 @@ describe Ably::Rest::Client do
93
93
  end
94
94
  end
95
95
 
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 })) }
96
+ context 'with an :auth_callback lambda (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: lambda { |token_params| token_request })) }
98
98
  let(:token_request) { client.auth.create_token_request({}, key_name: key_name, key_secret: key_secret) }
99
99
 
100
100
  it 'correctly sets the clientId on the token' do
@@ -178,7 +178,7 @@ describe Ably::Rest::Client do
178
178
 
179
179
  context 'using tokens' do
180
180
  let(:client) do
181
- Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new do
181
+ Ably::Rest::Client.new(client_options.merge(auth_callback: lambda do |token_params|
182
182
  @request_index ||= 0
183
183
  @request_index += 1
184
184
  send("token_request_#{@request_index > 2 ? 'next' : @request_index}")
@@ -290,7 +290,7 @@ describe Ably::Rest::Client do
290
290
 
291
291
  context 'fallback hosts', :webmock do
292
292
  let(:path) { '/channels/test/publish' }
293
- let(:publish_block) { proc { client.channel('test').publish('event', 'data') } }
293
+ let(:publish_block) { lambda { client.channel('test').publish('event', 'data') } }
294
294
 
295
295
  context 'configured' do
296
296
  let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
@@ -321,7 +321,7 @@ describe Ably::Rest::Client do
321
321
  let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
322
322
  let(:max_retry_count) { 2 }
323
323
  let(:max_retry_duration) { 0.5 }
324
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
324
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
325
325
  let(:client_options) do
326
326
  default_options.merge(
327
327
  environment: nil,
@@ -454,7 +454,7 @@ describe Ably::Rest::Client do
454
454
  context 'and server returns a 50x error' do
455
455
  let(:status) { 502 }
456
456
  let(:fallback_block) do
457
- Proc.new do
457
+ proc do
458
458
  {
459
459
  headers: { 'Content-Type' => 'text/html' },
460
460
  status: status
@@ -478,7 +478,7 @@ describe Ably::Rest::Client do
478
478
  let(:custom_hosts) { %w(A.foo.com B.foo.com) }
479
479
  let(:max_retry_count) { 2 }
480
480
  let(:max_retry_duration) { 0.5 }
481
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
481
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
482
482
  let(:production_options) do
483
483
  default_options.merge(
484
484
  environment: nil,
@@ -490,7 +490,7 @@ describe Ably::Rest::Client do
490
490
 
491
491
  let(:status) { 502 }
492
492
  let(:fallback_block) do
493
- Proc.new do
493
+ proc do
494
494
  {
495
495
  headers: { 'Content-Type' => 'text/html' },
496
496
  status: status
@@ -547,28 +547,36 @@ describe Ably::Rest::Client do
547
547
  context 'and timing out the primary host' do
548
548
  before do
549
549
  @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
550
+ request_handler = lambda do |result_body|
551
+ lambda do |req, res|
552
+ host = req.header["host"].first
553
+ if host.include?(primary_host)
554
+ @primary_host_request_count ||= 0
555
+ @primary_host_request_count += 1
558
556
  sleep request_timeout + 0.5
559
557
  else
560
- res.status = 200
561
- res['Content-Type'] = 'application/json'
562
- res.body = '{}'
558
+ @fallback_request_count ||= 0
559
+ @fallback_request_count += 1
560
+ @fallback_hosts_tried ||= []
561
+ @fallback_hosts_tried.push(host)
562
+ if @fallback_request_count <= fail_fallback_request_count
563
+ sleep request_timeout + 0.5
564
+ else
565
+ res.status = 200
566
+ res['Content-Type'] = 'application/json'
567
+ res.body = result_body
568
+ end
563
569
  end
564
570
  end
565
571
  end
572
+ @web_server.mount_proc "/time", &request_handler.call('[1000000000000]')
573
+ @web_server.mount_proc "/channels/#{channel_name}/publish", &request_handler.call('{}')
566
574
  Thread.new do
567
575
  @web_server.start
568
576
  end
569
577
  end
570
578
 
571
- context 'with request timeout less than max_retry_duration' do
579
+ context 'POST with request timeout less than max_retry_duration' do
572
580
  let(:client_options) do
573
581
  default_options.merge(
574
582
  rest_host: primary_host,
@@ -577,20 +585,44 @@ describe Ably::Rest::Client do
577
585
  port: port,
578
586
  tls: false,
579
587
  http_request_timeout: request_timeout,
580
- max_retry_duration: request_timeout * 3,
588
+ http_max_retry_duration: request_timeout * 2.5,
581
589
  log_level: :error
582
590
  )
583
591
  end
584
592
  let(:fail_fallback_request_count) { 1 }
585
593
 
586
- it 'tries one of the fallback hosts (#RSC15d)' do
594
+ it 'tries the primary host, then both fallback hosts (#RSC15d)' do
587
595
  client.channel(channel_name).publish('event', 'data')
588
- expect(@primary_host_requested).to be_truthy
596
+ expect(@primary_host_request_count).to eql(1)
597
+ expect(@fallback_request_count).to eql(2)
598
+ expect(@fallback_hosts_tried.uniq.length).to eql(2)
599
+ end
600
+ end
601
+
602
+ context 'GET with request timeout less than max_retry_duration' do
603
+ let(:client_options) do
604
+ default_options.merge(
605
+ rest_host: primary_host,
606
+ fallback_hosts: fallbacks,
607
+ token: 'fake.token',
608
+ port: port,
609
+ tls: false,
610
+ http_request_timeout: request_timeout,
611
+ http_max_retry_duration: request_timeout * 2.5,
612
+ log_level: :error
613
+ )
614
+ end
615
+ let(:fail_fallback_request_count) { 1 }
616
+
617
+ it 'tries the primary host, then both fallback hosts (#RSC15d)' do
618
+ client.time
619
+ expect(@primary_host_request_count).to eql(1)
589
620
  expect(@fallback_request_count).to eql(2)
621
+ expect(@fallback_hosts_tried.uniq.length).to eql(2)
590
622
  end
591
623
  end
592
624
 
593
- context 'with request timeout less than max_retry_duration' do
625
+ context 'POST with request timeout more than max_retry_duration' do
594
626
  let(:client_options) do
595
627
  default_options.merge(
596
628
  rest_host: primary_host,
@@ -599,18 +631,41 @@ describe Ably::Rest::Client do
599
631
  port: port,
600
632
  tls: false,
601
633
  http_request_timeout: request_timeout,
602
- max_retry_duration: request_timeout / 2,
634
+ http_max_retry_duration: request_timeout / 2,
603
635
  log_level: :error
604
636
  )
605
637
  end
606
638
  let(:fail_fallback_request_count) { 0 }
607
639
 
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)
640
+ it 'does not try any fallback hosts (#RSC15d)' do
641
+ expect { client.channel(channel_name).publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionTimeout
642
+ expect(@primary_host_request_count).to eql(1)
643
+ expect(@fallback_request_count).to be_nil
644
+ end
645
+ end
646
+
647
+ context 'GET with request timeout more than max_retry_duration' do
648
+ let(:client_options) do
649
+ default_options.merge(
650
+ rest_host: primary_host,
651
+ fallback_hosts: fallbacks,
652
+ token: 'fake.token',
653
+ port: port,
654
+ tls: false,
655
+ http_request_timeout: request_timeout,
656
+ http_max_retry_duration: request_timeout / 2,
657
+ log_level: :error
658
+ )
659
+ end
660
+ let(:fail_fallback_request_count) { 0 }
661
+
662
+ it 'does not try any fallback hosts (#RSC15d)' do
663
+ expect { client.time }.to raise_error Ably::Exceptions::ConnectionTimeout
664
+ expect(@primary_host_request_count).to eql(1)
665
+ expect(@fallback_request_count).to be_nil
612
666
  end
613
667
  end
668
+
614
669
  end
615
670
 
616
671
  context 'and failing the primary host' do
@@ -662,7 +717,7 @@ describe Ably::Rest::Client do
662
717
  let(:custom_hosts) { %w(A.foo.com B.foo.com) }
663
718
  let(:max_retry_count) { 2 }
664
719
  let(:max_retry_duration) { 0.5 }
665
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
720
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
666
721
  let(:env) { 'custom-env' }
667
722
  let(:production_options) do
668
723
  default_options.merge(
@@ -675,7 +730,7 @@ describe Ably::Rest::Client do
675
730
 
676
731
  let(:status) { 502 }
677
732
  let(:fallback_block) do
678
- Proc.new do
733
+ proc do
679
734
  {
680
735
  headers: { 'Content-Type' => 'text/html' },
681
736
  status: status
@@ -962,36 +1017,10 @@ describe Ably::Rest::Client do
962
1017
 
963
1018
  context 'request_id generation' do
964
1019
  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 }
1020
+ context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
1021
+ let(:custom_logger_object) { TestLogger.new }
994
1022
  let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
1023
+
995
1024
  before do
996
1025
  @request_id = nil
997
1026
  stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request|
@@ -1000,15 +1029,64 @@ describe Ably::Rest::Client do
1000
1029
  raise Faraday::TimeoutError.new('timeout error message')
1001
1030
  end
1002
1031
  end
1032
+
1003
1033
  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}/)
1034
+ expect { client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
1035
+ expect(@request_id).to be_a(String)
1036
+ expect(@request_id).to_not be_empty
1005
1037
  expect(custom_logger_object.logs.find { |severity, message| message.match(/#{@request_id}/i)} ).to_not be_nil
1006
1038
  end
1007
1039
  end
1008
1040
 
1009
- context 'when specifying fallback hosts', :webmock do
1010
- let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true } }
1041
+ context 'with option add_request_ids: true and REST operations with a message body' do
1042
+ let(:client_options) { default_options.merge({ key: api_key, add_request_ids: true }) }
1043
+ let(:channel_name) { random_str }
1044
+ let(:channel) { client.channels.get(channel_name) }
1045
+
1046
+ context 'with mocks to inspect the params', :webmock do
1047
+ before do
1048
+ stub_request(:post, Addressable::Template.new("#{client.endpoint}/channels/#{channel_name}/publish{?request_id}")).
1049
+ with do |request|
1050
+ @request_id = request.uri.query_values['request_id']
1051
+ end.to_return(:status => 200, :body => [], :headers => { 'Content-Type' => 'application/json' })
1052
+ end
1053
+
1054
+ context 'with a single publish' do
1055
+ it 'succeeds and sends the request_id as a param' do
1056
+ channel.publish('name', { body: random_str })
1057
+ expect(@request_id.to_s).to_not be_empty
1058
+ end
1059
+ end
1060
+
1061
+ context 'with an array publish' do
1062
+ it 'succeeds and sends the request_id as a param' do
1063
+ channel.publish([{ body: random_str }, { body: random_str }])
1064
+ expect(@request_id.to_s).to_not be_empty
1065
+ end
1066
+ end
1067
+ end
1068
+
1069
+ context 'without mocks to ensure the requests are accepted' do
1070
+ context 'with a single publish' do
1071
+ it 'succeeds and sends the request_id as a param' do
1072
+ channel.publish('name', { body: random_str })
1073
+ expect(channel.history.items.length).to eql(1)
1074
+ end
1075
+ end
1076
+
1077
+ context 'with an array publish' do
1078
+ it 'succeeds and sends the request_id as a param' do
1079
+ channel.publish([{ body: random_str }, { body: random_str }])
1080
+ expect(channel.history.items.length).to eql(2)
1081
+ end
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ context 'option add_request_ids: true and specified fallback hosts', :webmock do
1087
+ let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error } }
1011
1088
  let(:requests) { [] }
1089
+
1012
1090
  before do
1013
1091
  @request_id = nil
1014
1092
  hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io']
@@ -1021,8 +1099,11 @@ describe Ably::Rest::Client do
1021
1099
  end
1022
1100
  end
1023
1101
  end
1024
- it 'request_id is the same across retries' do
1102
+
1103
+ specify 'request_id is the same across retries' do
1025
1104
  expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
1105
+ expect(@request_id).to be_a(String)
1106
+ expect(@request_id).to_not be_empty
1026
1107
  expect(requests.uniq.count).to eql(1)
1027
1108
  expect(requests.uniq.first).to eql(@request_id)
1028
1109
  end
@@ -1030,6 +1111,7 @@ describe Ably::Rest::Client do
1030
1111
 
1031
1112
  context 'without request_id' do
1032
1113
  let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1114
+
1033
1115
  it 'does not include request_id in ConnectionTimeout error' do
1034
1116
  begin
1035
1117
  client.stats
@@ -1038,10 +1120,11 @@ describe Ably::Rest::Client do
1038
1120
  end
1039
1121
  end
1040
1122
  end
1041
- end
1123
+ end
1042
1124
 
1043
1125
  context 'UnauthorizedRequest nonce error' do
1044
1126
  let(:token_params) { { nonce: "samenonce_#{protocol}", timestamp: Time.now.to_i } }
1127
+
1045
1128
  it 'includes request_id in UnauthorizedRequest error due to replayed nonce' do
1046
1129
  client1 = Ably::Rest::Client.new(default_options.merge(key: api_key))
1047
1130
  client2 = Ably::Rest::Client.new(default_options.merge(key: api_key, add_request_ids: true))
@@ -1054,5 +1137,47 @@ describe Ably::Rest::Client do
1054
1137
  end
1055
1138
  end
1056
1139
  end
1140
+
1141
+ context 'failed request logging', :prevent_log_stubbing do
1142
+ let(:custom_logger) { TestLogger.new }
1143
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger) }
1144
+
1145
+ it 'is absent when requests do not fail' do
1146
+ client.time
1147
+ expect(custom_logger.logs(min_severity: :warn)).to be_empty
1148
+ end
1149
+
1150
+ context 'with the first request failing' do
1151
+ let(:client_options) do
1152
+ default_options.merge(
1153
+ rest_host: 'non.existent.domain.local',
1154
+ fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')],
1155
+ key: api_key,
1156
+ logger: custom_logger)
1157
+ end
1158
+
1159
+ it 'is present with success message when requests do not actually fail' do
1160
+ client.time
1161
+ expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to eql(1)
1162
+ expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/SUCCEEDED/) }.length).to eql(1)
1163
+ end
1164
+ end
1165
+
1166
+ context 'with all requests failing' do
1167
+ let(:client_options) do
1168
+ default_options.merge(
1169
+ rest_host: 'non.existent.domain.local',
1170
+ fallback_hosts: ['non2.existent.domain.local'],
1171
+ key: api_key,
1172
+ logger: custom_logger)
1173
+ end
1174
+
1175
+ it 'is present when all requests fail' do
1176
+ expect { client.time }.to raise_error(Ably::Exceptions::ConnectionError)
1177
+ expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to be >= 2
1178
+ expect(custom_logger.logs(min_severity: :error).select { |severity, msg| msg.match(/FAILED/) }.length).to eql(1)
1179
+ end
1180
+ end
1181
+ end
1057
1182
  end
1058
1183
  end
@@ -261,15 +261,7 @@ shared_examples 'a client initializer' do
261
261
  end
262
262
 
263
263
  context 'with custom logger and log_level' do
264
- let(:custom_logger) do
265
- Class.new do
266
- extend Forwardable
267
- def initialize
268
- @logger = Logger.new(STDOUT)
269
- end
270
- def_delegators :@logger, :fatal, :error, :warn, :info, :debug, :level, :level=
271
- end
272
- end
264
+ let(:custom_logger) { TestLogger }
273
265
  let(:client_options) { default_options.merge(logger: custom_logger.new, log_level: Logger::DEBUG, auto_connect: false) }
274
266
 
275
267
  it 'uses the custom logger' do
@@ -15,9 +15,11 @@ require 'ably'
15
15
 
16
16
  require 'support/api_helper'
17
17
  require 'support/debug_failure_helper'
18
+ require 'support/event_emitter_helper'
18
19
  require 'support/private_api_formatter'
19
20
  require 'support/protocol_helper'
20
21
  require 'support/random_helper'
22
+ require 'support/test_logger_helper'
21
23
 
22
24
  require 'rspec_config'
23
25