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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/README.md +23 -15
- data/ably-rest.gemspec +6 -6
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -8
- data/lib/submodules/ably-ruby/CHANGELOG.md +75 -3
- data/lib/submodules/ably-ruby/LICENSE +1 -3
- data/lib/submodules/ably-ruby/README.md +12 -7
- data/lib/submodules/ably-ruby/Rakefile +32 -0
- data/lib/submodules/ably-ruby/SPEC.md +1277 -835
- data/lib/submodules/ably-ruby/ably.gemspec +15 -10
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +7 -1
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
- data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +34 -20
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +53 -17
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +162 -35
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +245 -17
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +177 -59
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +153 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +129 -18
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +36 -34
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +201 -167
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
- data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
- data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
- 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
|