rapns 0.2.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. data/README.md +35 -10
  2. data/lib/generators/rapns_generator.rb +9 -1
  3. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  4. data/lib/generators/templates/rapns.yml +22 -9
  5. data/lib/rapns/daemon/configuration.rb +32 -14
  6. data/lib/rapns/daemon/connection.rb +35 -50
  7. data/lib/rapns/daemon/delivery_handler.rb +65 -15
  8. data/lib/rapns/daemon/delivery_handler_pool.rb +1 -5
  9. data/lib/rapns/daemon/delivery_queue.rb +10 -27
  10. data/lib/rapns/daemon/feedback_receiver.rb +57 -0
  11. data/lib/rapns/daemon/feeder.rb +9 -7
  12. data/lib/rapns/daemon/interruptible_sleep.rb +14 -0
  13. data/lib/rapns/daemon/pool.rb +0 -5
  14. data/lib/rapns/daemon.rb +9 -9
  15. data/lib/rapns/device_token_format_validator.rb +10 -0
  16. data/lib/rapns/feedback.rb +10 -0
  17. data/lib/rapns/notification.rb +15 -1
  18. data/lib/rapns/version.rb +1 -1
  19. data/lib/rapns.rb +3 -1
  20. data/spec/rapns/daemon/certificate_spec.rb +6 -0
  21. data/spec/rapns/daemon/configuration_spec.rb +124 -40
  22. data/spec/rapns/daemon/connection_spec.rb +81 -129
  23. data/spec/rapns/daemon/delivery_handler_pool_spec.rb +1 -6
  24. data/spec/rapns/daemon/delivery_handler_spec.rb +117 -30
  25. data/spec/rapns/daemon/delivery_queue_spec.rb +29 -0
  26. data/spec/rapns/daemon/feedback_receiver_spec.rb +86 -0
  27. data/spec/rapns/daemon/feeder_spec.rb +25 -9
  28. data/spec/rapns/daemon/interruptible_sleep_spec.rb +32 -0
  29. data/spec/rapns/daemon/logger_spec.rb +34 -14
  30. data/spec/rapns/daemon_spec.rb +34 -31
  31. data/spec/rapns/feedback_spec.rb +12 -0
  32. data/spec/rapns/notification_spec.rb +5 -0
  33. data/spec/spec_helper.rb +5 -2
  34. metadata +16 -5
  35. data/lib/rapns/daemon/connection_pool.rb +0 -31
  36. data/spec/rapns/daemon/connection_pool_spec.rb +0 -40
@@ -10,10 +10,10 @@ describe Rapns::Daemon::Connection, "when setting up the SSL context" do
10
10
  Rapns::Daemon.stub(:certificate).and_return(@certificate)
11
11
  @x509_certificate = mock("X509 Certificate")
12
12
  OpenSSL::X509::Certificate.stub(:new).and_return(@x509_certificate)
13
- @connection = Rapns::Daemon::Connection.new("Connection 1")
13
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
14
14
  @connection.stub(:connect_socket)
15
15
  @connection.stub(:setup_at_exit_hook)
16
- configuration = mock("Configuration", :host => "localhost", :port => 123, :certificate_password => "abc123")
16
+ configuration = mock("Configuration", :certificate_password => "abc123")
17
17
  Rapns::Daemon.stub(:configuration).and_return(configuration)
18
18
  end
19
19
 
@@ -32,24 +32,20 @@ end
32
32
 
33
33
  describe Rapns::Daemon::Connection, "when connecting the socket" do
34
34
  before do
35
- @connection = Rapns::Daemon::Connection.new("Connection 1")
35
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
36
36
  @connection.stub(:setup_at_exit_hook)
37
37
  @ssl_context = mock("SSLContext")
38
38
  @connection.stub(:setup_ssl_context).and_return(@ssl_context)
39
39
  @tcp_socket = mock("TCPSocket", :close => nil, :setsockopt => nil)
40
40
  TCPSocket.stub(:new).and_return(@tcp_socket)
41
- Rapns::Daemon::Configuration.stub(:host).and_return("localhost")
42
- Rapns::Daemon::Configuration.stub(:port).and_return(123)
43
41
  @ssl_socket = mock("SSLSocket", :sync= => nil, :connect => nil, :close => nil)
44
42
  OpenSSL::SSL::SSLSocket.stub(:new).and_return(@ssl_socket)
45
- configuration = mock("Configuration", :host => "localhost", :port => 123)
46
- Rapns::Daemon.stub(:configuration).and_return(configuration)
47
43
  @logger = mock("Logger", :info => nil)
48
44
  Rapns::Daemon.stub(:logger).and_return(@logger)
49
45
  end
50
46
 
51
47
  it "should create a TCP socket using the configured host and port" do
52
- TCPSocket.should_receive(:new).with("localhost", 123).and_return(@tcp_socket)
48
+ TCPSocket.should_receive(:new).with('gateway.push.apple.com', 2195).and_return(@tcp_socket)
53
49
  @connection.connect
54
50
  end
55
51
 
@@ -81,7 +77,7 @@ end
81
77
 
82
78
  describe Rapns::Daemon::Connection, "when shuting down the connection" do
83
79
  before do
84
- @connection = Rapns::Daemon::Connection.new("Connection 1")
80
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
85
81
  @connection.stub(:setup_ssl_context)
86
82
  @ssl_socket = mock("SSLSocket", :close => nil)
87
83
  @tcp_socket = mock("TCPSocket", :close => nil)
@@ -113,69 +109,58 @@ describe Rapns::Daemon::Connection, "when shuting down the connection" do
113
109
  @connection.instance_variable_set("@ssl_socket", nil)
114
110
  @connection.close
115
111
  end
116
- end
117
112
 
118
- describe Rapns::Daemon::Connection, "when the connection is lost" do
119
- before do
120
- @connection = Rapns::Daemon::Connection.new("Connection 1")
121
- @ssl_socket = mock("SSLSocket")
122
- @connection.instance_variable_set("@ssl_socket", @ssl_socket)
123
- @connection.stub(:connect_socket).and_return([mock("TCPSocket"), @ssl_socket])
124
- @ssl_socket.stub(:write).and_raise(Errno::EPIPE)
125
- @logger = mock("Logger", :error => nil)
126
- Rapns::Daemon.stub(:logger).and_return(@logger)
127
- @connection.stub(:sleep)
128
- configuration = mock("Configuration", :host => "localhost", :port => 123)
129
- Rapns::Daemon.stub(:configuration).and_return(configuration)
113
+ it "should ignore IOError when the socket is already closed" do
114
+ @tcp_socket.stub(:close).and_raise(IOError)
115
+ @connection.connect
116
+ expect {@connection.close }.should_not raise_error(IOError)
130
117
  end
118
+ end
131
119
 
132
- it "should log a error" do
133
- Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Lost connection to localhost:123, reconnecting...")
134
- begin
135
- @connection.write(nil)
136
- rescue Rapns::Daemon::ConnectionError
137
- end
120
+ describe Rapns::Daemon::Connection, "read" do
121
+ before do
122
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
123
+ @connection.stub(:setup_ssl_context)
124
+ @ssl_socket = mock("SSLSocket", :close => nil)
125
+ @tcp_socket = mock("TCPSocket", :close => nil)
126
+ @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
138
127
  end
139
128
 
140
- it "should retry to make a connection 3 times" do
141
- @connection.should_receive(:connect_socket).exactly(3).times
142
- begin
143
- @connection.write(nil)
144
- rescue Rapns::Daemon::ConnectionError
145
- end
129
+ it "reads the number of bytes from the SSL socket" do
130
+ @ssl_socket.should_receive(:read).with(123)
131
+ @connection.connect
132
+ @connection.read(123)
146
133
  end
134
+ end
147
135
 
148
- it "should raise a ConnectionError after 3 attempts at reconnecting" do
149
- expect do
150
- @connection.write(nil)
151
- end.to raise_error(Rapns::Daemon::ConnectionError, "Connection 1 tried 3 times to reconnect but failed: #<Errno::EPIPE: Broken pipe>")
136
+ describe Rapns::Daemon::Connection, "select" do
137
+ before do
138
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
139
+ @connection.stub(:setup_ssl_context)
140
+ @ssl_socket = mock("SSLSocket", :close => nil)
141
+ @tcp_socket = mock("TCPSocket", :close => nil)
142
+ @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
152
143
  end
153
144
 
154
- it "should sleep 1 second before retrying the connection" do
155
- @connection.should_receive(:sleep).with(1)
156
- begin
157
- @connection.write(nil)
158
- rescue Rapns::Daemon::ConnectionError
159
- end
145
+ it "selects on the SSL socket until the given timeout" do
146
+ IO.should_receive(:select).with([@ssl_socket], nil, nil, 10)
147
+ @connection.connect
148
+ @connection.select(10)
160
149
  end
161
150
  end
162
151
 
163
- describe Rapns::Daemon::Connection, "when an SSL error occurs during write" do
152
+ shared_examples_for "when the write fails" do
164
153
  before do
165
- @connection = Rapns::Daemon::Connection.new("Connection 1")
166
- @ssl_socket = mock("SSLSocket")
167
- @connection.instance_variable_set("@ssl_socket", @ssl_socket)
168
- @connection.stub(:connect_socket).and_return([mock("TCPSocket"), @ssl_socket])
169
- @ssl_socket.stub(:write).and_raise(OpenSSL::SSL::SSLError)
154
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
170
155
  @logger = mock("Logger", :error => nil)
171
156
  Rapns::Daemon.stub(:logger).and_return(@logger)
157
+ @connection.stub(:reconnect)
158
+ @connection.stub(:write_data).and_raise(error_type)
172
159
  @connection.stub(:sleep)
173
- configuration = mock("Configuration", :host => "localhost", :port => 123)
174
- Rapns::Daemon.stub(:configuration).and_return(configuration)
175
160
  end
176
161
 
177
- it "should log a error" do
178
- Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Lost connection to localhost:123, reconnecting...")
162
+ it "should log that the connection has been lost once only" do
163
+ Rapns::Daemon.logger.should_receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error_type.name}), reconnecting...").once
179
164
  begin
180
165
  @connection.write(nil)
181
166
  rescue Rapns::Daemon::ConnectionError
@@ -183,7 +168,7 @@ describe Rapns::Daemon::Connection, "when an SSL error occurs during write" do
183
168
  end
184
169
 
185
170
  it "should retry to make a connection 3 times" do
186
- @connection.should_receive(:connect_socket).exactly(3).times
171
+ @connection.should_receive(:reconnect).exactly(3).times
187
172
  begin
188
173
  @connection.write(nil)
189
174
  rescue Rapns::Daemon::ConnectionError
@@ -193,7 +178,7 @@ describe Rapns::Daemon::Connection, "when an SSL error occurs during write" do
193
178
  it "should raise a ConnectionError after 3 attempts at reconnecting" do
194
179
  expect do
195
180
  @connection.write(nil)
196
- end.to raise_error(Rapns::Daemon::ConnectionError, "Connection 1 tried 3 times to reconnect but failed: #<OpenSSL::SSL::SSLError: OpenSSL::SSL::SSLError>")
181
+ end.to raise_error(Rapns::Daemon::ConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error_type.name}).")
197
182
  end
198
183
 
199
184
  it "should sleep 1 second before retrying the connection" do
@@ -205,98 +190,65 @@ describe Rapns::Daemon::Connection, "when an SSL error occurs during write" do
205
190
  end
206
191
  end
207
192
 
208
- describe Rapns::Daemon::Connection, "when sending a notification" do
209
- before do
210
- @connection = Rapns::Daemon::Connection.new("Connection 1")
211
- @ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil)
212
- @tcp_socket = mock("TCPSocket", :close => nil)
213
- @connection.stub(:setup_ssl_context)
214
- @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
215
- @connection.stub(:check_for_error)
216
- @connection.connect
217
- end
218
-
219
- it "should write the data to the SSL socket" do
220
- @ssl_socket.should_receive(:write).with("blah")
221
- @connection.write("blah")
222
- end
223
-
224
- it "should flush the SSL socket" do
225
- @ssl_socket.should_receive(:flush)
226
- @connection.write("blah")
227
- end
193
+ describe Rapns::Daemon::Connection, "when write raises an Errno::EPIPE" do
194
+ it_should_behave_like "when the write fails"
228
195
 
229
- it "should select check for an error packet" do
230
- @connection.should_receive(:check_for_error)
231
- @connection.write("blah")
196
+ def error_type
197
+ Errno::EPIPE
232
198
  end
233
199
  end
234
200
 
235
- describe Rapns::Daemon::Connection, "when receiving an error packet" do
236
- before do
237
- @notification = Rapns::Notification.create!(:device_token => "a" * 64)
238
- @notification.stub(:save!)
239
- @connection = Rapns::Daemon::Connection.new("Connection 1")
240
- @ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil, :read => [8, 4, @notification.id].pack("ccN"))
241
- @connection.stub(:setup_ssl_context)
242
- @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
243
- IO.stub(:select).and_return([@ssl_socket, [], []])
244
- logger = mock("Logger", :error => nil, :warn => nil)
245
- Rapns::Daemon.stub(:logger).and_return(logger)
246
- @connection.connect
247
- end
201
+ describe Rapns::Daemon::Connection, "when write raises an Errno::ETIMEDOUT" do
202
+ it_should_behave_like "when the write fails"
248
203
 
249
- it "should raise a DeliveryError when an error is received" do
250
- expect { @connection.write("msg with an error") }.should raise_error(Rapns::DeliveryError)
204
+ def error_type
205
+ Errno::ETIMEDOUT
251
206
  end
207
+ end
252
208
 
253
- it "should not raise a DeliveryError if the packet cmd value is not 8" do
254
- @ssl_socket.stub(:read).and_return([6, 4, 12].pack("ccN"))
255
- expect { @connection.write("msg with an error") }.should_not raise_error(Rapns::DeliveryError)
256
- end
209
+ describe Rapns::Daemon::Connection, "when write raises an OpenSSL::SSL::SSLError" do
210
+ it_should_behave_like "when the write fails"
257
211
 
258
- it "should not raise a DeliveryError if the status code is 0 (no error)" do
259
- @ssl_socket.stub(:read).and_return([8, 0, 12].pack("ccN"))
260
- expect { @connection.write("msg with an error") }.should_not raise_error(Rapns::DeliveryError)
212
+ def error_type
213
+ OpenSSL::SSL::SSLError
261
214
  end
215
+ end
262
216
 
263
- it "should read 6 bytes from the socket" do
264
- @ssl_socket.should_receive(:read).with(6).and_return(nil)
265
- @connection.write("msg with an error")
217
+ describe Rapns::Daemon::Connection, "when reconnecting" do
218
+ before do
219
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
220
+ @connection.stub(:close)
221
+ @connection.stub(:connect_socket)
266
222
  end
267
223
 
268
- it "should not attempt to read from the socket if the socket was not selected for reading after the timeout" do
269
- IO.stub(:select).and_return(nil)
270
- @ssl_socket.should_not_receive(:read)
271
- @connection.write("msg with an error")
224
+ it 'closes the socket' do
225
+ @connection.should_receive(:close)
226
+ @connection.send(:reconnect)
272
227
  end
273
228
 
274
- it "should not raise a DeliveryError if the socket read returns nothing" do
275
- @ssl_socket.stub(:read).with(6).and_return(nil)
276
- expect { @connection.write("msg with an error") }.should_not raise_error(Rapns::DeliveryError)
229
+ it 'connects the socket' do
230
+ @connection.should_receive(:connect_socket)
231
+ @connection.send(:reconnect)
277
232
  end
233
+ end
278
234
 
279
- it "should close the socket after handling the error" do
280
- @connection.should_receive(:close)
281
- begin
282
- @connection.write("msg with an error")
283
- rescue Rapns::DeliveryError
284
- end
235
+ describe Rapns::Daemon::Connection, "when sending a notification" do
236
+ before do
237
+ @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
238
+ @ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil)
239
+ @tcp_socket = mock("TCPSocket", :close => nil)
240
+ @connection.stub(:setup_ssl_context)
241
+ @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
242
+ @connection.connect
285
243
  end
286
244
 
287
- it "should reconnect the socket" do
288
- @connection.should_receive(:connect_socket)
289
- begin
290
- @connection.write("msg with an error")
291
- rescue Rapns::DeliveryError
292
- end
245
+ it "should write the data to the SSL socket" do
246
+ @ssl_socket.should_receive(:write).with("blah")
247
+ @connection.write("blah")
293
248
  end
294
249
 
295
- it "should log that the connection is being reconnected" do
296
- Rapns::Daemon.logger.should_receive(:error).with("[Connection 1] Error received, reconnecting...")
297
- begin
298
- @connection.write("msg with an error")
299
- rescue Rapns::DeliveryError
300
- end
250
+ it "should flush the SSL socket" do
251
+ @ssl_socket.should_receive(:flush)
252
+ @connection.write("blah")
301
253
  end
302
254
  end
@@ -13,14 +13,9 @@ describe Rapns::Daemon::DeliveryHandlerPool do
13
13
  @pool.populate
14
14
  end
15
15
 
16
- it "should tell each connection to close when drained" do
16
+ it "waits for each handle to stop" do
17
17
  @pool.populate
18
18
  @handler.should_receive(:stop).exactly(3).times
19
19
  @pool.drain
20
20
  end
21
-
22
- it "should initiate the topping process for each DeliveryHandler before the pool is drained" do
23
- Rapns::Daemon.delivery_queue.should_receive(:push).with(0x666).exactly(3).times
24
- @pool.drain
25
- end
26
21
  end
@@ -1,115 +1,202 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Rapns::Daemon::DeliveryHandler do
4
+ let(:delivery_handler) { Rapns::Daemon::DeliveryHandler.new(0) }
5
+
4
6
  before do
5
7
  @notification = Rapns::Notification.create!(:device_token => "a" * 64)
6
- Rapns::Daemon.stub(:delivery_queue).and_return(Rapns::Daemon::DeliveryQueue.new(1))
8
+ Rapns::Daemon.stub(:delivery_queue).and_return(Rapns::Daemon::DeliveryQueue.new)
7
9
  Rapns::Daemon.delivery_queue.push(@notification)
8
- @connection = mock("Connection", :connect => nil, :write => nil)
10
+ @connection = mock("Connection", :connect => nil, :write => nil, :close => nil, :select => nil, :read => nil)
9
11
  Rapns::Daemon::Connection.stub(:new).and_return(@connection)
10
- @connection_pool = Rapns::Daemon::ConnectionPool.new(1)
11
- @connection_pool.populate
12
- Rapns::Daemon.stub(:connection_pool).and_return(@connection_pool)
13
- @delivery_handler = Rapns::Daemon::DeliveryHandler.new
12
+ configuration = mock("Configuration", :push => stub(:host => "gateway.push.apple.com", :port => 2195))
13
+ Rapns::Daemon.stub(:configuration).and_return(configuration)
14
14
  @logger = mock("Logger", :error => nil, :info => nil)
15
15
  Rapns::Daemon.stub(:logger).and_return(@logger)
16
16
  end
17
17
 
18
+ it "instantiates a new connection" do
19
+ Rapns::Daemon::Connection.should_receive(:new).with('DeliveryHandler 0', 'gateway.push.apple.com', 2195)
20
+ delivery_handler
21
+ end
22
+
23
+ it "connects the socket when started" do
24
+ @connection.should_receive(:connect)
25
+ delivery_handler.start
26
+ delivery_handler.stop
27
+ end
28
+
29
+ it "pushes a STOP instruction into the queue when told to stop" do
30
+ Rapns::Daemon.delivery_queue.should_receive(:push).with(Rapns::Daemon::DeliveryHandler::STOP)
31
+ delivery_handler.stop
32
+ end
33
+
34
+ it "closes the connection when a STOP instruction is received" do
35
+ Rapns::Daemon.delivery_queue.push(Rapns::Daemon::DeliveryHandler::STOP)
36
+ delivery_handler.send(:handle_next_notification)
37
+ end
38
+
18
39
  it "should pop a new notification from the delivery queue" do
19
40
  Rapns::Daemon.delivery_queue.should_receive(:pop)
20
- @delivery_handler.send(:handle_next_notification)
41
+ delivery_handler.send(:handle_next_notification)
21
42
  end
22
43
 
23
- it "should claim a connection for the delivery" do
24
- Rapns::Daemon.connection_pool.should_receive(:claim_connection)
25
- @delivery_handler.send(:handle_next_notification)
44
+ it "does not attempt to deliver a notification when a STOP instruction is received" do
45
+ Rapns::Daemon.delivery_queue.pop # empty the queue
46
+ delivery_handler.should_not_receive(:deliver)
47
+ Rapns::Daemon.delivery_queue.push(Rapns::Daemon::DeliveryHandler::STOP)
48
+ delivery_handler.send(:handle_next_notification)
26
49
  end
27
50
 
28
51
  it "should send the binary version of the notification" do
29
52
  @notification.stub((:to_binary)).and_return("hi mom")
30
53
  @connection.should_receive(:write).with("hi mom")
31
- @delivery_handler.send(:handle_next_notification)
54
+ delivery_handler.send(:handle_next_notification)
32
55
  end
33
56
 
34
57
  it "should log the notification delivery" do
35
58
  Rapns::Daemon.logger.should_receive(:info).with("Notification #{@notification.id} delivered to #{@notification.device_token}")
36
- @delivery_handler.send(:handle_next_notification)
59
+ delivery_handler.send(:handle_next_notification)
37
60
  end
38
61
 
39
62
  it "should mark the notification as delivered" do
40
- expect { @delivery_handler.send(:handle_next_notification); @notification.reload }.to change(@notification, :delivered).to(true)
63
+ expect { delivery_handler.send(:handle_next_notification); @notification.reload }.to change(@notification, :delivered).to(true)
41
64
  end
42
65
 
43
66
  it "should set the time the notification was delivered" do
44
67
  @notification.delivered_at.should be_nil
45
- @delivery_handler.send(:handle_next_notification)
68
+ delivery_handler.send(:handle_next_notification)
46
69
  @notification.reload
47
70
  @notification.delivered_at.should be_kind_of(Time)
48
71
  end
49
72
 
50
73
  it "should not trigger validations when saving the notification" do
51
74
  @notification.should_receive(:save!).with(:validate => false)
52
- @delivery_handler.send(:handle_next_notification)
75
+ delivery_handler.send(:handle_next_notification)
53
76
  end
54
77
 
55
78
  it "should log if an error is raised when updating the notification" do
56
79
  e = StandardError.new("bork!")
57
80
  @notification.stub(:save!).and_raise(e)
58
81
  Rapns::Daemon.logger.should_receive(:error).with(e)
59
- @delivery_handler.send(:handle_next_notification)
82
+ delivery_handler.send(:handle_next_notification)
60
83
  end
61
84
 
62
- it "should notify the delivery queue a handler is now available" do
63
- Rapns::Daemon.delivery_queue.should_receive(:handler_available)
64
- @delivery_handler.send(:handle_next_notification)
85
+ it "should notify the delivery queue the notification has been processed" do
86
+ Rapns::Daemon.delivery_queue.should_receive(:notification_processed)
87
+ delivery_handler.send(:handle_next_notification)
65
88
  end
66
89
 
67
90
  describe "when delivery fails" do
68
91
  before do
69
- @error = Rapns::DeliveryError.new(4, "Missing payload", 1)
70
- @connection.stub(:write).and_raise(@error)
92
+ @connection.stub(:select => true, :read => [8, 4, 69].pack("ccN"), :reconnect => nil)
93
+ @error = Rapns::DeliveryError.new(4, "Missing payload", 69)
94
+ Rapns::DeliveryError.stub(:new => @error)
71
95
  end
72
96
 
73
97
  it "should set the notification as not delivered" do
74
98
  @notification.should_receive(:delivered=).with(false)
75
- @delivery_handler.send(:handle_next_notification)
99
+ delivery_handler.send(:handle_next_notification)
76
100
  end
77
101
 
78
102
  it "should set the notification delivered_at timestamp to nil" do
79
103
  @notification.should_receive(:delivered_at=).with(nil)
80
- @delivery_handler.send(:handle_next_notification)
104
+ delivery_handler.send(:handle_next_notification)
81
105
  end
82
106
 
83
107
  it "should set the notification as failed" do
84
108
  @notification.should_receive(:failed=).with(true)
85
- @delivery_handler.send(:handle_next_notification)
109
+ delivery_handler.send(:handle_next_notification)
86
110
  end
87
111
 
88
112
  it "should set the notification failed_at timestamp" do
89
113
  now = Time.now
90
114
  Time.stub(:now).and_return(now)
91
115
  @notification.should_receive(:failed_at=).with(now)
92
- @delivery_handler.send(:handle_next_notification)
116
+ delivery_handler.send(:handle_next_notification)
93
117
  end
94
118
 
95
119
  it "should set the notification error code" do
96
120
  @notification.should_receive(:error_code=).with(4)
97
- @delivery_handler.send(:handle_next_notification)
121
+ delivery_handler.send(:handle_next_notification)
98
122
  end
99
123
 
100
124
  it "should log the delivery error" do
101
125
  Rapns::Daemon.logger.should_receive(:error).with(@error)
102
- @delivery_handler.send(:handle_next_notification)
126
+ delivery_handler.send(:handle_next_notification)
103
127
  end
104
128
 
105
129
  it "should set the notification error description" do
106
130
  @notification.should_receive(:error_description=).with("Missing payload")
107
- @delivery_handler.send(:handle_next_notification)
131
+ delivery_handler.send(:handle_next_notification)
108
132
  end
109
133
 
110
134
  it "should skip validation when saving the notification" do
111
135
  @notification.should_receive(:save!).with(:validate => false)
112
- @delivery_handler.send(:handle_next_notification)
136
+ delivery_handler.send(:handle_next_notification)
137
+ end
138
+
139
+ it "should not raise a DeliveryError if the packet cmd value is not 8" do
140
+ @connection.stub(:read).and_return([6, 4, 12].pack("ccN"))
141
+ expect { delivery_handler.send(:handle_next_notification) }.should_not raise_error(Rapns::DeliveryError)
142
+ end
143
+
144
+ it "should not raise a DeliveryError if the status code is 0 (no error)" do
145
+ @connection.stub(:read).and_return([8, 0, 12].pack("ccN"))
146
+ expect { delivery_handler.send(:handle_next_notification) }.should_not raise_error(Rapns::DeliveryError)
147
+ end
148
+
149
+ it "should read 6 bytes from the socket" do
150
+ @connection.should_receive(:read).with(6).and_return(nil)
151
+ delivery_handler.send(:handle_next_notification)
152
+ end
153
+
154
+ it "should not attempt to read from the socket if the socket was not selected for reading after the timeout" do
155
+ @connection.stub(:select => nil)
156
+ @connection.should_not_receive(:read)
157
+ delivery_handler.send(:handle_next_notification)
158
+ end
159
+
160
+ it "should not raise a DeliveryError if the socket read returns nothing" do
161
+ @connection.stub(:read).with(6).and_return(nil)
162
+ expect { delivery_handler.send(:handle_next_notification) }.should_not raise_error(Rapns::DeliveryError)
163
+ end
164
+
165
+ it "should reconnect the socket" do
166
+ @connection.should_receive(:reconnect)
167
+ begin
168
+ delivery_handler.send(:handle_next_notification)
169
+ rescue Rapns::DeliveryError
170
+ end
171
+ end
172
+
173
+ it "should log that the connection is being reconnected" do
174
+ Rapns::Daemon.logger.should_receive(:error).with("[DeliveryHandler 0] Error received, reconnecting...")
175
+ begin
176
+ delivery_handler.send(:handle_next_notification)
177
+ rescue Rapns::DeliveryError
178
+ end
113
179
  end
114
180
  end
115
- end
181
+ end
182
+
183
+ # describe Rapns::Daemon::Connection, "when receiving an error packet" do
184
+ # before do
185
+ # @notification = Rapns::Notification.create!(:device_token => "a" * 64)
186
+ # @notification.stub(:save!)
187
+ # @connection = Rapns::Daemon::Connection.new('Connection 0', 'gateway.push.apple.com', 2195)
188
+ # @ssl_socket = mock("SSLSocket", :write => nil, :flush => nil, :close => nil, :read => [8, 4, @notification.id].pack("ccN"))
189
+ # @connection.stub(:setup_ssl_context)
190
+ # @connection.stub(:connect_socket).and_return([@tcp_socket, @ssl_socket])
191
+ # IO.stub(:select).and_return([@ssl_socket, [], []])
192
+ # logger = mock("Logger", :error => nil, :warn => nil)
193
+ # Rapns::Daemon.stub(:logger).and_return(logger)
194
+ # @connection.connect
195
+ # end
196
+ #
197
+ # it "should raise a DeliveryError when an error is received" do
198
+ # expect { @connection.write("msg with an error") }.should raise_error(Rapns::DeliveryError)
199
+ # end
200
+ #
201
+
202
+ # end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe Rapns::Daemon::DeliveryQueue do
4
+ let(:queue) { Rapns::Daemon::DeliveryQueue.new }
5
+
6
+ it 'behaves likes a normal qeue' do
7
+ obj = stub
8
+ queue.push obj
9
+ queue.pop.should == obj
10
+ end
11
+
12
+ it 'returns false if notifications have not all been processed' do
13
+ queue.push stub
14
+ queue.notifications_processed?.should be_false
15
+ end
16
+
17
+ it 'returns false if the queue is empty but notifications have not all been processed' do
18
+ queue.push stub
19
+ queue.pop
20
+ queue.notifications_processed?.should be_false
21
+ end
22
+
23
+ it 'returns true if all notifications have been processed' do
24
+ queue.push stub
25
+ queue.pop
26
+ queue.notification_processed
27
+ queue.notifications_processed?.should be_true
28
+ end
29
+ end