rapns 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/README.md +9 -18
  3. data/lib/generators/templates/rapns.rb +5 -0
  4. data/lib/rapns.rb +4 -0
  5. data/lib/rapns/apns_feedback.rb +1 -0
  6. data/lib/rapns/configuration.rb +4 -3
  7. data/lib/rapns/daemon.rb +20 -4
  8. data/lib/rapns/daemon/apns/feedback_receiver.rb +9 -11
  9. data/lib/rapns/daemon/delivery.rb +3 -20
  10. data/lib/rapns/daemon/delivery_queue.rb +2 -2
  11. data/lib/rapns/daemon/feeder.rb +5 -10
  12. data/lib/rapns/daemon/gcm/delivery.rb +19 -9
  13. data/lib/rapns/daemon/store/active_record.rb +74 -0
  14. data/lib/rapns/daemon/store/active_record/reconnectable.rb +61 -0
  15. data/lib/rapns/gcm/notification.rb +5 -4
  16. data/lib/rapns/gcm/registration_ids_count_validator.rb +1 -1
  17. data/lib/rapns/logger.rb +6 -2
  18. data/lib/rapns/push.rb +1 -0
  19. data/lib/rapns/reflection.rb +1 -1
  20. data/lib/rapns/version.rb +1 -1
  21. data/spec/unit/apns/notification_spec.rb +2 -0
  22. data/spec/unit/apns_feedback_spec.rb +5 -0
  23. data/spec/unit/configuration_spec.rb +1 -1
  24. data/spec/unit/daemon/apns/delivery_spec.rb +7 -64
  25. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +2 -2
  26. data/spec/unit/daemon/feeder_spec.rb +6 -58
  27. data/spec/unit/daemon/gcm/delivery_spec.rb +49 -57
  28. data/spec/unit/daemon/{database_reconnectable_spec.rb → store/active_record/reconnectable_spec.rb} +4 -3
  29. data/spec/unit/daemon/store/active_record_spec.rb +181 -0
  30. data/spec/unit/daemon_spec.rb +27 -7
  31. data/spec/unit/gcm/notification_spec.rb +2 -9
  32. data/spec/unit/push_spec.rb +5 -0
  33. data/spec/unit/reflection_spec.rb +0 -4
  34. data/spec/unit_spec_helper.rb +4 -1
  35. metadata +10 -7
  36. data/lib/rapns/daemon/database_reconnectable.rb +0 -57
@@ -1,8 +1,9 @@
1
- require "unit_spec_helper"
1
+ require 'unit_spec_helper'
2
+ require 'rapns/daemon/store/active_record/reconnectable'
2
3
 
3
- describe Rapns::Daemon::DatabaseReconnectable do
4
+ describe Rapns::Daemon::Store::ActiveRecord::Reconnectable do
4
5
  class TestDouble
5
- include Rapns::Daemon::DatabaseReconnectable
6
+ include Rapns::Daemon::Store::ActiveRecord::Reconnectable
6
7
 
7
8
  attr_reader :name
8
9
 
@@ -0,0 +1,181 @@
1
+ require 'unit_spec_helper'
2
+ require 'rapns/daemon/store/active_record'
3
+
4
+ describe Rapns::Daemon::Store::ActiveRecord do
5
+ let(:app) { Rapns::Apns::App.create!(:name => 'my_app', :environment => 'development', :certificate => TEST_CERT) }
6
+ let(:notification) { Rapns::Apns::Notification.create!(:device_token => "a" * 64, :app => app) }
7
+ let(:store) { Rapns::Daemon::Store::ActiveRecord.new }
8
+ let(:now) { Time.now }
9
+
10
+ before { Time.stub(:now => now) }
11
+
12
+ describe 'deliverable_notifications' do
13
+ it 'checks for new notifications with the ability to reconnect the database' do
14
+ store.should_receive(:with_database_reconnect_and_retry)
15
+ store.deliverable_notifications(app)
16
+ end
17
+
18
+ it 'loads notifications in batches' do
19
+ Rapns.config.batch_size = 5000
20
+ Rapns.config.push = false
21
+ relation = stub.as_null_object
22
+ relation.should_receive(:limit).with(5000)
23
+ Rapns::Notification.stub(:ready_for_delivery => relation)
24
+ store.deliverable_notifications([app])
25
+ end
26
+
27
+ it 'does not load notification in batches if in push mode' do
28
+ Rapns.config.push = true
29
+ relation = stub.as_null_object
30
+ relation.should_not_receive(:limit)
31
+ Rapns::Notification.stub(:ready_for_delivery => relation)
32
+ store.deliverable_notifications([app])
33
+ end
34
+
35
+ it 'loads an undelivered notification without deliver_after set' do
36
+ notification.update_attributes!(:delivered => false, :deliver_after => nil)
37
+ store.deliverable_notifications([app]).should == [notification]
38
+ end
39
+
40
+ it 'loads an notification with a deliver_after time in the past' do
41
+ notification.update_attributes!(:delivered => false, :deliver_after => 1.hour.ago)
42
+ store.deliverable_notifications([app]).should == [notification]
43
+ end
44
+
45
+ it 'does not load an notification with a deliver_after time in the future' do
46
+ notification.update_attributes!(:delivered => false, :deliver_after => 1.hour.from_now)
47
+ store.deliverable_notifications([app]).should be_empty
48
+ end
49
+
50
+ it 'does not load a previously delivered notification' do
51
+ notification.update_attributes!(:delivered => true, :delivered_at => Time.now)
52
+ store.deliverable_notifications([app]).should be_empty
53
+ end
54
+
55
+ it "does not enqueue a notification that has previously failed delivery" do
56
+ notification.update_attributes!(:delivered => false, :failed => true)
57
+ store.deliverable_notifications([app]).should be_empty
58
+ end
59
+
60
+ it 'does not load notifications for apps that are still processing the previous batch' do
61
+ notification
62
+ store.deliverable_notifications([]).should be_empty
63
+ end
64
+ end
65
+
66
+ describe 'retry_after' do
67
+ it 'increments the retry count' do
68
+ expect do
69
+ store.retry_after(notification, now)
70
+ end.to change(notification, :retries).by(1)
71
+ end
72
+
73
+ it 'sets the deliver after timestamp' do
74
+ deliver_after = now + 10.seconds
75
+ expect do
76
+ store.retry_after(notification, deliver_after)
77
+ end.to change(notification, :deliver_after).to(deliver_after)
78
+ end
79
+
80
+ it 'saves the notification without validation' do
81
+ notification.should_receive(:save!).with(:validate => false)
82
+ store.retry_after(notification, now)
83
+ end
84
+ end
85
+
86
+ describe 'mark_delivered' do
87
+ it 'marks the notification as delivered' do
88
+ expect do
89
+ store.mark_delivered(notification)
90
+ end.to change(notification, :delivered).to(true)
91
+ end
92
+
93
+ it 'sets the time the notification was delivered' do
94
+ expect do
95
+ store.mark_delivered(notification)
96
+ end.to change(notification, :delivered_at).to(now)
97
+ end
98
+
99
+ it 'saves the notification without validation' do
100
+ notification.should_receive(:save!).with(:validate => false)
101
+ store.mark_delivered(notification)
102
+ end
103
+ end
104
+
105
+ describe 'mark_failed' do
106
+ it 'marks the notification as not delivered' do
107
+ store.mark_failed(notification, nil, '')
108
+ notification.delivered.should be_false
109
+ end
110
+
111
+ it 'marks the notification as failed' do
112
+ expect do
113
+ store.mark_failed(notification, nil, '')
114
+ end.to change(notification, :failed).to(true)
115
+ end
116
+
117
+ it 'sets the time the notification delivery failed' do
118
+ expect do
119
+ store.mark_failed(notification, nil, '')
120
+ end.to change(notification, :failed_at).to(now)
121
+ end
122
+
123
+ it 'sets the error code' do
124
+ expect do
125
+ store.mark_failed(notification, 42, '')
126
+ end.to change(notification, :error_code).to(42)
127
+ end
128
+
129
+ it 'sets the error description' do
130
+ expect do
131
+ store.mark_failed(notification, 42, 'Weeee')
132
+ end.to change(notification, :error_description).to('Weeee')
133
+ end
134
+
135
+ it 'saves the notification without validation' do
136
+ notification.should_receive(:save!).with(:validate => false)
137
+ store.mark_failed(notification, nil, '')
138
+ end
139
+ end
140
+
141
+ describe 'create_apns_feedback' do
142
+ it 'creates the Feedback record' do
143
+ Rapns::Apns::Feedback.should_receive(:create!).with(
144
+ :failed_at => now, :device_token => 'ab' * 32, :app => app)
145
+ store.create_apns_feedback(now, 'ab' * 32, app)
146
+ end
147
+ end
148
+
149
+ describe 'create_gcm_notification' do
150
+ let(:data) { { :data => true } }
151
+ let(:attributes) { { :device_token => 'ab' * 32 } }
152
+ let(:registration_ids) { ['123', '456'] }
153
+ let(:deliver_after) { now + 10.seconds }
154
+ let(:args) { [attributes, data, registration_ids, deliver_after, app] }
155
+
156
+ it 'sets the given attributes' do
157
+ new_notification = store.create_gcm_notification(*args)
158
+ new_notification.device_token.should == 'ab' * 32
159
+ end
160
+
161
+ it 'sets the given data' do
162
+ new_notification = store.create_gcm_notification(*args)
163
+ new_notification.data['data'].should be_true
164
+ end
165
+
166
+ it 'sets the given registration IDs' do
167
+ new_notification = store.create_gcm_notification(*args)
168
+ new_notification.registration_ids.should == registration_ids
169
+ end
170
+
171
+ it 'sets the deliver_after timestamp' do
172
+ new_notification = store.create_gcm_notification(*args)
173
+ new_notification.deliver_after.should == deliver_after
174
+ end
175
+
176
+ it 'saves the new notification' do
177
+ new_notification = store.create_gcm_notification(*args)
178
+ new_notification.new_record?.should be_false
179
+ end
180
+ end
181
+ end
@@ -1,4 +1,5 @@
1
1
  require 'unit_spec_helper'
2
+ require 'rapns/daemon/store/active_record'
2
3
 
3
4
  describe Rapns::Daemon, "when starting" do
4
5
  module Rails; end
@@ -6,27 +7,34 @@ describe Rapns::Daemon, "when starting" do
6
7
  let(:certificate) { stub }
7
8
  let(:password) { stub }
8
9
  let(:config) { stub(:pid_file => nil, :airbrake_notify => false,
9
- :foreground => true, :embedded => false, :push => false) }
10
+ :foreground => true, :embedded => false, :push => false, :store => :active_record) }
10
11
  let(:logger) { stub(:logger, :info => nil, :error => nil, :warn => nil) }
11
12
 
12
13
  before do
13
- Rapns.stub(:config => config)
14
- Rapns::Logger.stub(:new => logger)
14
+ Rapns.stub(:config => config, :logger => logger)
15
15
  Rapns::Daemon::Feeder.stub(:start)
16
16
  Rapns::Daemon::AppRunner.stub(:sync => nil, :stop => nil)
17
- Rapns::Daemon.stub(:daemonize => nil, :reconnect_database => nil, :exit => nil, :puts => nil)
17
+ Rapns::Daemon.stub(:daemonize => nil, :exit => nil, :puts => nil)
18
18
  File.stub(:open)
19
19
  Rails.stub(:root).and_return("/rails_root")
20
20
  end
21
21
 
22
- unless defined?(JRUBY_VERSION)
22
+ unless Rapns.jruby?
23
23
  it "forks into a daemon if the foreground option is false" do
24
24
  config.stub(:foreground => false)
25
- ActiveRecord::Base.stub(:establish_connection)
25
+ Rapns::Daemon.initialize_store
26
+ Rapns::Daemon.store.stub(:after_daemonize => nil)
26
27
  Rapns::Daemon.should_receive(:daemonize)
27
28
  Rapns::Daemon.start
28
29
  end
29
30
 
31
+ it 'notifies the store after forking' do
32
+ config.stub(:foreground => false)
33
+ Rapns::Daemon.initialize_store
34
+ Rapns::Daemon.store.should_receive(:after_daemonize)
35
+ Rapns::Daemon.start
36
+ end
37
+
30
38
  it "does not fork into a daemon if the foreground option is true" do
31
39
  config.stub(:foreground => true)
32
40
  Rapns::Daemon.should_not_receive(:daemonize)
@@ -57,6 +65,18 @@ describe Rapns::Daemon, "when starting" do
57
65
  Rapns::Daemon.start
58
66
  end
59
67
 
68
+ it 'instantiates the store' do
69
+ config.stub(:store => :active_record)
70
+ Rapns::Daemon.start
71
+ Rapns::Daemon.store.should be_kind_of(Rapns::Daemon::Store::ActiveRecord)
72
+ end
73
+
74
+ it 'logs an error if the store cannot be loaded' do
75
+ config.stub(:store => :foo_bar)
76
+ Rapns.logger.should_receive(:error).with(kind_of(LoadError))
77
+ Rapns::Daemon.start
78
+ end
79
+
60
80
  it "writes the process ID to the PID file" do
61
81
  Rapns::Daemon.should_receive(:write_pid_file)
62
82
  Rapns::Daemon.start
@@ -91,7 +111,7 @@ describe Rapns::Daemon, "when being shutdown" do
91
111
  end
92
112
 
93
113
  # These tests do not work on JRuby.
94
- unless defined? JRUBY_VERSION
114
+ unless Rapns.jruby?
95
115
  it "shuts down when signaled signaled SIGINT" do
96
116
  Rapns::Daemon.setup_signal_traps
97
117
  Rapns::Daemon.should_receive(:shutdown)
@@ -27,7 +27,7 @@ describe Rapns::Gcm::Notification do
27
27
  it 'num of registration Ids limit of 1000' do
28
28
  notification.registration_ids = ['a']*(1000+1)
29
29
  notification.valid?.should be_false
30
- notification.errors[:base].should == ["GCM notification num of registration_ids cannot be larger than 1000."]
30
+ notification.errors[:base].should == ["GCM notification number of registration_ids cannot be larger than 1000."]
31
31
  end
32
32
 
33
33
  it 'allows assignment of a single registration ID' do
@@ -45,15 +45,8 @@ describe Rapns::Gcm::Notification do
45
45
  notification.errors[:expiry].should == ['must be set when using a collapse_key']
46
46
  end
47
47
 
48
- it 'does not include time_to_live in the payload if collapse_key is not set' do
48
+ it 'includes time_to_live in the payload' do
49
49
  notification.expiry = 100
50
- notification.collapse_key = nil
51
- notification.as_json.key?('time_to_live').should be_false
52
- end
53
-
54
- it 'includes time_to_live in the payload if collapse_key is set' do
55
- notification.expiry = 100
56
- notification.collapse_key = 'sync'
57
50
  notification.as_json['time_to_live'].should == 100
58
51
  end
59
52
  end
@@ -12,6 +12,11 @@ describe Rapns, 'push' do
12
12
  Rapns.config.push.should be_true
13
13
  end
14
14
 
15
+ it 'initializes the store' do
16
+ Rapns::Daemon.should_receive(:initialize_store)
17
+ Rapns.push
18
+ end
19
+
15
20
  it 'syncs the app runner' do
16
21
  Rapns::Daemon::AppRunner.should_receive(:sync)
17
22
  Rapns.push
@@ -12,10 +12,6 @@ describe Rapns do
12
12
  end
13
13
  end
14
14
 
15
- # :apns_feedback, :notification_enqueued, :notification_delivered,
16
- # :notification_failed, :notification_will_retry, :apns_connection_lost,
17
- # :error
18
-
19
15
  describe Rapns::Reflections do
20
16
  it 'dispatches the given reflection' do
21
17
  did_yield = false
@@ -72,7 +72,10 @@ RSpec.configure do |config|
72
72
  DatabaseCleaner.clean
73
73
  end
74
74
 
75
- config.after(:each) { Rapns.logger = nil }
75
+ config.after(:each) do
76
+ Rapns.logger = nil
77
+ Rapns::Daemon.store = nil
78
+ end
76
79
  end
77
80
 
78
81
  # a test certificate that contains both an X509 certificate and
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapns
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-02 00:00:00.000000000 Z
12
+ date: 2013-04-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -80,7 +80,6 @@ files:
80
80
  - lib/rapns/daemon/apns/disconnection_error.rb
81
81
  - lib/rapns/daemon/apns/feedback_receiver.rb
82
82
  - lib/rapns/daemon/app_runner.rb
83
- - lib/rapns/daemon/database_reconnectable.rb
84
83
  - lib/rapns/daemon/delivery.rb
85
84
  - lib/rapns/daemon/delivery_error.rb
86
85
  - lib/rapns/daemon/delivery_handler.rb
@@ -93,6 +92,8 @@ files:
93
92
  - lib/rapns/daemon/gcm/delivery_handler.rb
94
93
  - lib/rapns/daemon/interruptible_sleep.rb
95
94
  - lib/rapns/daemon/reflectable.rb
95
+ - lib/rapns/daemon/store/active_record.rb
96
+ - lib/rapns/daemon/store/active_record/reconnectable.rb
96
97
  - lib/rapns/deprecatable.rb
97
98
  - lib/rapns/deprecation.rb
98
99
  - lib/rapns/embed.rb
@@ -134,7 +135,6 @@ files:
134
135
  - spec/unit/daemon/apns/feedback_receiver_spec.rb
135
136
  - spec/unit/daemon/app_runner_shared.rb
136
137
  - spec/unit/daemon/app_runner_spec.rb
137
- - spec/unit/daemon/database_reconnectable_spec.rb
138
138
  - spec/unit/daemon/delivery_error_spec.rb
139
139
  - spec/unit/daemon/delivery_handler_shared.rb
140
140
  - spec/unit/daemon/delivery_queue_spec.rb
@@ -144,6 +144,8 @@ files:
144
144
  - spec/unit/daemon/gcm/delivery_spec.rb
145
145
  - spec/unit/daemon/interruptible_sleep_spec.rb
146
146
  - spec/unit/daemon/reflectable_spec.rb
147
+ - spec/unit/daemon/store/active_record/reconnectable_spec.rb
148
+ - spec/unit/daemon/store/active_record_spec.rb
147
149
  - spec/unit/daemon_spec.rb
148
150
  - spec/unit/deprecatable_spec.rb
149
151
  - spec/unit/deprecation_spec.rb
@@ -173,7 +175,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
175
  version: '0'
174
176
  segments:
175
177
  - 0
176
- hash: -3688702007568166292
178
+ hash: -3085387548794744992
177
179
  required_rubygems_version: !ruby/object:Gem::Requirement
178
180
  none: false
179
181
  requirements:
@@ -182,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
184
  version: '0'
183
185
  segments:
184
186
  - 0
185
- hash: -3688702007568166292
187
+ hash: -3085387548794744992
186
188
  requirements: []
187
189
  rubyforge_project:
188
190
  rubygems_version: 1.8.25
@@ -211,7 +213,6 @@ test_files:
211
213
  - spec/unit/daemon/apns/feedback_receiver_spec.rb
212
214
  - spec/unit/daemon/app_runner_shared.rb
213
215
  - spec/unit/daemon/app_runner_spec.rb
214
- - spec/unit/daemon/database_reconnectable_spec.rb
215
216
  - spec/unit/daemon/delivery_error_spec.rb
216
217
  - spec/unit/daemon/delivery_handler_shared.rb
217
218
  - spec/unit/daemon/delivery_queue_spec.rb
@@ -221,6 +222,8 @@ test_files:
221
222
  - spec/unit/daemon/gcm/delivery_spec.rb
222
223
  - spec/unit/daemon/interruptible_sleep_spec.rb
223
224
  - spec/unit/daemon/reflectable_spec.rb
225
+ - spec/unit/daemon/store/active_record/reconnectable_spec.rb
226
+ - spec/unit/daemon/store/active_record_spec.rb
224
227
  - spec/unit/daemon_spec.rb
225
228
  - spec/unit/deprecatable_spec.rb
226
229
  - spec/unit/deprecation_spec.rb
@@ -1,57 +0,0 @@
1
- class PGError < StandardError; end if !defined?(PGError)
2
- class Mysql; class Error < StandardError; end; end if !defined?(Mysql)
3
- module Mysql2; class Error < StandardError; end; end if !defined?(Mysql2)
4
- module ActiveRecord; end
5
- class ActiveRecord::JDBCError < StandardError; end if !defined?(ActiveRecord::JDBCError)
6
-
7
- module Rapns
8
- module Daemon
9
- module DatabaseReconnectable
10
- ADAPTER_ERRORS = [ActiveRecord::StatementInvalid, PGError, Mysql::Error,
11
- Mysql2::Error, ActiveRecord::JDBCError]
12
-
13
- def with_database_reconnect_and_retry
14
- begin
15
- ActiveRecord::Base.connection_pool.with_connection do
16
- yield
17
- end
18
- rescue *ADAPTER_ERRORS => e
19
- Rapns.logger.error(e)
20
- database_connection_lost
21
- retry
22
- end
23
- end
24
-
25
- def database_connection_lost
26
- Rapns.logger.warn("Lost connection to database, reconnecting...")
27
- attempts = 0
28
- loop do
29
- begin
30
- Rapns.logger.warn("Attempt #{attempts += 1}")
31
- reconnect_database
32
- check_database_is_connected
33
- break
34
- rescue *ADAPTER_ERRORS => e
35
- Rapns.logger.error(e, :airbrake_notify => false)
36
- sleep_to_avoid_thrashing
37
- end
38
- end
39
- Rapns.logger.warn("Database reconnected")
40
- end
41
-
42
- def reconnect_database
43
- ActiveRecord::Base.clear_all_connections!
44
- ActiveRecord::Base.establish_connection
45
- end
46
-
47
- def check_database_is_connected
48
- # Simply asking the adapter for the connection state is not sufficient.
49
- Rapns::Notification.count
50
- end
51
-
52
- def sleep_to_avoid_thrashing
53
- sleep 2
54
- end
55
- end
56
- end
57
- end