require 'spec_helper'

describe Adhearsion::Reporter do
  EventClass = Class.new
  ExceptionClass = Class.new StandardError

  before do
    Adhearsion::Reporter.config.notifier = nil
    Adhearsion::Reporter.config.notifiers = []
  end

  context "with a DummyNotifier" do
    class DummyNotifier
      include Singleton
      attr_reader :initialized, :notified
      def init
        @initialized = true
      end

      def notify(ex)
        @notified = ex
      end

      def self.method_missing(m, *args, &block)
        instance.send m, *args, &block
      end
    end

    before(:each) do
      Adhearsion::Reporter.config.notifier = DummyNotifier
      Adhearsion::Plugin.init_plugins
      Adhearsion::Events.trigger_immediately :exception, ExceptionClass.new
    end

    it "calls init on the notifier instance" do
      expect(Adhearsion::Reporter.config.notifier.instance.initialized).to be(true)
    end

    it "logs an exception event" do
      sleep 0.25
      expect(Adhearsion::Reporter.config.notifier.instance.notified.class).to eq(ExceptionClass)
    end
  end

  context "with a AirbrakeNotifier" do
    before(:each) do
      Adhearsion::Reporter.config.notifier = Adhearsion::Reporter::AirbrakeNotifier
    end

    it "should initialize correctly" do
      expect(Toadhopper).to receive(:new).with(Adhearsion::Reporter.config.api_key, notify_host: Adhearsion::Reporter.config.url)
      Adhearsion::Plugin.init_plugins
    end

    context "exceptions" do
      let(:mock_notifier) { double 'notifier' }
      let(:event_error)   { ExceptionClass.new }
      let(:response)      { double('response', status: '200').as_null_object }

      before { expect(Toadhopper).to receive(:new).at_least(:once).and_return(mock_notifier) }

      after do
        Adhearsion::Plugin.init_plugins
        Adhearsion::Events.trigger_immediately :exception, event_error
      end

      it "should notify Airbrake" do
        expect(mock_notifier).to receive(:post!).at_least(:once).with(event_error, hash_including(framework_env: :production)).and_return(response)
      end

      context "with an environment set" do
        before { Adhearsion.config.platform.environment = :foo }

        it "notifies airbrake with that environment" do
          expect(mock_notifier).to receive(:post!).at_least(:once).with(event_error, hash_including(framework_env: :foo)).and_return(response)
        end
      end

      context "in excluded environments" do
        before do
          Adhearsion.config.platform.environment = :development
          Adhearsion::Plugin.init_plugins
        end
        it "should not report errors for excluded environments" do
          expect(mock_notifier).to_not receive(:post!)
        end
      end
    end
  end

  context "with a NewrelicNotifier" do
    before(:each) do
      Adhearsion::Reporter.config.notifier = Adhearsion::Reporter::NewrelicNotifier
    end

    it "should initialize correctly" do
      expect(NewRelic::Agent).to receive(:manual_start).with(Adhearsion::Reporter.config.newrelic.to_hash)
      Adhearsion::Plugin.init_plugins
    end

    it "should notify Newrelic" do
      expect(NewRelic::Agent).to receive(:manual_start)

      event_error = ExceptionClass.new
      expect(NewRelic::Agent).to receive(:notice_error).at_least(:once).with(event_error)

      Adhearsion::Plugin.init_plugins
      Adhearsion::Events.trigger_immediately :exception, event_error
    end
  end

  context 'with an EmailNotifier' do
    let(:email_options) do
      {
        via: :sendmail,
        to: 'recv@domain.ext'
      }
    end

    let(:time_freeze) { Time.parse("2014-07-24 17:30:00") }

    let(:fake_backtrace) do
      [
        '1: foo',
        '2: bar'
      ]
    end

    let(:error_message) { "Something bad" }

    before(:each) do
      Adhearsion::Reporter.config.notifier = Adhearsion::Reporter::EmailNotifier
      Adhearsion::Reporter.config.email = email_options
    end

    it "should initialize correctly" do
      Adhearsion::Plugin.init_plugins
      expect(Pony.options).to be(email_options)
    end

    it "should notify via email" do

      event_error = ExceptionClass.new error_message
      event_error.set_backtrace(fake_backtrace)

      hostname = Socket.gethostname
      environment = Adhearsion.config.platform.environment.to_s.upcase

      Timecop.freeze(time_freeze) do
        expect(Pony).to receive(:mail).at_least(:once).with({
          subject: "[#{Adhearsion::Reporter.config.app_name}-#{environment}] Exception: ExceptionClass (#{error_message})",
          body: "#{Adhearsion::Reporter.config.app_name} reported an exception at #{time_freeze.to_s}\n\nExceptionClass (#{error_message}):\n#{event_error.backtrace.join("\n")}\n\n",
          from: hostname
        })

        Adhearsion::Plugin.init_plugins
        Adhearsion::Events.trigger_immediately :exception, event_error
      end
    end
  end

  context "with a SentryNotifier" do
    let(:sentry_options) do
      {
        "dsn" => 'https://123abc:def456@app.getsentry.com/98765',
        "environments" => ['production', 'staging'],
        "current_environment" => 'production'
      }
    end

    before(:each) do
      Adhearsion::Reporter.config.notifier = Adhearsion::Reporter::SentryNotifier
      Adhearsion::Reporter.config.sentry = sentry_options
    end

    it "should initialize correctly" do
      Adhearsion::Plugin.init_plugins

      config = Raven.configuration
      sentry_options.each do |k, v|
        if k == "dsn"   #There is no getter for this attribute, so we have to get it through it components
          expect("#{config.scheme}://#{config.public_key}:#{config.secret_key}@#{config.host}/#{config.path}#{config.project_id}").to eq v
        else
          expect(Raven.configuration.send("#{k}")).to eq v
        end
      end
    end

    it "should notify Sentry" do
      expect(Raven).to receive(:configure)
      event_error = ExceptionClass.new
      expect(Raven).to receive(:capture_exception).at_least(:once).with(event_error)

      Adhearsion::Plugin.init_plugins
      Adhearsion::Events.trigger_immediately :exception, event_error
    end
  end

  context "with multiple notifiers" do

    class BaseNotifier
      include Singleton

      attr_reader :initialized, :notified

      def init
        @initialized = true
      end

      def notify(ex)
        @notified = ex
      end

      def self.method_missing(m, *args, &block)
        instance.send m, *args, &block
      end
    end

    class MockNotifier < BaseNotifier; end
    class AnotherMockNotifier < BaseNotifier; end

    before(:each) do
      Adhearsion::Events.clear_handlers(:exception)
      Adhearsion::Reporter::config.notifiers = [MockNotifier, AnotherMockNotifier]
      Adhearsion::Plugin.init_plugins
      Adhearsion::Events.trigger_immediately :exception, ExceptionClass.new
    end

    it "calls init on each of the notifier instances" do
      Adhearsion::Reporter::config.notifiers.each do |notifier|
        expect(notifier.instance.initialized).to be(true)
      end
    end

    it "logs an exception in each of the registered notifiers" do
      Adhearsion::Reporter::config.notifiers.each do |notifier|
        expect(notifier.instance.notified.class).to eq(ExceptionClass)
      end
    end
  end
end
