#! /usr/bin/env ruby
# frozen_string_literal: true

describe LegacyFacter::Util::Loader do
  let(:logger) { instance_spy(Facter::Log) }

  before do
    allow(Facter::Log).to receive(:new).and_return(logger)
  end

  def loader_from(places)
    env = places[:env] || {}
    search_path = places[:search_path] || []
    loader = LegacyFacter::Util::Loader.new(env)
    allow(loader).to receive(:search_path).and_return(search_path)
    loader
  end

  it 'has a method for loading individual facts by name' do
    expect(LegacyFacter::Util::Loader.new).to respond_to(:load)
  end

  it 'has a method for loading all facts' do
    expect(LegacyFacter::Util::Loader.new).to respond_to(:load_all)
  end

  it 'has a method for returning directories containing facts' do
    expect(LegacyFacter::Util::Loader.new).to respond_to(:search_path)
  end

  describe 'when determining the search path' do
    let(:loader) { LegacyFacter::Util::Loader.new }

    it 'includes the facter subdirectory of all paths in ruby LOAD_PATH' do
      dirs = $LOAD_PATH.collect { |d| File.expand_path('facter', d) }
      allow(File).to receive(:directory?).and_return true

      paths = loader.search_path

      dirs.each do |dir|
        expect(paths).to include(dir)
      end
    end

    it 'excludes invalid search paths' do
      dirs = $LOAD_PATH.collect { |d| File.expand_path('custom_facts', d) }
      allow(File).to receive(:directory?).and_return false

      paths = loader.search_path

      dirs.each do |dir|
        expect(paths).not_to include(dir)
      end
    end

    it 'includes all search paths registered with Facter' do
      allow(Facter::Options).to receive(:custom_dir).and_return %w[/one two/three]

      allow(File).to receive(:directory?).and_return false
      allow(File).to receive(:directory?).with('/one').and_return true
      allow(File).to receive(:directory?).with('two/three').and_return true

      paths = loader.search_path
      expect(paths).to include('/one', 'two/three')
    end

    it 'strips paths that are valid paths but are not present' do
      allow(Facter::Options).to receive(:custom_dir).and_return %w[/one /two]

      allow(File).to receive(:directory?).and_return false
      allow(File).to receive(:directory?).with('/one').and_return true
      allow(File).to receive(:directory?).with('/two').and_return false

      paths = loader.search_path
      expect(paths).to contain_exactly('/one')
    end

    describe 'and the FACTERLIB environment variable is set' do
      it 'includes all paths in FACTERLIB' do
        loader = LegacyFacter::Util::Loader.new('FACTERLIB' => "/one/path#{File::PATH_SEPARATOR}/two/path")

        allow(File).to receive(:directory?).and_return false
        allow(File).to receive(:directory?).with('/one/path').and_return true
        allow(File).to receive(:directory?).with('/two/path').and_return true

        paths = loader.search_path
        %w[/one/path /two/path].each do |dir|
          expect(paths).to include(dir)
        end
      end
    end
  end

  describe 'when loading facts' do
    it 'loads values from the matching environment variable if one is present' do
      loader = loader_from(env: { 'facter_testing' => 'yayness' })
      allow(LegacyFacter).to receive(:add)

      loader.load(:testing)

      expect(LegacyFacter).to have_received(:add).with('testing', { fact_type: :external, is_env: true })
    end

    it 'loads any files in the search path with names matching the fact name' do
      loader = loader_from(search_path: %w[/one/dir /two/dir])

      allow(loader).to receive(:search_path).and_return %w[/one/dir /two/dir]
      allow(FileTest).to receive(:file?).and_return false
      allow(FileTest).to receive(:file?).with('/one/dir/testing.rb').and_return true

      expect(Kernel).to receive(:load).with('/one/dir/testing.rb')
      loader.load(:testing)
    end

    it 'does not load any ruby files from subdirectories matching the fact name in the search path' do
      loader = LegacyFacter::Util::Loader.new
      allow(FileTest).to receive(:file?).and_return false
      allow(FileTest).to receive(:file?).with('/one/dir/testing.rb').and_return true

      allow(File).to receive(:directory?).with('/one/dir/testing').and_return true
      allow(loader).to receive(:search_path).and_return %w[/one/dir]

      allow(Dir).to receive(:entries).with('/one/dir/testing').and_return %w[foo.rb bar.rb]
      %w[/one/dir/testing/foo.rb /one/dir/testing/bar.rb].each do |f|
        allow(File).to receive(:directory?).with(f).and_return false
        allow(Kernel).to receive(:load).with(f)
      end

      expect(Kernel).to receive(:load).with('/one/dir/testing.rb')
      loader.load(:testing)
    end

    it "does not load files that don't end in '.rb'" do
      loader = LegacyFacter::Util::Loader.new
      allow(loader).to receive(:search_path).and_return %w[/one/dir]
      allow(FileTest).to receive(:file?).and_return false
      allow(FileTest).to receive(:file?).with('/one/dir/testing.rb').and_return false

      expect(Kernel).not_to receive(:load)

      loader.load(:testing)
    end
  end

  describe 'when loading all facts' do
    let(:loader) { LegacyFacter::Util::Loader.new }

    before do
      allow(loader).to receive(:search_path).and_return([])

      allow(File).to receive(:directory?).and_return true
    end

    it 'loads all files in all search paths' do
      loader = loader_from(search_path: %w[/one/dir /two/dir])

      allow(Dir).to receive(:glob).with('/one/dir/*.rb').and_return %w[/one/dir/a.rb /one/dir/b.rb]
      allow(Dir).to receive(:glob).with('/two/dir/*.rb').and_return %w[/two/dir/c.rb /two/dir/d.rb]

      %w[/one/dir/a.rb /one/dir/b.rb /two/dir/c.rb /two/dir/d.rb].each do |f|
        allow(FileTest).to receive(:file?).with(f).and_return true
        expect(Kernel).to receive(:load).with(f)
      end

      loader.load_all
    end

    it 'does not try to load subdirectories of search paths' do
      allow(loader).to receive(:search_path).and_return %w[/one/dir /two/dir]

      # a.rb is a directory
      allow(Dir).to receive(:glob).with('/one/dir/*.rb').and_return %w[/one/dir/a.rb /one/dir/b.rb]
      allow(FileTest).to receive(:file?).with('/one/dir/a.rb').and_return false
      allow(FileTest).to receive(:file?).with('/one/dir/b.rb').and_return true
      allow(Kernel).to receive(:load).with('/one/dir/b.rb')

      # c.rb is a directory
      allow(Dir).to receive(:glob).with('/two/dir/*.rb').and_return %w[/two/dir/c.rb /two/dir/d.rb]
      allow(FileTest).to receive(:file?).with('/two/dir/c.rb').and_return false
      allow(FileTest).to receive(:file?).with('/two/dir/d.rb').and_return true
      expect(Kernel).to receive(:load).with('/two/dir/d.rb')

      loader.load_all
    end

    it 'does not raise an exception when a file is unloadable' do
      allow(loader).to receive(:search_path).and_return %w[/one/dir]

      allow(Dir).to receive(:glob).with('/one/dir/*.rb').and_return %w[/one/dir/a.rb]
      allow(FileTest).to receive(:file?).with('/one/dir/a.rb').and_return true

      allow(Facter).to receive(:log_exception).with(LoadError, 'Error loading fact /one/dir/a.rb: LoadError')
      allow(Kernel).to receive(:load).with('/one/dir/a.rb').and_raise(LoadError)

      expect { loader.load_all }.not_to raise_error
    end

    context 'when loads all facts from the environment' do
      before do
        Facter::Util::Resolution.with_env 'FACTER_one' => 'yayness', 'FACTER_TWO' => 'boo' do
          loader.load_all
        end
      end

      it 'loaded fact one' do
        expect(LegacyFacter.value(:one)).to eq 'yayness'
      end

      it 'loaded fact two' do
        expect(LegacyFacter.value(:two)).to eq 'boo'
      end
    end

    it 'only load all facts once' do
      loader = loader_from(env: {})
      expect(loader).to receive(:load_env).once

      loader.load_all
      loader.load_all
    end

    context 'when directory path has wrong slashes' do
      before do
        allow(Dir).to receive(:glob).with('/one/dir/*.rb').and_return %w[/one/dir/a.rb]
      end

      dir_paths = ['//one///dir', '//one///\\dir', '/one///\/\dir', '\one///\\dir']

      dir_paths.each do |dir_path|
        it 'corrects the directory path' do
          allow(loader).to receive(:search_path).and_return [dir_path]

          loader.load_all

          expect(Dir).to have_received(:glob).with('/one/dir/*.rb')
        end
      end
    end
  end

  it 'loads facts on the facter search path only once' do
    loader = loader_from(env: {})
    loader.load_all

    expect(loader).not_to receive(:kernel_load).with(/ec2/)
    loader.load(:ec2)
  end
end
