rpush 1.0.0-java
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 +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +7 -0
- data/README.md +189 -0
- data/bin/rpush +36 -0
- data/config/database.yml +44 -0
- data/lib/generators/rpush_generator.rb +44 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +102 -0
- data/lib/generators/templates/add_rpush.rb +349 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +18 -0
- data/lib/generators/templates/create_rapns_notifications.rb +29 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
- data/lib/generators/templates/rpush.rb +104 -0
- data/lib/rpush/TODO +3 -0
- data/lib/rpush/adm/app.rb +15 -0
- data/lib/rpush/adm/data_validator.rb +11 -0
- data/lib/rpush/adm/notification.rb +29 -0
- data/lib/rpush/apns/app.rb +29 -0
- data/lib/rpush/apns/binary_notification_validator.rb +12 -0
- data/lib/rpush/apns/device_token_format_validator.rb +12 -0
- data/lib/rpush/apns/feedback.rb +16 -0
- data/lib/rpush/apns/notification.rb +84 -0
- data/lib/rpush/apns_feedback.rb +13 -0
- data/lib/rpush/app.rb +18 -0
- data/lib/rpush/configuration.rb +75 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rpush/daemon/apns/delivery.rb +64 -0
- data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
- data/lib/rpush/daemon/apns.rb +16 -0
- data/lib/rpush/daemon/app_runner.rb +187 -0
- data/lib/rpush/daemon/batch.rb +115 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +28 -0
- data/lib/rpush/daemon/delivery_error.rb +19 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
- data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
- data/lib/rpush/daemon/feeder.rb +68 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
- data/lib/rpush/daemon/loggable.rb +31 -0
- data/lib/rpush/daemon/reflectable.rb +13 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +20 -0
- data/lib/rpush/daemon/service_config_methods.rb +33 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
- data/lib/rpush/daemon/store/active_record.rb +154 -0
- data/lib/rpush/daemon/tcp_connection.rb +143 -0
- data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon.rb +140 -0
- data/lib/rpush/deprecatable.rb +23 -0
- data/lib/rpush/deprecation.rb +23 -0
- data/lib/rpush/embed.rb +28 -0
- data/lib/rpush/gcm/app.rb +11 -0
- data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rpush/gcm/notification.rb +30 -0
- data/lib/rpush/logger.rb +63 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/notification.rb +69 -0
- data/lib/rpush/notifier.rb +52 -0
- data/lib/rpush/payload_data_size_validator.rb +10 -0
- data/lib/rpush/push.rb +16 -0
- data/lib/rpush/railtie.rb +11 -0
- data/lib/rpush/reflection.rb +58 -0
- data/lib/rpush/registration_ids_count_validator.rb +10 -0
- data/lib/rpush/version.rb +3 -0
- data/lib/rpush/wpns/app.rb +9 -0
- data/lib/rpush/wpns/notification.rb +26 -0
- data/lib/rpush.rb +62 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/rpush.rake +16 -0
- data/lib/tasks/test.rake +38 -0
- data/spec/functional/adm_spec.rb +43 -0
- data/spec/functional/apns_spec.rb +58 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +42 -0
- data/spec/functional/wpns_spec.rb +41 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/install.sh +32 -0
- data/spec/support/simplecov_helper.rb +20 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/adm/app_spec.rb +58 -0
- data/spec/unit/adm/notification_spec.rb +45 -0
- data/spec/unit/apns/app_spec.rb +29 -0
- data/spec/unit/apns/feedback_spec.rb +9 -0
- data/spec/unit/apns/notification_spec.rb +208 -0
- data/spec/unit/apns_feedback_spec.rb +21 -0
- data/spec/unit/app_spec.rb +30 -0
- data/spec/unit/configuration_spec.rb +45 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
- data/spec/unit/daemon/app_runner_spec.rb +292 -0
- data/spec/unit/daemon/batch_spec.rb +232 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
- data/spec/unit/daemon/feeder_spec.rb +98 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
- data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
- data/spec/unit/daemon_spec.rb +159 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +50 -0
- data/spec/unit/gcm/app_spec.rb +4 -0
- data/spec/unit/gcm/notification_spec.rb +36 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +105 -0
- data/spec/unit/notification_spec.rb +15 -0
- data/spec/unit/notifier_spec.rb +49 -0
- data/spec/unit/push_spec.rb +43 -0
- data/spec/unit/reflection_spec.rb +30 -0
- data/spec/unit/rpush_spec.rb +9 -0
- data/spec/unit/wpns/app_spec.rb +4 -0
- data/spec/unit/wpns/notification_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +101 -0
- metadata +304 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Adm::Delivery do
|
4
|
+
let(:app) { Rpush::Adm::App.new(:name => 'MyApp', :client_id => 'CLIENT_ID', :client_secret => 'CLIENT_SECRET') }
|
5
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['xyz'], :deliver_after => Time.now, :data => {'message' => 'test'}) }
|
6
|
+
let(:logger) { double(:error => nil, :info => nil, :warn => nil) }
|
7
|
+
let(:response) { double(:code => 200, :header => {}) }
|
8
|
+
let(:http) { double(:shutdown => nil, :request => response)}
|
9
|
+
let(:now) { Time.parse('2012-10-14 00:00:00') }
|
10
|
+
let(:batch) { double(:mark_failed => nil, :mark_delivered => nil, :mark_retryable => nil) }
|
11
|
+
let(:delivery) { Rpush::Daemon::Adm::Delivery.new(app, http, notification, batch) }
|
12
|
+
let(:store) { double(:create_adm_notification => double(:id => 2)) }
|
13
|
+
|
14
|
+
def perform
|
15
|
+
delivery.perform
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
app.access_token = 'ACCESS_TOKEN'
|
20
|
+
app.access_token_expiration = Time.now + 1.month
|
21
|
+
|
22
|
+
delivery.stub(:reflect => nil)
|
23
|
+
Rpush::Daemon.stub(:store => store)
|
24
|
+
Time.stub(:now => now)
|
25
|
+
Rpush.stub(:logger => logger)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'unknown error response' do
|
29
|
+
before do
|
30
|
+
response.stub(:code => 408)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
34
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidData' }))
|
35
|
+
delivery.should_receive(:mark_failed).with(408, 'Request Timeout')
|
36
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'a 200 (Ok) response' do
|
41
|
+
before do
|
42
|
+
response.stub(:code => 200)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'marks the notification as delivered if delivered successfully to all devices' do
|
46
|
+
response.stub(:body => JSON.dump({ 'registrationID' => 'xyz' }))
|
47
|
+
delivery.should_receive(:mark_delivered)
|
48
|
+
perform
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'logs that the notification was delivered' do
|
52
|
+
response.stub(:body => JSON.dump({ 'registrationID' => 'xyz' }))
|
53
|
+
logger.should_receive(:info).with("[MyApp] #{notification.id} sent to xyz")
|
54
|
+
perform
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'reflects on canonical IDs' do
|
58
|
+
response.stub(:body => JSON.dump({'registrationID' => 'canonical123' }))
|
59
|
+
notification.stub(:registration_ids => ['1'])
|
60
|
+
delivery.should_receive(:reflect).with(:adm_canonical_id, '1', 'canonical123')
|
61
|
+
perform
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'a 400 (Bad Request) response' do
|
66
|
+
before do
|
67
|
+
response.stub(:code => 400)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
71
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidData' }))
|
72
|
+
delivery.should_receive(:mark_failed).with(nil, 'Failed to deliver to all recipients.')
|
73
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'logs that the notification was not delivered' do
|
77
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidRegistrationId' }))
|
78
|
+
logger.should_receive(:warn).with("[MyApp] bad_request: xyz (InvalidRegistrationId)")
|
79
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'a 401 (Unauthorized) response' do
|
84
|
+
let(:http) { double(:shutdown => nil)}
|
85
|
+
let(:token_response) { double(:code => 200, :header => {}, :body => JSON.dump({'access_token' => 'ACCESS_TOKEN', 'expires_in' => 60})) }
|
86
|
+
|
87
|
+
before do
|
88
|
+
response.stub(:code => 401, :header => { 'retry-after' => 10 })
|
89
|
+
|
90
|
+
# first request to deliver message that returns unauthorized response
|
91
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
92
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should retrieve a new access token and mark the notification for retry' do
|
96
|
+
# request for access token
|
97
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
98
|
+
|
99
|
+
store.should_receive(:update_app).with(notification.app)
|
100
|
+
delivery.should_receive(:mark_retryable).with(notification, now)
|
101
|
+
|
102
|
+
perform
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should update the app with the new access token' do
|
106
|
+
# request for access token
|
107
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
108
|
+
|
109
|
+
store.should_receive(:update_app).with do |app|
|
110
|
+
app.access_token.should eq 'ACCESS_TOKEN'
|
111
|
+
app.access_token_expiration.should eq now + 60.seconds
|
112
|
+
end
|
113
|
+
delivery.should_receive(:mark_retryable).with(notification, now)
|
114
|
+
|
115
|
+
perform
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should log the error and stop retrying if new access token can\'t be retrieved' do
|
119
|
+
token_response.stub(:code => 404, :body => "test")
|
120
|
+
# request for access token
|
121
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
122
|
+
|
123
|
+
store.should_not_receive(:update_app).with(notification.app)
|
124
|
+
delivery.should_not_receive(:mark_retryable)
|
125
|
+
|
126
|
+
logger.should_receive(:warn).with("[MyApp] Could not retrieve access token from ADM: test")
|
127
|
+
|
128
|
+
perform
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'a 429 (Too Many Request) response' do
|
133
|
+
let(:http) { double(:shutdown => nil) }
|
134
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['abc','xyz'], :deliver_after => Time.now, :collapse_key => 'sync', :data => {'message' => 'test'}) }
|
135
|
+
let(:too_many_request_response) { double(:code => 429, :header => { 'retry-after' => 3600 }) }
|
136
|
+
|
137
|
+
it 'should retry the entire notification respecting the Retry-After header if none sent out yet' do
|
138
|
+
response.stub(:code => 429, :header => { 'retry-after' => 3600 })
|
139
|
+
|
140
|
+
# first request to deliver message that returns too many request response
|
141
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
142
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
143
|
+
|
144
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 1.hour)
|
145
|
+
perform
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should retry the entire notification using exponential backoff' do
|
149
|
+
response.stub(:code => 429, :header => {})
|
150
|
+
|
151
|
+
# first request to deliver message that returns too many request response
|
152
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
153
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
154
|
+
|
155
|
+
delivery.should_receive(:mark_retryable).with(notification, Time.now + 2 ** (notification.retries + 1))
|
156
|
+
perform
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should keep sent reg ids in original notification and create new notification with remaining reg ids for retry' do
|
160
|
+
response.stub(:code => 200, :body => JSON.dump({ 'registrationID' => 'abc' }))
|
161
|
+
|
162
|
+
# first request to deliver message succeeds
|
163
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['abc'])
|
164
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
165
|
+
|
166
|
+
# first request to deliver message that returns too many request response
|
167
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['xyz'])
|
168
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(too_many_request_response)
|
169
|
+
|
170
|
+
store.should_receive(:update_notification).with do |notif|
|
171
|
+
notif.registration_ids.include?('abc').should be_true
|
172
|
+
notif.registration_ids.include?('xyz').should be_false
|
173
|
+
end
|
174
|
+
|
175
|
+
store.should_receive(:create_adm_notification).with do |attrs, notification_data, reg_ids, deliver_after, notification_app|
|
176
|
+
attrs.has_key?('collapse_key').should be_true
|
177
|
+
attrs.has_key?('delay_while_idle').should be_true
|
178
|
+
attrs.has_key?('app_id').should be_true
|
179
|
+
|
180
|
+
reg_ids.should eq ['xyz']
|
181
|
+
deliver_after.should eq now + 1.hour
|
182
|
+
notification_app.should eq notification.app
|
183
|
+
end
|
184
|
+
|
185
|
+
delivery.should_receive(:mark_delivered)
|
186
|
+
|
187
|
+
perform
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'a 500 (Internal Server Error) response' do
|
192
|
+
before do
|
193
|
+
response.stub(:code => 500)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
197
|
+
delivery.should_receive(:mark_failed).with(nil, 'Failed to deliver to all recipients.')
|
198
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'logs that the notification was not delivered' do
|
202
|
+
logger.should_receive(:warn).with("[MyApp] internal_server_error: xyz (Internal Server Error)")
|
203
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe 'a 503 (Service Unavailable) response' do
|
208
|
+
before do
|
209
|
+
response.stub(:code => 503, :header => { 'retry-after' => 10 })
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should retry the notification respecting the Retry-After header' do
|
213
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 10.seconds)
|
214
|
+
perform
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe 'some registration ids succeeding and some failing' do
|
219
|
+
let(:http) { double(:shutdown => nil) }
|
220
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['abc','xyz'], :deliver_after => Time.now, :collapse_key => 'sync', :data => {'message' => 'test'}) }
|
221
|
+
let(:bad_request_response) { double(:code => 400, :body => JSON.dump({ 'reason' => 'InvalidData' })) }
|
222
|
+
|
223
|
+
it 'should keep sent reg ids in original notification and create new notification with remaining reg ids for retry' do
|
224
|
+
response.stub(:code => 200, :body => JSON.dump({ 'registrationID' => 'abc' }))
|
225
|
+
|
226
|
+
# first request to deliver message succeeds
|
227
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['abc'])
|
228
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
229
|
+
|
230
|
+
# first request to deliver message that returns too many request response
|
231
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['xyz'])
|
232
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(bad_request_response)
|
233
|
+
|
234
|
+
store.should_receive(:update_notification).with do |notif|
|
235
|
+
notif.error_description.should eq "Failed to deliver to recipients: \nxyz: InvalidData"
|
236
|
+
end
|
237
|
+
|
238
|
+
delivery.should_receive(:mark_delivered)
|
239
|
+
|
240
|
+
perform
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Apns::CertificateExpiredError do
|
4
|
+
let(:app) { double(:name => 'test') }
|
5
|
+
let(:error) { Rpush::Apns::CertificateExpiredError.new(app, Time.now) }
|
6
|
+
|
7
|
+
it 'returns a message' do
|
8
|
+
error.message
|
9
|
+
error.to_s
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Apns::Delivery do
|
4
|
+
let(:app) { double(:name => 'MyApp') }
|
5
|
+
let(:notification) { double.as_null_object }
|
6
|
+
let(:batch) { double(:mark_failed => nil, :mark_delivered => nil) }
|
7
|
+
let(:logger) { double(:error => nil, :info => nil) }
|
8
|
+
let(:config) { double(:check_for_errors => true) }
|
9
|
+
let(:connection) { double(:select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
|
10
|
+
let(:delivery) { Rpush::Daemon::Apns::Delivery.new(app, connection, notification, batch) }
|
11
|
+
|
12
|
+
def perform
|
13
|
+
begin
|
14
|
+
delivery.perform
|
15
|
+
rescue Rpush::DeliveryError, Rpush::Apns::DisconnectionError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
before do
|
20
|
+
Rpush.stub(:config => config, :logger => logger)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "sends the binary version of the notification" do
|
24
|
+
notification.stub(:to_binary => "hi mom")
|
25
|
+
connection.should_receive(:write).with("hi mom")
|
26
|
+
perform
|
27
|
+
end
|
28
|
+
|
29
|
+
it "logs the notification delivery" do
|
30
|
+
notification.stub(:id => 666, :device_token => 'abc123')
|
31
|
+
logger.should_receive(:info).with("[MyApp] 666 sent to abc123")
|
32
|
+
perform
|
33
|
+
end
|
34
|
+
|
35
|
+
it "marks the notification as delivered" do
|
36
|
+
delivery.should_receive(:mark_delivered)
|
37
|
+
perform
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'does not check for errors if check_for_errors config option is false' do
|
41
|
+
config.stub(:check_for_errors => false)
|
42
|
+
delivery.should_not_receive(:check_for_error)
|
43
|
+
perform
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when delivery fails" do
|
47
|
+
before { connection.stub(:select => true, :read => [8, 4, 69].pack("ccN")) }
|
48
|
+
|
49
|
+
it "marks the notification as failed" do
|
50
|
+
delivery.should_receive(:mark_failed).with(4, "Missing payload")
|
51
|
+
perform
|
52
|
+
end
|
53
|
+
|
54
|
+
it "logs the delivery error" do
|
55
|
+
# checking for the doublebed error doesn't work in jruby, but checking
|
56
|
+
# for the exception by class does.
|
57
|
+
|
58
|
+
#error = Rpush::DeliveryError.new(4, 12, "Missing payload")
|
59
|
+
#Rpush::DeliveryError.stub(:new => error)
|
60
|
+
#expect { delivery.perform }.to raise_error(error)
|
61
|
+
|
62
|
+
expect { delivery.perform }.to raise_error(Rpush::DeliveryError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "reads 6 bytes from the socket" do
|
66
|
+
connection.should_receive(:read).with(6).and_return(nil)
|
67
|
+
perform
|
68
|
+
end
|
69
|
+
|
70
|
+
it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do
|
71
|
+
connection.stub(:select => nil)
|
72
|
+
connection.should_not_receive(:read)
|
73
|
+
perform
|
74
|
+
end
|
75
|
+
|
76
|
+
it "reconnects the socket" do
|
77
|
+
connection.should_receive(:reconnect)
|
78
|
+
perform
|
79
|
+
end
|
80
|
+
|
81
|
+
it "logs that the connection is being reconnected" do
|
82
|
+
Rpush.logger.should_receive(:error).with("[MyApp] Error received, reconnecting...")
|
83
|
+
perform
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the APNs disconnects without returning an error" do
|
87
|
+
before do
|
88
|
+
connection.stub(:read => nil)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'raises a DisconnectError error if the connection is closed without an error being returned' do
|
92
|
+
expect { delivery.perform }.to raise_error(Rpush::Apns::DisconnectionError)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'marks the notification as failed' do
|
96
|
+
delivery.should_receive(:mark_failed).with(nil, "APNs disconnected without returning an error.")
|
97
|
+
perform
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Apns::DisconnectionError do
|
4
|
+
let(:error) { Rpush::Apns::DisconnectionError.new }
|
5
|
+
|
6
|
+
it 'returns a nil error code' do
|
7
|
+
error.code.should be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'contains an error description' do
|
11
|
+
error.description
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a message' do
|
15
|
+
error.message
|
16
|
+
error.to_s
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "unit_spec_helper"
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
4
|
+
|
5
|
+
let(:host) { 'feedback.push.apple.com' }
|
6
|
+
let(:port) { 2196 }
|
7
|
+
let(:poll) { 60 }
|
8
|
+
let(:certificate) { double }
|
9
|
+
let(:password) { double }
|
10
|
+
let(:app) { double(:name => 'my_app', :password => password, :certificate => certificate, :environment => 'production') }
|
11
|
+
let(:connection) { double(:connect => nil, :read => nil, :close => nil) }
|
12
|
+
let(:logger) { double(:error => nil, :info => nil) }
|
13
|
+
let(:receiver) { Rpush::Daemon::Apns::FeedbackReceiver.new(app) }
|
14
|
+
let(:feedback) { double }
|
15
|
+
let(:sleeper) { double(Rpush::Daemon::InterruptibleSleep, :sleep => nil, :interrupt_sleep => nil) }
|
16
|
+
let(:store) { double(Rpush::Daemon::Store::ActiveRecord,
|
17
|
+
create_apns_feedback: feedback, release_connection: nil) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
Rpush.config.feedback_poll = poll
|
21
|
+
Rpush::Daemon::InterruptibleSleep.stub(:new => sleeper)
|
22
|
+
Rpush.stub(:logger => logger)
|
23
|
+
Rpush::Daemon::TcpConnection.stub(:new => connection)
|
24
|
+
receiver.instance_variable_set("@stop", false)
|
25
|
+
Rpush::Daemon.stub(:store => store)
|
26
|
+
end
|
27
|
+
|
28
|
+
def double_connection_read_with_tuple
|
29
|
+
connection.unstub(:read)
|
30
|
+
|
31
|
+
def connection.read(bytes)
|
32
|
+
if !@called
|
33
|
+
@called = true
|
34
|
+
"N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'instantiates a new connection' do
|
40
|
+
Rpush::Daemon::TcpConnection.should_receive(:new).with(app, host, port)
|
41
|
+
receiver.check_for_feedback
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'connects to the feeback service' do
|
45
|
+
connection.should_receive(:connect)
|
46
|
+
receiver.check_for_feedback
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'closes the connection' do
|
50
|
+
connection.should_receive(:close)
|
51
|
+
receiver.check_for_feedback
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'reads from the connection' do
|
55
|
+
connection.should_receive(:read).with(38)
|
56
|
+
receiver.check_for_feedback
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'logs the feedback' do
|
60
|
+
double_connection_read_with_tuple
|
61
|
+
Rpush.logger.should_receive(:info).with("[my_app] [FeedbackReceiver] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17.")
|
62
|
+
receiver.check_for_feedback
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'creates the feedback' do
|
66
|
+
Rpush::Daemon.store.should_receive(:create_apns_feedback).with(Time.at(1323533325), '834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17', app)
|
67
|
+
double_connection_read_with_tuple
|
68
|
+
receiver.check_for_feedback
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'logs errors' do
|
72
|
+
error = StandardError.new('bork!')
|
73
|
+
connection.stub(:read).and_raise(error)
|
74
|
+
Rpush.logger.should_receive(:error).with(error)
|
75
|
+
receiver.check_for_feedback
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'start' do
|
79
|
+
before do
|
80
|
+
Thread.stub(:new).and_yield
|
81
|
+
receiver.stub(:loop).and_yield
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'sleeps for the feedback poll period' do
|
85
|
+
receiver.stub(:check_for_feedback)
|
86
|
+
sleeper.should_receive(:sleep).with(60).at_least(:once)
|
87
|
+
receiver.start
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'checks for feedback when started' do
|
91
|
+
receiver.should_receive(:check_for_feedback).at_least(:once)
|
92
|
+
receiver.start
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe 'stop' do
|
97
|
+
it 'interrupts sleep when stopped' do
|
98
|
+
receiver.stub(:check_for_feedback)
|
99
|
+
sleeper.should_receive(:interrupt_sleep)
|
100
|
+
receiver.stop
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'releases the store connection' do
|
104
|
+
Thread.stub(:new).and_yield
|
105
|
+
receiver.stub(:loop).and_yield
|
106
|
+
Rpush::Daemon.store.should_receive(:release_connection)
|
107
|
+
receiver.start
|
108
|
+
receiver.stop
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'reflects feedback was received' do
|
113
|
+
double_connection_read_with_tuple
|
114
|
+
receiver.should_receive(:reflect).with(:apns_feedback, feedback)
|
115
|
+
receiver.check_for_feedback
|
116
|
+
end
|
117
|
+
end
|