rapns 3.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.
- data/CHANGELOG.md +31 -0
- data/LICENSE +7 -0
- data/README.md +138 -0
- data/bin/rapns +41 -0
- data/config/database.yml +39 -0
- data/lib/generators/rapns_generator.rb +34 -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_gcm.rb +94 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +15 -0
- data/lib/generators/templates/create_rapns_notifications.rb +26 -0
- data/lib/generators/templates/rapns.rb +39 -0
- data/lib/rapns/apns/app.rb +8 -0
- data/lib/rapns/apns/binary_notification_validator.rb +12 -0
- data/lib/rapns/apns/device_token_format_validator.rb +12 -0
- data/lib/rapns/apns/feedback.rb +14 -0
- data/lib/rapns/apns/notification.rb +86 -0
- data/lib/rapns/apns/required_fields_validator.rb +14 -0
- data/lib/rapns/app.rb +29 -0
- data/lib/rapns/configuration.rb +46 -0
- data/lib/rapns/daemon/apns/app_runner.rb +36 -0
- data/lib/rapns/daemon/apns/connection.rb +113 -0
- data/lib/rapns/daemon/apns/delivery.rb +63 -0
- data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
- data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
- data/lib/rapns/daemon/app_runner.rb +135 -0
- data/lib/rapns/daemon/database_reconnectable.rb +57 -0
- data/lib/rapns/daemon/delivery.rb +43 -0
- data/lib/rapns/daemon/delivery_error.rb +19 -0
- data/lib/rapns/daemon/delivery_handler.rb +46 -0
- data/lib/rapns/daemon/delivery_queue.rb +42 -0
- data/lib/rapns/daemon/delivery_queue_18.rb +44 -0
- data/lib/rapns/daemon/delivery_queue_19.rb +42 -0
- data/lib/rapns/daemon/feeder.rb +37 -0
- data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
- data/lib/rapns/daemon/gcm/delivery.rb +206 -0
- data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
- data/lib/rapns/daemon/interruptible_sleep.rb +18 -0
- data/lib/rapns/daemon/logger.rb +68 -0
- data/lib/rapns/daemon.rb +136 -0
- data/lib/rapns/gcm/app.rb +7 -0
- data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rapns/gcm/notification.rb +31 -0
- data/lib/rapns/gcm/payload_size_validator.rb +13 -0
- data/lib/rapns/multi_json_helper.rb +16 -0
- data/lib/rapns/notification.rb +54 -0
- data/lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb +12 -0
- data/lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb +17 -0
- data/lib/rapns/patches.rb +6 -0
- data/lib/rapns/version.rb +3 -0
- data/lib/rapns.rb +21 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/test.rake +33 -0
- data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
- data/spec/acceptance_spec_helper.rb +85 -0
- data/spec/support/simplecov_helper.rb +13 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/unit/apns/app_spec.rb +15 -0
- data/spec/unit/apns/feedback_spec.rb +12 -0
- data/spec/unit/apns/notification_spec.rb +198 -0
- data/spec/unit/app_spec.rb +18 -0
- data/spec/unit/configuration_spec.rb +38 -0
- data/spec/unit/daemon/apns/app_runner_spec.rb +39 -0
- data/spec/unit/daemon/apns/connection_spec.rb +234 -0
- data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +160 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +118 -0
- data/spec/unit/daemon/app_runner_shared.rb +66 -0
- data/spec/unit/daemon/app_runner_spec.rb +129 -0
- data/spec/unit/daemon/database_reconnectable_spec.rb +109 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_handler_shared.rb +28 -0
- data/spec/unit/daemon/delivery_queue_spec.rb +29 -0
- data/spec/unit/daemon/feeder_spec.rb +95 -0
- data/spec/unit/daemon/gcm/app_runner_spec.rb +17 -0
- data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +40 -0
- data/spec/unit/daemon/logger_spec.rb +156 -0
- data/spec/unit/daemon_spec.rb +139 -0
- data/spec/unit/gcm/app_spec.rb +5 -0
- data/spec/unit/gcm/notification_spec.rb +55 -0
- data/spec/unit/notification_shared.rb +38 -0
- data/spec/unit/notification_spec.rb +6 -0
- data/spec/unit_spec_helper.rb +145 -0
- metadata +240 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
require File.dirname(__FILE__) + '/../app_runner_shared.rb'
|
|
3
|
+
|
|
4
|
+
describe Rapns::Daemon::Apns::AppRunner do
|
|
5
|
+
it_behaves_like 'an AppRunner subclass'
|
|
6
|
+
|
|
7
|
+
let(:app_class) { Rapns::Apns::App }
|
|
8
|
+
let(:app) { app_class.create!(:name => 'my_app', :environment => 'development',
|
|
9
|
+
:certificate => TEST_CERT, :password => 'pass') }
|
|
10
|
+
let(:runner) { Rapns::Daemon::Apns::AppRunner.new(app) }
|
|
11
|
+
let(:handler) { stub(:start => nil, :stop => nil, :queue= => nil) }
|
|
12
|
+
let(:receiver) { stub(:start => nil, :stop => nil) }
|
|
13
|
+
let(:config) { {:feedback_poll => 60 } }
|
|
14
|
+
let(:logger) { stub(:info => nil) }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
Rapns::Daemon.stub(:logger => logger)
|
|
18
|
+
Rapns::Daemon::Apns::DeliveryHandler.stub(:new => handler)
|
|
19
|
+
Rapns::Daemon::Apns::FeedbackReceiver.stub(:new => receiver)
|
|
20
|
+
Rapns::Daemon.stub(:config => config)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'instantiates a new feedback receiver when started' do
|
|
24
|
+
Rapns::Daemon::Apns::FeedbackReceiver.should_receive(:new).with(app, 'feedback.sandbox.push.apple.com',
|
|
25
|
+
2196, 60)
|
|
26
|
+
runner.start
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'starts the feedback receiver' do
|
|
30
|
+
receiver.should_receive(:start)
|
|
31
|
+
runner.start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'stops the feedback receiver' do
|
|
35
|
+
runner.start
|
|
36
|
+
receiver.should_receive(:stop)
|
|
37
|
+
runner.stop
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::Daemon::Apns::Connection do
|
|
4
|
+
let(:ssl_context) { stub(:key= => nil, :cert= => nil) }
|
|
5
|
+
let(:rsa_key) { stub }
|
|
6
|
+
let(:certificate) { stub }
|
|
7
|
+
let(:password) { stub }
|
|
8
|
+
let(:x509_certificate) { stub }
|
|
9
|
+
let(:host) { 'gateway.push.apple.com' }
|
|
10
|
+
let(:port) { '2195' }
|
|
11
|
+
let(:tcp_socket) { stub(:setsockopt => nil, :close => nil) }
|
|
12
|
+
let(:ssl_socket) { stub(:sync= => nil, :connect => nil, :close => nil, :write => nil, :flush => nil) }
|
|
13
|
+
let(:logger) { stub(:info => nil, :error => nil) }
|
|
14
|
+
let(:connection) { Rapns::Daemon::Apns::Connection.new('Connection 0', host, port, certificate, password) }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
OpenSSL::SSL::SSLContext.stub(:new => ssl_context)
|
|
18
|
+
OpenSSL::PKey::RSA.stub(:new => rsa_key)
|
|
19
|
+
OpenSSL::X509::Certificate.stub(:new => x509_certificate)
|
|
20
|
+
TCPSocket.stub(:new => tcp_socket)
|
|
21
|
+
OpenSSL::SSL::SSLSocket.stub(:new => ssl_socket)
|
|
22
|
+
Rapns::Daemon.stub(:logger => logger)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "reads the number of bytes from the SSL socket" do
|
|
26
|
+
ssl_socket.should_receive(:read).with(123)
|
|
27
|
+
connection.connect
|
|
28
|
+
connection.read(123)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "selects on the SSL socket until the given timeout" do
|
|
32
|
+
IO.should_receive(:select).with([ssl_socket], nil, nil, 10)
|
|
33
|
+
connection.connect
|
|
34
|
+
connection.select(10)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe "when setting up the SSL context" do
|
|
38
|
+
it "sets the key on the context" do
|
|
39
|
+
OpenSSL::PKey::RSA.should_receive(:new).with(certificate, password).and_return(rsa_key)
|
|
40
|
+
ssl_context.should_receive(:key=).with(rsa_key)
|
|
41
|
+
connection.connect
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "sets the cert on the context" do
|
|
45
|
+
OpenSSL::X509::Certificate.should_receive(:new).with(certificate).and_return(x509_certificate)
|
|
46
|
+
ssl_context.should_receive(:cert=).with(x509_certificate)
|
|
47
|
+
connection.connect
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "when connecting the socket" do
|
|
52
|
+
it "creates a TCP socket using the configured host and port" do
|
|
53
|
+
TCPSocket.should_receive(:new).with(host, port).and_return(tcp_socket)
|
|
54
|
+
connection.connect
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "creates a new SSL socket using the TCP socket and SSL context" do
|
|
58
|
+
OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket)
|
|
59
|
+
connection.connect
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "sets the sync option on the SSL socket" do
|
|
63
|
+
ssl_socket.should_receive(:sync=).with(true)
|
|
64
|
+
connection.connect
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "connects the SSL socket" do
|
|
68
|
+
ssl_socket.should_receive(:connect)
|
|
69
|
+
connection.connect
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "sets the socket option TCP_NODELAY" do
|
|
73
|
+
tcp_socket.should_receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
74
|
+
connection.connect
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "sets the socket option SO_KEEPALIVE" do
|
|
78
|
+
tcp_socket.should_receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
|
|
79
|
+
connection.connect
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "when shuting down the connection" do
|
|
84
|
+
it "closes the TCP socket" do
|
|
85
|
+
connection.connect
|
|
86
|
+
tcp_socket.should_receive(:close)
|
|
87
|
+
connection.close
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "does not attempt to close the TCP socket if it is not connected" do
|
|
91
|
+
connection.connect
|
|
92
|
+
tcp_socket.should_not_receive(:close)
|
|
93
|
+
connection.instance_variable_set("@tcp_socket", nil)
|
|
94
|
+
connection.close
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "closes the SSL socket" do
|
|
98
|
+
connection.connect
|
|
99
|
+
ssl_socket.should_receive(:close)
|
|
100
|
+
connection.close
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "does not attempt to close the SSL socket if it is not connected" do
|
|
104
|
+
connection.connect
|
|
105
|
+
ssl_socket.should_not_receive(:close)
|
|
106
|
+
connection.instance_variable_set("@ssl_socket", nil)
|
|
107
|
+
connection.close
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "ignores IOError when the socket is already closed" do
|
|
111
|
+
tcp_socket.stub(:close).and_raise(IOError)
|
|
112
|
+
connection.connect
|
|
113
|
+
expect { connection.close }.to_not raise_error(IOError)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
shared_examples_for "when the write fails" do
|
|
118
|
+
before do
|
|
119
|
+
connection.stub(:sleep)
|
|
120
|
+
connection.connect
|
|
121
|
+
ssl_socket.stub(:write).and_raise(error_type)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "logs that the connection has been lost once only" do
|
|
125
|
+
logger.should_receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error_type.name}), reconnecting...").once
|
|
126
|
+
begin
|
|
127
|
+
connection.write(nil)
|
|
128
|
+
rescue Rapns::Daemon::Apns::ConnectionError
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it "retries to make a connection 3 times" do
|
|
133
|
+
connection.should_receive(:reconnect).exactly(3).times
|
|
134
|
+
begin
|
|
135
|
+
connection.write(nil)
|
|
136
|
+
rescue Rapns::Daemon::Apns::ConnectionError
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "raises a ConnectionError after 3 attempts at reconnecting" do
|
|
141
|
+
expect do
|
|
142
|
+
connection.write(nil)
|
|
143
|
+
end.to raise_error(Rapns::Daemon::Apns::ConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error_type.name}).")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "sleeps 1 second before retrying the connection" do
|
|
147
|
+
connection.should_receive(:sleep).with(1)
|
|
148
|
+
begin
|
|
149
|
+
connection.write(nil)
|
|
150
|
+
rescue Rapns::Daemon::Apns::ConnectionError
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
describe "when write raises an Errno::EPIPE" do
|
|
156
|
+
it_should_behave_like "when the write fails"
|
|
157
|
+
|
|
158
|
+
def error_type
|
|
159
|
+
Errno::EPIPE
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe "when write raises an Errno::ETIMEDOUT" do
|
|
164
|
+
it_should_behave_like "when the write fails"
|
|
165
|
+
|
|
166
|
+
def error_type
|
|
167
|
+
Errno::ETIMEDOUT
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
describe "when write raises an OpenSSL::SSL::SSLError" do
|
|
172
|
+
it_should_behave_like "when the write fails"
|
|
173
|
+
|
|
174
|
+
def error_type
|
|
175
|
+
OpenSSL::SSL::SSLError
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe "when reconnecting" do
|
|
180
|
+
it 'closes the socket' do
|
|
181
|
+
connection.should_receive(:close)
|
|
182
|
+
connection.send(:reconnect)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'connects the socket' do
|
|
186
|
+
connection.should_receive(:connect_socket)
|
|
187
|
+
connection.send(:reconnect)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe "when sending a notification" do
|
|
192
|
+
before { connection.connect }
|
|
193
|
+
|
|
194
|
+
it "writes the data to the SSL socket" do
|
|
195
|
+
ssl_socket.should_receive(:write).with("blah")
|
|
196
|
+
connection.write("blah")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "flushes the SSL socket" do
|
|
200
|
+
ssl_socket.should_receive(:flush)
|
|
201
|
+
connection.write("blah")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
describe 'idle period' do
|
|
206
|
+
before { connection.connect }
|
|
207
|
+
|
|
208
|
+
it 'reconnects if the connection has been idle for more than the defined period' do
|
|
209
|
+
Rapns::Daemon::Apns::Connection.stub(:idle_period => 0.1)
|
|
210
|
+
sleep 0.2
|
|
211
|
+
connection.should_receive(:reconnect)
|
|
212
|
+
connection.write('blah')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'resets the last write time' do
|
|
216
|
+
now = Time.now
|
|
217
|
+
Time.stub(:now => now)
|
|
218
|
+
connection.write('blah')
|
|
219
|
+
connection.last_write.should == now
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'does not reconnect if the connection has not been idle for more than the defined period' do
|
|
223
|
+
connection.should_not_receive(:reconnect)
|
|
224
|
+
connection.write('blah')
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 'logs the the connection is idle' do
|
|
228
|
+
Rapns::Daemon::Apns::Connection.stub(:idle_period => 0.1)
|
|
229
|
+
sleep 0.2
|
|
230
|
+
Rapns::Daemon.logger.should_receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
|
|
231
|
+
connection.write('blah')
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
require File.dirname(__FILE__) + '/../delivery_handler_shared.rb'
|
|
3
|
+
|
|
4
|
+
describe Rapns::Daemon::Apns::DeliveryHandler do
|
|
5
|
+
it_should_behave_like 'an DeliveryHandler subclass'
|
|
6
|
+
|
|
7
|
+
let(:host) { 'localhost' }
|
|
8
|
+
let(:port) { 2195 }
|
|
9
|
+
let(:certificate) { stub }
|
|
10
|
+
let(:password) { stub }
|
|
11
|
+
let(:app) { stub(:password => password, :certificate => certificate, :name => 'MyApp')}
|
|
12
|
+
let(:delivery_handler) { Rapns::Daemon::Apns::DeliveryHandler.new(app, host, port) }
|
|
13
|
+
let(:connection) { stub('Connection', :select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
|
|
14
|
+
let(:notification) { stub }
|
|
15
|
+
let(:http) { stub(:shutdown => nil)}
|
|
16
|
+
let(:queue) { Rapns::Daemon::DeliveryQueue.new }
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
Rapns::Daemon::Apns::Connection.stub(:new => connection)
|
|
20
|
+
Rapns::Daemon::Apns::Delivery.stub(:perform)
|
|
21
|
+
delivery_handler.queue = queue
|
|
22
|
+
queue.push(notification)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "instantiates a new connection" do
|
|
26
|
+
Rapns::Daemon::Apns::Connection.should_receive(:new).with(app.name, host, port, certificate, password)
|
|
27
|
+
Rapns::Daemon::Apns::DeliveryHandler.new(app, host, port)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'performs delivery of an notification' do
|
|
31
|
+
Rapns::Daemon::Apns::Delivery.should_receive(:perform).with(app, connection, notification)
|
|
32
|
+
delivery_handler.start
|
|
33
|
+
delivery_handler.stop
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "connects the socket when instantiated" do
|
|
37
|
+
connection.should_receive(:connect)
|
|
38
|
+
Rapns::Daemon::Apns::DeliveryHandler.new(app, host, port)
|
|
39
|
+
delivery_handler.start
|
|
40
|
+
delivery_handler.stop
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'closes the connection stopped' do
|
|
44
|
+
connection.should_receive(:close)
|
|
45
|
+
delivery_handler.start
|
|
46
|
+
delivery_handler.stop
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rapns::Daemon::Apns::Delivery do
|
|
4
|
+
let(:app) { stub(:name => 'MyApp') }
|
|
5
|
+
let(:notification) { stub.as_null_object }
|
|
6
|
+
let(:logger) { stub(:error => nil, :info => nil) }
|
|
7
|
+
let(:config) { stub(:check_for_errors => true) }
|
|
8
|
+
let(:connection) { stub(:select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
|
|
9
|
+
let(:delivery) { Rapns::Daemon::Apns::Delivery.new(app, connection, notification) }
|
|
10
|
+
|
|
11
|
+
def perform
|
|
12
|
+
begin
|
|
13
|
+
delivery.perform
|
|
14
|
+
rescue Rapns::DeliveryError, Rapns::Apns::DisconnectionError
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
Rapns.stub(:config => config)
|
|
20
|
+
Rapns::Daemon.stub(: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
|
+
notification.should_receive(:delivered=).with(true)
|
|
37
|
+
perform
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "sets the time the notification was delivered" do
|
|
41
|
+
now = Time.now
|
|
42
|
+
Time.stub(:now).and_return(now)
|
|
43
|
+
notification.should_receive(:delivered_at=).with(now)
|
|
44
|
+
perform
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "does not trigger validations when saving the notification" do
|
|
48
|
+
notification.should_receive(:save!).with(:validate => false)
|
|
49
|
+
perform
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "updates notification with the ability to reconnect the database" do
|
|
53
|
+
delivery.should_receive(:with_database_reconnect_and_retry)
|
|
54
|
+
perform
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'does not check for errors if check_for_errors config option is false' do
|
|
58
|
+
config.stub(:check_for_errors => false)
|
|
59
|
+
delivery.should_not_receive(:check_for_error)
|
|
60
|
+
perform
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "when delivery fails" do
|
|
64
|
+
before { connection.stub(:select => true, :read => [8, 4, 69].pack("ccN")) }
|
|
65
|
+
|
|
66
|
+
it "updates notification with the ability to reconnect the database" do
|
|
67
|
+
delivery.should_receive(:with_database_reconnect_and_retry)
|
|
68
|
+
perform
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "sets the notification as not delivered" do
|
|
72
|
+
notification.should_receive(:delivered=).with(false)
|
|
73
|
+
perform
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "sets the notification delivered_at timestamp to nil" do
|
|
77
|
+
notification.should_receive(:delivered_at=).with(nil)
|
|
78
|
+
perform
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "sets the notification as failed" do
|
|
82
|
+
notification.should_receive(:failed=).with(true)
|
|
83
|
+
perform
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "sets the notification failed_at timestamp" do
|
|
87
|
+
now = Time.now
|
|
88
|
+
Time.stub(:now).and_return(now)
|
|
89
|
+
notification.should_receive(:failed_at=).with(now)
|
|
90
|
+
perform
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "sets the notification error code" do
|
|
94
|
+
notification.should_receive(:error_code=).with(4)
|
|
95
|
+
perform
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "logs the delivery error" do
|
|
99
|
+
# checking for the stubbed error doesn't work in jruby, but checking
|
|
100
|
+
# for the exception by class does.
|
|
101
|
+
|
|
102
|
+
#error = Rapns::DeliveryError.new(4, 12, "Missing payload")
|
|
103
|
+
#Rapns::DeliveryError.stub(:new => error)
|
|
104
|
+
#expect { delivery.perform }.to raise_error(error)
|
|
105
|
+
|
|
106
|
+
expect { delivery.perform }.to raise_error(Rapns::DeliveryError)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "sets the notification error description" do
|
|
110
|
+
notification.should_receive(:error_description=).with("Missing payload")
|
|
111
|
+
perform
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "skips validation when saving the notification" do
|
|
115
|
+
notification.should_receive(:save!).with(:validate => false)
|
|
116
|
+
perform
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "reads 6 bytes from the socket" do
|
|
120
|
+
connection.should_receive(:read).with(6).and_return(nil)
|
|
121
|
+
perform
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do
|
|
125
|
+
connection.stub(:select => nil)
|
|
126
|
+
connection.should_not_receive(:read)
|
|
127
|
+
perform
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "reconnects the socket" do
|
|
131
|
+
connection.should_receive(:reconnect)
|
|
132
|
+
perform
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "logs that the connection is being reconnected" do
|
|
136
|
+
Rapns::Daemon.logger.should_receive(:error).with("[MyApp] Error received, reconnecting...")
|
|
137
|
+
perform
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context "when the APNs disconnects without returning an error" do
|
|
141
|
+
before do
|
|
142
|
+
connection.stub(:read => nil)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'raises a DisconnectError error if the connection is closed without an error being returned' do
|
|
146
|
+
expect { delivery.perform }.to raise_error(Rapns::Apns::DisconnectionError)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'does not set the error code on the notification' do
|
|
150
|
+
notification.should_receive(:error_code=).with(nil)
|
|
151
|
+
perform
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'sets the error description on the notification' do
|
|
155
|
+
notification.should_receive(:error_description=).with("APNs disconnected without returning an error.")
|
|
156
|
+
perform
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rapns::Apns::DisconnectionError do
|
|
4
|
+
let(:error) { Rapns::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,118 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
|
|
4
|
+
let(:host) { 'feedback.push.apple.com' }
|
|
5
|
+
let(:port) { 2196 }
|
|
6
|
+
let(:poll) { 60 }
|
|
7
|
+
let(:certificate) { stub }
|
|
8
|
+
let(:password) { stub }
|
|
9
|
+
let(:app) { stub(:name => 'my_app', :password => password, :certificate => certificate) }
|
|
10
|
+
let(:connection) { stub(:connect => nil, :read => nil, :close => nil) }
|
|
11
|
+
let(:logger) { stub(:error => nil, :info => nil) }
|
|
12
|
+
let(:receiver) { Rapns::Daemon::Apns::FeedbackReceiver.new(app, host, port, poll) }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
receiver.stub(:interruptible_sleep)
|
|
16
|
+
Rapns::Daemon.logger = logger
|
|
17
|
+
Rapns::Daemon::Apns::Connection.stub(:new => connection)
|
|
18
|
+
Rapns::Apns::Feedback.stub(:create!)
|
|
19
|
+
receiver.instance_variable_set("@stop", false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stub_connection_read_with_tuple
|
|
23
|
+
connection.unstub(:read)
|
|
24
|
+
|
|
25
|
+
def connection.read(bytes)
|
|
26
|
+
if !@called
|
|
27
|
+
@called = true
|
|
28
|
+
"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"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'instantiates a new connection' do
|
|
34
|
+
Rapns::Daemon::Apns::Connection.should_receive(:new).with("FeedbackReceiver:#{app.name}", host, port, certificate, password)
|
|
35
|
+
receiver.check_for_feedback
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'connects to the feeback service' do
|
|
39
|
+
connection.should_receive(:connect)
|
|
40
|
+
receiver.check_for_feedback
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'closes the connection' do
|
|
44
|
+
connection.should_receive(:close)
|
|
45
|
+
receiver.check_for_feedback
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'reads from the connection' do
|
|
49
|
+
connection.should_receive(:read).with(38)
|
|
50
|
+
receiver.check_for_feedback
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'logs the feedback' do
|
|
54
|
+
stub_connection_read_with_tuple
|
|
55
|
+
Rapns::Daemon.logger.should_receive(:info).with("[FeedbackReceiver:my_app] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17")
|
|
56
|
+
receiver.check_for_feedback
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'creates the feedback' do
|
|
60
|
+
stub_connection_read_with_tuple
|
|
61
|
+
Rapns::Apns::Feedback.should_receive(:create!).with(:failed_at => Time.at(1323533325), :device_token => '834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17', :app => app)
|
|
62
|
+
receiver.check_for_feedback
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'logs errors' do
|
|
66
|
+
error = StandardError.new('bork!')
|
|
67
|
+
connection.stub(:read).and_raise(error)
|
|
68
|
+
Rapns::Daemon.logger.should_receive(:error).with(error)
|
|
69
|
+
receiver.check_for_feedback
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'sleeps for the feedback poll period' do
|
|
73
|
+
receiver.stub(:check_for_feedback)
|
|
74
|
+
receiver.should_receive(:interruptible_sleep).with(60).at_least(:once)
|
|
75
|
+
Thread.stub(:new).and_yield
|
|
76
|
+
receiver.stub(:loop).and_yield
|
|
77
|
+
receiver.start
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'checks for feedback when started' do
|
|
81
|
+
receiver.should_receive(:check_for_feedback).at_least(:once)
|
|
82
|
+
Thread.stub(:new).and_yield
|
|
83
|
+
receiver.stub(:loop).and_yield
|
|
84
|
+
receiver.start
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'interrupts sleep when stopped' do
|
|
88
|
+
receiver.stub(:check_for_feedback)
|
|
89
|
+
receiver.should_receive(:interrupt_sleep)
|
|
90
|
+
receiver.stop
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'calls the apns_feedback_callback when feedback is received and the callback is set' do
|
|
94
|
+
stub_connection_read_with_tuple
|
|
95
|
+
Rapns.config.apns_feedback_callback = Proc.new {}
|
|
96
|
+
feedback = Object.new
|
|
97
|
+
Rapns::Apns::Feedback.stub(:create! => feedback)
|
|
98
|
+
Rapns.config.apns_feedback_callback.should_receive(:call).with(feedback)
|
|
99
|
+
receiver.check_for_feedback
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'catches exceptions in the apns_feedback_callback' do
|
|
103
|
+
error = StandardError.new('bork!')
|
|
104
|
+
stub_connection_read_with_tuple
|
|
105
|
+
callback = Proc.new { raise error }
|
|
106
|
+
Rapns.config.on_apns_feedback &callback
|
|
107
|
+
expect { receiver.check_for_feedback }.not_to raise_error
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'logs an exception from the apns_feedback_callback' do
|
|
111
|
+
error = StandardError.new('bork!')
|
|
112
|
+
stub_connection_read_with_tuple
|
|
113
|
+
callback = Proc.new { raise error }
|
|
114
|
+
Rapns::Daemon.logger.should_receive(:error).with(error)
|
|
115
|
+
Rapns.config.on_apns_feedback &callback
|
|
116
|
+
receiver.check_for_feedback
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
shared_examples_for "an AppRunner subclass" do
|
|
2
|
+
let(:queue) { stub(:notifications_processed? => true, :push => nil) }
|
|
3
|
+
|
|
4
|
+
before { Rapns::Daemon::DeliveryQueue.stub(:new => queue) }
|
|
5
|
+
after { Rapns::Daemon::AppRunner.runners.clear }
|
|
6
|
+
|
|
7
|
+
describe 'start' do
|
|
8
|
+
it 'starts a delivery handler for each connection' do
|
|
9
|
+
handler.should_receive(:start)
|
|
10
|
+
runner.start
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'assigns the queue to the handler' do
|
|
14
|
+
handler.should_receive(:queue=).with(queue)
|
|
15
|
+
runner.start
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe 'enqueue' do
|
|
20
|
+
let(:notification) { stub }
|
|
21
|
+
|
|
22
|
+
it 'enqueues the notification' do
|
|
23
|
+
queue.should_receive(:push).with(notification)
|
|
24
|
+
runner.enqueue(notification)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe 'stop' do
|
|
29
|
+
before { runner.start }
|
|
30
|
+
|
|
31
|
+
it 'stops the delivery handlers' do
|
|
32
|
+
handler.should_receive(:stop)
|
|
33
|
+
runner.stop
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe 'idle?' do
|
|
38
|
+
it 'is idle if all notifications have been processed' do
|
|
39
|
+
queue.stub(:notifications_processed? => true)
|
|
40
|
+
runner.idle?.should be_true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'is not idle if not all notifications have been processed' do
|
|
44
|
+
queue.stub(:notifications_processed? => false)
|
|
45
|
+
runner.idle?.should be_false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe 'sync' do
|
|
50
|
+
before { runner.start }
|
|
51
|
+
|
|
52
|
+
it 'reduces the number of handlers if needed' do
|
|
53
|
+
handler.should_receive(:stop)
|
|
54
|
+
new_app = app_class.new
|
|
55
|
+
new_app.stub(:connections => app.connections - 1)
|
|
56
|
+
runner.sync(new_app)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'increases the number of handlers if needed' do
|
|
60
|
+
runner.should_receive(:start_handler).and_return(handler)
|
|
61
|
+
new_app = app_class.new
|
|
62
|
+
new_app.stub(:connections => app.connections + 1)
|
|
63
|
+
runner.sync(new_app)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|