gitlab-ce/spec/keeps/delete_old_feature_flags_sp...

773 lines
28 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
require './keeps/delete_old_feature_flags'
RSpec.describe Keeps::DeleteOldFeatureFlags, feature_category: :tooling do
let(:groups) do
{
foo: {
label: 'group::global search',
backend_engineers: ['@john_doe']
}
}
end
let(:feature_flag_name) { 'feature_flag_name' }
let(:tmp_dir) { Pathname(Dir.mktmpdir) }
let(:feature_flag_milestone) { '15.8' }
let(:feature_flag_file) do
file_path = tmp_dir.join('feature_flag.yml')
File.write(file_path, {
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue: 'issue_url',
group: groups.dig(:foo, :label),
default_enabled: true
}.to_yaml)
file_path.to_s
end
let(:milestones_helper) { instance_double(Keeps::Helpers::Milestones) }
subject(:keep) { described_class.new }
before do
stub_request(:get, Keeps::Helpers::Groups::GROUPS_JSON_URL).to_return(status: 200, body: groups.to_json)
stub_request(:get, format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123'))
.to_return(status: 200, body: { labels: [] }.to_json)
allow(keep).to receive(:all_feature_flag_files).and_return([feature_flag_file])
allow(keep).to receive(:milestones_helper).and_return(milestones_helper)
allow(keep).to receive(:can_remove_ff?).and_return(true)
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_ENABLED_FLAG)
.and_return(true)
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_DISABLED_FLAG)
.and_return(true)
end
after do
FileUtils.rm_rf(tmp_dir)
end
describe '#can_remove_ff?' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: nil
)
end
let(:identifiers) { ['DeleteOldFeatureFlags', feature_flag_name] }
before do
allow(keep).to receive(:can_remove_ff?).and_call_original
allow(keep).to receive(:logger).and_return(double.as_null_object)
allow(keep).to receive(:matches_filter_identifiers?).and_return(true)
allow(keep).to receive(:feature_flag_rollout_issue_url).and_return(feature_flag.rollout_issue_url)
allow(keep).to receive(:get_latest_feature_flag_status).and_return(:enabled)
end
context 'when milestone is nil' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: nil,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: nil
)
end
it 'returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be false
end
end
context 'when milestone is after cutoff for enabled flags' do
before do
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_ENABLED_FLAG)
.and_return(false)
end
it 'returns false for enabled flag' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be false
end
end
context 'when milestone is after cutoff for disabled flags' do
before do
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_DISABLED_FLAG)
.and_return(false)
end
it 'returns false for disabled flag' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :disabled)).to be false
end
end
context 'when feature flag does not match filter identifiers' do
before do
allow(keep).to receive(:matches_filter_identifiers?).and_return(false)
end
it 'returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be false
end
end
context 'when feature flag is default enabled' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: true,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: nil
)
end
it 'returns true' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be true
end
end
context 'when latest feature flag status is nil' do
it 'returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, nil)).to be false
end
end
context 'when latest feature flag status is conditional' do
it 'returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :conditional)).to be false
end
end
context 'when all conditions are met for enabled flags' do
it 'returns true' do
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_ENABLED_FLAG)
.and_return(true)
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be true
end
end
context 'when all conditions are met for disabled flags' do
it 'returns true' do
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_DISABLED_FLAG)
.and_return(true)
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :disabled)).to be true
end
end
describe '#parse_date' do
it 'returns a date object for valid date strings' do
expect(keep.send(:parse_date, '2023-01-01')).to eq(Date.new(2023, 1, 1))
end
it 'returns nil for invalid date strings' do
expect(keep.send(:parse_date, '2020')).to be_nil
expect(keep.send(:parse_date, 'invalid')).to be_nil
expect(keep.send(:parse_date, 'February 31, 2023')).to be_nil
end
end
context 'when feature flag has a future intended_to_rollout_by date' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: (Time.zone.today + 30).to_s
)
end
it 'returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be false
end
end
context 'when feature flag has a past intended_to_rollout_by date' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: (Time.zone.today - 30).to_s
)
end
it 'returns true when other conditions are met' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be true
end
end
context 'when feature flag has an invalid intended_to_rollout_by date' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: '2020'
)
end
# When parse_date returns nil for an invalid date, it passes through
# the condition and allows removal
it 'returns true when other conditions are met' do
expect(keep.send(:parse_date, '2020')).to be_nil
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be true
end
end
context 'when feature flag has ready for removal label' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: nil, # This would normally fail validation
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123',
default_enabled: false,
group: groups.dig(:foo, :label),
path: feature_flag_file,
intended_to_rollout_by: (Time.zone.today + 30).to_s # Future date would normally fail validation
)
end
before do
stub_request(:get,
format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123')
).to_return(status: 200, body: { labels: ['feature flag::ready for removal'] }.to_json)
# Make milestone cutoff check fail to prove it's bypassed
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_ENABLED_FLAG)
.and_return(false)
end
it 'bypasses rollout date, milestone, and cutoff checks and returns true' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be true
end
end
context 'when feature flag does not have ready for removal label' do
before do
stub_request(:get,
format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123')
).to_return(status: 200, body: { labels: ['some other label'] }.to_json)
# Make milestone cutoff check fail
allow(milestones_helper)
.to receive(:before_cuttoff?).with(milestone: feature_flag_milestone,
milestones_ago: described_class::CUTOFF_MILESTONE_FOR_ENABLED_FLAG)
.and_return(false)
end
it 'respects milestone cutoff check and returns false' do
expect(keep.send(:can_remove_ff?, feature_flag, identifiers, :enabled)).to be false
end
end
end
describe '#has_ready_for_removal_label?' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
rollout_issue_url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/123'
)
end
before do
allow(keep).to receive(:logger).and_return(double.as_null_object)
allow(keep).to receive(:feature_flag_rollout_issue_url).and_return(feature_flag.rollout_issue_url)
end
context 'when rollout issue has ready for removal label' do
before do
stub_request(:get,
format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123')
).to_return(status: 200, body: { labels: ['feature flag::ready for removal',
'other label'] }.to_json)
end
it 'returns true' do
expect(keep.send(:has_ready_for_removal_label?, feature_flag)).to be true
end
end
context 'when rollout issue does not have ready for removal label' do
before do
stub_request(:get,
format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123')
).to_return(status: 200, body: { labels: ['some other label'] }.to_json)
end
it 'returns false' do
expect(keep.send(:has_ready_for_removal_label?, feature_flag)).to be false
end
end
context 'when rollout issue URL is missing' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
milestone: feature_flag_milestone,
rollout_issue_url: nil
)
end
before do
allow(keep).to receive(:feature_flag_rollout_issue_url).and_return('(missing URL)')
end
it 'returns false' do
expect(keep.send(:has_ready_for_removal_label?, feature_flag)).to be false
end
end
context 'when API request fails' do
before do
stub_request(:get,
format(described_class::API_ISSUE_URL, project_path: 'gitlab-org%2Fgitlab', issue_iid: '123'))
.to_return(status: 404)
end
it 'returns false' do
expect(keep.send(:has_ready_for_removal_label?, feature_flag)).to be false
end
end
end
describe '#each_change' do
before do
allow(keep).to receive(:can_remove_ff?).and_return(true)
end
context 'when we use ai to get the patch' do
let(:expected_change) { instance_double(Gitlab::Housekeeper::Change) }
let(:ai_helper) { instance_double(Keeps::Helpers::AiEditor) }
before do
allow(keep).to receive(:ai_helper).and_return(ai_helper)
allow(keep).to receive(:files_mentioning_feature_flag).and_return(['app/controllers/feature_controller.rb'])
allow(keep).to receive(:remove_feature_flag_prompts).and_return(
instance_double(Keeps::Prompts::RemoveFeatureFlags, fetch: 'user message')
)
allow(ai_helper).to receive(:ask_for_and_apply_patch).and_return(true)
allow(Gitlab::Housekeeper::Shell).to receive(:rubocop_autocorrect).and_return(true)
end
it 'returns a Gitlab::Housekeeper::Change', :aggregate_failures do
allow(keep).to receive(:execute_grep).and_return("grep results")
expect(FileUtils).to receive(:rm).with(feature_flag_file)
actual_changes = []
keep.each_change { |change| actual_changes << change }
expect(actual_changes.size).to eq(1)
actual_change = actual_changes.first
expect(actual_change).to be_a(Gitlab::Housekeeper::Change)
expect(actual_change.changelog_type).to eq('removed')
expect(actual_change.title).to eq("Delete the `#{feature_flag_name}` feature flag")
expect(actual_change.identifiers).to match_array([described_class.name.demodulize, feature_flag_name])
expect(actual_change.reviewers).to match_array(['@john_doe'])
expect(actual_change.labels).to match_array(['automation:feature-flag-removal', 'maintenance::removal',
'feature flag', groups.dig(:foo, :label)])
expect(actual_change.changed_files).to include(feature_flag_file)
expect(actual_change.changed_files).to include('app/controllers/feature_controller.rb')
end
end
context 'when we have feature flag patch path present' do
let(:expected_change) { instance_double(Gitlab::Housekeeper::Change) }
let(:feature_flag_patch_path) { feature_flag_file.sub(/.yml$/, '.patch') }
before do
File.write(feature_flag_patch_path, <<~DIFF)
diff --git a/foobar.txt b/foobar.txt
index 2ef267e25bd6..0fecdb8e98f3 100644
--- a/foobar.txt
+++ b/foobar.txt
@@ -1 +1 @@
-some content
+some content updated
DIFF
end
it 'returns a Gitlab::Housekeeper::Change', :aggregate_failures do
allow(keep).to receive(:execute_grep).and_return("grep results")
expect(FileUtils).to receive(:rm).with(feature_flag_file)
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with('git', 'apply', feature_flag_patch_path)
expect(FileUtils).to receive(:rm).with(feature_flag_patch_path)
actual_changes = []
keep.each_change { |change| actual_changes << change }
expect(actual_changes.size).to eq(1)
actual_change = actual_changes.first
expect(actual_change).to be_a(Gitlab::Housekeeper::Change)
expect(actual_change.changelog_type).to eq('removed')
expect(actual_change.title).to eq("Delete the `#{feature_flag_name}` feature flag")
expect(actual_change.identifiers).to match_array([described_class.name.demodulize, feature_flag_name])
expect(actual_change.changed_files).to match_array([feature_flag_file, feature_flag_patch_path, 'foobar.txt'])
expect(actual_change.reviewers).to match_array(['@john_doe'])
expect(actual_change.labels).to match_array(['automation:feature-flag-removal', 'maintenance::removal',
'feature flag', groups.dig(:foo, :label)])
end
end
end
describe '#files_mentioning_feature_flag' do
let(:logger) { instance_double(Gitlab::Housekeeper::Logger) }
before do
keep.instance_variable_set(:@logger, logger)
allow(logger).to receive(:puts)
end
context 'when there are matching files' do
it 'returns the list of files' do
expect(keep).to receive(:find_files_with_pattern).with("feature.*#{feature_flag_name}").and_return(['file1.rb'])
expect(keep).to receive(:find_files_with_pattern).with(
"push_frontend_feature_flag.*#{feature_flag_name}"
).and_return(['file2.rb'])
expect(keep).to receive(:find_files_with_pattern).with("glFeatures.*featureFlagName").and_return(['file3.js'])
expect(keep).to receive(:find_files_with_pattern).with("gon.*featureFlagName").and_return([])
expect(keep).to receive(:find_files_with_pattern).with("featureFlagName").and_return(['file4.vue'])
expect(keep).to receive(:find_files_with_pattern).with("feature_flag_name").and_return([])
result = keep.send(:files_mentioning_feature_flag, feature_flag_name)
expect(result).to match_array(['file1.rb', 'file2.rb', 'file3.js', 'file4.vue'])
end
it 'makes the expected git grep calls to find relevant files' do
camel_case_flag = feature_flag_name.camelize(:lower)
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', "feature.*#{feature_flag_name}",
'--', ':^locale/', ':^db/structure.sql'
).and_return("file1.rb")
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', "push_frontend_feature_flag.*#{feature_flag_name}",
'--', ':^locale/', ':^db/structure.sql'
).and_return("file2.rb")
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', "glFeatures.*#{camel_case_flag}",
'--', ':^locale/', ':^db/structure.sql'
).and_return("file3.js")
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', "gon.*#{camel_case_flag}",
'--', ':^locale/', ':^db/structure.sql'
).and_return("")
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', camel_case_flag,
'--', ':^locale/', ':^db/structure.sql'
).and_return("file4.vue")
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--name-only', feature_flag_name,
'--', ':^locale/', ':^db/structure.sql'
).and_return("")
result = keep.send(:files_mentioning_feature_flag, feature_flag_name)
expect(result).to match_array(['file1.rb', 'file2.rb', 'file3.js', 'file4.vue'])
end
end
context 'when there are no matching files' do
it 'returns an empty array' do
allow(keep).to receive(:find_files_with_pattern).and_return([])
result = keep.send(:files_mentioning_feature_flag, feature_flag_name)
expect(result).to eq([])
end
end
context 'when there are duplicate files' do
it 'returns unique file names' do
camel_case_flag = feature_flag_name.camelize(:lower)
expect(keep).to receive(:find_files_with_pattern).with("feature.*#{feature_flag_name}").and_return(['file1.rb'])
expect(keep).to receive(:find_files_with_pattern).with(
"push_frontend_feature_flag.*#{feature_flag_name}"
).and_return(['file1.rb'])
expect(keep).to receive(:find_files_with_pattern)
.with("glFeatures.*#{camel_case_flag}").and_return(['file2.js'])
expect(keep).to receive(:find_files_with_pattern).with("gon.*#{camel_case_flag}").and_return([])
expect(keep).to receive(:find_files_with_pattern).with(camel_case_flag).and_return(['file2.js'])
expect(keep).to receive(:find_files_with_pattern).with(feature_flag_name).and_return([])
result = keep.send(:files_mentioning_feature_flag, feature_flag_name)
expect(result).to match_array(['file1.rb', 'file2.js'])
end
end
end
describe '#find_files_with_pattern' do
let(:logger) { instance_double(Gitlab::Housekeeper::Logger) }
before do
keep.instance_variable_set(:@logger, logger)
allow(logger).to receive(:puts)
end
context 'when git grep finds files' do
it 'returns the list of files' do
expect(Gitlab::Housekeeper::Shell).to receive(:execute)
.with('git', 'grep', '--name-only', 'search_pattern', '--', ':^locale/', ':^db/structure.sql')
.and_return("file1.rb\nfile2.rb")
result = keep.send(:find_files_with_pattern, 'search_pattern')
expect(result).to eq(['file1.rb', 'file2.rb'])
end
end
context 'when git grep returns empty string' do
it 'returns an empty array' do
expect(Gitlab::Housekeeper::Shell).to receive(:execute)
.with('git', 'grep', '--name-only', 'search_pattern', '--', ':^locale/', ':^db/structure.sql')
.and_return("")
result = keep.send(:find_files_with_pattern, 'search_pattern')
expect(result).to eq([])
end
end
context 'when git grep returns nil' do
it 'returns an empty array' do
expect(Gitlab::Housekeeper::Shell).to receive(:execute)
.with('git', 'grep', '--name-only', 'search_pattern', '--', ':^locale/', ':^db/structure.sql')
.and_return(nil)
result = keep.send(:find_files_with_pattern, 'search_pattern')
expect(result).to eq([])
end
end
context 'when git grep raises an error' do
it 'logs the error and returns an empty array' do
expect(Gitlab::Housekeeper::Shell).to receive(:execute)
.with('git', 'grep', '--name-only', 'search_pattern', '--', ':^locale/', ':^db/structure.sql')
.and_raise(Gitlab::Housekeeper::Shell::Error)
expect(logger).to receive(:puts).with("No files found for pattern: search_pattern")
result = keep.send(:find_files_with_pattern, 'search_pattern')
expect(result).to eq([])
end
end
end
describe '#apply_patch' do
let(:feature_flag) do
instance_double(
Feature::Definition,
name: feature_flag_name,
path: feature_flag_file
)
end
let(:patch_path) { feature_flag_file.sub(/.yml$/, '.patch') }
let(:expected_files) { [patch_path, 'changed_file.rb'] }
it 'executes git apply with the correct patch file' do
change = instance_double(Gitlab::Housekeeper::Change)
allow(keep).to receive(:patch_path).with(feature_flag).and_return(patch_path)
allow(keep).to receive(:extract_changed_files_from_patch).with(feature_flag).and_return(['changed_file.rb'])
expect(change).to receive(:changed_files).at_least(:once).and_return([])
expect(change).to receive(:changed_files=).with(expected_files)
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'apply', patch_path
).and_return(true)
expect(FileUtils).to receive(:rm).with(patch_path)
result = keep.send(:apply_patch, feature_flag, change)
expect(result).to be true
end
it 'returns false when git apply fails' do
change = instance_double(Gitlab::Housekeeper::Change)
allow(keep).to receive(:patch_path).with(feature_flag).and_return(patch_path)
allow(keep).to receive(:extract_changed_files_from_patch).with(feature_flag).and_return(['changed_file.rb'])
expect(change).to receive(:changed_files).at_least(:once).and_return([])
expect(change).to receive(:changed_files=).with(expected_files)
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'apply', patch_path
).and_raise(Gitlab::Housekeeper::Shell::Error)
result = keep.send(:apply_patch, feature_flag, change)
expect(result).to be false
end
end
describe '#feature_flag_grep' do
before do
allow(keep).to receive(:git_patterns).with(feature_flag_name).and_return(
%w[pattern1 pattern2]
)
end
it 'collects grep results from all patterns' do
expect(keep).to receive(:execute_grep).with("pattern1").and_return("result1\n")
expect(keep).to receive(:execute_grep).with("pattern2").and_return("result2\n")
result = keep.send(:feature_flag_grep, feature_flag_name)
expect(result).to include("result1\n", "result2\n")
end
it 'handles nil results gracefully' do
expect(keep).to receive(:execute_grep).with("pattern1").and_return(nil)
expect(keep).to receive(:execute_grep).with("pattern2").and_return("result2")
result = keep.send(:feature_flag_grep, feature_flag_name)
expect(result).to eq("result2\n")
end
end
describe '#execute_grep' do
it 'calls git grep with the given pattern' do
pattern = "feature.*#{feature_flag_name}"
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--heading', '--line-number', '--break',
pattern, '--', ':^locale/', ':^db/structure.sql'
).and_return("grep results")
result = keep.send(:execute_grep, pattern)
expect(result).to eq("grep results")
end
it 'returns empty string when git grep raises an error' do
pattern = "feature.*#{feature_flag_name}"
expect(Gitlab::Housekeeper::Shell).to receive(:execute).with(
'git', 'grep', '--heading', '--line-number', '--break',
pattern, '--', ':^locale/', ':^db/structure.sql'
).and_raise(Gitlab::Housekeeper::Shell::Error)
result = keep.send(:execute_grep, pattern)
expect(result).to eq("")
end
end
describe '#each_feature_flag' do
let(:tmp_dir) { Pathname(Dir.mktmpdir) }
let(:feature_flag_file_1) { tmp_dir.join('feature_flag_1.yml') }
let(:feature_flag_file_2) { tmp_dir.join('feature_flag_2.yml') }
let(:feature_flag_file_3) { tmp_dir.join('feature_flag_3.yml') }
before do
# Create feature flags with different milestones
File.write(feature_flag_file_1, {
name: 'feature_flag_1',
milestone: '15.10',
rollout_issue: 'issue_url',
group: 'group::foo',
default_enabled: false
}.to_yaml)
File.write(feature_flag_file_2, {
name: 'feature_flag_2',
milestone: '15.8',
rollout_issue: 'issue_url',
group: 'group::foo',
default_enabled: false
}.to_yaml)
File.write(feature_flag_file_3, {
name: 'feature_flag_3',
milestone: '15.9',
rollout_issue: 'issue_url',
group: 'group::foo',
default_enabled: false
}.to_yaml)
allow(keep).to receive(:all_feature_flag_files).and_return([
feature_flag_file_1.to_s,
feature_flag_file_2.to_s,
feature_flag_file_3.to_s
])
end
after do
FileUtils.rm_rf(tmp_dir)
end
it 'yields feature flags sorted by milestone' do
yielded_flags = []
keep.send(:each_feature_flag) do |feature_flag|
yielded_flags << feature_flag
end
expect(yielded_flags.map(&:milestone)).to eq(['15.8', '15.9', '15.10'])
expect(yielded_flags.map(&:name)).to eq(%w[feature_flag_2 feature_flag_3 feature_flag_1])
end
end
end