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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -4
- data/README.md +24 -44
- data/lib/generators/rpush_migration_generator.rb +1 -0
- data/lib/generators/templates/rpush.rb +23 -13
- data/lib/generators/templates/rpush_7_1_0_updates.rb +12 -0
- data/lib/rpush/client/active_model/apns/notification.rb +0 -4
- data/lib/rpush/client/active_model/{gcm → fcm}/app.rb +4 -3
- data/lib/rpush/client/active_model/{gcm → fcm}/expiry_collapse_key_mutual_inclusion_validator.rb +1 -1
- data/lib/rpush/client/active_model/fcm/notification.rb +129 -0
- data/lib/rpush/client/active_model/fcm/notification_keys_in_allowed_list_validator.rb +20 -0
- data/lib/rpush/client/active_model.rb +4 -3
- data/lib/rpush/client/active_record/{gcm → fcm}/app.rb +2 -2
- data/lib/rpush/client/active_record/{gcm → fcm}/notification.rb +2 -2
- data/lib/rpush/client/active_record.rb +2 -2
- data/lib/rpush/client/redis/app.rb +2 -0
- data/lib/rpush/client/redis/{gcm → fcm}/app.rb +2 -2
- data/lib/rpush/client/redis/{gcm → fcm}/notification.rb +2 -2
- data/lib/rpush/client/redis.rb +2 -2
- data/lib/rpush/configuration.rb +2 -19
- data/lib/rpush/daemon/apns2/delivery.rb +0 -1
- data/lib/rpush/daemon/apnsp8/delivery.rb +0 -1
- data/lib/rpush/daemon/fcm/delivery.rb +162 -0
- data/lib/rpush/daemon/{gcm.rb → fcm.rb} +1 -1
- data/lib/rpush/daemon/google_credential_cache.rb +41 -0
- data/lib/rpush/daemon/service_config_methods.rb +0 -2
- data/lib/rpush/daemon/store/active_record.rb +15 -12
- data/lib/rpush/daemon/store/interface.rb +3 -3
- data/lib/rpush/daemon/store/redis.rb +13 -9
- data/lib/rpush/daemon/webpush/delivery.rb +2 -2
- data/lib/rpush/daemon.rb +3 -9
- data/lib/rpush/reflection_collection.rb +3 -3
- data/lib/rpush/version.rb +2 -2
- data/lib/rpush.rb +1 -1
- data/spec/functional/apns2_spec.rb +2 -6
- data/spec/functional/cli_spec.rb +41 -15
- data/spec/functional/embed_spec.rb +57 -26
- data/spec/functional/{gcm_priority_spec.rb → fcm_priority_spec.rb} +13 -7
- data/spec/functional/fcm_spec.rb +77 -0
- data/spec/functional/retry_spec.rb +21 -4
- data/spec/functional/synchronization_spec.rb +1 -1
- data/spec/functional_spec_helper.rb +1 -7
- data/spec/spec_helper.rb +4 -1
- data/spec/support/active_record_setup.rb +3 -1
- data/spec/unit/client/active_record/{gcm → fcm}/app_spec.rb +2 -2
- data/spec/unit/client/active_record/fcm/notification_spec.rb +10 -0
- data/spec/unit/client/active_record/shared/app.rb +1 -1
- data/spec/unit/client/redis/fcm/app_spec.rb +5 -0
- data/spec/unit/client/redis/fcm/notification_spec.rb +5 -0
- data/spec/unit/client/shared/apns/notification.rb +0 -15
- data/spec/unit/client/shared/fcm/app.rb +4 -0
- data/spec/unit/client/shared/fcm/notification.rb +92 -0
- data/spec/unit/configuration_spec.rb +1 -1
- data/spec/unit/daemon/apnsp8/delivery_spec.rb +1 -1
- data/spec/unit/daemon/fcm/delivery_spec.rb +127 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +1 -1
- data/spec/unit/daemon/shared/store.rb +0 -42
- data/spec/unit/daemon/wns/delivery_spec.rb +1 -1
- data/spec/unit/logger_spec.rb +1 -1
- data/spec/unit_spec_helper.rb +1 -1
- metadata +127 -69
- data/lib/rpush/apns_feedback.rb +0 -18
- data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
- data/lib/rpush/daemon/apns/delivery.rb +0 -43
- data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
- data/lib/rpush/daemon/apns.rb +0 -17
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
- data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
- data/lib/rpush/daemon/gcm/delivery.rb +0 -241
- data/lib/rpush/daemon/tcp_connection.rb +0 -190
- data/spec/functional/apns_spec.rb +0 -162
- data/spec/functional/gcm_spec.rb +0 -46
- data/spec/functional/new_app_spec.rb +0 -44
- data/spec/unit/apns_feedback_spec.rb +0 -39
- data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
- data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
- data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
- data/spec/unit/client/shared/gcm/app.rb +0 -4
- data/spec/unit/client/shared/gcm/notification.rb +0 -77
- data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
- data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
- 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
|