require 'puppet'
require 'spec_helper'
require 'puppet/provider/keystone_endpoint/openstack'

describe Puppet::Type.type(:keystone_endpoint).provider(:openstack) do

  let(:set_env) do
    ENV['OS_USERNAME']     = 'test'
    ENV['OS_PASSWORD']     = 'abc123'
    ENV['OS_SYSTEM_SCOPE'] = 'all'
    ENV['OS_AUTH_URL']     = 'http://127.0.0.1:5000'
  end

  describe 'when managing an endpoint' do

    let(:endpoint_attrs) do
      {
        :title        => 'region/endpoint',
        :ensure       => 'present',
        :public_url   => 'http://127.0.0.1:5000',
        :internal_url => 'http://127.0.0.1:5001',
        :admin_url    => 'http://127.0.0.1:5002'
      }
    end

    let(:resource) do
      Puppet::Type::Keystone_endpoint.new(endpoint_attrs)
    end

    let(:provider) do
      described_class.new(resource)
    end

    before(:each) do
      set_env
      described_class.endpoints = nil
      described_class.services  = nil
    end

    describe '#create' do
      before(:each) do
        described_class.expects(:openstack)
          .with('endpoint', 'create', '--format', 'shell',
                ['service_id1', 'admin', 'http://127.0.0.1:5002', '--region', 'region'])
          .returns('admin_url="http://127.0.0.1:5002"
id="endpoint1_id"
region="region"
')
        described_class.expects(:openstack)
          .with('endpoint', 'create', '--format', 'shell',
                ['service_id1', 'internal', 'http://127.0.0.1:5001', '--region', 'region'])
          .returns('internal_url="http://127.0.0.1:5001"
id="endpoint2_id"
region="region"
')
        described_class.expects(:openstack)
          .with('endpoint', 'create', '--format', 'shell',
                ['service_id1', 'public', 'http://127.0.0.1:5000', '--region', 'region'])
          .returns('public_url="http://127.0.0.1:5000"
id="endpoint3_id"
region="region"
')
        described_class.expects(:openstack)
          .with('service', 'list', '--quiet', '--format', 'csv', [])
          .returns('"ID","Name","Type"
"service_id1","endpoint","type_one"
')
      end
      context 'without the type' do
        it 'creates an endpoint' do
          provider.create
          expect(provider.exists?).to be_truthy
          expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id')
        end
      end
      context 'with the type' do
        let(:endpoint_attrs) do
          {
            :title        => 'region/endpoint',
            :ensure       => 'present',
            :public_url   => 'http://127.0.0.1:5000',
            :internal_url => 'http://127.0.0.1:5001',
            :admin_url    => 'http://127.0.0.1:5002',
            :type         => 'type_one'
          }
        end

        it 'creates an endpoint' do
          provider.create
          expect(provider.exists?).to be_truthy
          expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id')
        end
      end
    end

    describe '#destroy' do
      it 'destroys an endpoint' do
        provider.instance_variable_get('@property_hash')[:id] = 'endpoint1_id,endpoint2_id,endpoint3_id'
        described_class.expects(:openstack)
          .with('endpoint', 'delete', 'endpoint1_id')
        described_class.expects(:openstack)
          .with('endpoint', 'delete', 'endpoint2_id')
        described_class.expects(:openstack)
          .with('endpoint', 'delete', 'endpoint3_id')
        provider.destroy
        expect(provider.exists?).to be_falsey
      end
    end

    describe '#exists' do
      context 'when tenant does not exist' do
        subject(:response) do
          provider.exists?
        end

        it { is_expected.to be_falsey }
      end
    end

    describe '#instances' do
      context 'basic' do
        it 'finds every tenant' do
          described_class.expects(:openstack)
            .with('endpoint', 'list', '--quiet', '--format', 'csv', [])
            .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL"
"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002"
"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001"
"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000"
')
          instances = described_class.instances
          expect(instances.count).to eq(1)
        end
      end
      context 'many different region' do
        it 'should not mix up the endpoints' do
          described_class.expects(:openstack)
            .with('endpoint', 'list', '--quiet', '--format', 'csv', [])
            .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL"
"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://One-127.0.0.1:5002"
"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://One-127.0.0.1:5001"
"endpoint3_id","RegionOne","keystone","identity",True,"public","https://One-127.0.0.1:5000"
"endpoint4_id","RegionTwo","keystone","identity",True,"admin","http://Two-127.0.0.1:5002"
"endpoint5_id","RegionTwo","keystone","identity",True,"internal","https://Two-127.0.0.1:5001"
"endpoint6_id","RegionTwo","keystone","identity",True,"public","https://Two-127.0.0.1:5000"
"endpoint7_id","RegionThree","keystone","identity",True,"admin","http://Three-127.0.0.1:5002"
"endpoint8_id","RegionThree","keystone","identity",True,"internal","https://Three-127.0.0.1:5001"
"endpoint9_id","RegionThree","keystone","identity",True,"public","https://Three-127.0.0.1:5000"
"endpoint10_id","RegionFour","keystone","identity",True,"admin","http://Four-127.0.0.1:5002"
"endpoint11_id","RegionFour","keystone","identity",True,"internal","https://Four-127.0.0.1:5001"
"endpoint12_id","RegionFour","keystone","identity",True,"public","https://Four-127.0.0.1:5000"
"endpoint13_id","RegionFive","keystone","identity",True,"admin","http://Five-127.0.0.1:5002"
"endpoint14_id","RegionFive","keystone","identity",True,"internal","https://Five-127.0.0.1:5001"
"endpoint15_id","RegionFive","keystone","identity",True,"public","https://Five-127.0.0.1:5000"
"endpoint16_id","RegionSix","keystone","identity",True,"admin","http://Six-127.0.0.1:5002"
"endpoint17_id","RegionSix","keystone","identity",True,"internal","https://Six-127.0.0.1:5001"
"endpoint18_id","RegionSix","keystone","identity",True,"public","https://Six-127.0.0.1:5000"
"endpoint19_id","RegionSeven","keystone","identity",True,"admin","http://Seven-127.0.0.1:5002"
"endpoint20_id","RegionSeven","keystone","identity",True,"internal","https://Seven-127.0.0.1:5001"
"endpoint21_id","RegionSeven","keystone","identity",True,"public","https://Seven-127.0.0.1:5000"
')
          instances = described_class.instances
          expect(instances).to have_array_of_instances_hash([
            {:name=>"RegionOne/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint1_id,endpoint2_id,endpoint3_id",
              :region=>"RegionOne",
              :admin_url=>"http://One-127.0.0.1:5002",
              :internal_url=>"https://One-127.0.0.1:5001",
              :public_url=>"https://One-127.0.0.1:5000"},
            {:name=>"RegionTwo/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint4_id,endpoint5_id,endpoint6_id",
              :region=>"RegionTwo",
              :admin_url=>"http://Two-127.0.0.1:5002",
              :internal_url=>"https://Two-127.0.0.1:5001",
              :public_url=>"https://Two-127.0.0.1:5000"},
            {:name=>"RegionThree/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint7_id,endpoint8_id,endpoint9_id",
              :region=>"RegionThree",
              :admin_url=>"http://Three-127.0.0.1:5002",
              :internal_url=>"https://Three-127.0.0.1:5001",
              :public_url=>"https://Three-127.0.0.1:5000"},
            {:name=>"RegionFour/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint10_id,endpoint11_id,endpoint12_id",
              :region=>"RegionFour",
              :admin_url=>"http://Four-127.0.0.1:5002",
              :internal_url=>"https://Four-127.0.0.1:5001",
              :public_url=>"https://Four-127.0.0.1:5000"},
            {:name=>"RegionFive/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint13_id,endpoint14_id,endpoint15_id",
              :region=>"RegionFive",
              :admin_url=>"http://Five-127.0.0.1:5002",
              :internal_url=>"https://Five-127.0.0.1:5001",
              :public_url=>"https://Five-127.0.0.1:5000"},
            {:name=>"RegionSix/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint16_id,endpoint17_id,endpoint18_id",
              :region=>"RegionSix",
              :admin_url=>"http://Six-127.0.0.1:5002",
              :internal_url=>"https://Six-127.0.0.1:5001",
              :public_url=>"https://Six-127.0.0.1:5000"},
            {:name=>"RegionSeven/keystone::identity",
              :ensure=>:present,
              :id=>"endpoint19_id,endpoint20_id,endpoint21_id",
              :region=>"RegionSeven",
              :admin_url=>"http://Seven-127.0.0.1:5002",
              :internal_url=>"https://Seven-127.0.0.1:5001",
              :public_url=>"https://Seven-127.0.0.1:5000"}])
        end
      end
    end

    describe '#prefetch' do
      context 'working: fq or nfq and matching resource' do
        before(:each) do
          described_class.expects(:openstack)
            .with('endpoint', 'list', '--quiet', '--format', 'csv', [])
            .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL"
"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002"
"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001"
"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000"
')
        end
        context '#fq resource in title' do
          let(:resources) do
            [Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identity', :ensure => :present),
              Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)]
          end
          include_examples 'prefetch the resources'
        end
        context '#fq resource' do
          let(:resources) do
            [Puppet::Type.type(:keystone_endpoint).new(:title => 'keystone', :region => 'RegionOne', :type => 'identity', :ensure => :present),
              Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)]
          end
          include_examples 'prefetch the resources'
        end
        context '#nfq resource in title matching existing endpoint' do
          let(:resources) do
            [Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone', :ensure => :present),
              Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)]
          end
          include_examples 'prefetch the resources'
        end
        context '#nfq resource matching existing endpoint' do
          let(:resources) do
            [Puppet::Type.type(:keystone_endpoint).new(:title => 'keystone', :region => 'RegionOne', :ensure => :present),
              Puppet::Type.type(:keystone_endpoint).new(:title => 'RegionOne/keystone::identityv3', :ensure => :present)]
          end
          include_examples 'prefetch the resources'
        end
      end

      context 'not working' do
        context 'too many type' do
          before(:each) do
            described_class.expects(:openstack)
              .with('endpoint', 'list', '--quiet', '--format', 'csv', [])
              .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL"
"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002"
"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001"
"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000"
"endpoint4_id","RegionOne","keystone","identityv3",True,"admin","http://127.0.0.1:5002"
"endpoint5_id","RegionOne","keystone","identityv3",True,"internal","https://127.0.0.1:5001"
"endpoint6_id","RegionOne","keystone","identityv3",True,"public","https://127.0.0.1:5000"
')
          end
          it "should fail as it's not possible to get the right type here" do
            existing = Puppet::Type.type(:keystone_endpoint)
              .new(:title => 'RegionOne/keystone', :ensure => :present)
            resource = mock
            r = []
            r << existing

            catalog = Puppet::Resource::Catalog.new
            r.each { |res| catalog.add_resource(res) }
            m_value = mock
            m_first = mock
            resource.expects(:values).returns(m_value)
            m_value.expects(:first).returns(m_first)
            m_first.expects(:catalog).returns(catalog)
            m_first.expects(:class).returns(described_class.resource_type)
            expect { described_class.prefetch(resource) }
              .to raise_error(Puppet::Error,
                              /endpoint matching RegionOne\/keystone: identity identityv3/)
          end
        end
      end

      context 'not any type but existing service' do
        before(:each) do
          described_class.expects(:openstack)
            .with('endpoint', 'list', '--quiet', '--format', 'csv', [])
            .returns('"ID","Region","Service Name","Service Type","Enabled","Interface","URL"
"endpoint1_id","RegionOne","keystone","identity",True,"admin","http://127.0.0.1:5002"
"endpoint2_id","RegionOne","keystone","identity",True,"internal","https://127.0.0.1:5001"
"endpoint3_id","RegionOne","keystone","identity",True,"public","https://127.0.0.1:5000"
')
          described_class.expects(:openstack)
            .with('service', 'list', '--quiet', '--format', 'csv', [])
            .returns('"ID","Name","Type"
"service1_id","keystonev3","identity"
')
        end
        it 'should be successful' do
          existing = Puppet::Type.type(:keystone_endpoint)
            .new(:title => 'RegionOne/keystonev3', :ensure => :present)
          resource = mock
          r = []
          r << existing

          catalog = Puppet::Resource::Catalog.new
          r.each { |res| catalog.add_resource(res) }
          m_value = mock
          m_first = mock
          resource.expects(:values).returns(m_value)
          m_value.expects(:first).returns(m_first)
          m_first.expects(:catalog).returns(catalog)
          m_first.expects(:class).returns(described_class.resource_type)

          expect { described_class.prefetch(resource) }.not_to raise_error
          expect(existing.provider.ensure).to eq(:absent)
        end
      end
    end

    describe '#flush' do
      let(:endpoint_attrs) do
        {
          :title        => 'region/service_1',
          :ensure       => 'present',
          :public_url   => 'http://127.0.0.1:5000',
          :internal_url => 'http://127.0.0.1:5001',
          :admin_url    => 'http://127.0.0.1:4999',
          :type         => 'service_type1'
        }
      end
      context '#update a missing endpoint' do
        it 'creates an endpoint' do
          described_class.expects(:openstack)
            .with('endpoint', 'create', '--format', 'shell',
                  ['service_id_1', 'admin', 'http://127.0.0.1:4999',
                   '--region', 'region'])
            .returns(<<-eoo
enabled="True"
id="endpoint1_id"
interface="internal"
region="None"
region_id="None"
service_id="service_id_1"
service_name="service_1"
service_type="service_type1"
url="http://127.0.0.1:5001"
          eoo
                    )

          provider.expects(:property_flush)
            .times(5)
            .returns({:admin_url => 'http://127.0.0.1:4999'})
          provider.expects(:property_hash)
            .twice
            .returns({:id => ',endpoint2_id,endpoint3_id'})
          provider.expects(:service_id)
            .returns('service_id_1')
          provider.flush
          expect(provider.exists?).to be_truthy
          expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id')
        end
      end

      context 'adjust a url' do
        it 'update the url' do
          described_class.expects(:openstack)
            .with('endpoint', 'set',
                  ['endpoint1_id', '--url=http://127.0.0.1:4999'])
          provider.expects(:property_flush)
            .times(4)
            .returns({:admin_url => 'http://127.0.0.1:4999'})
          provider.expects(:property_hash)
            .twice
            .returns({:id => 'endpoint1_id,endpoint2_id,endpoint3_id'})
          provider.flush
          expect(provider.exists?).to be_truthy
          expect(provider.id).to eq('endpoint1_id,endpoint2_id,endpoint3_id')
        end
      end
    end
  end
end
