ably 1.1.1 → 1.1.5

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +27 -0
  3. data/CHANGELOG.md +60 -2
  4. data/COPYRIGHT +1 -0
  5. data/LICENSE +172 -11
  6. data/MAINTAINERS.md +1 -0
  7. data/README.md +2 -14
  8. data/SPEC.md +1020 -922
  9. data/ably.gemspec +5 -5
  10. data/lib/ably/auth.rb +12 -2
  11. data/lib/ably/exceptions.rb +2 -2
  12. data/lib/ably/logger.rb +7 -1
  13. data/lib/ably/modules/ably.rb +11 -1
  14. data/lib/ably/modules/state_machine.rb +1 -1
  15. data/lib/ably/realtime/channel.rb +7 -11
  16. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  17. data/lib/ably/realtime/channel/channel_properties.rb +24 -0
  18. data/lib/ably/realtime/client.rb +9 -0
  19. data/lib/ably/realtime/connection.rb +7 -4
  20. data/lib/ably/realtime/connection/connection_manager.rb +19 -1
  21. data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  22. data/lib/ably/realtime/presence.rb +0 -14
  23. data/lib/ably/rest/channel.rb +25 -17
  24. data/lib/ably/rest/client.rb +35 -17
  25. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  26. data/lib/ably/version.rb +1 -1
  27. data/spec/acceptance/realtime/auth_spec.rb +5 -4
  28. data/spec/acceptance/realtime/channel_spec.rb +21 -8
  29. data/spec/acceptance/realtime/client_spec.rb +80 -20
  30. data/spec/acceptance/realtime/connection_failures_spec.rb +90 -9
  31. data/spec/acceptance/realtime/connection_spec.rb +47 -19
  32. data/spec/acceptance/realtime/message_spec.rb +2 -4
  33. data/spec/acceptance/realtime/presence_history_spec.rb +0 -58
  34. data/spec/acceptance/realtime/presence_spec.rb +54 -0
  35. data/spec/acceptance/realtime/push_admin_spec.rb +43 -21
  36. data/spec/acceptance/rest/auth_spec.rb +6 -75
  37. data/spec/acceptance/rest/base_spec.rb +8 -4
  38. data/spec/acceptance/rest/channel_spec.rb +42 -4
  39. data/spec/acceptance/rest/client_spec.rb +121 -26
  40. data/spec/acceptance/rest/message_spec.rb +1 -2
  41. data/spec/acceptance/rest/push_admin_spec.rb +67 -27
  42. data/spec/shared/client_initializer_behaviour.rb +131 -8
  43. data/spec/spec_helper.rb +1 -0
  44. data/spec/support/debug_failure_helper.rb +9 -5
  45. data/spec/support/serialization_helper.rb +21 -0
  46. data/spec/support/test_app.rb +2 -2
  47. data/spec/unit/modules/enum_spec.rb +1 -1
  48. data/spec/unit/realtime/client_spec.rb +20 -7
  49. data/spec/unit/realtime/connection_spec.rb +1 -1
  50. metadata +22 -17
  51. data/.travis.yml +0 -19
@@ -278,28 +278,14 @@ module Ably::Realtime
278
278
 
279
279
  # Return the presence messages history for the channel
280
280
  #
281
- # Once attached to a channel, you can retrieve presence message history on the channel before the
282
- # channel was attached with the option <tt>until_attach: true</tt>. This is very useful for
283
- # developers who wish to capture new presence events as well as retrieve historical presence state with
284
- # the guarantee that no presence history has been missed.
285
- #
286
281
  # @param (see Ably::Rest::Presence#history)
287
282
  # @option options (see Ably::Rest::Presence#history)
288
- # @option options [Boolean] :until_attach When true, request for history will be limited only to messages published before the associated channel was attached. The associated channel must be attached.
289
283
  #
290
284
  # @yield [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items #items}.
291
285
  #
292
286
  # @return [Ably::Util::SafeDeferrable]
293
287
  #
294
288
  def history(options = {}, &callback)
295
- if options.delete(:until_attach)
296
- unless channel.attached?
297
- error = Ably::Exceptions::InvalidRequest.new('option :until_attach is invalid as the channel is not attached')
298
- return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
299
- end
300
- options[:from_serial] = channel.attached_serial
301
- end
302
-
303
289
  async_wrap(callback) do
304
290
  rest_presence.history(options.merge(async_blocking_operations: true))
305
291
  end
@@ -40,15 +40,14 @@ module Ably
40
40
  @push = PushChannel.new(self)
41
41
  end
42
42
 
43
- # Publish one or more messages to the channel.
44
- #
45
- # @param name [String, Array<Ably::Models::Message|Hash>, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs
46
- # @param data [String, ByteArray, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument
47
- # @param attributes [Hash, nil] Optional additional message attributes such as :extras, :id, :client_id or :connection_id, applied when name attribute is nil or a string
43
+ # Publish one or more messages to the channel. Three overloaded forms
44
+ # @param name [String, Array<Ably::Models::Message|Hash>, Ably::Models::Message, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs, or a single Ably::Model::Message object
45
+ # @param data [String, ByteArray, Hash, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument, in which case an optional hash of query parameters
46
+ # @param attributes [Hash, nil] Optional additional message attributes such as :extras, :id, :client_id or :connection_id, applied when name attribute is nil or a string (Deprecated, will be removed in 2.0 in favour of constructing a Message object)
48
47
  # @return [Boolean] true if the message was published, otherwise false
49
48
  #
50
49
  # @example
51
- # # Publish a single message
50
+ # # Publish a single message with (name, data) form
52
51
  # channel.publish 'click', { x: 1, y: 2 }
53
52
  #
54
53
  # # Publish an array of message Hashes
@@ -65,17 +64,25 @@ module Ably
65
64
  # ]
66
65
  # channel.publish messages
67
66
  #
68
- def publish(name, data = nil, attributes = {})
69
- messages = if name.kind_of?(Enumerable)
70
- name
67
+ # # Publish a single Ably::Models::Message object, with a query params
68
+ # # specifying quickAck: true
69
+ # message = Ably::Models::Message(name: 'click', { x: 1, y: 2 })
70
+ # channel.publish message, quickAck: 'true'
71
+ #
72
+ def publish(first, second = nil, third = {})
73
+ messages, qs_params = if first.kind_of?(Enumerable)
74
+ # ([Message], qs_params) form
75
+ [first, second]
76
+ elsif first.kind_of?(Ably::Models::Message)
77
+ # (Message, qs_params) form
78
+ [[first], second]
71
79
  else
72
- if name.kind_of?(Ably::Models::Message)
73
- raise ArgumentError, "name argument does not support single Message objects, only arrays of Message objects"
74
- end
75
-
76
- name = ensure_utf_8(:name, name, allow_nil: true)
77
- ensure_supported_payload data
78
- [{ name: name, data: data }.merge(attributes)]
80
+ # (name, data, attributes) form
81
+ first = ensure_utf_8(:name, first, allow_nil: true)
82
+ ensure_supported_payload second
83
+ # RSL1h - attributes as an extra method parameter is extra-spec but need to
84
+ # keep it for backcompat until version 2
85
+ [[{ name: first, data: second }.merge(third)], nil]
79
86
  end
80
87
 
81
88
  payload = messages.each_with_index.map do |message, index|
@@ -103,7 +110,8 @@ module Ably
103
110
  end
104
111
  end
105
112
 
106
- response = client.post("#{base_path}/publish", payload.length == 1 ? payload.first : payload)
113
+ options = qs_params ? { qs_params: qs_params } : {}
114
+ response = client.post("#{base_path}/publish", payload.length == 1 ? payload.first : payload, options)
107
115
 
108
116
  [201, 204].include?(response.status)
109
117
  end
@@ -3,6 +3,9 @@ require 'json'
3
3
  require 'logger'
4
4
  require 'uri'
5
5
 
6
+ require 'typhoeus'
7
+ require 'typhoeus/adapters/faraday'
8
+
6
9
  require 'ably/rest/middleware/exceptions'
7
10
 
8
11
  module Ably
@@ -32,6 +35,13 @@ module Ably
32
35
 
33
36
  FALLBACK_RETRY_TIMEOUT = 10 * 60
34
37
 
38
+ # Faraday 1.0 introduced new error types, however we want to support Faraday <1 too which only used Faraday::ClientError
39
+ FARADAY_CLIENT_OR_SERVER_ERRORS = if defined?(Faraday::ParsingError)
40
+ [Faraday::ClientError, Faraday::ServerError, Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ParsingError]
41
+ else
42
+ Faraday::ClientError
43
+ end
44
+
35
45
  def_delegators :auth, :client_id, :auth_options
36
46
 
37
47
  # Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
@@ -174,16 +184,18 @@ module Ably
174
184
  @idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
175
185
 
176
186
 
177
- if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
178
- raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
187
+ if options[:fallback_hosts_use_default] && options[:fallback_hosts]
188
+ raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
179
189
  end
180
190
  @fallback_hosts = case
181
191
  when options.delete(:fallback_hosts_use_default)
182
192
  Ably::FALLBACK_HOSTS
183
193
  when options_fallback_hosts = options.delete(:fallback_hosts)
184
194
  options_fallback_hosts
185
- when environment || custom_host || options[:realtime_host] || custom_port || custom_tls_port
195
+ when custom_host || options[:realtime_host] || custom_port || custom_tls_port
186
196
  []
197
+ when environment
198
+ CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
187
199
  else
188
200
  Ably::FALLBACK_HOSTS
189
201
  end
@@ -195,6 +207,8 @@ module Ably
195
207
  @http_defaults = HTTP_DEFAULTS.dup
196
208
  options.each do |key, val|
197
209
  if http_key = key[/^http_(.+)/, 1]
210
+ # Typhoeus converts decimal durations to milliseconds, so 0.0001 timeout is treated as 0 (no timeout)
211
+ val = 0.001 if val.kind_of?(Numeric) && (val > 0) && (val < 0.001)
198
212
  @http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
199
213
  end
200
214
  end
@@ -336,14 +350,14 @@ module Ably
336
350
  #
337
351
  # @return [Ably::Models::HttpPaginatedResponse<>]
338
352
  def request(method, path, params = {}, body = nil, headers = {}, options = {})
339
- raise "Method #{method.to_s.upcase} not supported" unless [:get, :put, :post].include?(method.to_sym)
353
+ raise "Method #{method.to_s.upcase} not supported" unless %i(get put patch post delete).include?(method.to_sym)
340
354
 
341
355
  response = case method.to_sym
342
- when :get
356
+ when :get, :delete
343
357
  reauthorize_on_authorization_failure do
344
358
  send_request(method, path, params, headers: headers)
345
359
  end
346
- when :post
360
+ when :post, :patch, :put
347
361
  path_with_params = Addressable::URI.new
348
362
  path_with_params.query_values = params || {}
349
363
  query = path_with_params.query
@@ -471,11 +485,13 @@ module Ably
471
485
 
472
486
  # Allowable duration for an external auth request
473
487
  # For REST client this defaults to request_timeout
474
- # For Realtime clients this defaults to realtime_request_timeout
488
+ # For Realtime clients this defaults to 250ms less than the realtime_request_timeout
489
+ # ensuring an auth failure will be triggered before the realtime request timeout fires
490
+ # which would lead to a misleading error message (connection timeout as opposed to auth request timeout)
475
491
  # @api private
476
492
  def auth_request_timeout
477
493
  if @realtime_client
478
- @realtime_client.connection.defaults.fetch(:realtime_request_timeout)
494
+ @realtime_client.connection.defaults.fetch(:realtime_request_timeout) - 0.25
479
495
  else
480
496
  http_defaults.fetch(:request_timeout)
481
497
  end
@@ -557,12 +573,14 @@ module Ably
557
573
  request.options.context = {} if request.options.context.nil?
558
574
  request.options.context[:request_id] = request_id
559
575
  end
576
+ if options[:qs_params]
577
+ request.params.merge!(options[:qs_params])
578
+ end
560
579
  unless options[:send_auth_header] == false
561
580
  request.headers[:authorization] = auth.auth_header
562
- if options[:headers]
563
- options[:headers].map do |key, val|
564
- request.headers[key] = val
565
- end
581
+
582
+ options[:headers].to_h.merge(auth.extra_auth_headers).map do |key, val|
583
+ request.headers[key] = val
566
584
  end
567
585
  end
568
586
  end.tap do
@@ -576,7 +594,7 @@ module Ably
576
594
  end
577
595
  end
578
596
 
579
- rescue Faraday::TimeoutError, Faraday::ClientError, Ably::Exceptions::ServerError => error
597
+ rescue *([Faraday::TimeoutError, Ably::Exceptions::ServerError] + FARADAY_CLIENT_OR_SERVER_ERRORS) => error
580
598
  retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
581
599
  time_passed = Time.now - requested_at
582
600
 
@@ -596,7 +614,7 @@ module Ably
596
614
  case error
597
615
  when Faraday::TimeoutError
598
616
  raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT, error, { request_id: request_id })
599
- when Faraday::ClientError
617
+ when *FARADAY_CLIENT_OR_SERVER_ERRORS
600
618
  # request_id is also available in the request context
601
619
  raise Ably::Exceptions::ConnectionError.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error, { request_id: request_id })
602
620
  else
@@ -654,7 +672,7 @@ module Ably
654
672
  }
655
673
  end
656
674
 
657
- # Return a Faraday middleware stack to initiate the Faraday::Connection with
675
+ # Return a Faraday middleware stack to initiate the Faraday::RackBuilder with
658
676
  #
659
677
  # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
660
678
  def middleware
@@ -666,8 +684,8 @@ module Ably
666
684
 
667
685
  setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
668
686
 
669
- # Set Faraday's HTTP adapter
670
- builder.adapter :excon
687
+ # Set Faraday's HTTP adapter with support for HTTP/2
688
+ builder.adapter :typhoeus, http_version: :httpv2_0
671
689
  end
672
690
  end
673
691
 
@@ -7,7 +7,10 @@ module Ably
7
7
  class FailIfUnsupportedMimeType < Faraday::Response::Middleware
8
8
  def on_complete(env)
9
9
  unless env.response_headers['Ably-Middleware-Parsed'] == true
10
- unless (500..599).include?(env.status)
10
+ # Ignore empty body with success status code for no body response
11
+ return if env.body.to_s.empty? && env.status == 204
12
+
13
+ unless (500..599).include?(env.status)
11
14
  raise Ably::Exceptions::InvalidResponseBody,
12
15
  "Content Type #{env.response_headers['Content-Type']} is not supported by this client library"
13
16
  end
data/lib/ably/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.1.1'
2
+ VERSION = '1.1.5'
3
3
  PROTOCOL_VERSION = '1.1'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -1058,7 +1058,7 @@ describe Ably::Realtime::Auth, :event_machine do
1058
1058
 
1059
1059
  it 'disconnected includes and invalid signature message' do
1060
1060
  client.connection.once(:disconnected) do |state_change|
1061
- expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1061
+ expect(state_change.reason.message.match(/signature verification failed/i)).to_not be_nil
1062
1062
  expect(state_change.reason.code).to eql(40144)
1063
1063
  stop_reactor
1064
1064
  end
@@ -1111,7 +1111,7 @@ describe Ably::Realtime::Auth, :event_machine do
1111
1111
 
1112
1112
  it 'authentication fails and reason for disconnection is invalid signature' do
1113
1113
  client.connection.once(:disconnected) do |state_change|
1114
- expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1114
+ expect(state_change.reason.message.match(/signature verification failed/i)).to_not be_nil
1115
1115
  expect(state_change.reason.code).to eql(40144)
1116
1116
  stop_reactor
1117
1117
  end
@@ -1139,10 +1139,11 @@ describe Ably::Realtime::Auth, :event_machine do
1139
1139
  context 'when credentials are invalid' do
1140
1140
  let(:key_secret) { 'invalid' }
1141
1141
  let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body }
1142
+ let(:client_options) { { token: token, environment: environment, protocol: protocol, log_level: :none } }
1142
1143
 
1143
1144
  it 'fails with an invalid signature error' do
1144
1145
  client.connection.once(:disconnected) do |state_change|
1145
- expect(state_change.reason.message.match(/invalid signature/i)).to_not be_nil
1146
+ expect(state_change.reason.message.match(/signature verification failed/i)).to_not be_nil
1146
1147
  expect(state_change.reason.code).to eql(40144)
1147
1148
  stop_reactor
1148
1149
  end
@@ -1239,7 +1240,7 @@ describe Ably::Realtime::Auth, :event_machine do
1239
1240
  Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}&capability=#{URI.escape(basic_capability)}").body
1240
1241
  end
1241
1242
  end
1242
- let(:client_options) { default_options.merge(auth_callback: auth_callback) }
1243
+ let(:client_options) { default_options.merge(auth_callback: auth_callback, log_level: :error) }
1243
1244
 
1244
1245
  it 'client fails to publish to a channel with subscribe-only capability and publishes successfully on a channel with permissions' do
1245
1246
  client.connection.once(:connected) do
@@ -83,6 +83,16 @@ describe Ably::Realtime::Channel, :event_machine do
83
83
  end
84
84
  end
85
85
 
86
+ it 'sets attach_serial property after the attachment (#RTL15a)' do
87
+ expect(channel.properties.attach_serial).to be_nil
88
+
89
+ channel.attach
90
+ channel.on(:attached) do
91
+ expect(channel.properties.attach_serial).to_not be_nil
92
+ stop_reactor
93
+ end
94
+ end
95
+
86
96
  it 'sends an ATTACH and waits for an ATTACHED (#RTL4c)' do
87
97
  connection.once(:connected) do
88
98
  attach_count = 0
@@ -279,8 +289,7 @@ describe Ably::Realtime::Channel, :event_machine do
279
289
  key: restricted_api_key,
280
290
  log_level: :fatal,
281
291
  use_token_auth: true,
282
- # TODO: Use wildcard / default when intersection issue resolved, realtime#780
283
- default_token_params: { capability: { "canpublish:foo" => ["publish"] } }
292
+ default_token_params: { capability: { "canpublish:foo" => ["*"] } }
284
293
  )
285
294
  end
286
295
  let(:restricted_client) do
@@ -1009,9 +1018,6 @@ describe Ably::Realtime::Channel, :event_machine do
1009
1018
 
1010
1019
  it 'publishes the message without a name attribute in the payload' do
1011
1020
  published = false
1012
- channel.publish(nil, data) do
1013
- published = true
1014
- end
1015
1021
 
1016
1022
  channel.subscribe do |message|
1017
1023
  expect(message.name).to be_nil
@@ -1024,6 +1030,10 @@ describe Ably::Realtime::Channel, :event_machine do
1024
1030
  end
1025
1031
  end
1026
1032
  end
1033
+
1034
+ channel.publish(nil, data) do
1035
+ published = true
1036
+ end
1027
1037
  end
1028
1038
  end
1029
1039
 
@@ -1032,9 +1042,6 @@ describe Ably::Realtime::Channel, :event_machine do
1032
1042
 
1033
1043
  it 'publishes the message without a data attribute in the payload' do
1034
1044
  published = false
1035
- channel.publish(name, nil) do
1036
- published = true
1037
- end
1038
1045
 
1039
1046
  channel.subscribe do |message|
1040
1047
  expect(message.data).to be_nil
@@ -1047,6 +1054,10 @@ describe Ably::Realtime::Channel, :event_machine do
1047
1054
  end
1048
1055
  end
1049
1056
  end
1057
+
1058
+ channel.publish(name, nil) do
1059
+ published = true
1060
+ end
1050
1061
  end
1051
1062
  end
1052
1063
 
@@ -1997,6 +2008,8 @@ describe Ably::Realtime::Channel, :event_machine do
1997
2008
  end
1998
2009
 
1999
2010
  context '#resume (#RTL2f)' do
2011
+ let(:client_options) { default_options.merge(log_level: :fatal) }
2012
+
2000
2013
  it 'is false when a channel first attaches' do
2001
2014
  channel.attach
2002
2015
  channel.on(:attached) do |channel_state_change|
@@ -87,22 +87,6 @@ describe Ably::Realtime::Client, :event_machine do
87
87
  end
88
88
  end
89
89
  end
90
-
91
- context 'with client_id' do
92
- let(:client_options) do
93
- default_options.merge(client_id: random_str)
94
- end
95
- it 'connects using token auth' do
96
- run_reactor do
97
- connection.on(:connected) do
98
- expect(connection.state).to eq(:connected)
99
- expect(auth_params[:access_token]).to_not be_nil
100
- expect(auth_params[:key]).to be_nil
101
- stop_reactor
102
- end
103
- end
104
- end
105
- end
106
90
  end
107
91
  end
108
92
 
@@ -249,6 +233,8 @@ describe Ably::Realtime::Client, :event_machine do
249
233
 
250
234
  context '#request (#RSC19*)' do
251
235
  let(:client_options) { default_options.merge(key: api_key) }
236
+ let(:device_id) { random_str }
237
+ let(:endpoint) { subject.rest_client.endpoint }
252
238
 
253
239
  context 'get' do
254
240
  it 'returns an HttpPaginatedResponse object' do
@@ -297,6 +283,76 @@ describe Ably::Realtime::Client, :event_machine do
297
283
  end
298
284
  end
299
285
  end
286
+
287
+
288
+ context 'post', :webmock do
289
+ before do
290
+ stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
291
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
292
+ end
293
+
294
+ it 'supports post' do
295
+ subject.request(:delete, "push/deviceRegistrations/#{device_id}/resetUpdateToken").callback do |response|
296
+ expect(response).to be_success
297
+ stop_reactor
298
+ end
299
+ end
300
+ end
301
+
302
+ context 'delete', :webmock do
303
+ before do
304
+ stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}").
305
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
306
+ end
307
+
308
+ it 'supports delete' do
309
+ subject.request(:delete, "/push/channelSubscriptions", { deviceId: device_id}).callback do |response|
310
+ expect(response).to be_success
311
+ stop_reactor
312
+ end
313
+ end
314
+ end
315
+
316
+ context 'patch', :webmock do
317
+ let(:body_params) { { 'metadata' => { 'key' => 'value' } } }
318
+
319
+ before do
320
+ stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}")
321
+ .with(body: serialize_body(body_params, protocol))
322
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
323
+ end
324
+
325
+ it 'supports patch' do
326
+ subject.request(:patch, "/push/deviceRegistrations/#{device_id}", {}, body_params).callback do |response|
327
+ expect(response).to be_success
328
+ stop_reactor
329
+ end
330
+ end
331
+ end
332
+
333
+ context 'put', :webmock do
334
+ let(:body_params) do
335
+ {
336
+ 'id' => random_str,
337
+ 'platform' => 'ios',
338
+ 'formFactor' => 'phone',
339
+ 'metadata' => { 'key' => 'value' }
340
+ }
341
+ end
342
+
343
+ before do
344
+ stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}")
345
+ .with(body: serialize_body(body_params, protocol))
346
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
347
+ end
348
+
349
+ it 'supports put' do
350
+ subject.request(:put, "/push/deviceRegistrations/#{device_id}", {}, body_params).callback do |response|
351
+ expect(response).to be_success
352
+ stop_reactor
353
+ end
354
+ end
355
+ end
300
356
  end
301
357
 
302
358
  context '#publish (#TBC)' do
@@ -315,8 +371,9 @@ describe Ably::Realtime::Client, :event_machine do
315
371
  expect(msg.data).to eql(data)
316
372
  stop_reactor
317
373
  end
374
+
375
+ subject.publish channel_name, event_name, data
318
376
  end
319
- subject.publish channel_name, event_name, data
320
377
  end
321
378
 
322
379
  specify 'publishing does not result in a channel being created' do
@@ -341,8 +398,9 @@ describe Ably::Realtime::Client, :event_machine do
341
398
  expect(msg.extras).to eql(extras)
342
399
  stop_reactor
343
400
  end
401
+
402
+ subject.publish channel_name, event_name, {}, extras: extras
344
403
  end
345
- subject.publish channel_name, event_name, {}, extras: extras
346
404
  end
347
405
  end
348
406
 
@@ -353,8 +411,9 @@ describe Ably::Realtime::Client, :event_machine do
353
411
  expect(msg.data).to eql(data)
354
412
  stop_reactor
355
413
  end
414
+
415
+ subject.publish channel_name, [message]
356
416
  end
357
- subject.publish channel_name, [message]
358
417
  end
359
418
 
360
419
  specify 'publishing supports an array of Hash objects' do
@@ -364,8 +423,9 @@ describe Ably::Realtime::Client, :event_machine do
364
423
  expect(msg.data).to eql(data)
365
424
  stop_reactor
366
425
  end
426
+
427
+ subject.publish channel_name, [name: event_name, data: data]
367
428
  end
368
- subject.publish channel_name, [name: event_name, data: data]
369
429
  end
370
430
 
371
431
  specify 'publishing on a closed connection fails' do