require 'logger'
require 'socket'
require 'spec_helper'

describe Rollbar do

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

      begin
        foo = bar
      rescue => e
        @exception = e
      end
    end

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

    it 'should report exceptions without person or request data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.report_exception(@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.report_exception(@exception)

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

    it 'should not be enabled when not configured' do
      Rollbar.unconfigure
      
      Rollbar.configuration.enabled.should == false
      Rollbar.report_exception(@exception).should == 'disabled'
    end

    it 'should report exceptions with request and person data' do
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      request_data = {
        :params => { :foo => "bar" },
        :url => 'http://localhost/',
        :user_ip => '127.0.0.1',
        :headers => {},
        :GET => { "baz" => "boz" },
        :session => { :user_id => 123 },
        :method => "GET",
      }
      person_data = {
        :id => 1,
        :username => "test",
        :email => "test@example.com"
      }
      Rollbar.report_exception(@exception, request_data, person_data)
    end

    it 'should ignore ignored exception classes' do
      saved_filters = Rollbar.configuration.exception_level_filters
      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.report_exception(@exception)

      Rollbar.configure do |config|
        config.exception_level_filters = saved_filters
      end
    end

    it 'should not report exceptions when silenced' do
      Rollbar.should_not_receive :schedule_payload

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

      test_var.should == 2
    end

    it 'should report exception objects with no backtrace' do
      payload = nil
      Rollbar.stub(:schedule_payload) do |*args|
        payload = MultiJson.load(args[0])
      end
      Rollbar.report_exception(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 'should return the exception data with a uuid, on platforms with SecureRandom' do
      if defined?(SecureRandom) and SecureRandom.respond_to?(:uuid)
        Rollbar.stub(:schedule_payload) do |*args| end
        exception_data = Rollbar.report_exception(StandardError.new("oops"))
        exception_data[:uuid].should_not be_nil
      end
    end
  end

  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 }

    it 'should report simple messages' do
      logger_mock.should_receive(:info).with('[Rollbar] Scheduling payload')
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.report_message("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.report_message("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.report_message("Test message with extra data", 'debug', :foo => "bar",
                               :hash => { :a => 123, :b => "xyz" })
    end

    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\] Error reporting message to Rollbar: (nesting of \d+ is too deep|object references itself)/)
      Rollbar.report_message("Test message with circular extra data", 'debug', a)
    end

    after(:each) do
      Rollbar.configure do |config|
        config.logger = ::Rails.logger
      end
    end
  end

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

      begin
        foo = bar
      rescue => e
        @exception = 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 payload')
      logger_mock.should_receive(:info).with('[Rollbar] Success')
      Rollbar.report_exception(@exception)
    end

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

      filepath = ''

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

      Rollbar.report_exception(@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 'asynchronous_handling' do
    before(:each) do
      configure
      Rollbar.configure do |config|
        config.logger = logger_mock
      end

      begin
        foo = bar
      rescue => e
        @exception = 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 payload')
      logger_mock.should_receive(:info).with('[Rollbar] Sending payload')
      logger_mock.should_receive(:info).with('[Rollbar] Success')

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

      Rollbar.report_exception(@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 payload')
      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_payload(payload)
        }
      end

      Rollbar.report_exception(@exception)

      Rollbar.configure do |config|
        config.use_async = false
        config.async_handler = Rollbar.method(:default_async_handler)
      end
    end
  end

  context 'message_data' do
    before(:each) do
      configure
      @message_body = "This is a test"
      @level = 'debug'
    end

    it 'should build a message' do
      data = Rollbar.send(:message_data, @message_body, @level, {})
      data[:body][:message][:body].should == @message_body
      data[:level].should == @level
    end

    it 'should accept extra_data' do
      user_id = 123
      name = "Tester"

      data = Rollbar.send(:message_data, @message_body, 'info',
                            :user_id => user_id, :name => name)

      message = data[:body][:message]
      message[:body].should == @message_body
      message[:user_id].should == user_id
      message[:name].should == name
    end
  end

  context 'exception_data' do
    before(:each) do
      configure
      begin
        foo = bar
      rescue => e
        @exception = e
      end
    end

    it 'should accept force_level' do
      level = 'critical'
      data = Rollbar.send(:exception_data, @exception, level)
      data[:level].should == level
    end

    it 'should build valid exception data' do
      data = Rollbar.send(:exception_data, @exception)

      data[:level].should_not be_nil

      trace = data[:body][:trace]

      frames = trace[:frames]
      frames.should be_a_kind_of(Array)
      frames.each do |frame|
        frame[:filename].should be_a_kind_of(String)
        frame[:lineno].should be_a_kind_of(Fixnum)
        if frame[:method]
          frame[:method].should be_a_kind_of(String)
        end
      end

      # should be NameError, but can be NoMethodError sometimes on rubinius 1.8
      # http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/
      trace[:exception][:class].should match(/^(NameError|NoMethodError)$/)
      trace[:exception][:message].should match(/^(undefined local variable or method `bar'|undefined method `bar' on an instance of)/)
    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
      Rollbar.send(:logger).should == ::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).should == logger
    end

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

    after(:each) do
      reset_configuration
    end
  end

  context 'build_payload' do
    it 'should build valid json' do
      json = Rollbar.send(:build_payload, {:foo => {:bar => "baz"}})
      hash = MultiJson.load(json)
      hash["data"]["foo"]["bar"].should == "baz"
    end
  end

  context 'base_data' do
    before(:each) { configure }

    it 'should have the correct notifier name' do
      Rollbar.send(:base_data)[:notifier][:name].should == 'rollbar-gem'
    end

    it 'should have the correct notifier version' do
      Rollbar.send(:base_data)[:notifier][:version].should == Rollbar::VERSION
    end

    it 'should have all the required keys' do
      data = Rollbar.send(:base_data)
      data[:timestamp].should_not be_nil
      data[:environment].should_not be_nil
      data[:level].should_not be_nil
      data[:language].should == 'ruby'
      data[:framework].should match(/^Rails/)
    end
  end

  context 'server_data' do
    it 'should have the right hostname' do
      Rollbar.send(:server_data)[:host] == Socket.gethostname
    end

    it 'should have root and branch set when configured' do
      configure
      Rollbar.configure do |config|
        config.root = '/path/to/root'
        config.branch = 'master'
      end

      data = Rollbar.send(:server_data)
      data[:root].should == '/path/to/root'
      data[:branch].should == 'master'
    end
  end

  # configure with some basic params
  def configure
    Rollbar.configure do |config|
      # special test access token
      config.access_token = test_access_token
      config.logger = ::Rails.logger
      config.environment = ::Rails.env
      config.root = ::Rails.root
      config.framework = "Rails: #{::Rails::VERSION::STRING}"
    end
  end

  def test_access_token
    'aaaabbbbccccddddeeeeffff00001111'
  end

end