#!/usr/bin/env rspec
require 'spec_helper'
require 'mcollective/pluginpackager/debpackage_packager'

module MCollective
  module PluginPackager
    describe DebpackagePackager, :unless => MCollective::Util.windows? do

      let(:plugin) do
        plugin = mock
        plugin.stubs(:mcname).returns('mcollective')
        plugin.stubs(:metadata).returns({ :name => 'rspec', :version => '1.0'})
        plugin.stubs(:target_path).returns('/rspec')
        plugin
      end

      let(:packager) do
        p = DebpackagePackager.new(plugin)
        p.instance_variable_set(:@plugin, plugin)
        p.instance_variable_set(:@tmpdir, 'rspec_tmp')
        p.instance_variable_set(:@build_dir, 'rspec_build')
        p.instance_variable_set(:@libdir, 'rspec_libdir')
        p
      end

      let(:data) do
        {:files => ['/rspec/agent/file.rb', '/rspec/agent/file.ddl', '/rspec/application/file.rb'],
         :dependencies => [{:name => 'dep1', :version => nil, :revision => nil},
                          {:name => 'dep2', :version => '1.1', :revision => nil},
                          {:name => 'dep3', :version => '1.1', :revision => 2}],
         :plugindependency => {:name => 'mcollective-rspec-common'}}
      end

      before :each do
        PluginPackager.stubs(:command_available?).returns(true)
      end

      describe '#initialize' do
        it 'should set the instance variables' do
          new_packager = DebpackagePackager.new(plugin)
          new_packager.instance_variable_get(:@plugin).should == plugin
          new_packager.instance_variable_get(:@verbose).should == false
          new_packager.instance_variable_get(:@libdir).should == '/usr/share/mcollective/plugins/mcollective/'
          new_packager.instance_variable_get(:@signature).should == nil
          new_packager.instance_variable_get(:@package_name).should == 'mcollective-rspec'
        end

        it 'should fail if debuild is not present on the system' do
          PluginPackager.stubs(:command_available?).with('debuild').returns(false)
          expect{
            DebpackagePackager.new(plugin)
          }.to raise_error("Cannot build package. 'debuild' is not present on the system.")
        end
      end

      describe '#create_packages' do
        it 'should run through the complete build process' do
          build_dir = 'mc-tmp/mcollective-rspec_1.0'
          tmpdir = 'mc-tmp'
          packager.stubs(:puts)
          Dir.expects(:mktmpdir).with('mcollective_packager').returns(tmpdir)
          Dir.expects(:mkdir).with(build_dir)
          packager.expects(:create_debian_dir)
          plugin.stubs(:packagedata).returns({:agent => data})
          packager.expects(:prepare_tmpdirs).with(data)
          packager.expects(:create_install_file).with(:agent, data)
          packager.expects(:create_pre_and_post_install).with(:agent)
          packager.expects(:create_debian_files)
          packager.expects(:create_tar)
          packager.expects(:run_build)
          packager.expects(:move_packages)
          packager.expects(:cleanup_tmpdirs)
          packager.create_packages
        end

        it 'should clean up tmpdirs if keep_artifacts is false' do
          packager.stubs(:puts)
          Dir.stubs(:mktmpdir).raises('error')
          packager.expects(:cleanup_tmpdirs)
          expect{
            packager.create_packages
          }.to raise_error('error')
        end

        it 'should keep the build artifacts if keep_artifacts is true' do
          packager.instance_variable_set(:@keep_artifacts, true)
          packager.stubs(:puts)
          Dir.stubs(:mktmpdir).raises('error')
          packager.expects(:cleanup_tmpdirs).never
          expect{
            packager.create_packages
          }.to raise_error('error')
        end
      end

      describe '#create_debian_files' do
        it 'should create all the debian build files' do
          ['control', 'Makefile', 'compat', 'rules', 'copyright', 'changelog'].each do |f|
            packager.expects(:create_file).with(f)
          end
          packager.send(:create_debian_files)
        end
      end

      describe '#run_build' do
        it 'should build the packages' do
          FileUtils.expects(:cd).with('rspec_build').yields
          PluginPackager.stubs(:do_quietly).with(false).yields
          PluginPackager.expects(:safe_system).with('debuild --no-lintian -i -us -uc')
          packager.send(:run_build)
        end

        it 'should build the package and sign it with the set signature' do
          packager.instance_variable_set(:@signature, '0x1234')
          FileUtils.expects(:cd).with('rspec_build').yields
          PluginPackager.stubs(:do_quietly).with(false).yields
          PluginPackager.expects(:safe_system).with('debuild --no-lintian -i -k0x1234')
          packager.send(:run_build)
        end


        it 'should sign with the exported gpg key' do
          packager.instance_variable_set(:@signature, true)
          FileUtils.expects(:cd).with('rspec_build').yields
          PluginPackager.stubs(:do_quietly).with(false).yields
          PluginPackager.expects(:safe_system).with('debuild --no-lintian -i')
          packager.send(:run_build)
        end
      end

      describe '#build_dependency_string' do
        it 'should create the correct dependency string' do
          PluginPackager.expects(:filter_dependencies).with('debian', data[:dependencies]).returns(data[:dependencies])
          result = packager.send(:build_dependency_string, data)
          result.should == 'dep1 | puppet-agent, dep2 (>=1.1) | puppet-agent, dep3 (>=1.1-2) | puppet-agent, mcollective-rspec-common (= ${binary:Version})'
        end
      end

      describe '#create_install_file' do
        it 'should create the .install file in the correct location' do
          file = mock
          file.expects(:puts).with('rspec_libdir/agent/file.rb rspec_libdir/agent')
          file.expects(:puts).with('rspec_libdir/agent/file.ddl rspec_libdir/agent')
          file.expects(:puts).with('rspec_libdir/application/file.rb rspec_libdir/application')
          File.expects(:open).with('rspec_build/debian/mcollective-rspec-agent.install', 'w').yields(file)
          packager.send(:create_install_file, :agent, data)
        end

        it 'should write a message and raise an error if we do not have permission' do
          File.expects(:open).with('rspec_build/debian/mcollective-rspec-agent.install', 'w').raises(Errno::EACCES)
          packager.expects(:puts).with("Could not create install file 'rspec_build/debian/mcollective-rspec-agent.install'. Permission denied")
          expect{
            packager.send(:create_install_file, :agent, data)
          }.to raise_error(Errno::EACCES)
        end

        it 'should write a message and raise an error if we cannot create the install file' do
          File.expects(:open).with('rspec_build/debian/mcollective-rspec-agent.install', 'w').raises('error')
          packager.expects(:puts).with("Could not create install file 'rspec_build/debian/mcollective-rspec-agent.install'.")
          expect{
            packager.send(:create_install_file, :agent, data)
          }.to raise_error('error')
        end
      end

      describe '#move_packages' do
        it 'should move the source package and debs to the cdw' do
          files = ['rspec.deb', 'rspec.diff.gz', 'rspec.orig.tar.gz', 'rspec.changes']
          Dir.stubs(:glob).returns(files)
          FileUtils.expects(:cp).with(files, '.')
          packager.send(:move_packages)
        end

        it 'should log an error and raise an error if the files cannot be moved' do
          files = ['rspec.deb', 'rspec.diff.gz', 'rspec.orig.tar.gz', 'rspec.changes']
          Dir.stubs(:glob).returns(files)
          FileUtils.expects(:cp).with(files, '.').raises('error')
          packager.expects(:puts).with('Could not copy packages to working directory.')
          expect{
            packager.send(:move_packages)
          }.to raise_error('error')
        end
      end

      describe '#create_pre_and_post_install' do
        it 'should create the pre-install file' do
          plugin.stubs(:preinstall).returns('rspec-preinstall')
          plugin.stubs(:postinstall).returns(nil)
          File.expects(:exists?).with('rspec-preinstall').returns(true)
          FileUtils.expects(:cp).with('rspec-preinstall', 'rspec_build/debian/mcollective-rspec-agent.preinst')
          packager.send(:create_pre_and_post_install, :agent)
        end

        it 'should create the post-install file' do
          plugin.stubs(:preinstall).returns(nil)
          plugin.stubs(:postinstall).returns('rspec-postinstall')
          File.expects(:exists?).with('rspec-postinstall').returns(true)
          FileUtils.expects(:cp).with('rspec-postinstall', 'rspec_build/debian/mcollective-rspec-agent.postinst')
          packager.send(:create_pre_and_post_install, :agent)
        end

        it 'should fail if a pre-install script is defined but the file does not exist' do
          plugin.stubs(:preinstall).returns('rspec-preinstall')
          File.expects(:exists?).with('rspec-preinstall').returns(false)
          packager.expects(:puts).with("pre-install script 'rspec-preinstall' not found.")
          expect{
            packager.send(:create_pre_and_post_install, :agent)
         }.to raise_error(Errno::ENOENT, 'No such file or directory - rspec-preinstall')
        end

        it 'should fail if a post-install script is defined but the file does not exist' do
          plugin.stubs(:preinstall).returns(nil)
          plugin.stubs(:postinstall).returns('rspec-postinstall')
          File.expects(:exists?).with('rspec-postinstall').returns(false)
          packager.expects(:puts).with("post-install script 'rspec-postinstall' not found.")
          expect{
            packager.send(:create_pre_and_post_install, :agent)
         }.to raise_error(Errno::ENOENT, 'No such file or directory - rspec-postinstall')
        end
      end

      describe '#create_tar' do
        it 'should create the tarball' do
          PluginPackager.stubs(:do_quietly?).yields
          Dir.stubs(:chdir).with('rspec_tmp').yields
          PluginPackager.expects(:safe_system).with('tar -Pcvzf rspec_tmp/mcollective-rspec_1.0.orig.tar.gz mcollective-rspec_1.0')
          packager.send(:create_tar)
        end

        it 'should log an error and raise an exception if it cannot create the tarball' do
          PluginPackager.stubs(:do_quietly?).yields
          Dir.stubs(:chdir).with('rspec_tmp').yields
          PluginPackager.expects(:safe_system).raises('error')
          packager.expects(:puts).with("Could not create tarball - mcollective-rspec_1.0.orig.tar.gz")
          expect{
            packager.send(:create_tar)
          }.to raise_error('error')
        end
      end

      describe '#create_file' do
        it 'should create the named file in the build dir' do
          file = mock
          erb_content = mock
          File.stubs(:read).returns('<%= "file content" %>')
          ERB.expects(:new).with('<%= "file content" %>', nil, '-').returns(erb_content)
          File.stubs(:open).with('rspec_build/debian/rspec', 'w').yields(file)
          erb_content.expects(:result).returns('file content')
          file.expects(:puts).with('file content')
          packager.send(:create_file, 'rspec')
        end

        it 'should log an error and raise if it cannot create the file' do
          File.stubs(:read).returns('<%= "file content" %>')
          ERB.stubs(:new).with('<%= "file content" %>', nil, '-').raises('error')
          packager.expects(:puts).with("Could not create file - 'rspec'")
          expect{
            packager.send(:create_file, 'rspec')
          }.to raise_error('error')
        end
      end

      describe '#prepare_tmpdirs' do
        it 'should create the target directories and copy the files' do
          FileUtils.expects(:mkdir_p).with('rspec_build/rspec_libdir/agent').twice
          FileUtils.expects(:mkdir_p).with('rspec_build/rspec_libdir/application')
          FileUtils.expects(:cp_r).with('/rspec/agent/file.rb', 'rspec_build/rspec_libdir/agent')
          FileUtils.expects(:cp_r).with('/rspec/agent/file.ddl', 'rspec_build/rspec_libdir/agent')
          FileUtils.expects(:cp_r).with('/rspec/application/file.rb', 'rspec_build/rspec_libdir/application')
          packager.send(:prepare_tmpdirs, data)
        end

        it 'should log an error and raise if permissions means the dir cannot be created' do
          FileUtils.stubs(:mkdir_p).with('rspec_build/rspec_libdir/agent').raises(Errno::EACCES)
          packager.expects(:puts).with("Could not create directory 'rspec_build/rspec_libdir/agent'. Permission denied")
          expect{
            packager.send(:prepare_tmpdirs, data)
          }.to raise_error(Errno::EACCES)
        end

        it 'should log an error and raise if the file does not exist' do
          FileUtils.stubs(:mkdir_p)
          FileUtils.expects(:cp_r).with('/rspec/agent/file.rb', 'rspec_build/rspec_libdir/agent').raises(Errno::ENOENT)
          packager.expects(:puts).with("Could not copy file '/rspec/agent/file.rb' to 'rspec_build/rspec_libdir/agent'. File does not exist")
          expect{
            packager.send(:prepare_tmpdirs, data)
          }.to raise_error(Errno::ENOENT)
        end

        it 'should log an error and raise for any other exception' do
          File.stubs(:expand_path).raises('error')
          packager.expects(:puts).with('Could not prepare build directory')
          expect{
            packager.send(:prepare_tmpdirs, data)
          }.to raise_error('error')
        end
      end

      describe '#create_debian_dir' do
        it 'should create the debian dir in the build dir' do
          FileUtils.expects(:mkdir_p).with('rspec_build/debian')
          packager.send(:create_debian_dir)
        end

        it 'should log an error and raise an exception if the dir cannot be created' do
          FileUtils.expects(:mkdir_p).with('rspec_build/debian').raises('error')
          packager.expects(:puts).with("Could not create directory 'rspec_build/debian'")
          expect{
            packager.send(:create_debian_dir)
          }.to raise_error('error')
        end
      end

      describe '#cleanup_tmpdirs' do
        it 'should remove the temporary build directory' do
          File.stubs(:directory?).with('rspec_tmp').returns(true)
          FileUtils.expects(:rm_r).with('rspec_tmp')
          packager.send(:cleanup_tmpdirs)
        end

        it 'should log an error and raise an exception if the directory could not be removed' do
          File.stubs(:directory?).with('rspec_tmp').returns(true)
          FileUtils.expects(:rm_r).with('rspec_tmp').raises('error')
          packager.expects(:puts).with("Could not remove temporary build directory - 'rspec_tmp'")
          expect{
            packager.send(:cleanup_tmpdirs)
          }.to raise_error('error')
        end
      end
    end
  end
end
