require 'spec_helper'

describe ApiAuth::RequestDrivers::GrapeRequest do
  let(:default_method) { 'PUT' }
  let(:default_params) do
    { 'message' => "hello\nworld" }
  end
  let(:default_options) do
    {
      method: method,
      params: params
    }
  end
  let(:default_env) do
    Rack::MockRequest.env_for('/', options)
  end
  let(:method) { default_method }
  let(:params) { default_params }
  let(:options) { default_options.merge(request_headers) }
  let(:env) { default_env }

  let(:request) do
    Grape::Request.new(env)
  end

  let(:timestamp) { Time.now.utc.httpdate }
  let(:request_headers) do
    {
      'HTTP_X_AUTHORIZATION' => 'APIAuth 1044:12345',
      'HTTP_X_AUTHORIZATION_CONTENT_SHA256' => 'bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=',
      'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain',
      'HTTP_X_HMAC_DATE' => timestamp
    }
  end

  subject(:driven_request) { ApiAuth::RequestDrivers::GrapeRequest.new(request) }

  describe 'getting headers correctly' do
    it 'gets the content_type' do
      expect(driven_request.content_type).to eq('text/plain')
    end

    it 'gets the content_hash' do
      expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
    end

    it 'gets the request_uri' do
      expect(driven_request.request_uri).to eq('http://example.org/')
    end

    it 'gets the timestamp' do
      expect(driven_request.timestamp).to eq(timestamp)
    end

    it 'gets the authorization_header' do
      expect(driven_request.authorization_header).to eq('APIAuth 1044:12345')
    end

    describe '#calculated_hash' do
      it 'calculates hash from the body' do
        expect(driven_request.calculated_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
      end

      context 'no body' do
        let(:params) { {} }

        it 'treats no body as empty string' do
          expect(driven_request.calculated_hash).to eq('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=')
        end
      end
    end

    describe 'http_method' do
      context 'when put request' do
        let(:method) { 'put' }

        it 'returns upcased put' do
          expect(driven_request.http_method).to eq('PUT')
        end
      end

      context 'when get request' do
        let(:method) { 'get' }

        it 'returns upcased get' do
          expect(driven_request.http_method).to eq('GET')
        end
      end
    end
  end

  describe 'setting headers correctly' do
    let(:request_headers) do
      {
        'HTTP_X_HMAC_CONTENT_TYPE' => 'text/plain'
      }
    end

    describe '#populate_content_hash' do
      context 'when getting' do
        let(:method) { 'get' }

        it "doesn't populate content hash" do
          driven_request.populate_content_hash
          expect(request.headers['X-Authorization-Content-Sha256']).to be_nil
        end
      end

      context 'when posting' do
        let(:method) { 'post' }

        it 'populates content bash' do
          driven_request.populate_content_hash
          expect(request.headers['X-Authorization-Content-Sha256']).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
        end

        it 'refreshes the cached headers' do
          driven_request.populate_content_hash
          expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
        end
      end

      context 'when putting' do
        let(:method) { 'put' }

        it 'populates content hash' do
          driven_request.populate_content_hash
          expect(request.headers['X-Authorization-Content-Sha256']).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
        end

        it 'refreshes the cached headers' do
          driven_request.populate_content_hash
          expect(driven_request.content_hash).to eq('bxVSdFeR6aHBtw7+EBi5Bt8KllUZpUutOg9ChQmaSPA=')
        end
      end

      context 'when deleting' do
        let(:method) { 'delete' }

        it "doesn't populate content hash" do
          driven_request.populate_content_hash
          expect(request.headers['X-Authorization-Content-Sha256']).to be_nil
        end
      end
    end

    describe '#set_date' do
      before do
        allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
      end

      it 'sets the date header of the request' do
        allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp)
        driven_request.set_date
        expect(request.headers['Date']).to eq(timestamp)
      end

      it 'refreshes the cached headers' do
        driven_request.set_date
        expect(driven_request.timestamp).to eq(timestamp)
      end
    end

    describe '#set_auth_header' do
      it 'sets the auth header' do
        driven_request.set_auth_header('APIAuth 1044:54321')
        expect(request.headers['Authorization']).to eq('APIAuth 1044:54321')
      end
    end
  end

  describe 'content_hash_mismatch?' do
    context 'when getting' do
      let(:method) { 'get' }

      it 'is false' do
        expect(driven_request.content_hash_mismatch?).to be false
      end
    end

    context 'when posting' do
      let(:method) { 'post' }

      context 'when calculated matches sent' do
        it 'is false' do
          expect(driven_request.content_hash_mismatch?).to be false
        end
      end

      context "when calculated doesn't match sent" do
        let(:params) { { 'message' => 'hello only' } }

        it 'is true' do
          expect(driven_request.content_hash_mismatch?).to be true
        end
      end
    end

    context 'when putting' do
      let(:method) { 'put' }

      context 'when calculated matches sent' do
        it 'is false' do
          puts driven_request.calculated_hash
          expect(driven_request.content_hash_mismatch?).to be false
        end
      end

      context "when calculated doesn't match sent" do
        let(:params) { { 'message' => 'hello only' } }
        it 'is true' do
          expect(driven_request.content_hash_mismatch?).to be true
        end
      end
    end

    context 'when deleting' do
      let(:method) { 'delete' }

      it 'is false' do
        expect(driven_request.content_hash_mismatch?).to be false
      end
    end
  end

  describe 'authentics?' do
    let(:request_headers) { {} }
    let(:signed_request) do
      ApiAuth.sign!(request, '1044', '123')
    end

    context 'when getting' do
      let(:method) { 'get' }

      it 'validates that the signature in the request header matches the way we sign it' do
        expect(ApiAuth.authentic?(signed_request, '123')).to eq true
      end
    end

    context 'when posting' do
      let(:method) { 'post' }

      it 'validates that the signature in the request header matches the way we sign it' do
        expect(ApiAuth.authentic?(signed_request, '123')).to eq true
      end
    end

    context 'when putting' do
      let(:method) { 'put' }

      let(:signed_request) do
        ApiAuth.sign!(request, '1044', '123')
      end

      it 'validates that the signature in the request header matches the way we sign it' do
        expect(ApiAuth.authentic?(signed_request, '123')).to eq true
      end
    end

    context 'when deleting' do
      let(:method) { 'delete' }

      let(:signed_request) do
        ApiAuth.sign!(request, '1044', '123')
      end

      it 'validates that the signature in the request header matches the way we sign it' do
        expect(ApiAuth.authentic?(signed_request, '123')).to eq true
      end
    end
  end

  describe 'fetch_headers' do
    it 'returns request headers' do
      expect(driven_request.fetch_headers).to include(
        'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
      )
    end
  end
end
