ably-rest 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -4
  3. data/lib/submodules/ably-ruby/.travis.yml +3 -0
  4. data/lib/submodules/ably-ruby/CHANGELOG.md +49 -4
  5. data/lib/submodules/ably-ruby/LICENSE +2 -2
  6. data/lib/submodules/ably-ruby/README.md +25 -6
  7. data/lib/submodules/ably-ruby/ably.gemspec +1 -1
  8. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +4 -2
  9. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +1 -1
  10. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +18 -4
  11. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +22 -3
  13. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +2 -2
  14. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  15. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  16. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +44 -0
  17. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +2 -0
  18. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +5 -2
  19. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +1 -1
  20. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +27 -2
  21. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +95 -0
  22. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +6 -3
  23. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +26 -7
  24. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +7 -0
  25. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db6fb6a1083ab0675e93787c68613dfe825626f8
4
- data.tar.gz: caafe4849ad502083c994fedbb1ff502dcaba821
3
+ metadata.gz: a0d3b8547bb94eb6d8efd1847fee0cc7231916da
4
+ data.tar.gz: 34e92947e49eb6ff09f3bc608277f09ef1e9b390
5
5
  SHA512:
6
- metadata.gz: 4fe732572bdbf0a4d1369f766fa8a2845f18919eb4d362f050ae44abdb25f8ee8beb402cdd263a7fb8e5355356e96d2a35f32633a9434ef52a81a98b4a0aedb1
7
- data.tar.gz: 75d72d55a94cf6e60b4b50fa8b1f4bc60c53aa601e9d199b38a4ace1539c74a7eb08655856af382c6941daffd51910659fdacf02429a776244a81196afe66ffd
6
+ metadata.gz: 155934f7ef691a2248776199b86ab243aa8e872193b28c40afaa77f08dc939d24bb1cb7592cb0bda624f4418d4a2ce7e68f969ed57c7494d4c663a25fc90e9ec
7
+ data.tar.gz: d935e38fa7b0bbbbdafc071b63bfecedcfe5693160438f802a8a18a3db106c5aeada7845485b0d128037a09e3b69b84bf6f45ed3b7180a21bf0bd582e497d705
data/README.md CHANGED
@@ -5,11 +5,15 @@
5
5
 
6
6
  A Ruby REST client library for [www.ably.io](https://www.ably.io), the realtime messaging service.
7
7
 
8
- Note: This library was created solely for developers who do not want EventMachine as a dependency of their application. If this is not a requirement for you, then we recommended you use the combined [REST & Realtime gem](https://rubygems.org/gems/ably).
9
-
10
8
  ## Documentation
11
9
 
12
- Visit https://www.ably.io/documentation for a complete API reference and more examples. The examples and API below is not exhaustive.
10
+ Visit https://www.ably.io/documentation for a complete API reference and more examples. The examples and API below is not exhaustive, you should use the completely [Ably API documentation](https://www.ably.io/documentation).
11
+
12
+ ## Realtime vs REST
13
+
14
+ This REST only library was created for developers who do not want EventMachine as a dependency of their application. Typically developers who are using Ably within their Rails or Sinatra apps would prefer to use the REST library as it has less dependencies and offers a synchronous API.
15
+
16
+ If however you need to use a realtime library that offers an asynchronous evented AP, then we recommended you [take a look at the combined REST & Realtime gem](https://rubygems.org/gems/ably).
13
17
 
14
18
  ## Installation
15
19
 
@@ -142,6 +146,10 @@ To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANG
142
146
  4. Push to the branch (`git push origin my-new-feature`)
143
147
  5. Create a new Pull Request
144
148
 
149
+ ## Release Process
150
+
151
+ See the [Ably Ruby release process notes](https://github.com/ably/ably-ruby#release-process).
152
+
145
153
  ## License
146
154
 
147
- Copyright (c) 2016 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
155
+ Copyright (c) 2017 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
@@ -6,7 +6,10 @@ language: ruby
6
6
  rvm:
7
7
  - 1.9.3
8
8
  - 2.0.0
9
+ - 2.1.10
9
10
  - 2.2.0
11
+ - 2.3.6
12
+ - 2.4.1
10
13
  script: bundle exec rspec
11
14
  notifications:
12
15
  slack:
@@ -1,11 +1,56 @@
1
1
  # Change Log
2
2
 
3
- ## [v1.0.0](https://github.com/ably/ably-ruby/tree/v1.0.0) (2017-03-07)
4
- [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.14...v1.0.0)
3
+ ## [Unreleased](https://github.com/ably/ably-ruby/tree/v1.0.5)
4
+
5
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.4...v1.0.5)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add Ruby 2.1 and 2.3 to Travis tests [\#129](https://github.com/ably/ably-ruby/issues/129)
10
+ - Add supported platforms to README file [\#128](https://github.com/ably/ably-ruby/issues/128)
11
+ - Add Ruby 2.1 and 2.3 to Travis tests [\#130](https://github.com/ably/ably-ruby/pull/130) ([funkyboy](https://github.com/funkyboy))
12
+
13
+ **Closed issues:**
14
+
15
+ - Cannot get realtime to work [\#127](https://github.com/ably/ably-ruby/issues/127)
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Improve pagination history test [\#138](https://github.com/ably/ably-ruby/pull/138) ([funkyboy](https://github.com/funkyboy))
20
+ - Fix failing auth test [\#135](https://github.com/ably/ably-ruby/pull/135) ([funkyboy](https://github.com/funkyboy))
21
+ - Add submodule instructions to Contributing section [\#134](https://github.com/ably/ably-ruby/pull/134) ([funkyboy](https://github.com/funkyboy))
22
+ - Add request\_id option to client [\#133](https://github.com/ably/ably-ruby/pull/133) ([funkyboy](https://github.com/funkyboy))
23
+ - Update README with supported platforms [\#131](https://github.com/ably/ably-ruby/pull/131) ([funkyboy](https://github.com/funkyboy))
24
+
25
+ ## [v1.0.4](https://github.com/ably/ably-ruby/tree/v1.0.4) (2017-05-31)
26
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.3...v1.0.4)
5
27
 
6
- ### v1.0 release and upgrade notes from v0.8
28
+ ## [v1.0.3](https://github.com/ably/ably-ruby/tree/v1.0.3) (2017-05-31)
29
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.2...v1.0.3)
30
+
31
+ ## [v1.0.2](https://github.com/ably/ably-ruby/tree/v1.0.2) (2017-05-16)
32
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.1...v1.0.2)
33
+
34
+ **Fixed bugs:**
35
+
36
+ - Reconnect following disconnection is hitting a 403 error [\#117](https://github.com/ably/ably-ruby/issues/117)
37
+
38
+ **Merged pull requests:**
39
+
40
+ - Fallback fixes [\#120](https://github.com/ably/ably-ruby/pull/120) ([mattheworiordan](https://github.com/mattheworiordan))
41
+ - Channel name encoding error for REST requests [\#119](https://github.com/ably/ably-ruby/pull/119) ([mattheworiordan](https://github.com/mattheworiordan))
42
+
43
+ ## [v1.0.1](https://github.com/ably/ably-ruby/tree/v1.0.1) (2017-05-11)
44
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.1.0-beta.push.1...v1.0.1)
45
+
46
+ ## [v1.1.0-beta.push.1](https://github.com/ably/ably-ruby/tree/v1.1.0-beta.push.1) (2017-04-25)
47
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v1.0.0...v1.1.0-beta.push.1)
48
+
49
+ ## [v1.0.0](https://github.com/ably/ably-ruby/tree/v1.0.0) (2017-03-07)
50
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.15...v1.0.0)
7
51
 
8
- - See https://github.com/ably/docs/issues/235
52
+ ## [v0.8.15](https://github.com/ably/ably-ruby/tree/v0.8.15) (2017-03-07)
53
+ [Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.14...v0.8.15)
9
54
 
10
55
  **Implemented enhancements:**
11
56
 
@@ -1,6 +1,6 @@
1
- Copyright (c) 2016 Ably
1
+ Copyright (c) 2015-2017 Ably
2
2
 
3
- Copyright 2016 Ably Real-time Ltd
3
+ Copyright 2015-2017 Ably Real-time Ltd
4
4
 
5
5
  Licensed under the Apache License, Version 2.0 (the "License");
6
6
  you may not use this file except in compliance with the License.
@@ -5,6 +5,14 @@
5
5
 
6
6
  A Ruby client library for [ably.io](https://www.ably.io), the realtime messaging service.
7
7
 
8
+ ## Supported platforms
9
+
10
+ This SDK supports Ruby 1.9.3+.
11
+
12
+ We regression-test the SDK against a selection of Ruby versions (which we update over time, but usually consists of mainstream and widely used versions). Please refer to [.travis.yml](./.travis.yml) for the set of versions that currently undergo CI testing.
13
+
14
+ If you find any compatibility issues, please [do raise an issue](https://github.com/ably/ably-ruby/issues/new) in this repository or [contact Ably customer support](https://support.ably.io/) for advice.
15
+
8
16
  ## Documentation
9
17
 
10
18
  Visit https://www.ably.io/documentation for a complete API reference and more examples.
@@ -296,12 +304,23 @@ To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANG
296
304
  ## Contributing
297
305
 
298
306
  1. Fork it
299
- 2. Create your feature branch (`git checkout -b my-new-feature`)
300
- 3. Commit your changes (`git commit -am 'Add some feature'`)
301
- 4. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
302
- 4. Push to the branch (`git push origin my-new-feature`)
303
- 5. Create a new Pull Request
307
+ 2. When pulling to local, make sure to also pull the `ably-common` repo (`git submodule init && git submodule update`)
308
+ 3. Create your feature branch (`git checkout -b my-new-feature`)
309
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
310
+ 5. Ensure you have added suitable tests and the test suite is passing(`bundle exec rspec`)
311
+ 6. Push to the branch (`git push origin my-new-feature`)
312
+ 7. Create a new Pull Request
313
+
314
+ ## Release process
315
+
316
+ This library uses [semantic versioning](http://semver.org/). For each release, the following needs to be done:
317
+
318
+ * Update the version number in [version.rb](./lib/ably/version.rb) and commit the change.
319
+ * Run [`github_changelog_generator`](https://github.com/skywinder/Github-Changelog-Generator) to automate the update of the [CHANGELOG](./CHANGELOG.md). Once the `CHANGELOG` update has completed, manually change the `Unreleased` heading and link with the current version number such as `v1.0.0`. Also ensure that the `Full Changelog` link points to the new version tag instead of the `HEAD`. Commit this change.
320
+ * Add a tag and push to origin such as `git tag v1.0.0 && git push origin v1.0.0`
321
+ * Visit [https://github.com/ably/ably-ruby/tags](https://github.com/ably/ably-ruby/tags) and `Add release notes` for the release including links to the changelog entry.
322
+ * Run `rake release` to publish the gem to [Rubygems](http://www.rubydoc.info/gems/ably)
304
323
 
305
324
  ## License
306
325
 
307
- Copyright (c) 2016 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
326
+ Copyright (c) 2017 Ably Real-time Ltd, Licensed under the Apache License, Version 2.0. Refer to [LICENSE](LICENSE) for the license terms.
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.description = %q{A Ruby client library for ably.io realtime messaging}
12
12
  spec.summary = %q{A Ruby client library for ably.io realtime messaging implemented using EventMachine}
13
13
  spec.homepage = 'http://github.com/ably/ably-ruby'
14
- spec.license = 'Apache 2'
14
+ spec.license = 'Apache-2.0'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -13,7 +13,7 @@ module Ably
13
13
  # @!attribute [r] code
14
14
  # @return [String] Ably specific error code
15
15
  class BaseAblyException < StandardError
16
- attr_reader :status, :code
16
+ attr_reader :status, :code, :request_id
17
17
 
18
18
  def initialize(message, status = nil, code = nil, base_exception = nil, options = {})
19
19
  super message
@@ -25,6 +25,7 @@ module Ably
25
25
  @code = code
26
26
  @code ||= base_exception.code if base_exception && base_exception.respond_to?(:code)
27
27
  @code ||= options[:fallback_code]
28
+ @request_id ||= options[:request_id]
28
29
  end
29
30
 
30
31
  def to_s
@@ -34,12 +35,13 @@ module Ably
34
35
  additional_info << "code: #{code}" if code
35
36
  additional_info << "http status: #{status}" if status
36
37
  additional_info << "base exception: #{@base_exception.class}" if @base_exception
38
+ additional_info << "request_id: #{request_id}" if request_id
37
39
  message << "(#{additional_info.join(', ')})"
38
40
  end
39
41
  message.join(' ')
40
42
  end
41
43
 
42
- def as_json
44
+ def as_json(*args)
43
45
  {
44
46
  message: "#{self.class}: #{message}",
45
47
  status: @status,
@@ -26,7 +26,7 @@ module Ably::Modules
26
26
 
27
27
  # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
28
28
  # @return [Hash]
29
- def as_json
29
+ def as_json(*args)
30
30
  attributes.as_json.reject { |key, val| val.nil? }
31
31
  end
32
32
 
@@ -351,7 +351,7 @@ module Ably
351
351
  def determine_host
352
352
  raise ArgumentError, 'Block required' unless block_given?
353
353
 
354
- if can_use_fallback_hosts?
354
+ if should_use_fallback_hosts?
355
355
  internet_up? do |internet_is_up_result|
356
356
  @current_host = if internet_is_up_result
357
357
  client.fallback_endpoint.host
@@ -424,7 +424,8 @@ module Ably
424
424
  )
425
425
 
426
426
  # Use native websocket heartbeats if possible
427
- url_params['heartbeats'] = 'false' unless defaults.fetch(:websocket_heartbeats_disabled)
427
+ # TODO: Fix once https://github.com/ably/ably-ruby/issues/116 is resolved
428
+ url_params['heartbeats'] = 'true' # unless defaults.fetch(:websocket_heartbeats_disabled)
428
429
 
429
430
  url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
430
431
 
@@ -444,6 +445,10 @@ module Ably
444
445
  end
445
446
 
446
447
  determine_host do |host|
448
+ # Ensure the hostname matches the fallback host name
449
+ url.hostname = host
450
+ url.port = port
451
+
447
452
  begin
448
453
  logger.debug { "Connection: Opening socket connection to #{host}:#{port}/#{url.path}?#{url.query}" }
449
454
  @transport = create_transport(host, port, url) do |websocket_transport|
@@ -509,6 +514,7 @@ module Ably
509
514
 
510
515
  # @api private
511
516
  def create_transport(host, port, url, &block)
517
+ logger.debug { "Connection: EventMachine connecting to #{host}:#{port} with URL: #{url}" }
512
518
  EventMachine.connect(host, port, WebsocketTransport, self, url.to_s, &block)
513
519
  end
514
520
 
@@ -641,14 +647,22 @@ module Ably
641
647
  !!client.custom_realtime_host
642
648
  end
643
649
 
644
- def can_use_fallback_hosts?
650
+ def should_use_fallback_hosts?
645
651
  if client.fallback_hosts && !client.fallback_hosts.empty?
646
- if connecting? && previous_state
652
+ if connecting? && previous_state && !disconnected_from_connected_state?
647
653
  use_fallback_if_disconnected? || use_fallback_if_suspended?
648
654
  end
649
655
  end
650
656
  end
651
657
 
658
+ def disconnected_from_connected_state?
659
+ most_recent_state_changes = state_history.last(3).first(2) # Ignore current state
660
+
661
+ # A valid connection was disconnected
662
+ most_recent_state_changes.last.fetch(:state) == Connection::STATE.Disconnected &&
663
+ most_recent_state_changes.first.fetch(:state) == Connection::STATE.Connected
664
+ end
665
+
652
666
  def use_fallback_if_disconnected?
653
667
  second_reconnect_attempt_for(:disconnected, 1)
654
668
  end
@@ -130,7 +130,7 @@ module Ably
130
130
 
131
131
  private
132
132
  def base_path
133
- "/channels/#{Addressable::URI.encode(name)}"
133
+ "/channels/#{URI.encode_www_form_component(name)}"
134
134
  end
135
135
 
136
136
  def decode_message(message)
@@ -83,6 +83,10 @@ module Ably
83
83
  # if empty or nil then fallback host functionality is disabled
84
84
  attr_reader :fallback_hosts
85
85
 
86
+ # Whethere the {Client} has to add a random identifier to the path of a request
87
+ # @return [Boolean]
88
+ attr_reader :add_request_ids
89
+
86
90
  # Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
87
91
  #
88
92
  # @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
@@ -146,6 +150,7 @@ module Ably
146
150
  @custom_host = options.delete(:rest_host)
147
151
  @custom_port = options.delete(:port)
148
152
  @custom_tls_port = options.delete(:tls_port)
153
+ @add_request_ids = options.delete(:add_request_ids)
149
154
 
150
155
  if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
151
156
  raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
@@ -434,11 +439,25 @@ module Ably
434
439
  max_retry_duration = http_defaults.fetch(:max_retry_duration)
435
440
  requested_at = Time.now
436
441
  retry_count = 0
442
+ request_id = nil
443
+ if add_request_ids
444
+ params = if params.nil?
445
+ {}
446
+ else
447
+ params.dup
448
+ end
449
+ request_id = SecureRandom.urlsafe_base64(10)
450
+ params[:request_id] = request_id
451
+ end
437
452
 
438
453
  begin
439
454
  use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
440
455
 
441
456
  connection(use_fallback: use_fallback).send(method, path, params) do |request|
457
+ if add_request_ids
458
+ request.options.context = {} if request.options.context.nil?
459
+ request.options.context[:request_id] = request_id
460
+ end
442
461
  unless options[:send_auth_header] == false
443
462
  request.headers[:authorization] = auth.auth_header
444
463
  if options[:headers]
@@ -456,12 +475,12 @@ module Ably
456
475
  logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}" }
457
476
  retry
458
477
  end
459
-
460
478
  case error
461
479
  when Faraday::TimeoutError
462
- raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error)
480
+ raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, { request_id: request_id })
463
481
  when Faraday::ClientError
464
- raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error)
482
+ # request_id is also available in the request context
483
+ raise Ably::Exceptions::ConnectionError.new(error.message, nil, 80000, error, { request_id: request_id })
465
484
  else
466
485
  raise error
467
486
  end
@@ -27,8 +27,8 @@ module Ably
27
27
  end
28
28
 
29
29
  message = 'Unknown server error' if message.to_s.strip == ''
30
-
31
- exception_args = [message, error_status_code, error_code]
30
+ request_id = env.request.context[:request_id] if env.request.context
31
+ exception_args = [message, error_status_code, error_code, nil, { request_id: request_id }]
32
32
 
33
33
  if env.status >= 500
34
34
  raise Ably::Exceptions::ServerError.new(*exception_args)
@@ -84,7 +84,7 @@ module Ably
84
84
 
85
85
  private
86
86
  def base_path
87
- "/channels/#{Addressable::URI.encode(channel.name)}/presence"
87
+ "/channels/#{URI.encode_www_form_component(channel.name)}/presence"
88
88
  end
89
89
 
90
90
  def decode_message(presence_message)
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.5'
3
3
  PROTOCOL_VERSION = '1.0'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -1178,6 +1178,27 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1178
1178
  stop_reactor
1179
1179
  end
1180
1180
  end
1181
+
1182
+ it 'does not use a fallback host if the connection connects on the default host and then later becomes disconnected', em_timeout: 25 do
1183
+ request = 0
1184
+
1185
+ allow(connection).to receive(:create_transport).and_wrap_original do |wrapped_proc, host, *args, &block|
1186
+ expect(host).to eql(expected_host)
1187
+ request += 1
1188
+ wrapped_proc.call(host, *args, &block)
1189
+ end
1190
+
1191
+ connection.on(:connected) do
1192
+ if request <= 2
1193
+ EventMachine.add_timer(3) do
1194
+ # Force a disconnect
1195
+ connection.transport.unbind
1196
+ end
1197
+ else
1198
+ stop_reactor
1199
+ end
1200
+ end
1201
+ end
1181
1202
  end
1182
1203
 
1183
1204
  context ':fallback_hosts array is provided' do
@@ -1286,6 +1307,29 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1286
1307
  end
1287
1308
  end
1288
1309
  end
1310
+
1311
+ it 'uses the correct host name for the WebSocket requests to the fallback hosts' do
1312
+ request = 0
1313
+ expect(connection).to receive(:create_transport).at_least(:once) do |host, port, uri|
1314
+ if request == 0 || request == expected_retry_attempts + 1
1315
+ expect(uri.hostname).to eql(expected_host)
1316
+ else
1317
+ expect(custom_hosts + [expected_host]).to include(uri.hostname)
1318
+ fallback_hosts_used << host if @suspended > 0
1319
+ end
1320
+ request += 1
1321
+ raise EventMachine::ConnectionError
1322
+ end
1323
+
1324
+ connection.on(:suspended) do
1325
+ @suspended += 1
1326
+
1327
+ if @suspended > 4
1328
+ expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host])
1329
+ stop_reactor
1330
+ end
1331
+ end
1332
+ end
1289
1333
  end
1290
1334
 
1291
1335
  context ':fallback_hosts array is provided by an empty array' do
@@ -980,6 +980,7 @@ describe Ably::Realtime::Connection, :event_machine do
980
980
 
981
981
  context 'transport-level heartbeats are supported in the websocket transport' do
982
982
  it 'provides the heartbeats argument in the websocket connection params (#RTN23b)' do
983
+ skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
983
984
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
984
985
  uri = URI.parse(url)
985
986
  expect(CGI::parse(uri.query)['heartbeats'][0]).to eql('false')
@@ -1007,6 +1008,7 @@ describe Ably::Realtime::Connection, :event_machine do
1007
1008
  let(:client_options) { default_options.merge(websocket_heartbeats_disabled: true) }
1008
1009
 
1009
1010
  it 'does not provide the heartbeats argument in the websocket connection params (#RTN23b)' do
1011
+ skip 'Native heartbeats not yet supported in the WS driver https://github.com/ably/ably-ruby/issues/116'
1010
1012
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1011
1013
  uri = URI.parse(url)
1012
1014
  expect(CGI::parse(uri.query)['heartbeats'][0]).to be_nil
@@ -76,6 +76,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
76
76
  end
77
77
 
78
78
  context 'with supported extra payload content type (#RTL6h, #RSL6a2)' do
79
+ let(:channel) { client.channel("pushenabled:#{random_str}") }
80
+
79
81
  def publish_and_check_extras(extras)
80
82
  channel.attach
81
83
  channel.publish 'event', {}, extras: extras
@@ -86,7 +88,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
86
88
  end
87
89
 
88
90
  context 'JSON Object (Hash)' do
89
- let(:data) { { 'push' => { 'title' => 'Testing' } } }
91
+ let(:data) { { 'push' => { 'notification' => { 'title' => 'Testing' } } } }
90
92
 
91
93
  it 'is encoded and decoded to the same hash' do
92
94
  publish_and_check_extras data
@@ -94,7 +96,8 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
94
96
  end
95
97
 
96
98
  context 'JSON Array' do
97
- let(:data) { { 'push' => [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } }
99
+ # TODO: Add nil type back in
100
+ let(:data) { { 'push' => { 'data' => { 'key' => [ true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } } } }
98
101
 
99
102
  it 'is encoded and decoded to the same Array' do
100
103
  publish_and_check_extras data
@@ -1083,7 +1083,7 @@ describe Ably::Auth do
1083
1083
  expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
1084
1084
  expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest)
1085
1085
  expect(error.status).to eql(401)
1086
- expect(error.code).to eql(40101)
1086
+ expect(error.code).to eql(40104)
1087
1087
  end
1088
1088
  end
1089
1089
 
@@ -256,6 +256,25 @@ describe Ably::Rest::Channel do
256
256
  end
257
257
  end
258
258
  end
259
+
260
+ context 'with a non ASCII channel name' do
261
+ let(:channel_name) { 'foo:¡€≤`☃' }
262
+ let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' }
263
+ let(:endpoint) { client.endpoint }
264
+ let(:channel) { client.channels.get(channel_name) }
265
+
266
+ context 'stubbed', :webmock do
267
+ let!(:get_stub) {
268
+ stub_request(:post, "#{endpoint}/channels/#{channel_name_encoded}/publish").
269
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
270
+ }
271
+
272
+ it 'correctly encodes the channel name' do
273
+ channel.publish('foo')
274
+ expect(get_stub).to have_been_requested
275
+ end
276
+ end
277
+ end
259
278
  end
260
279
 
261
280
  describe '#history' do
@@ -324,7 +343,13 @@ describe Ably::Rest::Channel do
324
343
 
325
344
  # Page 3
326
345
  expect(page_3.items.size).to eql(1)
327
- expect(page_3).to be_last
346
+ # This test should be deterministic but it's not.
347
+ # Sometimes the backend, to avoid too much work, returns a `next` link that contains empty reults.
348
+ if page_3.next
349
+ expect(page_3.next.items.length).to eql(0)
350
+ else
351
+ expect(page_3).to be_last
352
+ end
328
353
  end
329
354
 
330
355
  context 'direction' do
@@ -374,7 +399,7 @@ describe Ably::Rest::Channel do
374
399
  let!(:history_stub) {
375
400
  query_params = default_history_options
376
401
  .merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
377
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/messages?#{query_params}").
402
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}").
378
403
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
379
404
  }
380
405
 
@@ -959,5 +959,100 @@ describe Ably::Rest::Client do
959
959
  end
960
960
  end
961
961
  end
962
+
963
+ context 'request_id generation' do
964
+ 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 }
994
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
995
+ before do
996
+ @request_id = nil
997
+ stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request|
998
+ @request_id = request.uri.query_values['request_id']
999
+ end.to_return do
1000
+ raise Faraday::TimeoutError.new('timeout error message')
1001
+ end
1002
+ end
1003
+ 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}/)
1005
+ expect(custom_logger_object.logs.find { |severity, message| message.match(/#{@request_id}/i)} ).to_not be_nil
1006
+ end
1007
+ end
1008
+
1009
+ context 'when specifying fallback hosts', :webmock do
1010
+ let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true } }
1011
+ let(:requests) { [] }
1012
+ before do
1013
+ @request_id = nil
1014
+ hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io']
1015
+ hosts.each do |host|
1016
+ stub_request(:get, Addressable::Template.new("https://#{host.downcase}/time{?request_id}")).with do |request|
1017
+ @request_id = request.uri.query_values['request_id']
1018
+ requests << @request_id
1019
+ end.to_return do
1020
+ raise Faraday::TimeoutError.new('timeout error message')
1021
+ end
1022
+ end
1023
+ end
1024
+ it 'request_id is the same across retries' do
1025
+ expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
1026
+ expect(requests.uniq.count).to eql(1)
1027
+ expect(requests.uniq.first).to eql(@request_id)
1028
+ end
1029
+ end
1030
+
1031
+ context 'without request_id' do
1032
+ let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1033
+ it 'does not include request_id in ConnectionTimeout error' do
1034
+ begin
1035
+ client.stats
1036
+ rescue Ably::Exceptions::ConnectionTimeout => err
1037
+ expect(err.request_id).to eql(nil)
1038
+ end
1039
+ end
1040
+ end
1041
+ end
1042
+
1043
+ context 'UnauthorizedRequest nonce error' do
1044
+ let(:token_params) { { nonce: "samenonce_#{protocol}", timestamp: Time.now.to_i } }
1045
+ it 'includes request_id in UnauthorizedRequest error due to replayed nonce' do
1046
+ client1 = Ably::Rest::Client.new(default_options.merge(key: api_key))
1047
+ client2 = Ably::Rest::Client.new(default_options.merge(key: api_key, add_request_ids: true))
1048
+ expect { client1.auth.request_token(token_params) }.not_to raise_error
1049
+ begin
1050
+ client2.auth.request_token(token_params)
1051
+ rescue Ably::Exceptions::UnauthorizedRequest => err
1052
+ expect(err.request_id).to_not eql(nil)
1053
+ end
1054
+ end
1055
+ end
1056
+ end
962
1057
  end
963
1058
  end
@@ -62,8 +62,10 @@ describe Ably::Rest::Channel, 'messages' do
62
62
  end
63
63
 
64
64
  context 'with supported extra payload content type (#RSL1h, #RSL6a2)' do
65
+ let(:channel) { client.channel("pushenabled:#{random_str}") }
66
+
65
67
  context 'JSON Object (Hash)' do
66
- let(:data) { { 'push' => { 'title' => 'Testing' } } }
68
+ let(:data) { { 'push' => { 'notification' => { 'title' => 'Testing' } } } }
67
69
 
68
70
  it 'is encoded and decoded to the same hash' do
69
71
  channel.publish 'event', {}, extras: data
@@ -72,9 +74,10 @@ describe Ably::Rest::Channel, 'messages' do
72
74
  end
73
75
 
74
76
  context 'JSON Array' do
75
- let(:data) { { 'push' => [ nil, true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } }
77
+ # TODO: Add nil type back in
78
+ let(:data) { { 'push' => { 'data' => { 'key' => [ true, false, 55, 'string', { 'Hash' => true }, ['array'] ] } } } }
76
79
 
77
- it 'is encoded and decoded to the same Array' do
80
+ it 'is encoded and decoded to the same deep multi-type object' do
78
81
  channel.publish 'event', {}, extras: data
79
82
  expect(channel.history.items.first.extras).to eql(data)
80
83
  end
@@ -73,7 +73,7 @@ describe Ably::Rest::Presence do
73
73
  end
74
74
  let!(:get_stub) {
75
75
  query_params = query_options.map { |k, v| "#{k}=#{v}" }.join('&')
76
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?#{query_params}").
76
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}").
77
77
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
78
78
  }
79
79
  let(:channel_name) { random_str }
@@ -111,6 +111,25 @@ describe Ably::Rest::Presence do
111
111
  expect(fixtures_channel.presence.get(connection_id: 'does.not.exist').items).to be_empty
112
112
  end
113
113
  end
114
+
115
+ context 'with a non ASCII channel name' do
116
+ let(:channel_name) { 'foo:¡€≤`☃' }
117
+ let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' }
118
+ let(:endpoint) { client.endpoint }
119
+ let(:channel) { client.channels.get(channel_name) }
120
+
121
+ context 'stubbed', :webmock do
122
+ let!(:get_stub) {
123
+ stub_request(:get, "#{endpoint}/channels/#{channel_name_encoded}/presence?limit=100").
124
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
125
+ }
126
+
127
+ it 'correctly encodes the channel name' do
128
+ channel.presence.get
129
+ expect(get_stub).to have_been_requested
130
+ end
131
+ end
132
+ end
114
133
  end
115
134
 
116
135
  describe '#history' do
@@ -194,7 +213,7 @@ describe Ably::Rest::Presence do
194
213
  context 'limit options', :webmock do
195
214
  let!(:history_stub) {
196
215
  query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&')
197
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?#{query_params}").
216
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}").
198
217
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
199
218
  }
200
219
 
@@ -234,7 +253,7 @@ describe Ably::Rest::Presence do
234
253
  }
235
254
  let!(:history_stub) {
236
255
  query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&')
237
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?#{query_params}").
256
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}").
238
257
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
239
258
  }
240
259
 
@@ -338,7 +357,7 @@ describe Ably::Rest::Presence do
338
357
 
339
358
  context '#get' do
340
359
  let!(:get_stub) {
341
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?limit=100").
360
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100").
342
361
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
343
362
  }
344
363
 
@@ -355,7 +374,7 @@ describe Ably::Rest::Presence do
355
374
 
356
375
  context '#history' do
357
376
  let!(:history_stub) {
358
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?direction=backwards&limit=100").
377
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100").
359
378
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
360
379
  }
361
380
 
@@ -385,7 +404,7 @@ describe Ably::Rest::Presence do
385
404
  context '#get' do
386
405
  let(:client_options) { default_options.merge(log_level: :fatal) }
387
406
  let!(:get_stub) {
388
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence?limit=100").
407
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100").
389
408
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
390
409
  }
391
410
  let(:presence_message) { presence.get.items.first }
@@ -409,7 +428,7 @@ describe Ably::Rest::Presence do
409
428
  context '#history' do
410
429
  let(:client_options) { default_options.merge(log_level: :fatal) }
411
430
  let!(:history_stub) {
412
- stub_request(:get, "#{endpoint}/channels/#{Addressable::URI.encode(channel_name)}/presence/history?direction=backwards&limit=100").
431
+ stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100").
413
432
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
414
433
  }
415
434
  let(:presence_message) { presence.history.items.first }
@@ -50,4 +50,11 @@ describe Ably::Rest::Client do
50
50
  end
51
51
  end
52
52
  end
53
+
54
+ context 'request_id generation' do
55
+ let(:client_options) { { key: 'appid.keyuid:keysecret', add_request_ids: true } }
56
+ it 'includes request_id in URL' do
57
+ expect(subject.add_request_ids).to eql(true)
58
+ end
59
+ end
53
60
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ably-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew O'Riordan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-07 00:00:00.000000000 Z
11
+ date: 2018-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday