rapns 3.0.0-java
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.
- data/CHANGELOG.md +31 -0
- data/LICENSE +7 -0
- data/README.md +138 -0
- data/bin/rapns +41 -0
- data/config/database.yml +39 -0
- data/lib/generators/rapns_generator.rb +34 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_gcm.rb +94 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +15 -0
- data/lib/generators/templates/create_rapns_notifications.rb +26 -0
- data/lib/generators/templates/rapns.rb +39 -0
- data/lib/rapns/apns/app.rb +8 -0
- data/lib/rapns/apns/binary_notification_validator.rb +12 -0
- data/lib/rapns/apns/device_token_format_validator.rb +12 -0
- data/lib/rapns/apns/feedback.rb +14 -0
- data/lib/rapns/apns/notification.rb +86 -0
- data/lib/rapns/apns/required_fields_validator.rb +14 -0
- data/lib/rapns/app.rb +29 -0
- data/lib/rapns/configuration.rb +46 -0
- data/lib/rapns/daemon/apns/app_runner.rb +36 -0
- data/lib/rapns/daemon/apns/connection.rb +113 -0
- data/lib/rapns/daemon/apns/delivery.rb +63 -0
- data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
- data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
- data/lib/rapns/daemon/app_runner.rb +135 -0
- data/lib/rapns/daemon/database_reconnectable.rb +57 -0
- data/lib/rapns/daemon/delivery.rb +43 -0
- data/lib/rapns/daemon/delivery_error.rb +19 -0
- data/lib/rapns/daemon/delivery_handler.rb +46 -0
- data/lib/rapns/daemon/delivery_queue.rb +42 -0
- data/lib/rapns/daemon/delivery_queue_18.rb +44 -0
- data/lib/rapns/daemon/delivery_queue_19.rb +42 -0
- data/lib/rapns/daemon/feeder.rb +37 -0
- data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
- data/lib/rapns/daemon/gcm/delivery.rb +206 -0
- data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
- data/lib/rapns/daemon/interruptible_sleep.rb +18 -0
- data/lib/rapns/daemon/logger.rb +68 -0
- data/lib/rapns/daemon.rb +136 -0
- data/lib/rapns/gcm/app.rb +7 -0
- data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rapns/gcm/notification.rb +31 -0
- data/lib/rapns/gcm/payload_size_validator.rb +13 -0
- data/lib/rapns/multi_json_helper.rb +16 -0
- data/lib/rapns/notification.rb +54 -0
- data/lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb +12 -0
- data/lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb +17 -0
- data/lib/rapns/patches.rb +6 -0
- data/lib/rapns/version.rb +3 -0
- data/lib/rapns.rb +21 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/test.rake +33 -0
- data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
- data/spec/acceptance_spec_helper.rb +85 -0
- data/spec/support/simplecov_helper.rb +13 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/unit/apns/app_spec.rb +15 -0
- data/spec/unit/apns/feedback_spec.rb +12 -0
- data/spec/unit/apns/notification_spec.rb +198 -0
- data/spec/unit/app_spec.rb +18 -0
- data/spec/unit/configuration_spec.rb +38 -0
- data/spec/unit/daemon/apns/app_runner_spec.rb +39 -0
- data/spec/unit/daemon/apns/connection_spec.rb +234 -0
- data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +160 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +118 -0
- data/spec/unit/daemon/app_runner_shared.rb +66 -0
- data/spec/unit/daemon/app_runner_spec.rb +129 -0
- data/spec/unit/daemon/database_reconnectable_spec.rb +109 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_handler_shared.rb +28 -0
- data/spec/unit/daemon/delivery_queue_spec.rb +29 -0
- data/spec/unit/daemon/feeder_spec.rb +95 -0
- data/spec/unit/daemon/gcm/app_runner_spec.rb +17 -0
- data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +40 -0
- data/spec/unit/daemon/logger_spec.rb +156 -0
- data/spec/unit/daemon_spec.rb +139 -0
- data/spec/unit/gcm/app_spec.rb +5 -0
- data/spec/unit/gcm/notification_spec.rb +55 -0
- data/spec/unit/notification_shared.rb +38 -0
- data/spec/unit/notification_spec.rb +6 -0
- data/spec/unit_spec_helper.rb +145 -0
- metadata +240 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::Daemon::InterruptibleSleep do
|
|
4
|
+
class SleepTest
|
|
5
|
+
extend Rapns::Daemon::InterruptibleSleep
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
let(:rd) { stub(:close => nil) }
|
|
9
|
+
let(:wr) { stub(:close => nil) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
IO.stub(:pipe)
|
|
13
|
+
IO.stub(:select)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'creates a new pipe' do
|
|
17
|
+
IO.should_receive(:pipe)
|
|
18
|
+
SleepTest.interruptible_sleep 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'selects on the reader' do
|
|
22
|
+
IO.stub(:pipe => [rd, wr])
|
|
23
|
+
IO.should_receive(:select).with([rd], nil, nil, 1)
|
|
24
|
+
SleepTest.interruptible_sleep 1
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'closes both ends of the pipe after the timeout' do
|
|
28
|
+
IO.stub(:pipe => [rd, wr])
|
|
29
|
+
rd.should_receive(:close)
|
|
30
|
+
wr.should_receive(:close)
|
|
31
|
+
SleepTest.interruptible_sleep 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'closes the writer' do
|
|
35
|
+
IO.stub(:pipe => [rd, wr])
|
|
36
|
+
SleepTest.interruptible_sleep 1
|
|
37
|
+
wr.should_receive(:close)
|
|
38
|
+
SleepTest.interrupt_sleep
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
def self.logger
|
|
5
|
+
@logger
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.logger=(logger)
|
|
9
|
+
@logger = logger
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module HoptoadNotifier
|
|
14
|
+
def self.notify(e)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Airbrake
|
|
19
|
+
def self.notify(e)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe Rapns::Daemon::Logger do
|
|
24
|
+
let(:log) { stub(:sync= => true) }
|
|
25
|
+
let(:config) { stub(:airbrake_notify => true) }
|
|
26
|
+
|
|
27
|
+
before do
|
|
28
|
+
Rails.stub(:root).and_return("/rails_root")
|
|
29
|
+
@buffered_logger = mock("BufferedLogger", :info => nil, :error => nil, :level => 0, :auto_flushing => 1, :auto_flushing= => nil)
|
|
30
|
+
Rails.logger = @buffered_logger
|
|
31
|
+
ActiveSupport::BufferedLogger.stub(:new).and_return(@buffered_logger)
|
|
32
|
+
Rapns::Daemon.stub(:config => config)
|
|
33
|
+
File.stub(:open => log)
|
|
34
|
+
STDERR.stub(:puts)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "disables logging if the log file cannot be opened" do
|
|
38
|
+
File.stub(:open).and_raise(Errno::ENOENT)
|
|
39
|
+
STDERR.should_receive(:puts).with(/No such file or directory/)
|
|
40
|
+
STDERR.should_receive(:puts).with(/Logging disabled/)
|
|
41
|
+
Rapns::Daemon::Logger.new(:foreground => true)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "should open the a log file in the Rails log directory" do
|
|
45
|
+
File.should_receive(:open).with('/rails_root/log/rapns.log', 'a')
|
|
46
|
+
Rapns::Daemon::Logger.new(:foreground => true)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'sets sync mode on the log descriptor' do
|
|
50
|
+
log.should_receive(:sync=).with(true)
|
|
51
|
+
Rapns::Daemon::Logger.new(:foreground => true)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'instantiates the BufferedLogger' do
|
|
55
|
+
ActiveSupport::BufferedLogger.should_receive(:new).with(log, Rails.logger.level)
|
|
56
|
+
Rapns::Daemon::Logger.new(:foreground => true)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "should print out the msg if running in the foreground" do
|
|
60
|
+
logger = Rapns::Daemon::Logger.new(:foreground => true)
|
|
61
|
+
STDOUT.should_receive(:puts).with(/hi mom/)
|
|
62
|
+
logger.info("hi mom")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should not print out the msg if not running in the foreground" do
|
|
66
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
67
|
+
STDOUT.should_not_receive(:puts).with(/hi mom/)
|
|
68
|
+
logger.info("hi mom")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should prefix log lines with the current time" do
|
|
72
|
+
now = Time.now
|
|
73
|
+
Time.stub(:now).and_return(now)
|
|
74
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
75
|
+
@buffered_logger.should_receive(:info).with(/#{Regexp.escape("[#{now.to_s(:db)}]")}/)
|
|
76
|
+
logger.info("blah")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should prefix error logs with the ERROR label" do
|
|
80
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
81
|
+
@buffered_logger.should_receive(:error).with(/#{Regexp.escape("[ERROR]")}/)
|
|
82
|
+
logger.error("eeek")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "should prefix warn logs with the WARNING label" do
|
|
86
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
87
|
+
@buffered_logger.should_receive(:warn).with(/#{Regexp.escape("[WARNING]")}/)
|
|
88
|
+
logger.warn("eeek")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "should handle an Exception instance" do
|
|
92
|
+
e = RuntimeError.new("hi mom")
|
|
93
|
+
e.stub(:backtrace => [])
|
|
94
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
95
|
+
@buffered_logger.should_receive(:error).with(/RuntimeError, hi mom/)
|
|
96
|
+
logger.error(e)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "should notify Airbrake of the exception" do
|
|
100
|
+
e = RuntimeError.new("hi mom")
|
|
101
|
+
e.stub(:backtrace => [])
|
|
102
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false, :airbrake_notify => true)
|
|
103
|
+
Airbrake.should_receive(:notify_or_ignore).with(e)
|
|
104
|
+
logger.error(e)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "without Airbrake defined" do
|
|
108
|
+
before do
|
|
109
|
+
Object.send(:remove_const, :Airbrake)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
after do
|
|
113
|
+
module Airbrake
|
|
114
|
+
def self.notify(e)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "should notify using HoptoadNotifier" do
|
|
120
|
+
e = RuntimeError.new("hi mom")
|
|
121
|
+
e.stub(:backtrace => [])
|
|
122
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false, :airbrake_notify => true)
|
|
123
|
+
HoptoadNotifier.should_receive(:notify_or_ignore).with(e)
|
|
124
|
+
logger.error(e)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "should not notify Airbrake of the exception if the airbrake_notify option is false" do
|
|
129
|
+
e = RuntimeError.new("hi mom")
|
|
130
|
+
e.stub(:backtrace => [])
|
|
131
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false, :airbrake_notify => false)
|
|
132
|
+
Airbrake.should_not_receive(:notify_or_ignore).with(e)
|
|
133
|
+
logger.error(e)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "should not notify Airbrake if explicitly disabled in the call to error" do
|
|
137
|
+
e = RuntimeError.new("hi mom")
|
|
138
|
+
e.stub(:backtrace => [])
|
|
139
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false, :airbrake_notify => true)
|
|
140
|
+
Airbrake.should_not_receive(:notify_or_ignore).with(e)
|
|
141
|
+
logger.error(e, :airbrake_notify => false)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "should not attempt to notify Airbrake of the error is not an Exception" do
|
|
145
|
+
logger = Rapns::Daemon::Logger.new(:foreground => false)
|
|
146
|
+
Airbrake.should_not_receive(:notify_or_ignore)
|
|
147
|
+
logger.error("string error message")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'defaults auto_flushing to true if the Rails logger does not respond to auto_flushing' do
|
|
151
|
+
rails_logger = mock(:info => nil, :error => nil, :level => 0)
|
|
152
|
+
Rails.logger = rails_logger
|
|
153
|
+
logger = Rapns::Daemon::Logger.new({})
|
|
154
|
+
@buffered_logger.auto_flushing.should be_true
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::Daemon, "when starting" do
|
|
4
|
+
module Rails; end
|
|
5
|
+
|
|
6
|
+
let(:certificate) { stub }
|
|
7
|
+
let(:password) { stub }
|
|
8
|
+
let(:config) { stub(:pid_file => nil, :push_poll => 2, :airbrake_notify => false, :foreground => true) }
|
|
9
|
+
let(:logger) { stub(:info => nil, :error => nil, :warn => nil) }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
Rapns.stub(:config => config)
|
|
13
|
+
Rapns::Daemon::Feeder.stub(:start)
|
|
14
|
+
Rapns::Daemon::Logger.stub(:new).and_return(logger)
|
|
15
|
+
Rapns::Daemon::AppRunner.stub(:sync => nil, :stop => nil)
|
|
16
|
+
Rapns::Daemon.stub(:daemonize => nil, :reconnect_database => nil, :exit => nil, :puts => nil)
|
|
17
|
+
File.stub(:open)
|
|
18
|
+
Rails.stub(:root).and_return("/rails_root")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "forks into a daemon if the foreground option is false" do
|
|
22
|
+
config.stub(:foreground => false)
|
|
23
|
+
ActiveRecord::Base.stub(:establish_connection)
|
|
24
|
+
Rapns::Daemon.should_receive(:daemonize)
|
|
25
|
+
Rapns::Daemon.start
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "does not fork into a daemon if the foreground option is true" do
|
|
29
|
+
config.stub(:foreground => true)
|
|
30
|
+
Rapns::Daemon.should_not_receive(:daemonize)
|
|
31
|
+
Rapns::Daemon.start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "writes the process ID to the PID file" do
|
|
35
|
+
Rapns::Daemon.should_receive(:write_pid_file)
|
|
36
|
+
Rapns::Daemon.start
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "logs an error if the PID file could not be written" do
|
|
40
|
+
config.stub(:pid_file => '/rails_root/rapns.pid')
|
|
41
|
+
File.stub(:open).and_raise(Errno::ENOENT)
|
|
42
|
+
logger.should_receive(:error).with("Failed to write PID to '/rails_root/rapns.pid': #<Errno::ENOENT: No such file or directory>")
|
|
43
|
+
Rapns::Daemon.start
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "starts the feeder" do
|
|
47
|
+
Rapns::Daemon::Feeder.should_receive(:start).with(2)
|
|
48
|
+
Rapns::Daemon.start
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "syncs apps" do
|
|
52
|
+
Rapns::Daemon::AppRunner.should_receive(:sync)
|
|
53
|
+
Rapns::Daemon.start
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "sets up the logger" do
|
|
57
|
+
config.stub(:airbrake_notify => true)
|
|
58
|
+
Rapns::Daemon::Logger.should_receive(:new).with(:foreground => true, :airbrake_notify => true)
|
|
59
|
+
Rapns::Daemon.start
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "makes the logger accessible" do
|
|
63
|
+
Rapns::Daemon.start
|
|
64
|
+
Rapns::Daemon.logger.should == logger
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'prints a warning if there are no apps' do
|
|
68
|
+
Rapns::App.stub(:count => 0)
|
|
69
|
+
logger.should_receive(:warn).any_number_of_times
|
|
70
|
+
Rapns::Daemon.start
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'prints a warning exists if rapns has not been upgraded' do
|
|
74
|
+
Rapns::App.stub(:count).and_raise(ActiveRecord::StatementInvalid)
|
|
75
|
+
Rapns::Daemon.should_receive(:puts).any_number_of_times
|
|
76
|
+
Rapns::Daemon.should_receive(:exit).with(1)
|
|
77
|
+
Rapns::Daemon.start
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'warns if rapns.yml still exists' do
|
|
81
|
+
File.should_receive(:exists?).with('/rails_root/config/rapns/rapns.yml').and_return(true)
|
|
82
|
+
logger.should_receive(:warn).with("Since 2.0.0 rapns uses command-line options and a Ruby based configuration file.\nPlease run 'rails g rapns' to generate a new configuration file into config/initializers.\nRemove config/rapns/rapns.yml to avoid this warning.\n")
|
|
83
|
+
Rapns::Daemon.start
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe Rapns::Daemon, "when being shutdown" do
|
|
88
|
+
let(:config) { stub(:pid_file => '/rails_root/rapns.pid') }
|
|
89
|
+
|
|
90
|
+
before do
|
|
91
|
+
Rapns.stub(:config => config)
|
|
92
|
+
Rapns::Daemon.stub(:puts => nil)
|
|
93
|
+
Rapns::Daemon::Feeder.stub(:stop)
|
|
94
|
+
Rapns::Daemon::AppRunner.stub(:stop)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# These tests do not work on JRuby.
|
|
98
|
+
unless defined? JRUBY_VERSION
|
|
99
|
+
it "shuts down when signaled signaled SIGINT" do
|
|
100
|
+
Rapns::Daemon.setup_signal_hooks
|
|
101
|
+
Rapns::Daemon.should_receive(:shutdown)
|
|
102
|
+
Process.kill("SIGINT", Process.pid)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "shuts down when signaled signaled SIGTERM" do
|
|
106
|
+
Rapns::Daemon.setup_signal_hooks
|
|
107
|
+
Rapns::Daemon.should_receive(:shutdown)
|
|
108
|
+
Process.kill("SIGTERM", Process.pid)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "stops the feeder" do
|
|
113
|
+
Rapns::Daemon::Feeder.should_receive(:stop)
|
|
114
|
+
Rapns::Daemon.send(:shutdown)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "stops the app runners" do
|
|
118
|
+
Rapns::Daemon::AppRunner.should_receive(:stop)
|
|
119
|
+
Rapns::Daemon.send(:shutdown)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it "removes the PID file if one was written" do
|
|
123
|
+
File.stub(:exists?).and_return(true)
|
|
124
|
+
File.should_receive(:delete).with("/rails_root/rapns.pid")
|
|
125
|
+
Rapns::Daemon.send(:shutdown)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "does not attempt to remove the PID file if it does not exist" do
|
|
129
|
+
File.stub(:exists?).and_return(false)
|
|
130
|
+
File.should_not_receive(:delete)
|
|
131
|
+
Rapns::Daemon.send(:shutdown)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "does not attempt to remove the PID file if one was not written" do
|
|
135
|
+
config.stub(:pid_file).and_return(nil)
|
|
136
|
+
File.should_not_receive(:delete)
|
|
137
|
+
Rapns::Daemon.send(:shutdown)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
require 'unit/notification_shared.rb'
|
|
3
|
+
|
|
4
|
+
describe Rapns::Gcm::Notification do
|
|
5
|
+
it_should_behave_like 'an Notification subclass'
|
|
6
|
+
|
|
7
|
+
let(:app) { Rapns::Gcm::App.create!(:name => 'test', :auth_key => 'abc') }
|
|
8
|
+
let(:notification_class) { Rapns::Gcm::Notification }
|
|
9
|
+
let(:notification) { notification_class.new }
|
|
10
|
+
let(:data_setter) { 'data=' }
|
|
11
|
+
let(:data_getter) { 'data' }
|
|
12
|
+
|
|
13
|
+
it { should validate_presence_of :registration_ids }
|
|
14
|
+
|
|
15
|
+
it 'has a payload limit of 4096 bytes' do
|
|
16
|
+
notification.data = { :key => "a" * 4096 }
|
|
17
|
+
notification.valid?.should be_false
|
|
18
|
+
notification.errors[:base].should == ["GCM notification payload cannot be larger than 4096 bytes."]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'allows assignment of many registration IDs' do
|
|
22
|
+
notification.app = app
|
|
23
|
+
notification.registration_ids = ['a', 'b']
|
|
24
|
+
notification.save!
|
|
25
|
+
reloaded_notification = notification_class.find(notification.id)
|
|
26
|
+
reloaded_notification.registration_ids.should == ['a', 'b']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'allows assignment of a single registration ID' do
|
|
30
|
+
notification.app = app
|
|
31
|
+
notification.registration_ids = 'a'
|
|
32
|
+
notification.save!
|
|
33
|
+
reloaded_notification = notification_class.find(notification.id)
|
|
34
|
+
reloaded_notification.registration_ids.should == ['a']
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'validates expiry is present if collapse_key is set' do
|
|
38
|
+
notification.collapse_key = 'test'
|
|
39
|
+
notification.expiry = nil
|
|
40
|
+
notification.valid?.should be_false
|
|
41
|
+
notification.errors[:expiry].should == ['must be set when using a collapse_key']
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'does not include time_to_live in the payload if collapse_key is not set' do
|
|
45
|
+
notification.expiry = 100
|
|
46
|
+
notification.collapse_key = nil
|
|
47
|
+
notification.as_json.key?('time_to_live').should be_false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'includes time_to_live in the payload if collapse_key is set' do
|
|
51
|
+
notification.expiry = 100
|
|
52
|
+
notification.collapse_key = 'sync'
|
|
53
|
+
notification.as_json['time_to_live'].should == 100
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
shared_examples_for "an Notification subclass" do
|
|
2
|
+
describe "when assigning data for the device" do
|
|
3
|
+
it "calls MultiJson.dump when multi_json responds to :dump" do
|
|
4
|
+
notification = notification_class.new
|
|
5
|
+
MultiJson.stub(:respond_to?).with(:dump).and_return(true)
|
|
6
|
+
MultiJson.should_receive(:dump).with(any_args())
|
|
7
|
+
notification.send(data_setter, { :pirates => 1 })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "calls MultiJson.encode when multi_json does not respond to :dump" do
|
|
11
|
+
notification = notification_class.new
|
|
12
|
+
MultiJson.stub(:respond_to?).with(:dump).and_return(false)
|
|
13
|
+
MultiJson.should_receive(:encode).with(any_args())
|
|
14
|
+
notification.send(data_setter, { :ninjas => 1 })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "raises an ArgumentError if something other than a Hash is assigned" do
|
|
18
|
+
expect do
|
|
19
|
+
notification.send(data_setter, Array.new)
|
|
20
|
+
end.to raise_error(ArgumentError, "must be a Hash")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "encodes the given Hash as JSON" do
|
|
24
|
+
notification.send(data_setter, { :hi => "mom" })
|
|
25
|
+
notification.read_attribute(:data).should == "{\"hi\":\"mom\"}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "decodes the JSON when using the reader method" do
|
|
29
|
+
notification.send(data_setter, { :hi => "mom" })
|
|
30
|
+
notification.send(data_getter).should == {"hi" => "mom"}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'warns if attributes_for_device is assigned via mass-assignment' do
|
|
34
|
+
ActiveSupport::Deprecation.should_receive(:warn)
|
|
35
|
+
notification_class.new(:attributes_for_device => {:hi => 'mom'})
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
ENV['RAILS_ENV'] = 'test'
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require './spec/support/simplecov_helper'
|
|
5
|
+
include SimpleCovHelper
|
|
6
|
+
start_simple_cov("unit-#{RUBY_VERSION}")
|
|
7
|
+
rescue LoadError
|
|
8
|
+
puts "Coverage disabled."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require 'active_record'
|
|
12
|
+
|
|
13
|
+
jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
|
14
|
+
|
|
15
|
+
$adapter = ENV['ADAPTER'] || 'postgresql'
|
|
16
|
+
$adapter = 'jdbc' + $adapter if jruby
|
|
17
|
+
|
|
18
|
+
DATABASE_CONFIG = YAML.load_file(File.expand_path("../config/database.yml", File.dirname(__FILE__)))
|
|
19
|
+
|
|
20
|
+
if DATABASE_CONFIG[$adapter].nil?
|
|
21
|
+
puts "No such adapter '#{$adapter}'. Valid adapters are #{DATABASE_CONFIG.keys.join(', ')}."
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if ENV['TRAVIS']
|
|
26
|
+
DATABASE_CONFIG[$adapter]['username'] = 'postgres'
|
|
27
|
+
else
|
|
28
|
+
require 'etc'
|
|
29
|
+
DATABASE_CONFIG[$adapter]['username'] = Etc.getlogin
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "Using #{$adapter} adapter."
|
|
33
|
+
|
|
34
|
+
ActiveRecord::Base.establish_connection(DATABASE_CONFIG[$adapter])
|
|
35
|
+
|
|
36
|
+
require 'generators/templates/create_rapns_notifications'
|
|
37
|
+
require 'generators/templates/create_rapns_feedback'
|
|
38
|
+
require 'generators/templates/add_alert_is_json_to_rapns_notifications'
|
|
39
|
+
require 'generators/templates/add_app_to_rapns'
|
|
40
|
+
require 'generators/templates/create_rapns_apps'
|
|
41
|
+
require 'generators/templates/add_gcm'
|
|
42
|
+
|
|
43
|
+
[CreateRapnsNotifications, CreateRapnsFeedback,
|
|
44
|
+
AddAlertIsJsonToRapnsNotifications, AddAppToRapns, CreateRapnsApps, AddGcm].each do |migration|
|
|
45
|
+
migration.down rescue ActiveRecord::StatementInvalid
|
|
46
|
+
migration.up
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
require 'bundler'
|
|
50
|
+
Bundler.require(:default)
|
|
51
|
+
|
|
52
|
+
require 'shoulda'
|
|
53
|
+
require 'database_cleaner'
|
|
54
|
+
|
|
55
|
+
DatabaseCleaner.strategy = :truncation
|
|
56
|
+
|
|
57
|
+
require 'rapns'
|
|
58
|
+
require 'rapns/daemon'
|
|
59
|
+
|
|
60
|
+
Rapns::Notification.reset_column_information
|
|
61
|
+
Rapns::App.reset_column_information
|
|
62
|
+
Rapns::Apns::Feedback.reset_column_information
|
|
63
|
+
|
|
64
|
+
RSpec.configure do |config|
|
|
65
|
+
# config.before :suite do
|
|
66
|
+
# PerfTools::CpuProfiler.start('/tmp/rapns_profile')
|
|
67
|
+
# end
|
|
68
|
+
# config.after :suite do
|
|
69
|
+
# PerfTools::CpuProfiler.stop
|
|
70
|
+
# end
|
|
71
|
+
|
|
72
|
+
config.before(:each) { DatabaseCleaner.clean }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# a test certificate that contains both an X509 certificate and
|
|
76
|
+
# a private key, similar to those used for connecting to Apple
|
|
77
|
+
# push notification servers.
|
|
78
|
+
#
|
|
79
|
+
# Note that we cannot validate the certificate and private key
|
|
80
|
+
# because we are missing the certificate chain used to validate
|
|
81
|
+
# the certificate, and this is private to Apple. So if the app
|
|
82
|
+
# has a certificate and a private key in it, the only way to find
|
|
83
|
+
# out if it really is valid is to connect to Apple's servers.
|
|
84
|
+
#
|
|
85
|
+
TEST_CERT = <<EOF
|
|
86
|
+
Bag Attributes
|
|
87
|
+
friendlyName: test certificate
|
|
88
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
|
89
|
+
subject=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
|
90
|
+
issuer=/CN=test certificate/O=Example/OU=Example/ST=QLD/C=AU/L=Example/emailAddress=user@example.com
|
|
91
|
+
-----BEGIN CERTIFICATE-----
|
|
92
|
+
MIID5jCCAs6gAwIBAgIBATALBgkqhkiG9w0BAQswgY0xGTAXBgNVBAMMEHRlc3Qg
|
|
93
|
+
Y2VydGlmaWNhdGUxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAsMB0V4YW1wbGUx
|
|
94
|
+
DDAKBgNVBAgMA1FMRDELMAkGA1UEBhMCQVUxEDAOBgNVBAcMB0V4YW1wbGUxHzAd
|
|
95
|
+
BgkqhkiG9w0BCQEWEHVzZXJAZXhhbXBsZS5jb20wHhcNMTIwOTA5MDMxODMyWhcN
|
|
96
|
+
MjIwOTA3MDMxODMyWjCBjTEZMBcGA1UEAwwQdGVzdCBjZXJ0aWZpY2F0ZTEQMA4G
|
|
97
|
+
A1UECgwHRXhhbXBsZTEQMA4GA1UECwwHRXhhbXBsZTEMMAoGA1UECAwDUUxEMQsw
|
|
98
|
+
CQYDVQQGEwJBVTEQMA4GA1UEBwwHRXhhbXBsZTEfMB0GCSqGSIb3DQEJARYQdXNl
|
|
99
|
+
ckBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKF+
|
|
100
|
+
UDsN1sLen8g+97PNTiWju9+wkSv+H5rQlvb6YFLPx11YvqpK8ms6kFU1OmWeLfmh
|
|
101
|
+
cpsT+bZtKupC7aGPoSG3RXzzf/YUMgs/ZSXA0idZHA6tkReAEzIX6jL5otfPWbaP
|
|
102
|
+
luCTUoVMeP4u9ywk628zlqh9IQHC1Agl0R1xGCpULDk8kn1gPyEisl38wI5aDbzy
|
|
103
|
+
6lYQGNUKOqt1xfVjtIFe/jyY/v0sxFjIJlRLcAFBuJx4sRV+PwRBkusOQtYwcwpI
|
|
104
|
+
loMxJj+GQe66ueATW81aC4iOU66DAFFEuGzwIwm3bOilimGGQbGb92F339RfmSOo
|
|
105
|
+
TPAvVhsakI3mzESb4lkCAwEAAaNRME8wDgYDVR0PAQH/BAQDAgeAMCAGA1UdJQEB
|
|
106
|
+
/wQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAbBgNVHREEFDASgRB1c2VyQGV4YW1w
|
|
107
|
+
bGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA5UbNR+83ZdI2DiaB4dRmy0V5RDAqJ
|
|
108
|
+
k9+QskcTV4gBTjsOBS46Dw1tI6iTrfTyjYJdnyH0Y2Y2YVWBnvtON41UCZak+4ed
|
|
109
|
+
/IqyzU0dtfZ+frWa0RY4reyl80TwqnzyJfni0nDo4zGGvz70cxyaz2u1BWqwLjqb
|
|
110
|
+
dh8Dxvt+aHW2MQi0iGKh/HNbgwVanR4+ubNwziK9sR1Rnq9MkHWtwBw16SXQG6ao
|
|
111
|
+
SZKASWNaH8VL08Zz0E98cwd137UJkPsldCwJ8kHR5OzkcjPdXvnGD3d64yy2TC1Z
|
|
112
|
+
Gy1Aazt98wPcTYBytlhK8Rvzg9OoY9QmsdpmWxz1ZCXECJNqCa3IKsqO
|
|
113
|
+
-----END CERTIFICATE-----
|
|
114
|
+
Bag Attributes
|
|
115
|
+
friendlyName: test certificate
|
|
116
|
+
localKeyID: 00 93 8F E4 A3 C3 75 64 3D 7E EA 14 0B 0A EA DD 15 85 8A D5
|
|
117
|
+
Key Attributes: <No Attributes>
|
|
118
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
119
|
+
MIIEpQIBAAKCAQEAoX5QOw3Wwt6fyD73s81OJaO737CRK/4fmtCW9vpgUs/HXVi+
|
|
120
|
+
qkryazqQVTU6ZZ4t+aFymxP5tm0q6kLtoY+hIbdFfPN/9hQyCz9lJcDSJ1kcDq2R
|
|
121
|
+
F4ATMhfqMvmi189Zto+W4JNShUx4/i73LCTrbzOWqH0hAcLUCCXRHXEYKlQsOTyS
|
|
122
|
+
fWA/ISKyXfzAjloNvPLqVhAY1Qo6q3XF9WO0gV7+PJj+/SzEWMgmVEtwAUG4nHix
|
|
123
|
+
FX4/BEGS6w5C1jBzCkiWgzEmP4ZB7rq54BNbzVoLiI5TroMAUUS4bPAjCbds6KWK
|
|
124
|
+
YYZBsZv3YXff1F+ZI6hM8C9WGxqQjebMRJviWQIDAQABAoIBAQCTiLIDQUFSBdAz
|
|
125
|
+
QFNLD+S0vkCEuunlJuP4q1c/ir006l1YChsluBJ/o6D4NwiCjV+zDquEwVsALftm
|
|
126
|
+
yH4PewfZpXT2Ef508T5GyEO/mchj6iSXxDkpHvhqay6qIyWBwwxSnBtaTzy0Soi+
|
|
127
|
+
rmlhCtmLXbXld2sQEM1kJChGnWtWPtvSyrn+mapNPZviGRtgRNK+YsrAti1nUext
|
|
128
|
+
2syO5mTdHf1D8GR7I98OaX6odREuSocEV9PzfapWZx2GK5tvRiS1skiug5ciieTd
|
|
129
|
+
Am5/C+bb31h4drFslihLb5BRGO5SFQJvMJL2Sx1f19BCC4XikS01P4/zZbxQNq79
|
|
130
|
+
kxEQuDGBAoGBANP4pIYZ5xshCkx7cTYqmxzWLClGKE2S7Oa8N89mtOwfmqT9AFun
|
|
131
|
+
t9Us9Ukbi8BaKlKhGpQ1HlLf/KVcpyW0x2qLou6AyIWYH+/5VaR3graNgUnzpK9f
|
|
132
|
+
1F5HoaNHbhlAoebqhzhASFlJI2aqUdQjdOv73z+s9szJU4gpILNwGDFnAoGBAMMJ
|
|
133
|
+
j+vIxtG9J2jldyoXzpg5mbMXSj9u/wFLBVdjXWyOoiqVMMBto53RnoqAom7Ifr9D
|
|
134
|
+
49LxRAT1Q3l4vs/YnM3ziMsIg2vQK1EbrLsY9OnD/kvPaLXOlNIOdfLM8UeVWZMc
|
|
135
|
+
I4LPbbZrhv/7CC8RjbRhMoWWdGYPvxmvD6V4ZDY/AoGBALoI6OxA45Htx4okdNHj
|
|
136
|
+
RstiNNPsnQaoQn6nBhxiubraafEPkzbd1fukP4pwQJELEUX/2sHkdL6rkqLW1GPF
|
|
137
|
+
a5dZAiBsqpCFWNJWdBGqSfBJ9QSgbxLz+gDcwUH6OOi0zuNJRm/aCyVBiW5bYQHc
|
|
138
|
+
NIvAPMk31ksZDtTbs7WIVdNVAoGBALZ1+KWNxKqs+fSBT5UahpUUtfy8miJz9a7A
|
|
139
|
+
/3M8q0cGvSF3Rw+OwpW/aEGMi+l2OlU27ykFuyukRAac9m296RwnbF79TO2M5ylO
|
|
140
|
+
6a5zb5ROXlWP6RbE96b4DlIidssQJqegmHwlEC+rsrVBpOtb0aThlYEyOxzMOGyP
|
|
141
|
+
wOR9l8rDAoGADZ4TUHFM6VrvPlUZBkGbqiyXH9IM/y9JWk+22JQCEGnM6RFZemSs
|
|
142
|
+
jxWqQiPAdJtb3xKryJSCMtFPH9azedoCrSgaMflJ1QgoXgpiKZyoEXWraVUggh/0
|
|
143
|
+
CEavgZcTZ6SvMuayqJdGGB+zb1V8XwXMtCjApR/kTm47DjxO4DmpOPs=
|
|
144
|
+
-----END RSA PRIVATE KEY-----
|
|
145
|
+
EOF
|