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,287 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Daemon::TcpConnection do
4
+ let(:rsa_key) { double }
5
+ let(:certificate) { double }
6
+ let(:password) { double }
7
+ let(:x509_certificate) { OpenSSL::X509::Certificate.new(TEST_CERT) }
8
+ let(:ssl_context) { double(:key= => nil, :cert= => nil, :cert => x509_certificate) }
9
+ let(:host) { 'gateway.push.apple.com' }
10
+ let(:port) { '2195' }
11
+ let(:tcp_socket) { double(:setsockopt => nil, :close => nil) }
12
+ let(:ssl_socket) { double(:sync= => nil, :connect => nil, :close => nil, :write => nil, :flush => nil) }
13
+ let(:logger) { double(:info => nil, :error => nil, :warn => nil) }
14
+ let(:app) { double(:name => 'Connection 0', :certificate => certificate, :password => password)}
15
+ let(:connection) { Rpush::Daemon::TcpConnection.new(app, host, port) }
16
+
17
+ before do
18
+ OpenSSL::SSL::SSLContext.stub(:new => ssl_context)
19
+ OpenSSL::PKey::RSA.stub(:new => rsa_key)
20
+ OpenSSL::X509::Certificate.stub(:new => x509_certificate)
21
+ TCPSocket.stub(:new => tcp_socket)
22
+ OpenSSL::SSL::SSLSocket.stub(:new => ssl_socket)
23
+ Rpush.stub(:logger => logger)
24
+ connection.stub(:reflect)
25
+ end
26
+
27
+ it "reads the number of bytes from the SSL socket" do
28
+ ssl_socket.should_receive(:read).with(123)
29
+ connection.connect
30
+ connection.read(123)
31
+ end
32
+
33
+ it "selects on the SSL socket until the given timeout" do
34
+ IO.should_receive(:select).with([ssl_socket], nil, nil, 10)
35
+ connection.connect
36
+ connection.select(10)
37
+ end
38
+
39
+ describe "when setting up the SSL context" do
40
+ it "sets the key on the context" do
41
+ OpenSSL::PKey::RSA.should_receive(:new).with(certificate, password).and_return(rsa_key)
42
+ ssl_context.should_receive(:key=).with(rsa_key)
43
+ connection.connect
44
+ end
45
+
46
+ it "sets the cert on the context" do
47
+ OpenSSL::X509::Certificate.should_receive(:new).with(certificate).and_return(x509_certificate)
48
+ ssl_context.should_receive(:cert=).with(x509_certificate)
49
+ connection.connect
50
+ end
51
+ end
52
+
53
+ describe "when connecting the socket" do
54
+ it "creates a TCP socket using the configured host and port" do
55
+ TCPSocket.should_receive(:new).with(host, port).and_return(tcp_socket)
56
+ connection.connect
57
+ end
58
+
59
+ it "creates a new SSL socket using the TCP socket and SSL context" do
60
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket)
61
+ connection.connect
62
+ end
63
+
64
+ it "sets the sync option on the SSL socket" do
65
+ ssl_socket.should_receive(:sync=).with(true)
66
+ connection.connect
67
+ end
68
+
69
+ it "connects the SSL socket" do
70
+ ssl_socket.should_receive(:connect)
71
+ connection.connect
72
+ end
73
+
74
+ it "sets the socket option TCP_NODELAY" do
75
+ tcp_socket.should_receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
76
+ connection.connect
77
+ end
78
+
79
+ it "sets the socket option SO_KEEPALIVE" do
80
+ tcp_socket.should_receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
81
+ connection.connect
82
+ end
83
+
84
+ describe 'certificate expiry' do
85
+ it 'reflects if the certificate will expire soon' do
86
+ cert = OpenSSL::X509::Certificate.new(app.certificate)
87
+ connection.should_receive(:reflect).with(:ssl_certificate_will_expire, app, cert.not_after)
88
+ Timecop.freeze(cert.not_after - 3.days) { connection.connect }
89
+ end
90
+
91
+ it 'logs that the certificate will expire soon' do
92
+ cert = OpenSSL::X509::Certificate.new(app.certificate)
93
+ logger.should_receive(:warn).with("[#{app.name}] Certificate will expire at 2022-09-07 03:18:32 UTC.")
94
+ Timecop.freeze(cert.not_after - 3.days) { connection.connect }
95
+ end
96
+
97
+ it 'does not reflect if the certificate will not expire soon' do
98
+ cert = OpenSSL::X509::Certificate.new(app.certificate)
99
+ connection.should_not_receive(:reflect).with(:ssl_certificate_will_expire, app, kind_of(Time))
100
+ Timecop.freeze(cert.not_after - 2.months) { connection.connect }
101
+ end
102
+
103
+ it 'logs that the certificate has expired' do
104
+ cert = OpenSSL::X509::Certificate.new(app.certificate)
105
+ logger.should_receive(:error).with("[#{app.name}] Certificate expired at 2022-09-07 03:18:32 UTC.")
106
+ Timecop.freeze(cert.not_after + 1.day) { connection.connect rescue Rpush::Apns::CertificateExpiredError }
107
+ end
108
+
109
+ it 'raises an error if the certificate has expired' do
110
+ cert = OpenSSL::X509::Certificate.new(app.certificate)
111
+ Timecop.freeze(cert.not_after + 1.day) do
112
+ expect { connection.connect }.to raise_error(Rpush::Apns::CertificateExpiredError)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "when shuting down the connection" do
119
+ it "closes the TCP socket" do
120
+ connection.connect
121
+ tcp_socket.should_receive(:close)
122
+ connection.close
123
+ end
124
+
125
+ it "does not attempt to close the TCP socket if it is not connected" do
126
+ connection.connect
127
+ tcp_socket.should_not_receive(:close)
128
+ connection.instance_variable_set("@tcp_socket", nil)
129
+ connection.close
130
+ end
131
+
132
+ it "closes the SSL socket" do
133
+ connection.connect
134
+ ssl_socket.should_receive(:close)
135
+ connection.close
136
+ end
137
+
138
+ it "does not attempt to close the SSL socket if it is not connected" do
139
+ connection.connect
140
+ ssl_socket.should_not_receive(:close)
141
+ connection.instance_variable_set("@ssl_socket", nil)
142
+ connection.close
143
+ end
144
+
145
+ it "ignores IOError when the socket is already closed" do
146
+ tcp_socket.stub(:close).and_raise(IOError)
147
+ connection.connect
148
+ connection.close
149
+ end
150
+ end
151
+
152
+ shared_examples_for "when the write fails" do
153
+ before do
154
+ connection.stub(:sleep)
155
+ connection.connect
156
+ ssl_socket.stub(:write).and_raise(error_type)
157
+ end
158
+
159
+ it 'reflects the connection has been lost' do
160
+ connection.should_receive(:reflect).with(:tcp_connection_lost, app, kind_of(error_type))
161
+ begin
162
+ connection.write(nil)
163
+ rescue Rpush::Daemon::TcpConnectionError
164
+ end
165
+ end
166
+
167
+ it "logs that the connection has been lost once only" do
168
+ logger.should_receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error_type.name}), reconnecting...").once
169
+ begin
170
+ connection.write(nil)
171
+ rescue Rpush::Daemon::TcpConnectionError
172
+ end
173
+ end
174
+
175
+ it "retries to make a connection 3 times" do
176
+ connection.should_receive(:reconnect).exactly(3).times
177
+ begin
178
+ connection.write(nil)
179
+ rescue Rpush::Daemon::TcpConnectionError
180
+ end
181
+ end
182
+
183
+ it "raises a TcpConnectionError after 3 attempts at reconnecting" do
184
+ expect do
185
+ connection.write(nil)
186
+ end.to raise_error(Rpush::Daemon::TcpConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error_type.name}).")
187
+ end
188
+
189
+ it "sleeps 1 second before retrying the connection" do
190
+ connection.should_receive(:sleep).with(1)
191
+ begin
192
+ connection.write(nil)
193
+ rescue Rpush::Daemon::TcpConnectionError
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "when write raises an Errno::EPIPE" do
199
+ it_should_behave_like "when the write fails"
200
+
201
+ def error_type
202
+ Errno::EPIPE
203
+ end
204
+ end
205
+
206
+ describe "when write raises an Errno::ETIMEDOUT" do
207
+ it_should_behave_like "when the write fails"
208
+
209
+ def error_type
210
+ Errno::ETIMEDOUT
211
+ end
212
+ end
213
+
214
+ describe "when write raises an OpenSSL::SSL::SSLError" do
215
+ it_should_behave_like "when the write fails"
216
+
217
+ def error_type
218
+ OpenSSL::SSL::SSLError
219
+ end
220
+ end
221
+
222
+ describe "when write raises an IOError" do
223
+ it_should_behave_like "when the write fails"
224
+
225
+ def error_type
226
+ IOError
227
+ end
228
+ end
229
+
230
+ describe "when reconnecting" do
231
+ before { connection.connect }
232
+
233
+ it 'closes the socket' do
234
+ connection.should_receive(:close)
235
+ connection.send(:reconnect)
236
+ end
237
+
238
+ it 'connects the socket' do
239
+ connection.should_receive(:connect_socket)
240
+ connection.send(:reconnect)
241
+ end
242
+ end
243
+
244
+ describe "when sending a notification" do
245
+ before { connection.connect }
246
+
247
+ it "writes the data to the SSL socket" do
248
+ ssl_socket.should_receive(:write).with("blah")
249
+ connection.write("blah")
250
+ end
251
+
252
+ it "flushes the SSL socket" do
253
+ ssl_socket.should_receive(:flush)
254
+ connection.write("blah")
255
+ end
256
+ end
257
+
258
+ describe 'idle period' do
259
+ before { connection.connect }
260
+
261
+ it 'reconnects if the connection has been idle for more than the defined period' do
262
+ Rpush::Daemon::TcpConnection.stub(:idle_period => 60)
263
+ Time.stub(:now => Time.now + 61)
264
+ connection.should_receive(:reconnect)
265
+ connection.write('blah')
266
+ end
267
+
268
+ it 'resets the last write time' do
269
+ now = Time.now
270
+ Time.stub(:now => now)
271
+ connection.write('blah')
272
+ connection.last_write.should eq now
273
+ end
274
+
275
+ it 'does not reconnect if the connection has not been idle for more than the defined period' do
276
+ connection.should_not_receive(:reconnect)
277
+ connection.write('blah')
278
+ end
279
+
280
+ it 'logs the the connection is idle' do
281
+ Rpush::Daemon::TcpConnection.stub(:idle_period => 60)
282
+ Time.stub(:now => Time.now + 61)
283
+ Rpush.logger.should_receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
284
+ connection.write('blah')
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,14 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::TooManyRequestsError do
4
+ let(:response) { double(:code => 429, :header => { 'retry-after' => 3600 }) }
5
+ let(:error) { Rpush::TooManyRequestsError.new(429, 12, "Too Many Requests", response) }
6
+
7
+ it "returns an informative message" do
8
+ error.to_s.should eq "Too many requests for 12, received error 429 (Too Many Requests) - retry after 3600"
9
+ end
10
+
11
+ it "returns the error code" do
12
+ error.code.should eq 429
13
+ end
14
+ end
@@ -0,0 +1,159 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Wpns::Delivery do
4
+ let(:app) { Rpush::Wpns::App.new(:name => "MyApp") }
5
+ let(:notification) { Rpush::Wpns::Notification.create!(:app => app,:alert => "test",
6
+ :uri => "http://some.example/",
7
+ :deliver_after => Time.now) }
8
+ let(:logger) { double(:error => nil, :info => nil, :warn => nil) }
9
+ let(:response) { double(:code => 200, :header => {}) }
10
+ let(:http) { double(:shutdown => nil, :request => response) }
11
+ let(:now) { Time.parse('2012-10-14 00:00:00') }
12
+ let(:batch) { double(mark_failed: nil, mark_delivered: nil, mark_retryable: nil) }
13
+ let(:delivery) { Rpush::Daemon::Wpns::Delivery.new(app, http, notification, batch) }
14
+ let(:store) { double(:create_wpns_notification => double(:id => 2)) }
15
+
16
+ def perform
17
+ delivery.perform
18
+ end
19
+
20
+ before do
21
+ delivery.stub(:reflect => nil)
22
+ Rpush::Daemon.stub(:store => store)
23
+ Time.stub(:now => now)
24
+ Rpush.stub(:logger => logger)
25
+ end
26
+
27
+ shared_examples_for "an notification with some delivery faliures" do
28
+ let(:new_notification) { Rpush::Wpns::Notification.where('id != ?', notification.id).first }
29
+
30
+ before { response.stub(:body => JSON.dump(body)) }
31
+
32
+ it "marks the original notification falied" do
33
+ delivery.should_receive(:mark_failed).with(notification, nil, error_description)
34
+ perform rescue Rpush::DeliveryError
35
+ end
36
+
37
+ it "raises a DeliveryError" do
38
+ expect { perform }.to raise_error(Rpush::DeliveryError)
39
+ end
40
+ end
41
+
42
+ describe "an 200 response" do
43
+ before do
44
+ response.stub(:code => 200)
45
+ end
46
+
47
+ it "marks the notification as delivered if delivered successfully to all devices" do
48
+ response.stub(:body => JSON.dump({ "failure" => 0 }))
49
+ response.stub(:to_hash => {"x-notificationstatus" => ["Received"]})
50
+ batch.should_receive(:mark_delivered).with(notification)
51
+ perform
52
+ end
53
+
54
+ it "retries the notification when the queue is full" do
55
+ response.stub(:body => JSON.dump({ "failure" => 0 }))
56
+ response.stub(:to_hash => { "x-notificationstatus" => ["QueueFull"] })
57
+ batch.should_receive(:mark_retryable).with(notification, Time.now + (60*10))
58
+ perform
59
+ end
60
+
61
+ it "marks the notification as failed if the notification is suppressed" do
62
+ response.stub(:body => JSON.dump({ "faliure" => 0 }))
63
+ response.stub(:to_hash => { "x-notificationstatus" => ["Suppressed"] })
64
+ delivery.should_receive(:mark_failed).with(200, "Notification was received but suppressed by the service.")
65
+ perform rescue Rpush::DeliveryError
66
+ end
67
+ end
68
+
69
+ describe "an 400 response" do
70
+ before { response.stub(:code => 400) }
71
+ it "marks notifications as failed" do
72
+ delivery.should_receive(:mark_failed).with(400,
73
+ "Bad XML or malformed notification URI.")
74
+ perform rescue Rpush::DeliveryError
75
+ end
76
+ end
77
+
78
+ describe "an 401 response" do
79
+ before { response.stub(:code => 401) }
80
+ it "marks notifications as failed" do
81
+ delivery.should_receive(:mark_failed).with(401,
82
+ "Unauthorized to send a notification to this app.")
83
+ perform rescue Rpush::DeliveryError
84
+ end
85
+ end
86
+
87
+ describe "an 404 response" do
88
+ before { response.stub(:code => 404) }
89
+ it "marks notifications as failed" do
90
+ delivery.should_receive(:mark_failed).with(404, "Not Found")
91
+ perform rescue Rpush::DeliveryError
92
+ end
93
+ end
94
+
95
+ describe "an 405 response" do
96
+ before { response.stub(:code => 405) }
97
+ it "marks notifications as failed" do
98
+ delivery.should_receive(:mark_failed).with(405, "Method Not Allowed")
99
+ perform rescue Rpush::DeliveryError
100
+ end
101
+ end
102
+
103
+ describe "an 406 response" do
104
+ before { response.stub(:code => 406) }
105
+
106
+ it "retries the notification" do
107
+ batch.should_receive(:mark_retryable).with(notification, Time.now + (60*60))
108
+ perform
109
+ end
110
+
111
+ it "logs a warning that the notification will be retried" do
112
+ notification.retries = 1
113
+ notification.deliver_after = now + 2
114
+ logger.should_receive(:warn).with("[MyApp] Per-day throttling limit reached. Notification 1 will be retried after 2012-10-14 00:00:02 (retry 1).")
115
+ perform
116
+ end
117
+ end
118
+
119
+ describe "an 412 response" do
120
+ before { response.stub(:code => 412) }
121
+
122
+ it "retries the notification" do
123
+ batch.should_receive(:mark_retryable).with(notification, Time.now + (60*60))
124
+ perform
125
+ end
126
+
127
+ it "logs a warning that the notification will be retried" do
128
+ notification.retries = 1
129
+ notification.deliver_after = now + 2
130
+ logger.should_receive(:warn).with("[MyApp] Device unreachable. Notification 1 will be retried after 2012-10-14 00:00:02 (retry 1).")
131
+ perform
132
+ end
133
+ end
134
+
135
+ describe "an 503 response" do
136
+ before { response.stub(:code => 503) }
137
+
138
+ it "retries the notification exponentially" do
139
+ delivery.should_receive(:mark_retryable_exponential).with(notification)
140
+ perform
141
+ end
142
+
143
+ it 'logs a warning that the notification will be retried.' do
144
+ notification.retries = 1
145
+ notification.deliver_after = now + 2
146
+ logger.should_receive(:warn).with("[MyApp] Service Unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
147
+ perform
148
+ end
149
+ end
150
+
151
+ describe 'an un-handled response' do
152
+ before { response.stub(:code => 418) }
153
+
154
+ it 'marks the notification as failed' do
155
+ delivery.should_receive(:mark_failed).with(418, "I'm a Teapot")
156
+ perform rescue Rpush::DeliveryError
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,159 @@
1
+ require 'unit_spec_helper'
2
+ require 'rpush/daemon/store/active_record'
3
+
4
+ describe Rpush::Daemon, "when starting" do
5
+ module Rails; end
6
+
7
+ let(:certificate) { double }
8
+ let(:password) { double }
9
+ let(:config) { double(:pid_file => nil, :foreground => true,
10
+ :embedded => false, :push => false, :store => :active_record,
11
+ :logger => nil) }
12
+ let(:logger) { double(:logger, :info => nil, :error => nil, :warn => nil) }
13
+
14
+ before do
15
+ Rpush.stub(:config => config, :logger => logger)
16
+ Rpush::Daemon::Feeder.stub(:start)
17
+ Rpush::Daemon::AppRunner.stub(:sync => nil, :stop => nil)
18
+ Rpush::Daemon.stub(:daemonize => nil, :exit => nil, :puts => nil)
19
+ File.stub(:open)
20
+ Rails.stub(:root).and_return("/rails_root")
21
+ end
22
+
23
+ unless Rpush.jruby?
24
+ it "forks into a daemon if the foreground option is false" do
25
+ config.stub(:foreground => false)
26
+ Rpush::Daemon.initialize_store
27
+ Rpush::Daemon.store.stub(:after_daemonize => nil)
28
+ Rpush::Daemon.should_receive(:daemonize)
29
+ Rpush::Daemon.start
30
+ end
31
+
32
+ it 'notifies the store after forking' do
33
+ config.stub(:foreground => false)
34
+ Rpush::Daemon.initialize_store
35
+ Rpush::Daemon.store.should_receive(:after_daemonize)
36
+ Rpush::Daemon.start
37
+ end
38
+
39
+ it "does not fork into a daemon if the foreground option is true" do
40
+ config.stub(:foreground => true)
41
+ Rpush::Daemon.should_not_receive(:daemonize)
42
+ Rpush::Daemon.start
43
+ end
44
+
45
+ it "does not fork into a daemon if the push option is true" do
46
+ config.stub(:push => true)
47
+ Rpush::Daemon.should_not_receive(:daemonize)
48
+ Rpush::Daemon.start
49
+ end
50
+
51
+ it "does not fork into a daemon if the embedded option is true" do
52
+ config.stub(:embedded => true)
53
+ Rpush::Daemon.should_not_receive(:daemonize)
54
+ Rpush::Daemon.start
55
+ end
56
+ end
57
+
58
+ it 'sets up setup signal traps' do
59
+ Rpush::Daemon.should_receive(:setup_signal_traps)
60
+ Rpush::Daemon.start
61
+ end
62
+
63
+ it 'does not setup signal traps when embedded' do
64
+ config.stub(:embedded => true)
65
+ Rpush::Daemon.should_not_receive(:setup_signal_traps)
66
+ Rpush::Daemon.start
67
+ end
68
+
69
+ it 'instantiates the store' do
70
+ config.stub(:store => :active_record)
71
+ Rpush::Daemon.start
72
+ Rpush::Daemon.store.should be_kind_of(Rpush::Daemon::Store::ActiveRecord)
73
+ end
74
+
75
+ it 'logs an error if the store cannot be loaded' do
76
+ config.stub(:store => :foo_bar)
77
+ Rpush.logger.should_receive(:error).with(kind_of(LoadError))
78
+ Rpush::Daemon.start
79
+ end
80
+
81
+ it "writes the process ID to the PID file" do
82
+ Rpush::Daemon.should_receive(:write_pid_file)
83
+ Rpush::Daemon.start
84
+ end
85
+
86
+ it "logs an error if the PID file could not be written" do
87
+ config.stub(:pid_file => '/rails_root/rpush.pid')
88
+ File.stub(:open).and_raise(Errno::ENOENT)
89
+ logger.should_receive(:error).with("Failed to write PID to '/rails_root/rpush.pid': #<Errno::ENOENT: No such file or directory>")
90
+ Rpush::Daemon.start
91
+ end
92
+
93
+ it "starts the feeder" do
94
+ Rpush::Daemon::Feeder.should_receive(:start)
95
+ Rpush::Daemon.start
96
+ end
97
+
98
+ it "syncs apps" do
99
+ Rpush::Daemon::AppRunner.should_receive(:sync)
100
+ Rpush::Daemon.start
101
+ end
102
+ end
103
+
104
+ describe Rpush::Daemon, "when being shutdown" do
105
+ let(:config) { double(:pid_file => '/rails_root/rpush.pid') }
106
+ let(:logger) { double(:info => nil, :error => nil, :warn => nil) }
107
+
108
+ before do
109
+ Rpush.stub(:config => config)
110
+ Rpush::Daemon.stub(:puts => nil)
111
+ Rpush::Daemon::Feeder.stub(:stop)
112
+ Rpush::Daemon::AppRunner.stub(:stop)
113
+ end
114
+
115
+ # These tests do not work on JRuby.
116
+ unless Rpush.jruby?
117
+ it "shuts down when signaled signaled SIGINT" do
118
+ Rpush::Daemon.setup_signal_traps
119
+ Rpush::Daemon.should_receive(:shutdown)
120
+ Process.kill("SIGINT", Process.pid)
121
+ sleep 0.01
122
+ end
123
+
124
+ it "shuts down when signaled signaled SIGTERM" do
125
+ Rpush::Daemon.setup_signal_traps
126
+ Rpush::Daemon.should_receive(:shutdown)
127
+ Process.kill("SIGTERM", Process.pid)
128
+ sleep 0.01
129
+ end
130
+ end
131
+
132
+ it "stops the feeder" do
133
+ Rpush::Daemon::Feeder.should_receive(:stop)
134
+ Rpush::Daemon.shutdown
135
+ end
136
+
137
+ it "stops the app runners" do
138
+ Rpush::Daemon::AppRunner.should_receive(:stop)
139
+ Rpush::Daemon.shutdown
140
+ end
141
+
142
+ it "removes the PID file if one was written" do
143
+ File.stub(:exists?).and_return(true)
144
+ File.should_receive(:delete).with("/rails_root/rpush.pid")
145
+ Rpush::Daemon.shutdown
146
+ end
147
+
148
+ it "does not attempt to remove the PID file if it does not exist" do
149
+ File.stub(:exists?).and_return(false)
150
+ File.should_not_receive(:delete)
151
+ Rpush::Daemon.shutdown
152
+ end
153
+
154
+ it "does not attempt to remove the PID file if one was not written" do
155
+ config.stub(:pid_file).and_return(nil)
156
+ File.should_not_receive(:delete)
157
+ Rpush::Daemon.shutdown
158
+ end
159
+ end
@@ -0,0 +1,32 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Deprecatable do
4
+ class HasDeprecatedMethod
5
+ include Rpush::Deprecatable
6
+
7
+ def original_called?
8
+ @called == true
9
+ end
10
+
11
+ def deprecated_method
12
+ @called = true
13
+ end
14
+ deprecated(:deprecated_method, '4.0')
15
+ end
16
+
17
+ let(:klass) { HasDeprecatedMethod.new }
18
+
19
+ before do
20
+ Rpush::Deprecation.stub(:warn)
21
+ end
22
+
23
+ it 'warns the method is deprecated when called' do
24
+ Rpush::Deprecation.should_receive(:warn).with("deprecated_method is deprecated and will be removed from Rpush 4.0.")
25
+ klass.deprecated_method
26
+ end
27
+
28
+ it 'calls the original method' do
29
+ klass.deprecated_method
30
+ klass.original_called?.should be_true
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Deprecation do
4
+ it 'prints a warning' do
5
+ STDERR.should_receive(:puts).with("DEPRECATION WARNING: msg")
6
+ Rpush::Deprecation.warn("msg")
7
+ end
8
+
9
+ it 'does not print a warning when muted' do
10
+ STDERR.should_not_receive(:puts)
11
+ Rpush::Deprecation.muted do
12
+ Rpush::Deprecation.warn("msg")
13
+ end
14
+ end
15
+ end