require 'spec_helper'

module Beaker

  describe Unix::Exec do
    class UnixExecTest
      include Unix::Exec

      def initialize(hash, logger)
        @hash = hash
        @logger = logger
      end

      def [](k)
        @hash[k]
      end

      def to_s
        "me"
      end

    end

    let (:opts)     { @opts || {} }
    let (:logger)   { double( 'logger' ).as_null_object }
    let (:instance) { UnixExecTest.new(opts, logger) }

    context "rm" do

      it "deletes" do
        path = '/path/to/delete'
        expect( instance ).to receive(:execute).with("rm -rf #{path}").and_return(0)
        expect( instance.rm_rf(path) ).to be === 0
      end
    end

    context 'mv' do
      let(:origin)      { '/origin/path/of/content' }
      let(:destination) { '/destination/path/of/content' }

      it 'rm first' do
        expect( instance ).to receive(:execute).with("rm -rf #{destination}").and_return(0)
        expect( instance ).to receive(:execute).with("mv #{origin} #{destination}").and_return(0)
        expect( instance.mv(origin, destination) ).to be === 0

      end

      it 'does not rm' do
         expect( instance ).to receive(:execute).with("mv #{origin} #{destination}").and_return(0)
         expect( instance.mv(origin, destination, false) ).to be === 0
      end
    end

    describe '#environment_string' do
      let(:host) { {'pathseparator' => ':'} }

      it 'returns a blank string if theres no env' do
        expect( instance ).to receive( :is_powershell? ).never
        expect( instance.environment_string( {} ) ).to be == ''
      end

      it 'takes an env hash with var_name/value pairs' do
        expect( instance.environment_string( {:HOME => '/', :http_proxy => 'http://foo'} ) ).
          to be == 'env HOME="/" http_proxy="http://foo" HTTP_PROXY="http://foo"'
      end

      it 'takes an env hash with var_name/value[Array] pairs' do
        expect( instance.environment_string( {:LD_PATH => ['/', '/tmp']}) ).
          to be == "env LD_PATH=\"/:/tmp\""
      end
    end

    describe '#ssh_permit_user_environment' do
      context 'When called without error' do
        let (:directory) {'/directory'}
        let (:ssh_command) {"echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit"}
        let (:ssh_move) {"mv #{directory}/sshd_config.permit /etc/ssh/sshd_config"}

        platforms = PlatformHelpers::SYSTEMDPLATFORMS + PlatformHelpers::DEBIANPLATFORMS + PlatformHelpers::SYSTEMVPLATFORMS

        platforms.each do |platform|
          it "calls the correct commands for #{platform}" do
            opts['platform'] = platform
            expect(instance).to receive(:exec).twice
            expect(instance).to receive(:tmpdir).and_return(directory)
            expect(Beaker::Command).to receive(:new).with(ssh_move)
            expect(Beaker::Command).to receive(:new).with(ssh_command)
            expect(instance).to receive(:ssh_service_restart)
            expect{instance.ssh_permit_user_environment}.to_not raise_error
          end
        end
      end

      it 'raises an error on unsupported platforms' do
        opts['platform'] = 'notarealthing01-parts-arch'
        expect {
          instance.ssh_permit_user_environment
        }.to raise_error( ArgumentError, /#{opts['platform']}/ )
      end
    end

    describe '#ssh_service_restart' do
      PlatformHelpers::SYSTEMDPLATFORMS.each do |platform|
        it "calls the correct command for #{platform}" do
          opts['platform'] = platform
          expect(instance).to receive(:exec)
          expect(Beaker::Command).to receive(:new).with("systemctl restart sshd.service")
          expect{instance.ssh_service_restart}.to_not raise_error
        end
      end

      PlatformHelpers::DEBIANPLATFORMS.each do |platform|
        it "calls the correct command for #{platform}" do
          opts['platform'] = platform
          expect(instance).to receive(:exec)
          expect(Beaker::Command).to receive(:new).with("service ssh restart")
          expect{instance.ssh_service_restart}.to_not raise_error
        end
      end

      PlatformHelpers::SYSTEMVPLATFORMS.each do |platform|
        it "calls the correct command for #{platform}" do
          opts['platform'] = "#{platform}-arch"
          expect(instance).to receive(:exec)
          expect(Beaker::Command).to receive(:new).with("/sbin/service sshd restart")
          expect{instance.ssh_service_restart}.to_not raise_error
        end
      end

      it 'raises an error on unsupported platforms' do
        opts['platform'] = 'notarealthing02-parts-arch'
        expect {
          instance.ssh_service_restart
        }.to raise_error( ArgumentError, /#{opts['platform']}/ )
      end
    end

    describe '#prepend_commands' do
      it 'returns the pc parameter unchanged for non-cisco platforms' do
        allow( instance ).to receive( :[] ).with( :platform ).and_return( 'notcisco' )
        answer_prepend_commands = 'pc_param_unchanged_13579'
        answer_test = instance.prepend_commands( 'fake_cmd', answer_prepend_commands )
        expect( answer_test ).to be === answer_prepend_commands
      end
    end

    describe '#selinux_enabled?' do
      it 'calls selinuxenabled and selinux is enabled' do
        expect(Beaker::Command).to receive(:new).with("sudo selinuxenabled").and_return(0)
        expect(instance).to receive(:exec).with(0, :accept_all_exit_codes => true).and_return(generate_result("test", {:exit_code => 0}))
        expect(instance.selinux_enabled?).to be === true
      end

      it 'calls selinuxenabled and selinux is not enabled' do
        expect(Beaker::Command).to receive(:new).with("sudo selinuxenabled").and_return(1)
        expect(instance).to receive(:exec).with(1, :accept_all_exit_codes => true).and_return(generate_result("test", {:exit_code => 1}))
        expect(instance.selinux_enabled?).to be === false
      end
    end

    describe '#reboot' do
      # no-op response
      let (:response) { double( 'response' ) }
      let (:uptime_initial_response) { double( 'response' ) }
      let (:uptime_initial_stdout) { '19:52  up 14 mins, 2 users, load averages: 2.95 4.19 4.31' }

      let (:uptime_success_response) { double( 'response' ) }
      let (:uptime_success_stdout) { '19:52  up 0 mins, 2 users, load averages: 2.95 4.19 4.31' }
      let (:sleep_time) { 10 }

      before :each do
        # stubs enough to survive the first uptime call & output parsing
        #   note: just stubs input-chain between calls, parsing methods still run
        allow(Beaker::Command).to receive(:new).with("uptime").and_return(:uptime_command_stub)

        # mock initial uptime call
        allow(instance).to receive( :exec ).with(:uptime_command_stub).and_return(uptime_initial_response).once
        allow(uptime_initial_response).to receive(:stdout).and_return(uptime_initial_stdout)

        allow(uptime_success_response).to receive(:stdout).and_return(uptime_success_stdout)

        allow(instance).to receive(:sleep)

        allow(Beaker::Command).to receive(:new).with("/sbin/shutdown -r now").and_return(:shutdown_command_stub)
      end

      it 'raises a reboot failure when command fails' do
        expect(instance).not_to receive(:sleep)
        expect(instance).to receive(:exec).with(:shutdown_command_stub, anything).and_raise(Host::CommandFailure).once
        expect{ instance.reboot }.to raise_error(Beaker::Host::RebootFailure, /Command failed when attempting to reboot: .*/)
      end

      it 'raises a reboot failure when we receive an unexpected error' do
        expect(instance).not_to receive(:sleep)
        expect(instance).to receive(:exec).with(:shutdown_command_stub, anything).and_raise(Net::SSH::HostKeyError).once
        expect { instance.reboot }.to raise_error(Beaker::Host::RebootFailure, /Unexpected exception in reboot: .*/)
      end

      it 'raises RebootFailure if new uptime is never less than old uptime' do
        expect(instance).to receive(:sleep).with(sleep_time)
        # bypass shutdown command itself
        expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once
        # allow the second uptime and the hash arguments in exec, repeated 18 times by default in order to replicate the previous behavior of the ping based Host.down?
        expect(instance).to receive( :exec ).with(:uptime_command_stub, anything).and_return(uptime_initial_response).exactly(18).times

        expect { instance.reboot }.to raise_error(Beaker::Host::RebootFailure, /Uptime did not reset/)
      end

      it 'raises RebootFailure if new uptime is never less than old uptime when the number of retries is changed' do
        expect(instance).to receive(:sleep).with(sleep_time)
        # bypass shutdown command itself
        expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once
        # allow the second uptime and the hash arguments in exec, repeated 10 times by default
        expect(instance).to receive( :exec ).with(:uptime_command_stub, anything).and_return(uptime_initial_response).exactly(10).times

        expect { instance.reboot(wait_time=sleep_time, max_connection_tries=9, uptime_retries=10) }.to raise_error(Beaker::Host::RebootFailure, /Uptime did not reset/)
      end

      it 'passes if new uptime is less than old uptime' do
        expect(instance).to receive(:sleep).with(sleep_time)
        # bypass shutdown command itself
        expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once
        # allow the second uptime and the hash arguments in exec
        expect(instance).to receive( :exec ).with(:uptime_command_stub, anything).and_return(uptime_success_response).once

        expect(instance.reboot).to be(nil)
      end

      context 'with wait_time_parameter' do
        it 'passes if new uptime is less than old uptime' do
          expect(instance).to receive(:sleep).with(10)
          # bypass shutdown command itself
          expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once
          # allow the second uptime and the hash arguments in exec
          expect(instance).to receive( :exec ).with(:uptime_command_stub, anything).and_return(uptime_success_response).once

          expect(instance.reboot(10)).to be(nil)
        end
      end

      context 'with max_connection_tries parameter' do
        it 'passes if new uptime is less than old uptime' do
          expect(instance).to receive(:sleep).with(sleep_time)
          # bypass shutdown command itself
          expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once
          # allow the second uptime and the hash arguments in exec
          expect(instance).to receive( :exec ).with(:uptime_command_stub, hash_including(:max_connection_tries => 20)).and_return(uptime_success_response).once

          expect(instance.reboot(sleep_time, 20)).to be(nil)
        end
      end
    end

    describe '#enable_remote_rsyslog' do
      it 'always calls restart' do
        opts['platform'] = 'ubuntu-18-x86_64'
        allow(Beaker::Command).to receive(:new).with(anything)
        allow(instance).to receive(:exec)
        expect(Beaker::Command).to receive(:new).with("systemctl restart rsyslog")
        instance.enable_remote_rsyslog
      end

    end

    describe '#parse_uptime' do
      it 'parses variation of uptime string' do
        expect(instance.parse_uptime("19:52  up 14 mins, 2 users, load averages: 2.95 4.19 4.31")).to be == "14 mins"
      end
      it 'parses variation 2 of uptime string' do
        expect(instance.parse_uptime("8:03 up 52 days, 20:47, 3 users, load averages: 1.36 1.42 1.40")).to be == "52 days, 20:47"
      end
      it 'parses variation 3 of uptime string' do
        expect(instance.parse_uptime("22:19 up 54 days, 1 min, 4 users, load averages: 2.08 2.06 2.27")).to be == "54 days, 1 min"
      end
      it 'parses variation 4 of uptime string' do
        expect(instance.parse_uptime("18:44:45 up 5 min,  0 users,  load average: 0.14, 0.11, 0.05")).to be == "5 min"
      end
      it 'parses solaris\'s "just up" without time message' do
        opts['platform'] = 'solaris-11-x86_64'
        expect(instance.parse_uptime("10:05am  up  0 users,  load average: 0.66, 0.14, 0.05")).to be == "0 min"
      end
     end

    describe '#uptime_int' do
      it 'parses time segment variation into a minute value' do
        expect(instance.uptime_int("14 mins")).to be == 14
      end
      it 'parses time segment variation 2 into a minute value' do
        expect(instance.uptime_int("52 days, 20:47")).to be == 76127
      end
      it 'parses time segment variation 3 into a minute value' do
        expect(instance.uptime_int("54 days, 1 min")).to be == 77761
        end
      it 'parses time segment variation 4 into a minute value' do
        expect(instance.uptime_int("54 days")).to be == 77760
      end
      it 'raises if we pass garbage to it' do
        expect { instance.uptime_int("solaris roxx my soxx") }.to raise_error
      end
    end
  end
end
