#! /usr/bin/env ruby
require 'spec_helper'
require 'stringio'

provider = Puppet::Type.type(:package).provider(:dpkg)

describe provider do
  before do
    @resource = stub 'resource', :[] => "asdf"
    @provider = provider.new(@resource)
    @provider.expects(:execute).never # forbid "manual" executions

    @fakeresult = "install ok installed asdf 1.0\n"
  end

  it "should have documentation" do
    provider.doc.should be_instance_of(String)
  end

  describe "when listing all instances" do
    before do
      provider.stubs(:command).with(:dpkgquery).returns "myquery"
    end

    it "should use dpkg-query" do
      provider.expects(:command).with(:dpkgquery).returns "myquery"
      Puppet::Util::Execution.expects(:execpipe).with("myquery -W --showformat '${Status} ${Package} ${Version}\\n'").yields StringIO.new(@fakeresult)

      provider.instances
    end

    it "should create and return an instance with each parsed line from dpkg-query" do
      pipe = mock 'pipe'
      pipe.expects(:each).never
      pipe.expects(:each_line).yields @fakeresult
      Puppet::Util::Execution.expects(:execpipe).yields pipe

      asdf = mock 'pkg1'
      provider.expects(:new).with(:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg).returns asdf

      provider.instances.should == [asdf]
    end

    it "should warn on and ignore any lines it does not understand" do
      pipe = mock 'pipe'
      pipe.expects(:each).never
      pipe.expects(:each_line).yields "foobar"
      Puppet::Util::Execution.expects(:execpipe).yields pipe

      Puppet.expects(:warning)
      provider.expects(:new).never

      provider.instances.should == []
    end
  end

  describe "when querying the current state" do
    it "should use dpkg-query" do
      @provider.expects(:dpkgquery).with("-W", "--showformat",'${Status} ${Package} ${Version}\\n', "asdf").returns @fakeresult

      @provider.query
    end

    it "should consider the package purged if dpkg-query fails" do
      @provider.expects(:dpkgquery).raises Puppet::ExecutionFailure.new("eh")

      @provider.query[:ensure].should == :purged
    end

    it "should return a hash of the found status with the desired state, error state, status, name, and 'ensure'" do
      @provider.expects(:dpkgquery).returns @fakeresult

      @provider.query.should == {:ensure => "1.0", :error => "ok", :desired => "install", :name => "asdf", :status => "installed", :provider => :dpkg}
    end

    it "should consider the package absent if the dpkg-query result cannot be interpreted" do
      @provider.expects(:dpkgquery).returns "somebaddata"

      @provider.query[:ensure].should == :absent
    end

    it "should fail if an error is discovered" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("ok", "error")

      lambda { @provider.query }.should raise_error(Puppet::Error)
    end

    it "should consider the package purged if it is marked 'not-installed'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "not-installed")

      @provider.query[:ensure].should == :purged
    end

    it "should consider the package absent if it is marked 'config-files'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "config-files")
      @provider.query[:ensure].should == :absent
    end

    it "should consider the package absent if it is marked 'half-installed'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-installed")
      @provider.query[:ensure].should == :absent
    end

    it "should consider the package absent if it is marked 'unpacked'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "unpacked")
      @provider.query[:ensure].should == :absent
    end

    it "should consider the package absent if it is marked 'half-configured'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("installed", "half-configured")
      @provider.query[:ensure].should == :absent
    end

    it "should consider the package held if its state is 'hold'" do
      @provider.expects(:dpkgquery).returns @fakeresult.sub("install", "hold")
      @provider.query[:ensure].should == :held
    end
  end

  it "should be able to install" do
    @provider.should respond_to(:install)
  end

  describe "when installing" do
    before do
      @resource.stubs(:[]).with(:source).returns "mypkg"
    end

    it "should fail to install if no source is specified in the resource" do
      @resource.expects(:[]).with(:source).returns nil

      lambda { @provider.install }.should raise_error(ArgumentError)
    end

    it "should use 'dpkg -i' to install the package" do
      @resource.expects(:[]).with(:source).returns "mypackagefile"
      @provider.expects(:unhold)
      @provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile"  and command[-2] == "-i" }

      @provider.install
    end

    it "should keep old config files if told to do so" do
      @resource.expects(:[]).with(:configfiles).returns :keep
      @provider.expects(:unhold)
      @provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" }

      @provider.install
    end

    it "should replace old config files if told to do so" do
      @resource.expects(:[]).with(:configfiles).returns :replace
      @provider.expects(:unhold)
      @provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" }

      @provider.install
    end

    it "should ensure any hold is removed" do
      @provider.expects(:unhold).once
      @provider.expects(:dpkg)
      @provider.install
    end
  end

  describe "when holding or unholding" do
    before do
      @tempfile = stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file"
      @tempfile.stubs(:write)
      Tempfile.stubs(:new).returns @tempfile
    end

    it "should install first if holding" do
      @provider.stubs(:execute)
      @provider.expects(:install).once
      @provider.hold
    end

    it "should execute dpkg --set-selections when holding" do
      @provider.stubs(:install)
      @provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => @tempfile.path}).once
      @provider.hold
    end

    it "should execute dpkg --set-selections when unholding" do
      @provider.stubs(:install)
      @provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => @tempfile.path}).once
      @provider.hold
    end
  end

  it "should use :install to update" do
    @provider.expects(:install)
    @provider.update
  end

  describe "when determining latest available version" do
    it "should return the version found by dpkg-deb" do
      @resource.expects(:[]).with(:source).returns "myfile"
      @provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "asdf\t1.0"
      @provider.latest.should == "1.0"
    end

    it "should warn if the package file contains a different package" do
      @provider.expects(:dpkg_deb).returns("foo\tversion")
      @provider.expects(:warning)
      @provider.latest
    end

    it "should cope with names containing ++" do
      @resource = stub 'resource', :[] => "asdf++"
      @provider = provider.new(@resource)
      @provider.expects(:dpkg_deb).returns "asdf++\t1.0"
      @provider.latest.should == "1.0"
    end
  end

  it "should use 'dpkg -r' to uninstall" do
    @provider.expects(:dpkg).with("-r", "asdf")
    @provider.uninstall
  end

  it "should use 'dpkg --purge' to purge" do
    @provider.expects(:dpkg).with("--purge", "asdf")
    @provider.purge
  end
end
