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,31 @@
|
|
|
1
|
+
module Rapns
|
|
2
|
+
module Gcm
|
|
3
|
+
class Notification < Rapns::Notification
|
|
4
|
+
validates :registration_ids, :presence => true
|
|
5
|
+
validates_with Rapns::Gcm::ExpiryCollapseKeyMutualInclusionValidator
|
|
6
|
+
validates_with Rapns::Gcm::PayloadSizeValidator
|
|
7
|
+
|
|
8
|
+
def registration_ids=(ids)
|
|
9
|
+
ids = [ids] if ids && !ids.is_a?(Array)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def as_json
|
|
14
|
+
json = {
|
|
15
|
+
'registration_ids' => registration_ids,
|
|
16
|
+
'delay_while_idle' => delay_while_idle,
|
|
17
|
+
'data' => data
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if collapse_key
|
|
21
|
+
json.merge!({
|
|
22
|
+
'collapse_key' => collapse_key,
|
|
23
|
+
'time_to_live' => expiry
|
|
24
|
+
})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
json
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Rapns
|
|
2
|
+
module Gcm
|
|
3
|
+
class PayloadSizeValidator < ActiveModel::Validator
|
|
4
|
+
LIMIT = 4096
|
|
5
|
+
|
|
6
|
+
def validate(record)
|
|
7
|
+
if record.payload_size > LIMIT
|
|
8
|
+
record.errors[:base] << "GCM notification payload cannot be larger than #{LIMIT} bytes."
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Rapns
|
|
2
|
+
module MultiJsonHelper
|
|
3
|
+
def multi_json_load(string, options = {})
|
|
4
|
+
# Calling load on multi_json less than v1.3.0 attempts to load a file from disk.
|
|
5
|
+
if Gem.loaded_specs['multi_json'].version >= Gem::Version.create('1.3.0')
|
|
6
|
+
MultiJson.load(string, options)
|
|
7
|
+
else
|
|
8
|
+
MultiJson.decode(string, options)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def multi_json_dump(string, options = {})
|
|
13
|
+
MultiJson.respond_to?(:dump) ? MultiJson.dump(string, options) : MultiJson.encode(string, options)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Rapns
|
|
2
|
+
class Notification < ActiveRecord::Base
|
|
3
|
+
include Rapns::MultiJsonHelper
|
|
4
|
+
|
|
5
|
+
self.table_name = 'rapns_notifications'
|
|
6
|
+
|
|
7
|
+
# TODO: Dump using multi json.
|
|
8
|
+
serialize :registration_ids
|
|
9
|
+
|
|
10
|
+
belongs_to :app, :class_name => 'Rapns::App'
|
|
11
|
+
|
|
12
|
+
attr_accessible :badge, :device_token, :sound, :alert, :data, :expiry,:delivered,
|
|
13
|
+
:delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after,
|
|
14
|
+
:alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids
|
|
15
|
+
|
|
16
|
+
validates :expiry, :numericality => true, :allow_nil => true
|
|
17
|
+
validates :app, :presence => true
|
|
18
|
+
|
|
19
|
+
scope :ready_for_delivery, lambda {
|
|
20
|
+
where('delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)',
|
|
21
|
+
false, false, Time.now)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
scope :for_apps, lambda { |apps|
|
|
25
|
+
where(:app_id => apps.map(&:id))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def initialize(attributes = nil, options = {})
|
|
29
|
+
if attributes.is_a?(Hash) && attributes.keys.include?(:attributes_for_device)
|
|
30
|
+
msg = ":attributes_for_device via mass-assignment is deprecated. Use :data or the attributes_for_device= instance method."
|
|
31
|
+
ActiveSupport::Deprecation.warn(msg, caller(1))
|
|
32
|
+
end
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def data=(attrs)
|
|
37
|
+
return unless attrs
|
|
38
|
+
raise ArgumentError, "must be a Hash" if !attrs.is_a?(Hash)
|
|
39
|
+
write_attribute(:data, multi_json_dump(attrs))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def data
|
|
43
|
+
multi_json_load(read_attribute(:data)) if read_attribute(:data)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def payload
|
|
47
|
+
multi_json_dump(as_json)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def payload_size
|
|
51
|
+
payload.bytesize
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters
|
|
3
|
+
class PostgreSQLAdapter < AbstractAdapter
|
|
4
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
|
5
|
+
def dealloc(key)
|
|
6
|
+
@connection.query "DEALLOCATE #{key}" if connection_active?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def connection_active?
|
|
10
|
+
@connection.status == PGconn::CONNECTION_OK
|
|
11
|
+
rescue PGError
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
if Rails::VERSION::STRING == '3.1.0' || Rails::VERSION::STRING == '3.1.1'
|
|
2
|
+
if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
|
|
3
|
+
STDERR.puts "[WARNING] Detected Rails #{Rails::VERSION::STRING}, patching PostgreSQLAdapter to fix reconnection bug: https://github.com/rails/rails/issues/3160"
|
|
4
|
+
require "rapns/patches/rails/#{Rails::VERSION::STRING}/postgresql_adapter.rb"
|
|
5
|
+
end
|
|
6
|
+
end
|
data/lib/rapns.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'multi_json'
|
|
3
|
+
|
|
4
|
+
require 'rapns/version'
|
|
5
|
+
require 'rapns/multi_json_helper'
|
|
6
|
+
require 'rapns/notification'
|
|
7
|
+
require 'rapns/app'
|
|
8
|
+
require 'rapns/configuration'
|
|
9
|
+
|
|
10
|
+
require 'rapns/apns/binary_notification_validator'
|
|
11
|
+
require 'rapns/apns/device_token_format_validator'
|
|
12
|
+
require 'rapns/apns/required_fields_validator'
|
|
13
|
+
require 'rapns/apns/notification'
|
|
14
|
+
require 'rapns/apns/feedback'
|
|
15
|
+
require 'rapns/apns/app'
|
|
16
|
+
|
|
17
|
+
require 'rapns/gcm/expiry_collapse_key_mutual_inclusion_validator'
|
|
18
|
+
require 'rapns/gcm/payload_size_validator'
|
|
19
|
+
require 'rapns/gcm/notification'
|
|
20
|
+
require 'rapns/gcm/app'
|
|
21
|
+
|
data/lib/tasks/cane.rake
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'cane/rake_task'
|
|
3
|
+
|
|
4
|
+
desc "Run cane to check quality metrics"
|
|
5
|
+
Cane::RakeTask.new(:quality) do |cane|
|
|
6
|
+
cane.add_threshold 'coverage/covered_percent', :>=, 99
|
|
7
|
+
cane.no_style = false
|
|
8
|
+
cane.style_measure = 1000
|
|
9
|
+
cane.no_doc = true
|
|
10
|
+
cane.abc_max = 20
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
namespace :spec do
|
|
14
|
+
task :cane => ['spec', 'quality']
|
|
15
|
+
end
|
|
16
|
+
rescue LoadError
|
|
17
|
+
warn "cane not available, quality task not provided."
|
|
18
|
+
end
|
data/lib/tasks/test.rake
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
namespace :test do
|
|
2
|
+
task :build_rails do
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
def cmd(str)
|
|
6
|
+
puts "* #{str}"
|
|
7
|
+
retval = Bundler.with_clean_env { `#{str}` }
|
|
8
|
+
puts retval.strip
|
|
9
|
+
retval
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rapns_root = Dir.pwd
|
|
13
|
+
path = '/tmp/rails_test'
|
|
14
|
+
cmd("rm -rf #{path}")
|
|
15
|
+
FileUtils.mkdir_p(path)
|
|
16
|
+
pwd = Dir.pwd
|
|
17
|
+
|
|
18
|
+
cmd("bundle exec rails new #{path} --skip-bundle")
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
Dir.chdir(path)
|
|
22
|
+
cmd('echo "gem \'rake\'" >> Gemfile')
|
|
23
|
+
cmd("echo \"gem 'rapns', :path => '#{rapns_root}'\" >> Gemfile")
|
|
24
|
+
cmd('bundle install')
|
|
25
|
+
cmd('bundle exec rails g rapns')
|
|
26
|
+
cmd('bundle exec rake db:migrate')
|
|
27
|
+
ensure
|
|
28
|
+
Dir.chdir(pwd)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
puts "Built into #{path}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'acceptance_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'GCM upgrade' do
|
|
4
|
+
before do
|
|
5
|
+
setup_rails
|
|
6
|
+
generate
|
|
7
|
+
migrate('create_rapns_notifications', 'create_rapns_feedback',
|
|
8
|
+
'add_alert_is_json_to_rapns_notifications', 'add_app_to_rapns',
|
|
9
|
+
'create_rapns_apps')
|
|
10
|
+
|
|
11
|
+
as_test_rails_db do
|
|
12
|
+
now = Time.now.to_s(:db)
|
|
13
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
|
14
|
+
INSERT INTO rapns_apps (key, environment, certificate, created_at, updated_at)
|
|
15
|
+
VALUES ('test', 'development', 'c3rt', '#{now}', '#{now}')
|
|
16
|
+
SQL
|
|
17
|
+
|
|
18
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
|
19
|
+
INSERT INTO rapns_notifications (app, device_token, created_at, updated_at)
|
|
20
|
+
VALUES ('test', 't0k3n', '#{now}', '#{now}')
|
|
21
|
+
SQL
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
migrate('add_gcm')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'associates apps and notifications' do
|
|
28
|
+
as_test_rails_db do
|
|
29
|
+
app = Rapns::Apns::App.first
|
|
30
|
+
app.name.should == 'test'
|
|
31
|
+
app.notifications.count.should == 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
ENV['RAILS_ENV'] = 'test'
|
|
5
|
+
require 'bundler'
|
|
6
|
+
Bundler.require(:default)
|
|
7
|
+
|
|
8
|
+
TMP_DIR = '/tmp'
|
|
9
|
+
RAILS_DIR = File.join(TMP_DIR, 'rapns_test')
|
|
10
|
+
if ENV['TRAVIS']
|
|
11
|
+
TRAVIS_BRANCH = 'gcm'
|
|
12
|
+
RAPNS_ROOT = 'git://github.com/ileitch/rapns.git'
|
|
13
|
+
else
|
|
14
|
+
RAPNS_ROOT = File.expand_path(__FILE__ + '/../../')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup_rails
|
|
18
|
+
return if $rails_is_setup
|
|
19
|
+
`rm -rf #{RAILS_DIR}`
|
|
20
|
+
FileUtils.mkdir_p(RAILS_DIR)
|
|
21
|
+
cmd("bundle exec rails new #{RAILS_DIR} --skip-bundle")
|
|
22
|
+
branch = `git branch | grep '\*'`.split(' ').last
|
|
23
|
+
in_test_rails do
|
|
24
|
+
cmd('echo "gem \'rake\'" >> Gemfile')
|
|
25
|
+
if ENV['TRAVIS']
|
|
26
|
+
cmd("echo \"gem 'rapns', :git => '#{RAPNS_ROOT}', :branch => '#{TRAVIS_BRANCH}'\" >> Gemfile")
|
|
27
|
+
else
|
|
28
|
+
cmd("echo \"gem 'rapns', :git => '#{RAPNS_ROOT}', :branch => '#{branch}'\" >> Gemfile")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
cmd("bundle install")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def as_test_rails_db(env='development')
|
|
36
|
+
orig_config = ActiveRecord::Base.connection_config
|
|
37
|
+
begin
|
|
38
|
+
in_test_rails do
|
|
39
|
+
config = YAML.load_file('config/database.yml')
|
|
40
|
+
ActiveRecord::Base.establish_connection(config[env])
|
|
41
|
+
yield
|
|
42
|
+
end
|
|
43
|
+
ensure
|
|
44
|
+
ActiveRecord::Base.establish_connection(orig_config)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def cmd(str, echo = true)
|
|
49
|
+
puts "* #{str.strip}" if echo
|
|
50
|
+
retval = Bundler.with_clean_env { `#{str}` }
|
|
51
|
+
puts retval.strip if echo
|
|
52
|
+
retval
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def generate
|
|
56
|
+
in_test_rails { cmd('bundle exec rails g rapns') }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def migrate(*migrations)
|
|
60
|
+
in_test_rails do
|
|
61
|
+
if migrations.present?
|
|
62
|
+
migrations.each do |name|
|
|
63
|
+
migration = Dir.entries('db/migrate').find { |entry| entry =~ /#{name}/ }
|
|
64
|
+
version = migration.split('_').first
|
|
65
|
+
cmd("bundle exec rake db:migrate VERSION=#{version} RAILS_ENV=development")
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
cmd('bundle exec rake db:migrate RAILS_ENV=development')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def in_test_rails
|
|
74
|
+
pwd = Dir.pwd
|
|
75
|
+
begin
|
|
76
|
+
Dir.chdir(RAILS_DIR)
|
|
77
|
+
yield
|
|
78
|
+
ensure
|
|
79
|
+
Dir.chdir(pwd)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def runner(str)
|
|
84
|
+
in_test_rails { cmd("rails runner -e test '#{str}'").strip }
|
|
85
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'simplecov'
|
|
2
|
+
require './spec/support/simplecov_quality_formatter'
|
|
3
|
+
|
|
4
|
+
module SimpleCovHelper
|
|
5
|
+
def start_simple_cov(name)
|
|
6
|
+
SimpleCov.start do
|
|
7
|
+
add_filter '/spec/'
|
|
8
|
+
add_filter '/lib/generators'
|
|
9
|
+
command_name name
|
|
10
|
+
formatter SimpleCov::Formatter::QualityFormatter
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rapns::App do
|
|
4
|
+
it 'does not validate an app with an invalid certificate' do
|
|
5
|
+
app = Rapns::Apns::App.new(:key => 'test', :environment => 'development', :certificate => 'foo')
|
|
6
|
+
app.valid?
|
|
7
|
+
app.errors[:certificate].should == ['Certificate value must contain a certificate and a private key.']
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'validates a real certificate' do
|
|
11
|
+
app = Rapns::Apns::App.new :key => 'test', :environment => 'development', :certificate => TEST_CERT
|
|
12
|
+
app.valid?
|
|
13
|
+
app.errors[:certificate].should == []
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::Apns::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
|
+
notification = Rapns::Apns::Feedback.new(:device_token => "{$%^&*()}")
|
|
9
|
+
notification.valid?.should be_false
|
|
10
|
+
notification.errors[:device_token].include?("is invalid").should be_true
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
require 'unit/notification_shared.rb'
|
|
3
|
+
|
|
4
|
+
describe Rapns::Apns::Notification do
|
|
5
|
+
it_should_behave_like 'an Notification subclass'
|
|
6
|
+
|
|
7
|
+
it { should validate_presence_of(:device_token) }
|
|
8
|
+
it { should validate_numericality_of(:badge) }
|
|
9
|
+
|
|
10
|
+
let(:notification_class) { Rapns::Apns::Notification }
|
|
11
|
+
let(:notification) { notification_class.new }
|
|
12
|
+
let(:data_setter) { 'attributes_for_device=' }
|
|
13
|
+
let(:data_getter) { 'attributes_for_device' }
|
|
14
|
+
|
|
15
|
+
it "should validate the format of the device_token" do
|
|
16
|
+
notification = Rapns::Apns::Notification.new(:device_token => "{$%^&*()}")
|
|
17
|
+
notification.valid?.should be_false
|
|
18
|
+
notification.errors[:device_token].include?("is invalid").should be_true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should validate the length of the binary conversion of the notification" do
|
|
22
|
+
notification.device_token = "a" * 64
|
|
23
|
+
notification.alert = "way too long!" * 100
|
|
24
|
+
notification.valid?.should be_false
|
|
25
|
+
notification.errors[:base].include?("APN notification cannot be larger than 256 bytes. Try condensing your alert and device attributes.").should be_true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should default the sound to 'default'" do
|
|
29
|
+
notification.sound.should eq('default')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should default the expiry to 1 day" do
|
|
33
|
+
notification.expiry.should == 1.day.to_i
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The notification must contain one of alert, sound or badge.
|
|
37
|
+
# @see https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html
|
|
38
|
+
it "should not be valid if there is none of alert,sound,badge present" do
|
|
39
|
+
notification.alert = nil
|
|
40
|
+
notification.sound = nil
|
|
41
|
+
notification.badge = nil
|
|
42
|
+
notification.valid?.should be_false
|
|
43
|
+
notification.errors[:base].should include("APN Notification must contain one of alert, badge, or sound")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe Rapns::Apns::Notification, "when assigning the device token" do
|
|
48
|
+
it "should strip spaces from the given string" do
|
|
49
|
+
notification = Rapns::Apns::Notification.new(:device_token => "o m g")
|
|
50
|
+
notification.device_token.should == "omg"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should strip chevrons from the given string" do
|
|
54
|
+
notification = Rapns::Apns::Notification.new(:device_token => "<omg>")
|
|
55
|
+
notification.device_token.should == "omg"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe Rapns::Apns::Notification, "as_json" do
|
|
60
|
+
it "should include the alert if present" do
|
|
61
|
+
notification = Rapns::Apns::Notification.new(:alert => "hi mom")
|
|
62
|
+
notification.as_json["aps"]["alert"].should == "hi mom"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should not include the alert key if the alert is not present" do
|
|
66
|
+
notification = Rapns::Apns::Notification.new(:alert => nil)
|
|
67
|
+
notification.as_json["aps"].key?("alert").should be_false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should encode the alert as JSON if it is a Hash" do
|
|
71
|
+
notification = Rapns::Apns::Notification.new(:alert => { 'body' => "hi mom", 'alert-loc-key' => "View" })
|
|
72
|
+
notification.as_json["aps"]["alert"].should == { 'body' => "hi mom", 'alert-loc-key' => "View" }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should include the badge if present" do
|
|
76
|
+
notification = Rapns::Apns::Notification.new(:badge => 6)
|
|
77
|
+
notification.as_json["aps"]["badge"].should == 6
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "should not include the badge key if the badge is not present" do
|
|
81
|
+
notification = Rapns::Apns::Notification.new(:badge => nil)
|
|
82
|
+
notification.as_json["aps"].key?("badge").should be_false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "should include the sound if present" do
|
|
86
|
+
notification = Rapns::Apns::Notification.new(:alert => "my_sound.aiff")
|
|
87
|
+
notification.as_json["aps"]["alert"].should == "my_sound.aiff"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "should not include the sound key if the sound is not present" do
|
|
91
|
+
notification = Rapns::Apns::Notification.new(:sound => false)
|
|
92
|
+
notification.as_json["aps"].key?("sound").should be_false
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should include attributes for the device" do
|
|
96
|
+
notification = Rapns::Apns::Notification.new
|
|
97
|
+
notification.attributes_for_device = {:omg => :lol, :wtf => :dunno}
|
|
98
|
+
notification.as_json["omg"].should == "lol"
|
|
99
|
+
notification.as_json["wtf"].should == "dunno"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe Rapns::Apns::Notification, 'MDM' do
|
|
104
|
+
let(:magic) { 'abc123' }
|
|
105
|
+
let(:notification) { Rapns::Apns::Notification.new }
|
|
106
|
+
|
|
107
|
+
it 'includes the mdm magic in the payload' do
|
|
108
|
+
notification.mdm = magic
|
|
109
|
+
notification.as_json.should == {'mdm' => magic}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'does not include aps attribute' do
|
|
113
|
+
notification.alert = "i'm doomed"
|
|
114
|
+
notification.mdm = magic
|
|
115
|
+
notification.as_json.key?('aps').should be_false
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
describe Rapns::Apns::Notification, 'content-available' do
|
|
120
|
+
let(:notification) { Rapns::Apns::Notification.new }
|
|
121
|
+
|
|
122
|
+
it 'includes content-available in the payload' do
|
|
123
|
+
notification.content_available = true
|
|
124
|
+
notification.as_json['aps']['content-available'].should == 1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'does not include content-available in the payload if not set' do
|
|
128
|
+
notification.as_json['aps'].key?('content-available').should be_false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'does not include convert-available as a non-aps attribute' do
|
|
132
|
+
notification.content_available = true
|
|
133
|
+
notification.as_json.key?('content-available').should be_false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe Rapns::Apns::Notification, "to_binary" do
|
|
138
|
+
it "should correctly convert the notification to binary" do
|
|
139
|
+
notification = Rapns::Apns::Notification.new
|
|
140
|
+
notification.device_token = "a" * 64
|
|
141
|
+
notification.sound = "1.aiff"
|
|
142
|
+
notification.badge = 3
|
|
143
|
+
notification.alert = "Don't panic Mr Mainwaring, don't panic!"
|
|
144
|
+
notification.attributes_for_device = {:hi => :mom}
|
|
145
|
+
notification.expiry = 86400 # 1 day, \x00\x01Q\x80
|
|
146
|
+
notification.app = Rapns::Apns::App.create!(:name => 'my_app', :environment => 'development', :certificate => TEST_CERT)
|
|
147
|
+
notification.save!
|
|
148
|
+
notification.stub(:id).and_return(1234)
|
|
149
|
+
notification.to_binary.should == "\x01\x00\x00\x04\xD2\x00\x01Q\x80\x00 \xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x00a{\"aps\":{\"alert\":\"Don't panic Mr Mainwaring, don't panic!\",\"badge\":3,\"sound\":\"1.aiff\"},\"hi\":\"mom\"}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
describe Rapns::Apns::Notification, "bug #31" do
|
|
154
|
+
it 'does not confuse a JSON looking string as JSON' do
|
|
155
|
+
notification = Rapns::Apns::Notification.new
|
|
156
|
+
notification.alert = "{\"one\":2}"
|
|
157
|
+
notification.alert.should == "{\"one\":2}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'does confuse a JSON looking string as JSON if the alert_is_json attribute is not present' do
|
|
161
|
+
notification = Rapns::Apns::Notification.new
|
|
162
|
+
notification.stub(:has_attribute? => false)
|
|
163
|
+
notification.alert = "{\"one\":2}"
|
|
164
|
+
notification.alert.should == {"one" => 2}
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe Rapns::Apns::Notification, "bug #35" do
|
|
169
|
+
it "should limit payload size to 256 bytes but not the entire packet" do
|
|
170
|
+
notification = Rapns::Apns::Notification.new do |n|
|
|
171
|
+
n.device_token = "a" * 64
|
|
172
|
+
n.alert = "a" * 210
|
|
173
|
+
n.app = Rapns::Apns::App.create!(:name => 'my_app', :environment => 'development', :certificate => TEST_CERT)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
notification.to_binary(:for_validation => true).bytesize.should > 256
|
|
177
|
+
notification.payload_size.should < 256
|
|
178
|
+
notification.should be_valid
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe Rapns::Apns::Notification, "multi_json usage" do
|
|
183
|
+
describe Rapns::Apns::Notification, "alert" do
|
|
184
|
+
it "should call MultiJson.load when multi_json version is 1.3.0" do
|
|
185
|
+
notification = Rapns::Apns::Notification.new(:alert => { :a => 1 }, :alert_is_json => true)
|
|
186
|
+
Gem.stub(:loaded_specs).and_return( { 'multi_json' => Gem::Specification.new('multi_json', '1.3.0') } )
|
|
187
|
+
MultiJson.should_receive(:load).with(any_args())
|
|
188
|
+
notification.alert
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "should call MultiJson.decode when multi_json version is 1.2.9" do
|
|
192
|
+
notification = Rapns::Apns::Notification.new(:alert => { :a => 1 }, :alert_is_json => true)
|
|
193
|
+
Gem.stub(:loaded_specs).and_return( { 'multi_json' => Gem::Specification.new('multi_json', '1.2.9') } )
|
|
194
|
+
MultiJson.should_receive(:decode).with(any_args())
|
|
195
|
+
notification.alert
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rapns::App do
|
|
4
|
+
it { should validate_numericality_of(:connections) }
|
|
5
|
+
|
|
6
|
+
it 'validates the uniqueness of name within type and environment' do
|
|
7
|
+
Rapns::Apns::App.create!(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
|
|
8
|
+
app = Rapns::Apns::App.new(:name => 'test', :environment => 'production', :certificate => TEST_CERT)
|
|
9
|
+
app.valid?.should be_false
|
|
10
|
+
app.errors[:name].should == ['has already been taken']
|
|
11
|
+
|
|
12
|
+
app = Rapns::Apns::App.new(:name => 'test', :environment => 'development', :certificate => TEST_CERT)
|
|
13
|
+
app.valid?.should be_true
|
|
14
|
+
|
|
15
|
+
app = Rapns::Gcm::App.new(:name => 'test', :environment => 'production', :auth_key => TEST_CERT)
|
|
16
|
+
app.valid?.should be_true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rapns do
|
|
4
|
+
let(:config) { stub }
|
|
5
|
+
|
|
6
|
+
before { Rapns.stub(:config => config) }
|
|
7
|
+
|
|
8
|
+
it 'can yields a config block' do
|
|
9
|
+
expect { |b| Rapns.configure(&b) }.to yield_with_args(config)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe Rapns::Configuration do
|
|
14
|
+
let(:config) { Rapns::Configuration.new }
|
|
15
|
+
|
|
16
|
+
it 'configures a feedback callback' do
|
|
17
|
+
b = Proc.new {}
|
|
18
|
+
config.on_apns_feedback(&b)
|
|
19
|
+
config.apns_feedback_callback.should == b
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'can be updated' do
|
|
23
|
+
new_config = Rapns::Configuration.new
|
|
24
|
+
new_config.batch_size = 100
|
|
25
|
+
expect { config.update(new_config) }.to change(config, :batch_size).to(100)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'sets the pid_file relative if not absolute' do
|
|
29
|
+
Rails.stub(:root => '/rails')
|
|
30
|
+
config.pid_file = 'tmp/rapns.pid'
|
|
31
|
+
config.pid_file.should == '/rails/tmp/rapns.pid'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not alter an absolute pid_file path' do
|
|
35
|
+
config.pid_file = '/tmp/rapns.pid'
|
|
36
|
+
config.pid_file.should == '/tmp/rapns.pid'
|
|
37
|
+
end
|
|
38
|
+
end
|