Skip to content

Commit

Permalink
Wrote tests for the Delivery class
Browse files Browse the repository at this point in the history
  • Loading branch information
peter committed Dec 2, 2011
1 parent be7d60c commit 915dc1b
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 26 deletions.
25 changes: 22 additions & 3 deletions .rvmrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# development environment upon cd'ing into the directory

# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
environment_id="ruby-1.9.2-p290@apn_client"
environment_id="ruby-1.9.2-p180@apn_client"

#
# Uncomment following line if you want options to be set only for given project.
Expand All @@ -31,7 +31,7 @@ else
if ! rvm --create "$environment_id"
then
echo "Failed to create RVM environment '${environment_id}'."
exit 1
return 1
fi
fi

Expand All @@ -41,7 +41,26 @@ fi
# necessary.
#
# filename=".gems"
# if [[ -s "$filename" ]] ; then
# if [[ -s "$filename" ]]
# then
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
# fi

# If you use bundler, this might be useful to you:
# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
# then
# printf "The rubygem 'bundler' is not installed. Installing it now.\n"
# gem install bundler
# fi
# if [[ -s Gemfile ]] && command -v bundle
# then
# bundle install
# fi

if [[ $- == *i* ]] # check for interactive shells
then
echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
else
echo "Using: $GEM_HOME" # don't use colors in interactive shells
fi

4 changes: 2 additions & 2 deletions lib/apn_client/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def close
self.tcp_socket = nil
end

def write(*args)
ssl_socket.write(*args)
def write(arg)
ssl_socket.write(arg.to_s)
end

def read(*args)
Expand Down
45 changes: 27 additions & 18 deletions lib/apn_client/delivery.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
require 'apn_client/named_args'
require 'apn_client/message'
require 'apn_client/connection'

module ApnClient
class Delivery
attr_accessor :message_queue, :callbacks, :consecutive_failure_limit, :exception_limit, :sleep_on_exception,
attr_accessor :messages, :callbacks, :consecutive_failure_limit, :exception_limit, :sleep_on_exception,
:exception_count, :success_count, :failure_count, :consecutive_failure_count,
:started_at, :finished_at

# Creates a new APN delivery
#
# @param [#next] messages should be Enumerator type object that responds to #next. If it's an Array #shift will be used instead.
def initialize(messages, options = {})
initialize_message_queue(messages)
self.messages = messages
initialize_options(options)
self.exception_count = 0
self.success_count = 0
self.failure_count = 0
self.consecutive_failure_count = 0
end

def process!
Expand All @@ -22,25 +30,25 @@ def process!
end

def elapsed
finished_at ? (finished_at - started_at) : (Time.now - started_at)
end

private

def initialize_message_queue(messages)
if messages.respond_to?(:next)
self.message_queue = messages
if started_at
finished_at ? (finished_at - started_at) : (Time.now - started_at)
else
self.message_queue = messages.to_enum
0
end
end

def total_count
success_count + failure_count
end

private

def initialize_options(options)
check_option_keys_valid!(options)
check_callback_keys_valid!(options[:callbacks])
NamedArgs.assert_valid!(options, :optional => [:callbacks, :consecutive_failure_limit, :exception_limit, :sleep_on_exception])
NamedArgs.assert_valid!(options[:callbacks], :optional => [:on_write, :on_error, :on_nil_select, :on_read_exception, :on_exception, :on_failure])
self.callbacks = options[:callbacks]
self.consecutive_failures_limit = options[:consecutive_failures_limit]
self.exception_limit = options[:exception_limit]
self.consecutive_failure_limit = options[:consecutive_failure_limit] || 10
self.exception_limit = options[:exception_limit] || 3
self.sleep_on_exception = options[:sleep_on_exception] || 1
end

Expand All @@ -50,7 +58,7 @@ def current_message
end

def next_message!
@current_message = message_queue.next
@current_message = (messages.respond_to?(:next) ? messages.next : messages.shift)
rescue StopIteration
nil
end
Expand Down Expand Up @@ -103,7 +111,7 @@ def read_apns_error
select_return = nil
if connection && select_return = connection.select
response = connection.read(6)
command, status_code, message_id = response.unpack('cci') if response
command, error_code, message_id = response.unpack('cci') if response
else
invoke_callback(:on_nil_select)
end
Expand All @@ -115,6 +123,7 @@ def read_apns_error
end

def handle_exception!(e)
invoke_callback(:on_exception, e)
self.exception_count += 1
fail_message! if exception_limit_reached?
sleep(sleep_on_exception) if sleep_on_exception
Expand All @@ -126,7 +135,7 @@ def exception_limit_reached?

# # Give up on the message and move on to the next one
def fail_message!
self.failure_count += 1; self.consecutive_failures += 1; self.exception_count = 0
self.failure_count += 1; self.consecutive_failure_count += 1; self.exception_count = 0
invoke_callback(:on_failure, current_message)
next_message!
end
Expand Down
3 changes: 2 additions & 1 deletion lib/apn_client/named_args.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module ApnClient
class NamedArgs
def self.assert_valid!(arguments, options = {})
def self.assert_valid!(arguments, options)
arguments ||= {}
options[:optional] ||= []
options[:required] ||= []
assert_allowed!(arguments, options[:optional] + options[:required])
Expand Down
4 changes: 2 additions & 2 deletions spec/connection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
ApnClient::Connection.any_instance.expects(:connect_to_socket)
connection = ApnClient::Connection.new(valid_config)
ssl_socket = mock('ssl_socket')
ssl_socket.expects(:write).with("foo", "bar")
ssl_socket.expects(:write).with('foo')
connection.expects(:ssl_socket).returns(ssl_socket)
connection.write("foo", "bar")
connection.write(:foo)
end
end

Expand Down
136 changes: 136 additions & 0 deletions spec/delivery_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
require 'spec_helper'

require 'apn_client/delivery'

describe ApnClient::Delivery do
before(:each) do
@message1 = ApnClient::Message.new(1,
:device_token => "7b7b8de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d099",
:alert => "New version of the app is out. Get it now in the app store!",
:badge => 2
)
@message2 = ApnClient::Message.new(2,
:device_token => "6a5g4de5888bb742ba744a2a5c8e52c6481d1deeecc283e830533b7c6bf1d044",
:alert => "New version of the app is out. Get it now in the app store!",
:badge => 1
)
end

describe "#initialize" do
it "initializes counts and other attributes" do
delivery = create_delivery([@message1, @message2])
end
end

describe "#process!" do
it "can deliver to all messages successfully and invoke on_write callback" do
messages = [@message1, @message2]
written_messages = []
nil_selects = 0
callbacks = {
:on_write => lambda { |d, m| written_messages << m },
:on_nil_select => lambda { |d| nil_selects += 1 }
}
delivery = create_delivery(messages.dup, :callbacks => callbacks)

connection = mock('connection')
connection.expects(:write).with(@message1)
connection.expects(:write).with(@message2)
connection.expects(:select).times(2).returns(nil)
delivery.stubs(:connection).returns(connection)

delivery.process!

delivery.failure_count.should == 0
delivery.success_count.should == 2
delivery.total_count.should == 2
written_messages.should == messages
nil_selects.should == 2
end

it "fails a message if it fails more than 3 times" do
messages = [@message1, @message2]
written_messages = []
exceptions = []
failures = []
read_exceptions = []
callbacks = {
:on_write => lambda { |d, m| written_messages << m },
:on_exception => lambda { |d, e| exceptions << e },
:on_failure => lambda { |d, m| failures << m },
:on_read_exception => lambda { |d, e| read_exceptions << e }
}
delivery = create_delivery(messages.dup, :callbacks => callbacks)

connection = mock('connection')
connection.expects(:write).with(@message1).times(3).raises(RuntimeError)
connection.expects(:write).with(@message2)
connection.expects(:select).times(4).raises(RuntimeError)
delivery.stubs(:connection).returns(connection)

delivery.process!

delivery.failure_count.should == 1
delivery.success_count.should == 1
delivery.total_count.should == 2
written_messages.should == [@message2]
exceptions.size.should == 3
exceptions.first.is_a?(RuntimeError).should be_true
failures.should == [@message1]
read_exceptions.size.should == 4
end

it "invokes on_error callback if there are errors read" do
messages = [@message1, @message2]
written_messages = []
exceptions = []
failures = []
read_exceptions = []
errors = []
callbacks = {
:on_write => lambda { |d, m| written_messages << m },
:on_exception => lambda { |d, e| exceptions << e },
:on_failure => lambda { |d, m| failures << m },
:on_read_exception => lambda { |d, e| read_exceptions << e },
:on_error => lambda { |d, message_id, error_code| errors << [message_id, error_code] }
}
delivery = create_delivery(messages.dup, :callbacks => callbacks)

connection = mock('connection')
connection.expects(:write).with(@message1)
connection.expects(:write).with(@message2)
selects = sequence('selects')
connection.expects(:select).returns("something").in_sequence(selects)
connection.expects(:select).returns(nil).in_sequence(selects)
connection.expects(:read).returns("something")
delivery.stubs(:connection).returns(connection)

delivery.process!

delivery.failure_count.should == 1
delivery.success_count.should == 1
delivery.total_count.should == 2
written_messages.should == [@message1, @message2]
exceptions.size.should == 0
failures.size.should == 0
errors.should == [[1752458605, 111]]
end
end

def create_delivery(messages, options = {})
delivery = ApnClient::Delivery.new(messages, options)
delivery.messages.should == messages
delivery.callbacks.should == options[:callbacks]
delivery.exception_count.should == 0
delivery.success_count.should == 0
delivery.failure_count.should == 0
delivery.consecutive_failure_count.should == 0
delivery.started_at.should be_nil
delivery.finished_at.should be_nil
delivery.elapsed.should == 0
delivery.consecutive_failure_limit.should == 10
delivery.exception_limit.should == 3
delivery.sleep_on_exception.should == 1
delivery
end
end
4 changes: 4 additions & 0 deletions spec/named_args_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,9 @@
ApnClient::NamedArgs.assert_valid!({:foo => 1, :bar => 2, :bla => 3}, :required => [:foo], :optional => [:bar])
}.should raise_error(/bla/)
end

it "does not raise an exception if args are nil and all keys are optional" do
ApnClient::NamedArgs.assert_valid!(nil, :optional => [:bar])
end
end
end

0 comments on commit 915dc1b

Please sign in to comment.