rpush 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +11 -7
  4. data/lib/generators/templates/rpush.rb +8 -2
  5. data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
  6. data/lib/rpush/cli.rb +61 -27
  7. data/lib/rpush/client/active_model.rb +3 -0
  8. data/lib/rpush/client/active_model/apns/notification.rb +1 -1
  9. data/lib/rpush/client/active_model/wns/app.rb +23 -0
  10. data/lib/rpush/client/active_model/wns/notification.rb +28 -0
  11. data/lib/rpush/client/active_model/wpns/notification.rb +11 -6
  12. data/lib/rpush/client/active_record.rb +3 -0
  13. data/lib/rpush/client/active_record/wns/app.rb +11 -0
  14. data/lib/rpush/client/active_record/wns/notification.rb +11 -0
  15. data/lib/rpush/client/mongoid.rb +3 -0
  16. data/lib/rpush/client/mongoid/apns/feedback.rb +3 -0
  17. data/lib/rpush/client/mongoid/notification.rb +6 -0
  18. data/lib/rpush/client/mongoid/wns/app.rb +14 -0
  19. data/lib/rpush/client/mongoid/wns/notification.rb +11 -0
  20. data/lib/rpush/client/redis.rb +3 -0
  21. data/lib/rpush/client/redis/wns/app.rb +14 -0
  22. data/lib/rpush/client/redis/wns/notification.rb +11 -0
  23. data/lib/rpush/configuration.rb +3 -7
  24. data/lib/rpush/daemon.rb +9 -0
  25. data/lib/rpush/daemon/apns/feedback_receiver.rb +5 -0
  26. data/lib/rpush/daemon/app_runner.rb +4 -5
  27. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +47 -12
  28. data/lib/rpush/daemon/dispatcher_loop.rb +5 -0
  29. data/lib/rpush/daemon/feeder.rb +11 -0
  30. data/lib/rpush/daemon/interruptible_sleep.rb +8 -3
  31. data/lib/rpush/daemon/loggable.rb +4 -0
  32. data/lib/rpush/daemon/rpc.rb +9 -0
  33. data/lib/rpush/daemon/rpc/client.rb +27 -0
  34. data/lib/rpush/daemon/rpc/server.rb +82 -0
  35. data/lib/rpush/daemon/signal_handler.rb +7 -0
  36. data/lib/rpush/daemon/store/active_record.rb +17 -3
  37. data/lib/rpush/daemon/store/mongoid.rb +2 -2
  38. data/lib/rpush/daemon/store/redis.rb +2 -2
  39. data/lib/rpush/daemon/tcp_connection.rb +2 -2
  40. data/lib/rpush/daemon/wns.rb +9 -0
  41. data/lib/rpush/daemon/wns/delivery.rb +206 -0
  42. data/lib/rpush/embed.rb +15 -13
  43. data/lib/rpush/logger.rb +4 -0
  44. data/lib/rpush/plugin.rb +1 -1
  45. data/lib/rpush/push.rb +2 -11
  46. data/lib/rpush/reflection_collection.rb +15 -17
  47. data/lib/rpush/reflection_public_methods.rb +6 -4
  48. data/lib/rpush/version.rb +1 -1
  49. data/spec/functional/apns_spec.rb +1 -11
  50. data/spec/functional/cli_spec.rb +35 -0
  51. data/spec/functional_spec_helper.rb +11 -1
  52. data/spec/spec_helper.rb +4 -3
  53. data/spec/support/active_record_setup.rb +1 -1
  54. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
  55. data/spec/unit/configuration_spec.rb +0 -7
  56. data/spec/unit/daemon/adm/delivery_spec.rb +2 -2
  57. data/spec/unit/daemon/app_runner_spec.rb +2 -3
  58. data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
  59. data/spec/unit/daemon/tcp_connection_spec.rb +1 -1
  60. data/spec/unit/daemon/wns/delivery_spec.rb +171 -0
  61. data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
  62. data/spec/unit/daemon_spec.rb +2 -0
  63. data/spec/unit/embed_spec.rb +4 -11
  64. data/spec/unit/logger_spec.rb +2 -2
  65. data/spec/unit/push_spec.rb +0 -7
  66. data/spec/unit_spec_helper.rb +1 -1
  67. metadata +20 -3
@@ -1,9 +1,11 @@
1
1
  module Rpush
2
- def self.reflect
3
- yield reflection_stack[0] if block_given?
2
+ @reflection_stack ||= [ReflectionCollection.new]
3
+
4
+ class << self
5
+ attr_reader :reflection_stack
4
6
  end
5
7
 
6
- def self.reflection_stack
7
- @reflection_stack ||= [ReflectionCollection.new]
8
+ def self.reflect
9
+ yield reflection_stack[0] if block_given?
8
10
  end
9
11
  end
data/lib/rpush/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rpush
2
- VERSION = '2.4.0'
2
+ VERSION = '2.5.0'
3
3
  end
@@ -11,7 +11,7 @@ describe 'APNs' do
11
11
 
12
12
  before do
13
13
  Rpush.config.push_poll = 0.5
14
- stub_tcp_connection
14
+ stub_tcp_connection(tcp_socket, ssl_socket, io_double)
15
15
  end
16
16
 
17
17
  def create_app
@@ -32,12 +32,6 @@ describe 'APNs' do
32
32
  notification
33
33
  end
34
34
 
35
- def stub_tcp_connection
36
- allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(connect_socket: [tcp_socket, ssl_socket])
37
- allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(setup_ssl_context: double.as_null_object)
38
- stub_const('Rpush::Daemon::TcpConnection::IO', io_double)
39
- end
40
-
41
35
  def wait
42
36
  sleep 0.1
43
37
  end
@@ -71,10 +65,6 @@ describe 'APNs' do
71
65
  end
72
66
  end
73
67
 
74
- def timeout(&blk)
75
- Timeout.timeout(10, &blk)
76
- end
77
-
78
68
  it 'delivers a notification successfully' do
79
69
  notification = create_notification
80
70
  expect do
@@ -0,0 +1,35 @@
1
+ require 'functional_spec_helper'
2
+
3
+ describe Rpush::CLI do
4
+ def create_app
5
+ app = Rpush::Apns::App.new
6
+ app.certificate = TEST_CERT
7
+ app.name = 'test'
8
+ app.environment = 'sandbox'
9
+ app.save!
10
+ app
11
+ end
12
+
13
+ describe 'status' do
14
+ let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) }
15
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) }
16
+ let(:io_double) { double(select: nil) }
17
+
18
+ before do
19
+ create_app
20
+ stub_tcp_connection(tcp_socket, ssl_socket, io_double)
21
+ Rpush.embed
22
+
23
+ timeout do
24
+ Thread.pass until File.exist?(Rpush::Daemon::Rpc.socket_path)
25
+ end
26
+ end
27
+
28
+ after { timeout { Rpush.shutdown } }
29
+
30
+ it 'prints the status' do
31
+ expect(subject).to receive(:puts).with(/app_runners:/)
32
+ subject.status
33
+ end
34
+ end
35
+ end
@@ -4,7 +4,17 @@ require 'database_cleaner'
4
4
  DatabaseCleaner.strategy = :truncation
5
5
 
6
6
  def functional_example?(metadata)
7
- metadata[:file_path] =~ /spec\/functional/
7
+ metadata[:file_path] =~ %r{/spec/functional/}
8
+ end
9
+
10
+ def timeout(&blk)
11
+ Timeout.timeout(10, &blk)
12
+ end
13
+
14
+ def stub_tcp_connection(tcp_socket, ssl_socket, io_double)
15
+ allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(connect_socket: [tcp_socket, ssl_socket])
16
+ allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(setup_ssl_context: double.as_null_object)
17
+ stub_const('Rpush::Daemon::TcpConnection::IO', io_double)
8
18
  end
9
19
 
10
20
  RSpec.configure do |config|
data/spec/spec_helper.rb CHANGED
@@ -3,9 +3,6 @@ def client
3
3
  (ENV['CLIENT'] || :active_record).to_sym
4
4
  end
5
5
 
6
- require 'bundler/setup'
7
- Bundler.require(:default)
8
-
9
6
  if !ENV['TRAVIS'] || (ENV['TRAVIS'] && ENV['QUALITY'] == 'true')
10
7
  begin
11
8
  require './spec/support/simplecov_helper'
@@ -16,6 +13,9 @@ if !ENV['TRAVIS'] || (ENV['TRAVIS'] && ENV['QUALITY'] == 'true')
16
13
  end
17
14
  end
18
15
 
16
+ require 'timecop'
17
+ require 'activerecord-jdbc-adapter' if defined? JRUBY_VERSION
18
+
19
19
  require 'rpush'
20
20
  require 'rpush/daemon'
21
21
  require 'rpush/client/redis'
@@ -42,6 +42,7 @@ RPUSH_ROOT = '/tmp/rails_root'
42
42
 
43
43
  Rpush.configure do |config|
44
44
  config.client = client
45
+ config.log_level = ::Logger::Severity::DEBUG
45
46
  end
46
47
 
47
48
  RPUSH_CLIENT = Rpush.config.client
@@ -33,7 +33,7 @@ require 'generators/templates/rpush_2_1_0_updates'
33
33
  migrations = [AddRpush, Rpush200Updates, Rpush210Updates]
34
34
 
35
35
  unless ENV['TRAVIS']
36
- migrations.reverse.each do |m|
36
+ migrations.reverse_each do |m|
37
37
  begin
38
38
  m.down
39
39
  rescue ActiveRecord::StatementInvalid => e
@@ -217,7 +217,7 @@ describe Rpush::Client::ActiveRecord::Apns::Notification, "bug #31" do
217
217
 
218
218
  it 'does confuse a JSON looking string as JSON if the alert_is_json attribute is not present' do
219
219
  notification = Rpush::Client::ActiveRecord::Apns::Notification.new
220
- allow(notification).to receive_messages(:has_attribute? => false)
220
+ allow(notification).to receive_messages(has_attribute?: false)
221
221
  notification.alert = "{\"one\":2}"
222
222
  expect(notification.alert).to eq('one' => 2)
223
223
  end
@@ -43,11 +43,4 @@ describe Rpush::Configuration do
43
43
  Rpush.config.redis_options = { hi: :mom }
44
44
  expect(Modis.redis_options).to eq(hi: :mom)
45
45
  end
46
-
47
- it 'deprecates feedback_poll=' do
48
- expect(Rpush::Deprecation).to receive(:warn).with(/feedback_poll= is deprecated/)
49
- expect do
50
- Rpush.config.feedback_poll = 123
51
- end.to change { Rpush.config.apns.feedback_receiver.frequency }.to(123)
52
- end
53
46
  end
@@ -78,14 +78,14 @@ describe Rpush::Daemon::Adm::Delivery do
78
78
  it 'logs that the notification was not delivered' do
79
79
  allow(response).to receive_messages(body: JSON.dump('reason' => 'InvalidRegistrationId'))
80
80
  expect(logger).to receive(:warn).with("[MyApp] bad_request: xyz (InvalidRegistrationId)")
81
- expect { perform }.to raise_error
81
+ expect { perform }.to raise_error(Rpush::DeliveryError)
82
82
  end
83
83
 
84
84
  it 'reflects' do
85
85
  allow(response).to receive_messages(body: JSON.dump('registrationID' => 'canonical123', 'reason' => 'Unregistered'))
86
86
  allow(notification).to receive_messages(registration_ids: ['1'])
87
87
  expect(delivery).to receive(:reflect).with(:adm_failed_to_recipient, notification, '1', 'Unregistered')
88
- expect { perform }.to raise_error
88
+ expect { perform }.to raise_error(Rpush::DeliveryError)
89
89
  end
90
90
  end
91
91
 
@@ -94,9 +94,8 @@ describe Rpush::Daemon::AppRunner, 'debug' do
94
94
 
95
95
  after { Rpush::Daemon::AppRunner.stop_app(app.id) }
96
96
 
97
- it 'prints debug app states to the log' do
98
- expect(Rpush.logger).to receive(:info).with(kind_of(String))
99
- Rpush::Daemon::AppRunner.debug
97
+ it 'returns the app runner status' do
98
+ expect(Rpush::Daemon::AppRunner.status.key?(:app_runners)).to eq(true)
100
99
  end
101
100
  end
102
101
 
@@ -16,7 +16,7 @@ describe Rpush::Daemon::Gcm::Delivery do
16
16
  end
17
17
 
18
18
  def perform_with_rescue
19
- expect { perform }.to raise_error
19
+ expect { perform }.to raise_error(StandardError)
20
20
  end
21
21
 
22
22
  before do
@@ -126,7 +126,7 @@ describe Rpush::Daemon::TcpConnection do
126
126
  end
127
127
 
128
128
  it 'logs that the certificate has been revoked' do
129
- expect(logger).to receive(:warn).with('[Connection 0] Certificate has been revoked.')
129
+ expect(logger).to receive(:error).with('[Connection 0] Certificate has been revoked.')
130
130
  expect { connection.connect }.to raise_error(Rpush::Daemon::TcpConnectionError, 'OpenSSL::SSL::SSLError, certificate revoked')
131
131
  end
132
132
  end
@@ -0,0 +1,171 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Wns::Delivery do
4
+ let(:app) { Rpush::Wns::App.create!(name: "MyApp", client_id: "someclient", client_secret: "somesecret", access_token: "access_token", access_token_expiration: Time.now + (60 * 10)) }
5
+ let(:notification) { Rpush::Wns::Notification.create!(app: app, data: { title: "MyApp", body: "Example notification", param: "/param1" }, uri: "http://some.example/", deliver_after: Time.now) }
6
+ let(:logger) { double(error: nil, info: nil, warn: nil) }
7
+ let(:response) { double(code: 200, header: {}, body: '') }
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, notification_processed: nil) }
11
+ let(:delivery) { Rpush::Daemon::Wns::Delivery.new(app, http, notification, batch) }
12
+ let(:store) { double(create_wpns_notification: double(id: 2), update_app: nil) }
13
+
14
+ def perform
15
+ delivery.perform
16
+ end
17
+
18
+ def perform_with_rescue
19
+ expect { perform }.to raise_error(StandardError)
20
+ end
21
+
22
+ before do
23
+ allow(delivery).to receive_messages(reflect: nil)
24
+ allow(Rpush::Daemon).to receive_messages(store: store)
25
+ allow(Time).to receive_messages(now: now)
26
+ allow(Rpush).to receive_messages(logger: logger)
27
+ end
28
+
29
+ shared_examples_for "an notification with some delivery faliures" do
30
+ let(:new_notification) { Rpush::Wns::Notification.where('id != ?', notification.id).first }
31
+
32
+ before { allow(response).to receive_messages(body: JSON.dump(body)) }
33
+
34
+ it "marks the original notification falied" do
35
+ expect(delivery).to receive(:mark_failed) do |error|
36
+ expect(error.message).to match(error_description)
37
+ end
38
+ perform_with_rescue
39
+ end
40
+
41
+ it "raises a DeliveryError" do
42
+ expect { perform }.to raise_error(Rpush::DeliveryError)
43
+ end
44
+ end
45
+
46
+ describe "an 200 response without an access token" do
47
+ before do
48
+ allow(app).to receive_messages(access_token_expired?: true)
49
+ allow(response).to receive_messages(to_hash: {}, code: 200, body: JSON.dump(access_token: "dummy_access_token", expires_in: 60))
50
+ end
51
+
52
+ it 'set the access token for the app' do
53
+ expect(delivery).to receive(:update_access_token).with("access_token" => "dummy_access_token", "expires_in" => 60)
54
+ expect(store).to receive(:update_app).with app
55
+ perform
56
+ end
57
+ end
58
+
59
+ describe "an 200 response with a valid access token" do
60
+ before do
61
+ allow(response).to receive_messages(code: 200)
62
+ end
63
+
64
+ it "marks the notification as delivered if delivered successfully to all devices" do
65
+ allow(response).to receive_messages(body: JSON.dump("failure" => 0))
66
+ allow(response).to receive_messages(to_hash: { "X-WNS-Status" => ["received"] })
67
+ expect(batch).to receive(:mark_delivered).with(notification)
68
+ perform
69
+ end
70
+
71
+ it "retries the notification when the queue is full" do
72
+ allow(response).to receive_messages(body: JSON.dump("failure" => 0))
73
+ allow(response).to receive_messages(to_hash: { "X-WNS-Status" => ["channelthrottled"] })
74
+ expect(batch).to receive(:mark_retryable).with(notification, Time.now + (60 * 10))
75
+ perform
76
+ end
77
+
78
+ it "marks the notification as failed if the notification is suppressed" do
79
+ allow(response).to receive_messages(body: JSON.dump("faliure" => 0))
80
+ allow(response).to receive_messages(to_hash: { "X-WNS-Status" => ["dropped"], "X-WNS-Error-Description" => "" })
81
+ error = Rpush::DeliveryError.new(200, notification.id, 'Notification was received but suppressed by the service ().')
82
+ expect(delivery).to receive(:mark_failed).with(error)
83
+ perform_with_rescue
84
+ end
85
+ end
86
+
87
+ describe "an 400 response" do
88
+ before { allow(response).to receive_messages(code: 400) }
89
+ it "marks notifications as failed" do
90
+ error = Rpush::DeliveryError.new(400, notification.id, 'One or more headers were specified incorrectly or conflict with another header.')
91
+ expect(delivery).to receive(:mark_failed).with(error)
92
+ perform_with_rescue
93
+ end
94
+ end
95
+
96
+ describe "an 404 response" do
97
+ before { allow(response).to receive_messages(code: 404) }
98
+ it "marks notifications as failed" do
99
+ error = Rpush::DeliveryError.new(404, notification.id, 'The channel URI is not valid or is not recognized by WNS.')
100
+ expect(delivery).to receive(:mark_failed).with(error)
101
+ perform_with_rescue
102
+ end
103
+ end
104
+
105
+ describe "an 405 response" do
106
+ before { allow(response).to receive_messages(code: 405) }
107
+ it "marks notifications as failed" do
108
+ error = Rpush::DeliveryError.new(405, notification.id, 'Invalid method (GET, CREATE); only POST (Windows or Windows Phone) or DELETE (Windows Phone only) is allowed.')
109
+ expect(delivery).to receive(:mark_failed).with(error)
110
+ perform_with_rescue
111
+ end
112
+ end
113
+
114
+ describe "an 406 response" do
115
+ before { allow(response).to receive_messages(code: 406) }
116
+
117
+ it "retries the notification" do
118
+ expect(batch).to receive(:mark_retryable).with(notification, Time.now + (60 * 60))
119
+ perform
120
+ end
121
+
122
+ it "logs a warning that the notification will be retried" do
123
+ notification.retries = 1
124
+ notification.deliver_after = now + 2
125
+ expect(logger).to receive(:warn).with("[MyApp] Per-day throttling limit reached. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
126
+ perform
127
+ end
128
+ end
129
+
130
+ describe "an 412 response" do
131
+ before { allow(response).to receive_messages(code: 412) }
132
+
133
+ it "retries the notification" do
134
+ expect(batch).to receive(:mark_retryable).with(notification, Time.now + (60 * 60))
135
+ perform
136
+ end
137
+
138
+ it "logs a warning that the notification will be retried" do
139
+ notification.retries = 1
140
+ notification.deliver_after = now + 2
141
+ expect(logger).to receive(:warn).with("[MyApp] Device unreachable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
142
+ perform
143
+ end
144
+ end
145
+
146
+ describe "an 503 response" do
147
+ before { allow(response).to receive_messages(code: 503) }
148
+
149
+ it "retries the notification exponentially" do
150
+ expect(delivery).to receive(:mark_retryable_exponential).with(notification)
151
+ perform
152
+ end
153
+
154
+ it 'logs a warning that the notification will be retried.' do
155
+ notification.retries = 1
156
+ notification.deliver_after = now + 2
157
+ expect(logger).to receive(:warn).with("[MyApp] Service Unavailable. Notification #{notification.id} will be retried after 2012-10-14 00:00:02 (retry 1).")
158
+ perform
159
+ end
160
+ end
161
+
162
+ describe 'an un-handled response' do
163
+ before { allow(response).to receive_messages(code: 418) }
164
+
165
+ it 'marks the notification as failed' do
166
+ error = Rpush::DeliveryError.new(418, notification.id, "I'm a Teapot")
167
+ expect(delivery).to receive(:mark_failed).with(error)
168
+ perform_with_rescue
169
+ end
170
+ end
171
+ end
@@ -16,7 +16,7 @@ describe Rpush::Daemon::Wpns::Delivery do
16
16
  end
17
17
 
18
18
  def perform_with_rescue
19
- expect { perform }.to raise_error
19
+ expect { perform }.to raise_error(StandardError)
20
20
  end
21
21
 
22
22
  before do
@@ -13,6 +13,8 @@ describe Rpush::Daemon, "when starting" do
13
13
  allow(Rpush::Daemon::Feeder).to receive(:start)
14
14
  allow(Rpush::Daemon::Synchronizer).to receive(:sync)
15
15
  allow(Rpush::Daemon::AppRunner).to receive(:stop)
16
+ allow(Rpush::Daemon::Rpc::Server).to receive(:start)
17
+ allow(Rpush::Daemon::Rpc::Server).to receive(:stop)
16
18
  allow(Rpush::Daemon).to receive(:exit)
17
19
  allow(Rpush::Daemon).to receive(:puts)
18
20
  allow(Rpush::Daemon::SignalHandler).to receive(:start)
@@ -17,13 +17,6 @@ describe Rpush, 'embed' do
17
17
  expect(Rpush::Daemon).to receive(:start)
18
18
  Rpush.embed
19
19
  end
20
-
21
- it 'overrides the default config options with those given as a hash' do
22
- Rpush::Deprecation.muted do
23
- Rpush.config.push_poll = 4
24
- expect { Rpush.embed(push_poll: 2) }.to change(Rpush.config, :push_poll).to(2)
25
- end
26
- end
27
20
  end
28
21
 
29
22
  describe Rpush, 'shutdown' do
@@ -44,11 +37,11 @@ describe Rpush, 'sync' do
44
37
  end
45
38
  end
46
39
 
47
- describe Rpush, 'debug' do
40
+ describe Rpush, 'status' do
48
41
  before { Rpush.config.embedded = true }
49
42
 
50
- it 'debugs the AppRunner' do
51
- expect(Rpush::Daemon::AppRunner).to receive(:debug)
52
- Rpush.debug
43
+ it 'returns the AppRunner status' do
44
+ expect(Rpush::Daemon::AppRunner).to receive_messages(status: { status: true })
45
+ expect(Rpush.status).to eq(status: true)
53
46
  end
54
47
  end