2020-07-28 23:09:34 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
require 'fast_spec_helper'
|
|
|
|
require 'rspec-parameterized'
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
load File.expand_path('../../bin/feature-flag', __dir__)
|
|
|
|
|
|
|
|
RSpec.describe 'bin/feature-flag' do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
describe FeatureFlagCreator do
|
2020-10-24 23:57:45 +05:30
|
|
|
let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url -m http://url] }
|
2020-07-28 23:09:34 +05:30
|
|
|
let(:options) { FeatureFlagOptionParser.parse(argv) }
|
|
|
|
let(:creator) { described_class.new(options) }
|
2020-11-24 15:15:51 +05:30
|
|
|
let(:existing_flags) do
|
2021-01-29 00:20:46 +05:30
|
|
|
{ 'existing_feature_flag' => File.join('config', 'feature_flags', 'development', 'existing_feature_flag.yml') }
|
2020-11-24 15:15:51 +05:30
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
before do
|
2020-11-24 15:15:51 +05:30
|
|
|
allow(creator).to receive(:all_feature_flag_names) { existing_flags }
|
|
|
|
allow(creator).to receive(:branch_name) { 'feature-branch' }
|
|
|
|
allow(creator).to receive(:editor) { nil }
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
# ignore writes
|
|
|
|
allow(File).to receive(:write).and_return(true)
|
|
|
|
|
|
|
|
# ignore stdin
|
|
|
|
allow($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { creator.execute }
|
|
|
|
|
|
|
|
it 'properly creates a feature flag' do
|
|
|
|
expect(File).to receive(:write).with(
|
2021-01-29 00:20:46 +05:30
|
|
|
File.join('config', 'feature_flags', 'development', 'feature_flag_name.yml'),
|
2020-07-28 23:09:34 +05:30
|
|
|
anything)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
subject
|
2021-01-29 00:20:46 +05:30
|
|
|
end.to output(/name: feature_flag_name/).to_stdout
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when running on master' do
|
|
|
|
it 'requires feature branch' do
|
|
|
|
expect(creator).to receive(:branch_name) { 'master' }
|
|
|
|
|
|
|
|
expect { subject }.to raise_error(FeatureFlagHelpers::Abort, /Create a branch first/)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'validates feature flag name' do
|
|
|
|
where(:argv, :ex) do
|
|
|
|
%w[.invalid.feature.flag] | /Provide a name for the feature flag that is/
|
|
|
|
%w[existing-feature-flag] | /already exists!/
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it do
|
|
|
|
expect { subject }.to raise_error(ex)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe FeatureFlagOptionParser do
|
|
|
|
describe '.parse' do
|
|
|
|
where(:param, :argv, :result) do
|
|
|
|
:name | %w[foo] | 'foo'
|
|
|
|
:amend | %w[foo --amend] | true
|
|
|
|
:force | %w[foo -f] | true
|
|
|
|
:force | %w[foo --force] | true
|
|
|
|
:ee | %w[foo -e] | true
|
|
|
|
:ee | %w[foo --ee] | true
|
|
|
|
:introduced_by_url | %w[foo -m https://url] | 'https://url'
|
|
|
|
:introduced_by_url | %w[foo --introduced-by-url https://url] | 'https://url'
|
|
|
|
:rollout_issue_url | %w[foo -i https://url] | 'https://url'
|
|
|
|
:rollout_issue_url | %w[foo --rollout-issue-url https://url] | 'https://url'
|
|
|
|
:dry_run | %w[foo -n] | true
|
|
|
|
:dry_run | %w[foo --dry-run] | true
|
|
|
|
:type | %w[foo -t development] | :development
|
|
|
|
:type | %w[foo --type development] | :development
|
|
|
|
:type | %w[foo -t invalid] | nil
|
|
|
|
:type | %w[foo --type invalid] | nil
|
|
|
|
:group | %w[foo -g group::memory] | 'group::memory'
|
|
|
|
:group | %w[foo --group group::memory] | 'group::memory'
|
|
|
|
:group | %w[foo -g invalid] | nil
|
|
|
|
:group | %w[foo --group invalid] | nil
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it do
|
|
|
|
options = described_class.parse(Array(argv))
|
|
|
|
|
|
|
|
expect(options.public_send(param)).to eq(result)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'missing feature flag name' do
|
|
|
|
expect do
|
|
|
|
expect { described_class.parse(%w[--amend]) }.to output(/Feature flag name is required/).to_stdout
|
|
|
|
end.to raise_error(FeatureFlagHelpers::Abort)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'parses -h' do
|
|
|
|
expect do
|
|
|
|
expect { described_class.parse(%w[foo -h]) }.to output(/Usage:/).to_stdout
|
|
|
|
end.to raise_error(FeatureFlagHelpers::Done)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.read_type' do
|
|
|
|
let(:type) { 'development' }
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
context 'when there is only a single type defined' do
|
|
|
|
before do
|
|
|
|
stub_const('FeatureFlagOptionParser::TYPES',
|
|
|
|
development: { description: 'short' }
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns that type' do
|
2020-07-28 23:09:34 +05:30
|
|
|
expect(described_class.read_type).to eq(:development)
|
2020-10-24 23:57:45 +05:30
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
2021-01-29 00:20:46 +05:30
|
|
|
context 'when there is deprecated feature flag type' do
|
|
|
|
before do
|
|
|
|
stub_const('FeatureFlagOptionParser::TYPES',
|
|
|
|
development: { description: 'short' },
|
|
|
|
deprecated: { description: 'deprecated', deprecated: true }
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'and deprecated type is given' do
|
|
|
|
let(:type) { 'deprecated' }
|
|
|
|
|
|
|
|
it 'shows error message and retries' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(type)
|
|
|
|
expect($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
|
|
|
|
expect do
|
|
|
|
expect { described_class.read_type }.to raise_error(/EOF/)
|
|
|
|
end.to output(/Specify the feature flag type/).to_stdout
|
|
|
|
.and output(/Invalid type specified/).to_stderr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
context 'when there are many types defined' do
|
|
|
|
before do
|
|
|
|
stub_const('FeatureFlagOptionParser::TYPES',
|
|
|
|
development: { description: 'short' },
|
|
|
|
licensed: { description: 'licensed' }
|
|
|
|
)
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
it 'reads type from $stdin' do
|
2020-07-28 23:09:34 +05:30
|
|
|
expect($stdin).to receive(:gets).and_return(type)
|
|
|
|
expect do
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(described_class.read_type).to eq(:development)
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/Specify the feature flag type/).to_stdout
|
2020-10-24 23:57:45 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when invalid type is given' do
|
|
|
|
let(:type) { 'invalid' }
|
|
|
|
|
|
|
|
it 'shows error message and retries' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(type)
|
|
|
|
expect($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
|
|
|
|
expect do
|
|
|
|
expect { described_class.read_type }.to raise_error(/EOF/)
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/Specify the feature flag type/).to_stdout
|
2020-10-24 23:57:45 +05:30
|
|
|
.and output(/Invalid type specified/).to_stderr
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.read_group' do
|
|
|
|
let(:group) { 'group::memory' }
|
|
|
|
|
|
|
|
it 'reads type from $stdin' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(group)
|
|
|
|
expect do
|
|
|
|
expect(described_class.read_group).to eq('group::memory')
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/Specify the group introducing the feature flag/).to_stdout
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'invalid group given' do
|
|
|
|
let(:type) { 'invalid' }
|
|
|
|
|
|
|
|
it 'shows error message and retries' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(type)
|
|
|
|
expect($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
|
|
|
|
expect do
|
|
|
|
expect { described_class.read_group }.to raise_error(/EOF/)
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/Specify the group introducing the feature flag/).to_stdout
|
|
|
|
.and output(/The group needs to include/).to_stderr
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
describe '.read_introduced_by_url' do
|
|
|
|
let(:url) { 'https://merge-request' }
|
|
|
|
|
|
|
|
it 'reads type from $stdin' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(url)
|
|
|
|
expect do
|
|
|
|
expect(described_class.read_introduced_by_url).to eq('https://merge-request')
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/URL of the MR introducing the feature flag/).to_stdout
|
2020-10-24 23:57:45 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'empty URL given' do
|
|
|
|
let(:url) { '' }
|
|
|
|
|
|
|
|
it 'skips entry' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(url)
|
|
|
|
expect do
|
|
|
|
expect(described_class.read_introduced_by_url).to be_nil
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/URL of the MR introducing the feature flag/).to_stdout
|
2020-10-24 23:57:45 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'invalid URL given' do
|
|
|
|
let(:url) { 'invalid' }
|
|
|
|
|
|
|
|
it 'shows error message and retries' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(url)
|
|
|
|
expect($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
|
|
|
|
expect do
|
|
|
|
expect { described_class.read_introduced_by_url }.to raise_error(/EOF/)
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/URL of the MR introducing the feature flag/).to_stdout
|
2020-10-24 23:57:45 +05:30
|
|
|
.and output(/URL needs to start with/).to_stderr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.read_rollout_issue_url' do
|
2020-07-28 23:09:34 +05:30
|
|
|
let(:options) { OpenStruct.new(name: 'foo', type: :development) }
|
|
|
|
let(:url) { 'https://issue' }
|
|
|
|
|
|
|
|
it 'reads type from $stdin' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(url)
|
|
|
|
expect do
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(described_class.read_rollout_issue_url(options)).to eq('https://issue')
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/URL of the rollout issue/).to_stdout
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'invalid URL given' do
|
|
|
|
let(:type) { 'invalid' }
|
|
|
|
|
|
|
|
it 'shows error message and retries' do
|
|
|
|
expect($stdin).to receive(:gets).and_return(type)
|
|
|
|
expect($stdin).to receive(:gets).and_raise('EOF')
|
|
|
|
|
|
|
|
expect do
|
2020-10-24 23:57:45 +05:30
|
|
|
expect { described_class.read_rollout_issue_url(options) }.to raise_error(/EOF/)
|
2020-11-24 15:15:51 +05:30
|
|
|
end.to output(/URL of the rollout issue/).to_stdout
|
2020-07-28 23:09:34 +05:30
|
|
|
.and output(/URL needs to start/).to_stderr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-01-03 14:25:43 +05:30
|
|
|
|
|
|
|
describe '.read_ee_only' do
|
|
|
|
where(:type, :is_ee_only) do
|
|
|
|
:development | false
|
|
|
|
:licensed | true
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
let(:options) { OpenStruct.new(name: 'foo', type: type) }
|
|
|
|
|
|
|
|
it { expect(described_class.read_ee_only(options)).to eq(is_ee_only) }
|
|
|
|
end
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
end
|
|
|
|
end
|