2019-07-07 11:18:12 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-09-02 18:07:02 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
RSpec.describe ProtectedBranch do
|
2016-08-24 12:49:21 +05:30
|
|
|
subject { build_stubbed(:protected_branch) }
|
|
|
|
|
2014-09-02 18:07:02 +05:30
|
|
|
describe 'Associations' do
|
2015-04-26 12:48:37 +05:30
|
|
|
it { is_expected.to belong_to(:project) }
|
2014-09-02 18:07:02 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe 'Validation' do
|
2015-04-26 12:48:37 +05:30
|
|
|
it { is_expected.to validate_presence_of(:project) }
|
|
|
|
it { is_expected.to validate_presence_of(:name) }
|
2014-09-02 18:07:02 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
|
|
|
|
describe "#matches?" do
|
|
|
|
context "when the protected branch setting is not a wildcard" do
|
|
|
|
let(:protected_branch) { build(:protected_branch, name: "production/some-branch") }
|
|
|
|
|
|
|
|
it "returns true for branch names that are an exact match" do
|
|
|
|
expect(protected_branch.matches?("production/some-branch")).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false for branch names that are not an exact match" do
|
|
|
|
expect(protected_branch.matches?("staging/some-branch")).to be false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the protected branch name contains wildcard(s)" do
|
|
|
|
context "when there is a single '*'" do
|
|
|
|
let(:protected_branch) { build(:protected_branch, name: "production/*") }
|
|
|
|
|
|
|
|
it "returns true for branch names matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("production/some-branch")).to be true
|
|
|
|
expect(protected_branch.matches?("production/")).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false for branch names not matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("staging/some-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("production")).to be false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the wildcard contains regex symbols other than a '*'" do
|
|
|
|
let(:protected_branch) { build(:protected_branch, name: "pro.duc.tion/*") }
|
|
|
|
|
|
|
|
it "returns true for branch names matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("pro.duc.tion/some-branch")).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false for branch names not matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("production/some-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("proXducYtion/some-branch")).to be false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when there are '*'s at either end" do
|
|
|
|
let(:protected_branch) { build(:protected_branch, name: "*/production/*") }
|
|
|
|
|
|
|
|
it "returns true for branch names matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("gitlab/production/some-branch")).to be true
|
|
|
|
expect(protected_branch.matches?("/production/some-branch")).to be true
|
|
|
|
expect(protected_branch.matches?("gitlab/production/")).to be true
|
|
|
|
expect(protected_branch.matches?("/production/")).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false for branch names not matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("gitlabproductionsome-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("production/some-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("gitlab/production")).to be false
|
|
|
|
expect(protected_branch.matches?("production")).to be false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when there are arbitrarily placed '*'s" do
|
|
|
|
let(:protected_branch) { build(:protected_branch, name: "pro*duction/*/gitlab/*") }
|
|
|
|
|
|
|
|
it "returns true for branch names matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("production/some-branch/gitlab/second-branch")).to be true
|
|
|
|
expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/second-branch")).to be true
|
|
|
|
expect(protected_branch.matches?("proXYZduction/gitlab/gitlab/gitlab")).to be true
|
|
|
|
expect(protected_branch.matches?("proXYZduction//gitlab/")).to be true
|
|
|
|
expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/")).to be true
|
|
|
|
expect(protected_branch.matches?("proXYZduction//gitlab/some-branch")).to be true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns false for branch names not matching the wildcard" do
|
|
|
|
expect(protected_branch.matches?("production/some-branch/not-gitlab/second-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("prodXYZuction/some-branch/gitlab/second-branch")).to be false
|
|
|
|
expect(protected_branch.matches?("proXYZduction/gitlab/some-branch/gitlab")).to be false
|
|
|
|
expect(protected_branch.matches?("proXYZduction/gitlab//")).to be false
|
|
|
|
expect(protected_branch.matches?("proXYZduction/gitlab/")).to be false
|
|
|
|
expect(protected_branch.matches?("proXYZduction//some-branch/gitlab")).to be false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "#matching" do
|
|
|
|
context "for direct matches" do
|
|
|
|
it "returns a list of protected branches matching the given branch name" do
|
|
|
|
production = create(:protected_branch, name: "production")
|
|
|
|
staging = create(:protected_branch, name: "staging")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.matching("production")).to include(production)
|
|
|
|
expect(described_class.matching("production")).not_to include(staging)
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts a list of protected branches to search from, so as to avoid a DB call" do
|
|
|
|
production = build(:protected_branch, name: "production")
|
|
|
|
staging = build(:protected_branch, name: "staging")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.matching("production")).to be_empty
|
|
|
|
expect(described_class.matching("production", protected_refs: [production, staging])).to include(production)
|
|
|
|
expect(described_class.matching("production", protected_refs: [production, staging])).not_to include(staging)
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "for wildcard matches" do
|
|
|
|
it "returns a list of protected branches matching the given branch name" do
|
|
|
|
production = create(:protected_branch, name: "production/*")
|
|
|
|
staging = create(:protected_branch, name: "staging/*")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.matching("production/some-branch")).to include(production)
|
|
|
|
expect(described_class.matching("production/some-branch")).not_to include(staging)
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts a list of protected branches to search from, so as to avoid a DB call" do
|
|
|
|
production = build(:protected_branch, name: "production/*")
|
|
|
|
staging = build(:protected_branch, name: "staging/*")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.matching("production/some-branch")).to be_empty
|
|
|
|
expect(described_class.matching("production/some-branch", protected_refs: [production, staging])).to include(production)
|
|
|
|
expect(described_class.matching("production/some-branch", protected_refs: [production, staging])).not_to include(staging)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#protected?' do
|
|
|
|
context 'existing project' do
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
|
|
|
|
it 'returns true when the branch matches a protected branch via direct match' do
|
|
|
|
create(:protected_branch, project: project, name: "foo")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.protected?(project, 'foo')).to eq(true)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true when the branch matches a protected branch via wildcard match' do
|
|
|
|
create(:protected_branch, project: project, name: "production/*")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.protected?(project, 'production/some-branch')).to eq(true)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when the branch does not match a protected branch via direct match' do
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.protected?(project, 'foo')).to eq(false)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when the branch does not match a protected branch via wildcard match' do
|
|
|
|
create(:protected_branch, project: project, name: "production/*")
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
expect(described_class.protected?(project, 'staging/some-branch')).to eq(false)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2021-11-11 11:23:49 +05:30
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
it 'returns false when branch name is nil' do
|
|
|
|
expect(described_class.protected?(project, nil)).to eq(false)
|
|
|
|
end
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
context 'with caching', :use_clean_rails_memory_store_caching do
|
|
|
|
let_it_be(:project) { create(:project, :repository) }
|
2021-11-18 22:05:49 +05:30
|
|
|
let_it_be(:protected_branch) { create(:protected_branch, project: project, name: "“jawn”") }
|
2021-11-11 11:23:49 +05:30
|
|
|
|
|
|
|
before do
|
2021-11-18 22:05:49 +05:30
|
|
|
allow(described_class).to receive(:matching).with(protected_branch.name, protected_refs: anything).once.and_call_original
|
|
|
|
|
2021-11-11 11:23:49 +05:30
|
|
|
# the original call works and warms the cache
|
2021-11-18 22:05:49 +05:30
|
|
|
described_class.protected?(project, protected_branch.name)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'correctly invalidates a cache' do
|
2021-11-18 22:05:49 +05:30
|
|
|
expect(described_class).to receive(:matching).with(protected_branch.name, protected_refs: anything).once.and_call_original
|
2021-11-11 11:23:49 +05:30
|
|
|
|
|
|
|
create(:protected_branch, project: project, name: "bar")
|
|
|
|
# the cache is invalidated because the project has been "updated"
|
2021-11-18 22:05:49 +05:30
|
|
|
expect(described_class.protected?(project, protected_branch.name)).to eq(true)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'correctly uses the cached version' do
|
|
|
|
expect(described_class).not_to receive(:matching)
|
2021-11-18 22:05:49 +05:30
|
|
|
expect(described_class.protected?(project, protected_branch.name)).to eq(true)
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
2022-08-13 15:12:31 +05:30
|
|
|
|
|
|
|
it 'sets expires_in for a cache key' do
|
|
|
|
cache_key = described_class.protected_ref_cache_key(project, protected_branch.name)
|
|
|
|
|
|
|
|
expect(Rails.cache).to receive(:fetch).with(cache_key, expires_in: 1.hour)
|
|
|
|
|
|
|
|
described_class.protected?(project, protected_branch.name)
|
|
|
|
end
|
2021-11-11 11:23:49 +05:30
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
context 'new project' do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
let(:project) { create(:project) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
context 'when the group has set their own default_branch_protection level' do
|
|
|
|
where(:default_branch_protection_level, :result) do
|
|
|
|
Gitlab::Access::PROTECTION_NONE | false
|
|
|
|
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
|
|
|
|
Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
|
|
|
|
Gitlab::Access::PROTECTION_FULL | true
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
with_them do
|
|
|
|
it 'protects the default branch based on the default branch protection setting of the group' do
|
|
|
|
expect(project.namespace).to receive(:default_branch_protection).and_return(default_branch_protection_level)
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
expect(described_class.protected?(project, 'master')).to eq(result)
|
|
|
|
end
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
context 'when the group has not set their own default_branch_protection level' do
|
|
|
|
where(:default_branch_protection_level, :result) do
|
|
|
|
Gitlab::Access::PROTECTION_NONE | false
|
|
|
|
Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
|
|
|
|
Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
|
|
|
|
Gitlab::Access::PROTECTION_FULL | true
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
with_them do
|
|
|
|
before do
|
|
|
|
stub_application_setting(default_branch_protection: default_branch_protection_level)
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2020-04-08 14:13:33 +05:30
|
|
|
it 'protects the default branch based on the instance level default branch protection setting' do
|
|
|
|
expect(described_class.protected?(project, 'master')).to eq(result)
|
|
|
|
end
|
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-07-07 11:18:12 +05:30
|
|
|
|
2021-04-17 20:07:23 +05:30
|
|
|
describe "#allow_force_push?" do
|
|
|
|
context "when the attr allow_force_push is true" do
|
|
|
|
let(:subject_branch) { create(:protected_branch, allow_force_push: true, name: "foo") }
|
|
|
|
|
|
|
|
it "returns true" do
|
|
|
|
project = subject_branch.project
|
|
|
|
|
|
|
|
expect(described_class.allow_force_push?(project, "foo")).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the attr allow_force_push is false" do
|
|
|
|
let(:subject_branch) { create(:protected_branch, allow_force_push: false, name: "foo") }
|
|
|
|
|
|
|
|
it "returns false" do
|
|
|
|
project = subject_branch.project
|
|
|
|
|
|
|
|
expect(described_class.allow_force_push?(project, "foo")).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
describe '#any_protected?' do
|
|
|
|
context 'existing project' do
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
|
|
|
|
it 'returns true when any of the branch names match a protected branch via direct match' do
|
|
|
|
create(:protected_branch, project: project, name: 'foo')
|
|
|
|
|
|
|
|
expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true when any of the branch matches a protected branch via wildcard match' do
|
|
|
|
create(:protected_branch, project: project, name: 'production/*')
|
|
|
|
|
|
|
|
expect(described_class.any_protected?(project, ['foo', 'production/some-branch'])).to eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when none of branches does not match a protected branch via direct match' do
|
|
|
|
expect(described_class.any_protected?(project, ['foo'])).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when none of the branches does not match a protected branch via wildcard match' do
|
|
|
|
create(:protected_branch, project: project, name: 'production/*')
|
|
|
|
|
|
|
|
expect(described_class.any_protected?(project, ['staging/some-branch'])).to eq(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-03-13 15:44:24 +05:30
|
|
|
|
|
|
|
describe '.by_name' do
|
|
|
|
let!(:protected_branch) { create(:protected_branch, name: 'master') }
|
|
|
|
let!(:another_protected_branch) { create(:protected_branch, name: 'stable') }
|
|
|
|
|
|
|
|
it 'returns protected branches with a matching name' do
|
|
|
|
expect(described_class.by_name(protected_branch.name))
|
|
|
|
.to eq([protected_branch])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns protected branches with a partially matching name' do
|
|
|
|
expect(described_class.by_name(protected_branch.name[0..2]))
|
|
|
|
.to eq([protected_branch])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns protected branches with a matching name regardless of the casing' do
|
|
|
|
expect(described_class.by_name(protected_branch.name.upcase))
|
|
|
|
.to eq([protected_branch])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nothing when nothing matches' do
|
|
|
|
expect(described_class.by_name('unknown')).to be_empty
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'return nothing when query is blank' do
|
|
|
|
expect(described_class.by_name('')).to be_empty
|
|
|
|
end
|
|
|
|
end
|
2021-11-18 22:05:49 +05:30
|
|
|
|
|
|
|
describe '.get_ids_by_name' do
|
|
|
|
let(:branch_name) { 'branch_name' }
|
|
|
|
let!(:protected_branch) { create(:protected_branch, name: branch_name) }
|
|
|
|
let(:branch_id) { protected_branch.id }
|
|
|
|
|
|
|
|
it 'returns the id for each protected branch matching name' do
|
|
|
|
expect(described_class.get_ids_by_name([branch_name]))
|
|
|
|
.to match_array([branch_id])
|
|
|
|
end
|
|
|
|
end
|
2022-07-16 23:28:13 +05:30
|
|
|
|
|
|
|
describe '.downcase_humanized_name' do
|
|
|
|
it 'returns downcase humanized name' do
|
|
|
|
expect(described_class.downcase_humanized_name).to eq 'protected branch'
|
|
|
|
end
|
|
|
|
end
|
2014-09-02 18:07:02 +05:30
|
|
|
end
|