ab94a5a537
Currently if a project is inside a nested group and a user doesn't have specific permissions for that group but does have permissions on a parent group the `GET /projects/:id` API call will return the following permissions: ```json permissions: { project_access: null, group_access: null } ``` It could also happen that the group specific permissions are of lower level than the ones the user has in parent groups. This patch makes it so that the permission returned for `group_access` is the highest from amongst the hierarchy, which is (ostensibly) the information that the API user is interested in for that field.
813 lines
26 KiB
Ruby
813 lines
26 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Group do
|
|
let!(:group) { create(:group, :access_requestable) }
|
|
|
|
describe 'associations' do
|
|
it { is_expected.to have_many :projects }
|
|
it { is_expected.to have_many(:group_members).dependent(:destroy) }
|
|
it { is_expected.to have_many(:users).through(:group_members) }
|
|
it { is_expected.to have_many(:owners).through(:group_members) }
|
|
it { is_expected.to have_many(:requesters).dependent(:destroy) }
|
|
it { is_expected.to have_many(:members_and_requesters) }
|
|
it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
|
|
it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
|
|
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
|
|
it { is_expected.to have_many(:labels).class_name('GroupLabel') }
|
|
it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
|
|
it { is_expected.to have_many(:uploads) }
|
|
it { is_expected.to have_one(:chat_team) }
|
|
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
|
|
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
|
|
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
|
|
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
|
|
|
|
describe '#members & #requesters' do
|
|
let(:requester) { create(:user) }
|
|
let(:developer) { create(:user) }
|
|
before do
|
|
group.request_access(requester)
|
|
group.add_developer(developer)
|
|
end
|
|
|
|
it_behaves_like 'members and requesters associations' do
|
|
let(:namespace) { group }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'modules' do
|
|
subject { described_class }
|
|
|
|
it { is_expected.to include_module(Referable) }
|
|
end
|
|
|
|
describe 'validations' do
|
|
it { is_expected.to validate_presence_of :name }
|
|
it { is_expected.to validate_presence_of :path }
|
|
it { is_expected.not_to validate_presence_of :owner }
|
|
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) }
|
|
|
|
describe 'path validation' do
|
|
it 'rejects paths reserved on the root namespace when the group has no parent' do
|
|
group = build(:group, path: 'api')
|
|
|
|
expect(group).not_to be_valid
|
|
end
|
|
|
|
it 'allows root paths when the group has a parent' do
|
|
group = build(:group, path: 'api', parent: create(:group))
|
|
|
|
expect(group).to be_valid
|
|
end
|
|
|
|
it 'rejects any wildcard paths when not a top level group' do
|
|
group = build(:group, path: 'tree', parent: create(:group))
|
|
|
|
expect(group).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe '#notification_settings', :nested_groups do
|
|
let(:user) { create(:user) }
|
|
let(:group) { create(:group) }
|
|
let(:sub_group) { create(:group, parent_id: group.id) }
|
|
|
|
before do
|
|
group.add_developer(user)
|
|
sub_group.add_maintainer(user)
|
|
end
|
|
|
|
it 'also gets notification settings from parent groups' do
|
|
expect(sub_group.notification_settings.size).to eq(2)
|
|
expect(sub_group.notification_settings).to include(group.notification_settings.first)
|
|
end
|
|
|
|
context 'when sub group is deleted' do
|
|
it 'does not delete parent notification settings' do
|
|
expect do
|
|
sub_group.destroy
|
|
end.to change { NotificationSetting.count }.by(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#visibility_level_allowed_by_parent' do
|
|
let(:parent) { create(:group, :internal) }
|
|
let(:sub_group) { build(:group, parent_id: parent.id) }
|
|
|
|
context 'without a parent' do
|
|
it 'is valid' do
|
|
sub_group.parent_id = nil
|
|
|
|
expect(sub_group).to be_valid
|
|
end
|
|
end
|
|
|
|
context 'with a parent' do
|
|
context 'when visibility of sub group is greater than the parent' do
|
|
it 'is invalid' do
|
|
sub_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
|
|
|
|
expect(sub_group).to be_invalid
|
|
end
|
|
end
|
|
|
|
context 'when visibility of sub group is lower or equal to the parent' do
|
|
[Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE].each do |level|
|
|
it 'is valid' do
|
|
sub_group.visibility_level = level
|
|
|
|
expect(sub_group).to be_valid
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#visibility_level_allowed_by_projects' do
|
|
let!(:internal_group) { create(:group, :internal) }
|
|
let!(:internal_project) { create(:project, :internal, group: internal_group) }
|
|
|
|
context 'when group has a lower visibility' do
|
|
it 'is invalid' do
|
|
internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
|
|
|
|
expect(internal_group).to be_invalid
|
|
expect(internal_group.errors[:visibility_level]).to include('private is not allowed since this group contains projects with higher visibility.')
|
|
end
|
|
end
|
|
|
|
context 'when group has a higher visibility' do
|
|
it 'is valid' do
|
|
internal_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
|
|
|
|
expect(internal_group).to be_valid
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#visibility_level_allowed_by_sub_groups' do
|
|
let!(:internal_group) { create(:group, :internal) }
|
|
let!(:internal_sub_group) { create(:group, :internal, parent: internal_group) }
|
|
|
|
context 'when parent group has a lower visibility' do
|
|
it 'is invalid' do
|
|
internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
|
|
|
|
expect(internal_group).to be_invalid
|
|
expect(internal_group.errors[:visibility_level]).to include('private is not allowed since there are sub-groups with higher visibility.')
|
|
end
|
|
end
|
|
|
|
context 'when parent group has a higher visibility' do
|
|
it 'is valid' do
|
|
internal_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
|
|
|
|
expect(internal_group).to be_valid
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.public_or_visible_to_user' do
|
|
let!(:private_group) { create(:group, :private) }
|
|
let!(:internal_group) { create(:group, :internal) }
|
|
|
|
subject { described_class.public_or_visible_to_user(user) }
|
|
|
|
context 'when user is nil' do
|
|
let!(:user) { nil }
|
|
|
|
it { is_expected.to match_array([group]) }
|
|
end
|
|
|
|
context 'when user' do
|
|
let!(:user) { create(:user) }
|
|
|
|
context 'when user does not have access to any private group' do
|
|
it { is_expected.to match_array([internal_group, group]) }
|
|
end
|
|
|
|
context 'when user is a member of private group' do
|
|
before do
|
|
private_group.add_user(user, Gitlab::Access::DEVELOPER)
|
|
end
|
|
|
|
it { is_expected.to match_array([private_group, internal_group, group]) }
|
|
end
|
|
|
|
context 'when user is a member of private subgroup', :postgresql do
|
|
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
|
|
|
|
before do
|
|
private_subgroup.add_user(user, Gitlab::Access::DEVELOPER)
|
|
end
|
|
|
|
it { is_expected.to match_array([private_subgroup, internal_group, group]) }
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'scopes' do
|
|
let!(:private_group) { create(:group, :private) }
|
|
let!(:internal_group) { create(:group, :internal) }
|
|
|
|
describe 'public_only' do
|
|
subject { described_class.public_only.to_a }
|
|
|
|
it { is_expected.to eq([group]) }
|
|
end
|
|
|
|
describe 'public_and_internal_only' do
|
|
subject { described_class.public_and_internal_only.to_a }
|
|
|
|
it { is_expected.to match_array([group, internal_group]) }
|
|
end
|
|
|
|
describe 'non_public_only' do
|
|
subject { described_class.non_public_only.to_a }
|
|
|
|
it { is_expected.to match_array([private_group, internal_group]) }
|
|
end
|
|
end
|
|
|
|
describe '#to_reference' do
|
|
it 'returns a String reference to the object' do
|
|
expect(group.to_reference).to eq "@#{group.name}"
|
|
end
|
|
end
|
|
|
|
describe '#users' do
|
|
it { expect(group.users).to eq(group.owners) }
|
|
end
|
|
|
|
describe '#human_name' do
|
|
it { expect(group.human_name).to eq(group.name) }
|
|
end
|
|
|
|
describe '#add_user' do
|
|
let(:user) { create(:user) }
|
|
|
|
before do
|
|
group.add_user(user, GroupMember::MAINTAINER)
|
|
end
|
|
|
|
it { expect(group.group_members.maintainers.map(&:user)).to include(user) }
|
|
end
|
|
|
|
describe '#add_users' do
|
|
let(:user) { create(:user) }
|
|
|
|
before do
|
|
group.add_users([user.id], GroupMember::GUEST)
|
|
end
|
|
|
|
it "updates the group permission" do
|
|
expect(group.group_members.guests.map(&:user)).to include(user)
|
|
group.add_users([user.id], GroupMember::DEVELOPER)
|
|
expect(group.group_members.developers.map(&:user)).to include(user)
|
|
expect(group.group_members.guests.map(&:user)).not_to include(user)
|
|
end
|
|
end
|
|
|
|
describe '#avatar_type' do
|
|
let(:user) { create(:user) }
|
|
|
|
before do
|
|
group.add_user(user, GroupMember::MAINTAINER)
|
|
end
|
|
|
|
it "is true if avatar is image" do
|
|
group.update_attribute(:avatar, 'uploads/avatar.png')
|
|
expect(group.avatar_type).to be_truthy
|
|
end
|
|
|
|
it "is false if avatar is html page" do
|
|
group.update_attribute(:avatar, 'uploads/avatar.html')
|
|
expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico"])
|
|
end
|
|
end
|
|
|
|
describe '#avatar_url' do
|
|
let!(:group) { create(:group, :access_requestable, :with_avatar) }
|
|
let(:user) { create(:user) }
|
|
|
|
context 'when avatar file is uploaded' do
|
|
before do
|
|
group.add_maintainer(user)
|
|
end
|
|
|
|
it 'shows correct avatar url' do
|
|
expect(group.avatar_url).to eq(group.avatar.url)
|
|
expect(group.avatar_url(only_path: false)).to eq([Gitlab.config.gitlab.url, group.avatar.url].join)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.search' do
|
|
it 'returns groups with a matching name' do
|
|
expect(described_class.search(group.name)).to eq([group])
|
|
end
|
|
|
|
it 'returns groups with a partially matching name' do
|
|
expect(described_class.search(group.name[0..2])).to eq([group])
|
|
end
|
|
|
|
it 'returns groups with a matching name regardless of the casing' do
|
|
expect(described_class.search(group.name.upcase)).to eq([group])
|
|
end
|
|
|
|
it 'returns groups with a matching path' do
|
|
expect(described_class.search(group.path)).to eq([group])
|
|
end
|
|
|
|
it 'returns groups with a partially matching path' do
|
|
expect(described_class.search(group.path[0..2])).to eq([group])
|
|
end
|
|
|
|
it 'returns groups with a matching path regardless of the casing' do
|
|
expect(described_class.search(group.path.upcase)).to eq([group])
|
|
end
|
|
end
|
|
|
|
describe '#has_owner?' do
|
|
before do
|
|
@members = setup_group_members(group)
|
|
create(:group_member, :invited, :owner, group: group)
|
|
end
|
|
|
|
it { expect(group.has_owner?(@members[:owner])).to be_truthy }
|
|
it { expect(group.has_owner?(@members[:maintainer])).to be_falsey }
|
|
it { expect(group.has_owner?(@members[:developer])).to be_falsey }
|
|
it { expect(group.has_owner?(@members[:reporter])).to be_falsey }
|
|
it { expect(group.has_owner?(@members[:guest])).to be_falsey }
|
|
it { expect(group.has_owner?(@members[:requester])).to be_falsey }
|
|
it { expect(group.has_owner?(nil)).to be_falsey }
|
|
end
|
|
|
|
describe '#has_maintainer?' do
|
|
before do
|
|
@members = setup_group_members(group)
|
|
create(:group_member, :invited, :maintainer, group: group)
|
|
end
|
|
|
|
it { expect(group.has_maintainer?(@members[:owner])).to be_falsey }
|
|
it { expect(group.has_maintainer?(@members[:maintainer])).to be_truthy }
|
|
it { expect(group.has_maintainer?(@members[:developer])).to be_falsey }
|
|
it { expect(group.has_maintainer?(@members[:reporter])).to be_falsey }
|
|
it { expect(group.has_maintainer?(@members[:guest])).to be_falsey }
|
|
it { expect(group.has_maintainer?(@members[:requester])).to be_falsey }
|
|
it { expect(group.has_maintainer?(nil)).to be_falsey }
|
|
end
|
|
|
|
describe '#lfs_enabled?' do
|
|
context 'LFS enabled globally' do
|
|
before do
|
|
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
|
|
end
|
|
|
|
it 'returns true when nothing is set' do
|
|
expect(group.lfs_enabled?).to be_truthy
|
|
end
|
|
|
|
it 'returns false when set to false' do
|
|
group.update_attribute(:lfs_enabled, false)
|
|
|
|
expect(group.lfs_enabled?).to be_falsey
|
|
end
|
|
|
|
it 'returns true when set to true' do
|
|
group.update_attribute(:lfs_enabled, true)
|
|
|
|
expect(group.lfs_enabled?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context 'LFS disabled globally' do
|
|
before do
|
|
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
|
|
end
|
|
|
|
it 'returns false when nothing is set' do
|
|
expect(group.lfs_enabled?).to be_falsey
|
|
end
|
|
|
|
it 'returns false when set to false' do
|
|
group.update_attribute(:lfs_enabled, false)
|
|
|
|
expect(group.lfs_enabled?).to be_falsey
|
|
end
|
|
|
|
it 'returns false when set to true' do
|
|
group.update_attribute(:lfs_enabled, true)
|
|
|
|
expect(group.lfs_enabled?).to be_falsey
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#owners' do
|
|
let(:owner) { create(:user) }
|
|
let(:developer) { create(:user) }
|
|
|
|
it 'returns the owners of a Group' do
|
|
group.add_owner(owner)
|
|
group.add_developer(developer)
|
|
|
|
expect(group.owners).to eq([owner])
|
|
end
|
|
end
|
|
|
|
def setup_group_members(group)
|
|
members = {
|
|
owner: create(:user),
|
|
maintainer: create(:user),
|
|
developer: create(:user),
|
|
reporter: create(:user),
|
|
guest: create(:user),
|
|
requester: create(:user)
|
|
}
|
|
|
|
group.add_user(members[:owner], GroupMember::OWNER)
|
|
group.add_user(members[:maintainer], GroupMember::MAINTAINER)
|
|
group.add_user(members[:developer], GroupMember::DEVELOPER)
|
|
group.add_user(members[:reporter], GroupMember::REPORTER)
|
|
group.add_user(members[:guest], GroupMember::GUEST)
|
|
group.request_access(members[:requester])
|
|
|
|
members
|
|
end
|
|
|
|
describe '#web_url' do
|
|
it 'returns the canonical URL' do
|
|
expect(group.web_url).to include("groups/#{group.name}")
|
|
end
|
|
|
|
context 'nested group' do
|
|
let(:nested_group) { create(:group, :nested) }
|
|
|
|
it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") }
|
|
end
|
|
end
|
|
|
|
describe 'nested group' do
|
|
subject { build(:group, :nested) }
|
|
|
|
it { is_expected.to be_valid }
|
|
it { expect(subject.parent).to be_kind_of(described_class) }
|
|
end
|
|
|
|
describe '#members_with_parents', :nested_groups do
|
|
let!(:group) { create(:group, :nested) }
|
|
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
|
|
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
|
|
|
it 'returns parents members' do
|
|
expect(group.members_with_parents).to include(developer)
|
|
expect(group.members_with_parents).to include(maintainer)
|
|
end
|
|
end
|
|
|
|
describe '#direct_and_indirect_members', :nested_groups do
|
|
let!(:group) { create(:group, :nested) }
|
|
let!(:sub_group) { create(:group, parent: group) }
|
|
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
|
|
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
|
let!(:other_developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
|
|
|
|
it 'returns parents members' do
|
|
expect(group.direct_and_indirect_members).to include(developer)
|
|
expect(group.direct_and_indirect_members).to include(maintainer)
|
|
end
|
|
|
|
it 'returns descendant members' do
|
|
expect(group.direct_and_indirect_members).to include(other_developer)
|
|
end
|
|
end
|
|
|
|
describe '#users_with_descendants', :nested_groups do
|
|
let(:user_a) { create(:user) }
|
|
let(:user_b) { create(:user) }
|
|
|
|
let(:group) { create(:group) }
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:deep_nested_group) { create(:group, parent: nested_group) }
|
|
|
|
it 'returns member users on every nest level without duplication' do
|
|
group.add_developer(user_a)
|
|
nested_group.add_developer(user_b)
|
|
deep_nested_group.add_maintainer(user_a)
|
|
|
|
expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
|
|
expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
|
|
expect(deep_nested_group.users_with_descendants).to contain_exactly(user_a)
|
|
end
|
|
end
|
|
|
|
describe '#direct_and_indirect_users', :nested_groups do
|
|
let(:user_a) { create(:user) }
|
|
let(:user_b) { create(:user) }
|
|
let(:user_c) { create(:user) }
|
|
let(:user_d) { create(:user) }
|
|
|
|
let(:group) { create(:group) }
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:deep_nested_group) { create(:group, parent: nested_group) }
|
|
let(:project) { create(:project, namespace: group) }
|
|
|
|
before do
|
|
group.add_developer(user_a)
|
|
group.add_developer(user_c)
|
|
nested_group.add_developer(user_b)
|
|
deep_nested_group.add_developer(user_a)
|
|
project.add_developer(user_d)
|
|
end
|
|
|
|
it 'returns member users on every nest level without duplication' do
|
|
expect(group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c, user_d)
|
|
expect(nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
|
|
expect(deep_nested_group.direct_and_indirect_users).to contain_exactly(user_a, user_b, user_c)
|
|
end
|
|
|
|
it 'does not return members of projects belonging to ancestor groups' do
|
|
expect(nested_group.direct_and_indirect_users).not_to include(user_d)
|
|
end
|
|
end
|
|
|
|
describe '#project_users_with_descendants', :nested_groups do
|
|
let(:user_a) { create(:user) }
|
|
let(:user_b) { create(:user) }
|
|
let(:user_c) { create(:user) }
|
|
|
|
let(:group) { create(:group) }
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:deep_nested_group) { create(:group, parent: nested_group) }
|
|
let(:project_a) { create(:project, namespace: group) }
|
|
let(:project_b) { create(:project, namespace: nested_group) }
|
|
let(:project_c) { create(:project, namespace: deep_nested_group) }
|
|
|
|
it 'returns members of all projects in group and subgroups' do
|
|
project_a.add_developer(user_a)
|
|
project_b.add_developer(user_b)
|
|
project_c.add_developer(user_c)
|
|
|
|
expect(group.project_users_with_descendants).to contain_exactly(user_a, user_b, user_c)
|
|
expect(nested_group.project_users_with_descendants).to contain_exactly(user_b, user_c)
|
|
expect(deep_nested_group.project_users_with_descendants).to contain_exactly(user_c)
|
|
end
|
|
end
|
|
|
|
describe '#user_ids_for_project_authorizations' do
|
|
it 'returns the user IDs for which to refresh authorizations' do
|
|
maintainer = create(:user)
|
|
developer = create(:user)
|
|
|
|
group.add_user(maintainer, GroupMember::MAINTAINER)
|
|
group.add_user(developer, GroupMember::DEVELOPER)
|
|
|
|
expect(group.user_ids_for_project_authorizations)
|
|
.to include(maintainer.id, developer.id)
|
|
end
|
|
end
|
|
|
|
describe '#update_two_factor_requirement' do
|
|
let(:user) { create(:user) }
|
|
|
|
before do
|
|
group.add_user(user, GroupMember::OWNER)
|
|
end
|
|
|
|
it 'is called when require_two_factor_authentication is changed' do
|
|
expect_any_instance_of(User).to receive(:update_two_factor_requirement)
|
|
|
|
group.update!(require_two_factor_authentication: true)
|
|
end
|
|
|
|
it 'is called when two_factor_grace_period is changed' do
|
|
expect_any_instance_of(User).to receive(:update_two_factor_requirement)
|
|
|
|
group.update!(two_factor_grace_period: 23)
|
|
end
|
|
|
|
it 'is not called when other attributes are changed' do
|
|
expect_any_instance_of(User).not_to receive(:update_two_factor_requirement)
|
|
|
|
group.update!(description: 'foobar')
|
|
end
|
|
|
|
it 'calls #update_two_factor_requirement on each group member' do
|
|
other_user = create(:user)
|
|
group.add_user(other_user, GroupMember::OWNER)
|
|
|
|
calls = 0
|
|
allow_any_instance_of(User).to receive(:update_two_factor_requirement) do
|
|
calls += 1
|
|
end
|
|
|
|
group.update!(require_two_factor_authentication: true, two_factor_grace_period: 23)
|
|
|
|
expect(calls).to eq 2
|
|
end
|
|
end
|
|
|
|
describe '#path_changed_hook' do
|
|
let(:system_hook_service) { SystemHooksService.new }
|
|
|
|
context 'for a new group' do
|
|
let(:group) { build(:group) }
|
|
|
|
before do
|
|
expect(group).to receive(:system_hook_service).and_return(system_hook_service)
|
|
end
|
|
|
|
it 'does not trigger system hook' do
|
|
expect(system_hook_service).to receive(:execute_hooks_for).with(group, :create)
|
|
|
|
group.save!
|
|
end
|
|
end
|
|
|
|
context 'for an existing group' do
|
|
let(:group) { create(:group, path: 'old-path') }
|
|
|
|
context 'when the path is changed' do
|
|
let(:new_path) { 'very-new-path' }
|
|
|
|
it 'triggers the rename system hook' do
|
|
expect(group).to receive(:system_hook_service).and_return(system_hook_service)
|
|
expect(system_hook_service).to receive(:execute_hooks_for).with(group, :rename)
|
|
|
|
group.update!(path: new_path)
|
|
end
|
|
end
|
|
|
|
context 'when the path is not changed' do
|
|
it 'does not trigger system hook' do
|
|
expect(group).not_to receive(:system_hook_service)
|
|
|
|
group.update!(name: 'new name')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#ci_variables_for' do
|
|
let(:project) { create(:project, group: group) }
|
|
|
|
let!(:ci_variable) do
|
|
create(:ci_group_variable, value: 'secret', group: group)
|
|
end
|
|
|
|
let!(:protected_variable) do
|
|
create(:ci_group_variable, :protected, value: 'protected', group: group)
|
|
end
|
|
|
|
subject { group.ci_variables_for('ref', project) }
|
|
|
|
shared_examples 'ref is protected' do
|
|
it 'contains all the variables' do
|
|
is_expected.to contain_exactly(ci_variable, protected_variable)
|
|
end
|
|
end
|
|
|
|
context 'when the ref is not protected' do
|
|
before do
|
|
stub_application_setting(
|
|
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
end
|
|
|
|
it 'contains only the CI variables' do
|
|
is_expected.to contain_exactly(ci_variable)
|
|
end
|
|
end
|
|
|
|
context 'when the ref is a protected branch' do
|
|
before do
|
|
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
|
end
|
|
|
|
it_behaves_like 'ref is protected'
|
|
end
|
|
|
|
context 'when the ref is a protected tag' do
|
|
before do
|
|
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
|
end
|
|
|
|
it_behaves_like 'ref is protected'
|
|
end
|
|
|
|
context 'when group has children', :postgresql do
|
|
let(:group_child) { create(:group, parent: group) }
|
|
let(:group_child_2) { create(:group, parent: group_child) }
|
|
let(:group_child_3) { create(:group, parent: group_child_2) }
|
|
let(:variable_child) { create(:ci_group_variable, group: group_child) }
|
|
let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
|
|
let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
|
|
|
|
before do
|
|
allow(project).to receive(:protected_for?).with('ref').and_return(true)
|
|
end
|
|
|
|
it 'returns all variables belong to the group and parent groups' do
|
|
expected_array1 = [protected_variable, ci_variable]
|
|
expected_array2 = [variable_child, variable_child_2, variable_child_3]
|
|
got_array = group_child_3.ci_variables_for('ref', project).to_a
|
|
|
|
expect(got_array.shift(2)).to contain_exactly(*expected_array1)
|
|
expect(got_array).to eq(expected_array2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#highest_group_member', :nested_groups do
|
|
let(:nested_group) { create(:group, parent: group) }
|
|
let(:nested_group_2) { create(:group, parent: nested_group) }
|
|
let(:user) { create(:user) }
|
|
|
|
subject(:highest_group_member) { nested_group_2.highest_group_member(user) }
|
|
|
|
context 'when the user is not a member of any group in the hierarchy' do
|
|
it 'returns nil' do
|
|
expect(highest_group_member).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'when the user is only a member of one group in the hierarchy' do
|
|
before do
|
|
nested_group.add_developer(user)
|
|
end
|
|
|
|
it 'returns that group member' do
|
|
expect(highest_group_member.access_level).to eq(Gitlab::Access::DEVELOPER)
|
|
end
|
|
end
|
|
|
|
context 'when the user is a member of several groups in the hierarchy' do
|
|
before do
|
|
group.add_owner(user)
|
|
nested_group.add_developer(user)
|
|
nested_group_2.add_maintainer(user)
|
|
end
|
|
|
|
it 'returns the group member with the highest access level' do
|
|
expect(highest_group_member.access_level).to eq(Gitlab::Access::OWNER)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#has_parent?' do
|
|
context 'when the group has a parent' do
|
|
it 'should be truthy' do
|
|
group = create(:group, :nested)
|
|
expect(group.has_parent?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context 'when the group has no parent' do
|
|
it 'should be falsy' do
|
|
group = create(:group, parent: nil)
|
|
expect(group.has_parent?).to be_falsy
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with uploads' do
|
|
it_behaves_like 'model with uploads', true do
|
|
let(:model_object) { create(:group, :with_avatar) }
|
|
let(:upload_attribute) { :avatar }
|
|
let(:uploader_class) { AttachmentUploader }
|
|
end
|
|
end
|
|
|
|
describe '#group_clusters_enabled?' do
|
|
before do
|
|
# Override global stub in spec/spec_helper.rb
|
|
expect(Feature).to receive(:enabled?).and_call_original
|
|
end
|
|
|
|
subject { group.group_clusters_enabled? }
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
context 'explicitly disabled for root ancestor' do
|
|
before do
|
|
feature = Feature.get(:group_clusters)
|
|
feature.disable(group.root_ancestor)
|
|
end
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
|
|
context 'explicitly disabled for root ancestor' do
|
|
before do
|
|
feature = Feature.get(:group_clusters)
|
|
feature.enable(group.root_ancestor)
|
|
end
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
end
|
|
end
|