require "rake"

describe Appsignal::Hooks::RakeHook do
  let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
  let(:task) { Rake::Task.new("task:name", Rake::Application.new) }
  let(:arguments) { Rake::TaskArguments.new(["foo"], ["bar"]) }
  let(:options) { {} }
  before do
    start_agent(:options => options)
    allow(Kernel).to receive(:at_exit)
  end
  around { |example| keep_transactions { example.run } }
  after do
    if helper.instance_variable_defined?(:@register_at_exit_hook)
      helper.remove_instance_variable(:@register_at_exit_hook)
    end
  end

  def expect_to_not_have_registered_at_exit_hook
    expect(Kernel).to_not have_received(:at_exit)
  end

  def expect_to_have_registered_at_exit_hook
    expect(Kernel).to have_received(:at_exit)
  end

  describe "#execute" do
    context "without error" do
      def perform
        task.execute(arguments)
      end

      context "with :enable_rake_performance_instrumentation == false" do
        let(:options) { { :enable_rake_performance_instrumentation => false } }

        it "creates no transaction" do
          expect { perform }.to_not(change { created_transactions.count })
        end

        it "calls the original task" do
          expect(perform).to eq([])
        end

        it "does not register an at_exit hook" do
          perform
          expect_to_not_have_registered_at_exit_hook
        end
      end

      context "with :enable_rake_performance_instrumentation == true" do
        let(:options) { { :enable_rake_performance_instrumentation => true } }

        it "creates a transaction" do
          expect { perform }.to(change { created_transactions.count }.by(1))

          transaction = last_transaction
          expect(transaction).to have_id
          expect(transaction).to have_namespace("rake")
          expect(transaction).to have_action("task:name")
          expect(transaction).to_not have_error
          expect(transaction).to include_params("foo" => "bar")
          expect(transaction).to include_event("name" => "task.rake")
          expect(transaction).to be_completed
        end

        it "calls the original task" do
          expect(perform).to eq([])
        end

        it "registers an at_exit hook" do
          perform
          expect_to_have_registered_at_exit_hook
        end
      end
    end

    context "with error" do
      before do
        task.enhance { raise error }
      end

      def perform
        expect { task.execute(arguments) }.to raise_error(error)
      end

      context "with normal error" do
        let(:error) { ExampleException.new("error message") }

        it "creates a background job transaction" do
          perform

          transaction = last_transaction
          expect(transaction).to have_id
          expect(transaction).to have_namespace("rake")
          expect(transaction).to have_action("task:name")
          expect(transaction).to have_error("ExampleException", "error message")
          expect(transaction).to include_params("foo" => "bar")
          expect(transaction).to be_completed
        end

        it "registers an at_exit hook" do
          perform
          expect_to_have_registered_at_exit_hook
        end

        context "when first argument is not a `Rake::TaskArguments`" do
          let(:arguments) { nil }

          it "does not add the params to the transaction" do
            perform

            expect(last_transaction).to_not include_params
          end
        end
      end

      context "when error is a SystemExit" do
        let(:error) { SystemExit.new(1) }

        it "does not report the error" do
          perform

          transaction = last_transaction
          expect(transaction).to_not have_error
        end
      end

      context "when error is a SignalException" do
        let(:error) { SignalException.new(1) }

        it "does not report the error" do
          perform

          transaction = last_transaction
          expect(transaction).to_not have_error
        end
      end
    end
  end
end

describe "Appsignal::Integrations::RakeIntegrationHelper" do
  let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
  describe ".register_at_exit_hook" do
    before do
      start_agent
      allow(Appsignal).to receive(:stop)
    end

    it "registers the at_exit hook only once" do
      allow(Kernel).to receive(:at_exit)
      helper.register_at_exit_hook
      helper.register_at_exit_hook
      expect(Kernel).to have_received(:at_exit).once
    end
  end

  describe ".at_exit_hook" do
    let(:helper) { Appsignal::Integrations::RakeIntegrationHelper }
    before do
      start_agent
      allow(Appsignal).to receive(:stop)
    end

    it "calls Appsignal.stop" do
      helper.at_exit_hook
      expect(Appsignal).to have_received(:stop).with("rake")
    end
  end
end
