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,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
|