rpush 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.
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.rb +62 -0
  21. data/lib/rpush/TODO +3 -0
  22. data/lib/rpush/adm/app.rb +15 -0
  23. data/lib/rpush/adm/data_validator.rb +11 -0
  24. data/lib/rpush/adm/notification.rb +29 -0
  25. data/lib/rpush/apns/app.rb +29 -0
  26. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  27. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  28. data/lib/rpush/apns/feedback.rb +16 -0
  29. data/lib/rpush/apns/notification.rb +84 -0
  30. data/lib/rpush/apns_feedback.rb +13 -0
  31. data/lib/rpush/app.rb +18 -0
  32. data/lib/rpush/configuration.rb +75 -0
  33. data/lib/rpush/daemon.rb +140 -0
  34. data/lib/rpush/daemon/adm.rb +9 -0
  35. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  36. data/lib/rpush/daemon/apns.rb +16 -0
  37. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  38. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  39. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  40. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  41. data/lib/rpush/daemon/app_runner.rb +187 -0
  42. data/lib/rpush/daemon/batch.rb +115 -0
  43. data/lib/rpush/daemon/constants.rb +59 -0
  44. data/lib/rpush/daemon/delivery.rb +28 -0
  45. data/lib/rpush/daemon/delivery_error.rb +19 -0
  46. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  47. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  48. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  49. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  50. data/lib/rpush/daemon/feeder.rb +68 -0
  51. data/lib/rpush/daemon/gcm.rb +9 -0
  52. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  53. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  54. data/lib/rpush/daemon/loggable.rb +31 -0
  55. data/lib/rpush/daemon/reflectable.rb +13 -0
  56. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  57. data/lib/rpush/daemon/retryable_error.rb +20 -0
  58. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  59. data/lib/rpush/daemon/store/active_record.rb +154 -0
  60. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  61. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  62. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  63. data/lib/rpush/daemon/wpns.rb +9 -0
  64. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  65. data/lib/rpush/deprecatable.rb +23 -0
  66. data/lib/rpush/deprecation.rb +23 -0
  67. data/lib/rpush/embed.rb +28 -0
  68. data/lib/rpush/gcm/app.rb +11 -0
  69. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  70. data/lib/rpush/gcm/notification.rb +30 -0
  71. data/lib/rpush/logger.rb +63 -0
  72. data/lib/rpush/multi_json_helper.rb +16 -0
  73. data/lib/rpush/notification.rb +69 -0
  74. data/lib/rpush/notifier.rb +52 -0
  75. data/lib/rpush/payload_data_size_validator.rb +10 -0
  76. data/lib/rpush/push.rb +16 -0
  77. data/lib/rpush/railtie.rb +11 -0
  78. data/lib/rpush/reflection.rb +58 -0
  79. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  80. data/lib/rpush/version.rb +3 -0
  81. data/lib/rpush/wpns/app.rb +9 -0
  82. data/lib/rpush/wpns/notification.rb +26 -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 +276 -0
@@ -0,0 +1,21 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush, 'apns_feedback' do
4
+ let!(:app) { Rpush::Apns::App.create!(:name => 'test', :environment => 'production', :certificate => TEST_CERT) }
5
+ let(:receiver) { double(:check_for_feedback => nil) }
6
+
7
+ before do
8
+ Rpush::Daemon::Apns::FeedbackReceiver.stub(:new => receiver)
9
+ end
10
+
11
+ it 'initializes the store' do
12
+ Rpush::Daemon.should_receive(:initialize_store)
13
+ Rpush.apns_feedback
14
+ end
15
+
16
+ it 'checks feedback for each app' do
17
+ Rpush::Daemon::Apns::FeedbackReceiver.should_receive(:new).with(app).and_return(receiver)
18
+ receiver.should_receive(:check_for_feedback)
19
+ Rpush.apns_feedback
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::App do
4
+ it 'validates the uniqueness of name within type and environment' do
5
+ Rpush::Apns::App.create!(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
6
+ app = Rpush::Apns::App.new(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
7
+ app.valid?.should be_false
8
+ app.errors[:name].should eq ['has already been taken']
9
+
10
+ app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => TEST_CERT)
11
+ app.valid?.should be_true
12
+
13
+ app = Rpush::Gcm::App.new(:name => 'test', :environment => 'production', :auth_key => TEST_CERT)
14
+ app.valid?.should be_true
15
+ end
16
+
17
+ context 'validating certificates' do
18
+ it 'rescues from certificate error' do
19
+ app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => 'bad')
20
+ expect{app.valid?}.not_to raise_error
21
+ expect(app.valid?).to be_false
22
+ end
23
+
24
+ it 'raises other errors' do
25
+ app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => 'bad')
26
+ OpenSSL::X509::Certificate.stub(:new).and_raise(NameError, 'simulating no openssl')
27
+ expect{app.valid?}.to raise_error(NameError)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush do
4
+ let(:config) { double }
5
+
6
+ before { Rpush.stub(:config => config) }
7
+
8
+ it 'can yields a config block' do
9
+ expect { |b| Rpush.configure(&b) }.to yield_with_args(config)
10
+ end
11
+ end
12
+
13
+ describe Rpush::Configuration do
14
+ let(:config) do
15
+ Rpush::Deprecation.muted do
16
+ Rpush::Configuration.new
17
+ end
18
+ end
19
+
20
+ it 'can be updated' do
21
+ Rpush::Deprecation.muted do
22
+ new_config = Rpush::Configuration.new
23
+ new_config.batch_size = 200
24
+ expect { config.update(new_config) }.to change(config, :batch_size).to(200)
25
+ end
26
+ end
27
+
28
+ it 'sets the pid_file relative if not absolute' do
29
+ Rails.stub(:root => '/rails')
30
+ config.pid_file = 'tmp/rpush.pid'
31
+ config.pid_file.should eq '/rails/tmp/rpush.pid'
32
+ end
33
+
34
+ it 'does not alter an absolute pid_file path' do
35
+ config.pid_file = '/tmp/rpush.pid'
36
+ config.pid_file.should eq '/tmp/rpush.pid'
37
+ end
38
+
39
+ it 'does not allow foreground to be set to false if the platform is JRuby' do
40
+ config.foreground = true
41
+ Rpush.stub(:jruby? => true)
42
+ config.foreground = false
43
+ config.foreground.should be_true
44
+ end
45
+ end
@@ -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