rpush 7.0.1 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
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