rapns 2.0.5 → 3.0.0.beta.1

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 (82) hide show
  1. data/lib/generators/rapns_generator.rb +1 -0
  2. data/lib/generators/templates/add_gcm.rb +86 -0
  3. data/lib/generators/templates/create_rapns_notifications.rb +1 -1
  4. data/lib/rapns/apns/app.rb +8 -0
  5. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  6. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  7. data/lib/rapns/apns/feedback.rb +14 -0
  8. data/lib/rapns/apns/notification.rb +84 -0
  9. data/lib/rapns/app.rb +5 -6
  10. data/lib/rapns/{config.rb → configuration.rb} +5 -5
  11. data/lib/rapns/daemon/apns/app_runner.rb +36 -0
  12. data/lib/rapns/daemon/apns/connection.rb +113 -0
  13. data/lib/rapns/daemon/apns/delivery.rb +63 -0
  14. data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
  15. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  16. data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
  17. data/lib/rapns/daemon/app_runner.rb +76 -77
  18. data/lib/rapns/daemon/database_reconnectable.rb +3 -3
  19. data/lib/rapns/daemon/delivery.rb +43 -0
  20. data/lib/rapns/daemon/delivery_error.rb +6 -2
  21. data/lib/rapns/daemon/delivery_handler.rb +13 -79
  22. data/lib/rapns/daemon/delivery_queue_18.rb +2 -2
  23. data/lib/rapns/daemon/delivery_queue_19.rb +3 -3
  24. data/lib/rapns/daemon/feeder.rb +5 -5
  25. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  26. data/lib/rapns/daemon/gcm/delivery.rb +206 -0
  27. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  28. data/lib/rapns/daemon.rb +31 -20
  29. data/lib/rapns/gcm/app.rb +7 -0
  30. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  31. data/lib/rapns/gcm/notification.rb +31 -0
  32. data/lib/rapns/gcm/payload_size_validator.rb +13 -0
  33. data/lib/rapns/multi_json_helper.rb +16 -0
  34. data/lib/rapns/notification.rb +28 -95
  35. data/lib/rapns/version.rb +1 -1
  36. data/lib/rapns.rb +14 -4
  37. data/lib/tasks/cane.rake +19 -0
  38. data/lib/tasks/test.rake +34 -0
  39. data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
  40. data/spec/acceptance_spec_helper.rb +85 -0
  41. data/spec/support/simplecov_helper.rb +13 -0
  42. data/spec/support/simplecov_quality_formatter.rb +8 -0
  43. data/spec/unit/apns/app_spec.rb +15 -0
  44. data/spec/unit/apns/feedback_spec.rb +12 -0
  45. data/spec/{rapns → unit/apns}/notification_spec.rb +44 -72
  46. data/spec/unit/app_spec.rb +18 -0
  47. data/spec/unit/daemon/apns/app_runner_spec.rb +37 -0
  48. data/spec/{rapns/daemon → unit/daemon/apns}/connection_spec.rb +9 -9
  49. data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
  50. data/spec/unit/daemon/apns/delivery_spec.rb +154 -0
  51. data/spec/{rapns/daemon → unit/daemon/apns}/feedback_receiver_spec.rb +14 -14
  52. data/spec/unit/daemon/app_runner_shared.rb +66 -0
  53. data/spec/unit/daemon/app_runner_spec.rb +78 -0
  54. data/spec/{rapns → unit}/daemon/database_reconnectable_spec.rb +4 -5
  55. data/spec/{rapns → unit}/daemon/delivery_error_spec.rb +2 -2
  56. data/spec/unit/daemon/delivery_handler_shared.rb +19 -0
  57. data/spec/{rapns → unit}/daemon/delivery_queue_spec.rb +1 -1
  58. data/spec/{rapns → unit}/daemon/feeder_spec.rb +33 -33
  59. data/spec/unit/daemon/gcm/app_runner_spec.rb +15 -0
  60. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
  61. data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
  62. data/spec/{rapns → unit}/daemon/interruptible_sleep_spec.rb +1 -1
  63. data/spec/{rapns → unit}/daemon/logger_spec.rb +1 -1
  64. data/spec/{rapns → unit}/daemon_spec.rb +1 -1
  65. data/spec/unit/gcm/app_spec.rb +5 -0
  66. data/spec/unit/gcm/notification_spec.rb +55 -0
  67. data/spec/unit/notification_shared.rb +38 -0
  68. data/spec/unit/notification_spec.rb +6 -0
  69. data/spec/{rapns/app_spec.rb → unit_spec_helper.rb} +76 -16
  70. metadata +107 -45
  71. data/lib/rapns/binary_notification_validator.rb +0 -10
  72. data/lib/rapns/daemon/connection.rb +0 -114
  73. data/lib/rapns/daemon/delivery_handler_pool.rb +0 -18
  74. data/lib/rapns/daemon/disconnection_error.rb +0 -14
  75. data/lib/rapns/daemon/feedback_receiver.rb +0 -82
  76. data/lib/rapns/device_token_format_validator.rb +0 -10
  77. data/lib/rapns/feedback.rb +0 -12
  78. data/spec/rapns/daemon/app_runner_spec.rb +0 -193
  79. data/spec/rapns/daemon/delivery_handler_pool_spec.rb +0 -17
  80. data/spec/rapns/daemon/delivery_handler_spec.rb +0 -206
  81. data/spec/rapns/feedback_spec.rb +0 -12
  82. data/spec/spec_helper.rb +0 -78
@@ -1,10 +0,0 @@
1
- module Rapns
2
- class BinaryNotificationValidator < ActiveModel::Validator
3
-
4
- def validate(record)
5
- if record.payload_size > 256
6
- record.errors[:base] << "APN notification cannot be larger than 256 bytes. Try condensing your alert and device attributes."
7
- end
8
- end
9
- end
10
- end
@@ -1,114 +0,0 @@
1
- module Rapns
2
- module Daemon
3
- class ConnectionError < StandardError; end
4
-
5
- class Connection
6
- attr_accessor :last_write
7
-
8
- def self.idle_period
9
- 30.minutes
10
- end
11
-
12
- def initialize(name, host, port, certificate, password)
13
- @name = name
14
- @host = host
15
- @port = port
16
- @certificate = certificate
17
- @password = password
18
- written
19
- end
20
-
21
- def connect
22
- @ssl_context = setup_ssl_context
23
- @tcp_socket, @ssl_socket = connect_socket
24
- end
25
-
26
- def close
27
- begin
28
- @ssl_socket.close if @ssl_socket
29
- @tcp_socket.close if @tcp_socket
30
- rescue IOError
31
- end
32
- end
33
-
34
- def read(num_bytes)
35
- @ssl_socket.read(num_bytes)
36
- end
37
-
38
- def select(timeout)
39
- IO.select([@ssl_socket], nil, nil, timeout)
40
- end
41
-
42
- def write(data)
43
- reconnect_idle if idle_period_exceeded?
44
-
45
- retry_count = 0
46
-
47
- begin
48
- write_data(data)
49
- rescue Errno::EPIPE, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError => e
50
- retry_count += 1;
51
-
52
- if retry_count == 1
53
- Rapns::Daemon.logger.error("[#{@name}] Lost connection to #{@host}:#{@port} (#{e.class.name}), reconnecting...")
54
- end
55
-
56
- if retry_count <= 3
57
- reconnect
58
- sleep 1
59
- retry
60
- else
61
- raise ConnectionError, "#{@name} tried #{retry_count-1} times to reconnect but failed (#{e.class.name})."
62
- end
63
- end
64
- end
65
-
66
- def reconnect
67
- close
68
- @tcp_socket, @ssl_socket = connect_socket
69
- end
70
-
71
- protected
72
-
73
- def reconnect_idle
74
- Rapns::Daemon.logger.info("[#{@name}] Idle period exceeded, reconnecting...")
75
- reconnect
76
- end
77
-
78
- def idle_period_exceeded?
79
- Time.now - last_write > self.class.idle_period
80
- end
81
-
82
- def write_data(data)
83
- @ssl_socket.write(data)
84
- @ssl_socket.flush
85
- written
86
- end
87
-
88
- def written
89
- self.last_write = Time.now
90
- end
91
-
92
- def setup_ssl_context
93
- ssl_context = OpenSSL::SSL::SSLContext.new
94
- ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password)
95
- ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate)
96
- ssl_context
97
- end
98
-
99
- def connect_socket
100
- tcp_socket = TCPSocket.new(@host, @port)
101
- tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)
102
- tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
103
- ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
104
- ssl_socket.sync = true
105
- ssl_socket.connect
106
- Rapns::Daemon.logger.info("[#{@name}] Connected to #{@host}:#{@port}")
107
- [tcp_socket, ssl_socket]
108
- rescue StandardError => e
109
- Rapns::Daemon.logger.error("[#{@name}] Error connecting to #{@host}:#{@port} : #{e}")
110
- raise
111
- end
112
- end
113
- end
114
- end
@@ -1,18 +0,0 @@
1
- module Rapns
2
- module Daemon
3
- class DeliveryHandlerPool
4
- def initialize
5
- @handlers = []
6
- end
7
-
8
- def <<(handler)
9
- @handlers << handler
10
- handler.start
11
- end
12
-
13
- def drain
14
- @handlers.pop.stop while !@handlers.empty?
15
- end
16
- end
17
- end
18
- end
@@ -1,14 +0,0 @@
1
- module Rapns
2
- class DisconnectionError < StandardError
3
- attr_reader :code, :description
4
-
5
- def initialize
6
- @code = nil
7
- @description = "APNs disconnected without returning an error."
8
- end
9
-
10
- def message
11
- "The APNs disconnected without returning an error. This may indicate you are using an invalid certificate for the host."
12
- end
13
- end
14
- end
@@ -1,82 +0,0 @@
1
- module Rapns
2
- module Daemon
3
- class FeedbackReceiver
4
- include InterruptibleSleep
5
- include DatabaseReconnectable
6
-
7
- FEEDBACK_TUPLE_BYTES = 38
8
-
9
- def initialize(name, host, port, poll, certificate, password)
10
- @name = name
11
- @host = host
12
- @port = port
13
- @poll = poll
14
- @certificate = certificate
15
- @password = password
16
- end
17
-
18
- def start
19
- @thread = Thread.new do
20
- loop do
21
- begin
22
- break if @stop
23
- check_for_feedback
24
- rescue OpenSSL::SSL::SSLError
25
- # stop the thread if there is an SSL error. Other errors might be recoverable,
26
- # and retrying later might make sense (for example, a network outage)
27
- @stop = true
28
- break
29
- rescue
30
- # error will be logged in check_for_feedback
31
- end
32
- interruptible_sleep @poll
33
- end
34
- end
35
- end
36
-
37
- def stop
38
- @stop = true
39
- interrupt_sleep
40
- @thread.join if @thread
41
- end
42
-
43
- def check_for_feedback
44
- connection = nil
45
- begin
46
- connection = Connection.new("FeedbackReceiver:#{@name}", @host, @port, @certificate, @password)
47
- connection.connect
48
-
49
- while tuple = connection.read(FEEDBACK_TUPLE_BYTES)
50
- timestamp, device_token = parse_tuple(tuple)
51
- create_feedback(timestamp, device_token)
52
- end
53
- rescue StandardError => e
54
- Rapns::Daemon.logger.error(e)
55
- raise
56
- ensure
57
- connection.close if connection
58
- end
59
- end
60
-
61
- protected
62
-
63
- def parse_tuple(tuple)
64
- failed_at, _, device_token = tuple.unpack("N1n1H*")
65
- [Time.at(failed_at).utc, device_token]
66
- end
67
-
68
- def create_feedback(failed_at, device_token)
69
- formatted_failed_at = failed_at.strftime("%Y-%m-%d %H:%M:%S UTC")
70
- with_database_reconnect_and_retry do
71
- Rapns::Daemon.logger.info("[FeedbackReceiver:#{@name}] Delivery failed at #{formatted_failed_at} for #{device_token}")
72
- feedback = Rapns::Feedback.create!(:failed_at => failed_at, :device_token => device_token, :app => @name)
73
- begin
74
- Rapns.configuration.feedback_callback.call(feedback) if Rapns.configuration.feedback_callback
75
- rescue Exception => e
76
- Rapns::Daemon.logger.error(e)
77
- end
78
- end
79
- end
80
- end
81
- end
82
- end
@@ -1,10 +0,0 @@
1
- module Rapns
2
- class DeviceTokenFormatValidator < ActiveModel::Validator
3
-
4
- def validate(record)
5
- if record.device_token !~ /^[a-z0-9]{64}$/
6
- record.errors[:device_token] << "is invalid"
7
- end
8
- end
9
- end
10
- end
@@ -1,12 +0,0 @@
1
- module Rapns
2
- class Feedback < ActiveRecord::Base
3
- self.table_name = 'rapns_feedback'
4
-
5
- attr_accessible :device_token, :failed_at, :app
6
-
7
- validates :device_token, :presence => true
8
- validates :failed_at, :presence => true
9
-
10
- validates_with Rapns::DeviceTokenFormatValidator
11
- end
12
- end
@@ -1,193 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Rapns::Daemon::AppRunner do
4
- let(:app) { stub(:key => 'app', :certificate => 'cert', :password => '', :connections => 1) }
5
- let(:queue) { stub(:notifications_processed? => true, :push => nil) }
6
- let(:receiver) { stub(:start => nil, :stop => nil) }
7
- let(:handler) { stub(:start => nil, :stop => nil) }
8
- let(:push_config) { stub(:host => 'gateway.push.apple.com', :port => 2195) }
9
- let(:feedback_config) { stub(:host => 'feedback.push.apple.com', :port => 2196, :poll => 60) }
10
- let(:runner) { Rapns::Daemon::AppRunner.new(app, push_config.host, push_config.port,
11
- feedback_config.host, feedback_config.port, feedback_config.poll) }
12
-
13
- before do
14
- Rapns::Daemon::DeliveryQueue.stub(:new => queue)
15
- Rapns::Daemon::FeedbackReceiver.stub(:new => receiver)
16
- Rapns::Daemon::DeliveryHandler.stub(:new => handler)
17
- end
18
-
19
- after { Rapns::Daemon::AppRunner.all.clear }
20
-
21
- describe 'start' do
22
- it 'starts a feedback receiver' do
23
- Rapns::Daemon::FeedbackReceiver.should_receive(:new).with(app.key, feedback_config.host, feedback_config.port, feedback_config.poll, app.certificate, app.password)
24
- receiver.should_receive(:start)
25
- runner.start
26
- end
27
-
28
- it 'starts a delivery handler for each connection' do
29
- Rapns::Daemon::DeliveryHandler.should_receive(:new).with(queue, app.key, push_config.host,
30
- push_config.port, app.certificate, app.password)
31
- handler.should_receive(:start)
32
- runner.start
33
- end
34
- end
35
-
36
- describe 'deliver' do
37
- let(:notification) { stub }
38
-
39
- it 'enqueues the notification' do
40
- queue.should_receive(:push).with(notification)
41
- runner.deliver(notification)
42
- end
43
- end
44
-
45
- describe 'stop' do
46
- before { runner.start }
47
-
48
- it 'stops the delivery handlers' do
49
- handler.should_receive(:stop)
50
- runner.stop
51
- end
52
-
53
- it 'stops the feedback receiver' do
54
- receiver.should_receive(:stop)
55
- runner.stop
56
- end
57
- end
58
-
59
- describe 'ready?' do
60
- it 'is ready if all notifications have been processed' do
61
- queue.stub(:notifications_processed? => true)
62
- runner.ready?.should be_true
63
- end
64
-
65
- it 'is not ready if not all notifications have been processed' do
66
- queue.stub(:notifications_processed? => false)
67
- runner.ready?.should be_false
68
- end
69
- end
70
-
71
- describe 'sync' do
72
- let(:new_app) { stub(:key => 'app', :certificate => 'cert', :password => '', :connections => 1) }
73
- before { runner.start }
74
-
75
- it 'reduces the number of handlers if needed' do
76
- handler.should_receive(:stop)
77
- new_app.stub(:connections => app.connections - 1)
78
- runner.sync(new_app)
79
- end
80
-
81
- it 'increases the number of handlers if needed' do
82
- new_handler = stub
83
- Rapns::Daemon::DeliveryHandler.should_receive(:new).and_return(new_handler)
84
- new_handler.should_receive(:start)
85
- new_app.stub(:connections => app.connections + 1)
86
- runner.sync(new_app)
87
- end
88
- end
89
- end
90
-
91
- describe Rapns::Daemon::AppRunner, 'stop' do
92
- let(:runner) { stub }
93
- before { Rapns::Daemon::AppRunner.all['app'] = runner }
94
- after { Rapns::Daemon::AppRunner.all.clear }
95
-
96
- it 'stops all runners' do
97
- runner.should_receive(:stop)
98
- Rapns::Daemon::AppRunner.stop
99
- end
100
- end
101
-
102
- describe Rapns::Daemon::AppRunner, 'deliver' do
103
- let(:runner) { stub }
104
- let(:notification) { stub(:app => 'app') }
105
- let(:logger) { stub(:error => nil) }
106
-
107
- before do
108
- Rapns::Daemon.stub(:logger => logger)
109
- Rapns::Daemon::AppRunner.all['app'] = runner
110
- end
111
-
112
- after { Rapns::Daemon::AppRunner.all.clear }
113
-
114
- it 'delivers the notification' do
115
- runner.should_receive(:deliver).with(notification)
116
- Rapns::Daemon::AppRunner.deliver(notification)
117
- end
118
-
119
- it 'logs an error if there is no runner to deliver the notification' do
120
- notification.stub(:app => 'unknonw', :id => 123)
121
- logger.should_receive(:error).with("No such app '#{notification.app}' for notification #{notification.id}.")
122
- Rapns::Daemon::AppRunner.deliver(notification)
123
- end
124
- end
125
-
126
- describe Rapns::Daemon::AppRunner, 'ready' do
127
- let(:runner1) { stub(:ready? => true) }
128
- let(:runner2) { stub(:ready? => false) }
129
-
130
- before do
131
- Rapns::Daemon::AppRunner.all['app1'] = runner1
132
- Rapns::Daemon::AppRunner.all['app2'] = runner2
133
- end
134
-
135
- after { Rapns::Daemon::AppRunner.all.clear }
136
-
137
- it 'returns apps that are ready for more notifications' do
138
- Rapns::Daemon::AppRunner.ready.should == ['app1']
139
- end
140
- end
141
-
142
- describe Rapns::Daemon::AppRunner, 'sync' do
143
- let(:app) { stub(:key => 'app', :environment => 'development') }
144
- let(:new_app) { stub(:key => 'new_app', :environment => 'development') }
145
- let(:runner) { stub(:sync => nil, :stop => nil) }
146
- let(:new_runner) { stub }
147
- let(:logger) { stub(:error => nil) }
148
- let(:config) { stub(:feedback_poll => 60) }
149
-
150
- before do
151
- Rapns::Daemon.stub(:config => config, :logger => logger)
152
- Rapns::Daemon::AppRunner.all['app'] = runner
153
- Rapns::App.stub(:all => [app])
154
- end
155
-
156
- after { Rapns::Daemon::AppRunner.all.clear }
157
-
158
- it 'loads all apps' do
159
- Rapns::App.should_receive(:all)
160
- Rapns::Daemon::AppRunner.sync
161
- end
162
-
163
- it 'instructs existing runners to sync' do
164
- runner.should_receive(:sync).with(app)
165
- Rapns::Daemon::AppRunner.sync
166
- end
167
-
168
- it 'starts a runner for a new app with a production certificate' do
169
- new_app.stub(:environment => 'production')
170
- Rapns::App.stub(:all => [new_app])
171
- new_runner = stub
172
- Rapns::Daemon::AppRunner.should_receive(:new).with(new_app, 'gateway.push.apple.com', 2195,
173
- 'feedback.push.apple.com', 2196, config.feedback_poll).and_return(new_runner)
174
- new_runner.should_receive(:start)
175
- Rapns::Daemon::AppRunner.sync
176
- end
177
-
178
- it 'starts a runner for a new app with a development certificate' do
179
- new_app.stub(:environment => 'development')
180
- Rapns::App.stub(:all => [new_app])
181
- new_runner = stub
182
- Rapns::Daemon::AppRunner.should_receive(:new).with(new_app, 'gateway.sandbox.push.apple.com', 2195,
183
- 'feedback.sandbox.push.apple.com', 2196, config.feedback_poll).and_return(new_runner)
184
- new_runner.should_receive(:start)
185
- Rapns::Daemon::AppRunner.sync
186
- end
187
-
188
- it 'deletes old apps' do
189
- Rapns::App.stub(:all => [])
190
- runner.should_receive(:stop)
191
- Rapns::Daemon::AppRunner.sync
192
- end
193
- end
@@ -1,17 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Rapns::Daemon::DeliveryHandlerPool do
4
- let(:pool) { Rapns::Daemon::DeliveryHandlerPool.new }
5
- let(:handler) { stub(:start => nil, :stop => nil) }
6
-
7
- it 'starts the handler when added to the pool' do
8
- handler.should_receive(:start)
9
- pool << handler
10
- end
11
-
12
- it 'stops each handler when drained' do
13
- pool << handler
14
- handler.should_receive(:stop)
15
- pool.drain
16
- end
17
- end
@@ -1,206 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Rapns::Daemon::DeliveryHandler do
4
- let(:queue) { Rapns::Daemon::DeliveryQueue.new }
5
- let(:name) { 'my_app:0' }
6
- let(:host) { 'localhost' }
7
- let(:port) { 2195 }
8
- let(:certificate) { stub }
9
- let(:password) { stub }
10
- let(:delivery_handler) { Rapns::Daemon::DeliveryHandler.new(queue, name, host, port, certificate, password) }
11
- let(:connection) { stub(:select => false, :write => nil, :reconnect => nil, :close => nil, :connect => nil) }
12
- let(:logger) { stub(:error => nil, :info => nil) }
13
- let(:notification) { stub.as_null_object }
14
- let(:config) { stub(:check_for_errors => true) }
15
- let(:delivery_queues) { [] }
16
-
17
- before do
18
- Rapns::Daemon::Connection.stub(:new => connection)
19
- Rapns::Daemon.stub(:delivery_queues => delivery_queues, :logger => logger, :config => config)
20
- queue.push(notification)
21
- end
22
-
23
- it "instantiates a new connection" do
24
- Rapns::Daemon::Connection.should_receive(:new).with("DeliveryHandler:#{name}", host, port, certificate, password)
25
- delivery_handler
26
- end
27
-
28
- it "connects the socket when started" do
29
- connection.should_receive(:connect)
30
- delivery_handler.start
31
- delivery_handler.stop
32
- end
33
-
34
- it "instructs the queue to wakeup the thread when told to stop" do
35
- thread = stub
36
- Thread.stub(:new => thread)
37
- queue.should_receive(:wakeup).with(thread)
38
- delivery_handler.start
39
- delivery_handler.stop
40
- end
41
-
42
- it "sends the binary version of the notification" do
43
- notification.stub(:to_binary => "hi mom")
44
- connection.should_receive(:write).with("hi mom")
45
- delivery_handler.send(:handle_next_notification)
46
- end
47
-
48
- it "logs the notification delivery" do
49
- notification.stub(:id => 666, :device_token => 'abc123')
50
- logger.should_receive(:info).with("[DeliveryHandler:my_app:0] 666 sent to abc123")
51
- delivery_handler.send(:handle_next_notification)
52
- end
53
-
54
- it "marks the notification as delivered" do
55
- notification.should_receive(:delivered=).with(true)
56
- delivery_handler.send(:handle_next_notification)
57
- end
58
-
59
- it "sets the time the notification was delivered" do
60
- now = Time.now
61
- Time.stub(:now).and_return(now)
62
- notification.should_receive(:delivered_at=).with(now)
63
- delivery_handler.send(:handle_next_notification)
64
- end
65
-
66
- it "does not trigger validations when saving the notification" do
67
- notification.should_receive(:save!).with(:validate => false)
68
- delivery_handler.send(:handle_next_notification)
69
- end
70
-
71
- it "updates notification with the ability to reconnect the database" do
72
- delivery_handler.should_receive(:with_database_reconnect_and_retry)
73
- delivery_handler.send(:handle_next_notification)
74
- end
75
-
76
- it "logs if an error is raised when updating the notification" do
77
- e = StandardError.new("bork!")
78
- notification.stub(:save!).and_raise(e)
79
- Rapns::Daemon.logger.should_receive(:error).with(e)
80
- delivery_handler.send(:handle_next_notification)
81
- end
82
-
83
- it "notifies the delivery queue the notification has been processed" do
84
- queue.should_receive(:notification_processed)
85
- delivery_handler.send(:handle_next_notification)
86
- end
87
-
88
- it 'does not check for errors if check_for_errors config option is false' do
89
- config.stub(:check_for_errors => false)
90
- delivery_handler.should_not_receive(:check_for_error)
91
- delivery_handler.send(:handle_next_notification)
92
- end
93
-
94
- describe "when being stopped" do
95
- before { queue.pop }
96
-
97
- it "closes the connection when a DeliveryQueue::WakeupError is raised" do
98
- connection.should_receive(:close)
99
- queue.stub(:pop).and_raise(Rapns::Daemon::DeliveryQueue::WakeupError)
100
- delivery_handler.send(:handle_next_notification)
101
- end
102
-
103
- it "does not attempt to deliver a notification when a DeliveryQueue::::WakeupError is raised" do
104
- queue.stub(:pop).and_raise(Rapns::Daemon::DeliveryQueue::WakeupError)
105
- delivery_handler.should_not_receive(:deliver)
106
- delivery_handler.send(:handle_next_notification)
107
- end
108
- end
109
-
110
- describe "when delivery fails" do
111
- before { connection.stub(:select => true, :read => [8, 4, 69].pack("ccN")) }
112
-
113
- it "updates notification with the ability to reconnect the database" do
114
- delivery_handler.should_receive(:with_database_reconnect_and_retry)
115
- delivery_handler.send(:handle_next_notification)
116
- end
117
-
118
- it "sets the notification as not delivered" do
119
- notification.should_receive(:delivered=).with(false)
120
- delivery_handler.send(:handle_next_notification)
121
- end
122
-
123
- it "sets the notification delivered_at timestamp to nil" do
124
- notification.should_receive(:delivered_at=).with(nil)
125
- delivery_handler.send(:handle_next_notification)
126
- end
127
-
128
- it "sets the notification as failed" do
129
- notification.should_receive(:failed=).with(true)
130
- delivery_handler.send(:handle_next_notification)
131
- end
132
-
133
- it "sets the notification failed_at timestamp" do
134
- now = Time.now
135
- Time.stub(:now).and_return(now)
136
- notification.should_receive(:failed_at=).with(now)
137
- delivery_handler.send(:handle_next_notification)
138
- end
139
-
140
- it "sets the notification error code" do
141
- notification.should_receive(:error_code=).with(4)
142
- delivery_handler.send(:handle_next_notification)
143
- end
144
-
145
- it "logs the delivery error" do
146
- error = Rapns::DeliveryError.new(4, 12, "Missing payload")
147
- Rapns::DeliveryError.stub(:new => error)
148
- logger.should_receive(:error).with(error)
149
- delivery_handler.send(:handle_next_notification)
150
- end
151
-
152
- it "sets the notification error description" do
153
- notification.should_receive(:error_description=).with("Missing payload")
154
- delivery_handler.send(:handle_next_notification)
155
- end
156
-
157
- it "skips validation when saving the notification" do
158
- notification.should_receive(:save!).with(:validate => false)
159
- delivery_handler.send(:handle_next_notification)
160
- end
161
-
162
- it "reads 6 bytes from the socket" do
163
- connection.should_receive(:read).with(6).and_return(nil)
164
- delivery_handler.send(:handle_next_notification)
165
- end
166
-
167
- it "does not attempt to read from the socket if the socket was not selected for reading after the timeout" do
168
- connection.stub(:select => nil)
169
- connection.should_not_receive(:read)
170
- delivery_handler.send(:handle_next_notification)
171
- end
172
-
173
- it "reconnects the socket" do
174
- connection.should_receive(:reconnect)
175
- delivery_handler.send(:handle_next_notification)
176
- end
177
-
178
- it "logs that the connection is being reconnected" do
179
- Rapns::Daemon.logger.should_receive(:error).with("[DeliveryHandler:my_app:0] Error received, reconnecting...")
180
- delivery_handler.send(:handle_next_notification)
181
- end
182
-
183
- context "when the APNs disconnects without returning an error" do
184
- before do
185
- connection.stub(:read => nil)
186
- end
187
-
188
- it 'raises a DisconnectError error if the connection is closed without an error being returned' do
189
- error = Rapns::DisconnectionError.new
190
- Rapns::DisconnectionError.should_receive(:new).and_return(error)
191
- Rapns::Daemon.logger.should_receive(:error).with(error)
192
- delivery_handler.send(:handle_next_notification)
193
- end
194
-
195
- it 'does not set the error code on the notification' do
196
- notification.should_receive(:error_code=).with(nil)
197
- delivery_handler.send(:handle_next_notification)
198
- end
199
-
200
- it 'sets the error descriptipon on the notification' do
201
- notification.should_receive(:error_description=).with("APNs disconnected without returning an error.")
202
- delivery_handler.send(:handle_next_notification)
203
- end
204
- end
205
- end
206
- end
@@ -1,12 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Rapns::Feedback do
4
- it { should validate_presence_of(:device_token) }
5
- it { should validate_presence_of(:failed_at) }
6
-
7
- it "should validate the format of the device_token" do
8
- feedback = Rapns::Feedback.new(:device_token => "{$%^&*()}")
9
- feedback.valid?.should be_false
10
- feedback.errors[:device_token].include?("is invalid").should be_true
11
- end
12
- end