if DependencyHelper.sinatra_present?
  require "appsignal/rack/sinatra_instrumentation"

  module SinatraRequestHelpers
    def make_request
      middleware.call(env)
    end

    def make_request_with_error(error)
      expect { middleware.call(env) }.to raise_error(error)
    end
  end

  describe Appsignal::Rack::SinatraInstrumentation do
    include SinatraRequestHelpers

    let(:settings) { double(:raise_errors => false) }
    let(:app) { double(:call => true, :settings => settings) }
    let(:env) do
      Rack::MockRequest.env_for("/path", "sinatra.route" => "GET /path", "REQUEST_METHOD" => "GET")
    end
    let(:middleware) { Appsignal::Rack::SinatraInstrumentation.new(app) }

    before { start_agent }
    around do |example|
      keep_transactions { example.run }
    end

    describe "#call" do
      before { allow(middleware).to receive(:raw_payload).and_return({}) }

      it "doesn't instrument requests" do
        expect { make_request }.to_not(change { created_transactions.count })
      end
    end

    describe ".settings" do
      subject { middleware.settings }

      it "returns the app's settings" do
        expect(subject).to eq(app.settings)
      end
    end
  end

  describe Appsignal::Rack::SinatraBaseInstrumentation do
    include SinatraRequestHelpers

    let(:settings) { double(:raise_errors => false) }
    let(:app) { double(:call => true, :settings => settings) }
    let(:env) do
      Rack::MockRequest.env_for("/path", "sinatra.route" => "GET /path", "REQUEST_METHOD" => "GET")
    end
    let(:appsignal_env) { :default }
    let(:options) { {} }
    let(:middleware) { Appsignal::Rack::SinatraBaseInstrumentation.new(app, options) }

    before { start_agent(:env => appsignal_env) }
    around { |example| keep_transactions { example.run } }

    describe "#initialize" do
      context "with no settings method in the Sinatra app" do
        let(:app) { double(:call => true) }

        it "does not raise errors" do
          expect(middleware.raise_errors_on).to be(false)
        end
      end

      context "with no raise_errors setting in the Sinatra app" do
        let(:app) { double(:call => true, :settings => double) }

        it "does not raise errors" do
          expect(middleware.raise_errors_on).to be(false)
        end
      end

      context "with raise_errors turned off in the Sinatra app" do
        let(:app) { double(:call => true, :settings => double(:raise_errors => false)) }

        it "raises errors" do
          expect(middleware.raise_errors_on).to be(false)
        end
      end

      context "with raise_errors turned on in the Sinatra app" do
        let(:app) { double(:call => true, :settings => double(:raise_errors => true)) }

        it "raises errors" do
          expect(middleware.raise_errors_on).to be(true)
        end
      end
    end

    describe "#call" do
      before { allow(middleware).to receive(:raw_payload).and_return({}) }

      context "when appsignal is not active" do
        let(:appsignal_env) { :inactive_env }

        it "does not instrument requests" do
          expect { make_request }.to_not(change { created_transactions.count })
        end

        it "calls the next middleware in the stack" do
          make_request

          expect(app).to have_received(:call).with(env)
        end
      end

      context "when appsignal is active" do
        context "without an error" do
          it "creates a transaction for the request" do
            expect { make_request }.to(change { created_transactions.count }.by(1))

            expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST)
          end

          it "reports a process_action.sinatra event" do
            make_request

            expect(last_transaction).to include_event("name" => "process_action.sinatra")
          end
        end

        context "with an error in sinatra.error" do
          let(:error) { ExampleException.new("error message") }
          before { env["sinatra.error"] = error }

          it "creates a transaction for the request" do
            expect { make_request }.to(change { created_transactions.count }.by(1))

            expect(last_transaction).to have_namespace(Appsignal::Transaction::HTTP_REQUEST)
          end

          context "when raise_errors is off" do
            let(:settings) { double(:raise_errors => false) }

            it "records the error" do
              make_request

              expect(last_transaction).to have_error("ExampleException", "error message")
            end
          end

          context "when raise_errors is on" do
            let(:settings) { double(:raise_errors => true) }

            it "does not record the error" do
              make_request

              expect(last_transaction).to_not have_error
            end
          end

          context "if sinatra.skip_appsignal_error is set" do
            before do
              env.merge!(
                "sinatra.error" => error,
                "sinatra.skip_appsignal_error" => true
              )
            end

            it "does not record the error" do
              make_request

              expect(last_transaction).to_not have_error
            end
          end
        end

        describe "action name" do
          it "sets the action to the request method and path" do
            make_request

            expect(last_transaction).to have_action("GET /path")
          end

          context "without 'sinatra.route' env" do
            let(:env) do
              Rack::MockRequest.env_for("/path", "REQUEST_METHOD" => "GET")
            end

            it "doesn't set an action name" do
              make_request

              expect(last_transaction).to_not have_action
            end
          end

          context "with mounted modular application" do
            before { env["SCRIPT_NAME"] = "/api" }

            it "sets the action name with an application prefix path" do
              make_request

              expect(last_transaction).to have_action("GET /api/path")
            end

            context "without 'sinatra.route' env" do
              let(:env) do
                Rack::MockRequest.env_for("/path", "REQUEST_METHOD" => "GET")
              end

              it "doesn't set an action name" do
                make_request

                expect(last_transaction).to_not have_action
              end
            end
          end
        end
      end
    end
  end
end
