rpush 2.4.0 → 2.5.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 (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