require 'spec_helper'

describe Billy::ProxyHandler do
  subject { Billy::ProxyHandler.new }
  let(:request) do
    {
      method:   'post',
      url:      'http://usern:pw@example.test:8080/index?some=param',
      headers:  { 'Accept-Encoding'  => 'gzip',
                  'Cache-Control'    => 'no-cache' },
      body:     'Some body'
    }
  end

  describe '#handles_request?' do
    context 'with non-whitelisted requests enabled' do
      before do
        expect(Billy.config).to receive(:non_whitelisted_requests_disabled).and_return(false)
      end

      it 'handles all requests' do
        expect(subject.handles_request?(request[:method],
                                        request[:url],
                                        request[:headers],
                                        request[:body])).to be true
      end
    end
    context 'with non-whitelisted requests disabled' do
      before do
        expect(Billy.config).to receive(:non_whitelisted_requests_disabled).and_return(true)
      end

      it 'does not handle requests that are not white or black listed' do
        expect(subject.handles_request?(request[:method],
                                        request[:url],
                                        request[:headers],
                                        request[:body])).to be false
      end

      context 'a whitelisted host' do
        context 'with a blacklisted path' do
          before do
            expect(Billy.config).to receive(:path_blacklist) { ['/index'] }
          end

          it 'does not handle requests for blacklisted paths' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test:8080/index?some=param',
                                            request[:headers],
                                            request[:body])).to be false
          end
        end

        context 'as a regex' do
          before do
            expect(Billy.config).to receive(:whitelist) { [%r{example\.test\/a}] }
          end

          it 'handles requests for the host without a port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test/a',
                                            request[:headers],
                                            request[:body])).to be true
          end

          it 'handles requests for the host with a port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test:8080/a',
                                            request[:headers],
                                            request[:body])).to be true
          end
        end

        context 'without a port' do
          before do
            expect(Billy.config).to receive(:whitelist) { ['example.test'] }
          end

          it 'handles requests for the host without a port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test',
                                            request[:headers],
                                            request[:body])).to be true
          end

          it 'handles requests for the host with a port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test:8080',
                                            request[:headers],
                                            request[:body])).to be true
          end
        end

        context 'with a port' do
          before do
            expect(Billy.config).to receive(:whitelist) { ['example.test:8080'] }
          end

          it 'does not handle requests whitelisted for a specific port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test',
                                            request[:headers],
                                            request[:body])).to be false
          end

          it 'handles requests for the host with a port' do
            expect(subject.handles_request?(request[:method],
                                            'http://example.test:8080',
                                            request[:headers],
                                            request[:body])).to be true
          end
        end
      end
    end
  end

  describe '#handle_request' do
    it 'returns nil if it does not handle the request' do
      expect(subject).to receive(:handles_request?).and_return(false)
      expect(subject.handle_request(request[:method],
                                    request[:url],
                                    request[:headers],
                                    request[:body])).to be nil
    end

    context 'with a handled request' do
      let(:response_header) do
        header = Struct.new(:status, :raw).new
        header.status = 200
        header.raw = {}
        header
      end

      let(:em_response)     { double('response') }
      let(:em_request)      do
        double('EM::HttpRequest', error: nil, response: em_response, response_header: response_header)
      end

      before do
        allow(subject).to receive(:handles_request?).and_return(true)
        allow(em_response).to receive(:force_encoding).and_return('The response body')
        allow(EventMachine::HttpRequest).to receive(:new).and_return(em_request)
        expect(em_request).to receive(:post).and_return(em_request)
      end

      it 'returns any error in the response' do
        allow(em_request).to receive(:error).and_return('ERROR!')
        expect(subject.handle_request(request[:method],
                                      request[:url],
                                      request[:headers],
                                      request[:body])).to eql(error: "Request to #{request[:url]} failed with error: ERROR!")
      end

      it 'returns a hashed response if the request succeeds' do
        expect(subject.handle_request(request[:method],
                                      request[:url],
                                      request[:headers],
                                      request[:body])).to eql(status: 200, headers: { 'Connection' => 'close' }, content: 'The response body')
      end

      it 'returns nil if both the error and response are for some reason nil' do
        allow(em_request).to receive(:response).and_return(nil)
        expect(subject.handle_request(request[:method],
                                      request[:url],
                                      request[:headers],
                                      request[:body])).to be nil
      end

      it 'caches the response if cacheable' do
        expect(subject).to receive(:allowed_response_code?).and_return(true)
        expect(Billy::Cache.instance).to receive(:store)
        subject.handle_request(request[:method],
                               request[:url],
                               request[:headers],
                               request[:body])
      end

      it 'does NOT cache the response if the host is whitelisted but not cache_whitelisted' do
        uri = Addressable::URI.parse(request[:url])

        expect(subject).to receive(:allowed_response_code?).and_return(true)
        expect(Billy.config).to receive(:whitelist).and_return([uri.host])
        expect(Billy.config).to receive(:cache_whitelist).and_return([])

        expect(Billy::Cache.instance).not_to receive(:store)
        subject.handle_request(request[:method],
                               request[:url],
                               request[:headers],
                               request[:body])
      end

      it 'caches the response if the host is whitelisted AND cache_whitelisted' do
        uri = Addressable::URI.parse(request[:url])

        expect(subject).to receive(:allowed_response_code?).and_return(true)
        expect(Billy.config).to receive(:whitelist).and_return([uri.host])
        expect(Billy.config).to receive(:cache_whitelist).and_return([uri.host])

        expect(Billy::Cache.instance).to receive(:store)
        subject.handle_request(request[:method],
                               request[:url],
                               request[:headers],
                               request[:body])
      end

      it 'uses the timeouts defined in configuration' do
        allow(Billy.config).to receive(:proxied_request_inactivity_timeout).and_return(42)
        allow(Billy.config).to receive(:proxied_request_connect_timeout).and_return(24)

        expect(EventMachine::HttpRequest).to receive(:new).with(request[:url],
                                                                {
                                                                  inactivity_timeout: 42,
                                                                  connect_timeout: 24
                                                                }
        )

        subject.handle_request(request[:method],
                               request[:url],
                               request[:headers],
                               request[:body])
      end

      it 'uses the internal proxy settings defined in configuration' do
        allow(Billy.config).to receive(:proxied_request_host).and_return('10.10.10.10')
        allow(Billy.config).to receive(:proxied_request_port).and_return('2080')

        expect(EventMachine::HttpRequest).to receive(:new).with(request[:url],
                                                                {
                                                                  inactivity_timeout: 10,
                                                                  connect_timeout: 5,
                                                                  proxy: { host: '10.10.10.10', port: '2080' }
                                                                }
        )

        subject.handle_request(request[:method],
                               request[:url],
                               request[:headers],
                               request[:body])
      end
    end
  end

  describe '#build_request_options' do
    it 'creates authorization header when URL has basic auth' do
      request_options = subject.send(:build_request_options, request[:url],
                                                             request[:headers],
                                                             request[:body])
      expect(request_options[:head]).to have_key 'authorization'
    end

    it 'does not include authorization header without basic auth' do
      request_options = subject.send(:build_request_options, request[:url].gsub('usern:pw@',''),
                                                             request[:headers],
                                                             request[:body])
      expect(request_options[:head]).not_to have_key 'authorization'
    end
  end
end
