zeppelin 0.5.0 → 0.6.0

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.
@@ -0,0 +1,22 @@
1
+ class Zeppelin
2
+ class ResourceNotFound < Faraday::Error::ResourceNotFound
3
+ end
4
+
5
+ class ClientError < Faraday::Error::ClientError
6
+ end
7
+
8
+ module Middleware
9
+ # Intercept Faraday errors and re-raise our own to hide implementation details
10
+ #
11
+ # @private
12
+ class ResponseRaiseError < Faraday::Response::RaiseError
13
+ def on_complete(env)
14
+ super
15
+ rescue Faraday::Error::ResourceNotFound => msg
16
+ raise ResourceNotFound, msg
17
+ rescue Faraday::Error::ClientError => msg
18
+ raise ClientError, msg
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  class Zeppelin
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zeppelin::Middleware::JsonParser do
4
+ let(:json_body) { "{\"foo\":\"bar\"}" }
5
+
6
+ let(:expected_parsed_body) { { 'foo' => 'bar' } }
7
+
8
+ it 'parses a standard JSON content type' do
9
+ process(json_body, 'application/json').body.should eq(expected_parsed_body)
10
+ end
11
+
12
+ it 'parses vendor JSON content type' do
13
+ process(json_body, 'application/vnd.urbanairship+json').body.should eq(expected_parsed_body)
14
+ end
15
+
16
+ it 'does not change nil body' do
17
+ process(nil).body.should be_nil
18
+ end
19
+
20
+ it 'does not parse non-JSON content types' do
21
+ process('<hello>world</hello>', 'text/xml').body.should eq('<hello>world</hello>')
22
+ end
23
+
24
+ def process(body, content_type=nil, options={})
25
+ env = { :body => body, :response_headers => Faraday::Utils::Headers.new }
26
+ env[:response_headers]['content-type'] = content_type if content_type
27
+
28
+ middleware = Zeppelin::Middleware::JsonParser.new(
29
+ lambda { |env| Faraday::Response.new(env) }
30
+ )
31
+
32
+ middleware.call(env)
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zeppelin::Middleware::ResponseRaiseError do
4
+ subject do
5
+ Faraday.new do |b|
6
+ b.use(described_class)
7
+ b.adapter :test do |stub|
8
+ stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
9
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
10
+ stub.get('error') { [500, { 'X-Error' => 'bailout' }, 'fail' ] }
11
+ end
12
+ end
13
+ end
14
+
15
+ it 'does nothing when the response is successful' do
16
+ expect {
17
+ subject.get('ok')
18
+ }.to_not raise_error
19
+ end
20
+
21
+ it 'raises an error when the resource was not found' do
22
+ expect {
23
+ subject.get('not-found')
24
+ }.to raise_error(Zeppelin::ResourceNotFound)
25
+ end
26
+
27
+ it 'raises an error when a client error occurs' do
28
+ expect {
29
+ subject.get('error')
30
+ }.to raise_error(Zeppelin::ClientError)
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'zeppelin'
5
+
6
+ RSpec.configure do |config|
7
+ end
@@ -0,0 +1,470 @@
1
+ require 'spec_helper'
2
+
3
+ describe Zeppelin do
4
+ let(:device_token) { '1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF' }
5
+
6
+ let(:app_key) { 'app key' }
7
+
8
+ let(:master_secret) { 'app master secret' }
9
+
10
+ let(:options) { { :ssl => { :ca_path => '/dev/null' } } }
11
+
12
+ subject { Zeppelin.new(app_key, master_secret, options) }
13
+
14
+ describe '.new' do
15
+ its(:application_key) { should eq(app_key) }
16
+
17
+ its(:application_master_secret) { should eq(master_secret) }
18
+
19
+ its(:options) { should eq(options) }
20
+ end
21
+
22
+ describe '#connection' do
23
+ it { subject.connection.should be_a(Faraday::Connection) }
24
+
25
+ it { subject.connection.scheme.should eq('https') }
26
+
27
+ it { subject.connection.host.should eq('go.urbanairship.com') }
28
+
29
+ it { subject.connection.builder.handlers.should include(Faraday::Adapter::NetHttp) }
30
+
31
+ it { subject.connection.builder.handlers.should include(Faraday::Request::JSON) }
32
+
33
+ it { subject.connection.builder.handlers.should include(Zeppelin::Middleware::JsonParser) }
34
+
35
+ it { subject.connection.builder.handlers.should include(Zeppelin::Middleware::ResponseRaiseError) }
36
+
37
+ it { subject.connection.headers['Authorization'].should eq('Basic YXBwIGtleTphcHAgbWFzdGVyIHNlY3JldA==') }
38
+ end
39
+
40
+ describe '#register_device_token' do
41
+ let(:payload) { { :alias => 'CapnKernul' } }
42
+
43
+ it 'registers a device with the service' do
44
+ stub_requests do |stub|
45
+ stub.put("/api/device_tokens/#{device_token}") do |stub|
46
+ [201, {}, '']
47
+ end
48
+ end
49
+
50
+ subject.register_device_token(device_token).should be_true
51
+ end
52
+
53
+ it 'accepts a payload' do
54
+ stub_requests do |stub|
55
+ stub.put("/api/device_tokens/#{device_token}", MultiJson.encode(payload)) do
56
+ [200, {}, '']
57
+ end
58
+ end
59
+
60
+ subject.register_device_token(device_token, payload).should be_true
61
+ end
62
+
63
+ it 'responds with false when an error occurs' do
64
+ stub_requests do |stub|
65
+ stub.put("/api/device_tokens/#{device_token}", nil) do
66
+ [500, {}, '']
67
+ end
68
+ end
69
+
70
+ expect {
71
+ subject.register_device_token(device_token)
72
+ }.to raise_error(Zeppelin::ClientError)
73
+ end
74
+ end
75
+
76
+ describe '#device_token' do
77
+ let(:response_body) { { 'foo' => 'bar' } }
78
+
79
+ it 'gets information about a device' do
80
+ stub_requests do |stub|
81
+ stub.get("/api/device_tokens/#{device_token}") do
82
+ [200, { 'Content-Type' => 'application/json' }, MultiJson.encode(response_body)]
83
+ end
84
+ end
85
+
86
+ subject.device_token(device_token).should eq(response_body)
87
+ end
88
+
89
+ it 'is nil when the request fails' do
90
+ stub_requests do |stub|
91
+ stub.get("/api/device_tokens/#{device_token}") do
92
+ [404, {}, '']
93
+ end
94
+ end
95
+
96
+ expect {
97
+ subject.device_token(device_token)
98
+ }.to raise_error(Zeppelin::ResourceNotFound)
99
+ end
100
+ end
101
+
102
+ describe '#delete_device_token' do
103
+ it 'is true when successful' do
104
+ stub_requests do |stub|
105
+ stub.delete("/api/device_tokens/#{device_token}") do
106
+ [204, {}, '']
107
+ end
108
+ end
109
+
110
+ subject.delete_device_token(device_token).should be_true
111
+ end
112
+
113
+ it 'is false when the request fails' do
114
+ stub_requests do |stub|
115
+ stub.delete("/api/device_tokens/#{device_token}") do
116
+ [404, {}, '']
117
+ end
118
+ end
119
+
120
+ expect {
121
+ subject.delete_device_token(device_token)
122
+ }.to raise_error(Zeppelin::ResourceNotFound)
123
+ end
124
+ end
125
+
126
+ describe '#register_apid' do
127
+ let(:payload) { { :alias => 'CapnKernul' } }
128
+
129
+ it 'registers a device with the service' do
130
+ stub_requests do |stub|
131
+ stub.put("/api/apids/#{device_token}") do |stub|
132
+ [201, {}, '']
133
+ end
134
+ end
135
+
136
+ subject.register_apid(device_token).should be_true
137
+ end
138
+
139
+ it 'accepts a payload' do
140
+ stub_requests do |stub|
141
+ stub.put("/api/apids/#{device_token}", MultiJson.encode(payload)) do
142
+ [200, {}, '']
143
+ end
144
+ end
145
+
146
+ subject.register_apid(device_token, payload).should be_true
147
+ end
148
+
149
+ it 'responds with false when an error occurs' do
150
+ stub_requests do |stub|
151
+ stub.put("/api/apids/#{device_token}", nil) do
152
+ [500, {}, '']
153
+ end
154
+ end
155
+
156
+ expect {
157
+ subject.register_apid(device_token)
158
+ }.to raise_error(Zeppelin::ClientError)
159
+ end
160
+ end
161
+
162
+ describe '#apid' do
163
+ let(:response_body) { { 'foo' => 'bar' } }
164
+
165
+ it 'responds with information about a device when request is successful' do
166
+ stub_requests do |stub|
167
+ stub.get("/api/apids/#{device_token}") do
168
+ [200, { 'Content-Type' => 'application/json' }, MultiJson.encode(response_body)]
169
+ end
170
+ end
171
+
172
+ subject.apid(device_token).should eq(response_body)
173
+ end
174
+
175
+ it 'is nil when the request fails' do
176
+ stub_requests do |stub|
177
+ stub.get("/api/apids/#{device_token}") do
178
+ [404, {}, '']
179
+ end
180
+ end
181
+
182
+ expect {
183
+ subject.apid(device_token)
184
+ }.to raise_error(Zeppelin::ResourceNotFound)
185
+ end
186
+ end
187
+
188
+ describe '#delete_apid' do
189
+ it 'responds with true when request successful' do
190
+ stub_requests do |stub|
191
+ stub.delete("/api/apids/#{device_token}") do
192
+ [204, {}, '']
193
+ end
194
+ end
195
+
196
+ subject.delete_apid(device_token).should be_true
197
+ end
198
+
199
+ it 'responds with false when request fails' do
200
+ stub_requests do |stub|
201
+ stub.delete("/api/apids/#{device_token}") do
202
+ [404, {}, '']
203
+ end
204
+ end
205
+
206
+ expect {
207
+ subject.delete_apid(device_token)
208
+ }.to raise_error(Zeppelin::ResourceNotFound)
209
+ end
210
+ end
211
+
212
+ describe '#push' do
213
+ let(:payload) { { :device_tokens => [device_token], :aps => { :alert => 'Hello from Urban Airship!' } } }
214
+
215
+ it 'is true when the request is successful' do
216
+ stub_requests do |stub|
217
+ stub.post('/api/push/', MultiJson.encode(payload)) do
218
+ [200, {}, '']
219
+ end
220
+ end
221
+
222
+ subject.push(payload).should be_true
223
+ end
224
+
225
+ it 'is false when the request fails' do
226
+ stub_requests do |stub|
227
+ stub.post('/api/push/', '{}') do
228
+ [400, {}, '']
229
+ end
230
+ end
231
+
232
+ expect {
233
+ subject.push({})
234
+ }.to raise_error(Zeppelin::ClientError)
235
+ end
236
+ end
237
+
238
+ describe '#batch_push' do
239
+ let(:message1) {
240
+ {
241
+ :device_tokens => [@device_token],
242
+ :aps => { :alert => 'Hello from Urban Airship!' }
243
+ }
244
+ }
245
+
246
+ let(:message2) {
247
+ {
248
+ :device_tokens => [],
249
+ :aps => { :alert => 'Yet another hello from Urban Airship!' }
250
+ }
251
+ }
252
+
253
+ let(:payload) { [message1, message2] }
254
+
255
+ it 'is true when the request was successful' do
256
+ stub_requests do |stub|
257
+ stub.post('/api/push/batch/', MultiJson.encode(payload)) do
258
+ [200, {}, '']
259
+ end
260
+ end
261
+
262
+ subject.batch_push(message1, message2).should be_true
263
+ end
264
+
265
+ it 'is false when the request fails' do
266
+ stub_requests do |stub|
267
+ stub.post('/api/push/batch/', '[{},{}]') do
268
+ [400, {}, '']
269
+ end
270
+ end
271
+
272
+ expect {
273
+ subject.batch_push({}, {})
274
+ }.to raise_error(Zeppelin::ClientError)
275
+ end
276
+ end
277
+
278
+ describe '#broadcast' do
279
+ let(:payload) { { :aps => { :alert => 'Hello from Urban Airship!' } } }
280
+
281
+ it 'is true when the request is successful' do
282
+ stub_requests do |stub|
283
+ stub.post('/api/push/broadcast/', MultiJson.encode(payload)) do
284
+ [200, {}, '']
285
+ end
286
+ end
287
+
288
+ subject.broadcast(payload).should be_true
289
+ end
290
+
291
+ it 'is false when the request fails' do
292
+ stub_requests do |stub|
293
+ stub.post('/api/push/broadcast/', '{}') do
294
+ [400, {}, '']
295
+ end
296
+ end
297
+
298
+ expect {
299
+ subject.broadcast({})
300
+ }.to raise_error(Zeppelin::ClientError)
301
+ end
302
+ end
303
+
304
+ describe '#feedback' do
305
+ let(:response_body) { { 'foo' => 'bar' } }
306
+
307
+ let(:since) { Time.at(0) }
308
+
309
+ it 'is the response body for a successful request' do
310
+ stub_requests do |stub|
311
+ stub.get('/api/device_tokens/feedback/?since=1970-01-01T00%3A00%3A00Z') do
312
+ [200, { 'Content-Type' => 'application/json' }, MultiJson.encode(response_body)]
313
+ end
314
+ end
315
+
316
+ subject.feedback(since)
317
+ end
318
+
319
+ it 'is nil when the request fails' do
320
+ stub_requests do |stub|
321
+ stub.get('/api/device_tokens/feedback/?since=1970-01-01T00%3A00%3A00Z') do
322
+ [400, {}, '']
323
+ end
324
+ end
325
+
326
+ expect {
327
+ subject.feedback(since)
328
+ }.to raise_error(Zeppelin::ClientError)
329
+ end
330
+ end
331
+
332
+ describe '#modify_device_token_on_tag' do
333
+ let(:tag_name) { 'jimmy.page' }
334
+
335
+ let(:device_token) { 'CAFEBABE' }
336
+
337
+ it 'requets to modify device tokens on a tag' do
338
+ stub_requests do |stub|
339
+ stub.post("/api/tags/#{tag_name}") do
340
+ [200, {}, 'OK']
341
+ end
342
+ end
343
+
344
+ subject.modify_device_tokens_on_tag(tag_name, { 'device_tokens' => { 'add' => [device_token] } }).should be
345
+ end
346
+ end
347
+
348
+ describe '#add_tag' do
349
+ let(:tag_name) { 'chunky.bacon' }
350
+
351
+ it 'is true when the request is successful' do
352
+ stub_requests do |stub|
353
+ stub.put("/api/tags/#{tag_name}") do
354
+ [201, {}, '']
355
+ end
356
+ end
357
+
358
+ subject.add_tag(tag_name).should be_true
359
+ end
360
+ end
361
+
362
+ describe '#remove_tag' do
363
+ let(:tag_name) { 'cats.pajamas' }
364
+
365
+ it 'is true when the request is successful' do
366
+ stub_requests do |stub|
367
+ stub.delete("/api/tags/#{tag_name}") do
368
+ [204, {}, '']
369
+ end
370
+ end
371
+
372
+ subject.remove_tag(tag_name).should be_true
373
+ end
374
+
375
+ it 'is false when the request fails' do
376
+ stub_requests do |stub|
377
+ stub.delete("/api/tags/#{tag_name}") do
378
+ [404, {}, '']
379
+ end
380
+ end
381
+
382
+ expect {
383
+ subject.remove_tag(tag_name)
384
+ }.to raise_error(Zeppelin::ResourceNotFound)
385
+ end
386
+ end
387
+
388
+ describe '#device_tags' do
389
+ let(:response_body) { { 'tags' => ['tag1', 'some_tag'] } }
390
+
391
+ it 'is the collection of tags on a device when request is successful' do
392
+ stub_requests do |stub|
393
+ stub.get("/api/device_tokens/#{device_token}/tags/") do
394
+ [200, { 'Content-Type' => 'application/json' }, MultiJson.encode(response_body)]
395
+ end
396
+ end
397
+
398
+ subject.device_tags(device_token).should eq(response_body)
399
+ end
400
+
401
+ it 'is nil when the request fails' do
402
+ stub_requests do |stub|
403
+ stub.get("/api/device_tokens/#{device_token}/tags/") do
404
+ [404, {}, 'Not Found']
405
+ end
406
+ end
407
+
408
+ expect {
409
+ subject.device_tags(device_token)
410
+ }.to raise_error(Zeppelin::ResourceNotFound)
411
+ end
412
+ end
413
+
414
+ describe '#add_tag_to_device' do
415
+ let(:tag_name) { 'radio.head' }
416
+
417
+ it 'is true when the request is successful' do
418
+ stub_requests do |stub|
419
+ stub.put("/api/device_tokens/#{device_token}/tags/#{tag_name}") do
420
+ [201, {}, 'Created']
421
+ end
422
+ end
423
+
424
+ subject.add_tag_to_device(device_token, tag_name).should be_true
425
+ end
426
+
427
+ it 'is false when the request fails' do
428
+ stub_requests do |stub|
429
+ stub.put("/api/device_tokens/#{device_token}/tags/#{tag_name}") do
430
+ [404, {}, '']
431
+ end
432
+ end
433
+
434
+ expect {
435
+ subject.add_tag_to_device(device_token, tag_name)
436
+ }.to raise_error(Zeppelin::ResourceNotFound)
437
+ end
438
+ end
439
+
440
+ describe '#remove_tag_from_device' do
441
+ let(:tag_name) { 'martin.fowler' }
442
+
443
+ it 'is true when the request is successful' do
444
+ stub_requests do |stub|
445
+ stub.delete("/api/device_tokens/#{device_token}/tags/#{tag_name}") do
446
+ [204, {}, 'No Content']
447
+ end
448
+ end
449
+
450
+ subject.remove_tag_from_device(device_token, tag_name).should be_true
451
+ end
452
+
453
+ it 'is false when the request fails' do
454
+ stub_requests do |stub|
455
+ stub.delete("/api/device_tokens/#{device_token}/tags/#{tag_name}") do
456
+ [404, {}, '']
457
+ end
458
+ end
459
+
460
+ expect {
461
+ subject.remove_tag_from_device(device_token, tag_name)
462
+ }.to raise_error(Zeppelin::ResourceNotFound)
463
+ end
464
+ end
465
+
466
+ def stub_requests(&block)
467
+ subject.connection.builder.handlers.delete(Faraday::Adapter::NetHttp)
468
+ subject.connection.adapter(:test, &block)
469
+ end
470
+ end