urbanairship 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2011 Groupon, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,82 @@
1
+ Urbanairship is a Ruby library for interacting with the [Urbanairship API](http://urbanairship.com).
2
+
3
+ Installation
4
+ ============
5
+ gem install urbanairship
6
+
7
+ Configuration
8
+ =============
9
+ ```ruby
10
+ Urbanairship.application_key = 'application-key'
11
+ Urbanairship.application_secret = 'application-secret'
12
+ Urbanairship.master_secret = 'master-secret'
13
+ Urbanairship.logger = Rails.logger
14
+ Urbanairship.request_timeout = 5 # default
15
+ ```
16
+
17
+ Usage
18
+ =====
19
+
20
+ Registering a device token
21
+ --------------------------
22
+ ```ruby
23
+ Urbanairship.register_device 'DEVICE-TOKEN' # => true
24
+ ```
25
+
26
+ Unregistering a device token
27
+ ----------------------------
28
+ ```ruby
29
+ Urbanairship.unregister_device 'DEVICE-TOKEN' # => true
30
+ ```
31
+
32
+ Sending a push notification
33
+ ---------------------------
34
+ ```ruby
35
+ notification = {
36
+ :schedule_for => 1.hour.from_now,
37
+ :device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
38
+ :aps => {:alert => 'You have a new message!', :badge => 1}
39
+ }
40
+
41
+ Urbanairship.push notification # => true
42
+ ```
43
+
44
+ Batching push notification sends
45
+ --------------------------------
46
+ ```ruby
47
+ notifications = [
48
+ {
49
+ :schedule_for => 1.hour.from_now,
50
+ :device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
51
+ :aps => {:alert => 'You have a new message!', :badge => 1}
52
+ },
53
+ {
54
+ :schedule_for => 3.hours.from_now,
55
+ :device_tokens => ['DEVICE-TOKEN-THREE'],
56
+ :aps => {:alert => 'You have a new message!', :badge => 1}
57
+ }
58
+ ]
59
+
60
+ Urbanairship.batch_push notifications # => true
61
+ ```
62
+
63
+ Polling the feedback API
64
+ ------------------------
65
+ The first time you attempt to send a push notification to a device that has uninstalled your app (or has opted-out of notifications), both Apple and Urbanairship will register that token in their feedback API. Urbanairship will prevent further attempted notification sends to that device, but it's a good practice to periodically poll Urbanairship's feedback API and mark those tokens as inactive in your own system as well.
66
+
67
+ ```ruby
68
+ # find all device tokens deactivated in the past 24 hours
69
+ Urbanairship.feedback 24.hours.ago # =>
70
+ # [
71
+ # {
72
+ # "marked_inactive_on"=>"2011-06-03 22:53:23",
73
+ # "alias"=>nil,
74
+ # "device_token"=>"DEVICE-TOKEN-ONE"
75
+ # },
76
+ # {
77
+ # "marked_inactive_on"=>"2011-06-03 22:53:23",
78
+ # "alias"=>nil,
79
+ # "device_token"=>"DEVICE-TOKEN-TWO"
80
+ # }
81
+ # ]
82
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ desc 'Run all the tests'
2
+ task :default => :spec
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = ['-c', '-f progress', '-r ./spec/spec_helper.rb']
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
@@ -0,0 +1,100 @@
1
+ require 'json'
2
+ require 'net/https'
3
+ require 'time'
4
+ require 'system_timer'
5
+
6
+ module Urbanairship
7
+ VALID_PUSH_PARAMS = %w(device_tokens aliases tags schedule_for exclude_tokens aps)
8
+
9
+ class << self
10
+ attr_accessor :application_key, :application_secret, :master_secret, :logger, :request_timeout
11
+
12
+ def register_device(device_token)
13
+ response = do_request(:put, "/api/device_tokens/#{device_token}", :authenticate_with => :application_secret)
14
+ response && %w(200 201).include?(response.code)
15
+ end
16
+
17
+ def unregister_device(device_token)
18
+ response = do_request(:delete, "/api/device_tokens/#{device_token}", :authenticate_with => :application_secret)
19
+ response && response.code == "204"
20
+ end
21
+
22
+ def push(options = {})
23
+ response = do_request(:post, "/api/push/", :authenticate_with => :master_secret) do |request|
24
+ request.body = parse_push_options(options).to_json
25
+ request.add_field "Content-Type", "application/json"
26
+ end
27
+
28
+ response && response.code == "200"
29
+ end
30
+
31
+ def batch_push(notifications = [])
32
+ response = do_request(:post, "/api/push/batch/", :authenticate_with => :master_secret) do |request|
33
+ request.body = notifications.map{|notification| parse_push_options(notification)}.to_json
34
+ request.add_field "Content-Type", "application/json"
35
+ end
36
+
37
+ response && response.code == "200"
38
+ end
39
+
40
+ def feedback(time)
41
+ response = do_request(:get, "/api/device_tokens/feedback/?since=#{format_time(time)}", :authenticate_with => :master_secret)
42
+ response && response.code == "200" ? JSON.parse(response.body) : false
43
+ end
44
+
45
+ private
46
+
47
+ def do_request(http_method, path, options = {})
48
+ verify_configuration_values(:application_key, options[:authenticate_with])
49
+
50
+ klass = Net::HTTP.const_get(http_method.to_s.capitalize)
51
+
52
+ request = klass.new(path)
53
+ request.basic_auth @application_key, instance_variable_get("@#{options[:authenticate_with]}")
54
+
55
+ yield(request) if block_given?
56
+
57
+ SystemTimer.timeout_after(request_timeout) do
58
+ start_time = Time.now
59
+ response = http_client.request(request)
60
+ log_request_and_response(request, response, Time.now - start_time)
61
+ response
62
+ end
63
+ rescue Timeout::Error
64
+ logger.error "Urbanairship request timed out after #{request_timeout} seconds: [#{http_method} #{request.path} #{request.body}]"
65
+ return false
66
+ end
67
+
68
+ def verify_configuration_values(*symbols)
69
+ absent_values = symbols.select{|symbol| instance_variable_get("@#{symbol}").nil? }
70
+ raise("Must configure #{absent_values.join(", ")} before making this request.") unless absent_values.empty?
71
+ end
72
+
73
+ def http_client
74
+ Net::HTTP.new("go.urbanairship.com", 443).tap{|http| http.use_ssl = true}
75
+ end
76
+
77
+ def parse_push_options(hash = {})
78
+ hash[:schedule_for] = hash[:schedule_for].map{|time| format_time(time)} unless hash[:schedule_for].nil?
79
+ hash.delete_if{|key, value| !VALID_PUSH_PARAMS.include?(key.to_s)}
80
+ end
81
+
82
+ def log_request_and_response(request, response, time)
83
+ return if logger.nil?
84
+
85
+ time = (time * 1000).to_i
86
+ http_method = request.class.to_s.split('::')[-1]
87
+ logger.info "Urbanairship (#{time}ms): [#{http_method} #{request.path}, #{request.body}], [#{response.code}, #{response.body}]"
88
+ logger.flush if logger.respond_to?(:flush)
89
+ end
90
+
91
+ def format_time(time)
92
+ time = Time.parse(time) if time.is_a?(String)
93
+ time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
94
+ end
95
+
96
+ def request_timeout
97
+ @request_timeout || 5.0
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,4 @@
1
+ require 'base64'
2
+ require 'fakeweb'
3
+
4
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/urbanairship')
@@ -0,0 +1,440 @@
1
+ describe Urbanairship do
2
+
3
+ before(:all) do
4
+ FakeWeb.allow_net_connect = false
5
+
6
+ # register_device
7
+ FakeWeb.register_uri(:put, "https://my_app_key:my_app_secret@go.urbanairship.com/api/device_tokens/new_device_token", :status => ["201", "Created"])
8
+ FakeWeb.register_uri(:put, "https://my_app_key:my_app_secret@go.urbanairship.com/api/device_tokens/existing_device_token", :status => ["200", "OK"])
9
+ FakeWeb.register_uri(:put, /bad_key\:my_app_secret\@go\.urbanairship\.com/, :status => ["401", "Unauthorized"])
10
+
11
+ # unregister_device
12
+ FakeWeb.register_uri(:delete, /my_app_key\:my_app_secret\@go\.urbanairship.com\/api\/device_tokens\/.+/, :status => ["204", "No Content"])
13
+ FakeWeb.register_uri(:delete, /bad_key\:my_app_secret\@go\.urbanairship.com\/api\/device_tokens\/.+/, :status => ["401", "Unauthorized"])
14
+
15
+ # push
16
+ FakeWeb.register_uri(:post, "https://my_app_key:my_master_secret@go.urbanairship.com/api/push/", :status => ["200", "OK"])
17
+ FakeWeb.register_uri(:post, "https://my_app_key2:my_master_secret2@go.urbanairship.com/api/push/", :status => ["400", "Bad Request"])
18
+ FakeWeb.register_uri(:post, /bad_key\:my_master_secret\@go\.urbanairship\.com/, :status => ["401", "Unauthorized"])
19
+
20
+ # batch_push
21
+ FakeWeb.register_uri(:post, "https://my_app_key:my_master_secret@go.urbanairship.com/api/push/batch/", :status => ["200", "OK"])
22
+ FakeWeb.register_uri(:post, "https://my_app_key2:my_master_secret2@go.urbanairship.com/api/push/batch/", :status => ["400", "Bad Request"])
23
+
24
+ # feedback
25
+ FakeWeb.register_uri(:get, /my_app_key\:my_master_secret\@go\.urbanairship.com\/api\/device_tokens\/feedback/, :status => ["200", "OK"], :body => "[{\"device_token\":\"token\",\"marked_inactive_on\":\"2010-10-14T19:15:13Z\",\"alias\":\"my_alias\"}]")
26
+ FakeWeb.register_uri(:get, /my_app_key2\:my_master_secret2\@go\.urbanairship.com\/api\/device_tokens\/feedback/, :status => ["500", "Internal Server Error"])
27
+ end
28
+
29
+ after(:each) do
30
+ # reset configuration
31
+ Urbanairship.application_key = nil
32
+ Urbanairship.application_secret = nil
33
+ Urbanairship.master_secret = nil
34
+ Urbanairship.logger = nil
35
+
36
+ FakeWeb.instance_variable_set("@last_request", nil)
37
+ end
38
+
39
+ describe "configuration" do
40
+
41
+ it "enables you to configure the application key" do
42
+ Urbanairship.application_key.should be_nil
43
+ Urbanairship.application_key = "asdf1234"
44
+ Urbanairship.application_key.should == "asdf1234"
45
+ end
46
+
47
+ it "enables you to configure the application secret" do
48
+ Urbanairship.application_secret.should be_nil
49
+ Urbanairship.application_secret = "asdf1234"
50
+ Urbanairship.application_secret.should == "asdf1234"
51
+ end
52
+
53
+ it "enables you to configure the master secret" do
54
+ Urbanairship.master_secret.should be_nil
55
+ Urbanairship.master_secret = "asdf1234"
56
+ Urbanairship.master_secret.should == "asdf1234"
57
+ end
58
+
59
+ end
60
+
61
+ describe "registering a device" do
62
+
63
+ before(:each) do
64
+ Urbanairship.application_key = "my_app_key"
65
+ Urbanairship.application_secret = "my_app_secret"
66
+ end
67
+
68
+ it "raises an error if call is made without an app key and secret configured" do
69
+ Urbanairship.application_key = nil
70
+ Urbanairship.application_secret = nil
71
+
72
+ lambda {
73
+ Urbanairship.register_device("asdf1234")
74
+ }.should raise_error(RuntimeError, "Must configure application_key, application_secret before making this request.")
75
+ end
76
+
77
+ it "uses app key and secret to sign the request" do
78
+ Urbanairship.register_device("new_device_token")
79
+ FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_app_secret').chomp}"
80
+ end
81
+
82
+ it "takes and sends a device token" do
83
+ Urbanairship.register_device("new_device_token")
84
+ FakeWeb.last_request.path.should == "/api/device_tokens/new_device_token"
85
+ end
86
+
87
+ it "returns true when the device is registered for the first time" do
88
+ Urbanairship.register_device("new_device_token").should == true
89
+ end
90
+
91
+ it "returns true when the device is registered again" do
92
+ Urbanairship.register_device("existing_device_token").should == true
93
+ end
94
+
95
+ it "returns false when the authorization is invalid" do
96
+ Urbanairship.application_key = "bad_key"
97
+ Urbanairship.register_device("new_device_token").should == false
98
+ end
99
+
100
+ # TODO:
101
+ # it "accepts additional parameters (EXPAND THIS)"
102
+
103
+ end
104
+
105
+ describe "unregistering a device" do
106
+ before(:each) do
107
+ Urbanairship.application_key = "my_app_key"
108
+ Urbanairship.application_secret = "my_app_secret"
109
+ end
110
+
111
+ it "raises an error if call is made without an app key and secret configured" do
112
+ Urbanairship.application_key = nil
113
+ Urbanairship.application_secret = nil
114
+
115
+ lambda {
116
+ Urbanairship.unregister_device("asdf1234")
117
+ }.should raise_error(RuntimeError, "Must configure application_key, application_secret before making this request.")
118
+ end
119
+
120
+ it "uses app key and secret to sign the request" do
121
+ Urbanairship.unregister_device("key_to_delete")
122
+ FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_app_secret').chomp}"
123
+ end
124
+
125
+ it "sends the key that needs to be deleted" do
126
+ Urbanairship.unregister_device("key_to_delete")
127
+ FakeWeb.last_request.path.should == "/api/device_tokens/key_to_delete"
128
+ end
129
+
130
+ it "returns true when the device is successfully unregistered" do
131
+ Urbanairship.unregister_device("key_to_delete").should == true
132
+ FakeWeb.last_request.body.should be_nil
133
+ end
134
+
135
+ it "returns false when the authorization is invalid" do
136
+ Urbanairship.application_key = "bad_key"
137
+ Urbanairship.unregister_device("key_to_delete").should == false
138
+ end
139
+
140
+ end
141
+
142
+ describe "sending multiple push notifications" do
143
+
144
+ before(:each) do
145
+ @valid_params = {:device_tokens => ['device_token_one', 'device_token_two'], :aps => {:alert => 'foo'}}
146
+ Urbanairship.application_key = "my_app_key"
147
+ Urbanairship.master_secret = "my_master_secret"
148
+ end
149
+
150
+ it "raises an error if call is made without an app key and master secret configured" do
151
+ Urbanairship.application_key = nil
152
+ Urbanairship.master_secret = nil
153
+
154
+ lambda {
155
+ Urbanairship.push(@valid_params)
156
+ }.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
157
+ end
158
+
159
+ it "uses app key and secret to sign the request" do
160
+ Urbanairship.push(@valid_params)
161
+ FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
162
+ end
163
+
164
+ it "returns true when it successfully pushes a notification" do
165
+ Urbanairship.push(@valid_params).should == true
166
+ end
167
+
168
+ it "returns false when the authorization is invalid" do
169
+ Urbanairship.application_key = "bad_key"
170
+ Urbanairship.push(@valid_params).should == false
171
+ end
172
+
173
+ it "sets the content-type header to application/json" do
174
+ Urbanairship.push(@valid_params)
175
+ FakeWeb.last_request['content-type'].should == 'application/json'
176
+ end
177
+
178
+ it "adds device_tokens to the JSON payload" do
179
+ Urbanairship.push(@valid_params.merge(:device_tokens => ["one", "two"]))
180
+ request_json['device_tokens'].should == ["one", "two"]
181
+ end
182
+
183
+ it "adds aliases to the JSON payload" do
184
+ Urbanairship.push(@valid_params.merge(:aliases => ["one", "two"]))
185
+ request_json['aliases'].should == ["one", "two"]
186
+ end
187
+
188
+ it "adds tags to the JSON payload" do
189
+ Urbanairship.push(@valid_params.merge(:tags => ["one", "two"]))
190
+ request_json['tags'].should == ["one", "two"]
191
+ end
192
+
193
+ it "adds schedule_for to the JSON payload" do
194
+ time = Time.parse("Oct 17th, 2010, 8:00 PM UTC")
195
+ Urbanairship.push(@valid_params.merge(:schedule_for => [time]))
196
+ request_json['schedule_for'].should == ['2010-10-17T20:00:00Z']
197
+ end
198
+
199
+ it "only attempts to format schedule_for if it is a time object" do
200
+ Urbanairship.push(@valid_params.merge(:schedule_for => ["2010-10-10 09:09:09 UTC"]))
201
+ request_json['schedule_for'].should == ['2010-10-10T09:09:09Z']
202
+ end
203
+
204
+ it "adds exclude_tokens to the JSON payload" do
205
+ Urbanairship.push(@valid_params.merge(:exclude_tokens => ["one", "two"]))
206
+ request_json['exclude_tokens'].should == ["one", "two"]
207
+ end
208
+
209
+ it "adds aps parameters to the JSON payload" do
210
+ Urbanairship.push(@valid_params.merge(:aps => {:badge => 10, :alert => "Hi!", :sound => "cat.caf"}))
211
+ request_json['aps'].should == {'badge' => 10, 'alert' => 'Hi!', 'sound' => 'cat.caf'}
212
+ end
213
+
214
+ it "excludes invalid parameters from the JSON payload" do
215
+ Urbanairship.push(@valid_params.merge(:foo => 'bar'))
216
+ request_json['foo'].should be_nil
217
+ end
218
+
219
+ it "returns false if urbanairship responds with a non-200 response" do
220
+ Urbanairship.application_key = "my_app_key2"
221
+ Urbanairship.master_secret = "my_master_secret2"
222
+ Urbanairship.push.should == false
223
+ end
224
+
225
+ end
226
+
227
+ describe "sending batch push notifications" do
228
+
229
+ before(:each) do
230
+ @valid_params = [
231
+ {:device_tokens => ['device_token_one', 'device_token_two'], :aps => {:alert => 'foo'}},
232
+ {:device_tokens => ['device_token_three', 'device_token_four'], :aps => {:alert => 'bar'}}
233
+ ]
234
+ Urbanairship.application_key = "my_app_key"
235
+ Urbanairship.master_secret = "my_master_secret"
236
+ end
237
+
238
+ it "raises an error if call is made without an app key and master secret configured" do
239
+ Urbanairship.application_key = nil
240
+ Urbanairship.master_secret = nil
241
+
242
+ lambda {
243
+ Urbanairship.batch_push(@valid_params)
244
+ }.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
245
+ end
246
+
247
+ it "uses app key and secret to sign the request" do
248
+ Urbanairship.batch_push(@valid_params)
249
+ FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
250
+ end
251
+
252
+ it "returns true when it successfully pushes a notification" do
253
+ Urbanairship.batch_push(@valid_params).should == true
254
+ end
255
+
256
+ it "returns false when the authorization is invalid" do
257
+ Urbanairship.application_key = "bad_key"
258
+ Urbanairship.batch_push(@valid_params).should == false
259
+ end
260
+
261
+ it "sets the content-type header to application/json" do
262
+ Urbanairship.batch_push(@valid_params)
263
+ FakeWeb.last_request['content-type'].should == 'application/json'
264
+ end
265
+
266
+ it "adds device_tokens to the JSON payload" do
267
+ @valid_params[0].merge!(:device_tokens => ["one", "two"])
268
+ Urbanairship.batch_push(@valid_params)
269
+ request_json[0]['device_tokens'].should == ["one", "two"]
270
+ end
271
+
272
+ it "adds aliases to the JSON payload" do
273
+ @valid_params[0].merge!(:aliases => ["one", "two"])
274
+ Urbanairship.batch_push(@valid_params)
275
+ request_json[0]['aliases'].should == ["one", "two"]
276
+ end
277
+
278
+ it "adds tags to the JSON payload" do
279
+ @valid_params[0].merge!(:tags => ["one", "two"])
280
+ Urbanairship.batch_push(@valid_params)
281
+ request_json[0]['tags'].should == ["one", "two"]
282
+ end
283
+
284
+ it "adds schedule_for to the JSON payload" do
285
+ time = Time.parse("Oct 17th, 2010, 8:00 PM UTC")
286
+ @valid_params[0].merge!(:schedule_for => [time])
287
+ Urbanairship.batch_push(@valid_params)
288
+ request_json[0]['schedule_for'].should == ['2010-10-17T20:00:00Z']
289
+ end
290
+
291
+ it "accepts strings as schedule_for values" do
292
+ @valid_params[0].merge!(:schedule_for => ["2010-10-10 09:09:09 UTC"])
293
+ Urbanairship.batch_push(@valid_params)
294
+ request_json[0]['schedule_for'].should == ['2010-10-10T09:09:09Z']
295
+ end
296
+
297
+ it "adds exclude_tokens to the JSON payload" do
298
+ @valid_params[0].merge!(:exclude_tokens => ["one", "two"])
299
+ Urbanairship.batch_push(@valid_params)
300
+ request_json[0]['exclude_tokens'].should == ["one", "two"]
301
+ end
302
+
303
+ it "adds aps parameters to the JSON payload" do
304
+ @valid_params[0].merge!(:aps => {:badge => 10, :alert => "Hi!", :sound => "cat.caf"})
305
+ Urbanairship.batch_push(@valid_params)
306
+ request_json[0]['aps'].should == {'badge' => 10, 'alert' => 'Hi!', 'sound' => 'cat.caf'}
307
+ end
308
+
309
+ it "excludes invalid parameters from the JSON payload" do
310
+ @valid_params[0].merge!(:foo => 'bar')
311
+ Urbanairship.batch_push(@valid_params)
312
+ request_json[0]['foo'].should be_nil
313
+ end
314
+
315
+ it "returns false if urbanairship responds with a non-200 response" do
316
+ Urbanairship.application_key = "my_app_key2"
317
+ Urbanairship.master_secret = "my_master_secret2"
318
+ Urbanairship.batch_push.should == false
319
+ end
320
+
321
+ end
322
+
323
+ describe "feedback service" do
324
+
325
+ before(:each) do
326
+ Urbanairship.application_key = "my_app_key"
327
+ Urbanairship.master_secret = "my_master_secret"
328
+ end
329
+
330
+ it "raises an error if call is made without an app key and master secret configured" do
331
+ Urbanairship.application_key = nil
332
+ Urbanairship.master_secret = nil
333
+
334
+ lambda {
335
+ Urbanairship.feedback(Time.now)
336
+ }.should raise_error(RuntimeError, "Must configure application_key, master_secret before making this request.")
337
+ end
338
+
339
+ it "uses app key and secret to sign the request" do
340
+ Urbanairship.feedback(Time.now)
341
+ FakeWeb.last_request['authorization'].should == "Basic #{Base64::encode64('my_app_key:my_master_secret').chomp}"
342
+ end
343
+
344
+ it "encodes the time argument in UTC, ISO 8601 format" do
345
+ time = Time.parse("October 10, 2010, 8:00pm")
346
+ formatted_time = time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
347
+ Urbanairship.feedback(time)
348
+ FakeWeb.last_request.path.should include(formatted_time)
349
+ end
350
+
351
+ it "accepts a string as the time argument" do
352
+ Urbanairship.feedback("Oct 07, 2010 8:00AM UTC")
353
+ FakeWeb.last_request.path.should include("2010-10-07T08:00:00Z")
354
+ end
355
+
356
+ it "returns an array of responses from the feedback API" do
357
+ response = Urbanairship.feedback(Time.now)
358
+ response.class.should == Array
359
+ response[0].keys.should include("device_token")
360
+ response[0].keys.should include("marked_inactive_on")
361
+ response[0].keys.should include("alias")
362
+ end
363
+
364
+ it "returns false and doesn't parse JSON when the call doesn't return 200" do
365
+ Urbanairship.application_key = "my_app_key2"
366
+ Urbanairship.master_secret = "my_master_secret2"
367
+ JSON.should_not_receive(:parse)
368
+ Urbanairship.feedback(Time.now).should == false
369
+ end
370
+
371
+ end
372
+
373
+ describe "logging" do
374
+
375
+ before(:each) do
376
+ @logger = mock("logger", :info => true)
377
+ Urbanairship.application_key = "my_app_key"
378
+ Urbanairship.application_secret = "my_app_secret"
379
+ Urbanairship.master_secret = "my_master_secret"
380
+ Urbanairship.logger = @logger
381
+ end
382
+
383
+ it "logs request and response information when registering a device" do
384
+ @logger.should_receive(:info).with(/\/api\/device_tokens\/new_device_token/)
385
+ Urbanairship.register_device('new_device_token')
386
+ end
387
+
388
+ it "logs request and response information when sending push notifications" do
389
+ @logger.should_receive(:info).with(/\/api\/push/)
390
+ Urbanairship.push(:device_tokens => ["device_token"], :aps => {:alert => "foo"})
391
+ end
392
+
393
+ it "logs request and response information when sending batch push notifications" do
394
+ @logger.should_receive(:info).with(/\/api\/push\/batch/)
395
+ Urbanairship.batch_push([:device_tokens => ["device_token"], :aps => {:alert => "foo"}])
396
+ end
397
+
398
+ it "logs request and response information when sending feedback requests" do
399
+ @logger.should_receive(:info).with(/\/api\/device_tokens\/feedback/)
400
+ Urbanairship.feedback(Time.now)
401
+ end
402
+
403
+ it "flushes the logger buffer if it's an ActiveSupport::BufferedLogger (Default Rails logger)" do
404
+ @logger.stub(:flush).and_return("message in the buffer\n")
405
+ @logger.should_receive(:flush)
406
+ Urbanairship.feedback(Time.now)
407
+ end
408
+
409
+ end
410
+
411
+ describe "request timeout" do
412
+ before(:each) do
413
+ @logger = mock("logger", :info => true)
414
+ Urbanairship.application_key = "my_app_key"
415
+ Urbanairship.application_secret = "my_app_secret"
416
+ Urbanairship.master_secret = "my_master_secret"
417
+ Urbanairship.logger = @logger
418
+ end
419
+
420
+ it "uses a default request_timeout value of five seconds" do
421
+ SystemTimer.should_receive(:timeout_after).with(5.0).and_raise(Timeout::Error)
422
+ @logger.should_receive(:error).with(/Urbanairship request timed out/)
423
+
424
+ Urbanairship.register_device('new_device_token')
425
+ end
426
+
427
+ it "accepts a configured request_timeout value" do
428
+ SystemTimer.should_receive(:timeout_after).with(1.23).and_raise(Timeout::Error)
429
+ @logger.should_receive(:error).with(/Urbanairship request timed out/)
430
+
431
+ Urbanairship.request_timeout = 1.23
432
+ Urbanairship.register_device('new_device_token')
433
+ end
434
+ end
435
+
436
+ end
437
+
438
+ def request_json
439
+ JSON.parse FakeWeb.last_request.body
440
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: urbanairship
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Groupon, Inc.
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-04 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: system_timer
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: fakeweb
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :development
76
+ version_requirements: *id004
77
+ description: Urbanairship is a Ruby library for interacting with the Urbanairship (http://urbanairship.com) API.
78
+ email:
79
+ - rubygems@groupon.com
80
+ executables: []
81
+
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - README.markdown
88
+ - LICENSE
89
+ - Rakefile
90
+ - lib/urbanairship.rb
91
+ - spec/spec_helper.rb
92
+ - spec/urbanairship_spec.rb
93
+ has_rdoc: true
94
+ homepage: http://github.com/groupon/urbanairship
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ hash: 59
108
+ segments:
109
+ - 1
110
+ - 8
111
+ - 6
112
+ version: 1.8.6
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ requirements: []
123
+
124
+ rubyforge_project:
125
+ rubygems_version: 1.3.7
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: A Ruby wrapper for the Urbanairship API
129
+ test_files:
130
+ - spec/spec_helper.rb
131
+ - spec/urbanairship_spec.rb