rpush 8.0.0 → 9.1.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -6
  3. data/README.md +8 -65
  4. data/lib/generators/templates/rpush.rb +9 -16
  5. data/lib/rpush/client/active_model/fcm/notification.rb +1 -0
  6. data/lib/rpush/client/active_model.rb +0 -4
  7. data/lib/rpush/client/active_record/notification.rb +2 -0
  8. data/lib/rpush/client/active_record.rb +0 -3
  9. data/lib/rpush/client/redis.rb +0 -3
  10. data/lib/rpush/configuration.rb +2 -19
  11. data/lib/rpush/daemon/service_config_methods.rb +0 -2
  12. data/lib/rpush/daemon/store/active_record.rb +2 -14
  13. data/lib/rpush/daemon/store/interface.rb +2 -2
  14. data/lib/rpush/daemon/store/redis.rb +2 -11
  15. data/lib/rpush/daemon.rb +0 -10
  16. data/lib/rpush/reflection_collection.rb +1 -2
  17. data/lib/rpush/version.rb +2 -2
  18. data/lib/rpush.rb +0 -1
  19. data/spec/functional/cli_spec.rb +41 -15
  20. data/spec/functional/embed_spec.rb +57 -26
  21. data/spec/functional/retry_spec.rb +21 -4
  22. data/spec/functional/synchronization_spec.rb +1 -1
  23. data/spec/functional_spec_helper.rb +0 -6
  24. data/spec/spec_helper.rb +17 -7
  25. data/spec/unit/client/active_record/shared/app.rb +1 -1
  26. data/spec/unit/client/shared/fcm/notification.rb +6 -1
  27. data/spec/unit/daemon/shared/store.rb +0 -42
  28. metadata +61 -61
  29. data/lib/rpush/apns_feedback.rb +0 -18
  30. data/lib/rpush/client/active_model/gcm/app.rb +0 -19
  31. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +0 -14
  32. data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
  33. data/lib/rpush/client/active_record/gcm/app.rb +0 -11
  34. data/lib/rpush/client/active_record/gcm/notification.rb +0 -11
  35. data/lib/rpush/client/redis/gcm/app.rb +0 -11
  36. data/lib/rpush/client/redis/gcm/notification.rb +0 -11
  37. data/lib/rpush/daemon/apns/delivery.rb +0 -43
  38. data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
  39. data/lib/rpush/daemon/apns.rb +0 -17
  40. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
  41. data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
  42. data/lib/rpush/daemon/gcm/delivery.rb +0 -241
  43. data/lib/rpush/daemon/gcm.rb +0 -9
  44. data/lib/rpush/daemon/tcp_connection.rb +0 -190
  45. data/spec/functional/apns_spec.rb +0 -162
  46. data/spec/functional/gcm_priority_spec.rb +0 -40
  47. data/spec/functional/gcm_spec.rb +0 -46
  48. data/spec/functional/new_app_spec.rb +0 -44
  49. data/spec/unit/apns_feedback_spec.rb +0 -39
  50. data/spec/unit/client/active_record/gcm/app_spec.rb +0 -6
  51. data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
  52. data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
  53. data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
  54. data/spec/unit/client/shared/gcm/app.rb +0 -4
  55. data/spec/unit/client/shared/gcm/notification.rb +0 -77
  56. data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
  57. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
  58. data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
  59. data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
  60. data/spec/unit/daemon/tcp_connection_spec.rb +0 -293
@@ -1,387 +0,0 @@
1
- require 'unit_spec_helper'
2
-
3
- describe Rpush::Daemon::Gcm::Delivery do
4
- let(:app) { Rpush::Gcm::App.create!(name: 'MyApp', auth_key: 'abc123') }
5
- let(:notification) { Rpush::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, notification_processed: nil) }
11
- let(:delivery) { Rpush::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
- def perform_with_rescue
19
- expect { perform }.to raise_error(StandardError)
20
- end
21
-
22
- before do
23
- allow(delivery).to receive_messages(reflect: nil)
24
- allow(Rpush::Daemon).to receive_messages(store: store)
25
- allow(Time).to receive_messages(now: now)
26
- allow(Rpush).to receive_messages(logger: logger)
27
- end
28
-
29
- shared_examples_for 'a notification with some delivery failures' do
30
- let(:new_notification) { Rpush::Gcm::Notification.where('id != ?', notification.id).first }
31
-
32
- before { allow(response).to receive_messages(body: JSON.dump(body)) }
33
-
34
- it 'marks the original notification as failed' do
35
- # error = Rpush::DeliveryError.new(nil, notification.id, error_description)
36
- expect(delivery).to receive(:mark_failed) do |error|
37
- expect(error.to_s).to match(error_description)
38
- end
39
- perform_with_rescue
40
- end
41
-
42
- it 'creates a new notification for the unavailable devices' do
43
- notification.update(registration_ids: %w(id_0 id_1 id_2), data: { 'one' => 1 }, collapse_key: 'thing', delay_while_idle: true)
44
- allow(response).to receive_messages(header: { 'retry-after' => 10 })
45
- attrs = { 'collapse_key' => 'thing', 'delay_while_idle' => true, 'app_id' => app.id }
46
- expect(store).to receive(:create_gcm_notification).with(attrs, notification.data,
47
- %w(id_0 id_2), now + 10.seconds, notification.app)
48
- perform_with_rescue
49
- end
50
-
51
- it 'raises a DeliveryError' do
52
- expect { perform }.to raise_error(Rpush::DeliveryError)
53
- end
54
- end
55
-
56
- describe 'a 200 response' do
57
- before do
58
- allow(response).to receive_messages(code: 200)
59
- end
60
-
61
- it 'reflects on any IDs which successfully received the notification' do
62
- body = {
63
- 'failure' => 1,
64
- 'success' => 1,
65
- 'results' => [
66
- { 'message_id' => '1:000' },
67
- { 'error' => 'Err' }
68
- ]
69
- }
70
-
71
- allow(response).to receive_messages(body: JSON.dump(body))
72
- allow(notification).to receive_messages(registration_ids: %w(1 2))
73
- expect(delivery).to receive(:reflect).with(:gcm_delivered_to_recipient, notification, '1')
74
- expect(delivery).not_to receive(:reflect).with(:gcm_delivered_to_recipient, notification, '2')
75
- perform_with_rescue
76
- end
77
-
78
- it 'reflects on any IDs which failed to receive the notification' do
79
- body = {
80
- 'failure' => 1,
81
- 'success' => 1,
82
- 'results' => [
83
- { 'error' => 'Err' },
84
- { 'message_id' => '1:000' }
85
- ]
86
- }
87
-
88
- allow(response).to receive_messages(body: JSON.dump(body))
89
- allow(notification).to receive_messages(registration_ids: %w(1 2))
90
- expect(delivery).to receive(:reflect).with(:gcm_failed_to_recipient, notification, 'Err', '1')
91
- expect(delivery).not_to receive(:reflect).with(:gcm_failed_to_recipient, notification, anything, '2')
92
- perform_with_rescue
93
- end
94
-
95
- it 'reflects on canonical IDs' do
96
- body = {
97
- 'failure' => 0,
98
- 'success' => 3,
99
- 'canonical_ids' => 1,
100
- 'results' => [
101
- { 'message_id' => '1:000' },
102
- { 'message_id' => '1:000', 'registration_id' => 'canonical123' },
103
- { 'message_id' => '1:000' }
104
- ] }
105
-
106
- allow(response).to receive_messages(body: JSON.dump(body))
107
- allow(notification).to receive_messages(registration_ids: %w(1 2 3))
108
- expect(delivery).to receive(:reflect).with(:gcm_canonical_id, '2', 'canonical123')
109
- perform
110
- end
111
-
112
- it 'reflects on invalid IDs' do
113
- body = {
114
- 'failure' => 1,
115
- 'success' => 2,
116
- 'canonical_ids' => 0,
117
- 'results' => [
118
- { 'message_id' => '1:000' },
119
- { 'error' => 'NotRegistered' },
120
- { 'message_id' => '1:000' }
121
- ]
122
- }
123
-
124
- allow(response).to receive_messages(body: JSON.dump(body))
125
- allow(notification).to receive_messages(registration_ids: %w(1 2 3))
126
- expect(delivery).to receive(:reflect).with(:gcm_invalid_registration_id, app, 'NotRegistered', '2')
127
- perform_with_rescue
128
- end
129
-
130
- describe 'when delivered successfully to all devices' do
131
- let(:body) do
132
- {
133
- 'failure' => 0,
134
- 'success' => 1,
135
- 'results' => [{ 'message_id' => '1:000' }]
136
- }
137
- end
138
-
139
- before { allow(response).to receive_messages(body: JSON.dump(body)) }
140
-
141
- it 'marks the notification as delivered' do
142
- expect(delivery).to receive(:mark_delivered)
143
- perform
144
- end
145
-
146
- it 'logs that the notification was delivered' do
147
- expect(logger).to receive(:info).with("[MyApp] #{notification.id} sent to xyz")
148
- perform
149
- end
150
- end
151
-
152
- it 'marks a notification as failed if any ids are invalid' do
153
- body = {
154
- 'failure' => 1,
155
- 'success' => 2,
156
- 'canonical_ids' => 0,
157
- 'results' => [
158
- { 'message_id' => '1:000' },
159
- { 'error' => 'NotRegistered' },
160
- { 'message_id' => '1:000' }
161
- ]
162
- }
163
-
164
- allow(response).to receive_messages(body: JSON.dump(body))
165
- expect(delivery).to receive(:mark_failed)
166
- expect(delivery).not_to receive(:mark_retryable)
167
- expect(store).not_to receive(:create_gcm_notification)
168
- perform_with_rescue
169
- end
170
-
171
- it 'marks a notification as failed if any deliveries failed that cannot be retried' do
172
- body = {
173
- 'failure' => 1,
174
- 'success' => 1,
175
- 'results' => [
176
- { 'message_id' => '1:000' },
177
- { 'error' => 'InvalidDataKey' }
178
- ] }
179
- allow(response).to receive_messages(body: JSON.dump(body))
180
- error = Rpush::DeliveryError.new(nil, notification.id, 'Failed to deliver to all recipients. Errors: InvalidDataKey.')
181
- expect(delivery).to receive(:mark_failed).with(error)
182
- perform_with_rescue
183
- end
184
-
185
- describe 'all deliveries failed with Unavailable or InternalServerError' do
186
- let(:body) do
187
- {
188
- 'failure' => 2,
189
- 'success' => 0,
190
- 'results' => [
191
- { 'error' => 'Unavailable' },
192
- { 'error' => 'Unavailable' }
193
- ]
194
- }
195
- end
196
-
197
- before do
198
- allow(response).to receive_messages(body: JSON.dump(body))
199
- allow(notification).to receive_messages(registration_ids: %w(1 2))
200
- end
201
-
202
- it 'retries the notification respecting the Retry-After header' do
203
- allow(response).to receive_messages(header: { 'retry-after' => 10 })
204
- expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds)
205
- perform
206
- end
207
-
208
- it 'retries the notification using exponential back-off if the Retry-After header is not present' do
209
- expect(delivery).to receive(:mark_retryable).with(notification, now + 2)
210
- perform
211
- end
212
-
213
- it 'does not mark the notification as failed' do
214
- expect(delivery).not_to receive(:mark_failed)
215
- perform
216
- end
217
-
218
- it 'logs that the notification will be retried' do
219
- notification.retries = 1
220
- notification.deliver_after = now + 2
221
- expect(Rpush.logger).to receive(:warn).with("[MyApp] All recipients unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
222
- perform
223
- end
224
- end
225
-
226
- describe 'all deliveries failed with some as Unavailable or InternalServerError' do
227
- let(:body) do
228
- { 'failure' => 3,
229
- 'success' => 0,
230
- 'results' => [
231
- { 'error' => 'Unavailable' },
232
- { 'error' => 'InvalidDataKey' },
233
- { 'error' => 'Unavailable' }
234
- ]
235
- }
236
- end
237
- 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]+\./ }
238
- it_should_behave_like 'a notification with some delivery failures'
239
- end
240
-
241
- describe 'some deliveries failed with Unavailable or InternalServerError' do
242
- let(:body) do
243
- { 'failure' => 2,
244
- 'success' => 1,
245
- 'results' => [
246
- { 'error' => 'Unavailable' },
247
- { 'message_id' => '1:000' },
248
- { 'error' => 'InternalServerError' }
249
- ]
250
- }
251
- end
252
- let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 2. Errors: Unavailable, InternalServerError. 0, 2 will be retried as notification")} [\d]+\./ }
253
- it_should_behave_like 'a notification with some delivery failures'
254
- end
255
- end
256
-
257
- describe 'a 503 response' do
258
- before { allow(response).to receive_messages(code: 503) }
259
-
260
- it 'logs a warning that the notification will be retried.' do
261
- notification.retries = 1
262
- notification.deliver_after = now + 2
263
- expect(logger).to receive(:warn).with("[MyApp] GCM responded with an Service Unavailable Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
264
- perform
265
- end
266
-
267
- it 'respects an integer Retry-After header' do
268
- allow(response).to receive_messages(header: { 'retry-after' => 10 })
269
- expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds)
270
- perform
271
- end
272
-
273
- it 'respects a HTTP-date Retry-After header' do
274
- allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' })
275
- expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT'))
276
- perform
277
- end
278
-
279
- it 'defaults to exponential back-off if the Retry-After header is not present' do
280
- expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1)
281
- perform
282
- end
283
- end
284
-
285
- describe 'a 502 response' do
286
- before { allow(response).to receive_messages(code: 502) }
287
-
288
- it 'logs a warning that the notification will be retried.' do
289
- notification.retries = 1
290
- notification.deliver_after = now + 2
291
- expect(logger).to receive(:warn).with("[MyApp] GCM responded with a Bad Gateway Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
292
- perform
293
- end
294
-
295
- it 'respects an integer Retry-After header' do
296
- allow(response).to receive_messages(header: { 'retry-after' => 10 })
297
- expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds)
298
- perform
299
- end
300
-
301
- it 'respects a HTTP-date Retry-After header' do
302
- allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' })
303
- expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT'))
304
- perform
305
- end
306
-
307
- it 'defaults to exponential back-off if the Retry-After header is not present' do
308
- expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1)
309
- perform
310
- end
311
- end
312
-
313
- describe 'a 500 response' do
314
- before do
315
- notification.update_attribute(:retries, 2)
316
- allow(response).to receive_messages(code: 500)
317
- end
318
-
319
- it 'logs a warning that the notification has been re-queued.' do
320
- notification.retries = 3
321
- notification.deliver_after = now + 2**3
322
- expect(Rpush.logger).to receive(:warn).with("[MyApp] 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).")
323
- perform
324
- end
325
-
326
- it 'retries the notification in accordance with the exponential back-off strategy.' do
327
- expect(delivery).to receive(:mark_retryable).with(notification, now + 2**3)
328
- perform
329
- end
330
- end
331
-
332
- describe 'a 5xx response' do
333
- before { allow(response).to receive_messages(code: 555) }
334
-
335
- it 'logs a warning that the notification will be retried.' do
336
- notification.retries = 1
337
- notification.deliver_after = now + 2
338
- expect(logger).to receive(:warn).with("[MyApp] GCM responded with a 5xx Error. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
339
- perform
340
- end
341
-
342
- it 'respects an integer Retry-After header' do
343
- allow(response).to receive_messages(header: { 'retry-after' => 10 })
344
- expect(delivery).to receive(:mark_retryable).with(notification, now + 10.seconds)
345
- perform
346
- end
347
-
348
- it 'respects a HTTP-date Retry-After header' do
349
- allow(response).to receive_messages(header: { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' })
350
- expect(delivery).to receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT'))
351
- perform
352
- end
353
-
354
- it 'defaults to exponential back-off if the Retry-After header is not present' do
355
- expect(delivery).to receive(:mark_retryable).with(notification, now + 2**1)
356
- perform
357
- end
358
- end
359
-
360
- describe 'a 401 response' do
361
- before { allow(response).to receive_messages(code: 401) }
362
-
363
- it 'raises an error' do
364
- expect { perform }.to raise_error(Rpush::DeliveryError)
365
- end
366
- end
367
-
368
- describe 'a 400 response' do
369
- before { allow(response).to receive_messages(code: 400) }
370
-
371
- it 'marks the notification as failed' do
372
- error = Rpush::DeliveryError.new(400, notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.')
373
- expect(delivery).to receive(:mark_failed).with(error)
374
- perform_with_rescue
375
- end
376
- end
377
-
378
- describe 'an un-handled response' do
379
- before { allow(response).to receive_messages(code: 418) }
380
-
381
- it 'marks the notification as failed' do
382
- error = Rpush::DeliveryError.new(418, notification.id, "I'm a Teapot")
383
- expect(delivery).to receive(:mark_failed).with(error)
384
- perform_with_rescue
385
- end
386
- end
387
- end
@@ -1,293 +0,0 @@
1
- require "unit_spec_helper"
2
-
3
- describe Rpush::Daemon::TcpConnection 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) { Rpush::Daemon::TcpConnection.new(app, host, port) }
16
-
17
- before do
18
- allow(x509_certificate).to receive(:not_after).and_return(Time.now + 1.year)
19
- allow(OpenSSL::SSL::SSLContext).to receive_messages(new: ssl_context)
20
- allow(OpenSSL::PKey::RSA).to receive_messages(new: rsa_key)
21
- allow(OpenSSL::X509::Certificate).to receive_messages(new: x509_certificate)
22
- allow(TCPSocket).to receive_messages(new: tcp_socket)
23
- allow(OpenSSL::SSL::SSLSocket).to receive_messages(new: ssl_socket)
24
- allow(Rpush).to receive_messages(logger: logger)
25
- allow(connection).to receive(:reflect)
26
- end
27
-
28
- it "reads the number of bytes from the SSL socket" do
29
- expect(ssl_socket).to receive(:read).with(123)
30
- connection.connect
31
- connection.read(123)
32
- end
33
-
34
- it "selects on the SSL socket until the given timeout" do
35
- expect(IO).to receive(:select).with([ssl_socket], nil, nil, 10)
36
- connection.connect
37
- connection.select(10)
38
- end
39
-
40
- describe "when setting up the SSL context" do
41
- it "sets the key on the context" do
42
- expect(OpenSSL::PKey::RSA).to receive(:new).with(certificate, password).and_return(rsa_key)
43
- expect(ssl_context).to receive(:key=).with(rsa_key)
44
- connection.connect
45
- end
46
-
47
- it "sets the cert on the context" do
48
- expect(OpenSSL::X509::Certificate).to receive(:new).with(certificate).and_return(x509_certificate)
49
- expect(ssl_context).to receive(:cert=).with(x509_certificate)
50
- connection.connect
51
- end
52
- end
53
-
54
- describe "when connecting the socket" do
55
- it "creates a TCP socket using the configured host and port" do
56
- expect(TCPSocket).to receive(:new).with(host, port).and_return(tcp_socket)
57
- connection.connect
58
- end
59
-
60
- it "creates a new SSL socket using the TCP socket and SSL context" do
61
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket)
62
- connection.connect
63
- end
64
-
65
- it "sets the sync option on the SSL socket" do
66
- expect(ssl_socket).to receive(:sync=).with(true)
67
- connection.connect
68
- end
69
-
70
- it "connects the SSL socket" do
71
- expect(ssl_socket).to receive(:connect)
72
- connection.connect
73
- end
74
-
75
- it "sets the socket option TCP_NODELAY" do
76
- expect(tcp_socket).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
77
- connection.connect
78
- end
79
-
80
- it "sets the socket option SO_KEEPALIVE" do
81
- expect(tcp_socket).to receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
82
- connection.connect
83
- end
84
-
85
- describe 'certificate expiry' do
86
- it 'reflects if the certificate will expire soon' do
87
- cert = x509_certificate
88
- expect(connection).to receive(:reflect).with(:ssl_certificate_will_expire, app, cert.not_after)
89
- Timecop.freeze(cert.not_after - 3.days) { connection.connect }
90
- end
91
-
92
- it 'logs that the certificate will expire soon' do
93
- cert = x509_certificate
94
- expect(logger).to receive(:warn).with("[#{app.name}] Certificate will expire at #{cert.not_after.utc}.")
95
- Timecop.freeze(cert.not_after - 3.days) { connection.connect }
96
- end
97
-
98
- it 'does not reflect if the certificate will not expire soon' do
99
- cert = x509_certificate
100
- expect(connection).not_to receive(:reflect).with(:ssl_certificate_will_expire, app, kind_of(Time))
101
- Timecop.freeze(cert.not_after - 2.months) { connection.connect }
102
- end
103
-
104
- it 'logs that the certificate has expired' do
105
- cert = x509_certificate
106
- expect(logger).to receive(:error).with("[#{app.name}] Certificate expired at #{cert.not_after.utc}.")
107
- Timecop.freeze(cert.not_after + 1.day) { connection.connect rescue Rpush::CertificateExpiredError }
108
- end
109
-
110
- it 'raises an error if the certificate has expired' do
111
- cert = x509_certificate
112
- Timecop.freeze(cert.not_after + 1.day) do
113
- expect { connection.connect }.to raise_error(Rpush::CertificateExpiredError)
114
- end
115
- end
116
- end
117
-
118
- describe 'certificate revocation' do
119
- let(:cert_revoked_error) { OpenSSL::SSL::SSLError.new('certificate revoked') }
120
- before do
121
- allow(ssl_socket).to receive(:connect).and_raise(cert_revoked_error)
122
- end
123
-
124
- it 'reflects that the certificate has been revoked' do
125
- expect(connection).to receive(:reflect).with(:ssl_certificate_revoked, app, cert_revoked_error)
126
- expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked')
127
- end
128
-
129
- it 'logs that the certificate has been revoked' do
130
- expect(logger).to receive(:error).with('[Connection 0] Certificate has been revoked.')
131
- expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked')
132
- end
133
- end
134
- end
135
-
136
- describe "when shuting down the connection" do
137
- it "closes the TCP socket" do
138
- connection.connect
139
- expect(tcp_socket).to receive(:close)
140
- connection.close
141
- end
142
-
143
- it "does not attempt to close the TCP socket if it is not connected" do
144
- connection.connect
145
- expect(tcp_socket).not_to receive(:close)
146
- connection.instance_variable_set("@tcp_socket", nil)
147
- connection.close
148
- end
149
-
150
- it "closes the SSL socket" do
151
- connection.connect
152
- expect(ssl_socket).to receive(:close)
153
- connection.close
154
- end
155
-
156
- it "does not attempt to close the SSL socket if it is not connected" do
157
- connection.connect
158
- expect(ssl_socket).not_to receive(:close)
159
- connection.instance_variable_set("@ssl_socket", nil)
160
- connection.close
161
- end
162
-
163
- it "ignores IOError when the socket is already closed" do
164
- allow(tcp_socket).to receive(:close).and_raise(IOError)
165
- connection.connect
166
- connection.close
167
- end
168
- end
169
-
170
- shared_examples_for "when the write fails" do
171
- before do
172
- allow(connection).to receive(:sleep)
173
- connection.connect
174
- allow(ssl_socket).to receive(:write).and_raise(error)
175
- end
176
-
177
- it 'reflects the connection has been lost' do
178
- expect(connection).to receive(:reflect).with(:tcp_connection_lost, app, kind_of(error.class))
179
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
180
- end
181
-
182
- it "logs that the connection has been lost once only" do
183
- expect(logger).to receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error.class.name}, #{error.message}), reconnecting...").once
184
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
185
- end
186
-
187
- it "retries to make a connection 3 times" do
188
- expect(connection).to receive(:reconnect).exactly(3).times
189
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
190
- end
191
-
192
- it "raises a TcpConnectionError after 3 attempts at reconnecting" do
193
- expect do
194
- connection.write(nil)
195
- end.to raise_error(Rpush::Daemon::TcpConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error.class.name}, #{error.message}).")
196
- end
197
-
198
- it "sleeps 1 second before retrying the connection" do
199
- expect(connection).to receive(:sleep).with(1)
200
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
201
- end
202
- end
203
-
204
- describe "when write raises an Errno::EPIPE" do
205
- it_should_behave_like "when the write fails"
206
-
207
- def error
208
- Errno::EPIPE.new('an message')
209
- end
210
- end
211
-
212
- describe "when write raises an Errno::ETIMEDOUT" do
213
- it_should_behave_like "when the write fails"
214
-
215
- def error
216
- Errno::ETIMEDOUT.new('an message')
217
- end
218
- end
219
-
220
- describe "when write raises an OpenSSL::SSL::SSLError" do
221
- it_should_behave_like "when the write fails"
222
-
223
- def error
224
- OpenSSL::SSL::SSLError.new('an message')
225
- end
226
- end
227
-
228
- describe "when write raises an IOError" do
229
- it_should_behave_like "when the write fails"
230
-
231
- def error
232
- IOError.new('an message')
233
- end
234
- end
235
-
236
- describe "when reconnecting" do
237
- before { connection.connect }
238
-
239
- it 'closes the socket' do
240
- expect(connection).to receive(:close)
241
- connection.send(:reconnect)
242
- end
243
-
244
- it 'connects the socket' do
245
- expect(connection).to receive(:connect_socket)
246
- connection.send(:reconnect)
247
- end
248
- end
249
-
250
- describe "when sending a notification" do
251
- before { connection.connect }
252
-
253
- it "writes the data to the SSL socket" do
254
- expect(ssl_socket).to receive(:write).with("blah")
255
- connection.write("blah")
256
- end
257
-
258
- it "flushes the SSL socket" do
259
- expect(ssl_socket).to receive(:flush)
260
- connection.write("blah")
261
- end
262
- end
263
-
264
- describe 'idle period' do
265
- before { connection.connect }
266
-
267
- it 'reconnects if the connection has been idle for more than the defined period' do
268
- allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60)
269
- allow(Time).to receive_messages(now: Time.now + 61)
270
- expect(connection).to receive(:reconnect)
271
- connection.write('blah')
272
- end
273
-
274
- it 'resets the last touch time' do
275
- now = Time.now
276
- allow(Time).to receive_messages(now: now)
277
- connection.write('blah')
278
- expect(connection.last_touch).to eq now
279
- end
280
-
281
- it 'does not reconnect if the connection has not been idle for more than the defined period' do
282
- expect(connection).not_to receive(:reconnect)
283
- connection.write('blah')
284
- end
285
-
286
- it 'logs the the connection is idle' do
287
- allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60)
288
- allow(Time).to receive_messages(now: Time.now + 61)
289
- expect(Rpush.logger).to receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
290
- connection.write('blah')
291
- end
292
- end
293
- end