rapns 3.3.2-java → 3.4.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 +7 -0
- data/README.md +19 -21
- data/bin/rapns +14 -13
- data/lib/generators/templates/rapns.rb +8 -4
- data/lib/rapns.rb +7 -0
- data/lib/rapns/TODO +3 -0
- data/lib/rapns/apns/feedback.rb +4 -2
- data/lib/rapns/app.rb +3 -1
- data/lib/rapns/configuration.rb +8 -1
- data/lib/rapns/daemon.rb +3 -1
- data/lib/rapns/daemon/apns/app_runner.rb +3 -2
- data/lib/rapns/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rapns/daemon/apns/connection.rb +26 -0
- data/lib/rapns/daemon/apns/delivery.rb +2 -1
- data/lib/rapns/daemon/apns/delivery_handler.rb +2 -2
- data/lib/rapns/daemon/app_runner.rb +50 -28
- data/lib/rapns/daemon/batch.rb +100 -0
- data/lib/rapns/daemon/delivery.rb +6 -10
- data/lib/rapns/daemon/delivery_handler.rb +14 -12
- data/lib/rapns/daemon/delivery_handler_collection.rb +33 -0
- data/lib/rapns/daemon/feeder.rb +3 -5
- data/lib/rapns/daemon/gcm/delivery.rb +5 -4
- data/lib/rapns/daemon/gcm/delivery_handler.rb +2 -2
- data/lib/rapns/daemon/store/active_record.rb +23 -2
- data/lib/rapns/deprecation.rb +7 -6
- data/lib/rapns/logger.rb +1 -1
- data/lib/rapns/notification.rb +5 -3
- data/lib/rapns/reflection.rb +1 -1
- data/lib/rapns/version.rb +1 -1
- data/lib/tasks/cane.rake +1 -1
- data/lib/tasks/test.rake +8 -3
- data/spec/unit/apns/app_spec.rb +4 -4
- data/spec/unit/apns_feedback_spec.rb +1 -1
- data/spec/unit/configuration_spec.rb +12 -6
- data/spec/unit/daemon/apns/app_runner_spec.rb +6 -4
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/connection_spec.rb +46 -10
- data/spec/unit/daemon/apns/delivery_handler_spec.rb +24 -18
- data/spec/unit/daemon/apns/delivery_spec.rb +11 -12
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +16 -16
- data/spec/unit/daemon/app_runner_shared.rb +27 -10
- data/spec/unit/daemon/app_runner_spec.rb +48 -28
- data/spec/unit/daemon/batch_spec.rb +160 -0
- data/spec/unit/daemon/delivery_handler_collection_spec.rb +37 -0
- data/spec/unit/daemon/delivery_handler_shared.rb +20 -11
- data/spec/unit/daemon/feeder_spec.rb +12 -12
- data/spec/unit/daemon/gcm/app_runner_spec.rb +4 -2
- data/spec/unit/daemon/gcm/delivery_handler_spec.rb +18 -10
- data/spec/unit/daemon/gcm/delivery_spec.rb +47 -17
- data/spec/unit/daemon/interruptible_sleep_spec.rb +3 -3
- data/spec/unit/daemon/reflectable_spec.rb +1 -1
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +1 -1
- data/spec/unit/daemon/store/active_record_spec.rb +87 -10
- data/spec/unit/daemon_spec.rb +6 -6
- data/spec/unit/deprecation_spec.rb +2 -2
- data/spec/unit/logger_spec.rb +33 -17
- data/spec/unit/notification_shared.rb +7 -3
- data/spec/unit/upgraded_spec.rb +8 -14
- data/spec/unit_spec_helper.rb +9 -1
- metadata +53 -44
- data/lib/rapns/daemon/delivery_queue.rb +0 -42
- data/lib/rapns/daemon/delivery_queue_18.rb +0 -44
- data/lib/rapns/daemon/delivery_queue_19.rb +0 -42
- data/spec/acceptance/gcm_upgrade_spec.rb +0 -34
- data/spec/acceptance_spec_helper.rb +0 -85
- data/spec/unit/daemon/delivery_queue_spec.rb +0 -29
@@ -0,0 +1,100 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Daemon
|
3
|
+
class Batch
|
4
|
+
include Reflectable
|
5
|
+
|
6
|
+
attr_reader :num_processed, :notifications,
|
7
|
+
:delivered, :failed, :retryable
|
8
|
+
|
9
|
+
def initialize(notifications)
|
10
|
+
@notifications = notifications
|
11
|
+
@num_processed = 0
|
12
|
+
@delivered = []
|
13
|
+
@failed = {}
|
14
|
+
@retryable = {}
|
15
|
+
@mutex = Mutex.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def num_notifications
|
19
|
+
@notifications.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def mark_retryable(notification, deliver_after)
|
23
|
+
if Rapns.config.batch_storage_updates
|
24
|
+
@retryable[deliver_after] ||= []
|
25
|
+
@retryable[deliver_after] << notification
|
26
|
+
else
|
27
|
+
Rapns::Daemon.store.mark_retryable(notification, deliver_after)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def mark_delivered(notification)
|
32
|
+
if Rapns.config.batch_storage_updates
|
33
|
+
@delivered << notification
|
34
|
+
else
|
35
|
+
Rapns::Daemon.store.mark_delivered(notification)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def mark_failed(notification, code, description)
|
40
|
+
if Rapns.config.batch_storage_updates
|
41
|
+
key = [code, description]
|
42
|
+
@failed[key] ||= []
|
43
|
+
@failed[key] << notification
|
44
|
+
else
|
45
|
+
Rapns::Daemon.store.mark_failed(notification, code, description)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def notification_processed
|
50
|
+
@mutex.synchronize do
|
51
|
+
@num_processed += 1
|
52
|
+
complete if @num_processed >= @notifications.size
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def complete?
|
57
|
+
@complete == true
|
58
|
+
end
|
59
|
+
|
60
|
+
def describe
|
61
|
+
@notifications.map(&:id).join(', ')
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def complete
|
67
|
+
[:complete_delivered, :complete_failed, :complete_retried].each do |method|
|
68
|
+
begin
|
69
|
+
send(method)
|
70
|
+
rescue StandardError => e
|
71
|
+
Rapns.logger.error(e)
|
72
|
+
reflect(:error, e)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
@notifications.clear
|
77
|
+
@complete = true
|
78
|
+
end
|
79
|
+
|
80
|
+
def complete_delivered
|
81
|
+
Rapns::Daemon.store.mark_batch_delivered(@delivered)
|
82
|
+
@delivered.clear
|
83
|
+
end
|
84
|
+
|
85
|
+
def complete_failed
|
86
|
+
@failed.each do |(code, description), notifications|
|
87
|
+
Rapns::Daemon.store.mark_batch_failed(notifications, code, description)
|
88
|
+
end
|
89
|
+
@failed.clear
|
90
|
+
end
|
91
|
+
|
92
|
+
def complete_retried
|
93
|
+
@retryable.each do |deliver_after, notifications|
|
94
|
+
Rapns::Daemon.store.mark_batch_retryable(notifications, deliver_after)
|
95
|
+
end
|
96
|
+
@retryable.clear
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -3,26 +3,22 @@ module Rapns
|
|
3
3
|
class Delivery
|
4
4
|
include Reflectable
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def retry_after(notification, deliver_after)
|
11
|
-
Rapns::Daemon.store.retry_after(notification, deliver_after)
|
6
|
+
def mark_retryable(notification, deliver_after)
|
7
|
+
@batch.mark_retryable(notification, deliver_after)
|
12
8
|
reflect(:notification_will_retry, notification)
|
13
9
|
end
|
14
10
|
|
15
|
-
def
|
16
|
-
|
11
|
+
def mark_retryable_exponential(notification)
|
12
|
+
mark_retryable(notification, Time.now + 2 ** (notification.retries + 1))
|
17
13
|
end
|
18
14
|
|
19
15
|
def mark_delivered
|
20
|
-
|
16
|
+
@batch.mark_delivered(@notification)
|
21
17
|
reflect(:notification_delivered, @notification)
|
22
18
|
end
|
23
19
|
|
24
20
|
def mark_failed(code, description)
|
25
|
-
|
21
|
+
@batch.mark_failed(@notification, code, description)
|
26
22
|
reflect(:notification_failed, @notification)
|
27
23
|
end
|
28
24
|
end
|
@@ -3,6 +3,8 @@ module Rapns
|
|
3
3
|
class DeliveryHandler
|
4
4
|
include Reflectable
|
5
5
|
|
6
|
+
WAKEUP = :wakeup
|
7
|
+
|
6
8
|
attr_accessor :queue
|
7
9
|
|
8
10
|
def start
|
@@ -16,10 +18,14 @@ module Rapns
|
|
16
18
|
|
17
19
|
def stop
|
18
20
|
@stop = true
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
end
|
22
|
+
|
23
|
+
def wakeup
|
24
|
+
queue.push(WAKEUP) if @thread
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait
|
28
|
+
@thread.join if @thread
|
23
29
|
stopped
|
24
30
|
end
|
25
31
|
|
@@ -29,20 +35,16 @@ module Rapns
|
|
29
35
|
end
|
30
36
|
|
31
37
|
def handle_next_notification
|
32
|
-
|
33
|
-
|
34
|
-
rescue DeliveryQueue::WakeupError
|
35
|
-
return
|
36
|
-
end
|
38
|
+
notification, batch = queue.pop
|
39
|
+
return if notification == WAKEUP
|
37
40
|
|
38
41
|
begin
|
39
|
-
deliver(notification)
|
40
|
-
reflect(:notification_delivered, notification)
|
42
|
+
deliver(notification, batch)
|
41
43
|
rescue StandardError => e
|
42
44
|
Rapns.logger.error(e)
|
43
45
|
reflect(:error, e)
|
44
46
|
ensure
|
45
|
-
|
47
|
+
batch.notification_processed
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rapns
|
2
|
+
module Daemon
|
3
|
+
class DeliveryHandlerCollection
|
4
|
+
attr_reader :handlers
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@handlers = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(handler)
|
11
|
+
@handlers << handler
|
12
|
+
end
|
13
|
+
|
14
|
+
def pop
|
15
|
+
handler = @handlers.pop
|
16
|
+
handler.stop
|
17
|
+
handler.wakeup
|
18
|
+
@handlers.map(&:wakeup)
|
19
|
+
handler.wait
|
20
|
+
end
|
21
|
+
|
22
|
+
def size
|
23
|
+
@handlers.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
@handlers.map(&:stop)
|
28
|
+
@handlers.map(&:wakeup)
|
29
|
+
@handlers.map(&:wait)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/rapns/daemon/feeder.rb
CHANGED
@@ -38,11 +38,9 @@ module Rapns
|
|
38
38
|
def self.enqueue_notifications
|
39
39
|
begin
|
40
40
|
idle = Rapns::Daemon::AppRunner.idle.map(&:app)
|
41
|
-
|
42
|
-
Rapns::Daemon.store.deliverable_notifications(idle)
|
43
|
-
|
44
|
-
reflect(:notification_enqueued, notification)
|
45
|
-
end
|
41
|
+
return if idle.empty?
|
42
|
+
notifications = Rapns::Daemon.store.deliverable_notifications(idle)
|
43
|
+
Rapns::Daemon::AppRunner.enqueue(notifications)
|
46
44
|
rescue StandardError => e
|
47
45
|
Rapns.logger.error(e)
|
48
46
|
reflect(:error, e)
|
@@ -8,10 +8,11 @@ module Rapns
|
|
8
8
|
GCM_URI = URI.parse('https://android.googleapis.com/gcm/send')
|
9
9
|
UNAVAILABLE_STATES = ['Unavailable', 'InternalServerError']
|
10
10
|
|
11
|
-
def initialize(app, http, notification)
|
11
|
+
def initialize(app, http, notification, batch)
|
12
12
|
@app = app
|
13
13
|
@http = http
|
14
14
|
@notification = notification
|
15
|
+
@batch = batch
|
15
16
|
end
|
16
17
|
|
17
18
|
def perform
|
@@ -121,7 +122,7 @@ module Rapns
|
|
121
122
|
|
122
123
|
def deliver_after_header(response)
|
123
124
|
if response.header['retry-after']
|
124
|
-
|
125
|
+
if response.header['retry-after'].to_s =~ /^[0-9]+$/
|
125
126
|
Time.now + response.header['retry-after'].to_i
|
126
127
|
else
|
127
128
|
Time.httpdate(response.header['retry-after'])
|
@@ -131,9 +132,9 @@ module Rapns
|
|
131
132
|
|
132
133
|
def retry_delivery(notification, response)
|
133
134
|
if time = deliver_after_header(response)
|
134
|
-
|
135
|
+
mark_retryable(notification, time)
|
135
136
|
else
|
136
|
-
|
137
|
+
mark_retryable_exponential(notification)
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
@@ -7,8 +7,8 @@ module Rapns
|
|
7
7
|
@http = Net::HTTP::Persistent.new('rapns')
|
8
8
|
end
|
9
9
|
|
10
|
-
def deliver(notification)
|
11
|
-
Rapns::Daemon::Gcm::Delivery.
|
10
|
+
def deliver(notification, batch)
|
11
|
+
Rapns::Daemon::Gcm::Delivery.new(@app, @http, notification, batch).perform
|
12
12
|
end
|
13
13
|
|
14
14
|
def stopped
|
@@ -13,11 +13,11 @@ module Rapns
|
|
13
13
|
batch_size = Rapns.config.batch_size
|
14
14
|
relation = Rapns::Notification.ready_for_delivery.for_apps(apps)
|
15
15
|
relation = relation.limit(batch_size) unless Rapns.config.push
|
16
|
-
relation.
|
16
|
+
relation.to_a
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
20
|
+
def mark_retryable(notification, deliver_after)
|
21
21
|
with_database_reconnect_and_retry do
|
22
22
|
notification.retries += 1
|
23
23
|
notification.deliver_after = deliver_after
|
@@ -25,6 +25,13 @@ module Rapns
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def mark_batch_retryable(notifications, deliver_after)
|
29
|
+
ids = notifications.map(&:id)
|
30
|
+
with_database_reconnect_and_retry do
|
31
|
+
Rapns::Notification.where(:id => ids).update_all(['retries = retries + 1, deliver_after = ?', deliver_after])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
28
35
|
def mark_delivered(notification)
|
29
36
|
with_database_reconnect_and_retry do
|
30
37
|
notification.delivered = true
|
@@ -33,6 +40,13 @@ module Rapns
|
|
33
40
|
end
|
34
41
|
end
|
35
42
|
|
43
|
+
def mark_batch_delivered(notifications)
|
44
|
+
ids = notifications.map(&:id)
|
45
|
+
with_database_reconnect_and_retry do
|
46
|
+
Rapns::Notification.where(:id => ids).update_all(['delivered = true, delivered_at = ?', Time.now])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
36
50
|
def mark_failed(notification, code, description)
|
37
51
|
with_database_reconnect_and_retry do
|
38
52
|
notification.delivered = false
|
@@ -45,6 +59,13 @@ module Rapns
|
|
45
59
|
end
|
46
60
|
end
|
47
61
|
|
62
|
+
def mark_batch_failed(notifications, code, description)
|
63
|
+
ids = notifications.map(&:id)
|
64
|
+
with_database_reconnect_and_retry do
|
65
|
+
Rapns::Notification.where(:id => ids).update_all(['delivered = false, delivered_at = NULL, failed = true, failed_at = ?, error_code = ?, error_description = ?', Time.now, code, description])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
48
69
|
def create_apns_feedback(failed_at, device_token, app)
|
49
70
|
with_database_reconnect_and_retry do
|
50
71
|
Rapns::Apns::Feedback.create!(:failed_at => failed_at,
|
data/lib/rapns/deprecation.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
module Rapns
|
2
2
|
class Deprecation
|
3
|
-
def self.
|
3
|
+
def self.muted
|
4
4
|
begin
|
5
|
-
Thread.current[:
|
5
|
+
orig_val = Thread.current[:rapns_mute_deprecations]
|
6
|
+
Thread.current[:rapns_mute_deprecations] = true
|
6
7
|
yield
|
7
8
|
ensure
|
8
|
-
Thread.current[:
|
9
|
+
Thread.current[:rapns_mute_deprecations] = orig_val
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
def self.
|
13
|
-
Thread.current[:
|
13
|
+
def self.muted?
|
14
|
+
Thread.current[:rapns_mute_deprecations] == true
|
14
15
|
end
|
15
16
|
|
16
17
|
def self.warn(msg)
|
17
|
-
unless Rapns::Deprecation.
|
18
|
+
unless Rapns::Deprecation.muted?
|
18
19
|
STDERR.puts "DEPRECATION WARNING: #{msg}"
|
19
20
|
end
|
20
21
|
end
|
data/lib/rapns/logger.rb
CHANGED
@@ -32,7 +32,7 @@ module Rapns
|
|
32
32
|
def setup_logger(log)
|
33
33
|
if Rapns.config.logger
|
34
34
|
@logger = Rapns.config.logger
|
35
|
-
elsif
|
35
|
+
elsif ActiveSupport.const_defined?('BufferedLogger')
|
36
36
|
@logger = ActiveSupport::BufferedLogger.new(log, Rails.logger.level)
|
37
37
|
@logger.auto_flushing = Rails.logger.respond_to?(:auto_flushing) ? Rails.logger.auto_flushing : true
|
38
38
|
else
|
data/lib/rapns/notification.rb
CHANGED
@@ -9,9 +9,11 @@ module Rapns
|
|
9
9
|
|
10
10
|
belongs_to :app, :class_name => 'Rapns::App'
|
11
11
|
|
12
|
-
|
13
|
-
:
|
14
|
-
|
12
|
+
if Rapns.attr_accessible_available?
|
13
|
+
attr_accessible :badge, :device_token, :sound, :alert, :data, :expiry,:delivered,
|
14
|
+
:delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after,
|
15
|
+
:alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids
|
16
|
+
end
|
15
17
|
|
16
18
|
validates :expiry, :numericality => true, :allow_nil => true
|
17
19
|
validates :app, :presence => true
|
data/lib/rapns/reflection.rb
CHANGED
@@ -13,7 +13,7 @@ module Rapns
|
|
13
13
|
REFLECTIONS = [
|
14
14
|
:apns_feedback, :notification_enqueued, :notification_delivered,
|
15
15
|
:notification_failed, :notification_will_retry, :apns_connection_lost,
|
16
|
-
:gcm_canonical_id, :error
|
16
|
+
:gcm_canonical_id, :error, :apns_certificate_will_expire
|
17
17
|
]
|
18
18
|
|
19
19
|
REFLECTIONS.each do |reflection|
|
data/lib/rapns/version.rb
CHANGED
data/lib/tasks/cane.rake
CHANGED
@@ -3,7 +3,7 @@ begin
|
|
3
3
|
|
4
4
|
desc "Run cane to check quality metrics"
|
5
5
|
Cane::RakeTask.new(:quality) do |cane|
|
6
|
-
cane.add_threshold 'coverage/covered_percent', :>=,
|
6
|
+
cane.add_threshold 'coverage/covered_percent', :>=, 98
|
7
7
|
cane.no_style = false
|
8
8
|
cane.style_measure = 1000
|
9
9
|
cane.no_doc = true
|
data/lib/tasks/test.rake
CHANGED
@@ -2,9 +2,13 @@ namespace :test do
|
|
2
2
|
task :build_rails do
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
|
-
def cmd(str)
|
5
|
+
def cmd(str, clean_env = true)
|
6
6
|
puts "* #{str}"
|
7
|
-
retval =
|
7
|
+
retval = if clean_env
|
8
|
+
Bundler.with_clean_env { `#{str}` }
|
9
|
+
else
|
10
|
+
`#{str}`
|
11
|
+
end
|
8
12
|
puts retval.strip
|
9
13
|
retval
|
10
14
|
end
|
@@ -15,7 +19,8 @@ namespace :test do
|
|
15
19
|
FileUtils.mkdir_p(path)
|
16
20
|
pwd = Dir.pwd
|
17
21
|
|
18
|
-
cmd("bundle exec rails
|
22
|
+
cmd("bundle exec rails --version", false)
|
23
|
+
cmd("bundle exec rails new #{path} --skip-bundle", false)
|
19
24
|
|
20
25
|
begin
|
21
26
|
Dir.chdir(path)
|
data/spec/unit/apns/app_spec.rb
CHANGED
@@ -2,26 +2,26 @@ require 'unit_spec_helper'
|
|
2
2
|
|
3
3
|
describe Rapns::App do
|
4
4
|
it 'does not validate an app with an invalid certificate' do
|
5
|
-
app = Rapns::Apns::App.new(:
|
5
|
+
app = Rapns::Apns::App.new(:name => 'test', :environment => 'development', :certificate => 'foo')
|
6
6
|
app.valid?
|
7
7
|
app.errors[:certificate].should == ['Certificate value must contain a certificate and a private key.']
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'validates a certificate without a password' do
|
11
|
-
app = Rapns::Apns::App.new :
|
11
|
+
app = Rapns::Apns::App.new :name => 'test', :environment => 'development', :certificate => TEST_CERT
|
12
12
|
app.valid?
|
13
13
|
app.errors[:certificate].should == []
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'validates a certificate with a password' do
|
17
|
-
app = Rapns::Apns::App.new :
|
17
|
+
app = Rapns::Apns::App.new :name => 'test', :environment => 'development',
|
18
18
|
:certificate => TEST_CERT_WITH_PASSWORD, :password => 'fubar'
|
19
19
|
app.valid?
|
20
20
|
app.errors[:certificate].should == []
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'validates a certificate with an incorrect password' do
|
24
|
-
app = Rapns::Apns::App.new :
|
24
|
+
app = Rapns::Apns::App.new :name => 'test', :environment => 'development',
|
25
25
|
:certificate => TEST_CERT_WITH_PASSWORD, :password => 'incorrect'
|
26
26
|
app.valid?
|
27
27
|
app.errors[:certificate].should == ["Certificate value must contain a certificate and a private key."]
|