require 'spec_helper'
require 'webmock/rspec'
require 'puppet/http'

describe Puppet::HTTP::Session do
  let(:ssl_context) { Puppet::SSL::SSLContext.new }
  let(:client) { Puppet::HTTP::Client.new(ssl_context: ssl_context) }
  let(:uri) { URI.parse('https://www.example.com') }
  let(:good_service) {
    double('good', url: uri, connect: nil)
  }
  let(:bad_service) {
    service = double('good', url: uri)
    allow(service).to receive(:connect).and_raise(Puppet::HTTP::ConnectionError, 'whoops')
    service
  }

  class DummyResolver
    attr_reader :count

    def initialize(service)
      @service = service
      @count = 0
    end

    def resolve(session, name, &block)
      @count += 1
      yield @service
    end
  end

  context 'when routing' do
    it 'returns the first resolved service' do
      Puppet[:log_level] = :debug
      resolvers = [DummyResolver.new(bad_service), DummyResolver.new(good_service)]
      session = described_class.new(client, resolvers)
      resolved = session.route_to(:ca)

      expect(resolved).to eq(good_service)
      expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Connection to #{uri} failed, trying next route: whoops"))
    end

    it 'only resolves once per session' do
      resolver = DummyResolver.new(good_service)
      session = described_class.new(client, [resolver])
      session.route_to(:ca)
      session.route_to(:ca)

      expect(resolver.count).to eq(1)
    end

    it 'raises if there are no more routes' do
      resolvers = [DummyResolver.new(bad_service)]
      session = described_class.new(client, resolvers)

      expect {
        session.route_to(:ca)
      }.to raise_error(Puppet::HTTP::RouteError, 'No more routes to ca')
    end

    it 'accepts an ssl context to use when connecting' do
      alt_context = Puppet::SSL::SSLContext.new
      expect(good_service).to receive(:connect).with(ssl_context: alt_context)

      resolvers = [DummyResolver.new(good_service)]
      session = described_class.new(client, resolvers)
      session.route_to(:ca, ssl_context: alt_context)
    end

    it 'raises for unknown service names' do
      expect {
        session = described_class.new(client, [])
        session.route_to(:westbound)
      }.to raise_error(ArgumentError, "Unknown service westbound")
    end
  end

  context 'when creating services' do
    let(:session) { described_class.new(client, []) }

    it 'defaults the server and port based on settings' do
      Puppet[:ca_server] = 'ca.example.com'
      Puppet[:ca_port] = 8141
      service = session.create_service(:ca)

      expect(service.url.to_s).to eq("https://ca.example.com:8141/puppet-ca/v1")
    end

    it 'accepts server and port arguments' do
      service = session.create_service(:ca, 'ca2.example.com', 8142)

      expect(service.url.to_s).to eq("https://ca2.example.com:8142/puppet-ca/v1")
    end

    it 'raises for unknown service names' do
      expect {
        session = described_class.new(client, [])
        session.create_service(:westbound)
      }.to raise_error(ArgumentError, "Unknown service westbound")
    end
  end
end