rpush 1.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 +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.rb +62 -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.rb +140 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/apns.rb +16 -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/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.rb +9 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -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.rb +154 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -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.rb +9 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -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/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 +276 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush, 'apns_feedback' do
|
|
4
|
+
let!(:app) { Rpush::Apns::App.create!(:name => 'test', :environment => 'production', :certificate => TEST_CERT) }
|
|
5
|
+
let(:receiver) { double(:check_for_feedback => nil) }
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
Rpush::Daemon::Apns::FeedbackReceiver.stub(:new => receiver)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'initializes the store' do
|
|
12
|
+
Rpush::Daemon.should_receive(:initialize_store)
|
|
13
|
+
Rpush.apns_feedback
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'checks feedback for each app' do
|
|
17
|
+
Rpush::Daemon::Apns::FeedbackReceiver.should_receive(:new).with(app).and_return(receiver)
|
|
18
|
+
receiver.should_receive(:check_for_feedback)
|
|
19
|
+
Rpush.apns_feedback
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush::App do
|
|
4
|
+
it 'validates the uniqueness of name within type and environment' do
|
|
5
|
+
Rpush::Apns::App.create!(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
|
|
6
|
+
app = Rpush::Apns::App.new(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
|
|
7
|
+
app.valid?.should be_false
|
|
8
|
+
app.errors[:name].should eq ['has already been taken']
|
|
9
|
+
|
|
10
|
+
app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => TEST_CERT)
|
|
11
|
+
app.valid?.should be_true
|
|
12
|
+
|
|
13
|
+
app = Rpush::Gcm::App.new(:name => 'test', :environment => 'production', :auth_key => TEST_CERT)
|
|
14
|
+
app.valid?.should be_true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context 'validating certificates' do
|
|
18
|
+
it 'rescues from certificate error' do
|
|
19
|
+
app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => 'bad')
|
|
20
|
+
expect{app.valid?}.not_to raise_error
|
|
21
|
+
expect(app.valid?).to be_false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'raises other errors' do
|
|
25
|
+
app = Rpush::Apns::App.new(:name => 'test', :environment => 'development', :certificate => 'bad')
|
|
26
|
+
OpenSSL::X509::Certificate.stub(:new).and_raise(NameError, 'simulating no openssl')
|
|
27
|
+
expect{app.valid?}.to raise_error(NameError)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush do
|
|
4
|
+
let(:config) { double }
|
|
5
|
+
|
|
6
|
+
before { Rpush.stub(:config => config) }
|
|
7
|
+
|
|
8
|
+
it 'can yields a config block' do
|
|
9
|
+
expect { |b| Rpush.configure(&b) }.to yield_with_args(config)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe Rpush::Configuration do
|
|
14
|
+
let(:config) do
|
|
15
|
+
Rpush::Deprecation.muted do
|
|
16
|
+
Rpush::Configuration.new
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'can be updated' do
|
|
21
|
+
Rpush::Deprecation.muted do
|
|
22
|
+
new_config = Rpush::Configuration.new
|
|
23
|
+
new_config.batch_size = 200
|
|
24
|
+
expect { config.update(new_config) }.to change(config, :batch_size).to(200)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'sets the pid_file relative if not absolute' do
|
|
29
|
+
Rails.stub(:root => '/rails')
|
|
30
|
+
config.pid_file = 'tmp/rpush.pid'
|
|
31
|
+
config.pid_file.should eq '/rails/tmp/rpush.pid'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not alter an absolute pid_file path' do
|
|
35
|
+
config.pid_file = '/tmp/rpush.pid'
|
|
36
|
+
config.pid_file.should eq '/tmp/rpush.pid'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'does not allow foreground to be set to false if the platform is JRuby' do
|
|
40
|
+
config.foreground = true
|
|
41
|
+
Rpush.stub(:jruby? => true)
|
|
42
|
+
config.foreground = false
|
|
43
|
+
config.foreground.should be_true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush::Daemon::Adm::Delivery do
|
|
4
|
+
let(:app) { Rpush::Adm::App.new(:name => 'MyApp', :client_id => 'CLIENT_ID', :client_secret => 'CLIENT_SECRET') }
|
|
5
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['xyz'], :deliver_after => Time.now, :data => {'message' => 'test'}) }
|
|
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::Adm::Delivery.new(app, http, notification, batch) }
|
|
12
|
+
let(:store) { double(:create_adm_notification => double(:id => 2)) }
|
|
13
|
+
|
|
14
|
+
def perform
|
|
15
|
+
delivery.perform
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before do
|
|
19
|
+
app.access_token = 'ACCESS_TOKEN'
|
|
20
|
+
app.access_token_expiration = Time.now + 1.month
|
|
21
|
+
|
|
22
|
+
delivery.stub(:reflect => nil)
|
|
23
|
+
Rpush::Daemon.stub(:store => store)
|
|
24
|
+
Time.stub(:now => now)
|
|
25
|
+
Rpush.stub(:logger => logger)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe 'unknown error response' do
|
|
29
|
+
before do
|
|
30
|
+
response.stub(:code => 408)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
|
34
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidData' }))
|
|
35
|
+
delivery.should_receive(:mark_failed).with(408, 'Request Timeout')
|
|
36
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe 'a 200 (Ok) response' do
|
|
41
|
+
before do
|
|
42
|
+
response.stub(:code => 200)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'marks the notification as delivered if delivered successfully to all devices' do
|
|
46
|
+
response.stub(:body => JSON.dump({ 'registrationID' => 'xyz' }))
|
|
47
|
+
delivery.should_receive(:mark_delivered)
|
|
48
|
+
perform
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'logs that the notification was delivered' do
|
|
52
|
+
response.stub(:body => JSON.dump({ 'registrationID' => 'xyz' }))
|
|
53
|
+
logger.should_receive(:info).with("[MyApp] #{notification.id} sent to xyz")
|
|
54
|
+
perform
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'reflects on canonical IDs' do
|
|
58
|
+
response.stub(:body => JSON.dump({'registrationID' => 'canonical123' }))
|
|
59
|
+
notification.stub(:registration_ids => ['1'])
|
|
60
|
+
delivery.should_receive(:reflect).with(:adm_canonical_id, '1', 'canonical123')
|
|
61
|
+
perform
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'a 400 (Bad Request) response' do
|
|
66
|
+
before do
|
|
67
|
+
response.stub(:code => 400)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
|
71
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidData' }))
|
|
72
|
+
delivery.should_receive(:mark_failed).with(nil, 'Failed to deliver to all recipients.')
|
|
73
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'logs that the notification was not delivered' do
|
|
77
|
+
response.stub(:body => JSON.dump({ 'reason' => 'InvalidRegistrationId' }))
|
|
78
|
+
logger.should_receive(:warn).with("[MyApp] bad_request: xyz (InvalidRegistrationId)")
|
|
79
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe 'a 401 (Unauthorized) response' do
|
|
84
|
+
let(:http) { double(:shutdown => nil)}
|
|
85
|
+
let(:token_response) { double(:code => 200, :header => {}, :body => JSON.dump({'access_token' => 'ACCESS_TOKEN', 'expires_in' => 60})) }
|
|
86
|
+
|
|
87
|
+
before do
|
|
88
|
+
response.stub(:code => 401, :header => { 'retry-after' => 10 })
|
|
89
|
+
|
|
90
|
+
# first request to deliver message that returns unauthorized response
|
|
91
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
|
92
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'should retrieve a new access token and mark the notification for retry' do
|
|
96
|
+
# request for access token
|
|
97
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
|
98
|
+
|
|
99
|
+
store.should_receive(:update_app).with(notification.app)
|
|
100
|
+
delivery.should_receive(:mark_retryable).with(notification, now)
|
|
101
|
+
|
|
102
|
+
perform
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'should update the app with the new access token' do
|
|
106
|
+
# request for access token
|
|
107
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
|
108
|
+
|
|
109
|
+
store.should_receive(:update_app).with do |app|
|
|
110
|
+
app.access_token.should eq 'ACCESS_TOKEN'
|
|
111
|
+
app.access_token_expiration.should eq now + 60.seconds
|
|
112
|
+
end
|
|
113
|
+
delivery.should_receive(:mark_retryable).with(notification, now)
|
|
114
|
+
|
|
115
|
+
perform
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'should log the error and stop retrying if new access token can\'t be retrieved' do
|
|
119
|
+
token_response.stub(:code => 404, :body => "test")
|
|
120
|
+
# request for access token
|
|
121
|
+
http.should_receive(:request).with(Rpush::Daemon::Adm::Delivery::AMAZON_TOKEN_URI, instance_of(Net::HTTP::Post)).and_return(token_response)
|
|
122
|
+
|
|
123
|
+
store.should_not_receive(:update_app).with(notification.app)
|
|
124
|
+
delivery.should_not_receive(:mark_retryable)
|
|
125
|
+
|
|
126
|
+
logger.should_receive(:warn).with("[MyApp] Could not retrieve access token from ADM: test")
|
|
127
|
+
|
|
128
|
+
perform
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe 'a 429 (Too Many Request) response' do
|
|
133
|
+
let(:http) { double(:shutdown => nil) }
|
|
134
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['abc','xyz'], :deliver_after => Time.now, :collapse_key => 'sync', :data => {'message' => 'test'}) }
|
|
135
|
+
let(:too_many_request_response) { double(:code => 429, :header => { 'retry-after' => 3600 }) }
|
|
136
|
+
|
|
137
|
+
it 'should retry the entire notification respecting the Retry-After header if none sent out yet' do
|
|
138
|
+
response.stub(:code => 429, :header => { 'retry-after' => 3600 })
|
|
139
|
+
|
|
140
|
+
# first request to deliver message that returns too many request response
|
|
141
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
|
142
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
|
143
|
+
|
|
144
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 1.hour)
|
|
145
|
+
perform
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'should retry the entire notification using exponential backoff' do
|
|
149
|
+
response.stub(:code => 429, :header => {})
|
|
150
|
+
|
|
151
|
+
# first request to deliver message that returns too many request response
|
|
152
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % [notification.registration_ids.first])
|
|
153
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
|
154
|
+
|
|
155
|
+
delivery.should_receive(:mark_retryable).with(notification, Time.now + 2 ** (notification.retries + 1))
|
|
156
|
+
perform
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'should keep sent reg ids in original notification and create new notification with remaining reg ids for retry' do
|
|
160
|
+
response.stub(:code => 200, :body => JSON.dump({ 'registrationID' => 'abc' }))
|
|
161
|
+
|
|
162
|
+
# first request to deliver message succeeds
|
|
163
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['abc'])
|
|
164
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
|
165
|
+
|
|
166
|
+
# first request to deliver message that returns too many request response
|
|
167
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['xyz'])
|
|
168
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(too_many_request_response)
|
|
169
|
+
|
|
170
|
+
store.should_receive(:update_notification).with do |notif|
|
|
171
|
+
notif.registration_ids.include?('abc').should be_true
|
|
172
|
+
notif.registration_ids.include?('xyz').should be_false
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
store.should_receive(:create_adm_notification).with do |attrs, notification_data, reg_ids, deliver_after, notification_app|
|
|
176
|
+
attrs.has_key?('collapse_key').should be_true
|
|
177
|
+
attrs.has_key?('delay_while_idle').should be_true
|
|
178
|
+
attrs.has_key?('app_id').should be_true
|
|
179
|
+
|
|
180
|
+
reg_ids.should eq ['xyz']
|
|
181
|
+
deliver_after.should eq now + 1.hour
|
|
182
|
+
notification_app.should eq notification.app
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
delivery.should_receive(:mark_delivered)
|
|
186
|
+
|
|
187
|
+
perform
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
describe 'a 500 (Internal Server Error) response' do
|
|
192
|
+
before do
|
|
193
|
+
response.stub(:code => 500)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'marks the notification as failed because no successful delivery was made' do
|
|
197
|
+
delivery.should_receive(:mark_failed).with(nil, 'Failed to deliver to all recipients.')
|
|
198
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'logs that the notification was not delivered' do
|
|
202
|
+
logger.should_receive(:warn).with("[MyApp] internal_server_error: xyz (Internal Server Error)")
|
|
203
|
+
expect { perform }.to raise_error(Rpush::DeliveryError)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
describe 'a 503 (Service Unavailable) response' do
|
|
208
|
+
before do
|
|
209
|
+
response.stub(:code => 503, :header => { 'retry-after' => 10 })
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'should retry the notification respecting the Retry-After header' do
|
|
213
|
+
delivery.should_receive(:mark_retryable).with(notification, now + 10.seconds)
|
|
214
|
+
perform
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
describe 'some registration ids succeeding and some failing' do
|
|
219
|
+
let(:http) { double(:shutdown => nil) }
|
|
220
|
+
let(:notification) { Rpush::Adm::Notification.create!(:app => app, :registration_ids => ['abc','xyz'], :deliver_after => Time.now, :collapse_key => 'sync', :data => {'message' => 'test'}) }
|
|
221
|
+
let(:bad_request_response) { double(:code => 400, :body => JSON.dump({ 'reason' => 'InvalidData' })) }
|
|
222
|
+
|
|
223
|
+
it 'should keep sent reg ids in original notification and create new notification with remaining reg ids for retry' do
|
|
224
|
+
response.stub(:code => 200, :body => JSON.dump({ 'registrationID' => 'abc' }))
|
|
225
|
+
|
|
226
|
+
# first request to deliver message succeeds
|
|
227
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['abc'])
|
|
228
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(response)
|
|
229
|
+
|
|
230
|
+
# first request to deliver message that returns too many request response
|
|
231
|
+
adm_uri = URI.parse(Rpush::Daemon::Adm::Delivery::AMAZON_ADM_URL % ['xyz'])
|
|
232
|
+
http.should_receive(:request).with(adm_uri, instance_of(Net::HTTP::Post)).and_return(bad_request_response)
|
|
233
|
+
|
|
234
|
+
store.should_receive(:update_notification).with do |notif|
|
|
235
|
+
notif.error_description.should eq "Failed to deliver to recipients: \nxyz: InvalidData"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
delivery.should_receive(:mark_delivered)
|
|
239
|
+
|
|
240
|
+
perform
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush::Apns::CertificateExpiredError do
|
|
4
|
+
let(:app) { double(:name => 'test') }
|
|
5
|
+
let(:error) { Rpush::Apns::CertificateExpiredError.new(app, Time.now) }
|
|
6
|
+
|
|
7
|
+
it 'returns a message' do
|
|
8
|
+
error.message
|
|
9
|
+
error.to_s
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush::Daemon::Apns::Delivery do
|
|
4
|
+
let(:app) { double(:name => 'MyApp') }
|
|
5
|
+
let(:notification) { double.as_null_object }
|
|
6
|
+
let(:batch) { double(:mark_failed => nil, :mark_delivered => nil) }
|
|
7
|
+
let(:logger) { double(:error => nil, :info => nil) }
|
|
8
|
+
let(:config) { double(:check_for_errors => true) }
|
|
9
|
+
let(:connection) { double(:select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
|
|
10
|
+
let(:delivery) { Rpush::Daemon::Apns::Delivery.new(app, connection, notification, batch) }
|
|
11
|
+
|
|
12
|
+
def perform
|
|
13
|
+
begin
|
|
14
|
+
delivery.perform
|
|
15
|
+
rescue Rpush::DeliveryError, Rpush::Apns::DisconnectionError
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
before do
|
|
20
|
+
Rpush.stub(:config => config, :logger => logger)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "sends the binary version of the notification" do
|
|
24
|
+
notification.stub(:to_binary => "hi mom")
|
|
25
|
+
connection.should_receive(:write).with("hi mom")
|
|
26
|
+
perform
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "logs the notification delivery" do
|
|
30
|
+
notification.stub(:id => 666, :device_token => 'abc123')
|
|
31
|
+
logger.should_receive(:info).with("[MyApp] 666 sent to abc123")
|
|
32
|
+
perform
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "marks the notification as delivered" do
|
|
36
|
+
delivery.should_receive(:mark_delivered)
|
|
37
|
+
perform
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'does not check for errors if check_for_errors config option is false' do
|
|
41
|
+
config.stub(:check_for_errors => false)
|
|
42
|
+
delivery.should_not_receive(:check_for_error)
|
|
43
|
+
perform
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "when delivery fails" do
|
|
47
|
+
before { connection.stub(:select => true, :read => [8, 4, 69].pack("ccN")) }
|
|
48
|
+
|
|
49
|
+
it "marks the notification as failed" do
|
|
50
|
+
delivery.should_receive(:mark_failed).with(4, "Missing payload")
|
|
51
|
+
perform
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "logs the delivery error" do
|
|
55
|
+
# checking for the doublebed error doesn't work in jruby, but checking
|
|
56
|
+
# for the exception by class does.
|
|
57
|
+
|
|
58
|
+
#error = Rpush::DeliveryError.new(4, 12, "Missing payload")
|
|
59
|
+
#Rpush::DeliveryError.stub(:new => error)
|
|
60
|
+
#expect { delivery.perform }.to raise_error(error)
|
|
61
|
+
|
|
62
|
+
expect { delivery.perform }.to raise_error(Rpush::DeliveryError)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "reads 6 bytes from the socket" do
|
|
66
|
+
connection.should_receive(:read).with(6).and_return(nil)
|
|
67
|
+
perform
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do
|
|
71
|
+
connection.stub(:select => nil)
|
|
72
|
+
connection.should_not_receive(:read)
|
|
73
|
+
perform
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "reconnects the socket" do
|
|
77
|
+
connection.should_receive(:reconnect)
|
|
78
|
+
perform
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "logs that the connection is being reconnected" do
|
|
82
|
+
Rpush.logger.should_receive(:error).with("[MyApp] Error received, reconnecting...")
|
|
83
|
+
perform
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context "when the APNs disconnects without returning an error" do
|
|
87
|
+
before do
|
|
88
|
+
connection.stub(:read => nil)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'raises a DisconnectError error if the connection is closed without an error being returned' do
|
|
92
|
+
expect { delivery.perform }.to raise_error(Rpush::Apns::DisconnectionError)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'marks the notification as failed' do
|
|
96
|
+
delivery.should_receive(:mark_failed).with(nil, "APNs disconnected without returning an error.")
|
|
97
|
+
perform
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|