Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…into fuzzygroup-master

Conflicts:
	lib/freshbooks/connection.rb
  • Loading branch information
bcurren committed Feb 13, 2014
2 parents 53f65d5 + 996c91b commit 332538c
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 58 deletions.
52 changes: 26 additions & 26 deletions lib/freshbooks/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
module FreshBooks
class Connection
attr_reader :account_url, :auth_token, :utc_offset, :request_headers

@@logger = Logger.new(STDOUT)
def logger
@@logger
Expand All @@ -19,78 +19,78 @@ def self.log_level=(level)
@@logger.level = level
end
self.log_level = Logger::WARN

def initialize(account_url, auth_token, request_headers = {}, options = {})
raise InvalidAccountUrlError.new unless account_url =~ /^[0-9a-zA-Z\-_]+\.(freshbooks|billingarm)\.com$/
raise InvalidAccountUrlError.new("account_url is expected to be in the form www.example.com without any protocol string or trailing query parameters") unless account_url =~ /^[0-9a-zA-Z\-_]+\.(freshbooks|billingarm)\.com$/

@account_url = account_url
@auth_token = auth_token
@request_headers = request_headers
@utc_offset = options[:utc_offset] || -4
@start_session_count = 0
end

def call_api(method, elements = [])
request = create_request(method, elements)
result = post(request)
Response.new(result)
end

def direct_post(xml)
result = post(xml)
Response.new(result)
end

def start_session(&block)
@connection = obtain_connection if @start_session_count == 0
@start_session_count = @start_session_count + 1

begin
block.call(@connection)
ensure
@start_session_count = @start_session_count - 1
close if @start_session_count == 0
end
end

protected

def create_request(method, elements = [])
doc = REXML::Document.new '<?xml version="1.0" encoding="UTF-8"?>'
request = doc.add_element('request')
request.attributes['method'] = method

elements.each do |element|
if element.kind_of?(Hash)
element = element.to_a
end
key = element.first
value = element.last

if value.kind_of?(Base)
request.add_element(REXML::Document.new(value.to_xml))
else
request.add_element(REXML::Element.new(key.to_s)).text = value.to_s
end
end

doc.to_s
end

def obtain_connection(force = false)
return @connection if @connection && !force

@connection = Net::HTTP.new(@account_url, 443)
@connection.use_ssl = true
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
@connection.start
end

def reconnect
close
obtain_connection(true)
end

def close
begin
@connection.finish if @connection
Expand All @@ -99,7 +99,7 @@ def close
end
@connection = nil
end

def post(request_body)
result = nil
request = Net::HTTP::Post.new(FreshBooks::SERVICE_URL)
Expand All @@ -109,19 +109,19 @@ def post(request_body)
@request_headers.each_pair do |name, value|
request[name.to_s] = value
end

result = post_request(request)

if logger.debug?
logger.debug "Request:"
logger.debug request_body
logger.debug "Response:"
logger.debug result.body
end

check_for_api_error(result)
end

# For connections that take a long time, we catch EOFError's and reconnect seamlessly
def post_request(request)
response = nil
Expand All @@ -131,18 +131,18 @@ def post_request(request)
response = connection.request(request)
rescue EOFError => e
raise e if has_reconnected

has_reconnected = true
connection = reconnect
retry
end
end
response
end

def check_for_api_error(result)
return result.body if result.kind_of?(Net::HTTPSuccess)

case result
when Net::HTTPRedirection
if result["location"] =~ /loginSearch/
Expand All @@ -155,7 +155,7 @@ def check_for_api_error(result)
when Net::HTTPBadRequest
raise ApiAccessNotEnabledError.new("API not enabled.")
end

raise InternalError.new("Invalid HTTP code: #{result.class}")
end
end
Expand Down
71 changes: 39 additions & 32 deletions test/test_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,142 +4,149 @@ class TestConnection < Test::Unit::TestCase
def setup
@connection = FreshBooks::Connection.new("company.freshbooks.com", "auth_token")
end

def test_connection_accessors
assert_equal "company.freshbooks.com", @connection.account_url
assert_equal "auth_token", @connection.auth_token
end

def test_connection_request_headers
request_headers = { "key" => "value" }
connection = FreshBooks::Connection.new("company.freshbooks.com", "auth_token", request_headers)
assert_equal request_headers, connection.request_headers
end


def test_create_request__array_of_elements
request = @connection.send(:create_request, 'mymethod', [['element1', 'value1'], [:element2, :value2]])
assert_equal "<?xml version='1.0' encoding='UTF-8'?><request method='mymethod'><element1>value1</element1><element2>value2</element2></request>", request
end

def test_create_request__base_object_element
invoice = FreshBooks::Invoice.new
invoice.expects(:to_xml).with().returns("<invoice><number>23</number></invoice>")

request = @connection.send(:create_request, 'mymethod', 'invoice' => invoice)
assert_equal "<?xml version='1.0' encoding='UTF-8'?><request method='mymethod'><invoice><number>23</number></invoice></request>", request
end

def test_check_for_api_error__success
body = "body xml"
response = Net::HTTPSuccess.new("1.1", "200", "message")
response.expects(:body).with().returns(body)
assert_equal body, @connection.send(:check_for_api_error, response)
end

def test_check_for_api_error__unknown_system
response = Net::HTTPMovedPermanently.new("1.1", "301", "message")
response.stubs(:[]).with("location").returns("loginSearch")
assert_raise(FreshBooks::UnknownSystemError) do
@connection.send(:check_for_api_error, response)
end
end

def test_check_for_api_error__deactivated
response = Net::HTTPMovedPermanently.new("1.1", "301", "message")
response.stubs(:[]).with("location").returns("deactivated")
assert_raise(FreshBooks::AccountDeactivatedError) do
@connection.send(:check_for_api_error, response)
end
end

def test_check_for_api_error__unauthorized
response = Net::HTTPUnauthorized.new("1.1", "401", "message")
assert_raise(FreshBooks::AuthenticationError) do
@connection.send(:check_for_api_error, response)
end
end

def test_check_for_api_error__bad_request
response = Net::HTTPBadRequest.new("1.1", "401", "message")
assert_raise(FreshBooks::ApiAccessNotEnabledError) do
@connection.send(:check_for_api_error, response)
end
end

def test_check_for_api_error__internal_error
response = Net::HTTPBadGateway.new("1.1", "502", "message")
assert_raise(FreshBooks::InternalError) do
@connection.send(:check_for_api_error, response)
end

response = Net::HTTPMovedPermanently.new("1.1", "301", "message")
response.stubs(:[]).with("location").returns("somePage")
assert_raise(FreshBooks::InternalError) do
@connection.send(:check_for_api_error, response)
end
end

def test_close_is_only_called_once_in_ntexted_start_sessions
@connection.expects(:obtain_connection)
@connection.expects(:close)

@connection.start_session { @connection.start_session { } }
end

def test_reconnect
connection = stub()

@connection.expects(:close).with()
@connection.expects(:obtain_connection).with(true).returns(connection)

assert_equal connection, @connection.send(:reconnect)
end

def test_post_request_successfull_request
request = "<request></request>"
response = "<response></response>"

http_connection = stub()
http_connection.expects(:request).with(request).returns(response)
@connection.expects(:start_session).with().yields(http_connection)

assert_equal response, @connection.send(:post_request, request)
end

def test_post_request_eof_error_retry
request = "<request></request>"
response = "<response></response>"
eof_error = EOFError.new("End of file error")

bad_http_connection = stub()
bad_http_connection.expects(:request).with(request).raises(eof_error)

new_http_connection = stub()
new_http_connection.expects(:request).with(request).returns(response)

@connection.expects(:start_session).with().yields(bad_http_connection)
@connection.expects(:reconnect).with().returns(new_http_connection)

assert_equal response, @connection.send(:post_request, request)
end

def test_post_request_eof_error_retry_only_retry_once
request = "<request></request>"
response = "<response></response>"
eof_error = EOFError.new("End of file error")

bad_http_connection = stub()
bad_http_connection.expects(:request).with(request).raises(eof_error)

new_http_connection = stub()
new_http_connection.expects(:request).with(request).raises(eof_error)

@connection.expects(:start_session).with().yields(bad_http_connection)
@connection.expects(:reconnect).with().returns(new_http_connection)
assert_raises(EOFError, eof_error.message) do

assert_raises(EOFError, eof_error.message) do
@connection.send(:post_request, request)
end
end

def test_format_of_account_url_establish_connection
exception = assert_raise(FreshBooks::InvalidAccountUrlError) do
FreshBooks::Connection.new("https://company.freshbooks.com", "auth_token")
end
assert_equal "account_url is expected to be in the form www.example.com without any protocol string or trailing query parameters", exception.message
end
end

0 comments on commit 332538c

Please sign in to comment.