require 'spec_helper'
require 'puppet/provider/package/pkgng'

describe Puppet::Type.type(:package).provider(:pkgng) do
  let(:name) { 'bash' }
  let(:installed_name) { 'zsh' }
  let(:pkgng) { 'pkgng' }

  let(:resource) do
    # When bash is not present
    Puppet::Type.type(:package).new(:name => name, :provider => pkgng)
  end

  let(:installed_resource) do
    # When zsh is present
    Puppet::Type.type(:package).new(:name => installed_name, :provider => pkgng)
  end

  let(:latest_resource) do
    # When curl is installed but not the latest
    Puppet::Type.type(:package).new(:name => 'ftp/curl', :provider => pkgng, :ensure => latest)
  end

  let (:provider) { resource.provider }
  let (:installed_provider) { installed_resource.provider }

  def run_in_catalog(*resources)
    catalog = Puppet::Resource::Catalog.new
    catalog.host_config = false
    resources.each do |resource|
      catalog.add_resource(resource)
    end
    catalog.apply
  end

  before do
    allow(described_class).to receive(:command).with(:pkg).and_return('/usr/local/sbin/pkg')

    info = File.read(my_fixture('pkg.query'))
    allow(described_class).to receive(:get_query).and_return(info)

    version_list = File.read(my_fixture('pkg.version'))
    allow(described_class).to receive(:get_version_list).and_return(version_list)
  end

  context "#instances" do
    it "should return the empty set if no packages are listed" do
      allow(described_class).to receive(:get_query).and_return('')
      allow(described_class).to receive(:get_version_list).and_return('')
      expect(described_class.instances).to be_empty
    end

    it "should return all packages when invoked" do
      expect(described_class.instances.map(&:name).sort).to eq(
        %w{ca_root_nss curl nmap pkg gnupg zsh tac_plus}.sort)
    end

    it "should set latest to current version when no upgrade available" do
      nmap = described_class.instances.find {|i| i.properties[:origin] == 'security/nmap' }

      expect(nmap.properties[:version]).to eq(nmap.properties[:latest])
    end

    it "should return an empty array when pkg calls raise an exception" do
      allow(described_class).to receive(:get_query).and_raise(Puppet::ExecutionFailure, 'An error occurred.')
      expect(described_class.instances).to eq([])
    end

    describe "version" do
      it "should retrieve the correct version of the current package" do
        zsh = described_class.instances.find {|i| i.properties[:origin] == 'shells/zsh' }
        expect(zsh.properties[:version]).to eq('5.0.2_1')
      end
    end
  end

  context "#install" do
    it "should call pkg with the specified package version given an origin for package name" do
      resource = Puppet::Type.type(:package).new(
        :name     => 'ftp/curl',
        :provider => :pkgng,
        :ensure   => '7.33.1'
      )
      expect(resource.provider).to receive(:pkg) do |arg|
        expect(arg).to include('curl-7.33.1')
      end
      resource.provider.install
    end

    it "should call pkg with the specified package version" do
      resource = Puppet::Type.type(:package).new(
        :name     => 'curl',
        :provider => :pkgng,
        :ensure   => '7.33.1'
      )
      expect(resource.provider).to receive(:pkg) do |arg|
        expect(arg).to include('curl-7.33.1')
      end
      resource.provider.install
    end

    it "should call pkg with the specified package repo" do
      resource = Puppet::Type.type(:package).new(
        :name     => 'curl',
        :provider => :pkgng,
        :source   => 'urn:freebsd:repo:FreeBSD'
      )
      expect(resource.provider).to receive(:pkg) do |arg|
        expect(arg).to include('FreeBSD')
      end
      resource.provider.install
    end

    it "should call pkg with the specified install options string" do
      resource = Puppet::Type.type(:package).new(
        :name            => 'curl',
        :provider        => :pkgng,
        :install_options => ['--foo', '--bar']
      )
      expect(resource.provider).to receive(:pkg) do |arg|
        expect(arg).to include('--foo', '--bar')
      end
      resource.provider.install
    end

    it "should call pkg with the specified install options hash" do
      resource = Puppet::Type.type(:package).new(
        :name            => 'curl',
        :provider        => :pkgng,
        :install_options => ['--foo', { '--bar' => 'baz', '--baz' => 'foo' }]
      )
      expect(resource.provider).to receive(:pkg) do |arg|
        expect(arg).to include('--foo', '--bar=baz', '--baz=foo')
      end
      resource.provider.install
    end
  end

  context "#prefetch" do
    it "should fail gracefully when " do
      allow(described_class).to receive(:instances).and_return([])
      expect{ described_class.prefetch({}) }.to_not raise_error
    end
  end

  context "#query" do
    it "should return the installed version if present" do
      pkg_query_zsh = File.read(my_fixture('pkg.query.zsh'))
      allow(described_class).to receive(:get_resource_info).with('zsh').and_return(pkg_query_zsh)
      described_class.prefetch({installed_name => installed_resource})
      expect(installed_provider.query).to be >= {:version=>'5.0.2_1'}
    end

    it "should return nil if not present" do
      allow(described_class).to receive(:get_resource_info).with('bash').and_raise(Puppet::ExecutionFailure, 'An error occurred')

      expect(provider.query).to equal(nil)
    end
  end

  describe "latest" do
    it "should retrieve the correct version of the latest package" do
      described_class.prefetch( { installed_name => installed_resource })
      expect(installed_provider.latest).not_to be_nil
    end

    it "should set latest to newer package version when available" do
      instances = described_class.instances
      curl = instances.find {|i| i.properties[:origin] == 'ftp/curl' }
      expect(curl.properties[:latest]).to eq('7.33.0_2')
    end

    it "should call update to upgrade the version" do
      allow(described_class).to receive(:get_resource_info).with('ftp/curl').and_return('curl 7.61.1 ftp/curl')

      resource = Puppet::Type.type(:package).new(
        :name     => 'ftp/curl',
        :provider => pkgng,
        :ensure   => :latest
      )

      expect(resource.provider).to receive(:update)

      resource.property(:ensure).sync
    end
  end

  describe "get_latest_version" do
    it "should rereturn nil when the current package is the latest" do
      version_list = File.read(my_fixture('pkg.version'))
      allow(described_class).to receive(:get_version_list).and_return(version_list)
      nmap_latest_version = described_class.get_latest_version('security/nmap')

      expect(nmap_latest_version).to be_nil
    end

    it "should match the package name exactly" do
      version_list = File.read(my_fixture('pkg.version'))
      allow(described_class).to receive(:get_version_list).and_return(version_list)
      bash_comp_latest_version = described_class.get_latest_version('shells/bash-completion')

      expect(bash_comp_latest_version).to eq('2.1_3')
    end

    it "should return nil when the package is orphaned" do
      version_list = File.read(my_fixture('pkg.version'))
      allow(described_class).to receive(:get_version_list).and_return(version_list)
      orphan_latest_version = described_class.get_latest_version('sysutils/orphan')
      expect(orphan_latest_version).to be_nil
    end

    it "should return nil when the package is broken" do
      version_list = File.read(my_fixture('pkg.version'))
      allow(described_class).to receive(:get_version_list).and_return(version_list)
      broken_latest_version = described_class.get_latest_version('sysutils/broken')
      expect(broken_latest_version).to be_nil
    end
  end

  describe "confine" do
    context "on FreeBSD" do
      it "should be the default provider" do
        expect(Facter).to receive(:value).with('os.name').at_least(:once).and_return(:freebsd)
        expect(described_class).to be_default
      end
    end
  end
end
