# frozen_string_literal: true

describe Facter::Core::Execution::Windows do
  let(:executor) { Facter::Core::Execution::Windows.new }

  before do
    allow(LegacyFacter).to receive(:value).and_return('Windows')
    allow(LegacyFacter::Util::Config).to receive(:windows?).and_return(true)

    stub_const('::File::PATH_SEPARATOR', ';')
    stub_const('File::ALT_SEPARATOR', '\\')
  end

  describe '#search_paths' do
    it 'uses the PATH environment variable to determine locations' do
      allow(ENV).to receive(:[]).with('PATH').and_return 'C:\Windows;C:\Windows\System32'
      expect(executor.search_paths).to eq %w[C:\Windows C:\Windows\System32]
    end
  end

  describe '#execute' do
    context 'when expand is false' do
      subject(:executor) { Facter::Core::Execution::Windows.new }

      it 'raises exception' do
        expect { executor.execute('c:\foo.exe', expand: false) }
          .to raise_error(ArgumentError,
                          'Unsupported argument on Windows expand with value false')
      end
    end
  end

  describe '#which' do
    before do
      allow(executor)
        .to receive(:search_paths)
        .and_return ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem']
      allow(ENV).to receive(:[]).with('PATHEXT').and_return nil
    end

    context 'when trying to use builtin windows commands' do
      it 'allows echo' do
        expect(executor.which('echo')).to eq 'echo'
      end

      it 'disallows other builtin windows commands' do
        expect(executor.which('dir')).to be_nil
      end
    end

    context 'when it is provided with an absolute path' do
      it 'returns the path to binary if executable' do
        allow(File).to receive(:executable?).with('C:\Tools\foo.exe').and_return true

        expect(executor.which('C:\Tools\foo.exe')).to eq 'C:\Tools\foo.exe'
      end

      it 'returns the binary if executable' do
        allow(File).to receive(:executable?).with('\\\\remote\dir\foo.exe').and_return true

        expect(executor.which('\\\\remote\dir\foo.exe')).to eq '\\\\remote\dir\foo.exe'
      end

      it 'returns nil if the binary path is not executable' do
        allow(File).to receive(:executable?).with('C:\Tools\foo.exe').and_return false

        expect(executor.which('C:\Tools\foo.exe')).to be_nil
      end

      it 'returns nil if the binary is not executable' do
        allow(File).to receive(:executable?).with('\\\\remote\dir\foo.exe').and_return false

        expect(executor.which('\\\\remote\dir\foo.exe')).to be_nil
      end
    end

    context 'when it is not provided with an absolute path' do
      it 'returns the absolute path if found' do
        allow(File).to receive(:executable?).with('C:\Windows\system32\foo.exe').and_return false
        allow(File).to receive(:executable?).with('C:\Windows\foo.exe').and_return true

        expect(executor.which('foo.exe')).to eq 'C:\Windows\foo.exe'
      end

      it 'does not check all paths if one is executable' do
        allow(File).to receive(:executable?).with('C:\Windows\system32\foo.exe').and_return false
        allow(File).to receive(:executable?).with('C:\Windows\foo.exe').and_return true

        expect(File).not_to receive(:executable?).with('C:\Windows\System32\Wbem\foo.exe')
        executor.which('foo.exe')
      end

      it 'returns the absolute path with file extension if found' do
        ['.COM', '.EXE', '.BAT', '.CMD', ''].each do |ext|
          allow(File).to receive(:executable?).with("C:\\Windows\\system32\\foo#{ext}").and_return false
          allow(File).to receive(:executable?).with("C:\\Windows\\System32\\Wbem\\foo#{ext}").and_return false
        end
        ['.COM', '.BAT', '.CMD', ''].each do |ext|
          allow(File).to receive(:executable?).with("C:\\Windows\\foo#{ext}").and_return false
        end
        allow(File).to receive(:executable?).with('C:\Windows\foo.EXE').and_return true

        expect(executor.which('foo')).to eq 'C:\Windows\foo.EXE'
      end

      it 'returns nil if not found' do
        allow(File).to receive(:executable?).with('C:\Windows\system32\foo.exe').and_return false
        allow(File).to receive(:executable?).with('C:\Windows\foo.exe').and_return false
        allow(File).to receive(:executable?).with('C:\Windows\System32\Wbem\foo.exe').and_return false

        expect(executor.which('foo.exe')).to be_nil
      end
    end
  end

  describe '#expand_command' do
    it 'expands binary' do
      allow(executor).to receive(:which).with('cmd').and_return 'C:\Windows\System32\cmd'

      expect(executor.expand_command(
               'cmd /c echo foo > C:\bar'
             )).to eq 'C:\Windows\System32\cmd /c echo foo > C:\bar'
    end

    it 'returns nil if not found' do
      allow(executor).to receive(:which).with('my foo').and_return 'C:\My Tools\my foo.exe'
      expect(executor.expand_command('"my foo" /a /b')).to eq '"C:\My Tools\my foo.exe" /a /b'
    end

    it 'does not expand single quoted binary' do
      allow(executor).to receive(:which).with('\'C:\My').and_return nil
      expect(executor.expand_command('\'C:\My Tools\foo.exe\' /a /b')).to be_nil
    end

    it 'quotes expanded binary if found in path with spaces' do
      allow(executor).to receive(:which).with('foo').and_return 'C:\My Tools\foo.exe'
      expect(executor.expand_command('foo /a /b')).to eq '"C:\My Tools\foo.exe" /a /b'
    end

    it 'expands a multi-line command with double quotes' do
      allow(executor).to receive(:which).with('foo').and_return 'C:\My Tools\foo.exe'
      expect(executor.expand_command(
               'foo cmd /c\\n" show | grep "(^test)'
             )).to eq(%q("C:\My Tools\foo.exe" cmd /c\n" show | grep "(^test)))
    end

    it 'returns nil if command not found' do
      allow(executor).to receive(:which).with('foo').and_return nil
      expect(executor.expand_command('foo /a | stuff >> NUL')).to be_nil
    end
  end

  describe '#absolute_path?' do
    ['C:/foo',
     'C:\foo',
     '\\\\Server\Foo\Bar',
     '\\\\?\C:\foo\bar',
     '//Server/Foo/Bar',
     '//?/C:/foo/bar',
     '/\?\C:/foo\bar',
     '\/Server\Foo/Bar',
     'c:/foo//bar//baz'].each do |path|
      it "returns true for #{path}" do
        expect(executor).to be_absolute_path(path)
      end
    end

    %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path|
      it "returns false for #{path}" do
        expect(executor).not_to be_absolute_path(path)
      end
    end
  end
end
