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.
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.rb +62 -0
  21. data/lib/rpush/TODO +3 -0
  22. data/lib/rpush/adm/app.rb +15 -0
  23. data/lib/rpush/adm/data_validator.rb +11 -0
  24. data/lib/rpush/adm/notification.rb +29 -0
  25. data/lib/rpush/apns/app.rb +29 -0
  26. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  27. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  28. data/lib/rpush/apns/feedback.rb +16 -0
  29. data/lib/rpush/apns/notification.rb +84 -0
  30. data/lib/rpush/apns_feedback.rb +13 -0
  31. data/lib/rpush/app.rb +18 -0
  32. data/lib/rpush/configuration.rb +75 -0
  33. data/lib/rpush/daemon.rb +140 -0
  34. data/lib/rpush/daemon/adm.rb +9 -0
  35. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  36. data/lib/rpush/daemon/apns.rb +16 -0
  37. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  38. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  39. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  40. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  41. data/lib/rpush/daemon/app_runner.rb +187 -0
  42. data/lib/rpush/daemon/batch.rb +115 -0
  43. data/lib/rpush/daemon/constants.rb +59 -0
  44. data/lib/rpush/daemon/delivery.rb +28 -0
  45. data/lib/rpush/daemon/delivery_error.rb +19 -0
  46. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  47. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  48. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  49. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  50. data/lib/rpush/daemon/feeder.rb +68 -0
  51. data/lib/rpush/daemon/gcm.rb +9 -0
  52. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  53. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  54. data/lib/rpush/daemon/loggable.rb +31 -0
  55. data/lib/rpush/daemon/reflectable.rb +13 -0
  56. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  57. data/lib/rpush/daemon/retryable_error.rb +20 -0
  58. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  59. data/lib/rpush/daemon/store/active_record.rb +154 -0
  60. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  61. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  62. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  63. data/lib/rpush/daemon/wpns.rb +9 -0
  64. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  65. data/lib/rpush/deprecatable.rb +23 -0
  66. data/lib/rpush/deprecation.rb +23 -0
  67. data/lib/rpush/embed.rb +28 -0
  68. data/lib/rpush/gcm/app.rb +11 -0
  69. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  70. data/lib/rpush/gcm/notification.rb +30 -0
  71. data/lib/rpush/logger.rb +63 -0
  72. data/lib/rpush/multi_json_helper.rb +16 -0
  73. data/lib/rpush/notification.rb +69 -0
  74. data/lib/rpush/notifier.rb +52 -0
  75. data/lib/rpush/payload_data_size_validator.rb +10 -0
  76. data/lib/rpush/push.rb +16 -0
  77. data/lib/rpush/railtie.rb +11 -0
  78. data/lib/rpush/reflection.rb +58 -0
  79. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  80. data/lib/rpush/version.rb +3 -0
  81. data/lib/rpush/wpns/app.rb +9 -0
  82. data/lib/rpush/wpns/notification.rb +26 -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 +276 -0
@@ -0,0 +1,18 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Apns::DisconnectionError do
4
+ let(:error) { Rpush::Apns::DisconnectionError.new }
5
+
6
+ it 'returns a nil error code' do
7
+ error.code.should be_nil
8
+ end
9
+
10
+ it 'contains an error description' do
11
+ error.description
12
+ end
13
+
14
+ it 'returns a message' do
15
+ error.message
16
+ error.to_s
17
+ end
18
+ end
@@ -0,0 +1,117 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Daemon::Apns::FeedbackReceiver, 'check_for_feedback' do
4
+
5
+ let(:host) { 'feedback.push.apple.com' }
6
+ let(:port) { 2196 }
7
+ let(:poll) { 60 }
8
+ let(:certificate) { double }
9
+ let(:password) { double }
10
+ let(:app) { double(:name => 'my_app', :password => password, :certificate => certificate, :environment => 'production') }
11
+ let(:connection) { double(:connect => nil, :read => nil, :close => nil) }
12
+ let(:logger) { double(:error => nil, :info => nil) }
13
+ let(:receiver) { Rpush::Daemon::Apns::FeedbackReceiver.new(app) }
14
+ let(:feedback) { double }
15
+ let(:sleeper) { double(Rpush::Daemon::InterruptibleSleep, :sleep => nil, :interrupt_sleep => nil) }
16
+ let(:store) { double(Rpush::Daemon::Store::ActiveRecord,
17
+ create_apns_feedback: feedback, release_connection: nil) }
18
+
19
+ before do
20
+ Rpush.config.feedback_poll = poll
21
+ Rpush::Daemon::InterruptibleSleep.stub(:new => sleeper)
22
+ Rpush.stub(:logger => logger)
23
+ Rpush::Daemon::TcpConnection.stub(:new => connection)
24
+ receiver.instance_variable_set("@stop", false)
25
+ Rpush::Daemon.stub(:store => store)
26
+ end
27
+
28
+ def double_connection_read_with_tuple
29
+ connection.unstub(:read)
30
+
31
+ def connection.read(bytes)
32
+ if !@called
33
+ @called = true
34
+ "N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17"
35
+ end
36
+ end
37
+ end
38
+
39
+ it 'instantiates a new connection' do
40
+ Rpush::Daemon::TcpConnection.should_receive(:new).with(app, host, port)
41
+ receiver.check_for_feedback
42
+ end
43
+
44
+ it 'connects to the feeback service' do
45
+ connection.should_receive(:connect)
46
+ receiver.check_for_feedback
47
+ end
48
+
49
+ it 'closes the connection' do
50
+ connection.should_receive(:close)
51
+ receiver.check_for_feedback
52
+ end
53
+
54
+ it 'reads from the connection' do
55
+ connection.should_receive(:read).with(38)
56
+ receiver.check_for_feedback
57
+ end
58
+
59
+ it 'logs the feedback' do
60
+ double_connection_read_with_tuple
61
+ Rpush.logger.should_receive(:info).with("[my_app] [FeedbackReceiver] Delivery failed at 2011-12-10 16:08:45 UTC for 834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17.")
62
+ receiver.check_for_feedback
63
+ end
64
+
65
+ it 'creates the feedback' do
66
+ Rpush::Daemon.store.should_receive(:create_apns_feedback).with(Time.at(1323533325), '834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17', app)
67
+ double_connection_read_with_tuple
68
+ receiver.check_for_feedback
69
+ end
70
+
71
+ it 'logs errors' do
72
+ error = StandardError.new('bork!')
73
+ connection.stub(:read).and_raise(error)
74
+ Rpush.logger.should_receive(:error).with(error)
75
+ receiver.check_for_feedback
76
+ end
77
+
78
+ describe 'start' do
79
+ before do
80
+ Thread.stub(:new).and_yield
81
+ receiver.stub(:loop).and_yield
82
+ end
83
+
84
+ it 'sleeps for the feedback poll period' do
85
+ receiver.stub(:check_for_feedback)
86
+ sleeper.should_receive(:sleep).with(60).at_least(:once)
87
+ receiver.start
88
+ end
89
+
90
+ it 'checks for feedback when started' do
91
+ receiver.should_receive(:check_for_feedback).at_least(:once)
92
+ receiver.start
93
+ end
94
+ end
95
+
96
+ describe 'stop' do
97
+ it 'interrupts sleep when stopped' do
98
+ receiver.stub(:check_for_feedback)
99
+ sleeper.should_receive(:interrupt_sleep)
100
+ receiver.stop
101
+ end
102
+
103
+ it 'releases the store connection' do
104
+ Thread.stub(:new).and_yield
105
+ receiver.stub(:loop).and_yield
106
+ Rpush::Daemon.store.should_receive(:release_connection)
107
+ receiver.start
108
+ receiver.stop
109
+ end
110
+ end
111
+
112
+ it 'reflects feedback was received' do
113
+ double_connection_read_with_tuple
114
+ receiver.should_receive(:reflect).with(:apns_feedback, feedback)
115
+ receiver.check_for_feedback
116
+ end
117
+ end
@@ -0,0 +1,292 @@
1
+ require 'unit_spec_helper'
2
+
3
+ module Rpush
4
+ module AppRunnerSpecService
5
+ class App < Rpush::App
6
+ end
7
+ end
8
+
9
+ module Daemon
10
+ module AppRunnerSpecService
11
+ extend ServiceConfigMethods
12
+
13
+ class ServiceLoop
14
+ def initialize(app)
15
+ end
16
+
17
+ def start
18
+ end
19
+
20
+ def stop
21
+ end
22
+ end
23
+
24
+ dispatcher :http
25
+ loops ServiceLoop
26
+
27
+ class Delivery
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ describe Rpush::Daemon::AppRunner, 'stop' do
34
+ let(:runner) { double }
35
+ before { Rpush::Daemon::AppRunner.runners['app'] = runner }
36
+ after { Rpush::Daemon::AppRunner.runners.clear }
37
+
38
+ it 'stops all runners' do
39
+ runner.should_receive(:stop)
40
+ Rpush::Daemon::AppRunner.stop
41
+ end
42
+ end
43
+
44
+ describe Rpush::Daemon::AppRunner, 'enqueue' do
45
+ let(:runner) { double(:enqueue => nil) }
46
+ let(:notification1) { double(:app_id => 1) }
47
+ let(:notification2) { double(:app_id => 2) }
48
+ let(:logger) { double(Rpush::Logger, :error => nil) }
49
+
50
+ before do
51
+ Rpush.stub(:logger => logger)
52
+ Rpush::Daemon::AppRunner.runners[1] = runner
53
+ end
54
+
55
+ after { Rpush::Daemon::AppRunner.runners.clear }
56
+
57
+ it 'batches notifications by app' do
58
+ batch = double.as_null_object
59
+ Rpush::Daemon::Batch.stub(:new => batch)
60
+ Rpush::Daemon::Batch.should_receive(:new).with([notification1])
61
+ Rpush::Daemon::Batch.should_receive(:new).with([notification2])
62
+ Rpush::Daemon::AppRunner.enqueue([notification1, notification2])
63
+ end
64
+
65
+ it 'enqueues each batch' do
66
+ runner.should_receive(:enqueue).with(kind_of(Rpush::Daemon::Batch))
67
+ Rpush::Daemon::AppRunner.enqueue([notification1])
68
+ end
69
+
70
+ it 'logs an error if there is no runner to deliver the notification' do
71
+ notification1.stub(:app_id => 2, :id => 123)
72
+ notification2.stub(:app_id => 2, :id => 456)
73
+ logger.should_receive(:error).with("No such app '#{notification1.app_id}' for notifications 123, 456.")
74
+ Rpush::Daemon::AppRunner.enqueue([notification1, notification2])
75
+ end
76
+ end
77
+
78
+ describe Rpush::Daemon::AppRunner, 'sync' do
79
+ let(:app) { double(Rpush::AppRunnerSpecService::App, :name => 'test') }
80
+ let(:new_app) { double(Rpush::AppRunnerSpecService::App, :name => 'new_test') }
81
+ let(:runner) { double(:sync => nil, :stop => nil, :start => nil) }
82
+ let(:logger) { double(Rpush::Logger, :error => nil, :warn => nil) }
83
+ let(:queue) { Queue.new }
84
+
85
+ before do
86
+ app.stub(:id => 1)
87
+ new_app.stub(:id => 2)
88
+ Queue.stub(:new => queue)
89
+ Rpush::Daemon::AppRunner.runners[app.id] = runner
90
+ Rpush::App.stub(:all => [app])
91
+ Rpush.stub(:logger => logger)
92
+ end
93
+
94
+ after { Rpush::Daemon::AppRunner.runners.clear }
95
+
96
+ it 'loads all apps' do
97
+ Rpush::App.should_receive(:all)
98
+ Rpush::Daemon::AppRunner.sync
99
+ end
100
+
101
+ it 'instructs existing runners to sync' do
102
+ runner.should_receive(:sync).with(app)
103
+ Rpush::Daemon::AppRunner.sync
104
+ end
105
+
106
+ it 'starts a runner for a new app' do
107
+ Rpush::App.stub(:all => [app, new_app])
108
+ new_runner = double
109
+ Rpush::Daemon::AppRunner.should_receive(:new).with(new_app).and_return(new_runner)
110
+ new_runner.should_receive(:start)
111
+ Rpush::Daemon::AppRunner.sync
112
+ end
113
+
114
+ it 'deletes old runners' do
115
+ Rpush::App.stub(:all => [])
116
+ runner.should_receive(:stop)
117
+ Rpush::Daemon::AppRunner.sync
118
+ end
119
+
120
+ it 'logs an error if the runner could not be started' do
121
+ Rpush::App.stub(:all => [app, new_app])
122
+ new_runner = double
123
+ Rpush::Daemon::AppRunner.should_receive(:new).with(new_app).and_return(new_runner)
124
+ new_runner.stub(:start).and_raise(StandardError)
125
+ Rpush.logger.should_receive(:error)
126
+ Rpush::Daemon::AppRunner.sync
127
+ end
128
+
129
+ it 'reflects errors if the runner could not be started' do
130
+ Rpush::App.stub(:all => [app, new_app])
131
+ new_runner = double
132
+ Rpush::Daemon::AppRunner.should_receive(:new).with(new_app).and_return(new_runner)
133
+ e = StandardError.new
134
+ new_runner.stub(:start).and_raise(e)
135
+ Rpush::Daemon::AppRunner.should_receive(:reflect).with(:error, e)
136
+ Rpush::Daemon::AppRunner.sync
137
+ end
138
+ end
139
+
140
+ describe Rpush::Daemon::AppRunner, 'debug' do
141
+ let(:app) { double(Rpush::AppRunnerSpecService::App, :id => 1, :name => 'test', :connections => 1,
142
+ :environment => 'development', :certificate => TEST_CERT, :service_name => 'app_runner_spec_service') }
143
+ let(:logger) { double(Rpush::Logger, :info => nil) }
144
+
145
+ before do
146
+ Rpush::App.stub(:all => [app])
147
+ Rpush::Daemon.stub(:config => {})
148
+ Rpush.stub(:logger => logger)
149
+ Rpush::Daemon::AppRunner.sync
150
+ end
151
+
152
+ after { Rpush::Daemon::AppRunner.runners.clear }
153
+
154
+ it 'prints debug app states to the log' do
155
+ Rpush.logger.should_receive(:info).with("\ntest:\n dispatchers: 1\n queued: 0\n batch size: 0\n batch processed: 0\n idle: true\n")
156
+ Rpush::Daemon::AppRunner.debug
157
+ end
158
+ end
159
+
160
+ describe Rpush::Daemon::AppRunner, 'idle' do
161
+ let(:app) { double(Rpush::AppRunnerSpecService::App, :name => 'test', :connections => 1,
162
+ :environment => 'development', :certificate => TEST_CERT, :id => 1,
163
+ :service_name => 'app_runner_spec_service') }
164
+ let(:logger) { double(Rpush::Logger, :info => nil) }
165
+
166
+ before do
167
+ Rpush::App.stub(:all => [app])
168
+ Rpush.stub(:logger => logger)
169
+ Rpush::Daemon::AppRunner.sync
170
+ end
171
+
172
+ after { Rpush::Daemon::AppRunner.runners.clear }
173
+
174
+ it 'returns idle runners' do
175
+ runner = Rpush::Daemon::AppRunner.runners[app.id]
176
+ Rpush::Daemon::AppRunner.idle.should eq [runner]
177
+ end
178
+ end
179
+
180
+ describe Rpush::Daemon::AppRunner, 'wait' do
181
+ let(:app) { double(Rpush::AppRunnerSpecService::App, :id => 1, :name => 'test',
182
+ :connections => 1, :environment => 'development', :certificate => TEST_CERT,
183
+ :service_name => 'app_runner_spec_service') }
184
+ let(:logger) { double(Rpush::Logger, :info => nil) }
185
+
186
+ before do
187
+ Rpush::App.stub(:all => [app])
188
+ Rpush.stub(:logger => logger)
189
+ Rpush::Daemon::AppRunner.sync
190
+ end
191
+
192
+ after { Rpush::Daemon::AppRunner.runners.clear }
193
+
194
+ it 'waits until all runners are idle' do
195
+ Rpush::Daemon::AppRunner.runners.count.should eq 1
196
+ Timeout.timeout(5) { Rpush::Daemon::AppRunner.wait }
197
+ end
198
+ end
199
+
200
+ describe Rpush::Daemon::AppRunner do
201
+ let(:app) { double(Rpush::AppRunnerSpecService::App, :environment => :sandbox,
202
+ :connections => 1, :service_name => 'app_runner_spec_service',
203
+ :name => 'test') }
204
+ let(:runner) { Rpush::Daemon::AppRunner.new(app) }
205
+ let(:logger) { double(Rpush::Logger, :info => nil) }
206
+ let(:queue) { Queue.new }
207
+ let(:dispatcher_loop_collection) { Rpush::Daemon::DispatcherLoopCollection.new }
208
+ let(:service_loop) { double(Rpush::Daemon::AppRunnerSpecService::ServiceLoop,
209
+ :start => nil, :stop => nil) }
210
+ let(:store) { double(Rpush::Daemon::Store::ActiveRecord, release_connection: nil) }
211
+
212
+ before do
213
+ Rpush::Daemon.stub(store: store)
214
+ Rpush::Daemon::AppRunnerSpecService::ServiceLoop.stub(:new => service_loop)
215
+ Queue.stub(:new => queue)
216
+ Rpush.stub(:logger => logger)
217
+ Rpush::Daemon::DispatcherLoopCollection.stub(:new => dispatcher_loop_collection)
218
+ end
219
+
220
+ describe 'start' do
221
+ it 'starts a delivery dispatcher for each connection' do
222
+ app.stub(:connections => 2)
223
+ runner.start
224
+ runner.num_dispatchers.should eq 2
225
+ end
226
+
227
+ it 'starts the loops' do
228
+ service_loop.should_receive(:start)
229
+ runner.start
230
+ end
231
+ end
232
+
233
+ describe 'enqueue' do
234
+ let(:notification) { double }
235
+ let(:batch) { double(:notifications => [notification]) }
236
+
237
+ it 'enqueues the batch' do
238
+ queue.should_receive(:push).with([notification, batch])
239
+ runner.enqueue(batch)
240
+ end
241
+
242
+ it 'reflects the notification has been enqueued' do
243
+ runner.should_receive(:reflect).with(:notification_enqueued, notification)
244
+ runner.enqueue(batch)
245
+ end
246
+ end
247
+
248
+ describe 'stop' do
249
+ before { runner.start }
250
+
251
+ it 'stops the delivery dispatchers' do
252
+ dispatcher_loop_collection.should_receive(:stop)
253
+ runner.stop
254
+ end
255
+
256
+ it 'stop the loops' do
257
+ service_loop.should_receive(:stop)
258
+ runner.stop
259
+ end
260
+ end
261
+
262
+ describe 'idle?' do
263
+ it 'is idle if all notifications have been processed' do
264
+ runner.batch = double(:complete? => true)
265
+ runner.idle?.should be_true
266
+ end
267
+
268
+ it 'is idle if the runner has no associated batch' do
269
+ runner.batch = nil
270
+ runner.idle?.should be_true
271
+ end
272
+
273
+ it 'is not idle if not all notifications have been processed' do
274
+ runner.batch = double(:complete? => false)
275
+ runner.idle?.should be_false
276
+ end
277
+ end
278
+
279
+ describe 'sync' do
280
+ before { runner.start }
281
+
282
+ it 'reduces the number of dispatchers if needed' do
283
+ app.stub(:connections => 0)
284
+ expect { runner.sync(app) }.to change(runner, :num_dispatchers).to(0)
285
+ end
286
+
287
+ it 'increases the number of dispatchers if needed' do
288
+ app.stub(:connections => 2)
289
+ expect { runner.sync(app) }.to change(runner, :num_dispatchers).to(2)
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,232 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Batch do
4
+ let(:notification1) { double(:notification1, :id => 1) }
5
+ let(:notification2) { double(:notification2, :id => 2) }
6
+ let(:batch) { Rpush::Daemon::Batch.new([notification1, notification2]) }
7
+ let(:store) { double.as_null_object }
8
+ let(:time) { Time.now }
9
+
10
+ before do
11
+ Time.stub(:now => time)
12
+ Rpush::Daemon.stub(:store => store)
13
+ end
14
+
15
+ it 'exposes the notifications' do
16
+ batch.notifications.should eq [notification1, notification2]
17
+ end
18
+
19
+ it 'exposes the number notifications' do
20
+ batch.num_notifications.should eq 2
21
+ end
22
+
23
+ it 'exposes the number notifications processed' do
24
+ batch.num_processed.should eq 0
25
+ end
26
+
27
+ it 'increments the processed notifications count' do
28
+ expect { batch.notification_dispatched }.to change(batch, :num_processed).to(1)
29
+ end
30
+
31
+ it 'completes the batch when all notifications have been processed' do
32
+ batch.should_receive(:complete)
33
+ 2.times { batch.notification_dispatched }
34
+ end
35
+
36
+ it 'can be described' do
37
+ batch.describe.should eq '1, 2'
38
+ end
39
+
40
+ describe 'mark_delivered' do
41
+ describe 'batching is disabled' do
42
+ before { Rpush.config.batch_storage_updates = false }
43
+
44
+ it 'marks the notification as delivered immediately' do
45
+ store.should_receive(:mark_delivered).with(notification1, time)
46
+ batch.mark_delivered(notification1)
47
+ end
48
+
49
+ it 'reflects the notification was delivered' do
50
+ batch.should_receive(:reflect).with(:notification_delivered, notification1)
51
+ batch.mark_delivered(notification1)
52
+ end
53
+ end
54
+
55
+ describe 'batching is enabled' do
56
+ before { Rpush.config.batch_storage_updates = true }
57
+
58
+ it 'marks the notification as delivered immediately without persisting' do
59
+ store.should_receive(:mark_delivered).with(notification1, time, :persist => false)
60
+ batch.mark_delivered(notification1)
61
+ end
62
+
63
+ it 'defers persisting' do
64
+ batch.mark_delivered(notification1)
65
+ batch.delivered.should eq [notification1]
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'mark_failed' do
71
+ describe 'batching is disabled' do
72
+ before { Rpush.config.batch_storage_updates = false }
73
+
74
+ it 'marks the notification as failed' do
75
+ store.should_receive(:mark_failed).with(notification1, 1, 'an error', time)
76
+ batch.mark_failed(notification1, 1, 'an error')
77
+ end
78
+
79
+ it 'reflects the notification failed' do
80
+ batch.should_receive(:reflect).with(:notification_delivered, notification1)
81
+ batch.mark_delivered(notification1)
82
+ end
83
+ end
84
+
85
+ describe 'batching is enabled' do
86
+ before { Rpush.config.batch_storage_updates = true }
87
+
88
+ it 'marks the notification as failed without persisting' do
89
+ store.should_receive(:mark_failed).with(notification1, 1, 'an error', time, :persist => false)
90
+ batch.mark_failed(notification1, 1, 'an error')
91
+ end
92
+
93
+ it 'defers persisting' do
94
+ Rpush.config.batch_storage_updates = true
95
+ batch.mark_failed(notification1, 1, 'an error')
96
+ batch.failed.should eq({[1, 'an error'] => [notification1]})
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'mark_retryable' do
102
+ describe 'batching is disabled' do
103
+ before { Rpush.config.batch_storage_updates = false }
104
+
105
+ it 'marks the notification as retryable' do
106
+ store.should_receive(:mark_retryable).with(notification1, time)
107
+ batch.mark_retryable(notification1, time)
108
+ end
109
+
110
+ it 'reflects the notification will be retried' do
111
+ batch.should_receive(:reflect).with(:notification_will_retry, notification1)
112
+ batch.mark_retryable(notification1, time)
113
+ end
114
+ end
115
+
116
+ describe 'batching is enabled' do
117
+ before { Rpush.config.batch_storage_updates = true }
118
+
119
+ it 'marks the notification as retryable without persisting' do
120
+ store.should_receive(:mark_retryable).with(notification1, time, :persist => false)
121
+ batch.mark_retryable(notification1, time)
122
+ end
123
+
124
+ it 'defers persisting' do
125
+ batch.mark_retryable(notification1, time)
126
+ batch.retryable.should eq({time => [notification1]})
127
+ end
128
+ end
129
+ end
130
+
131
+ describe 'complete' do
132
+ before do
133
+ Rpush.config.batch_storage_updates = true
134
+ Rpush.stub(:logger => double.as_null_object)
135
+ batch.stub(:reflect)
136
+ end
137
+
138
+ it 'clears the notifications' do
139
+ expect do
140
+ 2.times { batch.notification_dispatched }
141
+ end.to change(batch, :notifications).to([])
142
+ end
143
+
144
+ it 'identifies as complete' do
145
+ expect do
146
+ 2.times { batch.notification_dispatched }
147
+ end.to change(batch, :complete?).to(be_true)
148
+ end
149
+
150
+ it 'reflects errors raised during completion' do
151
+ e = StandardError.new
152
+ batch.stub(:complete_delivered).and_raise(e)
153
+ batch.should_receive(:reflect).with(:error, e)
154
+ 2.times { batch.notification_dispatched }
155
+ end
156
+
157
+ describe 'delivered' do
158
+ def complete
159
+ [notification1, notification2].each do |n|
160
+ batch.mark_delivered(n)
161
+ batch.notification_dispatched
162
+ end
163
+ end
164
+
165
+ it 'marks the batch as delivered' do
166
+ store.should_receive(:mark_batch_delivered).with([notification1, notification2])
167
+ complete
168
+ end
169
+
170
+ it 'reflects the notifications were delivered' do
171
+ batch.should_receive(:reflect).with(:notification_delivered, notification1)
172
+ batch.should_receive(:reflect).with(:notification_delivered, notification2)
173
+ complete
174
+ end
175
+
176
+ it 'clears the delivered notifications' do
177
+ complete
178
+ batch.delivered.should eq([])
179
+ end
180
+ end
181
+
182
+ describe 'failed' do
183
+ def complete
184
+ [notification1, notification2].each do |n|
185
+ batch.mark_failed(n, 1, 'an error')
186
+ batch.notification_dispatched
187
+ end
188
+ end
189
+
190
+ it 'marks the batch as failed' do
191
+ store.should_receive(:mark_batch_failed).with([notification1, notification2], 1, 'an error')
192
+ complete
193
+ end
194
+
195
+ it 'reflects the notifications failed' do
196
+ batch.should_receive(:reflect).with(:notification_failed, notification1)
197
+ batch.should_receive(:reflect).with(:notification_failed, notification2)
198
+ complete
199
+ end
200
+
201
+ it 'clears the failed notifications' do
202
+ complete
203
+ batch.failed.should eq({})
204
+ end
205
+ end
206
+
207
+ describe 'retryable' do
208
+ def complete
209
+ [notification1, notification2].each do |n|
210
+ batch.mark_retryable(n, time)
211
+ batch.notification_dispatched
212
+ end
213
+ end
214
+
215
+ it 'marks the batch as retryable' do
216
+ store.should_receive(:mark_batch_retryable).with([notification1, notification2], time)
217
+ complete
218
+ end
219
+
220
+ it 'reflects the notifications will be retried' do
221
+ batch.should_receive(:reflect).with(:notification_will_retry, notification1)
222
+ batch.should_receive(:reflect).with(:notification_will_retry, notification2)
223
+ complete
224
+ end
225
+
226
+ it 'clears the retryable notifications' do
227
+ complete
228
+ batch.retryable.should eq({})
229
+ end
230
+ end
231
+ end
232
+ end