rpush 1.0.0-java
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 +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +7 -0
- data/README.md +189 -0
- data/bin/rpush +36 -0
- data/config/database.yml +44 -0
- data/lib/generators/rpush_generator.rb +44 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +102 -0
- data/lib/generators/templates/add_rpush.rb +349 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +18 -0
- data/lib/generators/templates/create_rapns_notifications.rb +29 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
- data/lib/generators/templates/rpush.rb +104 -0
- data/lib/rpush/TODO +3 -0
- data/lib/rpush/adm/app.rb +15 -0
- data/lib/rpush/adm/data_validator.rb +11 -0
- data/lib/rpush/adm/notification.rb +29 -0
- data/lib/rpush/apns/app.rb +29 -0
- data/lib/rpush/apns/binary_notification_validator.rb +12 -0
- data/lib/rpush/apns/device_token_format_validator.rb +12 -0
- data/lib/rpush/apns/feedback.rb +16 -0
- data/lib/rpush/apns/notification.rb +84 -0
- data/lib/rpush/apns_feedback.rb +13 -0
- data/lib/rpush/app.rb +18 -0
- data/lib/rpush/configuration.rb +75 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rpush/daemon/apns/delivery.rb +64 -0
- data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
- data/lib/rpush/daemon/apns.rb +16 -0
- data/lib/rpush/daemon/app_runner.rb +187 -0
- data/lib/rpush/daemon/batch.rb +115 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +28 -0
- data/lib/rpush/daemon/delivery_error.rb +19 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
- data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
- data/lib/rpush/daemon/feeder.rb +68 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
- data/lib/rpush/daemon/loggable.rb +31 -0
- data/lib/rpush/daemon/reflectable.rb +13 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +20 -0
- data/lib/rpush/daemon/service_config_methods.rb +33 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
- data/lib/rpush/daemon/store/active_record.rb +154 -0
- data/lib/rpush/daemon/tcp_connection.rb +143 -0
- data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon.rb +140 -0
- data/lib/rpush/deprecatable.rb +23 -0
- data/lib/rpush/deprecation.rb +23 -0
- data/lib/rpush/embed.rb +28 -0
- data/lib/rpush/gcm/app.rb +11 -0
- data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rpush/gcm/notification.rb +30 -0
- data/lib/rpush/logger.rb +63 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/notification.rb +69 -0
- data/lib/rpush/notifier.rb +52 -0
- data/lib/rpush/payload_data_size_validator.rb +10 -0
- data/lib/rpush/push.rb +16 -0
- data/lib/rpush/railtie.rb +11 -0
- data/lib/rpush/reflection.rb +58 -0
- data/lib/rpush/registration_ids_count_validator.rb +10 -0
- data/lib/rpush/version.rb +3 -0
- data/lib/rpush/wpns/app.rb +9 -0
- data/lib/rpush/wpns/notification.rb +26 -0
- data/lib/rpush.rb +62 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/rpush.rake +16 -0
- data/lib/tasks/test.rake +38 -0
- data/spec/functional/adm_spec.rb +43 -0
- data/spec/functional/apns_spec.rb +58 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +42 -0
- data/spec/functional/wpns_spec.rb +41 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/install.sh +32 -0
- data/spec/support/simplecov_helper.rb +20 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/adm/app_spec.rb +58 -0
- data/spec/unit/adm/notification_spec.rb +45 -0
- data/spec/unit/apns/app_spec.rb +29 -0
- data/spec/unit/apns/feedback_spec.rb +9 -0
- data/spec/unit/apns/notification_spec.rb +208 -0
- data/spec/unit/apns_feedback_spec.rb +21 -0
- data/spec/unit/app_spec.rb +30 -0
- data/spec/unit/configuration_spec.rb +45 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
- data/spec/unit/daemon/app_runner_spec.rb +292 -0
- data/spec/unit/daemon/batch_spec.rb +232 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
- data/spec/unit/daemon/feeder_spec.rb +98 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
- data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
- data/spec/unit/daemon_spec.rb +159 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +50 -0
- data/spec/unit/gcm/app_spec.rb +4 -0
- data/spec/unit/gcm/notification_spec.rb +36 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +105 -0
- data/spec/unit/notification_spec.rb +15 -0
- data/spec/unit/notifier_spec.rb +49 -0
- data/spec/unit/push_spec.rb +43 -0
- data/spec/unit/reflection_spec.rb +30 -0
- data/spec/unit/rpush_spec.rb +9 -0
- data/spec/unit/wpns/app_spec.rb +4 -0
- data/spec/unit/wpns/notification_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +101 -0
- metadata +304 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::DispatcherLoop do
|
4
|
+
def run_dispatcher_loop
|
5
|
+
dispatcher_loop.start
|
6
|
+
dispatcher_loop.stop
|
7
|
+
dispatcher_loop.wakeup
|
8
|
+
dispatcher_loop.wait
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:notification) { double }
|
12
|
+
let(:batch) { double(:notification_dispatched => nil) }
|
13
|
+
let(:queue) { Queue.new }
|
14
|
+
let(:dispatcher) { double(:dispatch => nil, :cleanup => nil) }
|
15
|
+
let(:dispatcher_loop) { Rpush::Daemon::DispatcherLoop.new(queue, dispatcher) }
|
16
|
+
let(:store) { double(Rpush::Daemon::Store::ActiveRecord, release_connection: nil)}
|
17
|
+
|
18
|
+
before do
|
19
|
+
Rpush::Daemon.stub(:store => store)
|
20
|
+
queue.push([notification, batch])
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'logs errors' do
|
24
|
+
logger = double
|
25
|
+
Rpush.stub(:logger => logger)
|
26
|
+
error = StandardError.new
|
27
|
+
dispatcher.stub(:dispatch).and_raise(error)
|
28
|
+
Rpush.logger.should_receive(:error).with(error)
|
29
|
+
run_dispatcher_loop
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'reflects an exception' do
|
33
|
+
Rpush.stub(:logger => double(:error => nil))
|
34
|
+
error = StandardError.new
|
35
|
+
dispatcher.stub(:dispatch).and_raise(error)
|
36
|
+
dispatcher_loop.should_receive(:reflect).with(:error, error)
|
37
|
+
run_dispatcher_loop
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'instructs the batch that the notification has been processed' do
|
41
|
+
batch.should_receive(:notification_dispatched)
|
42
|
+
run_dispatcher_loop
|
43
|
+
end
|
44
|
+
|
45
|
+
it "instructs the queue to wakeup the thread when told to stop" do
|
46
|
+
queue.should_receive(:push).with(Rpush::Daemon::DispatcherLoop::WAKEUP).and_call_original
|
47
|
+
run_dispatcher_loop
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'stop' do
|
51
|
+
before do
|
52
|
+
queue.clear
|
53
|
+
queue.push(Rpush::Daemon::DispatcherLoop::WAKEUP)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not attempt to dispatch when a WAKEUP is dequeued' do
|
57
|
+
dispatcher.should_not_receive(:dispatch)
|
58
|
+
run_dispatcher_loop
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'instructs the dispatcher to cleanup' do
|
62
|
+
dispatcher.should_receive(:cleanup)
|
63
|
+
run_dispatcher_loop
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'releases the store connection' do
|
67
|
+
Rpush::Daemon.store.should_receive(:release_connection)
|
68
|
+
run_dispatcher_loop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "unit_spec_helper"
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Feeder do
|
4
|
+
let(:config) { double(:batch_size => 5000,
|
5
|
+
:push_poll => 0,
|
6
|
+
:embedded => false,
|
7
|
+
:push => false,
|
8
|
+
:wakeup => nil) }
|
9
|
+
let!(:app) { Rpush::Apns::App.create!(:name => 'my_app', :environment => 'development', :certificate => TEST_CERT) }
|
10
|
+
let(:notification) { Rpush::Apns::Notification.create!(:device_token => "a" * 64, :app => app) }
|
11
|
+
let(:logger) { double }
|
12
|
+
let(:interruptible_sleep) { double(:sleep => nil, :interrupt_sleep => nil) }
|
13
|
+
let(:store) { double(Rpush::Daemon::Store::ActiveRecord,
|
14
|
+
deliverable_notifications: [notification], release_connection: nil) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
Rpush.stub(:config => config,:logger => logger)
|
18
|
+
Rpush::Daemon.stub(:store => store)
|
19
|
+
Rpush::Daemon::Feeder.stub(:stop? => true)
|
20
|
+
Rpush::Daemon::AppRunner.stub(:enqueue => nil, :idle => [double(:app => app)])
|
21
|
+
Rpush::Daemon::InterruptibleSleep.stub(:new => interruptible_sleep)
|
22
|
+
Rpush::Daemon::Feeder.instance_variable_set('@interruptible_sleeper', nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_and_stop
|
26
|
+
Rpush::Daemon::Feeder.start
|
27
|
+
Rpush::Daemon::Feeder.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'starts the loop in a new thread if embedded' do
|
31
|
+
config.stub(:embedded => true)
|
32
|
+
Thread.should_receive(:new).and_yield
|
33
|
+
Rpush::Daemon::Feeder.should_receive(:feed_forever)
|
34
|
+
start_and_stop
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'loads deliverable notifications' do
|
38
|
+
Rpush::Daemon.store.should_receive(:deliverable_notifications).with([app])
|
39
|
+
start_and_stop
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not attempt to load deliverable notifications if there are no idle runners' do
|
43
|
+
Rpush::Daemon::AppRunner.stub(:idle => [])
|
44
|
+
Rpush::Daemon.store.should_not_receive(:deliverable_notifications)
|
45
|
+
start_and_stop
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'enqueues notifications without looping if in push mode' do
|
49
|
+
config.stub(:push => true)
|
50
|
+
Rpush::Daemon::Feeder.should_not_receive(:feed_forever)
|
51
|
+
Rpush::Daemon::Feeder.should_receive(:enqueue_notifications)
|
52
|
+
start_and_stop
|
53
|
+
end
|
54
|
+
|
55
|
+
it "enqueues the notifications" do
|
56
|
+
Rpush::Daemon::AppRunner.should_receive(:enqueue).with([notification])
|
57
|
+
start_and_stop
|
58
|
+
end
|
59
|
+
|
60
|
+
it "logs errors" do
|
61
|
+
e = StandardError.new("bork")
|
62
|
+
Rpush::Daemon.store.stub(:deliverable_notifications).and_raise(e)
|
63
|
+
Rpush.logger.should_receive(:error).with(e)
|
64
|
+
start_and_stop
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'stop' do
|
68
|
+
it 'interrupts sleep when stopped' do
|
69
|
+
Rpush::Daemon::Feeder.should_receive(:interrupt_sleep)
|
70
|
+
start_and_stop
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'releases the store connection when stopped' do
|
74
|
+
Rpush::Daemon.store.should_receive(:release_connection)
|
75
|
+
start_and_stop
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "enqueues notifications when started" do
|
80
|
+
Rpush::Daemon::Feeder.should_receive(:enqueue_notifications).at_least(:once)
|
81
|
+
Rpush::Daemon::Feeder.stub(:loop).and_yield
|
82
|
+
start_and_stop
|
83
|
+
end
|
84
|
+
|
85
|
+
it "sleeps for the given period" do
|
86
|
+
config.stub(:push_poll => 2)
|
87
|
+
interruptible_sleep.should_receive(:sleep).with(2)
|
88
|
+
start_and_stop
|
89
|
+
end
|
90
|
+
|
91
|
+
it "creates the wakeup socket" do
|
92
|
+
bind = '127.0.0.1'
|
93
|
+
port = 12345
|
94
|
+
config.stub(:wakeup => { :bind => bind, :port => port})
|
95
|
+
interruptible_sleep.should_receive(:enable_wake_on_udp).with(bind, port)
|
96
|
+
start_and_stop
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Gcm::Delivery do
|
4
|
+
let(:app) { Rpush::Gcm::App.new(: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) }
|
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
|
+
before do
|
19
|
+
delivery.stub(:reflect => nil)
|
20
|
+
Rpush::Daemon.stub(:store => store)
|
21
|
+
Time.stub(:now => now)
|
22
|
+
Rpush.stub(:logger => logger)
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_examples_for 'a notification with some delivery failures' do
|
26
|
+
let(:new_notification) { Rpush::Gcm::Notification.where('id != ?', notification.id).first }
|
27
|
+
|
28
|
+
before { response.stub(:body => JSON.dump(body)) }
|
29
|
+
|
30
|
+
it 'marks the original notification as failed' do
|
31
|
+
delivery.should_receive(:mark_failed).with(nil, error_description)
|
32
|
+
perform rescue Rpush::DeliveryError
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'creates a new notification for the unavailable devices' do
|
36
|
+
notification.update_attributes(:registration_ids => ['id_0', 'id_1', 'id_2'], :data => {'one' => 1}, :collapse_key => 'thing', :delay_while_idle => true)
|
37
|
+
response.stub(:header => { 'retry-after' => 10 })
|
38
|
+
attrs = { 'collapse_key' => 'thing', 'delay_while_idle' => true, 'app_id' => app.id }
|
39
|
+
store.should_receive(:create_gcm_notification).with(attrs, notification.data,
|
40
|
+
['id_0', 'id_2'], now + 10.seconds, notification.app)
|
41
|
+
perform rescue Rpush::DeliveryError
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises a DeliveryError' do
|
45
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'a 200 response' do
|
50
|
+
before do
|
51
|
+
response.stub(:code => 200)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'reflects on any IDs which successfully received the notification' do
|
55
|
+
body = {
|
56
|
+
'failure' => 1,
|
57
|
+
'success' => 1,
|
58
|
+
'results' => [
|
59
|
+
{ 'message_id' => '1:000' },
|
60
|
+
{ 'error' => 'Err' }
|
61
|
+
]
|
62
|
+
}
|
63
|
+
|
64
|
+
response.stub(:body => JSON.dump(body))
|
65
|
+
notification.stub(:registration_ids => ['1', '2'])
|
66
|
+
delivery.should_receive(:reflect).with(:gcm_delivered_to_recipient, notification, '1')
|
67
|
+
delivery.should_not_receive(:reflect).with(:gcm_delivered_to_recipient, notification, '2')
|
68
|
+
perform rescue Rpush::DeliveryError
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'reflects on any IDs which failed to receive the notification' do
|
72
|
+
body = {
|
73
|
+
'failure' => 1,
|
74
|
+
'success' => 1,
|
75
|
+
'results' => [
|
76
|
+
{ 'error' => 'Err' },
|
77
|
+
{ 'message_id' => '1:000' }
|
78
|
+
]
|
79
|
+
}
|
80
|
+
|
81
|
+
response.stub(:body => JSON.dump(body))
|
82
|
+
notification.stub(:registration_ids => ['1', '2'])
|
83
|
+
delivery.should_receive(:reflect).with(:gcm_failed_to_recipient, notification, 'Err', '1')
|
84
|
+
delivery.should_not_receive(:reflect).with(:gcm_failed_to_recipient, notification, anything, '2')
|
85
|
+
perform rescue Rpush::DeliveryError
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'reflects on canonical IDs' do
|
89
|
+
body = {
|
90
|
+
'failure' => 0,
|
91
|
+
'success' => 3,
|
92
|
+
'canonical_ids' => 1,
|
93
|
+
'results' => [
|
94
|
+
{ 'message_id' => '1:000' },
|
95
|
+
{ 'message_id' => '1:000', 'registration_id' => 'canonical123' },
|
96
|
+
{ 'message_id' => '1:000' },
|
97
|
+
]}
|
98
|
+
|
99
|
+
response.stub(:body => JSON.dump(body))
|
100
|
+
notification.stub(:registration_ids => ['1', '2', '3'])
|
101
|
+
delivery.should_receive(:reflect).with(:gcm_canonical_id, '2', 'canonical123')
|
102
|
+
perform
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'reflects on invalid IDs' do
|
106
|
+
body = {
|
107
|
+
'failure' => 1,
|
108
|
+
'success' => 2,
|
109
|
+
'canonical_ids' => 0,
|
110
|
+
'results' => [
|
111
|
+
{ 'message_id' => '1:000' },
|
112
|
+
{ 'error' => 'NotRegistered' },
|
113
|
+
{ 'message_id' => '1:000' },
|
114
|
+
]}
|
115
|
+
|
116
|
+
response.stub(:body => JSON.dump(body))
|
117
|
+
notification.stub(:registration_ids => ['1', '2', '3'])
|
118
|
+
delivery.should_receive(:reflect).with(:gcm_invalid_registration_id, app, 'NotRegistered', '2')
|
119
|
+
perform rescue Rpush::DeliveryError
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'when delivered successfully to all devices' do
|
123
|
+
let(:body) {{
|
124
|
+
'failure' => 0,
|
125
|
+
'success' => 1,
|
126
|
+
'results' => [{ 'message_id' => '1:000'}]
|
127
|
+
}}
|
128
|
+
|
129
|
+
before { response.stub(:body => JSON.dump(body)) }
|
130
|
+
|
131
|
+
it 'marks the notification as delivered' do
|
132
|
+
delivery.should_receive(:mark_delivered)
|
133
|
+
perform
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'logs that the notification was delivered' do
|
137
|
+
logger.should_receive(:info).with("[MyApp] #{notification.id} sent to xyz")
|
138
|
+
perform
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'marks a notification as failed if any ids are invalid' do
|
143
|
+
body = {
|
144
|
+
'failure' => 1,
|
145
|
+
'success' => 2,
|
146
|
+
'canonical_ids' => 0,
|
147
|
+
'results' => [
|
148
|
+
{ 'message_id' => '1:000' },
|
149
|
+
{ 'error' => 'NotRegistered' },
|
150
|
+
{ 'message_id' => '1:000' },
|
151
|
+
]}
|
152
|
+
|
153
|
+
response.stub(:body => JSON.dump(body))
|
154
|
+
delivery.should_receive(:mark_failed)
|
155
|
+
delivery.should_not_receive(:mark_retryable)
|
156
|
+
store.should_not_receive(:create_gcm_notification)
|
157
|
+
perform rescue Rpush::DeliveryError
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'marks a notification as failed if any deliveries failed that cannot be retried' do
|
161
|
+
body = {
|
162
|
+
'failure' => 1,
|
163
|
+
'success' => 1,
|
164
|
+
'results' => [
|
165
|
+
{ 'message_id' => '1:000' },
|
166
|
+
{ 'error' => 'InvalidDataKey' }
|
167
|
+
]}
|
168
|
+
response.stub(:body => JSON.dump(body))
|
169
|
+
delivery.should_receive(:mark_failed).with(nil, "Failed to deliver to all recipients. Errors: InvalidDataKey.")
|
170
|
+
perform rescue Rpush::DeliveryError
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'all deliveries failed with Unavailable or InternalServerError' do
|
174
|
+
let(:body) {{
|
175
|
+
'failure' => 2,
|
176
|
+
'success' => 0,
|
177
|
+
'results' => [
|
178
|
+
{ 'error' => 'Unavailable' },
|
179
|
+
{ 'error' => 'Unavailable' }
|
180
|
+
]}}
|
181
|
+
|
182
|
+
before do
|
183
|
+
response.stub(:body => JSON.dump(body))
|
184
|
+
notification.stub(:registration_ids => ['1', '2'])
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'retries the notification respecting the Retry-After header' do
|
188
|
+
response.stub(:header => { 'retry-after' => 10 })
|
189
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 10.seconds)
|
190
|
+
perform
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'retries the notification using exponential back-off if the Retry-After header is not present' do
|
194
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 2)
|
195
|
+
perform
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'does not mark the notification as failed' do
|
199
|
+
delivery.should_not_receive(:mark_failed)
|
200
|
+
perform
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'logs that the notification will be retried' do
|
204
|
+
notification.retries = 1
|
205
|
+
notification.deliver_after = now + 2
|
206
|
+
Rpush.logger.should_receive(:warn).with("[MyApp] All recipients unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
|
207
|
+
perform
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe 'all deliveries failed with some as Unavailable or InternalServerError' do
|
212
|
+
let(:body) {{
|
213
|
+
'failure' => 3,
|
214
|
+
'success' => 0,
|
215
|
+
'results' => [
|
216
|
+
{ 'error' => 'Unavailable' },
|
217
|
+
{ 'error' => 'InvalidDataKey' },
|
218
|
+
{ 'error' => 'Unavailable' }
|
219
|
+
]}}
|
220
|
+
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]+\./ }
|
221
|
+
it_should_behave_like 'a notification with some delivery failures'
|
222
|
+
end
|
223
|
+
|
224
|
+
describe 'some deliveries failed with Unavailable or InternalServerError' do
|
225
|
+
let(:body) {{
|
226
|
+
'failure' => 2,
|
227
|
+
'success' => 1,
|
228
|
+
'results' => [
|
229
|
+
{ 'error' => 'Unavailable' },
|
230
|
+
{ 'message_id' => '1:000' },
|
231
|
+
{ 'error' => 'InternalServerError' }
|
232
|
+
]}}
|
233
|
+
let(:error_description) { /#{Regexp.escape("Failed to deliver to recipients 0, 2. Errors: Unavailable, InternalServerError. 0, 2 will be retried as notification")} [\d]+\./ }
|
234
|
+
it_should_behave_like 'a notification with some delivery failures'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe 'a 503 response' do
|
239
|
+
before { response.stub(:code => 503) }
|
240
|
+
|
241
|
+
it 'logs a warning that the notification will be retried.' do
|
242
|
+
notification.retries = 1
|
243
|
+
notification.deliver_after = now + 2
|
244
|
+
logger.should_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).")
|
245
|
+
perform
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'respects an integer Retry-After header' do
|
249
|
+
response.stub(:header => { 'retry-after' => 10 })
|
250
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 10.seconds)
|
251
|
+
perform
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'respects a HTTP-date Retry-After header' do
|
255
|
+
response.stub(:header => { 'retry-after' => 'Wed, 03 Oct 2012 20:55:11 GMT' })
|
256
|
+
delivery.should_receive(:mark_retryable).with(notification, Time.parse('Wed, 03 Oct 2012 20:55:11 GMT'))
|
257
|
+
perform
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'defaults to exponential back-off if the Retry-After header is not present' do
|
261
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 2 ** 1)
|
262
|
+
perform
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe 'a 500 response' do
|
267
|
+
before do
|
268
|
+
notification.update_attribute(:retries, 2)
|
269
|
+
response.stub(:code => 500)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'logs a warning that the notification has been re-queued.' do
|
273
|
+
notification.retries = 3
|
274
|
+
notification.deliver_after = now + 2 ** 3
|
275
|
+
Rpush.logger.should_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).")
|
276
|
+
perform
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'retries the notification in accordance with the exponential back-off strategy.' do
|
280
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 2 ** 3)
|
281
|
+
perform
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe 'a 401 response' do
|
286
|
+
before { response.stub(:code => 401) }
|
287
|
+
|
288
|
+
it 'raises an error' do
|
289
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe 'a 400 response' do
|
294
|
+
before { response.stub(:code => 400) }
|
295
|
+
|
296
|
+
it 'marks the notification as failed' do
|
297
|
+
delivery.should_receive(:mark_failed).with(400, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.')
|
298
|
+
perform rescue Rpush::DeliveryError
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe 'an un-handled response' do
|
303
|
+
before { response.stub(:code => 418) }
|
304
|
+
|
305
|
+
it 'marks the notification as failed' do
|
306
|
+
delivery.should_receive(:mark_failed).with(418, "I'm a Teapot")
|
307
|
+
perform rescue Rpush::DeliveryError
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "unit_spec_helper"
|
2
|
+
|
3
|
+
describe Rpush::Daemon::InterruptibleSleep do
|
4
|
+
|
5
|
+
let(:rd) { double(:close => nil) }
|
6
|
+
let(:wr) { double(:close => nil) }
|
7
|
+
|
8
|
+
subject { Rpush::Daemon::InterruptibleSleep.new }
|
9
|
+
|
10
|
+
it 'creates a new pipe' do
|
11
|
+
IO.should_receive(:pipe)
|
12
|
+
subject
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'selects on the reader' do
|
16
|
+
IO.stub(:pipe => [rd, wr])
|
17
|
+
IO.should_receive(:select).with([rd], nil, nil, 1)
|
18
|
+
subject.sleep(1)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'closes the writer' do
|
22
|
+
IO.stub(:pipe => [rd, wr])
|
23
|
+
rd.should_receive(:close)
|
24
|
+
wr.should_receive(:close)
|
25
|
+
subject.close
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns false when timeout occurs' do
|
29
|
+
expect(subject.sleep(0.01)).to be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns true when sleep does not timeout' do
|
33
|
+
subject.interrupt_sleep
|
34
|
+
expect(subject.sleep(0.01)).to be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with UDP socket connected' do
|
38
|
+
before :each do
|
39
|
+
@host, @port = subject.enable_wake_on_udp('127.0.0.1', 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'times out with no udp activity' do
|
43
|
+
expect(subject.sleep(0.01)).to be_false
|
44
|
+
end
|
45
|
+
|
46
|
+
unless defined? JRUBY_VERSION
|
47
|
+
it 'wakes on UDPSocket' do
|
48
|
+
waker = UDPSocket.new
|
49
|
+
waker.connect(@host, @port)
|
50
|
+
waker.write('x')
|
51
|
+
expect(subject.sleep(0.01)).to be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'consumes all data on udp socket' do
|
55
|
+
waker = UDPSocket.new
|
56
|
+
waker.connect(@host, @port)
|
57
|
+
waker.send('x', 0)
|
58
|
+
waker.send('x', 0)
|
59
|
+
waker.send('x', 0)
|
60
|
+
# true since there is data to be read => no timeout
|
61
|
+
expect(subject.sleep(0.01)).to be_true
|
62
|
+
# false since data is consumed => wait for full timeout
|
63
|
+
expect(subject.sleep(0.01)).to be_false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::Reflectable do
|
4
|
+
class TestReflectable
|
5
|
+
include Rpush::Daemon::Reflectable
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:logger) { double(:error => nil) }
|
9
|
+
let(:test_reflectable) { TestReflectable.new }
|
10
|
+
|
11
|
+
before do
|
12
|
+
Rpush.reflections.stub(:__dispatch)
|
13
|
+
Rpush.stub(:logger => logger)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'dispatches the given reflection' do
|
17
|
+
Rpush.reflections.should_receive(:__dispatch).with(:error)
|
18
|
+
test_reflectable.reflect(:error)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'logs errors raise by the reflection' do
|
22
|
+
error = StandardError.new
|
23
|
+
Rpush.reflections.stub(:__dispatch).and_raise(error)
|
24
|
+
Rpush.logger.should_receive(:error).with(error)
|
25
|
+
test_reflectable.reflect(:error)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "unit_spec_helper"
|
2
|
+
|
3
|
+
describe Rpush::RetryableError do
|
4
|
+
let(:response) { double(:code => 401, :header => { 'retry-after' => 3600 }) }
|
5
|
+
let(:error) { Rpush::RetryableError.new(401, 12, "Unauthorized", response) }
|
6
|
+
|
7
|
+
it "returns an informative message" do
|
8
|
+
error.to_s.should eq "Retryable error for 12, received error 401 (Unauthorized) - retry after 3600"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns the error code" do
|
12
|
+
error.code.should eq 401
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'unit_spec_helper'
|
2
|
+
|
3
|
+
describe Rpush::Daemon::ServiceConfigMethods do
|
4
|
+
module ServiceConfigMethodsSpec
|
5
|
+
extend Rpush::Daemon::ServiceConfigMethods
|
6
|
+
class Delivery; end
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'returns the delivery class' do
|
10
|
+
ServiceConfigMethodsSpec.delivery_class.should eq ServiceConfigMethodsSpec::Delivery
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'gets & sets loops' do
|
14
|
+
loop_class = double
|
15
|
+
ServiceConfigMethodsSpec.loops loop_class
|
16
|
+
ServiceConfigMethodsSpec.loops.should eq [loop_class]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns a new dispatcher' do
|
20
|
+
ServiceConfigMethodsSpec.dispatcher :http, an: :option
|
21
|
+
app = double
|
22
|
+
dispatcher = double
|
23
|
+
Rpush::Daemon::Dispatcher::Http.should_receive(:new).with(app, ServiceConfigMethodsSpec::Delivery, an: :option).and_return(dispatcher)
|
24
|
+
ServiceConfigMethodsSpec.new_dispatcher(app).should eq dispatcher
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'raises a NotImplementedError for an unknown dispatcher type' do
|
28
|
+
expect do
|
29
|
+
ServiceConfigMethodsSpec.dispatcher :unknown
|
30
|
+
ServiceConfigMethodsSpec.dispatcher_class
|
31
|
+
end.to raise_error(NotImplementedError)
|
32
|
+
end
|
33
|
+
end
|