require 'spec_helper'

require 'datadog/core/transport/http/adapters/net'
require 'datadog/core/configuration/agent_settings'

RSpec.describe Datadog::Core::Transport::HTTP::Adapters::Net do
  subject(:adapter) { described_class.new(agent_settings) }

  let(:hostname) { 'hostname' }
  let(:port) { 9999 }
  let(:timeout) { 15 }
  let(:ssl) { false }
  let(:proxy_addr) { nil } # We currently disable proxy for transport HTTP requests
  let(:agent_settings) do
    Datadog::Core::Configuration::AgentSettings.new(
      adapter: nil,
      ssl: ssl,
      uds_path: nil,
      hostname: hostname,
      port: port,
      timeout_seconds: timeout,
    )
  end

  shared_context 'HTTP connection stub' do
    let(:http_connection) { instance_double(::Net::HTTP) }

    before do
      allow(::Net::HTTP).to receive(:new)
        .with(
          adapter.hostname,
          adapter.port,
          proxy_addr
        ).and_return(http_connection)

      allow(http_connection).to receive(:open_timeout=).with(adapter.timeout)
      allow(http_connection).to receive(:read_timeout=).with(adapter.timeout)
      allow(http_connection).to receive(:use_ssl=).with(adapter.ssl)

      allow(http_connection).to receive(:start).and_yield(http_connection)
    end
  end

  shared_context 'HTTP Env' do
    let(:env) do
      instance_double(
        Datadog::Core::Transport::HTTP::Env,
        path: path,
        query: nil,
        body: body,
        headers: headers,
        form: form
      )
    end

    let(:path) { '/foo' }
    let(:body) { '{}' }
    let(:headers) { {} }
    let(:form) { {} }
  end

  describe '#initialize' do
    context 'given a :timeout option' do
      let(:timeout) { double('timeout') }

      it { is_expected.to have_attributes(timeout: timeout) }
    end

    context 'given a :ssl option' do
      context 'with true' do
        let(:ssl) { true }

        it { is_expected.to have_attributes(ssl: true) }
      end
    end
  end

  describe '#open' do
    include_context 'HTTP connection stub'

    it 'opens and yields a Net::HTTP connection' do
      expect { |b| adapter.open(&b) }.to yield_with_args(http_connection)
    end
  end

  describe '#call' do
    include_context 'HTTP Env'

    subject(:call) { adapter.call(env) }

    context 'given an HTTP::Env with a verb' do
      before { allow(env).to receive(:verb).and_return(verb) }

      context ':get' do
        include_context 'HTTP connection stub'
        let(:verb) { :get }
        let(:http_response) { double('http_response') }

        context 'and a simple get request' do
          let(:get) { instance_double(Net::HTTP::Get) }

          it 'makes a GET and produces a response' do
            expect(Net::HTTP::Get)
              .to receive(:new)
              .with(env.path, env.headers)
              .and_return(get)

            expect(http_connection)
              .to receive(:request)
              .with(get)
              .and_return(http_response)

            is_expected.to be_a_kind_of(described_class::Response)
            expect(call.http_response).to be(http_response)
          end
        end
      end

      context ':post' do
        include_context 'HTTP connection stub'
        let(:verb) { :post }
        let(:http_response) { double('http_response') }

        context 'and an empty form body' do
          let(:form) { {} }
          let(:post) { instance_double(Net::HTTP::Post) }

          it 'makes a POST and produces a response' do
            expect(Net::HTTP::Post)
              .to receive(:new)
              .with(env.path, env.headers)
              .and_return(post)

            expect(post)
              .to receive(:body=)
              .with(env.body)

            expect(http_connection)
              .to receive(:request)
              .with(post)
              .and_return(http_response)

            is_expected.to be_a_kind_of(described_class::Response)
            expect(call.http_response).to be(http_response)
          end
        end

        context 'and a form with fields' do
          let(:form) { {'id' => '1234', 'type' => 'Test'} }
          let(:multipart_post) { instance_double(Datadog::Core::Vendor::Net::HTTP::Post::Multipart) }

          it 'makes a multipart POST and produces a response' do
            expect(Datadog::Core::Vendor::Net::HTTP::Post::Multipart)
              .to receive(:new)
              .with(env.path, env.form, env.headers)
              .and_return(multipart_post)

            expect(http_connection)
              .to receive(:request)
              .with(multipart_post)
              .and_return(http_response)

            is_expected.to be_a_kind_of(described_class::Response)
            expect(call.http_response).to be(http_response)
          end
        end
      end

      context ':head' do
        let(:verb) { :head }

        it { expect { call }.to raise_error(described_class::UnknownHTTPMethod) }
      end

      context ':put' do
        let(:verb) { :put }

        it { expect { call }.to raise_error(described_class::UnknownHTTPMethod) }
      end

      context ':delete' do
        let(:verb) { :delete }

        it { expect { call }.to raise_error(described_class::UnknownHTTPMethod) }
      end

      context ':patch' do
        let(:verb) { :patch }

        it { expect { call }.to raise_error(described_class::UnknownHTTPMethod) }
      end
    end
  end

  describe '#post' do
    include_context 'HTTP connection stub'
    include_context 'HTTP Env'

    subject(:post) { adapter.post(env) }

    let(:http_response) { double('http_response') }

    before { expect(http_connection).to receive(:request).and_return(http_response) }

    it 'produces a response' do
      is_expected.to be_a_kind_of(described_class::Response)
      expect(post.http_response).to be(http_response)
    end
  end

  describe '#url' do
    subject(:url) { adapter.url }

    let(:hostname) { 'local.test' }
    let(:port) { '345' }
    let(:timeout) { 7 }

    it { is_expected.to eq('http://local.test:345?timeout=7') }
  end
end
