require 'spec_helper'

class ClassMixedWithDSLInstallUtils
  include Beaker::DSL::InstallUtils
  include Beaker::DSL::Wrappers
  include Beaker::DSL::Helpers
  include Beaker::DSL::Structure
  include Beaker::DSL::Roles
  include Beaker::DSL::Patterns

  attr_accessor :hosts

  def logger
    @logger ||= RSpec::Mocks::Double.new('logger').as_null_object
  end
end

describe ClassMixedWithDSLInstallUtils do
  let(:presets)       { Beaker::Options::Presets.new }
  let(:opts)          { presets.presets.merge(presets.env_vars) }
  let(:basic_hosts)   { make_hosts( { :pe_ver => '3.0',
                                      :platform => 'linux',
                                      :roles => [ 'agent' ],
                                      :type => 'pe'}, 4 ) }
  let(:hosts)         { basic_hosts[0][:roles] = ['master', 'database', 'dashboard']
                        basic_hosts[1][:platform] = 'windows'
                        basic_hosts[2][:platform] = 'osx-10.9-x86_64'
                        basic_hosts[3][:platform] = 'eos'
                        basic_hosts  }
  let(:hosts_sorted)  { [ hosts[1], hosts[0], hosts[2], hosts[3] ] }
  let(:winhost)       { make_host( 'winhost', { :platform => 'windows',
                                                :pe_ver => '3.0',
                                                :type => 'pe',
                                                :working_dir => '/tmp' } ) }
  let(:machost)       { make_host( 'machost', { :platform => 'osx-10.9-x86_64',
                                                :pe_ver => '3.0',
                                                :type => 'pe',
                                                :working_dir => '/tmp' } ) }
  let(:unixhost)      { make_host( 'unixhost', { :platform => 'linux',
                                                 :pe_ver => '3.0',
                                                :type => 'pe',
                                                 :working_dir => '/tmp',
                                                 :dist => 'puppet-enterprise-3.1.0-rc0-230-g36c9e5c-debian-7-i386' } ) }
  let(:eoshost)       { make_host( 'eoshost', { :platform => 'eos',
                                                :pe_ver => '3.0',
                                                :type => 'pe',
                                                :working_dir => '/tmp',
                                                :dist => 'puppet-enterprise-3.7.1-rc0-78-gffc958f-eos-4-i386' } ) }
  context '#configure_pe_defaults_on' do
    it 'uses aio paths for hosts of role aio' do
      hosts.each do |host|
        host[:pe_ver] = nil
        host[:version] = nil
        host[:roles] = host[:roles] | ['aio']
      end
      expect(subject).to receive(:add_pe_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_aio_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_puppet_paths_on).exactly(hosts.length).times

      subject.configure_pe_defaults_on( hosts )
    end

    it 'uses pe paths for hosts of type pe' do
      hosts.each do |host|
        host[:type] = 'pe'
      end
      expect(subject).to receive(:add_pe_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_aio_defaults_on).never
      expect(subject).to receive(:add_puppet_paths_on).exactly(hosts.length).times

      subject.configure_pe_defaults_on( hosts )
    end

    it 'uses aio paths for hosts of type aio' do
      hosts.each do |host|
        host[:pe_ver] = nil
        host[:version] = nil
        host[:type] = 'aio'
      end
      expect(subject).to receive(:add_aio_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_puppet_paths_on).exactly(hosts.length).times

      subject.configure_pe_defaults_on( hosts )
    end

    it 'uses no paths for hosts with no type' do
      hosts.each do |host|
        host[:type] = nil
      end
      expect(subject).to receive(:add_pe_defaults_on).never
      expect(subject).to receive(:add_aio_defaults_on).never
      expect(subject).to receive(:add_puppet_paths_on).never

      subject.configure_pe_defaults_on( hosts )
    end

    it 'uses aio paths for hosts of version >= 4.0' do
      hosts.each do |host|
        host[:pe_ver] = '4.0'
        end
      expect(subject).to receive(:add_pe_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_aio_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_puppet_paths_on).exactly(hosts.length).times

      subject.configure_pe_defaults_on( hosts )
    end

    it 'uses pe paths for hosts of version < 4.0' do
      hosts.each do |host|
        host[:pe_ver] = '3.8'
      end
      expect(subject).to receive(:add_pe_defaults_on).exactly(hosts.length).times
      expect(subject).to receive(:add_aio_defaults_on).never
      expect(subject).to receive(:add_puppet_paths_on).exactly(hosts.length).times

      subject.configure_pe_defaults_on( hosts )
    end

  end

  describe 'sorted_hosts' do
    it 'can reorder so that the master comes first' do
      allow( subject ).to receive( :hosts ).and_return( hosts_sorted )
      expect( subject.sorted_hosts ).to be === hosts
    end

    it 'leaves correctly ordered hosts alone' do
      allow( subject ).to receive( :hosts ).and_return( hosts )
      expect( subject.sorted_hosts ).to be === hosts
    end

    it 'does not allow nil entries' do
      allow( subject ).to receive( :options ).and_return( { :masterless => true } )
      masterless_host = [basic_hosts[0]]
      allow( subject ).to receive( :hosts ).and_return( masterless_host )
      expect( subject.sorted_hosts ).to be === masterless_host
    end
  end

  describe 'installer_cmd' do

    it 'generates a unix PE install command for a unix host' do
      the_host = unixhost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      expect( subject.installer_cmd( the_host, {} ) ).to be === "cd /tmp/puppet-enterprise-3.1.0-rc0-230-g36c9e5c-debian-7-i386 && ./puppet-enterprise-installer -a /tmp/answers"
    end

    it 'generates a unix PE frictionless install command for a unix host with role "frictionless"' do
      allow( subject ).to receive( :version_is_less ).and_return( false )
      allow( subject ).to receive( :master ).and_return( 'testmaster' )
      the_host = unixhost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      the_host['roles'] = ['frictionless']
      expect( subject.installer_cmd( the_host, {} ) ).to be ===  "cd /tmp && curl --tlsv1 -kO https://testmaster:8140/packages/3.0/install.bash && bash install.bash"
    end

    it 'generates a unix PE frictionless install command for a unix host with role "frictionless" and "frictionless_options"' do
      allow( subject ).to receive( :version_is_less ).and_return( false )
      allow( subject ).to receive( :master ).and_return( 'testmaster' )
      the_host = unixhost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      the_host['roles'] = ['frictionless']
      the_host['frictionless_options'] = { 'main' => { 'dns_alt_names' => 'puppet' } }
      expect( subject.installer_cmd( the_host, {} ) ).to be ===  "cd /tmp && curl --tlsv1 -kO https://testmaster:8140/packages/3.0/install.bash && bash install.bash main:dns_alt_names=puppet"
    end

    it 'generates a osx PE install command for a osx host' do
      the_host = machost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      expect( subject.installer_cmd( the_host, {} ) ).to be === "cd /tmp && hdiutil attach .dmg && installer -pkg /Volumes/puppet-enterprise-3.0/puppet-enterprise-installer-3.0.pkg -target /"
    end

    it 'calls the EOS PE install command for an EOS host' do
      the_host = eoshost.dup
      expect( the_host ).to receive( :install_from_file ).with( /swix$/ )
      subject.installer_cmd( the_host, {} )
    end

    it 'generates a unix PE install command in verbose for a unix host when pe_debug is enabled' do
      the_host = unixhost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      the_host[:pe_debug] = true
      expect( subject.installer_cmd( the_host, {} ) ).to be === "cd /tmp/puppet-enterprise-3.1.0-rc0-230-g36c9e5c-debian-7-i386 && ./puppet-enterprise-installer -D -a /tmp/answers"
    end

    it 'generates a osx PE install command in verbose for a osx host when pe_debug is enabled' do
      the_host = machost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      the_host[:pe_debug] = true
      expect( subject.installer_cmd( the_host, {} ) ).to be === "cd /tmp && hdiutil attach .dmg && installer -verboseR -pkg /Volumes/puppet-enterprise-3.0/puppet-enterprise-installer-3.0.pkg -target /"
    end

    it 'generates a unix PE frictionless install command in verbose for a unix host with role "frictionless" and pe_debug is enabled' do
      allow( subject ).to receive( :version_is_less ).and_return( false )
      allow( subject ).to receive( :master ).and_return( 'testmaster' )
      the_host = unixhost.dup
      the_host['pe_installer'] = 'puppet-enterprise-installer'
      the_host['roles'] = ['frictionless']
      the_host[:pe_debug] = true
      expect( subject.installer_cmd( the_host, {} ) ).to be === "cd /tmp && curl --tlsv1 -kO https://testmaster:8140/packages/3.0/install.bash && bash -x install.bash"
    end

  end


  describe 'fetch_pe' do

    it 'can push a local PE .tar.gz to a host and unpack it' do
      allow( File ).to receive( :directory? ).and_return( true ) #is local
      allow( File ).to receive( :exists? ).and_return( true ) #is a .tar.gz
      unixhost['pe_dir'] = '/local/file/path'
      allow( subject ).to receive( :scp_to ).and_return( true )

      path = unixhost['pe_dir']
      filename = "#{ unixhost['dist'] }"
      extension = '.tar.gz'
      expect( subject ).to receive( :scp_to ).with( unixhost, "#{ path }/#{ filename }#{ extension }", "#{ unixhost['working_dir'] }/#{ filename }#{ extension }" ).once
      expect( subject ).to receive( :on ).with( unixhost, /gunzip/ ).once
      expect( subject ).to receive( :on ).with( unixhost, /tar -xvf/ ).once
      subject.fetch_pe( [unixhost], {} )
    end

    it 'can download a PE .tar from a URL to a host and unpack it' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ) do |arg|
        if arg =~ /.tar.gz/ #there is no .tar.gz link, only a .tar
          false
        else
          true
        end
      end
      allow( subject ).to receive( :on ).and_return( true )

      path = unixhost['pe_dir']
      filename = "#{ unixhost['dist'] }"
      extension = '.tar'
      expect( subject ).to receive( :on ).with( unixhost, "cd #{ unixhost['working_dir'] }; curl #{ path }/#{ filename }#{ extension } | tar -xvf -" ).once
      subject.fetch_pe( [unixhost], {} )
    end

    it 'can download a PE .tar from a URL to #fetch_and_push_pe' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ) do |arg|
        if arg =~ /.tar.gz/ #there is no .tar.gz link, only a .tar
          false
        else
          true
        end
      end
      allow( subject ).to receive( :on ).and_return( true )

      path = unixhost['pe_dir']
      filename = "#{ unixhost['dist'] }"
      extension = '.tar'
      expect( subject ).to receive( :fetch_and_push_pe ).with( unixhost, anything, filename, extension ).once
      expect( subject ).to receive( :on ).with( unixhost, "cd #{ unixhost['working_dir'] }; cat #{ filename }#{ extension } | tar -xvf -" ).once
      subject.fetch_pe( [unixhost], {:fetch_local_then_push_to_host => true} )
    end

    it 'can download a PE .tar.gz from a URL to a host and unpack it' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ).and_return( true ) #is a tar.gz
      allow( subject ).to receive( :on ).and_return( true )

      path = unixhost['pe_dir']
      filename = "#{ unixhost['dist'] }"
      extension = '.tar.gz'
      expect( subject ).to receive( :on ).with( unixhost, "cd #{ unixhost['working_dir'] }; curl #{ path }/#{ filename }#{ extension } | gunzip | tar -xvf -" ).once
      subject.fetch_pe( [unixhost], {} )
    end

    it 'can download a PE .tar.gz from a URL to #fetch_and_push_pe' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ).and_return( true ) #is a tar.gz
      allow( subject ).to receive( :on ).and_return( true )

      path = unixhost['pe_dir']
      filename = "#{ unixhost['dist'] }"
      extension = '.tar.gz'
      expect( subject ).to receive( :fetch_and_push_pe ).with( unixhost, anything, filename, extension ).once
      expect( subject ).to receive( :on ).with( unixhost, "cd #{ unixhost['working_dir'] }; cat #{ filename }#{ extension } | gunzip | tar -xvf -" ).once
      subject.fetch_pe( [unixhost], {:fetch_local_then_push_to_host => true} )
    end

    it 'calls the host method to get an EOS .swix file from a URL' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ).and_return( true ) #skip file check

      expect( eoshost ).to receive( :get_remote_file ).with( /swix$/ ).once
      subject.fetch_pe( [eoshost], {} )
    end

    it 'can push a local PE package to a windows host' do
      allow( File ).to receive( :directory? ).and_return( true ) #is local
      allow( File ).to receive( :exists? ).and_return( true ) #is present
      winhost['dist'] = 'puppet-enterprise-3.0'
      allow( subject ).to receive( :scp_to ).and_return( true )

      path = winhost['pe_dir']
      filename = "puppet-enterprise-#{ winhost['pe_ver'] }"
      extension = '.msi'
      expect( subject ).to receive( :scp_to ).with( winhost, "#{ path }/#{ filename }#{ extension }", "#{ winhost['working_dir'] }/#{ filename }#{ extension }" ).once
      subject.fetch_pe( [winhost], {} )

    end

    it 'can download a PE dmg from a URL to a mac host' do
      allow( File ).to receive( :directory? ).and_return( false ) #is not local
      allow( subject ).to receive( :link_exists? ).and_return( true ) #is  not local
      allow( subject ).to receive( :on ).and_return( true )

      path = machost['pe_dir']
      filename = "#{ machost['dist'] }"
      extension = '.dmg'
      expect( subject ).to receive( :on ).with( machost, "cd #{ machost['working_dir'] }; curl -O #{ path }/#{ filename }#{ extension }" ).once
      subject.fetch_pe( [machost], {} )
    end

    it 'can push a PE dmg to a mac host' do
      allow( File ).to receive( :directory? ).and_return( true ) #is local
      allow( File ).to receive( :exists? ).and_return( true ) #is present
      allow( subject ).to receive( :scp_to ).and_return( true )

      path = machost['pe_dir']
      filename = "#{ machost['dist'] }"
      extension = '.dmg'
      expect( subject ).to receive( :scp_to ).with( machost, "#{ path }/#{ filename }#{ extension }", "#{ machost['working_dir'] }/#{ filename }#{ extension }" ).once
      subject.fetch_pe( [machost], {} )
    end

    it "does nothing for a frictionless agent for PE >= 3.2.0" do
      unixhost['roles'] << 'frictionless'
      unixhost['pe_ver'] = '3.2.0'

      expect( subject).to_not receive(:scp_to)
      expect( subject).to_not receive(:on)
      allow( subject ).to receive(:version_is_less).with('3.2.0', '3.2.0').and_return(false)
      subject.fetch_pe( [unixhost], {} )
    end
  end

  describe 'do_install' do
    it 'can perform a simple installation' do
      allow( subject ).to receive( :on ).and_return( Beaker::Result.new( {}, '' ) )
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :create_remote_file ).and_return( true )
      allow( subject ).to receive( :sign_certificate_for ).and_return( true )
      allow( subject ).to receive( :stop_agent_on ).and_return( true )
      allow( subject ).to receive( :sleep_until_puppetdb_started ).and_return( true )
      allow( subject ).to receive( :max_version ).with(anything, '3.8').and_return('3.0')
      allow( subject ).to receive( :version_is_less ).with('3.0', '4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.4').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.99').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.99', '3.0').and_return( false )
      allow( subject ).to receive( :wait_for_host_in_dashboard ).and_return( true )
      allow( subject ).to receive( :puppet_agent ) do |arg|
        "puppet agent #{arg}"
      end
      allow( subject ).to receive( :puppet ) do |arg|
        "puppet #{arg}"
      end

      allow( subject ).to receive( :hosts ).and_return( hosts )
      #create answers file per-host, except windows
      expect( subject ).to receive( :create_remote_file ).with( hosts[0], /answers/, /q/ ).once
      #run installer on all hosts
      expect( subject ).to receive( :on ).with( hosts[0], /puppet-enterprise-installer/ ).once
      expect( subject ).to receive( :install_msi_on ).with ( any_args ) do | host, msi_path, msi_opts, opts |
        expect( host ).to eq( hosts[1] )
      end.once
      expect( subject ).to receive( :on ).with( hosts[2], / hdiutil attach puppet-enterprise-3.0-osx-10.9-x86_64.dmg && installer -pkg \/Volumes\/puppet-enterprise-3.0\/puppet-enterprise-installer-3.0.pkg -target \// ).once
      expect( hosts[3] ).to receive( :install_from_file ).with( /swix$/ ).once
      #does extra mac/EOS specific commands
      expect( subject ).to receive( :on ).with( hosts[2], /puppet config set server/ ).once
      expect( subject ).to receive( :on ).with( hosts[3], /puppet config set server/ ).once
      expect( subject ).to receive( :on ).with( hosts[2], /puppet config set certname/ ).once
      expect( subject ).to receive( :on ).with( hosts[3], /puppet config set certname/ ).once
      expect( subject ).to receive( :on ).with( hosts[2], /puppet agent -t/, :acceptable_exit_codes => [1] ).once
      expect( subject ).to receive( :on ).with( hosts[3], /puppet agent -t/, :acceptable_exit_codes => [0, 1] ).once
      #sign certificate per-host
      expect( subject ).to receive( :sign_certificate_for ).with( hosts[0] ).once
      expect( subject ).to receive( :sign_certificate_for ).with( hosts[1] ).once
      expect( subject ).to receive( :sign_certificate_for ).with( hosts[2] ).once
      expect( subject ).to receive( :sign_certificate_for ).with( hosts[3] ).once
      #stop puppet agent on all hosts
      expect( subject ).to receive( :stop_agent_on ).with( hosts[0] ).once
      expect( subject ).to receive( :stop_agent_on ).with( hosts[1] ).once
      expect( subject ).to receive( :stop_agent_on ).with( hosts[2] ).once
      expect( subject ).to receive( :stop_agent_on ).with( hosts[3] ).once
      #wait for puppetdb to start
      expect( subject ).to receive( :sleep_until_puppetdb_started ).with( hosts[0] ).once
      #run each puppet agent once
      expect( subject ).to receive( :on ).with( hosts[0], /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      expect( subject ).to receive( :on ).with( hosts[1], /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      expect( subject ).to receive( :on ).with( hosts[2], /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      expect( subject ).to receive( :on ).with( hosts[3], /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      #run rake task on dashboard

      expect( subject ).to receive( :on ).with( hosts[0], /\/opt\/puppet\/bin\/rake -sf \/opt\/puppet\/share\/puppet-dashboard\/Rakefile .* RAILS_ENV=production/ ).once
      #wait for all hosts to appear in the dashboard
      #run puppet agent now that installation is complete
      expect( subject ).to receive( :on ).with( hosts, /puppet agent/, :acceptable_exit_codes => [0,2] ).once
      subject.do_install( hosts, opts )
    end

    it 'can perform a masterless installation' do
      hosts = make_hosts({
        :pe_ver => '3.0',
        :roles => ['agent']
      }, 1)
      opts[:masterless] = true

      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :on ).and_return( Beaker::Result.new( {}, '' ) )
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :create_remote_file ).and_return( true )
      allow( subject ).to receive( :stop_agent_on ).and_return( true )
      allow( subject ).to receive( :max_version ).with(['3.0'], '3.8').and_return('3.0')
      allow( subject ).to receive( :version_is_less ).with('3.99', '3.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with(anything, '3.2.0').exactly(hosts.length + 1).times.and_return( false )
      allow( subject ).to receive( :version_is_less ).with(anything, '4.0').exactly(hosts.length + 1).times.and_return( true )

      expect( subject ).to receive( :on ).with( hosts[0], /puppet-enterprise-installer/ ).once
      expect( subject ).to receive( :create_remote_file ).with( hosts[0], /answers/, /q/ ).once
      expect( subject ).to_not receive( :sign_certificate_for )
      expect( subject ).to receive( :stop_agent_on ).with( hosts[0] ).once
      expect( subject ).to_not receive( :sleep_until_puppetdb_started )
      expect( subject ).to_not receive( :wait_for_host_in_dashboard )
      expect( subject ).to_not receive( :on ).with( hosts[0], /puppet agent -t/, :acceptable_exit_codes => [0,2] )
      subject.do_install( hosts, opts)
    end

    it 'can perform a 4+ installation using AIO agents' do
      hosts = make_hosts({
        :pe_ver => '4.0',
        :roles => ['agent'],
      }, 3)
      hosts[0][:roles] = ['master', 'database', 'dashboard']
      hosts[1][:platform] = 'windows'
      hosts[2][:platform] = Beaker::Platform.new('el-6-x86_64')

      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :options ).and_return(Beaker::Options::Presets.new.presets)
      allow( subject ).to receive( :on ).and_return( Beaker::Result.new( {}, '' ) )
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :create_remote_file ).and_return( true )
      allow( subject ).to receive( :sign_certificate_for ).and_return( true )
      allow( subject ).to receive( :stop_agent_on ).and_return( true )
      allow( subject ).to receive( :sleep_until_puppetdb_started ).and_return( true )
      allow( subject ).to receive( :max_version ).with(anything, '3.8').and_return('4.0')
      allow( subject ).to receive( :version_is_less ).with('4.0', '4.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.4').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('3.99', '4.0').and_return( true )
      # pe_ver is only set on the hosts for this test, not the opt
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.99').and_return( true )
      allow( subject ).to receive( :wait_for_host_in_dashboard ).and_return( true )
      allow( subject ).to receive( :puppet_agent ) do |arg|
        "puppet agent #{arg}"
      end
      allow( subject ).to receive( :puppet ) do |arg|
        "puppet #{arg}"
      end

      allow( subject ).to receive( :hosts ).and_return( hosts )
      #create answers file per-host, except windows
      expect( subject ).to receive( :create_remote_file ).with( hosts[0], /answers/, /q/ ).once
      #run installer on all hosts
      expect( subject ).to receive( :on ).with( hosts[0], /puppet-enterprise-installer/ ).once
      expect( subject ).to receive( :install_puppet_agent_pe_promoted_repo_on ).with( hosts[1],
                                                                                     {:puppet_agent_version=>nil, :puppet_agent_sha=>nil, :pe_ver=>hosts[1][:pe_ver], :puppet_collection=>nil} ).once
      expect( subject ).to receive( :install_puppet_agent_pe_promoted_repo_on ).with( hosts[2],
                                                                                     {:puppet_agent_version=>nil, :puppet_agent_sha=>nil, :pe_ver=>hosts[2][:pe_ver], :puppet_collection=>nil} ).once
      hosts.each do |host|
        expect( subject ).to receive( :configure_type_defaults_on ).with( host ).once
        expect( subject ).to receive( :sign_certificate_for ).with( host ).once
        expect( subject ).to receive( :stop_agent_on ).with( host ).once
        expect( subject ).to receive( :on ).with( host, /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      end
      #wait for puppetdb to start
      expect( subject ).to receive( :sleep_until_puppetdb_started ).with( hosts[0] ).once#wait for all hosts to appear in the dashboard
      #run puppet agent now that installation is complete
      expect( subject ).to receive( :on ).with( hosts, /puppet agent/, :acceptable_exit_codes => [0,2] ).once
      subject.do_install( hosts, opts )
    end

    it 'can perform a 4/3 mixed installation with AIO and -non agents' do
      hosts = make_hosts({
                           :pe_ver => '4.0',
                           :roles => ['agent'],
                         }, 3)
      hosts[0][:roles] = ['master', 'database', 'dashboard']
      hosts[1][:platform] = 'windows'
      hosts[2][:platform] = Beaker::Platform.new('el-6-x86_64')
      hosts[2][:pe_ver]   = '3.8'

      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :options ).and_return(Beaker::Options::Presets.new.presets)
      allow( subject ).to receive( :on ).and_return( Beaker::Result.new( {}, '' ) )
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :create_remote_file ).and_return( true )
      allow( subject ).to receive( :sign_certificate_for ).and_return( true )
      allow( subject ).to receive( :stop_agent_on ).and_return( true )
      allow( subject ).to receive( :sleep_until_puppetdb_started ).and_return( true )
      allow( subject ).to receive( :max_version ).with(anything, '3.8').and_return('4.0')
      allow( subject ).to receive( :version_is_less ).with('4.0', '4.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.4').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('3.99', '4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.8', '4.0').and_return( true )
      # pe_ver is only set on the hosts for this test, not the opt
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.99').and_return( true )
      allow( subject ).to receive( :wait_for_host_in_dashboard ).and_return( true )
      allow( subject ).to receive( :puppet_agent ) do |arg|
        "puppet agent #{arg}"
      end
      allow( subject ).to receive( :puppet ) do |arg|
        "puppet #{arg}"
      end

      allow( subject ).to receive( :hosts ).and_return( hosts )
      #create answers file per-host, except windows
      expect( subject ).to receive( :create_remote_file ).with( hosts[0], /answers/, /q/ ).once
      #run installer on all hosts
      expect( subject ).to receive( :on ).with( hosts[0], /puppet-enterprise-installer/ ).once
      expect( subject ).to receive( :install_puppet_agent_pe_promoted_repo_on ).with( hosts[1],
                                                                                      {:puppet_agent_version=>nil, :puppet_agent_sha=>nil, :pe_ver=>hosts[1][:pe_ver], :puppet_collection=>nil} ).once
      expect( subject ).to receive( :on ).with( hosts[2], /puppet-enterprise-installer/ ).once
      hosts.each do |host|
        expect( subject ).to receive( :configure_type_defaults_on ).with( host ).once
        expect( subject ).to receive( :sign_certificate_for ).with( host ).once
        expect( subject ).to receive( :stop_agent_on ).with( host ).once
        expect( subject ).to receive( :on ).with( host, /puppet agent -t/, :acceptable_exit_codes => [0,2] ).once
      end
      #wait for puppetdb to start
      expect( subject ).to receive( :sleep_until_puppetdb_started ).with( hosts[0] ).once#wait for all hosts to appear in the dashboard
      #run puppet agent now that installation is complete
      expect( subject ).to receive( :on ).with( hosts, /puppet agent/, :acceptable_exit_codes => [0,2] ).once
      subject.do_install( hosts, opts )
    end

    it 'sets puppet-agent acceptable_exit_codes correctly for config helper on upgrade' do
      hosts = make_hosts({
                           :pe_ver => '4.0',
                           :roles => ['agent'],
                         }, 2)
      hosts[0][:roles] = ['master', 'database', 'dashboard']
      hosts[1][:platform] = Beaker::Platform.new('el-6-x86_64')

      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :options ).and_return(Beaker::Options::Presets.new.presets)
      allow( subject ).to receive( :on ).and_return( Beaker::Result.new( {}, '' ) )
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :create_remote_file ).and_return( true )
      allow( subject ).to receive( :sign_certificate_for ).and_return( true )
      allow( subject ).to receive( :stop_agent_on ).and_return( true )
      allow( subject ).to receive( :sleep_until_puppetdb_started ).and_return( true )
      allow( subject ).to receive( :max_version ).with(anything, '3.8').and_return('4.0')
      allow( subject ).to receive( :version_is_less ).with('4.0', '4.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.4').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.0').and_return( false )
      allow( subject ).to receive( :version_is_less ).with('3.99', '4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.8', '4.0').and_return( true )
      # pe_ver is only set on the hosts for this test, not the opt
      allow( subject ).to receive( :version_is_less ).with('4.0', '3.99').and_return( true )
      allow( subject ).to receive( :wait_for_host_in_dashboard ).and_return( true )
      allow( subject ).to receive( :puppet_agent ) do |arg|
        "puppet agent #{arg}"
      end
      allow( subject ).to receive( :puppet ) do |arg|
        "puppet #{arg}"
      end

      allow( subject ).to receive( :hosts ).and_return( hosts )
      #create answers file per-host, except windows
      allow( subject ).to receive( :create_remote_file ).with( hosts[0], /answers/, /q/ )
      #run installer on all hosts
      allow( subject ).to receive( :on ).with( hosts[0], /puppet-enterprise-installer/ )
      allow( subject ).to receive( :install_puppet_agent_pe_promoted_repo_on ).with( hosts[1],
                                                                                      {:puppet_agent_version=>nil, :puppet_agent_sha=>nil, :pe_ver=>hosts[1][:pe_ver], :puppet_collection=>nil} )
      # expect( subject ).to receive( :on ).with( hosts[2], /puppet-enterprise-installer/ ).once
      hosts.each do |host|
        allow( subject ).to receive( :add_pe_defaults_on ).with( host ) unless subject.aio_version?(host)
        allow( subject ).to receive( :sign_certificate_for ).with( host )
        allow( subject ).to receive( :stop_agent_on ).with( host )
        allow( subject ).to receive( :on ).with( host, /puppet agent -t/, :acceptable_exit_codes => [0,2] )
      end
      #wait for puppetdb to start
      allow( subject ).to receive( :sleep_until_puppetdb_started ).with( hosts[0] ) #wait for all hosts to appear in the dashboard
      #run puppet agent now that installation is complete
      allow( subject ).to receive( :on ).with( hosts, /puppet agent/, :acceptable_exit_codes => [0,2] )

      opts[:type] = :upgrade
      expect( subject ).to receive( :setup_defaults_and_config_helper_on ).with( hosts[1], hosts[0], [0, 1, 2] )
      subject.do_install( hosts, opts )
    end

  end

  describe 'do_higgs_install' do

    before :each do
      my_time = double( "time double" )
      allow( my_time ).to receive( :strftime ).and_return( "2014-07-01_15.27.53" )
      allow( Time ).to receive( :new ).and_return( my_time )

      hosts[0]['working_dir'] = "tmp/2014-07-01_15.27.53"
      hosts[0]['dist'] = 'dist'
      hosts[0]['pe_installer'] = 'pe-installer'
      allow( hosts[0] ).to receive( :tmpdir ).and_return( "/tmp/2014-07-01_15.27.53" )

      @fail_result = Beaker::Result.new( {}, '' )
      @fail_result.stdout = "No match here"
      @success_result = Beaker::Result.new( {}, '' )
      @success_result.stdout = "Please go to https://website in your browser to continue installation"
    end

    it 'can perform a simple installation' do
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :sleep ).and_return( true )

      allow( subject ).to receive( :hosts ).and_return( hosts )

      #run higgs installer command
      expect( subject ).to receive( :on ).with( hosts[0],
                                         "cd /tmp/2014-07-01_15.27.53/puppet-enterprise-3.0-linux ; nohup ./pe-installer <<<Y > higgs_2014-07-01_15.27.53.log 2>&1 &",
                                        opts ).once
      #check to see if the higgs installation has proceeded correctly, works on second check
      expect( subject ).to receive( :on ).with( hosts[0], /cat #{hosts[0]['higgs_file']}/, { :accept_all_exit_codes => true }).and_return( @fail_result, @success_result )
      subject.do_higgs_install( hosts[0], opts )
    end

    it 'fails out after checking installation log 10 times' do
      allow( subject ).to receive( :fetch_pe ).and_return( true )
      allow( subject ).to receive( :sleep ).and_return( true )

      allow( subject ).to receive( :hosts ).and_return( hosts )

      #run higgs installer command
      expect( subject ).to receive( :on ).with( hosts[0],
                                         "cd /tmp/2014-07-01_15.27.53/puppet-enterprise-3.0-linux ; nohup ./pe-installer <<<Y > higgs_2014-07-01_15.27.53.log 2>&1 &",
                                        opts ).once
      #check to see if the higgs installation has proceeded correctly, works on second check
      expect( subject ).to receive( :on ).with( hosts[0], /cat #{hosts[0]['higgs_file']}/, { :accept_all_exit_codes => true }).exactly(10).times.and_return( @fail_result )
      expect{ subject.do_higgs_install( hosts[0], opts ) }.to raise_error RuntimeError, "Failed to kick off PE (Higgs) web installation"
    end

  end

  describe 'install_pe' do

    it 'calls do_install with sorted hosts' do
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :hosts ).and_return( hosts_sorted )
      allow( subject ).to receive( :do_install ).and_return( true )
      expect( subject ).to receive( :do_install ).with( hosts, {} )
      subject.install_pe
    end

    it 'fills in missing pe_ver' do
      hosts.each do |h|
        h['pe_ver'] = nil
      end
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '2.8' )
      allow( subject ).to receive( :hosts ).and_return( hosts_sorted )
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :do_install ).and_return( true )
      expect( subject ).to receive( :do_install ).with( hosts, {} )
      subject.install_pe
      hosts.each do |h|
        expect( h['pe_ver'] ).to be === '2.8'
      end
    end

    it 'can act upon a single host' do
      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :sorted_hosts ).and_return( [hosts[0]] )
      expect( subject ).to receive( :do_install ).with( [hosts[0]], {} )
      subject.install_pe_on(hosts[0], {})
    end
  end

  describe 'install_higgs' do
    it 'fills in missing pe_ver' do
      hosts[0]['pe_ver'] = nil
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '2.8' )
      allow( subject ).to receive( :hosts ).and_return( [ hosts[1], hosts[0], hosts[2] ] )
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :do_higgs_install ).and_return( true )
      expect( subject ).to receive( :do_higgs_install ).with( hosts[0], {} )
      subject.install_higgs
      expect( hosts[0]['pe_ver'] ).to be === '2.8'
    end

  end

  describe 'upgrade_pe' do

    it 'calls puppet-enterprise-upgrader for pre 3.0 upgrades' do
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '2.8' )
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version_win ).and_return( '2.8' )
      the_hosts = [ hosts[0].dup, hosts[1].dup, hosts[2].dup ]
      allow( subject ).to receive( :hosts ).and_return( the_hosts )
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('2.8', '3.0').and_return( true )
      version = version_win = '2.8'
      path = "/path/to/upgradepkg"
      expect( subject ).to receive( :do_install ).with( the_hosts, {:type=>:upgrade, :set_console_password=>true} )
      subject.upgrade_pe( path )
      the_hosts.each do |h|
        expect( h['pe_installer'] ).to be === 'puppet-enterprise-upgrader'
      end
    end

    it 'uses standard upgrader for post 3.0 upgrades' do
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '3.1' )
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version_win ).and_return( '3.1' )
      the_hosts = [ hosts[0].dup, hosts[1].dup, hosts[2].dup ]
      allow( subject ).to receive( :hosts ).and_return( the_hosts )
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.1', '3.0').and_return( false )
      version = version_win = '3.1'
      path = "/path/to/upgradepkg"
      expect( subject ).to receive( :do_install ).with( the_hosts, {:type=>:upgrade, :set_console_password=>true} )
      subject.upgrade_pe( path )
      the_hosts.each do |h|
        expect( h['pe_installer'] ).to be nil
      end
    end

    it 'updates pe_ver post upgrade' do
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '2.8' )
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version_win ).and_return( '2.8' )
      the_hosts = [ hosts[0].dup, hosts[1].dup, hosts[2].dup ]
      allow( subject ).to receive( :hosts ).and_return( the_hosts )
      allow( subject ).to receive( :options ).and_return( {} )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('2.8', '3.0').and_return( true )
      version = version_win = '2.8'
      path = "/path/to/upgradepkg"
      expect( subject ).to receive( :do_install ).with( the_hosts, {:type=>:upgrade, :set_console_password=>true} )
      subject.upgrade_pe( path )
      the_hosts.each do |h|
        expect( h['pe_ver'] ).to be === '2.8'
      end
    end

    it 'can act upon a single host' do
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version ).and_return( '3.1' )
      allow( Beaker::Options::PEVersionScraper ).to receive( :load_pe_version_win ).and_return( '3.1' )
      allow( subject ).to receive( :hosts ).and_return( hosts )
      allow( subject ).to receive( :version_is_less ).with('3.0', '3.4.0').and_return( true )
      allow( subject ).to receive( :version_is_less ).with('3.1', '3.0').and_return( false )
      allow( subject ).to receive( :sorted_hosts ).and_return( [hosts[0]] )
      version = version_win = '3.1'
      path = "/path/to/upgradepkg"
      expect( subject ).to receive( :do_install ).with( [hosts[0]], {:type=>:upgrade, :set_console_password=>true} )
      subject.upgrade_pe_on(hosts[0], {}, path)
    end

  end

  describe 'fetch_and_push_pe' do

    it 'fetches the file' do
      allow( subject ).to receive( :scp_to )

      path = 'abcde/fg/hij'
      filename = 'pants'
      extension = '.txt'
      expect( subject ).to receive( :fetch_http_file ).with( path, "#{filename}#{extension}", 'tmp/pe' )
      subject.fetch_and_push_pe(unixhost, path, filename, extension)
    end

    it 'allows you to set the local copy dir' do
      allow( subject ).to receive( :scp_to )

      path = 'defg/hi/j'
      filename = 'pants'
      extension = '.txt'
      local_dir = '/root/domes'
      expect( subject ).to receive( :fetch_http_file ).with( path, "#{filename}#{extension}", local_dir )
      subject.fetch_and_push_pe(unixhost, path, filename, extension, local_dir)
    end

    it 'scp\'s to the host' do
      allow( subject ).to receive( :fetch_http_file )

      path = 'abcde/fg/hij'
      filename = 'pants'
      extension = '.txt'
      expect( subject ).to receive( :scp_to ).with( unixhost, "tmp/pe/#{filename}#{extension}", unixhost['working_dir'] )
      subject.fetch_and_push_pe(unixhost, path, filename, extension)
    end

  end

  describe 'create_agent_specified_arrays' do
    let(:master)        { make_host( 'master',       { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['master', 'agent']})}
    let(:db)            { make_host( 'db',           { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['database', 'agent']})}
    let(:console)       { make_host( 'console',      { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['dashboard', 'agent']})}
    let(:monolith)      { make_host( 'monolith',     { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => %w(master dashboard database)})}
    let(:frictionless)  { make_host( 'frictionless', { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['agent', 'frictionless']})}
    let(:agent1)        { make_host( 'agent1',       { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['agent']})}
    let(:agent2)        { make_host( 'agent2',       { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['agent']})}
    let(:default_agent) { make_host( 'default',      { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['default', 'agent']})}
    let(:masterless)    { make_host( 'masterless',   { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['agent', 'masterless']})}
    let(:compiler)      { make_host( 'compiler',     { :platform => 'linux',
                                                       :pe_ver   => '4.0',
                                                       :roles => ['agent', 'compile_master']})}

    it 'sorts hosts with common PE roles' do
      these_hosts = [master, db, console, agent1, frictionless]
      agent_only, non_agent = subject.create_agent_specified_arrays(these_hosts)
      expect(agent_only.length).to be 1
      expect(agent_only).to include(agent1)
      expect(non_agent.length).to be 4
      expect(non_agent).to include(master)
      expect(non_agent).to include(db)
      expect(non_agent).to include(console)
      expect(non_agent).to include(frictionless)
    end

    # Possibly needed for NetDev and Scale testing
    it 'defaults to classifying custom roles as "agent only"' do
      these_hosts = [monolith, compiler, agent1, agent2]
      agent_only, non_agent = subject.create_agent_specified_arrays(these_hosts)
      expect(agent_only.length).to be 3
      expect(agent_only).to include(agent1)
      expect(agent_only).to include(agent2)
      expect(agent_only).to include(compiler)
      expect(non_agent.length).to be 1
      expect(non_agent).to include(monolith)
    end

    # Most common form of module testing
    it 'allows a puppet-agent host to be the default test target' do
      these_hosts = [monolith, default_agent]
      agent_only, non_agent = subject.create_agent_specified_arrays(these_hosts)
      expect(agent_only.length).to be 1
      expect(agent_only).to include(default_agent)
      expect(non_agent.length).to be 1
      expect(non_agent).to include(monolith)
    end

    # Preferred module on commit scenario
    it 'handles masterless scenarios' do
      these_hosts = [masterless]
      agent_only, non_agent = subject.create_agent_specified_arrays(these_hosts)
      expect(agent_only.length).to be 1
      expect(non_agent).to be_empty
    end

    # IIRC, this is the basic PE integration smoke test
    it 'handles agent-only-less scenarios' do
      these_hosts = [monolith, frictionless]
      agent_only, non_agent = subject.create_agent_specified_arrays(these_hosts)
      expect(agent_only).to be_empty
      expect(non_agent.length).to be 2
    end
  end

end
