ably 1.1.0 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +27 -0
  3. data/CHANGELOG.md +68 -2
  4. data/COPYRIGHT +1 -0
  5. data/LICENSE +172 -11
  6. data/MAINTAINERS.md +1 -0
  7. data/README.md +3 -7
  8. data/SPEC.md +944 -914
  9. data/ably.gemspec +7 -7
  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/state_machine.rb +1 -1
  14. data/lib/ably/realtime/channel.rb +7 -11
  15. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  16. data/lib/ably/realtime/channel/channel_properties.rb +24 -0
  17. data/lib/ably/realtime/client.rb +12 -3
  18. data/lib/ably/realtime/connection.rb +31 -19
  19. data/lib/ably/realtime/connection/connection_manager.rb +19 -3
  20. data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  21. data/lib/ably/realtime/presence.rb +0 -14
  22. data/lib/ably/rest/channel.rb +25 -17
  23. data/lib/ably/rest/client.rb +22 -11
  24. data/lib/ably/version.rb +1 -1
  25. data/spec/acceptance/realtime/auth_spec.rb +16 -13
  26. data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  27. data/spec/acceptance/realtime/channel_spec.rb +21 -8
  28. data/spec/acceptance/realtime/client_spec.rb +80 -20
  29. data/spec/acceptance/realtime/connection_failures_spec.rb +71 -5
  30. data/spec/acceptance/realtime/connection_spec.rb +153 -26
  31. data/spec/acceptance/realtime/message_spec.rb +17 -17
  32. data/spec/acceptance/realtime/presence_history_spec.rb +0 -58
  33. data/spec/acceptance/realtime/presence_spec.rb +250 -162
  34. data/spec/acceptance/realtime/push_admin_spec.rb +49 -25
  35. data/spec/acceptance/rest/auth_spec.rb +6 -75
  36. data/spec/acceptance/rest/channel_spec.rb +79 -4
  37. data/spec/acceptance/rest/channels_spec.rb +6 -0
  38. data/spec/acceptance/rest/client_spec.rb +72 -12
  39. data/spec/acceptance/rest/message_spec.rb +8 -27
  40. data/spec/acceptance/rest/push_admin_spec.rb +67 -27
  41. data/spec/shared/client_initializer_behaviour.rb +0 -8
  42. data/spec/spec_helper.rb +2 -1
  43. data/spec/support/debug_failure_helper.rb +9 -5
  44. data/spec/support/serialization_helper.rb +21 -0
  45. data/spec/support/test_app.rb +2 -2
  46. data/spec/unit/modules/enum_spec.rb +1 -1
  47. data/spec/unit/realtime/client_spec.rb +20 -7
  48. data/spec/unit/realtime/connection_spec.rb +1 -1
  49. metadata +40 -29
  50. data/.travis.yml +0 -16
@@ -63,20 +63,22 @@ describe Ably::Realtime::Push::Admin, :event_machine do
63
63
  end
64
64
 
65
65
  context 'invalid recipient' do
66
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } }
67
+
66
68
  it 'raises an error after receiving a 40x realtime response' do
67
- skip 'validation on raw push is not enabled in realtime'
68
69
  subject.publish({ invalid_recipient_details: 'foo.bar' }, basic_notification_payload).errback do |error|
69
- expect(error.message).to match(/Invalid recipient/)
70
+ expect(error.message).to match(/recipient must contain/)
70
71
  stop_reactor
71
72
  end
72
73
  end
73
74
  end
74
75
 
75
76
  context 'invalid push data' do
77
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } }
78
+
76
79
  it 'raises an error after receiving a 40x realtime response' do
77
- skip 'validation on raw push is not enabled in realtime'
78
80
  subject.publish(basic_recipient, { invalid_property_only: true }).errback do |error|
79
- expect(error.message).to match(/Invalid push notification data/)
81
+ expect(error.message).to match(/Unexpected field/)
80
82
  stop_reactor
81
83
  end
82
84
  end
@@ -99,31 +101,15 @@ describe Ably::Realtime::Push::Admin, :event_machine do
99
101
  end
100
102
  end
101
103
 
102
- def request_body(request, protocol)
103
- if protocol == :msgpack
104
- MessagePack.unpack(request.body)
105
- else
106
- JSON.parse(request.body)
107
- end
108
- end
109
-
110
- def serialize(object, protocol)
111
- if protocol == :msgpack
112
- MessagePack.pack(object)
113
- else
114
- JSON.dump(object)
115
- end
116
- end
117
-
118
104
  let!(:publish_stub) do
119
105
  stub_request(:post, "#{client.rest_client.endpoint}/push/publish").
120
106
  with do |request|
121
- expect(request_body(request, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
122
- expect(request_body(request, protocol)['recipient']).to_not have_key('camel_case')
107
+ expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
108
+ expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case')
123
109
  true
124
110
  end.to_return(
125
111
  :status => 201,
126
- :body => serialize({}, protocol),
112
+ :body => serialize_body({}, protocol),
127
113
  :headers => { 'Content-Type' => content_type }
128
114
  )
129
115
  end
@@ -215,6 +201,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
215
201
  let(:client_id) { random_str }
216
202
  let(:fixture_count) { 6 }
217
203
 
204
+ before(:all) do
205
+ # As push tests often use the global scope (devices),
206
+ # we need to ensure tests cannot conflict
207
+ reload_test_app
208
+ end
209
+
218
210
  before do
219
211
  fixture_count.times.map do |index|
220
212
  Thread.new do # Parallelise the setup
@@ -272,6 +264,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
272
264
  let(:fixture_count) { 2 }
273
265
  let(:client_id) { random_str }
274
266
 
267
+ before(:all) do
268
+ # As push tests often use the global scope (devices),
269
+ # we need to ensure tests cannot conflict
270
+ reload_test_app
271
+ end
272
+
275
273
  before do
276
274
  fixture_count.times.map do |index|
277
275
  Thread.new do # Parallelise the setup
@@ -351,6 +349,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
351
349
  }
352
350
  end
353
351
 
352
+ before(:all) do
353
+ # As push tests often use the global scope (devices),
354
+ # we need to ensure tests cannot conflict
355
+ reload_test_app
356
+ end
357
+
354
358
  after do
355
359
  rest_device_registrations.remove_where client_id: client_id
356
360
  end
@@ -386,6 +390,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
386
390
  let(:device_id) { random_str }
387
391
  let(:client_id) { random_str }
388
392
 
393
+ before(:all) do
394
+ # As push tests often use the global scope (devices),
395
+ # we need to ensure tests cannot conflict
396
+ reload_test_app
397
+ end
398
+
389
399
  before do
390
400
  rest_device_registrations.save({
391
401
  id: "device-#{client_id}-0",
@@ -419,6 +429,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
419
429
  let(:device_id) { random_str }
420
430
  let(:client_id) { random_str }
421
431
 
432
+ before(:all) do
433
+ # As push tests often use the global scope (devices),
434
+ # we need to ensure tests cannot conflict
435
+ reload_test_app
436
+ end
437
+
422
438
  before do
423
439
  rest_device_registrations.save({
424
440
  id: "device-#{client_id}-0",
@@ -479,6 +495,12 @@ describe Ably::Realtime::Push::Admin, :event_machine do
479
495
  client.push.admin.channel_subscriptions
480
496
  }
481
497
 
498
+ before(:all) do
499
+ # As push tests often use the global scope (devices),
500
+ # we need to ensure tests cannot conflict
501
+ reload_test_app
502
+ end
503
+
482
504
  # Set up 2 devices with the same client_id
483
505
  # and two device with the unique device_id and no client_id
484
506
  before do
@@ -541,8 +563,10 @@ describe Ably::Realtime::Push::Admin, :event_machine do
541
563
  describe '#list_channels' do
542
564
  let(:fixture_count) { 6 }
543
565
 
544
- before(:context) do
545
- reload_test_app # TODO: Review if necessary later, currently other tests may affect list_channels
566
+ before(:all) do
567
+ # As push tests often use the global scope (devices),
568
+ # we need to ensure tests cannot conflict
569
+ reload_test_app
546
570
  end
547
571
 
548
572
  before do
@@ -41,22 +41,10 @@ describe Ably::Auth do
41
41
  end
42
42
 
43
43
  def request_body_includes(request, protocol, key, val)
44
- body = if protocol == :msgpack
45
- MessagePack.unpack(request.body)
46
- else
47
- JSON.parse(request.body)
48
- end
44
+ body = deserialize_body(request.body, protocol)
49
45
  body[convert_to_mixed_case(key)].to_s == val.to_s
50
46
  end
51
47
 
52
- def serialize(object, protocol)
53
- if protocol == :msgpack
54
- MessagePack.pack(object)
55
- else
56
- JSON.dump(object)
57
- end
58
- end
59
-
60
48
  it 'has immutable options' do
61
49
  expect { auth.options['key_name'] = 'new_name' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
62
50
  end
@@ -74,7 +62,7 @@ describe Ably::Auth do
74
62
 
75
63
  it 'creates a TokenRequest automatically and sends it to Ably to obtain a token', webmock: true do
76
64
  token_request_stub = stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
77
- to_return(status: 201, body: serialize({}, protocol), headers: { 'Content-Type' => content_type })
65
+ to_return(status: 201, body: serialize_body({}, protocol), headers: { 'Content-Type' => content_type })
78
66
  expect(auth).to receive(:create_token_request).and_call_original
79
67
  auth.request_token
80
68
 
@@ -107,7 +95,7 @@ describe Ably::Auth do
107
95
  request_body_includes(request, protocol, token_param, coerce_if_time_value(token_param, random, multiply: 1000))
108
96
  end.to_return(
109
97
  :status => 201,
110
- :body => serialize(token_response, protocol),
98
+ :body => serialize_body(token_response, protocol),
111
99
  :headers => { 'Content-Type' => content_type }
112
100
  )
113
101
  end
@@ -138,7 +126,7 @@ describe Ably::Auth do
138
126
  request_body_includes(request, protocol, 'mac', mac)
139
127
  end.to_return(
140
128
  :status => 201,
141
- :body => serialize(token_response, protocol),
129
+ :body => serialize_body(token_response, protocol),
142
130
  :headers => { 'Content-Type' => content_type })
143
131
  end
144
132
 
@@ -168,7 +156,7 @@ describe Ably::Auth do
168
156
  request_body_includes(request, protocol, 'mac', mac)
169
157
  end.to_return(
170
158
  :status => 201,
171
- :body => serialize(token_response, protocol),
159
+ :body => serialize_body(token_response, protocol),
172
160
  :headers => { 'Content-Type' => content_type })
173
161
  end
174
162
 
@@ -310,7 +298,7 @@ describe Ably::Auth do
310
298
  request_body_includes(request, protocol, 'key_name', key_name)
311
299
  end.to_return(
312
300
  :status => 201,
313
- :body => serialize(token_response, protocol),
301
+ :body => serialize_body(token_response, protocol),
314
302
  :headers => { 'Content-Type' => content_type }
315
303
  )
316
304
  end
@@ -1129,63 +1117,6 @@ describe Ably::Auth do
1129
1117
  end
1130
1118
  end
1131
1119
 
1132
- context 'when implicit as a result of using :client_id' do
1133
- let(:client_id) { '999' }
1134
- let(:client) do
1135
- Ably::Rest::Client.new(key: api_key, client_id: client_id, environment: environment, protocol: protocol)
1136
- end
1137
- let(:token) { 'unique-token' }
1138
- let(:token_response) do
1139
- {
1140
- token: token
1141
- }.to_json
1142
- end
1143
-
1144
- context 'and requests to the Ably server are mocked', :webmock do
1145
- let!(:request_token_stub) do
1146
- stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
1147
- to_return(:status => 201, :body => token_response, :headers => { 'Content-Type' => 'application/json' })
1148
- end
1149
- let!(:publish_message_stub) do
1150
- stub_request(:post, "#{client.endpoint}/channels/foo/publish").
1151
- with(headers: { 'Authorization' => "Bearer #{encode64(token)}" }).
1152
- to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
1153
- end
1154
-
1155
- it 'will send a token request to the server' do
1156
- client.channel('foo').publish('event', 'data')
1157
- expect(request_token_stub).to have_been_requested
1158
- end
1159
- end
1160
-
1161
- describe 'a token is created' do
1162
- let(:token) { client.auth.current_token_details }
1163
-
1164
- it 'before a request is made' do
1165
- expect(token).to be_nil
1166
- end
1167
-
1168
- it 'when a message is published' do
1169
- expect(client.channel('foo').publish('event', 'data')).to be_truthy
1170
- end
1171
-
1172
- it 'with capability and TTL defaults (#TK2a, #TK2b)' do
1173
- client.channel('foo').publish('event', 'data')
1174
-
1175
- expect(token).to be_a(Ably::Models::TokenDetails)
1176
- capability_with_str_key = { "*" => ["*"] } # Ably default is all capabilities
1177
- capability = Hash[capability_with_str_key.keys.map(&:to_s).zip(capability_with_str_key.values)]
1178
- expect(token.capability).to eq(capability)
1179
- expect(token.expires.to_i).to be_within(2).of(Time.now.to_i + 60 * 60) # Ably default is 1hr
1180
- expect(token.client_id).to eq(client_id)
1181
- end
1182
-
1183
- specify '#client_id contains the client_id' do
1184
- expect(client.auth.client_id).to eql(client_id)
1185
- end
1186
- end
1187
- end
1188
-
1189
1120
  context 'when token expires' do
1190
1121
  before do
1191
1122
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
@@ -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
@@ -12,7 +12,7 @@ describe Ably::Rest::Client do
12
12
  http_defaults = Ably::Rest::Client::HTTP_DEFAULTS
13
13
 
14
14
  def encode64(text)
15
- Base64.encode64(text).gsub("\n", '')
15
+ Base64.urlsafe_encode64(text)
16
16
  end
17
17
 
18
18
  context '#initialize' do
@@ -56,14 +56,6 @@ describe Ably::Rest::Client do
56
56
  end
57
57
  end
58
58
 
59
- context 'with a :client_id configured' do
60
- let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: random_str)) }
61
-
62
- it 'uses token authentication' do
63
- expect(client.auth).to be_using_token_auth
64
- end
65
- end
66
-
67
59
  context 'with a non string :client_id' do
68
60
  let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: 1)) }
69
61
 
@@ -144,11 +136,12 @@ describe Ably::Rest::Client do
144
136
  let(:history_querystring) { history_params.map { |k, v| "#{k}=#{v}" }.join("&") }
145
137
 
146
138
  context 'with basic auth', webmock: true do
147
- let(:client_options) { default_options.merge(key: api_key) }
139
+ let(:client_options) { default_options.merge(key: api_key, client_id: client_id) }
148
140
 
149
141
  let!(:get_message_history_stub) do
150
- stub_request(:get, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}").
151
- to_return(body: [], headers: { 'Content-Type' => 'application/json' })
142
+ stub_request(:get, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}")
143
+ .with(headers: { 'X-Ably-ClientId' => encode64(client_id) })
144
+ .to_return(body: [], headers: { 'Content-Type' => 'application/json' })
152
145
  end
153
146
 
154
147
  it 'sends the API key in authentication part of the secure URL (the Authorization: Basic header is not used with the Faraday HTTP library by default)' do
@@ -1091,6 +1084,8 @@ describe Ably::Rest::Client do
1091
1084
 
1092
1085
  context '#request (#RSC19*)' do
1093
1086
  let(:client_options) { default_options.merge(key: api_key) }
1087
+ let(:device_id) { random_str }
1088
+ let(:endpoint) { client.endpoint }
1094
1089
 
1095
1090
  context 'get' do
1096
1091
  it 'returns an HttpPaginatedResponse object' do
@@ -1130,6 +1125,71 @@ describe Ably::Rest::Client do
1130
1125
  end
1131
1126
  end
1132
1127
  end
1128
+
1129
+ context 'post', :webmock do
1130
+ before do
1131
+ stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
1132
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1133
+ end
1134
+
1135
+ it 'supports post' do
1136
+ response = client.request(:delete, "push/deviceRegistrations/#{device_id}/resetUpdateToken")
1137
+
1138
+ expect(response).to be_success
1139
+ end
1140
+ end
1141
+
1142
+ context 'delete', :webmock do
1143
+ before do
1144
+ stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}").
1145
+ to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1146
+ end
1147
+
1148
+ it 'supports delete' do
1149
+ response = client.request(:delete, "/push/channelSubscriptions", { deviceId: device_id})
1150
+
1151
+ expect(response).to be_success
1152
+ end
1153
+ end
1154
+
1155
+ context 'patch', :webmock do
1156
+ let(:body_params) { { 'metadata' => { 'key' => 'value' } } }
1157
+
1158
+ before do
1159
+ stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}")
1160
+ .with(body: serialize_body(body_params, protocol))
1161
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1162
+ end
1163
+
1164
+ it 'supports patch' do
1165
+ response = client.request(:patch, "/push/deviceRegistrations/#{device_id}", {}, body_params)
1166
+
1167
+ expect(response).to be_success
1168
+ end
1169
+ end
1170
+
1171
+ context 'put', :webmock do
1172
+ let(:body_params) do
1173
+ {
1174
+ 'id' => random_str,
1175
+ 'platform' => 'ios',
1176
+ 'formFactor' => 'phone',
1177
+ 'metadata' => { 'key' => 'value' }
1178
+ }
1179
+ end
1180
+
1181
+ before do
1182
+ stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}")
1183
+ .with(body: serialize_body(body_params, protocol))
1184
+ .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
1185
+ end
1186
+
1187
+ it 'supports put' do
1188
+ response = client.request(:put, "/push/deviceRegistrations/#{device_id}", {}, body_params)
1189
+
1190
+ expect(response).to be_success
1191
+ end
1192
+ end
1133
1193
  end
1134
1194
 
1135
1195
  context 'request_id generation' do