require 'spec_helper' require 'r10k/util/purgeable' require 'r10k/util/cleaner' RSpec.describe R10K::Util::Purgeable do let(:managed_directories) do [ 'spec/fixtures/unit/util/purgeable/managed_one', 'spec/fixtures/unit/util/purgeable/managed_two', ] end let(:desired_contents) do [ 'spec/fixtures/unit/util/purgeable/managed_one/expected_1', 'spec/fixtures/unit/util/purgeable/managed_one/new_1', 'spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1', 'spec/fixtures/unit/util/purgeable/managed_one/managed_symlink_dir', 'spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/subdir_expected_1', 'spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/subdir_new_1', 'spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_symlink_file', 'spec/fixtures/unit/util/purgeable/managed_two/expected_2', 'spec/fixtures/unit/util/purgeable/managed_two/new_2', 'spec/fixtures/unit/util/purgeable/managed_two/.hidden', ] end subject { R10K::Util::Cleaner.new(managed_directories, desired_contents) } context 'without recurse option' do let(:recurse) { false } describe '#current_contents' do it 'collects direct contents of all managed directories' do expect(subject.current_contents(recurse)).to contain_exactly(/\/expected_1/, /\/expected_2/, /\/unmanaged_1/, /\/unmanaged_2/, /\/managed_subdir_1/, /\/managed_symlink_dir/, /\/unmanaged_symlink_file/) end end describe '#pending_contents' do it 'collects desired_contents that do not yet exist' do expect(subject.pending_contents(recurse)).to include(/\/new_1/, /\/new_2/) end end describe '#stale_contents' do context 'with no whitelist or exclusions' do let(:exclusions) { [] } let(:whitelist) { [] } it 'collects current_contents that should not exist' do expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_1/, /\/unmanaged_2/, /\/unmanaged_symlink_file/) end end context 'with whitelisted items' do let(:exclusions) { [] } let(:whitelist) { ['**/unmanaged_1'] } it 'collects current_contents that should not exist except whitelisted items' do expect(subject.logger).to receive(:debug).with(/unmanaged_1.*whitelist match/i) expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_2/, /\/unmanaged_symlink_file/) end end context 'with excluded items' do let(:exclusions) { ['**/unmanaged_2'] } let(:whitelist) { [] } it 'collects current_contents that should not exist except excluded items' do expect(subject.logger).to receive(:debug2).with(/unmanaged_2.*internal exclusion match/i) expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_1/, /\/unmanaged_symlink_file/) end end end describe '#purge!' do let(:exclusions) { [] } let(:whitelist) { [] } let(:purge_opts) { { recurse: recurse, whitelist: whitelist } } it 'does nothing when there is no stale_contents' do allow(subject).to receive(:stale_contents).and_return([]) expect(FileUtils).to_not receive(:rm_rf) subject.purge!(purge_opts) end it 'recursively deletes all stale_contents' do subject.stale_contents(recurse, exclusions, whitelist).each do |stale| expect(FileUtils).to receive(:rm_r).with(stale, hash_including(secure: true)) end subject.purge!(purge_opts) end end end context 'with recurse option' do let(:recurse) { true } describe '#current_contents' do it 'collects contents of all managed directories recursively' do expect(subject.current_contents(recurse)). to contain_exactly(/\/expected_1/, /\/expected_2/, /\/unmanaged_1/, /\/unmanaged_2/, /\/managed_symlink_dir/, /\/unmanaged_symlink_file/, /\/managed_subdir_1/, /\/subdir_expected_1/, /\/subdir_unmanaged_1/, /\/managed_symlink_file/, /\/unmanaged_symlink_dir/, /\/subdir_allowlisted_2/, /\/ignored_1/, /\/\.hidden/) end end describe '#pending_contents' do it 'collects desired_contents that do not yet exist recursively' do expect(subject.pending_contents(recurse)).to include(/\/new_1/, /\/new_2/, /\/subdir_new_1/) end end describe '#stale_contents' do context 'with no whitelist or exclusions' do let(:exclusions) { [] } let(:whitelist) { [] } it 'collects current_contents that should not exist recursively' do expect(subject.stale_contents(recurse, exclusions, whitelist)). to contain_exactly(/\/unmanaged_1/, /\/unmanaged_2/, /\/unmanaged_symlink_file/, /\/subdir_unmanaged_1/, /\/ignored_1/, /\/subdir_allowlisted_2/, /\/unmanaged_symlink_dir/) end end context 'with whitelisted items' do let(:exclusions) { [] } let(:whitelist) { ['**/unmanaged_1'] } it 'collects current_contents that should not exist except whitelisted items' do expect(subject.logger).to receive(:debug).with(/unmanaged_1.*whitelist match/i) expect(subject.stale_contents(recurse, exclusions, whitelist)). to contain_exactly(/\/unmanaged_2/, /\/subdir_unmanaged_1/, /\/unmanaged_symlink_file/, /\/ignored_1/, /\/subdir_allowlisted_2/, /\/unmanaged_symlink_dir/) end it 'does not collect contents that match recursive globbed whitelist items as intermediate values' do recursive_whitelist = ['**/managed_subdir_1/**/*'] expect(subject.logger).not_to receive(:debug).with(/ignored_1/) expect(subject.stale_contents(recurse, exclusions, recursive_whitelist)). to contain_exactly(/\/unmanaged_2/, /\/managed_one\/unmanaged_1/, /\/managed_one\/unmanaged_symlink_file/) end end context 'with excluded items' do let(:exclusions) { ['**/unmanaged_2'] } let(:whitelist) { [] } it 'collects current_contents that should not exist except excluded items' do expect(subject.logger).to receive(:debug2).with(/unmanaged_2.*internal exclusion match/i) expect(subject.stale_contents(recurse, exclusions, whitelist)). to contain_exactly(/\/unmanaged_1/, /\/unmanaged_symlink_file/, /\/subdir_unmanaged_1/, /\/ignored_1/, /\/subdir_allowlisted_2/, /\/unmanaged_symlink_dir/) end it 'does not collect contents that match recursive globbed exclusion items as intermediate values' do recursive_exclusions = ['**/managed_subdir_1/**/*'] expect(subject.logger).not_to receive(:debug).with(/ignored_1/) expect(subject.stale_contents(recurse, recursive_exclusions, whitelist)). to contain_exactly(/\/unmanaged_2/, /\/unmanaged_symlink_file/, /\/managed_one\/unmanaged_1/) end end end describe '#purge!' do let(:exclusions) { [] } let(:whitelist) { [] } let(:purge_opts) { { recurse: recurse, whitelist: whitelist } } it 'does nothing when there is no stale_contents' do allow(subject).to receive(:stale_contents).and_return([]) expect(FileUtils).to_not receive(:rm_r) subject.purge!(purge_opts) end it 'recursively deletes all stale_contents' do subject.stale_contents(recurse, exclusions, whitelist).each do |stale| expect(FileUtils).to receive(:rm_r).with(stale, hash_including(secure: true)) end subject.purge!(purge_opts) end end end describe "user whitelist functionality" do context "non-recursive whitelist glob" do let(:whitelist) { managed_directories.collect { |dir| File.join(dir, "*unmanaged*") } } let(:purge_opts) { { recurse: true, whitelist: whitelist } } describe '#purge!' do it 'does not purge items matching glob at root level' do allow(FileUtils).to receive(:rm_r) expect(FileUtils).to_not receive(:rm_r).with(/\/unmanaged_[12]/, anything) expect(FileUtils).to_not receive(:rm_r).with(/\/unmanaged_symlink_file/, anything) expect(subject.logger).to receive(:debug).with(/whitelist match/i).at_least(:once) subject.purge!(purge_opts) end end end context "recursive whitelist glob" do let(:whitelist) do managed_directories.flat_map do |dir| [File.join(dir, "**", "*unmanaged*"), File.join(dir, "**", "subdir_allowlisted_2")] end end let(:purge_opts) { { recurse: true, whitelist: whitelist } } describe '#purge!' do it 'does not purge items matching glob at any level' do expect(FileUtils).to_not receive(:rm_r) expect(subject.logger).to receive(:debug).with(/whitelist match/i).at_least(:once) subject.purge!(purge_opts) end end end end describe "internal exclusions functionality" do let(:purge_opts) { { recurse: true, whitelist: [] } } let(:exclusions) { [ File.join('**', 'unmanaged_1') ] } context "when class implements #purge_exclusions" do describe '#purge!' do it 'does not purge items matching exclusion glob' do expect(subject).to receive(:purge_exclusions).and_return(exclusions) allow(FileUtils).to receive(:rm_r) expect(FileUtils).to_not receive(:rm_r).with(/\/unmanaged_1/, anything) expect(subject.logger).to receive(:debug2).with(/unmanaged_1.*internal exclusion match/i) subject.purge!(purge_opts) end end end context "when class does not implement #purge_exclusions" do describe '#purge!' do it 'purges normally' do expect(FileUtils).to receive(:rm_r).at_least(4).times subject.purge!(purge_opts) end end end end end