ably-rest 1.0.6 → 1.1.4.rc

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/README.md +23 -15
  4. data/ably-rest.gemspec +6 -6
  5. data/lib/submodules/ably-ruby/.editorconfig +14 -0
  6. data/lib/submodules/ably-ruby/.travis.yml +10 -8
  7. data/lib/submodules/ably-ruby/CHANGELOG.md +75 -3
  8. data/lib/submodules/ably-ruby/LICENSE +1 -3
  9. data/lib/submodules/ably-ruby/README.md +12 -7
  10. data/lib/submodules/ably-ruby/Rakefile +32 -0
  11. data/lib/submodules/ably-ruby/SPEC.md +1277 -835
  12. data/lib/submodules/ably-ruby/ably.gemspec +15 -10
  13. data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
  14. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
  15. data/lib/submodules/ably-ruby/lib/ably/logger.rb +7 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
  17. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
  18. data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
  19. data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
  20. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
  21. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  22. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
  23. data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
  24. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
  25. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
  26. data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
  27. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
  28. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
  29. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
  35. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
  36. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
  37. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  38. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  39. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +34 -20
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  42. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
  43. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
  44. data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
  45. data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
  46. data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  47. data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
  48. data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
  49. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +53 -17
  50. data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
  51. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +162 -35
  52. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  53. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  54. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
  55. data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
  56. data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
  57. data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  58. data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
  59. data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
  60. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +245 -17
  61. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  62. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +177 -59
  63. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +153 -0
  64. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
  65. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +129 -18
  66. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +36 -34
  67. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +201 -167
  68. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  69. data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
  70. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
  71. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
  72. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
  73. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
  74. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
  75. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
  76. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
  77. data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
  78. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
  79. data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
  80. data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -1
  81. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
  82. data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
  83. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
  84. data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
  85. data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
  86. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
  87. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
  88. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  90. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
  91. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
  92. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +1 -1
  93. data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
  94. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
  95. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
  96. data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
  97. metadata +46 -21
@@ -0,0 +1,736 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ # These tests are a subset of Ably::Rest::Push::Admin in async EM style
5
+ # The more robust complete test suite is in rest/push_admin_spec.rb
6
+ describe Ably::Realtime::Push::Admin, :event_machine do
7
+ include Ably::Modules::Conversions
8
+
9
+ vary_by_protocol do
10
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
11
+ let(:client_options) { default_options }
12
+ let(:client) do
13
+ Ably::Realtime::Client.new(client_options)
14
+ end
15
+
16
+ let(:basic_notification_payload) do
17
+ {
18
+ notification: {
19
+ title: 'Test message',
20
+ body: 'Test message body'
21
+ }
22
+ }
23
+ end
24
+
25
+ let(:basic_recipient) do
26
+ {
27
+ transport_type: 'apns',
28
+ deviceToken: 'foo.bar'
29
+ }
30
+ end
31
+
32
+ describe '#publish' do
33
+ subject { client.push.admin }
34
+
35
+ it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
36
+ publish_deferrable = subject.publish(basic_recipient, basic_notification_payload)
37
+ expect(publish_deferrable).to be_a(Ably::Util::SafeDeferrable)
38
+ publish_deferrable.callback do
39
+ stop_reactor
40
+ end
41
+ end
42
+
43
+ context 'invalid arguments' do
44
+ it 'raises an exception with a nil recipient' do
45
+ expect { subject.publish(nil, {}) }.to raise_error ArgumentError, /Expecting a Hash/
46
+ stop_reactor
47
+ end
48
+
49
+ it 'raises an exception with a empty recipient' do
50
+ expect { subject.publish({}, {}) }.to raise_error ArgumentError, /empty/
51
+ stop_reactor
52
+ end
53
+
54
+ it 'raises an exception with a nil recipient' do
55
+ expect { subject.publish(basic_recipient, nil) }.to raise_error ArgumentError, /Expecting a Hash/
56
+ stop_reactor
57
+ end
58
+
59
+ it 'raises an exception with a empty recipient' do
60
+ expect { subject.publish(basic_recipient, {}) }.to raise_error ArgumentError, /empty/
61
+ stop_reactor
62
+ end
63
+ end
64
+
65
+ context 'invalid recipient' do
66
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } }
67
+
68
+ it 'raises an error after receiving a 40x realtime response' do
69
+ subject.publish({ invalid_recipient_details: 'foo.bar' }, basic_notification_payload).errback do |error|
70
+ expect(error.message).to match(/recipient must contain/)
71
+ stop_reactor
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'invalid push data' do
77
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } }
78
+
79
+ it 'raises an error after receiving a 40x realtime response' do
80
+ subject.publish(basic_recipient, { invalid_property_only: true }).errback do |error|
81
+ expect(error.message).to match(/Unexpected field/)
82
+ stop_reactor
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'recipient variable case', webmock: true do
88
+ let(:recipient_payload) do
89
+ {
90
+ camel_case: {
91
+ second_level_camel_case: 'val'
92
+ }
93
+ }
94
+ end
95
+
96
+ let(:content_type) do
97
+ if protocol == :msgpack
98
+ 'application/x-msgpack'
99
+ else
100
+ 'application/json'
101
+ end
102
+ end
103
+
104
+ def request_body(request, protocol)
105
+ if protocol == :msgpack
106
+ MessagePack.unpack(request.body)
107
+ else
108
+ JSON.parse(request.body)
109
+ end
110
+ end
111
+
112
+ def serialize(object, protocol)
113
+ if protocol == :msgpack
114
+ MessagePack.pack(object)
115
+ else
116
+ JSON.dump(object)
117
+ end
118
+ end
119
+
120
+ let!(:publish_stub) do
121
+ stub_request(:post, "#{client.rest_client.endpoint}/push/publish").
122
+ with do |request|
123
+ expect(request_body(request, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
124
+ expect(request_body(request, protocol)['recipient']).to_not have_key('camel_case')
125
+ true
126
+ end.to_return(
127
+ :status => 201,
128
+ :body => serialize({}, protocol),
129
+ :headers => { 'Content-Type' => content_type }
130
+ )
131
+ end
132
+
133
+ it 'is converted to snakeCase' do
134
+ subject.publish(recipient_payload, basic_notification_payload) do
135
+ expect(publish_stub).to have_been_requested
136
+ stop_reactor
137
+ end
138
+ end
139
+ end
140
+
141
+ it 'accepts valid push data and recipient' do
142
+ subject.publish(basic_recipient, basic_notification_payload) do
143
+ stop_reactor
144
+ end
145
+ end
146
+
147
+ context 'using test environment channel recipient (#RSH1a)' do
148
+ let(:channel) { random_str }
149
+ let(:recipient) do
150
+ {
151
+ 'transportType' => 'ablyChannel',
152
+ 'channel' => channel,
153
+ 'ablyKey' => api_key,
154
+ 'ablyUrl' => client.rest_client.endpoint.to_s
155
+ }
156
+ end
157
+ let(:notification_payload) do
158
+ {
159
+ notification: {
160
+ title: random_str,
161
+ },
162
+ data: {
163
+ foo: random_str
164
+ }
165
+ }
166
+ end
167
+ let(:push_channel) do
168
+ client.channels.get(channel)
169
+ end
170
+
171
+ it 'triggers a push notification' do
172
+ push_channel.attach do
173
+ push_channel.subscribe do |message|
174
+ expect(message.name).to eql('__ably_push__')
175
+ expect(JSON.parse(message.data)['data']).to eql(JSON.parse(notification_payload[:data].to_json))
176
+ stop_reactor
177
+ end
178
+ subject.publish recipient, notification_payload
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#device_registrations' do
185
+ subject { client.push.admin.device_registrations }
186
+ let(:rest_device_registrations) {
187
+ client.rest_client.push.admin.device_registrations
188
+ }
189
+
190
+ context 'without permissions' do
191
+ let(:client_options) do
192
+ default_options.merge(
193
+ use_token_auth: true,
194
+ default_token_params: { capability: { :foo => ['subscribe'] } },
195
+ log_level: :fatal,
196
+ )
197
+ end
198
+
199
+ it 'raises a permissions not authorized exception' do
200
+ subject.get('does-not-exist').errback do |err|
201
+ expect(err).to be_a(Ably::Exceptions::UnauthorizedRequest)
202
+ subject.list.errback do |err|
203
+ expect(err).to be_a(Ably::Exceptions::UnauthorizedRequest)
204
+ subject.remove('does-not-exist').errback do |err|
205
+ expect(err).to be_a(Ably::Exceptions::UnauthorizedRequest)
206
+ subject.remove_where(device_id: 'does-not-exist').errback do |err|
207
+ expect(err).to be_a(Ably::Exceptions::UnauthorizedRequest)
208
+ stop_reactor
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ describe '#list' do
217
+ let(:client_id) { random_str }
218
+ let(:fixture_count) { 6 }
219
+
220
+ before(:all) do
221
+ # As push tests often use the global scope (devices),
222
+ # we need to ensure tests cannot conflict
223
+ reload_test_app
224
+ end
225
+
226
+ before do
227
+ fixture_count.times.map do |index|
228
+ Thread.new do # Parallelise the setup
229
+ rest_device_registrations.save({
230
+ id: "device-#{client_id}-#{index}",
231
+ platform: 'ios',
232
+ form_factor: 'phone',
233
+ client_id: client_id,
234
+ push: {
235
+ recipient: {
236
+ transport_type: 'gcm',
237
+ registration_token: 'secret_token',
238
+ }
239
+ }
240
+ })
241
+ end
242
+ end.each(&:join) # Wait for all threads to complete
243
+ end
244
+
245
+ after do
246
+ rest_device_registrations.remove_where client_id: client_id
247
+ end
248
+
249
+ it 'returns a PaginatedResult object containing DeviceDetails objects' do
250
+ subject.list do |page|
251
+ expect(page).to be_a(Ably::Models::PaginatedResult)
252
+ expect(page.items.first).to be_a(Ably::Models::DeviceDetails)
253
+ stop_reactor
254
+ end
255
+ end
256
+
257
+ it 'supports paging' do
258
+ subject.list(limit: 3, client_id: client_id) do |page|
259
+ expect(page).to be_a(Ably::Models::PaginatedResult)
260
+
261
+ expect(page.items.count).to eql(3)
262
+ page.next do |page|
263
+ expect(page.items.count).to eql(3)
264
+ page.next do |page|
265
+ expect(page.items.count).to eql(0)
266
+ expect(page).to be_last
267
+ stop_reactor
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ it 'raises an exception if params are invalid' do
274
+ expect { subject.list("invalid_arg") }.to raise_error(ArgumentError)
275
+ stop_reactor
276
+ end
277
+ end
278
+
279
+ describe '#get' do
280
+ let(:fixture_count) { 2 }
281
+ let(:client_id) { random_str }
282
+
283
+ before(:all) do
284
+ # As push tests often use the global scope (devices),
285
+ # we need to ensure tests cannot conflict
286
+ reload_test_app
287
+ end
288
+
289
+ before do
290
+ fixture_count.times.map do |index|
291
+ Thread.new do # Parallelise the setup
292
+ rest_device_registrations.save({
293
+ id: "device-#{client_id}-#{index}",
294
+ platform: 'ios',
295
+ form_factor: 'phone',
296
+ client_id: client_id,
297
+ push: {
298
+ recipient: {
299
+ transport_type: 'gcm',
300
+ registration_token: 'secret_token',
301
+ }
302
+ }
303
+ })
304
+ end
305
+ end.each(&:join) # Wait for all threads to complete
306
+ end
307
+
308
+ after do
309
+ rest_device_registrations.remove_where client_id: client_id
310
+ end
311
+
312
+ it 'returns a DeviceDetails object if a device ID string is provided' do
313
+ subject.get("device-#{client_id}-0").callback do |device|
314
+ expect(device).to be_a(Ably::Models::DeviceDetails)
315
+ expect(device.platform).to eql('ios')
316
+ expect(device.client_id).to eql(client_id)
317
+ expect(device.push.recipient.fetch(:transport_type)).to eql('gcm')
318
+ stop_reactor
319
+ end
320
+ end
321
+
322
+ context 'with a failed request' do
323
+ let(:client_options) do
324
+ default_options.merge(
325
+ log_level: :fatal,
326
+ )
327
+ end
328
+
329
+ it 'raises a ResourceMissing exception if device ID does not exist' do
330
+ subject.get("device-does-not-exist").errback do |err|
331
+ expect(err).to be_a(Ably::Exceptions::ResourceMissing)
332
+ stop_reactor
333
+ end
334
+ end
335
+ end
336
+ end
337
+
338
+ describe '#save' do
339
+ let(:device_id) { random_str }
340
+ let(:client_id) { random_str }
341
+ let(:transport_token) { random_str }
342
+
343
+ let(:device_details) do
344
+ {
345
+ id: device_id,
346
+ platform: 'android',
347
+ form_factor: 'phone',
348
+ client_id: client_id,
349
+ metadata: {
350
+ foo: 'bar',
351
+ deep: {
352
+ val: true
353
+ }
354
+ },
355
+ push: {
356
+ recipient: {
357
+ transport_type: 'apns',
358
+ device_token: transport_token,
359
+ foo_bar: 'string',
360
+ },
361
+ error_reason: {
362
+ message: "this will be ignored"
363
+ },
364
+ }
365
+ }
366
+ end
367
+
368
+ before(:all) do
369
+ # As push tests often use the global scope (devices),
370
+ # we need to ensure tests cannot conflict
371
+ reload_test_app
372
+ end
373
+
374
+ after do
375
+ rest_device_registrations.remove_where client_id: client_id
376
+ end
377
+
378
+ it 'saves the new DeviceDetails Hash object' do
379
+ subject.save(device_details) do
380
+ subject.get(device_details.fetch(:id)) do |device_retrieved|
381
+ expect(device_retrieved).to be_a(Ably::Models::DeviceDetails)
382
+ expect(device_retrieved.id).to eql(device_id)
383
+ expect(device_retrieved.platform).to eql('android')
384
+ stop_reactor
385
+ end
386
+ end
387
+ end
388
+
389
+ context 'with a failed request' do
390
+ let(:client_options) do
391
+ default_options.merge(
392
+ log_level: :fatal,
393
+ )
394
+ end
395
+
396
+ it 'fails if data is invalid' do
397
+ subject.save(id: random_str, foo: 'bar').errback do |err|
398
+ expect(err).to be_a(Ably::Exceptions::InvalidRequest)
399
+ stop_reactor
400
+ end
401
+ end
402
+ end
403
+ end
404
+
405
+ describe '#remove_where' do
406
+ let(:device_id) { random_str }
407
+ let(:client_id) { random_str }
408
+
409
+ before(:all) do
410
+ # As push tests often use the global scope (devices),
411
+ # we need to ensure tests cannot conflict
412
+ reload_test_app
413
+ end
414
+
415
+ before do
416
+ rest_device_registrations.save({
417
+ id: "device-#{client_id}-0",
418
+ platform: 'ios',
419
+ form_factor: 'phone',
420
+ client_id: client_id,
421
+ push: {
422
+ recipient: {
423
+ transport_type: 'gcm',
424
+ registrationToken: 'secret_token',
425
+ }
426
+ }
427
+ })
428
+ end
429
+
430
+ after do
431
+ rest_device_registrations.remove_where client_id: client_id
432
+ end
433
+
434
+ it 'removes all matching device registrations by client_id' do
435
+ subject.remove_where(client_id: client_id, full_wait: true) do
436
+ subject.list do |page|
437
+ expect(page.items.count).to eql(0)
438
+ stop_reactor
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ describe '#remove' do
445
+ let(:device_id) { random_str }
446
+ let(:client_id) { random_str }
447
+
448
+ before(:all) do
449
+ # As push tests often use the global scope (devices),
450
+ # we need to ensure tests cannot conflict
451
+ reload_test_app
452
+ end
453
+
454
+ before do
455
+ rest_device_registrations.save({
456
+ id: "device-#{client_id}-0",
457
+ platform: 'ios',
458
+ form_factor: 'phone',
459
+ client_id: client_id,
460
+ push: {
461
+ recipient: {
462
+ transport_type: 'gcm',
463
+ registration_token: 'secret_token',
464
+ }
465
+ }
466
+ })
467
+ end
468
+
469
+ after do
470
+ rest_device_registrations.remove_where client_id: client_id
471
+ end
472
+
473
+ it 'removes the provided device id string' do
474
+ subject.remove("device-#{client_id}-0") do
475
+ subject.list do |page|
476
+ expect(page.items.count).to eql(0)
477
+ stop_reactor
478
+ end
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ describe '#channel_subscriptions' do
485
+ let(:client_id) { random_str }
486
+ let(:device_id) { random_str }
487
+ let(:device_id_2) { random_str }
488
+ let(:default_device_attr) {
489
+ {
490
+ platform: 'ios',
491
+ form_factor: 'phone',
492
+ client_id: client_id,
493
+ push: {
494
+ recipient: {
495
+ transport_type: 'gcm',
496
+ registration_token: 'secret_token',
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ let(:rest_device_registrations) {
503
+ client.rest_client.push.admin.device_registrations
504
+ }
505
+
506
+ let(:rest_channel_subscriptions) {
507
+ client.rest_client.push.admin.channel_subscriptions
508
+ }
509
+
510
+ subject {
511
+ client.push.admin.channel_subscriptions
512
+ }
513
+
514
+ before(:all) do
515
+ # As push tests often use the global scope (devices),
516
+ # we need to ensure tests cannot conflict
517
+ reload_test_app
518
+ end
519
+
520
+ # Set up 2 devices with the same client_id
521
+ # and two device with the unique device_id and no client_id
522
+ before do
523
+ [
524
+ lambda { rest_device_registrations.save(default_device_attr.merge(id: device_id)) },
525
+ lambda { rest_device_registrations.save(default_device_attr.merge(id: device_id_2)) },
526
+ lambda { rest_device_registrations.save(default_device_attr.merge(client_id: client_id, id: random_str)) },
527
+ lambda { rest_device_registrations.save(default_device_attr.merge(client_id: client_id, id: random_str)) }
528
+ ].map do |proc|
529
+ Thread.new { proc.call }
530
+ end.each(&:join) # Wait for all threads to complete
531
+ end
532
+
533
+ after do
534
+ rest_device_registrations.remove_where client_id: client_id
535
+ rest_device_registrations.remove_where device_id: device_id
536
+ end
537
+
538
+ describe '#list' do
539
+ let(:fixture_count) { 6 }
540
+
541
+ before do
542
+ fixture_count.times.map do |index|
543
+ Thread.new { rest_channel_subscriptions.save(channel: "pushenabled:#{random_str}", client_id: client_id) }
544
+ end + fixture_count.times.map do |index|
545
+ Thread.new { rest_channel_subscriptions.save(channel: "pushenabled:#{random_str}", device_id: device_id) }
546
+ end.each(&:join) # Wait for all threads to complete
547
+ end
548
+
549
+ it 'returns a PaginatedResult object containing DeviceDetails objects' do
550
+ subject.list(client_id: client_id) do |page|
551
+ expect(page).to be_a(Ably::Models::PaginatedResult)
552
+ expect(page.items.first).to be_a(Ably::Models::PushChannelSubscription)
553
+ stop_reactor
554
+ end
555
+ end
556
+
557
+ it 'supports paging' do
558
+ subject.list(limit: 3, device_id: device_id) do |page|
559
+ expect(page).to be_a(Ably::Models::PaginatedResult)
560
+
561
+ expect(page.items.count).to eql(3)
562
+ page.next do |page|
563
+ expect(page.items.count).to eql(3)
564
+ page.next do |page|
565
+ expect(page.items.count).to eql(0)
566
+ expect(page).to be_last
567
+ stop_reactor
568
+ end
569
+ end
570
+ end
571
+ end
572
+
573
+ it 'raises an exception if none of the required filters are provided' do
574
+ expect { subject.list({ limit: 100 }) }.to raise_error(ArgumentError)
575
+ stop_reactor
576
+ end
577
+ end
578
+
579
+ describe '#list_channels' do
580
+ let(:fixture_count) { 6 }
581
+
582
+ before(:all) do
583
+ # As push tests often use the global scope (devices),
584
+ # we need to ensure tests cannot conflict
585
+ reload_test_app
586
+ end
587
+
588
+ before do
589
+ fixture_count.times.map do |index|
590
+ Thread.new do
591
+ rest_channel_subscriptions.save(channel: "pushenabled:#{index}:#{random_str}", client_id: client_id)
592
+ end
593
+ end.each(&:join) # Wait for all threads to complete
594
+ end
595
+
596
+ after do
597
+ rest_channel_subscriptions.remove_where client_id: client_id, full_wait: true # undocumented arg to do deletes synchronously
598
+ end
599
+
600
+ it 'returns a PaginatedResult object containing String objects' do
601
+ subject.list_channels do |page|
602
+ expect(page).to be_a(Ably::Models::PaginatedResult)
603
+ expect(page.items.first).to be_a(String)
604
+ expect(page.items.length).to eql(fixture_count)
605
+ stop_reactor
606
+ end
607
+ end
608
+ end
609
+
610
+ describe '#save' do
611
+ let(:channel) { "pushenabled:#{random_str}" }
612
+ let(:client_id) { random_str }
613
+ let(:device_id) { random_str }
614
+
615
+ it 'saves the new client_id PushChannelSubscription Hash object' do
616
+ subject.save(channel: channel, client_id: client_id) do
617
+ subject.list(client_id: client_id) do |page|
618
+ channel_sub = page.items.first
619
+ expect(channel_sub).to be_a(Ably::Models::PushChannelSubscription)
620
+ expect(channel_sub.channel).to eql(channel)
621
+ stop_reactor
622
+ end
623
+ end
624
+ end
625
+
626
+ it 'raises an exception for invalid params' do
627
+ expect { subject.save(channel: '', client_id: '') }.to raise_error ArgumentError
628
+ expect { subject.save({}) }.to raise_error ArgumentError
629
+ stop_reactor
630
+ end
631
+
632
+ context 'failed requests' do
633
+ let(:client_options) do
634
+ default_options.merge(
635
+ log_level: :fatal,
636
+ )
637
+ end
638
+
639
+ it 'fails for invalid requests' do
640
+ subject.save(channel: 'not-enabled-channel', device_id: 'foo').errback do |err|
641
+ expect(err).to be_a(Ably::Exceptions::UnauthorizedRequest)
642
+ subject.save(channel: 'pushenabled:foo', device_id: 'not-registered-so-will-fail').errback do |err|
643
+ expect(err).to be_a(Ably::Exceptions::InvalidRequest)
644
+ stop_reactor
645
+ end
646
+ end
647
+ end
648
+ end
649
+ end
650
+
651
+ describe '#remove_where' do
652
+ let(:client_id) { random_str }
653
+ let(:device_id) { random_str }
654
+ let(:fixed_channel) { "pushenabled:#{random_str}" }
655
+
656
+ let(:fixture_count) { 6 }
657
+
658
+ before do
659
+ fixture_count.times.map do |index|
660
+ Thread.new do
661
+ rest_channel_subscriptions.save(channel: "pushenabled:#{random_str}", client_id: client_id)
662
+ end
663
+ end.each(&:join) # Wait for all threads to complete
664
+ end
665
+
666
+ it 'removes matching client_ids' do
667
+ subject.list(client_id: client_id) do |page|
668
+ expect(page.items.count).to eql(fixture_count)
669
+ subject.remove_where(client_id: client_id, full_wait: true) do
670
+ subject.list(client_id: client_id) do |page|
671
+ expect(page.items.count).to eql(0)
672
+ stop_reactor
673
+ end
674
+ end
675
+ end
676
+ end
677
+
678
+ context 'failed requests' do
679
+ let(:client_options) do
680
+ default_options.merge(
681
+ log_level: :fatal,
682
+ )
683
+ end
684
+
685
+ it 'device_id and client_id filters in the same request are not supported' do
686
+ subject.remove_where(device_id: device_id, client_id: client_id).errback do |err|
687
+ expect(err).to be_a(Ably::Exceptions::InvalidRequest)
688
+ stop_reactor
689
+ end
690
+ end
691
+ end
692
+
693
+ it 'succeeds on no match' do
694
+ subject.remove_where(device_id: random_str, full_wait: true) do
695
+ subject.list(client_id: client_id) do |page|
696
+ expect(page.items.count).to eql(fixture_count)
697
+ stop_reactor
698
+ end
699
+ end
700
+ end
701
+ end
702
+
703
+ describe '#remove' do
704
+ let(:channel) { "pushenabled:#{random_str}" }
705
+ let(:channel2) { "pushenabled:#{random_str}" }
706
+ let(:client_id) { random_str }
707
+ let(:device_id) { random_str }
708
+
709
+ before do
710
+ rest_channel_subscriptions.save(channel: channel, client_id: client_id)
711
+ end
712
+
713
+ it 'removes match for Hash object by channel and client_id' do
714
+ subject.list(client_id: client_id) do |page|
715
+ expect(page.items.count).to eql(1)
716
+ subject.remove(channel: channel, client_id: client_id, full_wait: true) do
717
+ subject.list(client_id: client_id) do |page|
718
+ expect(page.items.count).to eql(0)
719
+ stop_reactor
720
+ end
721
+ end
722
+ end
723
+ end
724
+
725
+ it 'succeeds even if there is no match' do
726
+ subject.remove(device_id: 'does-not-exist', channel: random_str) do
727
+ subject.list(device_id: 'does-not-exist') do |page|
728
+ expect(page.items.count).to eql(0)
729
+ stop_reactor
730
+ end
731
+ end
732
+ end
733
+ end
734
+ end
735
+ end
736
+ end