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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +99 -0
  3. data/LICENSE +7 -0
  4. data/README.md +189 -0
  5. data/bin/rpush +36 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rpush_generator.rb +44 -0
  8. data/lib/generators/templates/add_adm.rb +23 -0
  9. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  10. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  11. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
  12. data/lib/generators/templates/add_gcm.rb +102 -0
  13. data/lib/generators/templates/add_rpush.rb +349 -0
  14. data/lib/generators/templates/add_wpns.rb +16 -0
  15. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  16. data/lib/generators/templates/create_rapns_feedback.rb +18 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +29 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
  19. data/lib/generators/templates/rpush.rb +104 -0
  20. data/lib/rpush/TODO +3 -0
  21. data/lib/rpush/adm/app.rb +15 -0
  22. data/lib/rpush/adm/data_validator.rb +11 -0
  23. data/lib/rpush/adm/notification.rb +29 -0
  24. data/lib/rpush/apns/app.rb +29 -0
  25. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  26. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  27. data/lib/rpush/apns/feedback.rb +16 -0
  28. data/lib/rpush/apns/notification.rb +84 -0
  29. data/lib/rpush/apns_feedback.rb +13 -0
  30. data/lib/rpush/app.rb +18 -0
  31. data/lib/rpush/configuration.rb +75 -0
  32. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  33. data/lib/rpush/daemon/adm.rb +9 -0
  34. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  35. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  36. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  37. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  38. data/lib/rpush/daemon/apns.rb +16 -0
  39. data/lib/rpush/daemon/app_runner.rb +187 -0
  40. data/lib/rpush/daemon/batch.rb +115 -0
  41. data/lib/rpush/daemon/constants.rb +59 -0
  42. data/lib/rpush/daemon/delivery.rb +28 -0
  43. data/lib/rpush/daemon/delivery_error.rb +19 -0
  44. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  45. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  46. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  47. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  48. data/lib/rpush/daemon/feeder.rb +68 -0
  49. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  50. data/lib/rpush/daemon/gcm.rb +9 -0
  51. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  52. data/lib/rpush/daemon/loggable.rb +31 -0
  53. data/lib/rpush/daemon/reflectable.rb +13 -0
  54. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  55. data/lib/rpush/daemon/retryable_error.rb +20 -0
  56. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  57. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  58. data/lib/rpush/daemon/store/active_record.rb +154 -0
  59. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  60. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  61. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  62. data/lib/rpush/daemon/wpns.rb +9 -0
  63. data/lib/rpush/daemon.rb +140 -0
  64. data/lib/rpush/deprecatable.rb +23 -0
  65. data/lib/rpush/deprecation.rb +23 -0
  66. data/lib/rpush/embed.rb +28 -0
  67. data/lib/rpush/gcm/app.rb +11 -0
  68. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  69. data/lib/rpush/gcm/notification.rb +30 -0
  70. data/lib/rpush/logger.rb +63 -0
  71. data/lib/rpush/multi_json_helper.rb +16 -0
  72. data/lib/rpush/notification.rb +69 -0
  73. data/lib/rpush/notifier.rb +52 -0
  74. data/lib/rpush/payload_data_size_validator.rb +10 -0
  75. data/lib/rpush/push.rb +16 -0
  76. data/lib/rpush/railtie.rb +11 -0
  77. data/lib/rpush/reflection.rb +58 -0
  78. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  79. data/lib/rpush/version.rb +3 -0
  80. data/lib/rpush/wpns/app.rb +9 -0
  81. data/lib/rpush/wpns/notification.rb +26 -0
  82. data/lib/rpush.rb +62 -0
  83. data/lib/tasks/cane.rake +18 -0
  84. data/lib/tasks/rpush.rake +16 -0
  85. data/lib/tasks/test.rake +38 -0
  86. data/spec/functional/adm_spec.rb +43 -0
  87. data/spec/functional/apns_spec.rb +58 -0
  88. data/spec/functional/embed_spec.rb +49 -0
  89. data/spec/functional/gcm_spec.rb +42 -0
  90. data/spec/functional/wpns_spec.rb +41 -0
  91. data/spec/support/cert_with_password.pem +90 -0
  92. data/spec/support/cert_without_password.pem +59 -0
  93. data/spec/support/install.sh +32 -0
  94. data/spec/support/simplecov_helper.rb +20 -0
  95. data/spec/support/simplecov_quality_formatter.rb +8 -0
  96. data/spec/tmp/.gitkeep +0 -0
  97. data/spec/unit/adm/app_spec.rb +58 -0
  98. data/spec/unit/adm/notification_spec.rb +45 -0
  99. data/spec/unit/apns/app_spec.rb +29 -0
  100. data/spec/unit/apns/feedback_spec.rb +9 -0
  101. data/spec/unit/apns/notification_spec.rb +208 -0
  102. data/spec/unit/apns_feedback_spec.rb +21 -0
  103. data/spec/unit/app_spec.rb +30 -0
  104. data/spec/unit/configuration_spec.rb +45 -0
  105. data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
  106. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  107. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  108. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  109. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
  110. data/spec/unit/daemon/app_runner_spec.rb +292 -0
  111. data/spec/unit/daemon/batch_spec.rb +232 -0
  112. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  113. data/spec/unit/daemon/delivery_spec.rb +38 -0
  114. data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
  115. data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
  116. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
  117. data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
  118. data/spec/unit/daemon/feeder_spec.rb +98 -0
  119. data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
  120. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  121. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  122. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  123. data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
  124. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  125. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  126. data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
  127. data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
  128. data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
  129. data/spec/unit/daemon_spec.rb +159 -0
  130. data/spec/unit/deprecatable_spec.rb +32 -0
  131. data/spec/unit/deprecation_spec.rb +15 -0
  132. data/spec/unit/embed_spec.rb +50 -0
  133. data/spec/unit/gcm/app_spec.rb +4 -0
  134. data/spec/unit/gcm/notification_spec.rb +36 -0
  135. data/spec/unit/logger_spec.rb +127 -0
  136. data/spec/unit/notification_shared.rb +105 -0
  137. data/spec/unit/notification_spec.rb +15 -0
  138. data/spec/unit/notifier_spec.rb +49 -0
  139. data/spec/unit/push_spec.rb +43 -0
  140. data/spec/unit/reflection_spec.rb +30 -0
  141. data/spec/unit/rpush_spec.rb +9 -0
  142. data/spec/unit/wpns/app_spec.rb +4 -0
  143. data/spec/unit/wpns/notification_spec.rb +30 -0
  144. data/spec/unit_spec_helper.rb +101 -0
  145. 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