rapns_rails_2 3.4.3

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 (113) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +7 -0
  4. data/README.md +168 -0
  5. data/bin/rapns +37 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rapns_generator.rb +25 -0
  8. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  9. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  10. data/lib/generators/templates/add_gcm.rb +95 -0
  11. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  12. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  13. data/lib/generators/templates/create_rapns_notifications.rb +26 -0
  14. data/lib/generators/templates/rapns.rb +87 -0
  15. data/lib/rapns/TODO +3 -0
  16. data/lib/rapns/apns/app.rb +25 -0
  17. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  18. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  19. data/lib/rapns/apns/feedback.rb +16 -0
  20. data/lib/rapns/apns/notification.rb +91 -0
  21. data/lib/rapns/apns_feedback.rb +13 -0
  22. data/lib/rapns/app.rb +16 -0
  23. data/lib/rapns/configuration.rb +89 -0
  24. data/lib/rapns/daemon/apns/app_runner.rb +26 -0
  25. data/lib/rapns/daemon/apns/certificate_expired_error.rb +20 -0
  26. data/lib/rapns/daemon/apns/connection.rb +142 -0
  27. data/lib/rapns/daemon/apns/delivery.rb +64 -0
  28. data/lib/rapns/daemon/apns/delivery_handler.rb +35 -0
  29. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  30. data/lib/rapns/daemon/apns/feedback_receiver.rb +89 -0
  31. data/lib/rapns/daemon/app_runner.rb +179 -0
  32. data/lib/rapns/daemon/batch.rb +112 -0
  33. data/lib/rapns/daemon/delivery.rb +23 -0
  34. data/lib/rapns/daemon/delivery_error.rb +19 -0
  35. data/lib/rapns/daemon/delivery_handler.rb +52 -0
  36. data/lib/rapns/daemon/delivery_handler_collection.rb +33 -0
  37. data/lib/rapns/daemon/feeder.rb +65 -0
  38. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  39. data/lib/rapns/daemon/gcm/delivery.rb +228 -0
  40. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  41. data/lib/rapns/daemon/interruptible_sleep.rb +65 -0
  42. data/lib/rapns/daemon/reflectable.rb +13 -0
  43. data/lib/rapns/daemon/store/active_record/reconnectable.rb +66 -0
  44. data/lib/rapns/daemon/store/active_record.rb +128 -0
  45. data/lib/rapns/daemon.rb +129 -0
  46. data/lib/rapns/deprecatable.rb +23 -0
  47. data/lib/rapns/deprecation.rb +23 -0
  48. data/lib/rapns/embed.rb +28 -0
  49. data/lib/rapns/gcm/app.rb +7 -0
  50. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  51. data/lib/rapns/gcm/notification.rb +37 -0
  52. data/lib/rapns/gcm/payload_data_size_validator.rb +13 -0
  53. data/lib/rapns/gcm/registration_ids_count_validator.rb +13 -0
  54. data/lib/rapns/logger.rb +76 -0
  55. data/lib/rapns/multi_json_helper.rb +16 -0
  56. data/lib/rapns/notification.rb +62 -0
  57. data/lib/rapns/notifier.rb +35 -0
  58. data/lib/rapns/push.rb +17 -0
  59. data/lib/rapns/rails-2-compatibility.rb +34 -0
  60. data/lib/rapns/reflection.rb +44 -0
  61. data/lib/rapns/upgraded.rb +31 -0
  62. data/lib/rapns/version.rb +3 -0
  63. data/lib/rapns_rails_2.rb +67 -0
  64. data/lib/tasks/cane.rake +18 -0
  65. data/lib/tasks/test.rake +38 -0
  66. data/spec/support/cert_with_password.pem +90 -0
  67. data/spec/support/cert_without_password.pem +59 -0
  68. data/spec/support/simplecov_helper.rb +13 -0
  69. data/spec/support/simplecov_quality_formatter.rb +8 -0
  70. data/spec/tmp/.gitkeep +0 -0
  71. data/spec/unit/apns/app_spec.rb +29 -0
  72. data/spec/unit/apns/feedback_spec.rb +9 -0
  73. data/spec/unit/apns/notification_spec.rb +215 -0
  74. data/spec/unit/apns_feedback_spec.rb +21 -0
  75. data/spec/unit/app_spec.rb +16 -0
  76. data/spec/unit/configuration_spec.rb +55 -0
  77. data/spec/unit/daemon/apns/app_runner_spec.rb +45 -0
  78. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  79. data/spec/unit/daemon/apns/connection_spec.rb +287 -0
  80. data/spec/unit/daemon/apns/delivery_handler_spec.rb +59 -0
  81. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  82. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  83. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +134 -0
  84. data/spec/unit/daemon/app_runner_shared.rb +83 -0
  85. data/spec/unit/daemon/app_runner_spec.rb +170 -0
  86. data/spec/unit/daemon/batch_spec.rb +219 -0
  87. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  88. data/spec/unit/daemon/delivery_handler_collection_spec.rb +37 -0
  89. data/spec/unit/daemon/delivery_handler_shared.rb +45 -0
  90. data/spec/unit/daemon/feeder_spec.rb +81 -0
  91. data/spec/unit/daemon/gcm/app_runner_spec.rb +19 -0
  92. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +44 -0
  93. data/spec/unit/daemon/gcm/delivery_spec.rb +289 -0
  94. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  95. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  96. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  97. data/spec/unit/daemon/store/active_record_spec.rb +281 -0
  98. data/spec/unit/daemon_spec.rb +157 -0
  99. data/spec/unit/deprecatable_spec.rb +32 -0
  100. data/spec/unit/deprecation_spec.rb +15 -0
  101. data/spec/unit/embed_spec.rb +50 -0
  102. data/spec/unit/gcm/app_spec.rb +4 -0
  103. data/spec/unit/gcm/notification_spec.rb +52 -0
  104. data/spec/unit/logger_spec.rb +180 -0
  105. data/spec/unit/notification_shared.rb +45 -0
  106. data/spec/unit/notification_spec.rb +4 -0
  107. data/spec/unit/notifier_spec.rb +32 -0
  108. data/spec/unit/push_spec.rb +44 -0
  109. data/spec/unit/rapns_spec.rb +9 -0
  110. data/spec/unit/reflection_spec.rb +30 -0
  111. data/spec/unit/upgraded_spec.rb +40 -0
  112. data/spec/unit_spec_helper.rb +137 -0
  113. metadata +232 -0
@@ -0,0 +1,287 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::Apns::Connection 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) { Rapns::Daemon::Apns::Connection.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
+ Rapns.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(:apns_certificate_will_expire, app, cert.not_after)
88
+ Timecop.freeze(cert.not_after.to_datetime - 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.to_datetime - 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(:apns_certificate_will_expire, app, kind_of(Time))
100
+ Timecop.freeze(cert.not_after.to_datetime - 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.to_datetime + 1.day) { connection.connect rescue Rapns::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.to_datetime + 1.day) do
112
+ expect { connection.connect }.to raise_error(Rapns::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(:apns_connection_lost, app, kind_of(error_type))
161
+ begin
162
+ connection.write(nil)
163
+ rescue Rapns::Daemon::Apns::ConnectionError
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 Rapns::Daemon::Apns::ConnectionError
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 Rapns::Daemon::Apns::ConnectionError
180
+ end
181
+ end
182
+
183
+ it "raises a ConnectionError after 3 attempts at reconnecting" do
184
+ expect do
185
+ connection.write(nil)
186
+ end.to raise_error(Rapns::Daemon::Apns::ConnectionError, "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 Rapns::Daemon::Apns::ConnectionError
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
+ Rapns::Daemon::Apns::Connection.stub(:idle_period => 0.1)
263
+ sleep 0.2
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 == 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
+ Rapns::Daemon::Apns::Connection.stub(:idle_period => 0.1)
282
+ sleep 0.2
283
+ Rapns.logger.should_receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
284
+ connection.write('blah')
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path("spec/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) { 'gateway.push.apple.com' }
8
+ let(:port) { 2195 }
9
+ let(:certificate) { double }
10
+ let(:password) { double }
11
+ let(:app) { double(:password => password, :certificate => certificate, :name => 'MyApp', :environment => 'production')}
12
+ let(:delivery_handler) { Rapns::Daemon::Apns::DeliveryHandler.new(app) }
13
+ let(:connection) { double(:connection, :select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
14
+ let(:notification) { double(:notification) }
15
+ let(:batch) { double(:batch, :notification_processed => nil) }
16
+ let(:http) { double(:http, :shutdown => nil)}
17
+ let(:queue) { Queue.new }
18
+ let(:delivery) { double(:perform => nil) }
19
+
20
+ before do
21
+ Rapns::Daemon::Apns::Connection.stub(:new => connection)
22
+ Rapns::Daemon::Apns::Delivery.stub(:new => delivery)
23
+ delivery_handler.queue = queue
24
+ queue.push([notification, batch])
25
+ end
26
+
27
+ def run_delivery_handler
28
+ delivery_handler.start
29
+ delivery_handler.stop
30
+ delivery_handler.wakeup
31
+ delivery_handler.wait
32
+ end
33
+
34
+ it "instantiates a new connection" do
35
+ Rapns::Daemon::Apns::Connection.should_receive(:new).with(app, host, port)
36
+ run_delivery_handler
37
+ end
38
+
39
+ it "connects the socket" do
40
+ connection.should_receive(:connect)
41
+ run_delivery_handler
42
+ end
43
+
44
+ it 'performs delivery of an notification' do
45
+ Rapns::Daemon::Apns::Delivery.should_receive(:new).with(app, connection, notification, batch).and_return(delivery)
46
+ delivery.should_receive(:perform)
47
+ run_delivery_handler
48
+ end
49
+
50
+ it 'closes the connection stopped' do
51
+ connection.should_receive(:close)
52
+ run_delivery_handler
53
+ end
54
+
55
+ it 'does not attempt to close the connection if the connection was not established' do
56
+ connection.should_not_receive(:close)
57
+ delivery_handler.stop
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::Apns::Delivery do
4
+ let(:app) { double(:name => 'MyApp') }
5
+ let(:notification) { double(:id => 123).as_null_object }
6
+ let(:batch) { double(:mark_failed => nil, :mark_delivered => nil) }
7
+ let(:logger) { double(:error => nil, :info => nil) }
8
+ let(:config) { double(:check_for_errors => true) }
9
+ let(:connection) { double(:select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
10
+ let(:delivery) { Rapns::Daemon::Apns::Delivery.new(app, connection, notification, batch) }
11
+
12
+ def perform
13
+ begin
14
+ delivery.perform
15
+ rescue Rapns::DeliveryError, Rapns::Apns::DisconnectionError
16
+ end
17
+ end
18
+
19
+ before do
20
+ Rapns.stub(:config => config, :logger => logger)
21
+ end
22
+
23
+ it "sends the binary version of the notification" do
24
+ notification.stub(:to_binary => "hi mom")
25
+ connection.should_receive(:write).with("hi mom")
26
+ perform
27
+ end
28
+
29
+ it "logs the notification delivery" do
30
+ notification.stub(:id => 666, :device_token => 'abc123')
31
+ logger.should_receive(:info).with("[MyApp] 666 sent to abc123")
32
+ perform
33
+ end
34
+
35
+ it "marks the notification as delivered" do
36
+ batch.should_receive(:mark_delivered).with(notification)
37
+ perform
38
+ end
39
+
40
+ it 'does not check for errors if check_for_errors config option is false' do
41
+ config.stub(:check_for_errors => false)
42
+ delivery.should_not_receive(:check_for_error)
43
+ perform
44
+ end
45
+
46
+ describe "when delivery fails" do
47
+ before { connection.stub(:select => true, :read => [8, 4, 69].pack("ccN")) }
48
+
49
+ it "marks the notification as failed" do
50
+ batch.should_receive(:mark_failed).with(notification, 4, "Missing payload")
51
+ perform
52
+ end
53
+
54
+ it "logs the delivery error" do
55
+ # checking for the doublebed error doesn't work in jruby, but checking
56
+ # for the exception by class does.
57
+
58
+ #error = Rapns::DeliveryError.new(4, 12, "Missing payload")
59
+ #Rapns::DeliveryError.stub(:new => error)
60
+ #expect { delivery.perform }.to raise_error(error)
61
+
62
+ expect { delivery.perform }.to raise_error(Rapns::DeliveryError)
63
+ end
64
+
65
+ it "reads 6 bytes from the socket" do
66
+ connection.should_receive(:read).with(6).and_return(nil)
67
+ perform
68
+ end
69
+
70
+ it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do
71
+ connection.stub(:select => nil)
72
+ connection.should_not_receive(:read)
73
+ perform
74
+ end
75
+
76
+ it "reconnects the socket" do
77
+ connection.should_receive(:reconnect)
78
+ perform
79
+ end
80
+
81
+ it "logs that the connection is being reconnected" do
82
+ Rapns.logger.should_receive(:error).with("[MyApp] Error received, reconnecting...")
83
+ perform
84
+ end
85
+
86
+ context "when the APNs disconnects without returning an error" do
87
+ before do
88
+ connection.stub(:read => nil)
89
+ end
90
+
91
+ it 'raises a DisconnectError error if the connection is closed without an error being returned' do
92
+ expect { delivery.perform }.to raise_error(Rapns::Apns::DisconnectionError)
93
+ end
94
+
95
+ it 'marks the notification as failed' do
96
+ batch.should_receive(:mark_failed).with(notification, nil, "APNs disconnected without returning an error.")
97
+ perform
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path("spec/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,134 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
4
+
5
+ let(:host) { 'feedback.push.apple.com' }
6
+ let(:port) { 2196 }
7
+ let(:poll) { 60 }
8
+ let(:certificate) { double }
9
+ let(:password) { double }
10
+ let(:app) { double(:name => 'my_app', :password => password, :certificate => certificate, :environment => 'production') }
11
+ let(:connection) { double(:connect => nil, :read => nil, :close => nil) }
12
+ let(:logger) { double(:error => nil, :info => nil) }
13
+ let(:receiver) { Rapns::Daemon::Apns::FeedbackReceiver.new(app, poll) }
14
+ let(:feedback) { double }
15
+
16
+ before do
17
+ receiver.stub(:interruptible_sleep)
18
+ Rapns.stub(:logger => logger)
19
+ Rapns::Daemon::Apns::Connection.stub(:new => connection)
20
+ receiver.instance_variable_set("@stop", false)
21
+ Rapns::Daemon.stub(:store => double(:create_apns_feedback => feedback))
22
+ end
23
+
24
+ def double_connection_read_with_tuple
25
+ connection.unstub(:read)
26
+
27
+ def connection.read(bytes)
28
+ if !@called
29
+ @called = true
30
+ "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"
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'instantiates a new connection' do
36
+ Rapns::Daemon::Apns::Connection.should_receive(:new).with(app, host, port)
37
+ receiver.check_for_feedback
38
+ end
39
+
40
+ it 'connects to the feeback service' do
41
+ connection.should_receive(:connect)
42
+ receiver.check_for_feedback
43
+ end
44
+
45
+ it 'closes the connection' do
46
+ connection.should_receive(:close)
47
+ receiver.check_for_feedback
48
+ end
49
+
50
+ it 'reads from the connection' do
51
+ connection.should_receive(:read).with(38)
52
+ receiver.check_for_feedback
53
+ end
54
+
55
+ it 'logs the feedback' do
56
+ double_connection_read_with_tuple
57
+ Rapns.logger.should_receive(:info).with("[my_app] [FeedbackReceiver] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17.")
58
+ receiver.check_for_feedback
59
+ end
60
+
61
+ it 'creates the feedback' do
62
+ Rapns::Daemon.store.should_receive(:create_apns_feedback).with(Time.at(1323533325), '834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17', app)
63
+ double_connection_read_with_tuple
64
+ receiver.check_for_feedback
65
+ end
66
+
67
+ it 'logs errors' do
68
+ error = StandardError.new('bork!')
69
+ connection.stub(:read).and_raise(error)
70
+ Rapns.logger.should_receive(:error).with(error)
71
+ receiver.check_for_feedback
72
+ end
73
+
74
+ it 'sleeps for the feedback poll period' do
75
+ receiver.stub(:check_for_feedback)
76
+ sleeper = double(Rapns::Daemon::InterruptibleSleep, :sleep => false)
77
+ sleeper.should_receive(:sleep).with(60).at_least(:once)
78
+ receiver.stub :interruptible_sleep => sleeper
79
+ Thread.stub(:new).and_yield
80
+ receiver.stub(:loop).and_yield
81
+ receiver.start
82
+ end
83
+
84
+ it 'checks for feedback when started' do
85
+ sleeper = double(Rapns::Daemon::InterruptibleSleep, :sleep => false)
86
+ receiver.stub :interruptible_sleep => sleeper
87
+ receiver.should_receive(:check_for_feedback).at_least(:once)
88
+ Thread.stub(:new).and_yield
89
+ receiver.stub(:loop).and_yield
90
+ receiver.start
91
+ end
92
+
93
+ it 'interrupts sleep when stopped' do
94
+ sleeper = double(Rapns::Daemon::InterruptibleSleep, :sleep => false)
95
+ receiver.stub :interruptible_sleep => sleeper
96
+ receiver.stub(:check_for_feedback)
97
+ sleeper.should_receive(:interrupt_sleep)
98
+ receiver.stop
99
+ end
100
+
101
+ it 'reflects feedback was received' do
102
+ double_connection_read_with_tuple
103
+ receiver.should_receive(:reflect).with(:apns_feedback, feedback)
104
+ receiver.check_for_feedback
105
+ end
106
+
107
+ it 'calls the apns_feedback_callback when feedback is received and the callback is set' do
108
+ double_connection_read_with_tuple
109
+ Rapns.config.apns_feedback_callback = Proc.new {}
110
+ Rapns.config.apns_feedback_callback.should_receive(:call).with(feedback)
111
+ receiver.check_for_feedback
112
+ end
113
+
114
+ it 'catches exceptions in the apns_feedback_callback' do
115
+ error = StandardError.new('bork!')
116
+ double_connection_read_with_tuple
117
+ callback = Proc.new { raise error }
118
+ Rapns::Deprecation.muted do
119
+ Rapns.config.on_apns_feedback &callback
120
+ end
121
+ lambda { receiver.check_for_feedback }.should_not raise_error
122
+ end
123
+
124
+ it 'logs an exception from the apns_feedback_callback' do
125
+ error = StandardError.new('bork!')
126
+ double_connection_read_with_tuple
127
+ callback = Proc.new { raise error }
128
+ Rapns.logger.should_receive(:error).with(error)
129
+ Rapns::Deprecation.muted do
130
+ Rapns.config.on_apns_feedback &callback
131
+ end
132
+ receiver.check_for_feedback
133
+ end
134
+ end
@@ -0,0 +1,83 @@
1
+ shared_examples_for "an AppRunner subclass" do
2
+ let(:queue) { double(:push => nil) }
3
+
4
+ before { Queue.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 'adds the delivery handler to the collection' do
14
+ handler_collection.should_receive(:push).with(handler)
15
+ runner.start
16
+ end
17
+
18
+ it 'assigns the queue to the handler' do
19
+ handler.should_receive(:queue=).with(queue)
20
+ runner.start
21
+ end
22
+ end
23
+
24
+ describe 'enqueue' do
25
+ let(:notification) { double }
26
+ let(:batch) { double(:notifications => [notification]) }
27
+
28
+ it 'enqueues the batch' do
29
+ queue.should_receive(:push).with([notification, batch])
30
+ runner.enqueue(batch)
31
+ end
32
+
33
+ it 'reflects the notification has been enqueued' do
34
+ runner.should_receive(:reflect).with(:notification_enqueued, notification)
35
+ runner.enqueue(batch)
36
+ end
37
+ end
38
+
39
+ describe 'stop' do
40
+ before { runner.start }
41
+
42
+ it 'stops the delivery handlers' do
43
+ handler_collection.should_receive(:stop)
44
+ runner.stop
45
+ end
46
+ end
47
+
48
+ describe 'idle?' do
49
+ it 'is idle if all notifications have been processed' do
50
+ runner.batch = double(:complete? => true)
51
+ runner.idle?.should be_true
52
+ end
53
+
54
+ it 'is idle if the runner has no associated batch' do
55
+ runner.batch = nil
56
+ runner.idle?.should be_true
57
+ end
58
+
59
+ it 'is not idle if not all notifications have been processed' do
60
+ runner.batch = double(:complete? => false)
61
+ runner.idle?.should be_false
62
+ end
63
+ end
64
+
65
+ describe 'sync' do
66
+ before { runner.start }
67
+
68
+ it 'reduces the number of handlers if needed' do
69
+ handler_collection.should_receive(:pop)
70
+ new_app = app_class.new
71
+ new_app.stub(:connections => app.connections - 1)
72
+ runner.sync(new_app)
73
+ end
74
+
75
+ it 'increases the number of handlers if needed' do
76
+ runner.should_receive(:start_handler).and_return(handler)
77
+ handler_collection.should_receive(:push).with(handler)
78
+ new_app = app_class.new
79
+ new_app.stub(:connections => app.connections + 1)
80
+ runner.sync(new_app)
81
+ end
82
+ end
83
+ end