require 'spec_helper'

class ClassMixedWithDSLInstallUtils
  include Beaker::DSL::Helpers
  include Beaker::DSL::Patterns
  include Beaker::DSL::InstallUtils

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

describe ClassMixedWithDSLInstallUtils do
  let(:windows_temp) { 'C:\\Windows\\Temp' }
  let(:batch_path) { '/fake/batch/path' }
  let(:msi_path)            { 'c:\\foo\\puppet.msi' }
  let(:winhost)             do
    make_host('winhost',
              { platform: Beaker::Platform.new('windows-2008r2-64'),
                pe_ver: '3.0',
                working_dir: '/tmp',
                is_cygwin: true, })
  end
  let(:winhost_non_cygwin) do
    make_host('winhost_non_cygwin',
              { platform: 'windows',
                pe_ver: '3.0',
                working_dir: '/tmp',
                is_cygwin: 'false', })
  end
  let(:hosts) { [winhost, winhost_non_cygwin] }

  def expect_install_called
    result = Beaker::Result.new(nil, 'temp')
    result.exit_code = 0

    hosts.each do |host|
      expectation = expect(subject).to receive(:on).with(host, having_attributes(command: "\"#{batch_path}\""),
                                                         anything).and_return(result)
      if block_given?
        should_break = yield expectation
        break if should_break
      end
    end
  end

  def expect_status_called(start_type = 'DEMAND_START')
    result = Beaker::Result.new(nil, 'temp')
    result.exit_code = 0
    result.stdout = case start_type
                    when 'DISABLED'
                      '        START_TYPE         : 4   DISABLED'
                    when 'AUTOMATIC'
                      '        START_TYPE         : 2   AUTO_START'
                    else # 'DEMAND_START'
                      '        START_TYPE         : 3   DEMAND_START'
                    end

    hosts.each do |host|
      expect(subject).to receive(:on).with(host,
                                           having_attributes(command: 'sc qc puppet || sc qc pe-puppet')).and_yield(result)
    end
  end

  def expect_version_log_called(times = hosts.length)
    path = "'%PROGRAMFILES%\\Puppet Labs\\puppet\\misc\\versions.txt'"

    result = Beaker::Result.new(nil, 'temp')
    result.exit_code = 0

    hosts.each do |host|
      expect(subject).to receive(:on).with(host, "cmd /c type #{path}", anything).and_return(result)
    end
  end

  def expect_script_matches(hosts, contents)
    hosts.each do |host|
      expect(host)
        .to receive(:do_scp_to) do |local_path, remote_path|
          expect(File.read(local_path)).to match(contents)
        end
        .and_return(true)
    end
  end

  def expect_reg_query_called(times = hosts.length)
    hosts.each do |host|
      expect(host).to receive(:is_x86_64?).and_return(:true)
    end

    hosts.each do |host|
      expect(subject).to receive(:on)
        .with(host, having_attributes(command: /reg query "HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller/))
    end
  end

  def expect_puppet_path_called
    hosts.each do |host|
      next if host.is_cygwin?

      result = Beaker::Result.new(nil, 'temp')
      result.exit_code = 0

      expect(subject).to receive(:on)
        .with(host, having_attributes(command: 'puppet -h'), anything)
        .and_return(result)
    end
  end

  describe '#install_msi_on' do
    let(:log_file) { '/fake/log/file.log' }

    before :each do
      result = Beaker::Result.new(nil, 'temp')
      result.exit_code = 0

      hosts.each do |host|
        allow(subject).to receive(:on)
          .with(host, having_attributes(command: "\"#{batch_path}\""))
          .and_return(result)
      end

      allow(subject).to receive(:file_exists_on).and_return(true)
      allow(subject).to receive(:create_install_msi_batch_on).and_return([batch_path, log_file])
    end

    it 'will specify a PUPPET_AGENT_STARTUP_MODE of Manual by default' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called
      expect_reg_query_called
      expect_version_log_called
      expect(subject).to receive(:create_install_msi_batch_on).with(
        anything, anything,
        { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' }
      )
      subject.install_msi_on(hosts, msi_path, {})
    end

    it 'allows configuration of PUPPET_AGENT_STARTUP_MODE to Automatic' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called('AUTOMATIC')
      expect_reg_query_called
      expect_version_log_called
      value = 'Automatic'
      expect(subject).to receive(:create_install_msi_batch_on).with(
        anything, anything,
        { 'PUPPET_AGENT_STARTUP_MODE' => value }
      )
      subject.install_msi_on(hosts, msi_path, { 'PUPPET_AGENT_STARTUP_MODE' => value })
    end

    it 'allows configuration of PUPPET_AGENT_STARTUP_MODE to Disabled' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called('DISABLED')
      expect_reg_query_called
      expect_version_log_called
      value = 'Disabled'
      expect(subject).to receive(:create_install_msi_batch_on).with(
        anything, anything,
        { 'PUPPET_AGENT_STARTUP_MODE' => value }
      )
      subject.install_msi_on(hosts, msi_path, { 'PUPPET_AGENT_STARTUP_MODE' => value })
    end

    it 'will not generate a command to emit a log file without the :debug option set' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called
      expect_reg_query_called
      expect_version_log_called

      expect(subject).to receive(:file_contents_on).with(anything, log_file).never

      subject.install_msi_on(hosts, msi_path)
    end

    it 'will generate a command to emit a log file when the install script fails' do
      # NOTE: a single failure aborts executing against remaining hosts
      expect_install_called do |e|
        e.and_raise
        true # break
      end

      expect(subject).to receive(:file_contents_on).with(anything, log_file)
      expect do
        subject.install_msi_on(hosts, msi_path)
      end.to raise_error(RuntimeError)
    end

    it 'will generate a command to emit a log file with the :debug option set' do
      expect_install_called
      expect_reg_query_called
      expect_puppet_path_called
      expect_status_called
      expect_version_log_called

      expect(subject).to receive(:file_contents_on).with(anything, log_file).exactly(hosts.length).times

      subject.install_msi_on(hosts, msi_path, {}, { debug: true })
    end

    it 'will pass msi_path to #create_install_msi_batch_on as-is' do
      expect_install_called
      expect_reg_query_called
      expect_puppet_path_called
      expect_status_called
      expect_version_log_called
      test_path = 'test/path'
      expect(subject).to receive(:create_install_msi_batch_on).with(
        anything, test_path, anything
      )
      subject.install_msi_on(hosts, test_path)
    end

    it 'will search in Wow6432Node for the remembered startup setting on 64-bit hosts' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called
      expect_version_log_called

      hosts.each do |host|
        expect(host).to receive(:is_x86_64?).and_return(true)

        expect(subject).to receive(:on)
          .with(host, having_attributes(command: 'reg query "HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller" /v "RememberedPuppetAgentStartupMode" | findstr Manual'))
      end

      subject.install_msi_on(hosts, msi_path, { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' })
    end

    it 'will omit Wow6432Node in the registry search for remembered startup setting on 32-bit hosts' do
      expect_install_called
      expect_puppet_path_called
      expect_status_called
      expect_version_log_called

      hosts.each do |host|
        expect(host).to receive(:is_x86_64?).and_return(false)

        expect(subject).to receive(:on)
          .with(host, having_attributes(command: 'reg query "HKLM\\SOFTWARE\\Puppet Labs\\PuppetInstaller" /v "RememberedPuppetAgentStartupMode" | findstr Manual'))
      end

      subject.install_msi_on(hosts, msi_path, { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' })
    end
  end

  describe '#create_install_msi_batch_on' do
    let(:tmp) { '/tmp/create_install_msi_batch_on' }
    let(:tmp_slashes) { tmp.gsub('/', '\\') }

    before :each do
      FakeFS::FileSystem.add(File.expand_path(tmp))
      hosts.each do |host|
        allow(host).to receive(:system_temp_path).and_return(tmp)
      end
    end

    it 'passes msi_path & msi_opts down to #msi_install_script' do
      allow(winhost).to receive(:do_scp_to)
      test_path = '/path/to/test/with/13540'
      test_opts = { 'key1' => 'val1', 'key2' => 'val2' }
      expect(subject).to receive(:msi_install_script).with(
        test_path, test_opts, anything
      )
      subject.create_install_msi_batch_on(winhost, test_path, test_opts)
    end

    it 'SCPs to & returns the same batch file path, corrected for slashes' do
      test_time = Time.now
      allow(Time).to receive(:new).and_return(test_time)
      timestamp = test_time.strftime('%Y-%m-%d_%H.%M.%S')

      correct_path = "#{tmp_slashes}\\install-puppet-msi-#{timestamp}.bat"
      expect(winhost).to receive(:do_scp_to).with(anything, correct_path, {})
      test_path, = subject.create_install_msi_batch_on(winhost, msi_path, {})
      expect(test_path).to be === correct_path
    end

    it 'returns & sends log_path to #msi_install_scripts, corrected for slashes' do
      allow(winhost).to receive(:do_scp_to)
      test_time = Time.now
      allow(Time).to receive(:new).and_return(test_time)
      timestamp = test_time.strftime('%Y-%m-%d_%H.%M.%S')

      correct_path = "#{tmp_slashes}\\install-puppet-#{timestamp}.log"
      expect(subject).to receive(:msi_install_script).with(
        anything, anything, correct_path
      )
      _, log_path = subject.create_install_msi_batch_on(winhost, msi_path, {})
      expect(log_path).to be === correct_path
    end
  end

  describe '#msi_install_script' do
    let(:log_path) { '/log/msi_install_script' }

    context 'msi_params parameter' do
      it 'can take an empty hash' do
        expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ .exit}m
        expect(subject.msi_install_script(msi_path, {}, log_path)).to match(expected_cmd)
      end

      it 'uses a key-value pair correctly' do
        params = { 'tk1' => 'tv1' }
        expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ tk1=tv1}
        expect(subject.msi_install_script(msi_path, params, log_path)).to match(expected_cmd)
      end

      it 'uses multiple key-value pairs correctly' do
        params = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
        expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ tk1=tv1\ tk2=tv2}
        expect(subject.msi_install_script(msi_path, params, log_path)).to match(expected_cmd)
      end
    end

    context 'msi_path parameter' do
      it 'will generate an appropriate command with a MSI file path using non-Windows slashes' do
        msi_path = 'c:/foo/puppet.msi'
        expected_cmd = %r{^start /w msiexec\.exe /i "c:\\foo\\puppet.msi" /qn /L\*V #{log_path}}
        expect(subject.msi_install_script(msi_path, {}, log_path)).to match(expected_cmd)
      end

      it 'will generate an appropriate command with a MSI http(s) url' do
        msi_url = 'https://downloads.puppetlabs.com/puppet.msi'
        expected_cmd = %r{^start /w msiexec\.exe /i "https://downloads\.puppetlabs\.com/puppet\.msi" /qn /L\*V #{log_path}}
        expect(subject.msi_install_script(msi_url, {}, log_path)).to match(expected_cmd)
      end

      it 'will generate an appropriate command with a MSI file url' do
        msi_url = 'file://c:\\foo\\puppet.msi'
        expected_cmd = %r{^start /w msiexec\.exe /i "file://c:\\foo\\puppet\.msi" /qn /L\*V #{log_path}}
        expect(subject.msi_install_script(msi_url, {}, log_path)).to match(expected_cmd)
      end
    end
  end
end