require 'puppet'
require 'spec_helper'
require 'puppet/provider/openstack'
require 'puppet/provider/openstack/auth'
require 'tempfile'

class Puppet::Provider::Openstack::AuthTester < Puppet::Provider::Openstack
  extend Puppet::Provider::Openstack::Auth
end

klass = Puppet::Provider::Openstack::AuthTester

describe Puppet::Provider::Openstack::Auth do

  let(:type) do
    Puppet::Type.newtype(:test_resource) do
      newparam(:name, :namevar => true)
      newparam(:log_file)
    end
  end

  let(:resource_attrs) do
    {
      :name => 'stubresource'
    }
  end

  let(:provider) do
    klass.new(type.new(resource_attrs))
  end

  before(:each) do
    ENV['OS_USERNAME']     = nil
    ENV['OS_PASSWORD']     = nil
    ENV['OS_PROJECT_NAME'] = nil
    ENV['OS_AUTH_URL']     = nil
    ENV['OS_TOKEN']        = nil
    ENV['OS_ENDPOINT']     = nil
  end

  describe '#set_credentials' do
    it 'adds keys to the object' do
      credentials = Puppet::Provider::Openstack::CredentialsV3.new
      set = { 'OS_USERNAME'             => 'user',
              'OS_PASSWORD'             => 'secret',
              'OS_PROJECT_NAME'         => 'tenant',
              'OS_AUTH_URL'             => 'http://127.0.0.1:5000',
              'OS_TOKEN'                => 'token',
              'OS_ENDPOINT'             => 'http://127.0.0.1:5000',
              'OS_IDENTITY_API_VERSION' => '2.0',
              'OS_NOT_VALID'            => 'notvalid'
        }
      klass.set_credentials(credentials, set)
      expect(credentials.to_env).to eq(
        "OS_AUTH_URL"             => "http://127.0.0.1:5000",
        "OS_IDENTITY_API_VERSION" => '2.0',
        "OS_PASSWORD"             => "secret",
        "OS_PROJECT_NAME"         => "tenant",
        "OS_TOKEN"                => "token",
        "OS_ENDPOINT"             => "http://127.0.0.1:5000",
        "OS_USERNAME"             => "user")
    end
  end

  describe '#rc_filename' do
    it 'returns RCFILENAME' do
      expect(klass.rc_filename).to eq("#{ENV['HOME']}/openrc")
    end
  end

  describe '#get_os_from_env' do
    context 'with Openstack environment variables set' do
      it 'provides a hash' do
        ENV['OS_AUTH_URL']     = 'http://127.0.0.1:5000'
        ENV['OS_PASSWORD']     = 'abc123'
        ENV['OS_PROJECT_NAME'] = 'test'
        ENV['OS_USERNAME']     = 'test'
        response = klass.get_os_vars_from_env
        expect(response).to eq({
          "OS_AUTH_URL"     => "http://127.0.0.1:5000",
          "OS_PASSWORD"     => "abc123",
          "OS_PROJECT_NAME" => "test",
          "OS_USERNAME"     => "test"})
      end
    end
  end

  describe '#get_os_vars_from_rcfile' do
    context 'with a valid RC file' do
      it 'provides a hash' do
        mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
        filename = 'file'
        File.expects(:exists?).with('file').returns(true)
        File.expects(:open).with('file').returns(StringIO.new(mock))

        response = klass.get_os_vars_from_rcfile(filename)
        expect(response).to eq({
          "OS_AUTH_URL"     => "http://127.0.0.1:5000",
          "OS_PASSWORD"     => "abc123",
          "OS_PROJECT_NAME" => "test",
          "OS_USERNAME"     => "test"})
      end
    end

    context 'with a valid RC file with extra code in it' do
      it 'provides a hash' do
        mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'\n_openstack() {\n foo\n} "
        filename = 'file'
        File.expects(:exists?).with('file').returns(true)
        File.expects(:open).with('file').returns(StringIO.new(mock))

        response = klass.get_os_vars_from_rcfile(filename)
        expect(response).to eq({
          "OS_AUTH_URL"     => "http://127.0.0.1:5000",
          "OS_PASSWORD"     => "abc123",
          "OS_PROJECT_NAME" => "test",
          "OS_USERNAME"     => "test"})
      end
    end

    context 'with an empty file' do
      it 'provides an empty hash' do
        filename = 'file'
        File.expects(:exists?).with(filename).returns(true)
        File.expects(:open).with(filename).returns(StringIO.new(""))

        response = klass.get_os_vars_from_rcfile(filename)
        expect(response).to eq({})
      end
    end

    context 'with a nonexistent file' do
      it 'should get default rcfile when no environment or openrc file' do
        ENV.clear
        mock = "export OS_USERNAME='user'\nexport OS_PASSWORD='secret'\nexport OS_PROJECT_NAME='project'\nexport OS_AUTH_URL='http://127.0.0.1:5000'"
        filename = '/root/openrc'

        File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(false)
        File.expects(:exists?).with(filename).returns(true)
        File.expects(:open).with(filename).returns(StringIO.new(mock))

        expect(klass.get_os_vars_from_rcfile("#{ENV['HOME']}/openrc")).to eq({
          'OS_USERNAME'     => 'user',
          'OS_PASSWORD'     => 'secret',
          'OS_PROJECT_NAME' => 'project',
          'OS_AUTH_URL'     => 'http://127.0.0.1:5000'
        })
      end
    end
  end

  before(:each) do
    class Puppet::Provider::Openstack::AuthTester
      @credentials =  Puppet::Provider::Openstack::CredentialsV3.new
    end
  end

  describe '#request' do
    context 'with no valid credentials' do
      it 'fails to authenticate' do
        expect { klass.request('project', 'list', ['--long']) }.to raise_error(Puppet::Error::OpenstackAuthInputError, "Insufficient credentials to authenticate")
        expect(klass.instance_variable_get(:@credentials).to_env).to eq({'OS_IDENTITY_API_VERSION' => '3'})
      end
    end

    context 'with user credentials in env' do
      it 'is successful' do
        klass.expects(:get_os_vars_from_env)
             .returns({ 'OS_USERNAME'     => 'test',
                        'OS_PASSWORD'     => 'abc123',
                        'OS_PROJECT_NAME' => 'test',
                        'OS_AUTH_URL'     => 'http://127.0.0.1:5000',
                        'OS_NOT_VALID'    => 'notvalid' })
        klass.expects(:openstack)
             .with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
             .returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
        response = klass.request('project', 'list', ['--long'])
        expect(response.first[:description]).to eq("Test tenant")
        expect(klass.instance_variable_get(:@credentials).to_env).to eq({
          'OS_USERNAME'             => 'test',
          'OS_PASSWORD'             => 'abc123',
          'OS_PROJECT_NAME'         => 'test',
          'OS_AUTH_URL'             => 'http://127.0.0.1:5000',
          'OS_IDENTITY_API_VERSION' => '3'
        })
      end
    end

    context 'with service token credentials in env' do
      it 'is successful' do
        klass.expects(:get_os_vars_from_env)
             .returns({ 'OS_TOKEN'     => 'test',
                        'OS_ENDPOINT'  => 'http://127.0.0.1:5000',
                        'OS_NOT_VALID' => 'notvalid' })
        klass.expects(:openstack)
             .with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
             .returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
        response = klass.request('project', 'list', ['--long'])
        expect(response.first[:description]).to eq("Test tenant")
        expect(klass.instance_variable_get(:@credentials).to_env).to eq({
          'OS_IDENTITY_API_VERSION' => '3',
          'OS_TOKEN'                => 'test',
          'OS_ENDPOINT'             => 'http://127.0.0.1:5000',
        })
      end
    end

    context 'with a RC file containing user credentials' do
      it 'is successful' do
        # return incomplete creds from env
        klass.expects(:get_os_vars_from_env)
             .returns({ 'OS_USERNAME' => 'incompleteusername',
                        'OS_AUTH_URL' => 'incompleteauthurl' })
        mock = "export OS_USERNAME='test'\nexport OS_PASSWORD='abc123'\nexport OS_PROJECT_NAME='test'\nexport OS_AUTH_URL='http://127.0.0.1:5000'\nexport OS_NOT_VALID='notvalid'"
        File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
        File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
        klass.expects(:openstack)
             .with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
             .returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
        response = provider.class.request('project', 'list', ['--long'])
        expect(response.first[:description]).to eq("Test tenant")
        expect(klass.instance_variable_get(:@credentials).to_env).to eq({
          'OS_USERNAME'             => 'test',
          'OS_PASSWORD'             => 'abc123',
          'OS_PROJECT_NAME'         => 'test',
          'OS_AUTH_URL'             => 'http://127.0.0.1:5000',
          'OS_IDENTITY_API_VERSION' => '3'
        })
      end
    end

    context 'with a RC file containing service token credentials' do
      it 'is successful' do
        # return incomplete creds from env
        klass.expects(:get_os_vars_from_env)
             .returns({ 'OS_TOKEN' => 'incomplete' })
        mock = "export OS_TOKEN='test'\nexport OS_ENDPOINT='abc123'\nexport OS_NOT_VALID='notvalid'\n"
        File.expects(:exists?).with("#{ENV['HOME']}/openrc").returns(true)
        File.expects(:open).with("#{ENV['HOME']}/openrc").returns(StringIO.new(mock))
        klass.expects(:openstack)
             .with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
             .returns('"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
')
        response = klass.request('project', 'list', ['--long'])
        expect(response.first[:description]).to eq("Test tenant")
        expect(klass.instance_variable_get(:@credentials).to_env).to eq({
          'OS_IDENTITY_API_VERSION' => '3',
          'OS_TOKEN'                => 'test',
          'OS_ENDPOINT'             => 'abc123',
        })
      end
    end
  end
end
