zeppelin 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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