rpush 7.0.1 → 9.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -4
  3. data/README.md +24 -44
  4. data/lib/generators/rpush_migration_generator.rb +1 -0
  5. data/lib/generators/templates/rpush.rb +23 -13
  6. data/lib/generators/templates/rpush_7_1_0_updates.rb +12 -0
  7. data/lib/rpush/client/active_model/apns/notification.rb +0 -4
  8. data/lib/rpush/client/active_model/{gcm → fcm}/app.rb +4 -3
  9. data/lib/rpush/client/active_model/{gcm → fcm}/expiry_collapse_key_mutual_inclusion_validator.rb +1 -1
  10. data/lib/rpush/client/active_model/fcm/notification.rb +129 -0
  11. data/lib/rpush/client/active_model/fcm/notification_keys_in_allowed_list_validator.rb +20 -0
  12. data/lib/rpush/client/active_model.rb +4 -3
  13. data/lib/rpush/client/active_record/{gcm → fcm}/app.rb +2 -2
  14. data/lib/rpush/client/active_record/{gcm → fcm}/notification.rb +2 -2
  15. data/lib/rpush/client/active_record.rb +2 -2
  16. data/lib/rpush/client/redis/app.rb +2 -0
  17. data/lib/rpush/client/redis/{gcm → fcm}/app.rb +2 -2
  18. data/lib/rpush/client/redis/{gcm → fcm}/notification.rb +2 -2
  19. data/lib/rpush/client/redis.rb +2 -2
  20. data/lib/rpush/configuration.rb +2 -19
  21. data/lib/rpush/daemon/apns2/delivery.rb +0 -1
  22. data/lib/rpush/daemon/apnsp8/delivery.rb +0 -1
  23. data/lib/rpush/daemon/fcm/delivery.rb +162 -0
  24. data/lib/rpush/daemon/{gcm.rb → fcm.rb} +1 -1
  25. data/lib/rpush/daemon/google_credential_cache.rb +41 -0
  26. data/lib/rpush/daemon/service_config_methods.rb +0 -2
  27. data/lib/rpush/daemon/store/active_record.rb +15 -12
  28. data/lib/rpush/daemon/store/interface.rb +3 -3
  29. data/lib/rpush/daemon/store/redis.rb +13 -9
  30. data/lib/rpush/daemon/webpush/delivery.rb +2 -2
  31. data/lib/rpush/daemon.rb +3 -9
  32. data/lib/rpush/reflection_collection.rb +3 -3
  33. data/lib/rpush/version.rb +2 -2
  34. data/lib/rpush.rb +1 -1
  35. data/spec/functional/apns2_spec.rb +2 -6
  36. data/spec/functional/cli_spec.rb +41 -15
  37. data/spec/functional/embed_spec.rb +57 -26
  38. data/spec/functional/{gcm_priority_spec.rb → fcm_priority_spec.rb} +13 -7
  39. data/spec/functional/fcm_spec.rb +77 -0
  40. data/spec/functional/retry_spec.rb +21 -4
  41. data/spec/functional/synchronization_spec.rb +1 -1
  42. data/spec/functional_spec_helper.rb +1 -7
  43. data/spec/spec_helper.rb +4 -1
  44. data/spec/support/active_record_setup.rb +3 -1
  45. data/spec/unit/client/active_record/{gcm → fcm}/app_spec.rb +2 -2
  46. data/spec/unit/client/active_record/fcm/notification_spec.rb +10 -0
  47. data/spec/unit/client/active_record/shared/app.rb +1 -1
  48. data/spec/unit/client/redis/fcm/app_spec.rb +5 -0
  49. data/spec/unit/client/redis/fcm/notification_spec.rb +5 -0
  50. data/spec/unit/client/shared/apns/notification.rb +0 -15
  51. data/spec/unit/client/shared/fcm/app.rb +4 -0
  52. data/spec/unit/client/shared/fcm/notification.rb +92 -0
  53. data/spec/unit/configuration_spec.rb +1 -1
  54. data/spec/unit/daemon/apnsp8/delivery_spec.rb +1 -1
  55. data/spec/unit/daemon/fcm/delivery_spec.rb +127 -0
  56. data/spec/unit/daemon/service_config_methods_spec.rb +1 -1
  57. data/spec/unit/daemon/shared/store.rb +0 -42
  58. data/spec/unit/daemon/wns/delivery_spec.rb +1 -1
  59. data/spec/unit/logger_spec.rb +1 -1
  60. data/spec/unit_spec_helper.rb +1 -1
  61. metadata +127 -69
  62. data/lib/rpush/apns_feedback.rb +0 -18
  63. data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
  64. data/lib/rpush/daemon/apns/delivery.rb +0 -43
  65. data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
  66. data/lib/rpush/daemon/apns.rb +0 -17
  67. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
  68. data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
  69. data/lib/rpush/daemon/gcm/delivery.rb +0 -241
  70. data/lib/rpush/daemon/tcp_connection.rb +0 -190
  71. data/spec/functional/apns_spec.rb +0 -162
  72. data/spec/functional/gcm_spec.rb +0 -46
  73. data/spec/functional/new_app_spec.rb +0 -44
  74. data/spec/unit/apns_feedback_spec.rb +0 -39
  75. data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
  76. data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
  77. data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
  78. data/spec/unit/client/shared/gcm/app.rb +0 -4
  79. data/spec/unit/client/shared/gcm/notification.rb +0 -77
  80. data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
  81. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
  82. data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
  83. data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
  84. data/spec/unit/daemon/tcp_connection_spec.rb +0 -292
@@ -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,292 +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(OpenSSL::SSL::SSLContext).to receive_messages(new: ssl_context)
19
- allow(OpenSSL::PKey::RSA).to receive_messages(new: rsa_key)
20
- allow(OpenSSL::X509::Certificate).to receive_messages(new: x509_certificate)
21
- allow(TCPSocket).to receive_messages(new: tcp_socket)
22
- allow(OpenSSL::SSL::SSLSocket).to receive_messages(new: ssl_socket)
23
- allow(Rpush).to receive_messages(logger: logger)
24
- allow(connection).to receive(:reflect)
25
- end
26
-
27
- it "reads the number of bytes from the SSL socket" do
28
- expect(ssl_socket).to receive(:read).with(123)
29
- connection.connect
30
- connection.read(123)
31
- end
32
-
33
- it "selects on the SSL socket until the given timeout" do
34
- expect(IO).to receive(:select).with([ssl_socket], nil, nil, 10)
35
- connection.connect
36
- connection.select(10)
37
- end
38
-
39
- describe "when setting up the SSL context" do
40
- it "sets the key on the context" do
41
- expect(OpenSSL::PKey::RSA).to receive(:new).with(certificate, password).and_return(rsa_key)
42
- expect(ssl_context).to receive(:key=).with(rsa_key)
43
- connection.connect
44
- end
45
-
46
- it "sets the cert on the context" do
47
- expect(OpenSSL::X509::Certificate).to receive(:new).with(certificate).and_return(x509_certificate)
48
- expect(ssl_context).to receive(:cert=).with(x509_certificate)
49
- connection.connect
50
- end
51
- end
52
-
53
- describe "when connecting the socket" do
54
- it "creates a TCP socket using the configured host and port" do
55
- expect(TCPSocket).to receive(:new).with(host, port).and_return(tcp_socket)
56
- connection.connect
57
- end
58
-
59
- it "creates a new SSL socket using the TCP socket and SSL context" do
60
- expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_context).and_return(ssl_socket)
61
- connection.connect
62
- end
63
-
64
- it "sets the sync option on the SSL socket" do
65
- expect(ssl_socket).to receive(:sync=).with(true)
66
- connection.connect
67
- end
68
-
69
- it "connects the SSL socket" do
70
- expect(ssl_socket).to receive(:connect)
71
- connection.connect
72
- end
73
-
74
- it "sets the socket option TCP_NODELAY" do
75
- expect(tcp_socket).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
76
- connection.connect
77
- end
78
-
79
- it "sets the socket option SO_KEEPALIVE" do
80
- expect(tcp_socket).to receive(:setsockopt).with(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
81
- connection.connect
82
- end
83
-
84
- describe 'certificate expiry' do
85
- it 'reflects if the certificate will expire soon' do
86
- cert = OpenSSL::X509::Certificate.new(app.certificate)
87
- expect(connection).to receive(:reflect).with(:ssl_certificate_will_expire, app, cert.not_after)
88
- Timecop.freeze(cert.not_after - 3.days) { connection.connect }
89
- end
90
-
91
- it 'logs that the certificate will expire soon' do
92
- cert = OpenSSL::X509::Certificate.new(app.certificate)
93
- expect(logger).to receive(:warn).with("[#{app.name}] Certificate will expire at 2022-09-07 03:18:32 UTC.")
94
- Timecop.freeze(cert.not_after - 3.days) { connection.connect }
95
- end
96
-
97
- it 'does not reflect if the certificate will not expire soon' do
98
- cert = OpenSSL::X509::Certificate.new(app.certificate)
99
- expect(connection).not_to receive(:reflect).with(:ssl_certificate_will_expire, app, kind_of(Time))
100
- Timecop.freeze(cert.not_after - 2.months) { connection.connect }
101
- end
102
-
103
- it 'logs that the certificate has expired' do
104
- cert = OpenSSL::X509::Certificate.new(app.certificate)
105
- expect(logger).to receive(:error).with("[#{app.name}] Certificate expired at 2022-09-07 03:18:32 UTC.")
106
- Timecop.freeze(cert.not_after + 1.day) { connection.connect rescue Rpush::CertificateExpiredError }
107
- end
108
-
109
- it 'raises an error if the certificate has expired' do
110
- cert = OpenSSL::X509::Certificate.new(app.certificate)
111
- Timecop.freeze(cert.not_after + 1.day) do
112
- expect { connection.connect }.to raise_error(Rpush::CertificateExpiredError)
113
- end
114
- end
115
- end
116
-
117
- describe 'certificate revocation' do
118
- let(:cert_revoked_error) { OpenSSL::SSL::SSLError.new('certificate revoked') }
119
- before do
120
- allow(ssl_socket).to receive(:connect).and_raise(cert_revoked_error)
121
- end
122
-
123
- it 'reflects that the certificate has been revoked' do
124
- expect(connection).to receive(:reflect).with(:ssl_certificate_revoked, app, cert_revoked_error)
125
- expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked')
126
- end
127
-
128
- it 'logs that the certificate has been revoked' do
129
- expect(logger).to receive(:error).with('[Connection 0] Certificate has been revoked.')
130
- expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked')
131
- end
132
- end
133
- end
134
-
135
- describe "when shuting down the connection" do
136
- it "closes the TCP socket" do
137
- connection.connect
138
- expect(tcp_socket).to receive(:close)
139
- connection.close
140
- end
141
-
142
- it "does not attempt to close the TCP socket if it is not connected" do
143
- connection.connect
144
- expect(tcp_socket).not_to receive(:close)
145
- connection.instance_variable_set("@tcp_socket", nil)
146
- connection.close
147
- end
148
-
149
- it "closes the SSL socket" do
150
- connection.connect
151
- expect(ssl_socket).to receive(:close)
152
- connection.close
153
- end
154
-
155
- it "does not attempt to close the SSL socket if it is not connected" do
156
- connection.connect
157
- expect(ssl_socket).not_to receive(:close)
158
- connection.instance_variable_set("@ssl_socket", nil)
159
- connection.close
160
- end
161
-
162
- it "ignores IOError when the socket is already closed" do
163
- allow(tcp_socket).to receive(:close).and_raise(IOError)
164
- connection.connect
165
- connection.close
166
- end
167
- end
168
-
169
- shared_examples_for "when the write fails" do
170
- before do
171
- allow(connection).to receive(:sleep)
172
- connection.connect
173
- allow(ssl_socket).to receive(:write).and_raise(error)
174
- end
175
-
176
- it 'reflects the connection has been lost' do
177
- expect(connection).to receive(:reflect).with(:tcp_connection_lost, app, kind_of(error.class))
178
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
179
- end
180
-
181
- it "logs that the connection has been lost once only" do
182
- expect(logger).to receive(:error).with("[Connection 0] Lost connection to gateway.push.apple.com:2195 (#{error.class.name}, #{error.message}), reconnecting...").once
183
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
184
- end
185
-
186
- it "retries to make a connection 3 times" do
187
- expect(connection).to receive(:reconnect).exactly(3).times
188
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
189
- end
190
-
191
- it "raises a TcpConnectionError after 3 attempts at reconnecting" do
192
- expect do
193
- connection.write(nil)
194
- end.to raise_error(Rpush::Daemon::TcpConnectionError, "Connection 0 tried 3 times to reconnect but failed (#{error.class.name}, #{error.message}).")
195
- end
196
-
197
- it "sleeps 1 second before retrying the connection" do
198
- expect(connection).to receive(:sleep).with(1)
199
- expect { connection.write(nil) }.to raise_error(Rpush::Daemon::TcpConnectionError)
200
- end
201
- end
202
-
203
- describe "when write raises an Errno::EPIPE" do
204
- it_should_behave_like "when the write fails"
205
-
206
- def error
207
- Errno::EPIPE.new('an message')
208
- end
209
- end
210
-
211
- describe "when write raises an Errno::ETIMEDOUT" do
212
- it_should_behave_like "when the write fails"
213
-
214
- def error
215
- Errno::ETIMEDOUT.new('an message')
216
- end
217
- end
218
-
219
- describe "when write raises an OpenSSL::SSL::SSLError" do
220
- it_should_behave_like "when the write fails"
221
-
222
- def error
223
- OpenSSL::SSL::SSLError.new('an message')
224
- end
225
- end
226
-
227
- describe "when write raises an IOError" do
228
- it_should_behave_like "when the write fails"
229
-
230
- def error
231
- IOError.new('an message')
232
- end
233
- end
234
-
235
- describe "when reconnecting" do
236
- before { connection.connect }
237
-
238
- it 'closes the socket' do
239
- expect(connection).to receive(:close)
240
- connection.send(:reconnect)
241
- end
242
-
243
- it 'connects the socket' do
244
- expect(connection).to receive(:connect_socket)
245
- connection.send(:reconnect)
246
- end
247
- end
248
-
249
- describe "when sending a notification" do
250
- before { connection.connect }
251
-
252
- it "writes the data to the SSL socket" do
253
- expect(ssl_socket).to receive(:write).with("blah")
254
- connection.write("blah")
255
- end
256
-
257
- it "flushes the SSL socket" do
258
- expect(ssl_socket).to receive(:flush)
259
- connection.write("blah")
260
- end
261
- end
262
-
263
- describe 'idle period' do
264
- before { connection.connect }
265
-
266
- it 'reconnects if the connection has been idle for more than the defined period' do
267
- allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60)
268
- allow(Time).to receive_messages(now: Time.now + 61)
269
- expect(connection).to receive(:reconnect)
270
- connection.write('blah')
271
- end
272
-
273
- it 'resets the last touch time' do
274
- now = Time.now
275
- allow(Time).to receive_messages(now: now)
276
- connection.write('blah')
277
- expect(connection.last_touch).to eq now
278
- end
279
-
280
- it 'does not reconnect if the connection has not been idle for more than the defined period' do
281
- expect(connection).not_to receive(:reconnect)
282
- connection.write('blah')
283
- end
284
-
285
- it 'logs the the connection is idle' do
286
- allow(Rpush::Daemon::TcpConnection).to receive_messages(idle_period: 60)
287
- allow(Time).to receive_messages(now: Time.now + 61)
288
- expect(Rpush.logger).to receive(:info).with('[Connection 0] Idle period exceeded, reconnecting...')
289
- connection.write('blah')
290
- end
291
- end
292
- end