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.
Files changed (89) hide show
  1. data/CHANGELOG.md +31 -0
  2. data/LICENSE +7 -0
  3. data/README.md +138 -0
  4. data/bin/rapns +41 -0
  5. data/config/database.yml +39 -0
  6. data/lib/generators/rapns_generator.rb +34 -0
  7. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  8. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  9. data/lib/generators/templates/add_gcm.rb +94 -0
  10. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  11. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  12. data/lib/generators/templates/create_rapns_notifications.rb +26 -0
  13. data/lib/generators/templates/rapns.rb +39 -0
  14. data/lib/rapns/apns/app.rb +8 -0
  15. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  16. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  17. data/lib/rapns/apns/feedback.rb +14 -0
  18. data/lib/rapns/apns/notification.rb +86 -0
  19. data/lib/rapns/apns/required_fields_validator.rb +14 -0
  20. data/lib/rapns/app.rb +29 -0
  21. data/lib/rapns/configuration.rb +46 -0
  22. data/lib/rapns/daemon/apns/app_runner.rb +36 -0
  23. data/lib/rapns/daemon/apns/connection.rb +113 -0
  24. data/lib/rapns/daemon/apns/delivery.rb +63 -0
  25. data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
  26. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  27. data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
  28. data/lib/rapns/daemon/app_runner.rb +135 -0
  29. data/lib/rapns/daemon/database_reconnectable.rb +57 -0
  30. data/lib/rapns/daemon/delivery.rb +43 -0
  31. data/lib/rapns/daemon/delivery_error.rb +19 -0
  32. data/lib/rapns/daemon/delivery_handler.rb +46 -0
  33. data/lib/rapns/daemon/delivery_queue.rb +42 -0
  34. data/lib/rapns/daemon/delivery_queue_18.rb +44 -0
  35. data/lib/rapns/daemon/delivery_queue_19.rb +42 -0
  36. data/lib/rapns/daemon/feeder.rb +37 -0
  37. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  38. data/lib/rapns/daemon/gcm/delivery.rb +206 -0
  39. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  40. data/lib/rapns/daemon/interruptible_sleep.rb +18 -0
  41. data/lib/rapns/daemon/logger.rb +68 -0
  42. data/lib/rapns/daemon.rb +136 -0
  43. data/lib/rapns/gcm/app.rb +7 -0
  44. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  45. data/lib/rapns/gcm/notification.rb +31 -0
  46. data/lib/rapns/gcm/payload_size_validator.rb +13 -0
  47. data/lib/rapns/multi_json_helper.rb +16 -0
  48. data/lib/rapns/notification.rb +54 -0
  49. data/lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb +12 -0
  50. data/lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb +17 -0
  51. data/lib/rapns/patches.rb +6 -0
  52. data/lib/rapns/version.rb +3 -0
  53. data/lib/rapns.rb +21 -0
  54. data/lib/tasks/cane.rake +18 -0
  55. data/lib/tasks/test.rake +33 -0
  56. data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
  57. data/spec/acceptance_spec_helper.rb +85 -0
  58. data/spec/support/simplecov_helper.rb +13 -0
  59. data/spec/support/simplecov_quality_formatter.rb +8 -0
  60. data/spec/unit/apns/app_spec.rb +15 -0
  61. data/spec/unit/apns/feedback_spec.rb +12 -0
  62. data/spec/unit/apns/notification_spec.rb +198 -0
  63. data/spec/unit/app_spec.rb +18 -0
  64. data/spec/unit/configuration_spec.rb +38 -0
  65. data/spec/unit/daemon/apns/app_runner_spec.rb +39 -0
  66. data/spec/unit/daemon/apns/connection_spec.rb +234 -0
  67. data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
  68. data/spec/unit/daemon/apns/delivery_spec.rb +160 -0
  69. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  70. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +118 -0
  71. data/spec/unit/daemon/app_runner_shared.rb +66 -0
  72. data/spec/unit/daemon/app_runner_spec.rb +129 -0
  73. data/spec/unit/daemon/database_reconnectable_spec.rb +109 -0
  74. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  75. data/spec/unit/daemon/delivery_handler_shared.rb +28 -0
  76. data/spec/unit/daemon/delivery_queue_spec.rb +29 -0
  77. data/spec/unit/daemon/feeder_spec.rb +95 -0
  78. data/spec/unit/daemon/gcm/app_runner_spec.rb +17 -0
  79. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
  80. data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
  81. data/spec/unit/daemon/interruptible_sleep_spec.rb +40 -0
  82. data/spec/unit/daemon/logger_spec.rb +156 -0
  83. data/spec/unit/daemon_spec.rb +139 -0
  84. data/spec/unit/gcm/app_spec.rb +5 -0
  85. data/spec/unit/gcm/notification_spec.rb +55 -0
  86. data/spec/unit/notification_shared.rb +38 -0
  87. data/spec/unit/notification_spec.rb +6 -0
  88. data/spec/unit_spec_helper.rb +145 -0
  89. 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