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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +99 -0
  3. data/LICENSE +7 -0
  4. data/README.md +189 -0
  5. data/bin/rpush +36 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rpush_generator.rb +44 -0
  8. data/lib/generators/templates/add_adm.rb +23 -0
  9. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  10. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  11. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
  12. data/lib/generators/templates/add_gcm.rb +102 -0
  13. data/lib/generators/templates/add_rpush.rb +349 -0
  14. data/lib/generators/templates/add_wpns.rb +16 -0
  15. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  16. data/lib/generators/templates/create_rapns_feedback.rb +18 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +29 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
  19. data/lib/generators/templates/rpush.rb +104 -0
  20. data/lib/rpush/TODO +3 -0
  21. data/lib/rpush/adm/app.rb +15 -0
  22. data/lib/rpush/adm/data_validator.rb +11 -0
  23. data/lib/rpush/adm/notification.rb +29 -0
  24. data/lib/rpush/apns/app.rb +29 -0
  25. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  26. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  27. data/lib/rpush/apns/feedback.rb +16 -0
  28. data/lib/rpush/apns/notification.rb +84 -0
  29. data/lib/rpush/apns_feedback.rb +13 -0
  30. data/lib/rpush/app.rb +18 -0
  31. data/lib/rpush/configuration.rb +75 -0
  32. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  33. data/lib/rpush/daemon/adm.rb +9 -0
  34. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  35. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  36. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  37. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  38. data/lib/rpush/daemon/apns.rb +16 -0
  39. data/lib/rpush/daemon/app_runner.rb +187 -0
  40. data/lib/rpush/daemon/batch.rb +115 -0
  41. data/lib/rpush/daemon/constants.rb +59 -0
  42. data/lib/rpush/daemon/delivery.rb +28 -0
  43. data/lib/rpush/daemon/delivery_error.rb +19 -0
  44. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  45. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  46. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  47. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  48. data/lib/rpush/daemon/feeder.rb +68 -0
  49. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  50. data/lib/rpush/daemon/gcm.rb +9 -0
  51. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  52. data/lib/rpush/daemon/loggable.rb +31 -0
  53. data/lib/rpush/daemon/reflectable.rb +13 -0
  54. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  55. data/lib/rpush/daemon/retryable_error.rb +20 -0
  56. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  57. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  58. data/lib/rpush/daemon/store/active_record.rb +154 -0
  59. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  60. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  61. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  62. data/lib/rpush/daemon/wpns.rb +9 -0
  63. data/lib/rpush/daemon.rb +140 -0
  64. data/lib/rpush/deprecatable.rb +23 -0
  65. data/lib/rpush/deprecation.rb +23 -0
  66. data/lib/rpush/embed.rb +28 -0
  67. data/lib/rpush/gcm/app.rb +11 -0
  68. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  69. data/lib/rpush/gcm/notification.rb +30 -0
  70. data/lib/rpush/logger.rb +63 -0
  71. data/lib/rpush/multi_json_helper.rb +16 -0
  72. data/lib/rpush/notification.rb +69 -0
  73. data/lib/rpush/notifier.rb +52 -0
  74. data/lib/rpush/payload_data_size_validator.rb +10 -0
  75. data/lib/rpush/push.rb +16 -0
  76. data/lib/rpush/railtie.rb +11 -0
  77. data/lib/rpush/reflection.rb +58 -0
  78. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  79. data/lib/rpush/version.rb +3 -0
  80. data/lib/rpush/wpns/app.rb +9 -0
  81. data/lib/rpush/wpns/notification.rb +26 -0
  82. data/lib/rpush.rb +62 -0
  83. data/lib/tasks/cane.rake +18 -0
  84. data/lib/tasks/rpush.rake +16 -0
  85. data/lib/tasks/test.rake +38 -0
  86. data/spec/functional/adm_spec.rb +43 -0
  87. data/spec/functional/apns_spec.rb +58 -0
  88. data/spec/functional/embed_spec.rb +49 -0
  89. data/spec/functional/gcm_spec.rb +42 -0
  90. data/spec/functional/wpns_spec.rb +41 -0
  91. data/spec/support/cert_with_password.pem +90 -0
  92. data/spec/support/cert_without_password.pem +59 -0
  93. data/spec/support/install.sh +32 -0
  94. data/spec/support/simplecov_helper.rb +20 -0
  95. data/spec/support/simplecov_quality_formatter.rb +8 -0
  96. data/spec/tmp/.gitkeep +0 -0
  97. data/spec/unit/adm/app_spec.rb +58 -0
  98. data/spec/unit/adm/notification_spec.rb +45 -0
  99. data/spec/unit/apns/app_spec.rb +29 -0
  100. data/spec/unit/apns/feedback_spec.rb +9 -0
  101. data/spec/unit/apns/notification_spec.rb +208 -0
  102. data/spec/unit/apns_feedback_spec.rb +21 -0
  103. data/spec/unit/app_spec.rb +30 -0
  104. data/spec/unit/configuration_spec.rb +45 -0
  105. data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
  106. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  107. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  108. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  109. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
  110. data/spec/unit/daemon/app_runner_spec.rb +292 -0
  111. data/spec/unit/daemon/batch_spec.rb +232 -0
  112. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  113. data/spec/unit/daemon/delivery_spec.rb +38 -0
  114. data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
  115. data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
  116. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
  117. data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
  118. data/spec/unit/daemon/feeder_spec.rb +98 -0
  119. data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
  120. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  121. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  122. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  123. data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
  124. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  125. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  126. data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
  127. data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
  128. data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
  129. data/spec/unit/daemon_spec.rb +159 -0
  130. data/spec/unit/deprecatable_spec.rb +32 -0
  131. data/spec/unit/deprecation_spec.rb +15 -0
  132. data/spec/unit/embed_spec.rb +50 -0
  133. data/spec/unit/gcm/app_spec.rb +4 -0
  134. data/spec/unit/gcm/notification_spec.rb +36 -0
  135. data/spec/unit/logger_spec.rb +127 -0
  136. data/spec/unit/notification_shared.rb +105 -0
  137. data/spec/unit/notification_spec.rb +15 -0
  138. data/spec/unit/notifier_spec.rb +49 -0
  139. data/spec/unit/push_spec.rb +43 -0
  140. data/spec/unit/reflection_spec.rb +30 -0
  141. data/spec/unit/rpush_spec.rb +9 -0
  142. data/spec/unit/wpns/app_spec.rb +4 -0
  143. data/spec/unit/wpns/notification_spec.rb +30 -0
  144. data/spec/unit_spec_helper.rb +101 -0
  145. 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