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.
Files changed (89) hide show
  1. data/CHANGELOG.md +31 -0
  2. data/LICENSE +7 -0
  3. data/README.md +138 -0
  4. data/bin/rapns +41 -0
  5. data/config/database.yml +39 -0
  6. data/lib/generators/rapns_generator.rb +34 -0
  7. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  8. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  9. data/lib/generators/templates/add_gcm.rb +94 -0
  10. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  11. data/lib/generators/templates/create_rapns_feedback.rb +15 -0
  12. data/lib/generators/templates/create_rapns_notifications.rb +26 -0
  13. data/lib/generators/templates/rapns.rb +39 -0
  14. data/lib/rapns/apns/app.rb +8 -0
  15. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  16. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  17. data/lib/rapns/apns/feedback.rb +14 -0
  18. data/lib/rapns/apns/notification.rb +86 -0
  19. data/lib/rapns/apns/required_fields_validator.rb +14 -0
  20. data/lib/rapns/app.rb +29 -0
  21. data/lib/rapns/configuration.rb +46 -0
  22. data/lib/rapns/daemon/apns/app_runner.rb +36 -0
  23. data/lib/rapns/daemon/apns/connection.rb +113 -0
  24. data/lib/rapns/daemon/apns/delivery.rb +63 -0
  25. data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
  26. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  27. data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
  28. data/lib/rapns/daemon/app_runner.rb +135 -0
  29. data/lib/rapns/daemon/database_reconnectable.rb +57 -0
  30. data/lib/rapns/daemon/delivery.rb +43 -0
  31. data/lib/rapns/daemon/delivery_error.rb +19 -0
  32. data/lib/rapns/daemon/delivery_handler.rb +46 -0
  33. data/lib/rapns/daemon/delivery_queue.rb +42 -0
  34. data/lib/rapns/daemon/delivery_queue_18.rb +44 -0
  35. data/lib/rapns/daemon/delivery_queue_19.rb +42 -0
  36. data/lib/rapns/daemon/feeder.rb +37 -0
  37. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  38. data/lib/rapns/daemon/gcm/delivery.rb +206 -0
  39. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  40. data/lib/rapns/daemon/interruptible_sleep.rb +18 -0
  41. data/lib/rapns/daemon/logger.rb +68 -0
  42. data/lib/rapns/daemon.rb +136 -0
  43. data/lib/rapns/gcm/app.rb +7 -0
  44. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  45. data/lib/rapns/gcm/notification.rb +31 -0
  46. data/lib/rapns/gcm/payload_size_validator.rb +13 -0
  47. data/lib/rapns/multi_json_helper.rb +16 -0
  48. data/lib/rapns/notification.rb +54 -0
  49. data/lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb +12 -0
  50. data/lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb +17 -0
  51. data/lib/rapns/patches.rb +6 -0
  52. data/lib/rapns/version.rb +3 -0
  53. data/lib/rapns.rb +21 -0
  54. data/lib/tasks/cane.rake +18 -0
  55. data/lib/tasks/test.rake +33 -0
  56. data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
  57. data/spec/acceptance_spec_helper.rb +85 -0
  58. data/spec/support/simplecov_helper.rb +13 -0
  59. data/spec/support/simplecov_quality_formatter.rb +8 -0
  60. data/spec/unit/apns/app_spec.rb +15 -0
  61. data/spec/unit/apns/feedback_spec.rb +12 -0
  62. data/spec/unit/apns/notification_spec.rb +198 -0
  63. data/spec/unit/app_spec.rb +18 -0
  64. data/spec/unit/configuration_spec.rb +38 -0
  65. data/spec/unit/daemon/apns/app_runner_spec.rb +39 -0
  66. data/spec/unit/daemon/apns/connection_spec.rb +234 -0
  67. data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
  68. data/spec/unit/daemon/apns/delivery_spec.rb +160 -0
  69. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  70. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +118 -0
  71. data/spec/unit/daemon/app_runner_shared.rb +66 -0
  72. data/spec/unit/daemon/app_runner_spec.rb +129 -0
  73. data/spec/unit/daemon/database_reconnectable_spec.rb +109 -0
  74. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  75. data/spec/unit/daemon/delivery_handler_shared.rb +28 -0
  76. data/spec/unit/daemon/delivery_queue_spec.rb +29 -0
  77. data/spec/unit/daemon/feeder_spec.rb +95 -0
  78. data/spec/unit/daemon/gcm/app_runner_spec.rb +17 -0
  79. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
  80. data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
  81. data/spec/unit/daemon/interruptible_sleep_spec.rb +40 -0
  82. data/spec/unit/daemon/logger_spec.rb +156 -0
  83. data/spec/unit/daemon_spec.rb +139 -0
  84. data/spec/unit/gcm/app_spec.rb +5 -0
  85. data/spec/unit/gcm/notification_spec.rb +55 -0
  86. data/spec/unit/notification_shared.rb +38 -0
  87. data/spec/unit/notification_spec.rb +6 -0
  88. data/spec/unit_spec_helper.rb +145 -0
  89. 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,12 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ def clear_cache!
5
+ @statements.each_value do |value|
6
+ @connection.query "DEALLOCATE #{value}" if active?
7
+ end
8
+ @statements.clear
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,3 @@
1
+ module Rapns
2
+ VERSION = '3.0.0'
3
+ 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
+
@@ -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
@@ -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,8 @@
1
+ class SimpleCov::Formatter::QualityFormatter
2
+ def format(result)
3
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
4
+ File.open("coverage/covered_percent", "w") do |f|
5
+ f.puts result.source_files.covered_percent.to_f
6
+ end
7
+ end
8
+ 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