require File.expand_path '../helper', __FILE__ class SenderTest < Test::Unit::TestCase def setup reset_config end def build_sender(opts = {}) HydraulicBrake.configure do |conf| opts.each {|opt, value| conf.send(:"#{opt}=", value) } end end def build_logger klass = Class.new do attr_reader :errors, :infos, :debugs def error(i); @errors ||= []; @errors << i; end def info(i); @infos ||= []; @infos << i; end def debug(i); @debugs ||= []; @debugs << i; end end klass.new end def send_exception(args = {}) notice = args.delete(:notice) || HydraulicBrake::Notice.new(args) sender = args.delete(:sender) || build_sender(args) sender.send_to_airbrake(notice) end def stub_http(response=nil) response ||= stub(:body => 'body') http = stub(:post => response, :read_timeout= => nil, :open_timeout= => nil, :ca_file= => nil, :verify_mode= => nil, :use_ssl= => nil) Net::HTTP.stubs(:new => http) http end should "post to Airbrake with a Notice instance passed" do notice = HydraulicBrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar") http = stub_http sender = build_sender sender.send_to_airbrake(notice) assert_received(http, :post) do |expect| expect.with(anything, notice.to_xml, HydraulicBrake::HEADERS) end end should "post to Airbrake when using an HTTP proxy" do response = stub(:body => 'body') http = stub(:post => response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) proxy = stub(:new => http) Net::HTTP.stubs(:Proxy => proxy) url = "http://api.airbrake.io:80#{HydraulicBrake::Sender::NOTICES_URI}" uri = URI.parse(url) proxy_host = 'some.host' proxy_port = 88 proxy_user = 'login' proxy_pass = 'passwd' send_exception(:proxy_host => proxy_host, :proxy_port => proxy_port, :proxy_user => proxy_user, :proxy_pass => proxy_pass) assert_received(http, :post) do |expect| expect.with(uri.path, anything, HydraulicBrake::HEADERS) end assert_received(Net::HTTP, :Proxy) do |expect| expect.with(proxy_host, proxy_port, proxy_user, proxy_pass) end end should "return the created group's id on successful posting" do response = Net::HTTPSuccess.new(1.0, 200, "OK") def response.body '3799307' end stub_http(response) assert_equal "3799307", send_exception(:secure => false) end should "log the url to the error on successful posting" do response = Net::HTTPSuccess.new(1.0, 200, "OK") def response.body '3799307http://some-url.com/foo' end logger = build_logger HydraulicBrake.configure do |c| c.logger = logger end stub_http(response) send_exception(:secure => false) matcher = %r{Success: sent error to Airbrake: http://some-url.com/foo} assert_match matcher, logger.infos.first end context "when encountering exceptions: " do context "HTTP connection setup problems" do should "not be rescued" do proxy = stub() proxy.stubs(:new).raises(NoMemoryError) Net::HTTP.stubs(:Proxy => proxy) assert_raise NoMemoryError do build_sender.send(:setup_http_connection) end end should "be logged" do proxy = stub() proxy.stubs(:new).raises(RuntimeError) Net::HTTP.stubs(:Proxy => proxy) sender = build_sender sender.expects(:log) assert_raise RuntimeError do sender.send(:setup_http_connection) end end end context "unexpected exception sending problems" do should "be logged" do sender = build_sender sender.stubs(:setup_http_connection).raises(RuntimeError.new) sender.expects(:log) send_exception(:sender => sender) end should "return nil no matter what" do sender = build_sender sender.stubs(:setup_http_connection).raises(LocalJumpError) assert_nothing_thrown do assert_nil sender.send_to_airbrake(build_notice_data) end end end should "return nil on failed posting" do http = stub_http http.stubs(:post).raises(Errno::ECONNREFUSED) assert_equal nil, send_exception(:secure => false) end should "not fail when posting and a timeout exception occurs" do http = stub_http http.stubs(:post).raises(TimeoutError) assert_nothing_thrown do send_exception(:secure => false) end end should "not fail when posting and a connection refused exception occurs" do http = stub_http http.stubs(:post).raises(Errno::ECONNREFUSED) assert_nothing_thrown do send_exception(:secure => false) end end should "not fail when posting any http exception occurs" do http = stub_http HydraulicBrake::Sender::HTTP_ERRORS.each do |error| http.stubs(:post).raises(error) assert_nothing_thrown do send_exception(:secure => false) end end end end context "SSL" do should "post to the right url for non-ssl" do http = stub_http url = "http://api.airbrake.io:80#{HydraulicBrake::Sender::NOTICES_URI}" uri = URI.parse(url) send_exception(:secure => false) assert_received(http, :post) {|expect| expect.with(uri.path, anything, HydraulicBrake::HEADERS) } end should "post to the right path for ssl" do http = stub_http send_exception(:secure => true) assert_received(http, :post) {|expect| expect.with(HydraulicBrake::Sender::NOTICES_URI, anything, HydraulicBrake::HEADERS) } end should "verify the SSL peer when the use_ssl option is set to true" do url = "https://api.airbrake.io#{HydraulicBrake::Sender::NOTICES_URI}" uri = URI.parse(url) real_http = Net::HTTP.new(uri.host, uri.port) real_http.stubs(:post => nil) proxy = stub(:new => real_http) Net::HTTP.stubs(:Proxy => proxy) File.stubs(:exist?).with(OpenSSL::X509::DEFAULT_CERT_FILE).returns(false) send_exception(:secure => true) assert(real_http.use_ssl?) assert_equal(OpenSSL::SSL::VERIFY_PEER, real_http.verify_mode) assert_equal(HydraulicBrake.configuration.local_cert_path, real_http.ca_file) end should "use the default DEFAULT_CERT_FILE if asked to" do config = HydraulicBrake::Configuration.new config.use_system_ssl_cert_chain = true sender = HydraulicBrake::Sender.new(config) assert(sender.use_system_ssl_cert_chain?) http = sender.send(:setup_http_connection) assert_not_equal http.ca_file, config.local_cert_path end should "verify the connection when the use_ssl option is set (VERIFY_PEER)" do sender = build_sender(:secure => true) http = sender.send(:setup_http_connection) assert_equal(OpenSSL::SSL::VERIFY_PEER, http.verify_mode) end should "use the default cert (OpenSSL::X509::DEFAULT_CERT_FILE) only if explicitly told to" do sender = build_sender(:secure => true) http = sender.send(:setup_http_connection) assert_equal(HydraulicBrake.configuration.local_cert_path, http.ca_file) File.stubs(:exist?).with(OpenSSL::X509::DEFAULT_CERT_FILE).returns(true) sender = build_sender(:secure => true, :use_system_ssl_cert_chain => true) http = sender.send(:setup_http_connection) assert_not_equal(HydraulicBrake.configuration.local_cert_path, http.ca_file) assert_equal(OpenSSL::X509::DEFAULT_CERT_FILE, http.ca_file) end should "connect to the right port for ssl" do stub_http send_exception(:secure => true) assert_received(Net::HTTP, :new) {|expect| expect.with("api.airbrake.io", 443) } end should "connect to the right port for non-ssl" do stub_http send_exception(:secure => false) assert_received(Net::HTTP, :new) {|expect| expect.with("api.airbrake.io", 80) } end should "use ssl if secure" do stub_http send_exception(:secure => true, :host => 'example.org') assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 443) } end should "not use ssl if not secure" do stub_http send_exception(:secure => false, :host => 'example.org') assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 80) } end end context "network timeouts" do should "default the open timeout to 2 seconds" do http = stub_http send_exception assert_received(http, :open_timeout=) {|expect| expect.with(2) } end should "default the read timeout to 5 seconds" do http = stub_http send_exception assert_received(http, :read_timeout=) {|expect| expect.with(5) } end should "allow override of the open timeout" do http = stub_http send_exception(:http_open_timeout => 4) assert_received(http, :open_timeout=) {|expect| expect.with(4) } end should "allow override of the read timeout" do http = stub_http send_exception(:http_read_timeout => 10) assert_received(http, :read_timeout=) {|expect| expect.with(10) } end end end