require "pact_broker/app"

module PactBroker
  describe App do

    before do
      allow(PactBroker::DB).to receive(:run_migrations)
    end

    class TestApp < PactBroker::App
      def configure_database_connection
        # do nothing so we don't screw up our test connection
      end
    end

    let(:app) do
      TestApp.new do | configuration |
        configuration.database_connection = PactBroker::DB.connection
      end
    end

    describe "on start up" do
      it "fails any retrying triggered webhooks left over when the app shut down" do
        expect(PactBroker::Webhooks::Service).to receive(:fail_retrying_triggered_webhooks)
        app
      end
    end

    it "adds the x-pact-broker-version header" do
      get "/"
      expect(last_response.headers["x-pact-broker-version"]).to match(/\d/)
    end

    class TestMiddleware

      def initialize app
        @app = app
      end

      def call env
        self.class.calls << env
        @app.call(env)
      end

      def self.calls
        @calls ||= []
      end
    end

    it "acts like a ::Rack::Builder" do
      app.use TestMiddleware
      expect { get "/" }.to change { TestMiddleware.calls.count }.by(1)
    end

    describe "before_resource and after_resource" do
      CALLBACKS = []
      before do
        PactBroker.configuration.before_resource { | _resource | CALLBACKS << "before" }
        PactBroker.configuration.after_resource { | _resource | CALLBACKS << "after" }
      end

      it "executes the callbacks" do
        get "/"
        expect(CALLBACKS).to eq ["before", "after"]
      end
    end

    describe "use_custom_ui" do
      before do
        app.use_custom_ui(custom_ui)
        get "/", nil, { "HTTP_ACCEPT" => "text/html" }
      end

      context "when the UI returns a non 404 response" do
        let(:custom_ui) { double("ui", call: [200, {}, ["hello"]]) }

        it "returns the given page" do
          expect(last_response.body).to eq "hello"
        end
      end

      context "when the UI returns a 404 response" do
        let(:custom_ui) { double("ui", call: [404, {}, []]) }

        it "passes on the call to the rest of the app" do
          expect(last_response.status).to eq 200
        end
      end
    end

    describe "use_custom_api" do
      before do
        app.use_custom_api(custom_api)
        get "/", nil, { "HTTP_ACCEPT" => "application/hal+json" }
      end

      context "when the API returns a non 404 response" do
        let(:custom_api) { double("api", call: [200, {}, ["hello"]]) }

        it "returns the given resource" do
          expect(last_response.body).to eq "hello"
        end
      end

      context "when the custom API returns a 404 response" do
        let(:custom_api) { double("api", call: [404, {}, []]) }

        it "passes on the call to the rest of the app" do
          expect(last_response.body).to_not eq "hello"
        end
      end
    end

    describe "use_xxx_auth" do
      class TestAuth
        def initialize app, *_args
          @app = Rack::Auth::Basic.new(app, "Protected") do | username, password |
            username == "foo" && password == "bar"
          end
        end

        def call(env)
          @app.call(env)
        end
      end

      describe "use_api_auth" do
        it "allows the API to be protected" do
          app.use_api_auth TestAuth

          basic_authorize "foo", "bar"
          get "/"
          expect(last_response.status).to eq 200

          basic_authorize "foo", "wrong"
          get "/"
          expect(last_response.status).to eq 401
          expect(last_response.headers["WWW-Authenticate"]).to eq "Basic realm=\"Protected\""
        end
      end

      describe "use_ui_auth" do
        it "allows the UI to be protected" do
          app.use_ui_auth TestAuth

          basic_authorize "foo", "bar"
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
          expect(last_response.status).to eq 200

          basic_authorize "foo", "wrong"
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
          expect(last_response.status).to eq 401
          expect(last_response.headers["WWW-Authenticate"]).to eq "Basic realm=\"Protected\""
        end
      end

      context "ordering of calls" do
        class TestAuth1
          def initialize app; end
          def call env; end
        end

        class TestAuth2 < TestAuth1; end

        before do
          allow(TestAuth1).to receive(:new).and_return(test_auth_1)
          allow(TestAuth2).to receive(:new).and_return(test_auth_2)
        end

        let(:test_auth_1) { instance_double("TestAuth1", call: [404, {}, []]) }
        let(:test_auth_2) { instance_double("TestAuth2", call: [404, {}, []]) }

        it "calls the UI auth before the API auth" do
          expect(test_auth_1).to receive(:call).ordered
          expect(test_auth_2).to receive(:call).ordered
          app.use_ui_auth TestAuth1
          app.use_api_auth TestAuth2
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
        end
      end
    end

    describe "authenticate" do
      before do
        PactBroker.configuration.authenticate do | _resource, authorization_header, _options |
          authorization_header == "letmein"
        end
      end

      context "with an invalid Authorization header" do
        it "returns a 401" do
          get "/", {}, {"HTTP_AUTHORIZATION" => "wrong"}
          expect(last_response.status).to eq 401
        end
      end

      context "with valid Authorization header" do
        it "returns a 200" do
          get "/", {}, {"HTTP_AUTHORIZATION" => "letmein"}
          expect(last_response.status).to eq 200
        end
      end
    end

    describe "authenticate_with_basic_auth" do
      before do
        PactBroker.configuration.authenticate_with_basic_auth do | _resource, username, password, _options |
          username == "username" && password == "password"
        end
      end

      context "with a request for the API with incorrect username or password" do
        it "returns a 401" do
          basic_authorize "foo", "password"
          get "/"
          expect(last_response.status).to eq 401
        end
      end

      context "with a request for the UI with incorrect username or password" do
        it "returns a 401" do
          basic_authorize "foo", "password"
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
          expect(last_response.status).to eq 401
        end
      end

      context "with a request for diagnostics with incorrect username or password" do
        it "returns a 401" do
          basic_authorize "foo", "password"
          get "/diagnostic/status/heartbeat"
          expect(last_response.status).to eq 401
        end
      end

      context "with a request for the API with correct username and password" do
        it "returns a 200" do
          basic_authorize "username", "password"
          get "/"
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for the UI with correct username or password" do
        it "returns a 200" do
          basic_authorize "username", "password"
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for diagnostics with correct username or password" do
        it "returns a 200" do
          basic_authorize "username", "password"
          get "/diagnostic/status/heartbeat"
          expect(last_response.status).to eq 200
        end
      end
    end

    describe "authorize" do
      before do
        PactBroker.configuration.authorize do | resource, _options |
          resource.request.headers["Role"] == "important"
        end
      end

      context "with a request for the API with an authorized request" do
        it "returns a 200" do
          get "/", nil, {"HTTP_ROLE" => "important"}
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for the UI with an authorized request" do
        it "returns a 200" do
          get "/", nil, {"HTTP_ACCEPT" => "text/html", "HTTP_ROLE" => "important"}
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for diagnostics with an authorized request" do
        it "returns a 200" do
          get "/diagnostic/status/heartbeat", nil, {"HTTP_ROLE" => "important"}
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for the API with an unauthorized request" do
        it "returns a 403" do
          get "/"
          expect(last_response.status).to eq 403
        end
      end

      context "with a request for the UI with an unauthorized request" do
        it "returns a 200 because there's no point doing authorization on the UI at the moment" do
          get "/", nil, {"HTTP_ACCEPT" => "text/html"}
          expect(last_response.status).to eq 200
        end
      end

      context "with a request for diagnostics with an unauthorized request" do
        it "returns a 403" do
          get "/diagnostic/status/heartbeat"
          expect(last_response.status).to eq 403
        end
      end
    end

    describe "transactions", no_db_clean: true do
      let(:pact_content) { load_fixture("a_consumer-a_provider.json") }
      let(:path) { "/pacts/provider/A%20Provider/consumer/A%20Consumer/versions/1.2.3" }
      let(:response_body_json) { JSON.parse(subject.body) }

      before do
        PactBroker::TestDatabase.truncate
        allow_any_instance_of(PactBroker::Pacts::Repository).to receive(:create).and_raise("an error")
      end

      after do
        PactBroker::TestDatabase.truncate
      end

      subject { put path, pact_content, { "CONTENT_TYPE" => "application/json" }; last_response  }

      it "wraps the API with a database transaction" do
        expect { subject }.to_not change { PactBroker::Domain::Pacticipant.count }
      end
    end

    describe "when resource is not found" do
      subject { get("/does/not/exist", nil, { "CONTENT_TYPE" => "application/hal+json" })  }

      it "returns a Content-Type of application/hal+json" do
        expect(subject.headers["Content-Type"]).to eq "application/json;charset=utf-8"
      end

      it "returns a JSON body" do
        expect(subject.body).to eq "{\"error\":\"The requested document was not found on this server.\"}"
      end

      it "returns a 404" do
        expect(subject.status).to eq 404
      end
    end
  end
end
