urbanairship 1.0.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.
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