# encoding: utf-8

require 'logger'
require 'socket'
require 'girl_friday'
require 'redis'
require 'active_support/core_ext/object'
require 'active_support/json/encoding'

require 'rollbar/item'
require 'ostruct'

begin
  require 'rollbar/delay/sidekiq'
  require 'rollbar/delay/sucker_punch'
rescue LoadError
end

begin
  require 'sucker_punch'
  require 'sucker_punch/testing/inline'
rescue LoadError
end

begin
  require 'rollbar/delay/shoryuken'
rescue LoadError
end

require 'spec_helper'

describe Rollbar do
  let(:notifier) { Rollbar.notifier }

  before do
    Rollbar.clear_notifier!
    configure
  end

  context 'when notifier has been used before configure it' do
    before do
      Rollbar.clear_notifier!
    end

    it 'is finally reset' do
      Rollbar.log_debug('Testing notifier')
      expect(Rollbar.error('error message')).to be_eql('disabled')

      reconfigure_notifier

      expect(Rollbar.error('error message')).not_to be_eql('disabled')
    end
  end

  shared_examples 'stores the root notifier' do

  end

  describe '.configure' do
    before { Rollbar.clear_notifier! }

    it 'stores the root notifier' do
      Rollbar.configure { |c| }
      expect(Rollbar.root_notifier).to be(Rollbar.notifier)
    end
  end

  describe '.preconfigure' do
    before { Rollbar.clear_notifier! }

    it 'stores the root notifier' do
      Rollbar.preconfigure { |c| }
      expect(Rollbar.root_notifier).to be(Rollbar.notifier)
    end
  end

  describe '.reconfigure' do
    before { Rollbar.clear_notifier! }

    it 'stores the root notifier' do
      Rollbar.reconfigure { |c| }
      expect(Rollbar.root_notifier).to be(Rollbar.notifier)
    end
  end

  describe '.unconfigure' do
    before { Rollbar.clear_notifier! }

    it 'stores the root notifier' do
      expect(Rollbar.root_notifier).to receive(:unconfigure)

      Rollbar.unconfigure

      expect(Rollbar.root_notifier).to be(Rollbar.notifier)
    end
  end

  context 'Notifier' do
    describe '#log' do
      let(:exception) do
        begin
          foo = bar
        rescue => e
          e
        end
      end

      let(:configuration) { Rollbar.configuration }

      context 'executing a Thread before Rollbar is configured' do
        before do
          Rollbar.clear_notifier!

          Thread.new {}

          Rollbar.configure do |config|
            config.access_token = 'my-access-token'
          end
        end

        it 'sets correct configuration for Rollbar.notifier' do
          expect(Rollbar.notifier.configuration.enabled).to be_truthy
        end
      end

      it 'should report a simple message' do
        expect(notifier).to receive(:report).with('error', 'test message', nil, nil)
        notifier.log('error', 'test message')
      end

      it 'should report a simple message with extra data' do
        extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}}

        expect(notifier).to receive(:report).with('error', 'test message', nil, extra_data)
        notifier.log('error', 'test message', extra_data)
      end

      it 'should report an exception' do
        expect(notifier).to receive(:report).with('error', nil, exception, nil)
        notifier.log('error', exception)
      end

      it 'should report an exception with extra data' do
        extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}}

        expect(notifier).to receive(:report).with('error', nil, exception, extra_data)
        notifier.log('error', exception, extra_data)
      end

      it 'should report an exception with a description' do
        expect(notifier).to receive(:report).with('error', 'exception description', exception, nil)
        notifier.log('error', exception, 'exception description')
      end

      it 'should report an exception with a description and extra data' do
        extra_data = {:key => 'value', :hash => {:inner_key => 'inner_value'}}

        expect(notifier).to receive(:report).with('error', 'exception description', exception, extra_data)
        notifier.log('error', exception, extra_data, 'exception description')
      end
    end

    context 'with before_process handlers in configuration' do
      let!(:notifier) { Rollbar::Notifier.new }
      let(:scope) { { :bar => :foo } }
      let(:configuration) do
        config = Rollbar::Configuration.new
        config.access_token = test_access_token
        config.enabled = true
        config
      end
      let(:message) { 'message' }
      let(:exception) { Exception.new }
      let(:extra) { {:foo => :bar } }
      let(:level) { 'error' }

      before do
        notifier.configuration = configuration
        notifier.scope!(scope)
      end

      context 'without raise Rollbar::Ignore' do
        let(:handler) do
          proc do |options|

          end
        end

        before do
          configuration.before_process = handler
        end

        it 'calls the handler with the correct options' do
          options = {
            :level => level,
            :scope => Rollbar::LazyStore.new(scope),
            :exception => exception,
            :message => message,
            :extra => extra
          }

          expect(handler).to receive(:call).with(options)
          expect(notifier).to receive(:report).with(level, message, exception, extra)

          notifier.log(level, message, exception, extra)
        end
      end

      context 'raising Rollbar::Ignore in the handler' do
        let(:handler) do
          proc do |options|
            raise Rollbar::Ignore
          end
        end

        before do
          configuration.before_process = handler
        end

        it "calls the handler with correct options and doesn't call #report" do
          options = {
            :level => level,
            :scope => Rollbar::LazyStore.new(scope),
            :exception => exception,
            :message => message,
            :extra => extra
          }
          expect(handler).to receive(:call).with(options).and_call_original
          expect(notifier).not_to receive(:report)

          result = notifier.log(level, message, exception, extra)

          expect(result).to be_eql('ignored')
        end
      end

      context 'with 2 handlers, raising Rollbar::Ignore in the first one' do
        let(:handler1) do
          proc do |options|
            raise Rollbar::Ignore
          end
        end

        let(:handler2) do
          proc do |options|

          end
        end

        before do
          configuration.before_process << handler1
          configuration.before_process << handler2
        end

        it "calls only the first handler and doesn't calls #report" do
          options = {
            :level => level,
            :scope => Rollbar::LazyStore.new(scope),
            :exception => exception,
            :message => message,
            :extra => extra
          }

          expect(handler1).to receive(:call).with(options).and_call_original
          expect(handler2).not_to receive(:call)
          expect(notifier).not_to receive(:report)

          result = notifier.log(level, message, exception, extra)

          expect(result).to be_eql('ignored')
        end

        context 'if the first handler fails' do
          let(:exception) { StandardError.new('foo') }
          let(:handler1) do
            proc { |options|  raise exception }
          end

          it 'doesnt call the second handler and logs the error' do
            expect(handler2).not_to receive(:call)
            expect(notifier).to receive(:log_error).with("[Rollbar] Error calling the `before_process` hook: #{exception}")

            notifier.log(level, message, exception, extra)
          end
        end
      end
    end

    context 'debug/info/warning/error/critical' do
      let(:exception) do
        begin
          foo = bar
        rescue => e
          e
        end
      end

      let(:extra_data) { {:key => 'value', :hash => {:inner_key => 'inner_value'}} }

      it 'should report with a debug level' do
        expect(notifier).to receive(:report).with('debug', nil, exception, nil)
        notifier.debug(exception)

        expect(notifier).to receive(:report).with('debug', 'description', exception, nil)
        notifier.debug(exception, 'description')

        expect(notifier).to receive(:report).with('debug', 'description', exception, extra_data)
        notifier.debug(exception, 'description', extra_data)
      end

      it 'should report with an info level' do
        expect(notifier).to receive(:report).with('info', nil, exception, nil)
        notifier.info(exception)

        expect(notifier).to receive(:report).with('info', 'description', exception, nil)
        notifier.info(exception, 'description')

        expect(notifier).to receive(:report).with('info', 'description', exception, extra_data)
        notifier.info(exception, 'description', extra_data)
      end

      it 'should report with a warning level' do
        expect(notifier).to receive(:report).with('warning', nil, exception, nil)
        notifier.warning(exception)

        expect(notifier).to receive(:report).with('warning', 'description', exception, nil)
        notifier.warning(exception, 'description')

        expect(notifier).to receive(:report).with('warning', 'description', exception, extra_data)
        notifier.warning(exception, 'description', extra_data)
      end

      it 'should report with an error level' do
        expect(notifier).to receive(:report).with('error', nil, exception, nil)
        notifier.error(exception)

        expect(notifier).to receive(:report).with('error', 'description', exception, nil)
        notifier.error(exception, 'description')

        expect(notifier).to receive(:report).with('error', 'description', exception, extra_data)
        notifier.error(exception, 'description', extra_data)
      end

      it 'should report with a critical level' do
        expect(notifier).to receive(:report).with('critical', nil, exception, nil)
        notifier.critical(exception)

        expect(notifier).to receive(:report).with('critical', 'description', exception, nil)
        notifier.critical(exception, 'description')

        expect(notifier).to receive(:report).with('critical', 'description', exception, extra_data)
        notifier.critical(exception, 'description', extra_data)
      end
    end

    context 'scope' do
      it 'should create a new notifier object' do
        notifier2 = notifier.scope

        notifier2.should_not eq(notifier)
        notifier2.should be_instance_of(Rollbar::Notifier)
      end

      it 'should create a copy of the parent notifier\'s configuration' do
        notifier.configure do |config|
          config.code_version = '123'
          config.payload_options = {
            :a => 'a',
            :b => {:c => 'c'}
          }
        end

        notifier2 = notifier.scope

        notifier2.configuration.code_version.should == '123'
        notifier2.configuration.should_not equal(notifier.configuration)
        notifier2.configuration.payload_options.should_not equal(notifier.configuration.payload_options)
        notifier2.configuration.payload_options.should == notifier.configuration.payload_options
        notifier2.configuration.payload_options.should == {
          :a => 'a',
          :b => {:c => 'c'}
        }
      end

      it 'should not modify any parent notifier configuration' do
        Rollbar.clear_notifier!
        configure
        Rollbar.configuration.code_version.should be_nil
        Rollbar.configuration.payload_options.should be_empty

        notifier = Rollbar.notifier.scope
        notifier.configure do |config|
          config.code_version = '123'
          config.payload_options = {
            :a => 'a',
            :b => {:c => 'c'}
          }
        end

        notifier2 = notifier.scope

        notifier2.configure do |config|
          config.payload_options[:c] = 'c'
        end

        notifier.configuration.payload_options[:c].should be_nil

        notifier3 = notifier2.scope({
          :b => {:c => 3, :d => 'd'}
        })

        notifier3.configure do |config|
          config.code_version = '456'
        end

        notifier.configuration.code_version.should == '123'
        notifier.configuration.payload_options.should == {
          :a => 'a',
          :b => {:c => 'c'}
        }
        notifier2.configuration.code_version.should == '123'
        notifier2.configuration.payload_options.should == {
          :a => 'a',
          :b => {:c => 'c'},
          :c => 'c'
        }
        notifier3.configuration.code_version.should == '456'
        notifier3.configuration.payload_options.should == {
          :a => 'a',
          :b => {:c => 'c'},
          :c => 'c'
        }

        Rollbar.configuration.code_version.should be_nil
        Rollbar.configuration.payload_options.should be_empty
      end
    end

    context 'report' do
      let(:logger_mock) { double("Rails.logger").as_null_object }

      before(:each) do
        configure
        Rollbar.configure do |config|
          config.logger = logger_mock
        end
      end

      after do
        configure
      end

      it 'should reject input that doesn\'t contain an exception, message or extra data' do
        expect(logger_mock).to receive(:error).with('[Rollbar] Tried to send a report with no message, exception or extra data.')
        expect(notifier).not_to receive(:schedule_payload)

        result = notifier.send(:report, 'info', nil, nil, nil)
        result.should == 'error'
      end

      it 'should be ignored if the person is ignored' do
        person_data = {
          :id => 1,
          :username => "test",
          :email => "test@example.com"
        }

        notifier.configure do |config|
          config.ignored_person_ids += [1]
          config.payload_options = { :person => person_data }
        end

        expect(notifier).not_to receive(:schedule_payload)

        result = notifier.send(:report, 'info', 'message', nil, nil)
        result.should == 'ignored'
      end
    end
  end

  context 'reporting' do
    let(:exception) do
      begin
        foo = bar
      rescue => e
        e
      end
    end

    let(:logger_mock) { double("Rails.logger").as_null_object }
    let(:user) { User.create(:email => 'email@example.com', :encrypted_password => '', :created_at => Time.now, :updated_at => Time.now) }

    before do
      Rollbar.unconfigure
      configure

      Rollbar.configure do |config|
        config.logger = logger_mock
      end
    end

    it 'should report exceptions without person or request data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.error(exception)
    end

    it 'should not report anything when disabled' do
      logger_mock.should_not_receive(:info).with('[Rollbar] Success')

      Rollbar.configure do |config|
        config.enabled = false
      end

      Rollbar.error(exception).should == 'disabled'
    end

    it 'should report exceptions without person or request data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.error(exception)
    end

    it 'should be enabled when freshly configured' do
      Rollbar.configuration.enabled.should == true
    end

    it 'should not be enabled when not configured' do
      Rollbar.clear_notifier!

      Rollbar.configuration.enabled.should be_nil
      Rollbar.error(exception).should == 'disabled'
    end

    it 'should stay disabled if configure is called again' do
      # configure once, setting enabled to false.
      Rollbar.configure do |config|
        config.enabled = false
      end

      # now configure again (perhaps to change some other values)
      Rollbar.configure { |_| }

      Rollbar.configuration.enabled.should == false
      Rollbar.error(exception).should == 'disabled'
    end

    context 'using configuration.use_exception_level_filters_default' do
      before do
        Rollbar.configure do |config|
          config.use_exception_level_filters_default = true
        end
      end

      context 'without use_exception_level_filters argument' do
        it 'sends the correct filtered level' do
          Rollbar.configure do |config|
            config.exception_level_filters = { 'NameError' => 'warning' }
          end

          Rollbar.error(exception)

          expect(Rollbar.last_report[:level]).to be_eql('warning')
        end

        it 'ignore ignored exception classes' do
          Rollbar.configure do |config|
            config.exception_level_filters = { 'NameError' => 'ignore' }
          end

          logger_mock.should_not_receive(:info)
          logger_mock.should_not_receive(:warn)
          logger_mock.should_not_receive(:error)

          Rollbar.error(exception)
        end

        it 'should not use the filters if overriden at log site' do
          Rollbar.configure do |config|
            config.exception_level_filters = { 'NameError' => 'ignore' }
          end

          Rollbar.error(exception, :use_exception_level_filters => false)

          expect(Rollbar.last_report[:level]).to be_eql('error')
        end
      end
    end

    context 'using :use_exception_level_filters option as true' do
      it 'sends the correct filtered level' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'warning' }
        end

        Rollbar.error(exception, :use_exception_level_filters => true)
        expect(Rollbar.last_report[:level]).to be_eql('warning')
      end

      it 'ignore ignored exception classes' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'ignore' }
        end

        logger_mock.should_not_receive(:info)
        logger_mock.should_not_receive(:warn)
        logger_mock.should_not_receive(:error)

        Rollbar.error(exception, :use_exception_level_filters => true)
      end

      context 'using :use_exception_level_filters option as false' do
        it 'sends the correct filtered level' do
          Rollbar.configure do |config|
            config.exception_level_filters = { 'NameError' => 'warning' }
          end

          Rollbar.error(exception, :use_exception_level_filters => false)
          expect(Rollbar.last_report[:level]).to be_eql('error')
        end

        it 'ignore ignored exception classes' do
          Rollbar.configure do |config|
            config.exception_level_filters = { 'NameError' => 'ignore' }
          end

          Rollbar.error(exception, :use_exception_level_filters => false)

          expect(Rollbar.last_report[:level]).to be_eql('error')
        end
      end
    end

    context 'using :use_exception_level_filters option as true' do
      it 'sends the correct filtered level' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'warning' }
        end

        Rollbar.error(exception, :use_exception_level_filters => true)
        expect(Rollbar.last_report[:level]).to be_eql('warning')
      end

      it 'ignore ignored exception classes' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'ignore' }
        end

        logger_mock.should_not_receive(:info)
        logger_mock.should_not_receive(:warn)
        logger_mock.should_not_receive(:error)

        Rollbar.error(exception, :use_exception_level_filters => true)
      end
    end

    context 'if not using :use_exception_level_filters option' do
      it 'sends the level defined by the used method' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'warning' }
        end

        Rollbar.error(exception)
        expect(Rollbar.last_report[:level]).to be_eql('error')
      end

      it 'ignore ignored exception classes' do
        Rollbar.configure do |config|
          config.exception_level_filters = { 'NameError' => 'ignore' }
        end

        Rollbar.error(exception)

        expect(Rollbar.last_report[:level]).to be_eql('error')
      end
    end

    # Skip jruby 1.9+ (https://github.com/jruby/jruby/issues/2373)
    if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' && (not RUBY_VERSION =~ /^1\.9/)
      it "should work with an IO object as rack.errors" do
        logger_mock.should_receive(:info).with('[Rollbar] Success')

        Rollbar.error(exception, :env => { :"rack.errors" => IO.new(2, File::WRONLY) })
      end
    end

    it 'should ignore ignored persons' do
      person_data = {
        :id => 1,
        :username => "test",
        :email => "test@example.com"
      }

      Rollbar.configure do |config|
        config.payload_options = { :person => person_data }
        config.ignored_person_ids += [1]
      end

      logger_mock.should_not_receive(:info)
      logger_mock.should_not_receive(:warn)
      logger_mock.should_not_receive(:error)

      Rollbar.error(exception)
    end

    it 'should not ignore non-ignored persons' do
      person_data = {
        :id => 1,
        :username => "test",
        :email => "test@example.com"
      }
      Rollbar.configure do |config|
        config.payload_options = { :person => person_data }
        config.ignored_person_ids += [1]
      end

      Rollbar.last_report = nil

      Rollbar.error(exception)
      Rollbar.last_report.should be_nil

      person_data = {
        :id => 2,
        :username => "test2",
        :email => "test2@example.com"
      }

      new_options = {
        :person => person_data
      }

      Rollbar.scoped(new_options) do
        Rollbar.error(exception)
      end

      Rollbar.last_report.should_not be_nil
    end

    it 'should allow callables to set exception filtered level' do
      callable_mock = double
      saved_filters = Rollbar.configuration.exception_level_filters

      Rollbar.configure do |config|
        config.exception_level_filters = { 'NameError' => callable_mock }
      end

      callable_mock.should_receive(:call).with(exception).at_least(:once).and_return("info")
      logger_mock.should_receive(:info)
      logger_mock.should_not_receive(:warn)
      logger_mock.should_not_receive(:error)

      Rollbar.error(exception, :use_exception_level_filters => true)
    end

    it 'should not report exceptions when silenced' do
      expect_any_instance_of(Rollbar::Notifier).to_not receive(:schedule_payload)

      begin
        test_var = 1
        Rollbar.silenced do
          test_var = 2
          raise
        end
      rescue => e
        Rollbar.error(e)
      end

      test_var.should == 2
    end

    it 'should report exception objects with no backtrace' do
      payload = nil

      notifier.stub(:schedule_item) do |*args|
        payload = args[0]
      end

      Rollbar.error(StandardError.new("oops"))

      payload["data"][:body][:trace][:frames].should == []
      payload["data"][:body][:trace][:exception][:class].should == "StandardError"
      payload["data"][:body][:trace][:exception][:message].should == "oops"
    end

    it 'gets the backtrace from the caller' do
      Rollbar.configure do |config|
        config.populate_empty_backtraces = true
      end

      exception = Exception.new

      Rollbar.error(exception)

      gem_dir = Gem::Specification.find_by_name('rollbar').gem_dir
      gem_lib_dir = gem_dir + '/lib'
      last_report = Rollbar.last_report

      filepaths = last_report[:body][:trace][:frames].map {|frame| frame[:filename] }.reverse

      expect(filepaths[0]).not_to include(gem_lib_dir)
      expect(filepaths.any? {|filepath| filepath.include?(gem_dir) }).to eq true
    end

    it 'should return the exception data with a uuid, on platforms with SecureRandom' do
      if defined?(SecureRandom) and SecureRandom.respond_to?(:uuid)
        exception_data = Rollbar.error(StandardError.new("oops"))
        exception_data[:uuid].should_not be_nil
      end
    end

    it 'should report exception objects with nonstandard backtraces' do
      payload = nil

      notifier.stub(:schedule_item) do |*args|
        payload = args[0]
      end

      class CustomException < StandardError
        def backtrace
          ["custom backtrace line"]
        end
      end

      exception = CustomException.new("oops")

      notifier.error(exception)

      payload["data"][:body][:trace][:frames][0][:method].should == "custom backtrace line"
    end

    it 'should report exceptions with a custom level' do
      payload = nil

      notifier.stub(:schedule_item) do |*args|
        payload = args[0]
      end

      Rollbar.error(exception)

      payload['data'][:level].should == 'error'

      Rollbar.log('debug', exception)

      payload['data'][:level].should == 'debug'
    end

    context 'with invalid utf8 encoding' do
      let(:extra) do
        { :extra => force_to_ascii("bad value 1\255") }
      end

      it 'removes te invalid characteres' do
        Rollbar.info('removing invalid chars', extra)

        extra_value = Rollbar.last_report[:body][:message][:extra][:extra]
        expect(extra_value).to be_eql('bad value 1')
      end
    end
  end

  # Backwards
  context 'report_message' do
    before(:each) do
      configure
      Rollbar.configure do |config|
        config.logger = logger_mock
      end
    end

    let(:logger_mock) { double("Rails.logger").as_null_object }
    let(:user) { User.create(:email => 'email@example.com', :encrypted_password => '', :created_at => Time.now, :updated_at => Time.now) }

    it 'should report simple messages' do
      logger_mock.should_receive(:info).with('[Rollbar] Scheduling item')
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.error('Test message')
    end

    it 'should not report anything when disabled' do
      logger_mock.should_not_receive(:info).with('[Rollbar] Success')
      Rollbar.configure do |config|
        config.enabled = false
      end

      Rollbar.error('Test message that should be ignored')

      Rollbar.configure do |config|
        config.enabled = true
      end
    end

    it 'should report messages with extra data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.debug('Test message with extra data', 'debug', :foo => "bar",
                                                             :hash => { :a => 123, :b => "xyz" })
    end

    # END Backwards

    it 'should not crash with circular extra_data' do
      a = { :foo => "bar" }
      b = { :a => a }
      c = { :b => b }
      a[:c] = c

      logger_mock.should_receive(:error).with(/\[Rollbar\] Reporting internal error encountered while sending data to Rollbar./)

      Rollbar.error("Test message with circular extra data", a)
    end

    it 'should be able to report form validation errors when they are present' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      user.errors.add(:example, "error")
      user.report_validation_errors_to_rollbar
    end

    it 'should not report form validation errors when they are not present' do
      logger_mock.should_not_receive(:info).with('[Rollbar] Success')
      user.errors.clear
      user.report_validation_errors_to_rollbar
    end

    it 'should report messages with extra data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.info("Test message with extra data", :foo => "bar",
                               :hash => { :a => 123, :b => "xyz" })
    end

    it 'should report messages with request, person data and extra data' do
      logger_mock.should_receive(:info).with('[Rollbar] Scheduling item')
      logger_mock.should_receive(:info).with('[Rollbar] Success')

      request_data = {
        :params => {:foo => 'bar'}
      }

      person_data = {
        :id => 123,
        :username => 'username'
      }

      extra_data = {
        :extra_foo => 'extra_bar'
      }

      Rollbar.configure do |config|
        config.payload_options = {
          :request => request_data,
          :person => person_data
        }
      end

      Rollbar.info("Test message", extra_data)

      Rollbar.last_report[:request].should == request_data
      Rollbar.last_report[:person].should == person_data
      Rollbar.last_report[:body][:message][:extra][:extra_foo].should == 'extra_bar'
    end
  end

  context 'payload_destination' do
    before(:each) do
      configure
      Rollbar.configure do |config|
        config.logger = logger_mock
        config.filepath = 'test.rollbar'
      end
    end

    after do
      configure
    end

    let(:exception) do
      begin
        foo = bar
      rescue => e
        e
      end
    end

    let(:logger_mock) { double("Rails.logger").as_null_object }

    it 'should send the payload over the network by default' do
      logger_mock.should_not_receive(:info).with('[Rollbar] Writing payload to file')
      logger_mock.should_receive(:info).with('[Rollbar] Sending item').once
      logger_mock.should_receive(:info).with('[Rollbar] Success').once
      Rollbar.error(exception)
    end

    it 'should save the payload to a file if set' do
      logger_mock.should_not_receive(:info).with('[Rollbar] Sending item')
      logger_mock.should_receive(:info).with('[Rollbar] Writing item to file').once
      logger_mock.should_receive(:info).with('[Rollbar] Success').once

      filepath = ''

      Rollbar.configure do |config|
        config.write_to_file = true
        filepath = config.filepath
      end

      Rollbar.error(exception)

      File.exist?(filepath).should == true
      File.read(filepath).should include test_access_token
      File.delete(filepath)

      Rollbar.configure do |config|
        config.write_to_file = false
      end
    end
  end

  context 'using a proxy server' do
    before do
      allow_any_instance_of(Net::HTTP).to receive(:request).and_return(OpenStruct.new(:code => 200, :body => "Success"))
      @env_vars = clear_proxy_env_vars
    end

    after do
     restore_proxy_env_vars(@env_vars)
    end

    context 'via environment variables' do
      it 'honors proxy settings in the environment' do
        ENV['http_proxy']  = 'http://user:pass@example.com:80'
        ENV['https_proxy'] = 'http://user:pass@example.com:80'

        uri = URI.parse(Rollbar::Configuration::DEFAULT_ENDPOINT)
        expect(Net::HTTP).to receive(:new).with(uri.host, uri.port, 'example.com', 80, 'user', 'pass').and_call_original
        Rollbar.info("proxy this")
      end

      it 'does not use a proxy if no proxy settings in environemnt' do
        uri = URI.parse(Rollbar::Configuration::DEFAULT_ENDPOINT)
        expect(Net::HTTP).to receive(:new).with(uri.host, uri.port, nil, nil, nil, nil).and_call_original
        Rollbar.info("proxy this")
      end
    end

    context 'set in configuration file' do
      before do
        Rollbar.configure do |config|
          config.proxy = {
            :host => 'http://config.com',
            :port => 8080,
            :user => 'foo',
            :password => 'bar'
          }
        end
      end

      it 'honors proxy settings in the config file' do
        uri = URI.parse(Rollbar::Configuration::DEFAULT_ENDPOINT)
        expect(Net::HTTP).to receive(:new).with(uri.host, uri.port, 'config.com', 8080, 'foo', 'bar').and_call_original
        Rollbar.info("proxy this")
      end

      it 'gives the configuration settings precedence over environment' do
        ENV['http_proxy']  = 'http://user:pass@example.com:80'
        ENV['https_proxy'] = 'http://user:pass@example.com:80'

        uri = URI.parse(Rollbar::Configuration::DEFAULT_ENDPOINT)
        expect(Net::HTTP).to receive(:new).with(uri.host, uri.port, 'config.com', 8080, 'foo', 'bar').and_call_original
        Rollbar.info("proxy this")
      end
    end
  end

  context 'asynchronous_handling' do
    before do
      Rollbar.clear_notifier!
      configure
      Rollbar.configure do |config|
        config.logger = logger_mock
      end
    end

    after do
      configure
    end

    let(:exception) do
      begin
        foo = bar
      rescue => e
        e
      end
    end

    let(:logger_mock) { double("Rails.logger").as_null_object }

    it 'should send the payload using the default asynchronous handler girl_friday' do
      logger_mock.should_receive(:info).with('[Rollbar] Scheduling item')
      logger_mock.should_receive(:info).with('[Rollbar] Sending item')
      logger_mock.should_receive(:info).with('[Rollbar] Success')

      Rollbar.configure do |config|
        config.use_async = true
        GirlFriday::WorkQueue.immediate!
      end

      Rollbar.error(exception)

      Rollbar.configure do |config|
        config.use_async = false
        GirlFriday::WorkQueue.queue!
      end
    end

    it 'should send the payload using a user-supplied asynchronous handler' do
      logger_mock.should_receive(:info).with('Custom async handler called')
      logger_mock.should_receive(:info).with('[Rollbar] Sending item')
      logger_mock.should_receive(:info).with('[Rollbar] Success')

      Rollbar.configure do |config|
        config.use_async = true
        config.async_handler = Proc.new { |payload|
          logger_mock.info 'Custom async handler called'
          Rollbar.process_from_async_handler(payload)
        }
      end

      Rollbar.error(exception)
    end

    # We should be able to send String payloads, generated
    # by a previous version of the gem. This can happend just
    # after a deploy with an gem upgrade.
    context 'with a payload generated as String' do
      let(:async_handler) do
        proc do |payload|
          # simulate previous gem version
          string_payload = Rollbar::JSON.dump(payload)

          Rollbar.process_from_async_handler(string_payload)
        end
      end

      before do
        Rollbar.configuration.stub(:use_async).and_return(true)
        Rollbar.configuration.stub(:async_handler).and_return(async_handler)
      end

      it 'sends a payload generated as String, not as a Hash' do
        logger_mock.should_receive(:info).with('[Rollbar] Success')

        Rollbar.error(exception)
      end

      context 'with async failover handlers' do
        before do
          Rollbar.reconfigure do |config|
            config.use_async = true
            config.async_handler = async_handler
            config.failover_handlers = handlers
            config.logger = logger_mock
          end
        end

        let(:exception) { StandardError.new('the error') }

        context 'if the async handler doesnt fail' do
          let(:async_handler) { proc { |_| 'success' } }
          let(:handler) { proc { |_| 'success' } }
          let(:handlers) { [handler] }

          it 'doesnt call any failover handler' do
            expect(handler).not_to receive(:call)

            Rollbar.error(exception)
          end
        end

        context 'if the async handler fails' do
          let(:async_handler) { proc { |_| fail 'this handler will crash' } }

          context 'if any failover handlers is configured' do
            let(:handlers) { [] }
            let(:log_message) do
              '[Rollbar] Async handler failed, and there are no failover handlers configured. See the docs for "failover_handlers"'
            end

            it 'logs the error but doesnt try to report an internal error' do
              expect(logger_mock).to receive(:error).with(log_message)

              Rollbar.error(exception)
            end
          end

          context 'if the first failover handler success' do
            let(:handler) { proc { |_| 'success' } }
            let(:handlers) { [handler] }

            it 'calls the failover handler and doesnt report internal error' do
              expect(Rollbar).not_to receive(:report_internal_error)
              expect(handler).to receive(:call)

              Rollbar.error(exception)
            end
          end

          context 'with two handlers, the first failing' do
            let(:handler1) { proc { |_| fail 'this handler fails' } }
            let(:handler2) { proc { |_| 'success' } }
            let(:handlers) { [handler1, handler2] }

            it 'calls the second handler and doesnt report internal error' do
              expect(handler2).to receive(:call)

              Rollbar.error(exception)
            end
          end

          context 'with two handlers, both failing' do
            let(:handler1) { proc { |_| fail 'this handler fails' } }
            let(:handler2) { proc { |_| fail 'this will also fail' } }
            let(:handlers) { [handler1, handler2] }

            it 'reports internal error' do
              expect(logger_mock).to receive(:error)

              Rollbar.error(exception)
            end
          end
        end
      end
    end

    describe "#use_sucker_punch", :if => defined?(SuckerPunch) do
      it "should send the payload to sucker_punch delayer" do
        logger_mock.should_receive(:info).with('[Rollbar] Scheduling item')
        expect(Rollbar::Delay::SuckerPunch).to receive(:call)

        Rollbar.configure(&:use_sucker_punch)
        Rollbar.error(exception)
      end
    end

    describe "#use_shoryuken", :if => defined?(Shoryuken) do
      it "should send the payload to shoryuken delayer" do
        logger_mock.should_receive(:info).with('[Rollbar] Scheduling item')
        expect(Rollbar::Delay::Shoryuken).to receive(:call)

        Rollbar.configure(&:use_shoryuken)
        Rollbar.error(exception)
      end
    end

    describe "#use_sidekiq", :if => defined?(Sidekiq) do
      it "should instanciate sidekiq delayer with custom values" do
        Rollbar::Delay::Sidekiq.should_receive(:new).with('queue' => 'test_queue')
        config = Rollbar::Configuration.new
        config.use_sidekiq 'queue' => 'test_queue'
      end

      it "should send the payload to sidekiq delayer" do
        handler = double('sidekiq_handler_mock')
        handler.should_receive(:call)

        Rollbar.configure do |config|
          config.use_sidekiq
          config.async_handler = handler
        end

        Rollbar.error(exception)
      end
    end
  end

  context 'logger' do
    before(:each) do
      reset_configuration
    end

    it 'should have use the Rails logger when configured to do so' do
      configure
      expect(Rollbar.send(:logger)).to be_kind_of(Rollbar::LoggerProxy)
      expect(Rollbar.send(:logger).object).to eq ::Rails.logger
    end

    it 'should use the default_logger when no logger is set' do
      logger = Logger.new(STDERR)

      Rollbar.configure do |config|
        config.default_logger = lambda { logger }
      end

      Rollbar.send(:logger).object.should == logger
    end

    it 'should have a default default_logger' do
      Rollbar.send(:logger).should_not be_nil
    end

    after do
      reset_configuration
    end
  end

  context "project_gems" do
    it "should include gem paths for specified project gems in the payload" do
      gems = ['rack', 'rspec-rails']
      gem_paths = []

      Rollbar.configure do |config|
        config.project_gems = gems
      end

      gem_paths = gems.map do |gem|
        gem_spec = Gem::Specification.find_all_by_name(gem)[0]
        gem_spec.gem_dir if gem_spec
      end.compact

      data = notifier.send(:build_item, 'info', 'test', nil, {})['data']
      data[:project_package_paths].kind_of?(Array).should == true
      data[:project_package_paths].length.should == gem_paths.length

      data[:project_package_paths].each_with_index{|path, index|
        path.should == gem_paths[index]
      }
    end

    it "should handle regex gem patterns" do
      gems = ["rack", /rspec/, /roll/]
      gem_paths = []

      Rollbar.configure do |config|
        config.project_gems = gems
      end

      gem_paths = gems.map{|gem| Gem::Specification.find_all_by_name(gem).map(&:gem_dir) }.flatten.compact.uniq
      gem_paths.length.should > 1

      gem_paths.any?{|path| path.include? 'rollbar-gem'}.should == true
      gem_paths.any?{|path| path.include? 'rspec-rails'}.should == true

      data = notifier.send(:build_item, 'info', 'test', nil, {})['data']
      data[:project_package_paths].kind_of?(Array).should == true
      data[:project_package_paths].length.should == gem_paths.length
      (data[:project_package_paths] - gem_paths).length.should == 0
    end

    it "should not break on non-existent gems" do
      gems = ["this_gem_does_not_exist", "rack"]

      Rollbar.configure do |config|
        config.project_gems = gems
      end

      data = notifier.send(:build_item, 'info', 'test', nil, {})['data']
      data[:project_package_paths].kind_of?(Array).should == true
      data[:project_package_paths].length.should == 1
    end
  end

  context 'report_internal_error', :reconfigure_notifier => true do
    it "should not crash when given an exception object" do
      begin
        1 / 0
      rescue => e
        notifier.send(:report_internal_error, e)
      end
    end
  end

  context "send_failsafe" do
    let(:exception) { StandardError.new }

    it "doesn't crash when given a message and exception" do
      sent_payload = notifier.send(:send_failsafe, "test failsafe", exception)

      expected_message = 'Failsafe from rollbar-gem. StandardError: test failsafe'
      expect(sent_payload['data'][:body][:message][:body]).to be_eql(expected_message)
    end

    it "doesn't crash when given all nils" do
      notifier.send(:send_failsafe, nil, nil)
    end

    context 'with a non default exception message' do
      let(:exception) { StandardError.new 'Something is wrong' }

      it 'adds it to exception info' do
        sent_payload = notifier.send(:send_failsafe, "test failsafe", exception)

        expected_message = 'Failsafe from rollbar-gem. StandardError: "Something is wrong": test failsafe'
        expect(sent_payload['data'][:body][:message][:body]).to be_eql(expected_message)
      end
    end

    context 'without exception object' do
      it 'just sends the given message' do
        sent_payload = notifier.send(:send_failsafe, "test failsafe", nil)

        expected_message = 'Failsafe from rollbar-gem. test failsafe'
        expect(sent_payload['data'][:body][:message][:body]).to be_eql(expected_message)
      end
    end

    context 'if the exception has a backtrace' do
      let(:backtrace) { ['func3', 'func2', 'func1'] }
      let(:failsafe_reason) { 'StandardError in func3: test failsafe' }
      let(:expected_body) { "Failsafe from rollbar-gem. #{failsafe_reason}" }
      let(:expected_log_message) do
        "[Rollbar] Sending failsafe response due to #{failsafe_reason}"
      end

      before { exception.set_backtrace(backtrace) }

      it 'adds the nearest frame to the message' do
        expect(notifier).to receive(:log_error).with(expected_log_message)

        sent_payload = notifier.send(:send_failsafe, "test failsafe", exception)

        expect(sent_payload['data'][:body][:message][:body]).to be_eql(expected_body)
      end
    end

    context 'with uuid and host' do
      let(:host) { 'the-host' }
      let(:uuid) { 'the-uuid' }
      it 'sets the uuid and host in correct keys' do
        sent_payload = notifier.send(:send_failsafe, 'testing uuid and host',
                                     exception, uuid, host)

        expect(sent_payload['data'][:custom][:orig_uuid]).to be_eql('the-uuid')
        expect(sent_payload['data'][:custom][:orig_host]).to be_eql('the-host')
      end
    end
  end

  context 'when reporting internal error with nil context' do
    let(:context_proc) { proc {} }
    let(:scoped_notifier) { notifier.scope(:context => context_proc) }
    let(:exception) { Exception.new }
    let(:logger_mock) { double("Rails.logger").as_null_object }

    it 'reports successfully' do
      configure

      Rollbar.configure do |config|
        config.logger = logger_mock
      end

      logger_mock.should_receive(:info).with('[Rollbar] Sending item').once
      logger_mock.should_receive(:info).with('[Rollbar] Success').once
      scoped_notifier.send(:report_internal_error, exception)
    end
  end

  context "request_data_extractor" do
    before(:each) do
      class DummyClass
      end
      @dummy_class = DummyClass.new
      @dummy_class.extend(Rollbar::RequestDataExtractor)
    end

    context "rollbar_headers" do
      it "should not include cookies" do
        env = {"HTTP_USER_AGENT" => "test", "HTTP_COOKIE" => "cookie"}
        headers = @dummy_class.send(:rollbar_headers, env)
        headers.should have_key "User-Agent"
        headers.should_not have_key "Cookie"
      end
    end
  end

  describe '.scoped' do
    let(:scope_options) do
      { :foo => 'bar' }
    end

    it 'changes data in scope_object inside the block' do
      Rollbar.clear_notifier!
      configure

      current_notifier_id = Rollbar.notifier.object_id

      Rollbar.scoped(scope_options) do
        scope_object = Rollbar.notifier.scope_object

        expect(Rollbar.notifier.object_id).not_to be_eql(current_notifier_id)
        expect(scope_object).to be_eql(scope_options)
      end

      expect(Rollbar.notifier.object_id).to be_eql(current_notifier_id)
    end

    context 'if the block fails' do
      let(:crashing_block) { proc { fail } }

      it 'restores the old notifier' do
        notifier = Rollbar.notifier

        expect { Rollbar.scoped(&crashing_block) }.to raise_error
        expect(notifier).to be_eql(Rollbar.notifier)
      end
    end

    context 'if the block creates a new thread' do
      let(:block) do
        proc do
          Thread.new do
            scope = Rollbar.notifier.scope_object
            Thread.main[:inner_scope] = scope
          end.join
        end
      end

      let(:scope) do
        { :foo => 'bar' }
      end

      it 'maintains the parent thread notifier scope' do
        Rollbar.scoped(scope, &block)

        expect(Thread.main[:inner_scope]).to be_eql(scope)
      end
    end
  end

  describe '.scope!' do
    let(:new_scope) do
      { :person => { :id => 1 } }
    end

    before { reconfigure_notifier }

    it 'adds the new scope to the payload options' do
      scope_object = Rollbar.notifier.scope_object
      Rollbar.scope!(new_scope)

      expect(scope_object).to be_eql(new_scope)
    end
  end

  describe '.clear_notifier' do
    before { Rollbar.notifier }

    it 'resets the notifier' do
      Rollbar.clear_notifier!
      expect(Rollbar.instance_variable_get('@notifier')).to be_nil
      expect(Rollbar.instance_variable_get('@root_notifier')).to be_nil
    end
  end

  describe '.process_item' do
    context 'if there is an exception sending the payload' do
      let(:exception) { StandardError.new('error message') }
      let(:payload) { Rollbar::Item.build_with({ :foo => :bar }) }

      it 'logs the error and the payload' do
        allow(Rollbar.notifier).to receive(:send_item).and_raise(exception)
        expect(Rollbar.notifier).to receive(:log_error)

        expect { Rollbar.notifier.process_item(payload) }.to raise_error(exception)
      end
    end
  end

  describe '.process_from_async_handler' do
    context 'with errors' do
      let(:exception) { StandardError.new('the error') }

      it 'raises anything and sends internal error' do
        allow(Rollbar.notifier).to receive(:process_item).and_raise(exception)
        expect(Rollbar.notifier).to receive(:report_internal_error).with(exception)

        expect do
          Rollbar.notifier.process_from_async_handler({})
        end.to raise_error(exception)

        rollbar_do_not_report = exception.instance_variable_get(:@_rollbar_do_not_report)
        expect(rollbar_do_not_report).to be_eql(true)
      end
    end
  end

  describe '.preconfigure'do
    before do
      Rollbar.clear_notifier!
    end

    it 'resets the notifier' do
      Rollbar.configure do |config|
        config.access_token = 'foo'
      end

      Thread.new {}

      Rollbar.preconfigure do |config|
        config.root = 'bar'
      end

      notifier_config = Rollbar.notifier.configuration
      expect(notifier_config.root).to be_eql('bar')
    end
  end

  context 'having timeout issues (for ruby > 1.9.3)' do
    before do
      skip if Rollbar::LanguageSupport.ruby_18? || Rollbar::LanguageSupport.ruby_19?
    end

    let(:exception_class) do
      Rollbar::LanguageSupport.timeout_exceptions.first
    end
    let(:net_exception) do
      exception_class.new
    end

    before do
      allow_any_instance_of(Net::HTTP).to receive(:request).and_raise(net_exception)
    end

    it 'retries the request' do
      expect_any_instance_of(Net::HTTP).to receive(:request).exactly(3)
      expect(Rollbar.notifier).to receive(:report_internal_error).with(net_exception)

      Rollbar.info('foo')
    end
  end

  describe '.with_config' do
    let(:new_config) do
      { 'environment' => 'foo' }
    end

    it 'uses the new config and restores the old one' do
      config1 = described_class.configuration

      subject.with_config(:environment => 'bar') do
        expect(described_class.configuration).not_to be(config1)
      end

      expect(described_class.configuration).to be(config1)
    end
  end

  # configure with some basic params
  def configure
    reconfigure_notifier
  end
end