require 'spec_helper'

describe Localeapp::ApiCaller, ".new(endpoint, options = {})" do
  it "stores the endpoint and options" do
    api_caller = Localeapp::ApiCaller.new(:endpoint, :foo => :bar)
    api_caller.endpoint.should == :endpoint
    api_caller.options.should == { :foo => :bar }
  end
end

describe Localeapp::ApiCaller, "#call(object)" do
  before do
    with_configuration do
      @api_caller = Localeapp::ApiCaller.new(:test)
    end
    @url = 'https://example.com/test'
    @api_caller.stub!(:test_endpoint).and_return([:get, @url])
    @api_caller.stub!(:sleep_if_retrying)
  end

  it "gets the method and url for the endpoint" do
    @api_caller.should_receive(:test_endpoint).with({}).and_return([:get, @url])
    RestClient::Request.stub!(:execute).and_return(double('response', :code => 200))
    @api_caller.call(self)
  end

  it "passes through any url options" do
    @api_caller.should_receive(:test_endpoint).with({:foo => :bar}).and_return([:get, @url])
    @api_caller.options[:url_options] = { :foo => :bar }
    RestClient::Request.stub!(:execute).and_return(double('response', :code => 200))
    @api_caller.call(self)
  end

  it "adds the gem version to the headers" do
    RestClient::Request.should_receive(:execute).with(hash_including(:headers => { :x_localeapp_gem_version => Localeapp::VERSION })).and_return(double('response', :code => 200))
    @api_caller.call(self)
  end

  context "Proxy" do
    before do
      RestClient::Request.stub!(:execute).and_return(double('response', :code => 200))
    end

    it "sets the proxy if configured" do
      Localeapp.configuration.proxy = "http://localhost:8888"
      RestClient.should_receive(:proxy=).with('http://localhost:8888')
      @api_caller.call(self)
    end

    it "doesn't set the proxy if it's not configured" do
      RestClient.should_not_receive(:proxy=)
      @api_caller.call(self)
    end
  end

  context "SSL Certificate Validation" do
    it "set the HTTPClient verify_ssl to VERIFY_PEER if ssl_verify is set to true" do
      Localeapp.configuration.ssl_verify = true
      RestClient::Request.should_receive(:execute).with(hash_including(:verify_ssl => OpenSSL::SSL::VERIFY_PEER)).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end

    it "set the HTTPClient verify_ssl to false if ssl_verify is set to false" do
      RestClient::Request.should_receive(:execute).with(hash_including(:verify_ssl => false)).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end
  end

  context "SSL Certificate Validation" do
    it "set the HTTPClient ca_file to the value given to ssl_ca_file if it's not nil" do
      Localeapp.configuration.ssl_ca_file = '/tmp/test'
      RestClient::Request.should_receive(:execute).with(hash_including(:ca_file => '/tmp/test')).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end

    it "doesn't set the HTTPClient ca_file if ssl_ca_file is nil" do
      Localeapp.configuration.ssl_ca_file = nil
      RestClient::Request.should_receive(:execute).with(hash_not_including(:ca_file => nil)).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end
  end

  context "a GET request" do
    it "makes the call to the api" do
      RestClient::Request.should_receive(:execute).with(hash_including(:url => @url, :method => :get)).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end

    it "adds any :headers to the api call" do
      RestClient::Request.should_receive(:execute).with(hash_including(:headers => { :x_localeapp_gem_version => Localeapp::VERSION, :foo => :bar })).and_return(double('response', :code => 200))
      @api_caller.options[:headers] = { :foo => :bar }
      @api_caller.call(self)
    end
  end


  context " a POST request" do
    before do
      @api_caller.stub!(:test_endpoint).and_return([:post, @url])
      @api_caller.options[:payload] = "test data"
    end

    it "makes the call to the api using :payload as the payload" do
      RestClient::Request.should_receive(:execute).with(hash_including(:url => @url, :payload => "test data", :method => :post)).and_return(double('response', :code => 200))
      @api_caller.call(self)
    end

    it "adds any :headers to the api call" do
      RestClient::Request.should_receive(:execute).with(hash_including(:headers => { :x_localeapp_gem_version => Localeapp::VERSION, :foo => :bar })).and_return(double('response', :code => 200))
      @api_caller.options[:headers] = { :foo => :bar }
      @api_caller.call(self)
    end
  end

  context " call succeeded" do
    before do
      FakeWeb.register_uri(:get, @url, :body => '', :status => [200, 'OK'])
      @object = double('calling object')
    end

    it "calls the :success option callback if present" do
      @api_caller.options[:success] = :success
      @object.should_receive(:success).with(kind_of(RestClient::Response))
      @api_caller.call(@object)
    end

    it "does nothing if :success option callback not present" do
      @object.should_not_receive(:success)
      @api_caller.call(@object)
    end

    it "should not try the call again" do
      @api_caller.max_connection_attempts = 2
      @api_caller.call(@object)
      @api_caller.connection_attempts.should == 1
    end

    it "doesn't call the failure handler" do
      @api_caller.options[:failure] = :failure
      @object.should_not_receive(:failure)
      @api_caller.call(@object)
    end
  end

  context " call failed" do
    before do
      FakeWeb.register_uri(:get, @url, :body => '', :status => [500, 'Internal Server Error'])
      @object = double('calling object')
    end

    it "retries call, up to value of :max_connection_attempts option" do
      @api_caller.max_connection_attempts = 2
      @api_caller.call(@object)
      @api_caller.connection_attempts.should == 2
    end

    it "backs off each retry attempt" do
      @api_caller.should_receive(:sleep_if_retrying)
      @api_caller.call(@object)
    end

    it "calls the :failure option callback if present" do
      @api_caller.options[:failure] = :fail
      @object.should_receive(:fail).with(kind_of(RestClient::Response))
      @api_caller.call(@object)
    end

    it "does nothing if :failure option callback not present" do
      @object.should_not_receive(:fail)
      @api_caller.call(@object)
    end

    {
      304 => 'Not Modified',
      404 => 'Resource Not Found',
      500 => 'Internal Server Error',
      # Work out when this could happen
      # 501 => 'Not Implemented',
      502 => 'Bad Gateway',
      503 => 'Service Unavailable',
      504 => 'Gateway Timeout',
      # Work out when this could happen
      # 505 => 'HTTP Version Not Supported',
      # Work out when this could happen
      # 506 => 'Variant Also Negotiates',
      # Work out when this could happen
      # 507 => 'Insufficient Storage', #WebDAV
      # Work out when this could happen
      # 509 => 'Bandwidth Limit Exceeded', #Apache
      # Work out when this could happen
      # 510 => 'Not Extended'
    }.each do |code, reason|
      it "fails when response is #{code} #{reason}" do
        FakeWeb.register_uri(:get, @url, :body => '', :status => [code.to_s, reason])
        @api_caller.options[:failure] = :fail
        @object.should_receive(:fail)
        @api_caller.call(@object)
      end
    end

    it "handles ECONNREFUSED" do
      RestClient::Request.stub(:execute).and_raise(Errno::ECONNREFUSED)
      @api_caller.options[:failure] = :fail
      @object.should_receive(:fail)
      @api_caller.call(@object)
    end

    it "handles SocketError" do
      RestClient::Request.stub(:execute).and_raise(SocketError)
      @api_caller.options[:failure] = :fail
      @object.should_receive(:fail)
      @api_caller.call(@object)
    end
  end
end