rapns_rails_2 3.4.3

Sign up to get free protection for your applications and to get access to all the features.
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,289 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::Gcm::Delivery do
4
+ let(:app) { Rapns::Gcm::App.new(:name => 'MyApp', :auth_key => 'abc123') }
5
+ let(:notification) { Rapns::Gcm::Notification.create!(:app => app, :registration_ids => ['xyz'], :deliver_after => Time.now) }
6
+ let(:logger) { double(:error => nil, :info => nil, :warn => nil) }
7
+ let(:response) { double(:code => 200, :header => {}) }
8
+ let(:http) { double(:shutdown => nil, :request => response)}
9
+ let(:now) { Time.parse('2012-10-14 00:00:00') }
10
+ let(:batch) { double(:mark_failed => nil, :mark_delivered => nil, :mark_retryable => nil) }
11
+ let(:delivery) { Rapns::Daemon::Gcm::Delivery.new(app, http, notification, batch) }
12
+ let(:store) { double(:create_gcm_notification => double(:id => 2)) }
13
+
14
+ def perform
15
+ delivery.perform
16
+ end
17
+
18
+ before do
19
+ delivery.stub(:reflect => nil)
20
+ Rapns::Daemon.stub(:store => store)
21
+ Time.stub(:now => now)
22
+ Rapns.stub(:logger => logger)
23
+ end
24
+
25
+ shared_examples_for 'an notification with some delivery failures' do
26
+ let(:new_notification) { Rapns::Gcm::Notification.where('id != ?', notification.id).first }
27
+
28
+ before { response.stub(:body => MultiJson.dump(body) ) }
29
+
30
+ it 'marks the original notification as failed' do
31
+ batch.should_receive(:mark_failed).with(notification, nil, error_description)
32
+ perform rescue Rapns::DeliveryError
33
+ end
34
+
35
+ it 'creates a new notification for the unavailable devices' do
36
+ notification.update_attributes(:registration_ids => ['id_0', 'id_1', 'id_2'], :data => {'one' => 1}, :collapse_key => 'thing', :delay_while_idle => true)
37
+ response.stub(:header => { 'retry-after' => 10 })
38
+ attrs = { 'collapse_key' => 'thing', 'delay_while_idle' => true, 'app_id' => app.id }
39
+ store.should_receive(:create_gcm_notification).with(attrs, notification.data,
40
+ ['id_0', 'id_2'], now + 10.seconds, notification.app)
41
+ perform rescue Rapns::DeliveryError
42
+ end
43
+
44
+ it 'raises a DeliveryError' do
45
+ expect { perform }.to raise_error(Rapns::DeliveryError)
46
+ end
47
+ end
48
+
49
+ describe 'an 200 response' do
50
+ before do
51
+ response.stub(:code => 200)
52
+ end
53
+
54
+ it 'marks the notification as delivered if delivered successfully to all devices' do
55
+ response.stub(:body => MultiJson.dump({ 'failure' => 0 }))
56
+ batch.should_receive(:mark_delivered).with(notification)
57
+ perform
58
+ end
59
+
60
+ it 'logs that the notification was delivered' do
61
+ response.stub(:body => MultiJson.dump({ 'failure' => 0 }))
62
+ logger.should_receive(:info).with("[MyApp] #{notification.id} sent to xyz")
63
+ perform
64
+ end
65
+
66
+ it 'marks a notification as failed if any deliveries failed that cannot be retried.' do
67
+ body = {
68
+ 'failure' => 1,
69
+ 'success' => 1,
70
+ 'results' => [
71
+ { 'message_id' => '1:000' },
72
+ { 'error' => 'InvalidDataKey' }
73
+ ]}
74
+ response.stub(:body => MultiJson.dump(body))
75
+ batch.should_receive(:mark_failed).with(notification, nil, "Failed to deliver to all recipients. Errors: InvalidDataKey.")
76
+ perform rescue Rapns::DeliveryError
77
+ end
78
+
79
+ it 'reflects on canonical IDs' do
80
+ body = {
81
+ 'failure' => 0,
82
+ 'success' => 3,
83
+ 'canonical_ids' => 1,
84
+ 'results' => [
85
+ { 'message_id' => '1:000' },
86
+ { 'message_id' => '1:000', 'registration_id' => 'canonical123' },
87
+ { 'message_id' => '1:000' },
88
+ ]}
89
+
90
+ response.stub(:body => MultiJson.dump(body))
91
+ notification.stub(:registration_ids => ['1', '2', '3'])
92
+ delivery.should_receive(:reflect).with(:gcm_canonical_id, '2', 'canonical123')
93
+ perform
94
+ end
95
+
96
+ it 'reflects on invalid IDs' do
97
+ body = {
98
+ 'failure' => 1,
99
+ 'success' => 2,
100
+ 'canonical_ids' => 0,
101
+ 'results' => [
102
+ { 'message_id' => '1:000' },
103
+ { 'error' => 'NotRegistered' },
104
+ { 'message_id' => '1:000' },
105
+ ]}
106
+
107
+ response.stub(:body => MultiJson.dump(body))
108
+ notification.stub(:registration_ids => ['1', '2', '3'])
109
+ delivery.should_receive(:reflect).with(:gcm_invalid_registration_id, app, 'NotRegistered', '2')
110
+ perform
111
+ end
112
+
113
+ it 'does not retry, raise or marks a notification as failed for invalid ids.' do
114
+ body = {
115
+ 'failure' => 1,
116
+ 'success' => 2,
117
+ 'canonical_ids' => 0,
118
+ 'results' => [
119
+ { 'message_id' => '1:000' },
120
+ { 'error' => 'NotRegistered' },
121
+ { 'message_id' => '1:000' },
122
+ ]}
123
+
124
+ response.stub(:body => MultiJson.dump(body))
125
+ batch.should_not_receive(:mark_failed)
126
+ batch.should_not_receive(:mark_retryable)
127
+ store.should_not_receive(:create_gcm_notification)
128
+ perform
129
+ end
130
+
131
+ describe 'all deliveries returned Unavailable or InternalServerError' do
132
+ let(:body) {{
133
+ 'failure' => 2,
134
+ 'success' => 0,
135
+ 'results' => [
136
+ { 'error' => 'Unavailable' },
137
+ { 'error' => 'Unavailable' }
138
+ ]}}
139
+
140
+ before { response.stub(:body => MultiJson.dump(body)) }
141
+
142
+ it 'retries the notification respecting the Retry-After header' do
143
+ response.stub(:header => { 'retry-after' => 10 })
144
+ batch.should_receive(:mark_retryable).with(notification, now + 10.seconds)
145
+ perform
146
+ end
147
+
148
+ it 'retries the notification using exponential back-off if the Retry-After header is not present' do
149
+ batch.should_receive(:mark_retryable).with(notification, now + 2)
150
+ perform
151
+ end
152
+
153
+ it 'does not mark the notification as failed' do
154
+ batch.should_not_receive(:mark_failed)
155
+ perform
156
+ end
157
+
158
+ it 'logs that the notification will be retried' do
159
+ notification.retries = 1
160
+ notification.deliver_after = now + 2
161
+ Rapns.logger.should_receive(:warn).with("All recipients unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
162
+ perform
163
+ end
164
+ end
165
+
166
+ shared_examples_for 'an notification with some delivery failures (200)' do
167
+ let(:new_notification) { Rapns::Gcm::Notification.where('id != ?', notification.id).first }
168
+
169
+ before { response.stub(:body => MultiJson.dump(body)) }
170
+
171
+ it 'marks the original notification as failed' do
172
+ batch.should_receive(:mark_failed).with(notification, nil, error_description)
173
+ perform rescue Rapns::DeliveryError
174
+ end
175
+
176
+ it 'creates a new notification for the unavailable devices' do
177
+ notification.update_attributes(:registration_ids => ['id_0', 'id_1', 'id_2'], :data => {'one' => 1}, :collapse_key => 'thing', :delay_while_idle => true)
178
+ response.stub(:header => { 'retry-after' => 10 })
179
+ attrs = { 'collapse_key' => 'thing', 'delay_while_idle' => true, 'app_id' => app.id }
180
+ store.should_receive(:create_gcm_notification).with(attrs, notification.data,
181
+ ['id_0', 'id_2'], now + 10.seconds, notification.app)
182
+ perform rescue Rapns::DeliveryError
183
+ end
184
+
185
+ it 'raises a DeliveryError' do
186
+ expect { perform }.to raise_error(Rapns::DeliveryError)
187
+ end
188
+ end
189
+
190
+ describe 'all deliveries failed with some as Unavailable or InternalServerError' do
191
+ let(:body) {{
192
+ 'failure' => 3,
193
+ 'success' => 0,
194
+ 'results' => [
195
+ { 'error' => 'Unavailable' },
196
+ { 'error' => 'InvalidDataKey' },
197
+ { 'error' => 'Unavailable' }
198
+ ]}}
199
+ let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 1, 2. Errors: Unavailable, InvalidDataKey, Unavailable. 0, 2 will be retried as notification")} [\d]+\./ }
200
+ it_should_behave_like 'an notification with some delivery failures (200)'
201
+ end
202
+ end
203
+
204
+ describe 'some deliveries failed with Unavailable or InternalServerError' do
205
+ let(:body) {{
206
+ 'failure' => 2,
207
+ 'success' => 1,
208
+ 'results' => [
209
+ { 'error' => 'Unavailable' },
210
+ { 'message_id' => '1:000' },
211
+ { 'error' => 'InternalServerError' }
212
+ ]}}
213
+ let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 2. Errors: Unavailable, InternalServerError. 0, 2 will be retried as notification")} [\d]+\./ }
214
+ it_should_behave_like 'an notification with some delivery failures'
215
+ end
216
+
217
+ describe 'an 503 response' do
218
+ before { response.stub(:code => 503) }
219
+
220
+ it 'logs a warning that the notification will be retried.' do
221
+ notification.retries = 1
222
+ notification.deliver_after = now + 2
223
+ logger.should_receive(:warn).with("GCM responded with an Service Unavailable Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
224
+ perform
225
+ end
226
+
227
+ it 'respects an integer Retry-After header' do
228
+ response.stub(:header => { 'retry-after' => 10 })
229
+ batch.should_receive(:mark_retryable).with(notification, now + 10.seconds)
230
+ perform
231
+ end
232
+
233
+ it 'respects a HTTP-date Retry-After header' do
234
+ response.stub(:header => { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' })
235
+ batch.should_receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT'))
236
+ perform
237
+ end
238
+
239
+ it 'defaults to exponential back-off if the Retry-After header is not present' do
240
+ batch.should_receive(:mark_retryable).with(notification, now + 2 ** 1)
241
+ perform
242
+ end
243
+ end
244
+
245
+ describe 'an 500 response' do
246
+ before do
247
+ notification.update_attribute(:retries, 2)
248
+ response.stub(:code => 500)
249
+ end
250
+
251
+ it 'logs a warning that the notification has been re-queued.' do
252
+ notification.retries = 3
253
+ notification.deliver_after = now + 2 ** 3
254
+ Rapns.logger.should_receive(:warn).with("GCM responded with an Internal Error. Notification #{notification.id} will be retried after #{(now + 2 ** 3).strftime("%Y-%m-%d %H:%M:%S")} (retry 3).")
255
+ perform
256
+ end
257
+
258
+ it 'retries the notification in accordance with the exponential back-off strategy.' do
259
+ batch.should_receive(:mark_retryable).with(notification, now + 2 ** 3)
260
+ perform
261
+ end
262
+ end
263
+
264
+ describe 'an 401 response' do
265
+ before { response.stub(:code => 401) }
266
+
267
+ it 'raises an error' do
268
+ expect { perform }.to raise_error(Rapns::DeliveryError)
269
+ end
270
+ end
271
+
272
+ describe 'an 400 response' do
273
+ before { response.stub(:code => 400) }
274
+
275
+ it 'marks the notification as failed' do
276
+ batch.should_receive(:mark_failed).with(notification, 400, 'GCM failed to parse the JSON request. Possibly an rapns bug, please open an issue.')
277
+ perform rescue Rapns::DeliveryError
278
+ end
279
+ end
280
+
281
+ describe 'an un-handled response' do
282
+ before { response.stub(:code => 418) }
283
+
284
+ it 'marks the notification as failed' do
285
+ batch.should_receive(:mark_failed).with(notification, 418, "I'm a Teapot")
286
+ perform rescue Rapns::DeliveryError
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,68 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::InterruptibleSleep do
4
+
5
+ let(:rd) { double(:close => nil) }
6
+ let(:wr) { double(:close => nil) }
7
+
8
+ subject { Rapns::Daemon::InterruptibleSleep.new }
9
+
10
+ it 'creates a new pipe' do
11
+ IO.should_receive(:pipe)
12
+ subject
13
+ end
14
+
15
+ it 'selects on the reader' do
16
+ IO.stub(:pipe => [rd, wr])
17
+ IO.should_receive(:select).with([rd], nil, nil, 1)
18
+ subject.sleep(1)
19
+ end
20
+
21
+ it 'closes the writer' do
22
+ IO.stub(:pipe => [rd, wr])
23
+ rd.should_receive(:close)
24
+ wr.should_receive(:close)
25
+ subject.close
26
+ end
27
+
28
+ it 'returns false when timeout occurs' do
29
+ subject.sleep(0.01).should be_false
30
+ end
31
+
32
+ it 'returns true when sleep does not timeout' do
33
+ subject.interrupt_sleep
34
+ subject.sleep(0.01).should be_true
35
+ end
36
+
37
+ context 'with UDP socket connected' do
38
+ before :each do
39
+ @host, @port = subject.enable_wake_on_udp('127.0.0.1', 0)
40
+ end
41
+
42
+ it 'times out with no udp activity' do
43
+ subject.sleep(0.01).should be_false
44
+ end
45
+
46
+ it 'wakes on UDPSocket' do
47
+ waker = UDPSocket.new
48
+ waker.connect(@host, @port)
49
+ waker.write('x')
50
+ subject.sleep(0.01).should be_true
51
+ waker.close
52
+ end
53
+
54
+ it 'consumes all data on udp socket' do
55
+ waker = UDPSocket.new
56
+ # waker.connect(@host, @port)
57
+ waker.send('x', 0, @host, @port)
58
+ waker.send('x', 0, @host, @port)
59
+ waker.send('x', 0, @host, @port)
60
+ # true since there is data to be read => no timeout
61
+ subject.sleep(0.01).should be_true
62
+ # false since data is consumed => wait for full timeout
63
+ subject.sleep(0.01).should be_false
64
+ waker.close
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+
3
+ describe Rapns::Daemon::Reflectable do
4
+ class TestReflectable
5
+ include Rapns::Daemon::Reflectable
6
+ end
7
+
8
+ let(:logger) { double(:error => nil) }
9
+ let(:test_reflectable) { TestReflectable.new }
10
+
11
+ before do
12
+ Rapns.reflections.stub(:__dispatch)
13
+ Rapns.stub(:logger => logger)
14
+ end
15
+
16
+ it 'dispatches the given reflection' do
17
+ Rapns.reflections.should_receive(:__dispatch).with(:error)
18
+ test_reflectable.reflect(:error)
19
+ end
20
+
21
+ it 'logs errors raise by the reflection' do
22
+ error = StandardError.new
23
+ Rapns.reflections.stub(:__dispatch).and_raise(error)
24
+ Rapns.logger.should_receive(:error).with(error)
25
+ test_reflectable.reflect(:error)
26
+ end
27
+ end
@@ -0,0 +1,114 @@
1
+ require File.expand_path("spec/unit_spec_helper")
2
+ require 'rapns/daemon/store/active_record/reconnectable'
3
+
4
+ describe Rapns::Daemon::Store::ActiveRecord::Reconnectable do
5
+ class TestDouble
6
+ include Rapns::Daemon::Store::ActiveRecord::Reconnectable
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(error, max_calls)
11
+ @error = error
12
+ @max_calls = max_calls
13
+ @calls = 0
14
+ end
15
+
16
+ def perform
17
+ with_database_reconnect_and_retry do
18
+ @calls += 1
19
+ raise @error if @calls <= @max_calls
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:adapter_error_class) do
25
+ case $adapter
26
+ when 'postgresql'
27
+ PGError
28
+ when 'mysql'
29
+ Mysql::Error
30
+ when 'mysql2'
31
+ Mysql2::Error
32
+ when 'jdbcpostgresql'
33
+ ActiveRecord::JDBCError
34
+ when 'jdbcmysql'
35
+ ActiveRecord::JDBCError
36
+ when 'jdbch2'
37
+ ActiveRecord::JDBCError
38
+ when 'sqlite3'
39
+ SQLite3::Exception
40
+ else
41
+ raise "Please update #{__FILE__} for adapter #{$adapter}"
42
+ end
43
+ end
44
+ let(:error) { adapter_error_class.new("db down!") }
45
+ let(:test_double) { TestDouble.new(error, 1) }
46
+
47
+ before do
48
+ @logger = double("Logger", :info => nil, :error => nil, :warn => nil)
49
+ Rapns.stub(:logger).and_return(@logger)
50
+
51
+ ActiveRecord::Base.stub(:clear_all_connections!)
52
+ ActiveRecord::Base.stub(:establish_connection)
53
+ test_double.stub(:sleep)
54
+ end
55
+
56
+ it "should log the error raised" do
57
+ Rapns.logger.should_receive(:error).with(error)
58
+ test_double.perform
59
+ end
60
+
61
+ it "should log that the database is being reconnected" do
62
+ Rapns.logger.should_receive(:warn).with("Lost connection to database, reconnecting...")
63
+ test_double.perform
64
+ end
65
+
66
+ it "should log the reconnection attempt" do
67
+ Rapns.logger.should_receive(:warn).with("Attempt 1")
68
+ test_double.perform
69
+ end
70
+
71
+ it "should clear all connections" do
72
+ ActiveRecord::Base.should_receive(:clear_all_connections!)
73
+ test_double.perform
74
+ end
75
+
76
+ it "should establish a new connection" do
77
+ ActiveRecord::Base.should_receive(:establish_connection)
78
+ test_double.perform
79
+ end
80
+
81
+ it "should test out the new connection by performing a count" do
82
+ Rapns::Notification.should_receive(:count)
83
+ test_double.perform
84
+ end
85
+
86
+ context "when the reconnection attempt is not successful" do
87
+ before do
88
+ class << Rapns::Notification
89
+ def count
90
+ @count_calls += 1
91
+ return if @count_calls == 2
92
+ raise @error
93
+ end
94
+ end
95
+ Rapns::Notification.instance_variable_set("@count_calls", 0)
96
+ Rapns::Notification.instance_variable_set("@error", error)
97
+ end
98
+
99
+ it "should log the 2nd attempt" do
100
+ Rapns.logger.should_receive(:warn).with("Attempt 2")
101
+ test_double.perform
102
+ end
103
+
104
+ it "should log errors raised when the reconnection is not successful without notifying airbrake" do
105
+ Rapns.logger.should_receive(:error).with(error, :airbrake_notify => false)
106
+ test_double.perform
107
+ end
108
+
109
+ it "should sleep to avoid thrashing when the database is down" do
110
+ test_double.should_receive(:sleep).with(2)
111
+ test_double.perform
112
+ end
113
+ end
114
+ end