summaryrefslogtreecommitdiff
path: root/spec/models/group_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/group_spec.rb')
-rw-r--r--spec/models/group_spec.rb337
1 files changed, 330 insertions, 7 deletions
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index e79b54b4674..24d09d1c035 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -25,7 +25,6 @@ RSpec.describe Group do
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
it { is_expected.to have_many(:container_repositories) }
it { is_expected.to have_many(:milestones) }
- it { is_expected.to have_many(:iterations) }
it { is_expected.to have_many(:group_deploy_keys) }
it { is_expected.to have_many(:services) }
it { is_expected.to have_one(:dependency_proxy_setting) }
@@ -65,6 +64,59 @@ RSpec.describe Group do
it { is_expected.to validate_presence_of :two_factor_grace_period }
it { is_expected.to validate_numericality_of(:two_factor_grace_period).is_greater_than_or_equal_to(0) }
+ context 'validating the parent of a group' do
+ context 'when the group has no parent' do
+ it 'allows a group to have no parent associated with it' do
+ group = build(:group)
+
+ expect(group).to be_valid
+ end
+ end
+
+ context 'when the group has a parent' do
+ it 'does not allow a group to have a namespace as its parent' do
+ group = build(:group, parent: build(:namespace))
+
+ expect(group).not_to be_valid
+ expect(group.errors[:parent_id].first).to eq('a group cannot have a user namespace as its parent')
+ end
+
+ it 'allows a group to have another group as its parent' do
+ group = build(:group, parent: build(:group))
+
+ expect(group).to be_valid
+ end
+ end
+
+ context 'when the feature flag `validate_namespace_parent_type` is disabled' do
+ before do
+ stub_feature_flags(validate_namespace_parent_type: false)
+ end
+
+ context 'when the group has no parent' do
+ it 'allows a group to have no parent associated with it' do
+ group = build(:group)
+
+ expect(group).to be_valid
+ end
+ end
+
+ context 'when the group has a parent' do
+ it 'allows a group to have a namespace as its parent' do
+ group = build(:group, parent: build(:namespace))
+
+ expect(group).to be_valid
+ end
+
+ it 'allows a group to have another group as its parent' do
+ group = build(:group, parent: build(:group))
+
+ expect(group).to be_valid
+ end
+ end
+ end
+ end
+
describe 'path validation' do
it 'rejects paths reserved on the root namespace when the group has no parent' do
group = build(:group, path: 'api')
@@ -513,6 +565,42 @@ RSpec.describe Group do
end
end
+ describe '#last_blocked_owner?' do
+ let(:blocked_user) { create(:user, :blocked) }
+
+ before do
+ group.add_user(blocked_user, GroupMember::OWNER)
+ end
+
+ it { expect(group.last_blocked_owner?(blocked_user)).to be_truthy }
+
+ context 'with another active owner' do
+ before do
+ group.add_user(create(:user), GroupMember::OWNER)
+ end
+
+ it { expect(group.last_blocked_owner?(blocked_user)).to be_falsy }
+ end
+
+ context 'with 2 blocked owners' do
+ before do
+ group.add_user(create(:user, :blocked), GroupMember::OWNER)
+ end
+
+ it { expect(group.last_blocked_owner?(blocked_user)).to be_falsy }
+ end
+
+ context 'with owners from a parent' do
+ before do
+ parent_group = create(:group)
+ create(:group_member, :owner, group: parent_group)
+ group.update(parent: parent_group)
+ end
+
+ it { expect(group.last_blocked_owner?(blocked_user)).to be_falsy }
+ end
+ end
+
describe '#lfs_enabled?' do
context 'LFS enabled globally' do
before do
@@ -729,8 +817,16 @@ RSpec.describe Group do
context 'evaluating admin access level' do
let_it_be(:admin) { create(:admin) }
- it 'returns OWNER by default' do
- expect(group.max_member_access_for_user(admin)).to eq(Gitlab::Access::OWNER)
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it 'returns OWNER by default' do
+ expect(group.max_member_access_for_user(admin)).to eq(Gitlab::Access::OWNER)
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it 'returns NO_ACCESS' do
+ expect(group.max_member_access_for_user(admin)).to eq(Gitlab::Access::NO_ACCESS)
+ end
end
it 'returns NO_ACCESS when only concrete membership should be considered' do
@@ -740,6 +836,33 @@ RSpec.describe Group do
end
end
+ describe '#direct_members' do
+ let_it_be(:group) { create(:group, :nested) }
+ let_it_be(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
+ let_it_be(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
+
+ it 'does not return members of the parent' do
+ expect(group.direct_members).not_to include(maintainer)
+ end
+
+ it 'returns the direct member of the group' do
+ expect(group.direct_members).to include(developer)
+ end
+
+ context 'group sharing' do
+ let!(:shared_group) { create(:group) }
+
+ before do
+ create(:group_group_link, shared_group: shared_group, shared_with_group: group)
+ end
+
+ it 'does not return members of the shared_with group' do
+ expect(shared_group.direct_members).not_to(
+ include(developer))
+ end
+ end
+ end
+
describe '#members_with_parents' do
let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
@@ -932,6 +1055,65 @@ RSpec.describe Group do
end
end
+ describe '#refresh_members_authorized_projects' do
+ let_it_be(:group) { create(:group, :nested) }
+ let_it_be(:parent_group_user) { create(:user) }
+ let_it_be(:group_user) { create(:user) }
+
+ before do
+ group.parent.add_maintainer(parent_group_user)
+ group.add_developer(group_user)
+ end
+
+ context 'users for which authorizations refresh is executed' do
+ it 'processes authorizations refresh for all members of the group' do
+ expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id, parent_group_user.id)).and_call_original
+
+ group.refresh_members_authorized_projects
+ end
+
+ context 'when explicitly specified to run only for direct members' do
+ it 'processes authorizations refresh only for direct members of the group' do
+ expect(UserProjectAccessChangedService).to receive(:new).with(contain_exactly(group_user.id)).and_call_original
+
+ group.refresh_members_authorized_projects(direct_members_only: true)
+ end
+ end
+ end
+ end
+
+ describe '#users_ids_of_direct_members' do
+ let_it_be(:group) { create(:group, :nested) }
+ let_it_be(:parent_group_user) { create(:user) }
+ let_it_be(:group_user) { create(:user) }
+
+ before do
+ group.parent.add_maintainer(parent_group_user)
+ group.add_developer(group_user)
+ end
+
+ it 'does not return user ids of the members of the parent' do
+ expect(group.users_ids_of_direct_members).not_to include(parent_group_user.id)
+ end
+
+ it 'returns the user ids of the direct member of the group' do
+ expect(group.users_ids_of_direct_members).to include(group_user.id)
+ end
+
+ context 'group sharing' do
+ let!(:shared_group) { create(:group) }
+
+ before do
+ create(:group_group_link, shared_group: shared_group, shared_with_group: group)
+ end
+
+ it 'does not return the user ids of members of the shared_with group' do
+ expect(shared_group.users_ids_of_direct_members).not_to(
+ include(group_user.id))
+ end
+ end
+ end
+
describe '#user_ids_for_project_authorizations' do
it 'returns the user IDs for which to refresh authorizations' do
maintainer = create(:user)
@@ -959,6 +1141,29 @@ RSpec.describe Group do
include(group_user.id))
end
end
+
+ context 'distinct user ids' do
+ let_it_be(:subgroup) { create(:group, :nested) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:shared_with_group) { create(:group) }
+ let_it_be(:other_subgroup_user) { create(:user) }
+
+ before do
+ create(:group_group_link, shared_group: subgroup, shared_with_group: shared_with_group)
+ subgroup.add_maintainer(other_subgroup_user)
+
+ # `user` is added as a direct member of the parent group, the subgroup
+ # and another group shared with the subgroup.
+ subgroup.parent.add_maintainer(user)
+ subgroup.add_developer(user)
+ shared_with_group.add_guest(user)
+ end
+
+ it 'returns only distinct user ids of users for which to refresh authorizations' do
+ expect(subgroup.user_ids_for_project_authorizations).to(
+ contain_exactly(user.id, other_subgroup_user.id))
+ end
+ end
end
describe '#update_two_factor_requirement' do
@@ -1149,9 +1354,10 @@ RSpec.describe Group do
describe '#ci_variables_for' do
let(:project) { create(:project, group: group) }
+ let(:environment_scope) { '*' }
let!(:ci_variable) do
- create(:ci_group_variable, value: 'secret', group: group)
+ create(:ci_group_variable, value: 'secret', group: group, environment_scope: environment_scope)
end
let!(:protected_variable) do
@@ -1160,13 +1366,16 @@ RSpec.describe Group do
subject { group.ci_variables_for('ref', project) }
- it 'memoizes the result by ref', :request_store do
+ it 'memoizes the result by ref and environment', :request_store do
+ scoped_variable = create(:ci_group_variable, value: 'secret', group: group, environment_scope: 'scoped')
+
expect(project).to receive(:protected_for?).with('ref').once.and_return(true)
- expect(project).to receive(:protected_for?).with('other').once.and_return(false)
+ expect(project).to receive(:protected_for?).with('other').twice.and_return(false)
2.times do
- expect(group.ci_variables_for('ref', project)).to contain_exactly(ci_variable, protected_variable)
+ expect(group.ci_variables_for('ref', project, environment: 'production')).to contain_exactly(ci_variable, protected_variable)
expect(group.ci_variables_for('other', project)).to contain_exactly(ci_variable)
+ expect(group.ci_variables_for('other', project, environment: 'scoped')).to contain_exactly(ci_variable, scoped_variable)
end
end
@@ -1203,6 +1412,120 @@ RSpec.describe Group do
it_behaves_like 'ref is protected'
end
+ context 'when environment name is specified' do
+ let(:environment) { 'review/name' }
+
+ subject do
+ group.ci_variables_for('ref', project, environment: environment)
+ end
+
+ context 'when environment scope is exactly matched' do
+ let(:environment_scope) { 'review/name' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ let(:environment_scope) { 'review/*' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope does not match' do
+ let(:environment_scope) { 'review/*/special' }
+
+ it { is_expected.not_to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope has _' do
+ let(:environment_scope) { '*_*' }
+
+ it 'does not treat it as wildcard' do
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains underscore' do
+ let(:environment) { 'foo_bar/test' }
+ let(:environment_scope) { 'foo_bar/*' }
+
+ it 'matches literally for _' do
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ ci_variable.update_attribute(:environment_scope, '*%*')
+
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains a percent' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for %' do
+ ci_variable.update(environment_scope: 'foo%bar/*')
+
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ context 'when variables with the same name have different environment scopes' do
+ let!(:partially_matched_variable) do
+ create(:ci_group_variable,
+ key: ci_variable.key,
+ value: 'partial',
+ environment_scope: 'review/*',
+ group: group)
+ end
+
+ let!(:perfectly_matched_variable) do
+ create(:ci_group_variable,
+ key: ci_variable.key,
+ value: 'prefect',
+ environment_scope: 'review/name',
+ group: group)
+ end
+
+ it 'puts variables matching environment scope more in the end' do
+ is_expected.to eq(
+ [ci_variable,
+ partially_matched_variable,
+ perfectly_matched_variable])
+ end
+ end
+
+ context 'when :scoped_group_variables feature flag is disabled' do
+ before do
+ stub_feature_flags(scoped_group_variables: false)
+ end
+
+ context 'when environment scope is exactly matched' do
+ let(:environment_scope) { 'review/name' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope is partially matched' do
+ let(:environment_scope) { 'review/*' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope does not match' do
+ let(:environment_scope) { 'review/*/special' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+ end
+ end
+
context 'when group has children' do
let(:group_child) { create(:group, parent: group) }
let(:group_child_2) { create(:group, parent: group_child) }