2019-03-30 03:23:56 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-09-14 10:54:10 -04:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-06-24 14:09:03 -04:00
|
|
|
RSpec.describe Member do
|
2020-04-07 11:09:30 -04:00
|
|
|
include ExclusiveLeaseHelpers
|
|
|
|
|
2020-03-26 05:07:52 -04:00
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
2014-09-14 10:54:10 -04:00
|
|
|
describe "Associations" do
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to belong_to(:user) }
|
2014-09-14 10:54:10 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe "Validation" do
|
2017-07-25 13:09:00 -04:00
|
|
|
subject { described_class.new(access_level: Member::GUEST) }
|
2014-09-14 10:54:10 -04:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to validate_presence_of(:user) }
|
|
|
|
it { is_expected.to validate_presence_of(:source) }
|
2015-04-10 09:09:37 -04:00
|
|
|
|
2020-10-01 14:10:20 -04:00
|
|
|
context 'expires_at' do
|
|
|
|
it { is_expected.not_to allow_value(Date.yesterday).for(:expires_at) }
|
|
|
|
it { is_expected.to allow_value(Date.tomorrow).for(:expires_at) }
|
|
|
|
it { is_expected.to allow_value(Date.today).for(:expires_at) }
|
|
|
|
it { is_expected.to allow_value(nil).for(:expires_at) }
|
|
|
|
end
|
|
|
|
|
2016-02-09 09:51:06 -05:00
|
|
|
it_behaves_like 'an object with email-formated attributes', :invite_email do
|
|
|
|
subject { build(:project_member) }
|
|
|
|
end
|
|
|
|
|
2015-04-10 09:09:37 -04:00
|
|
|
context "when an invite email is provided" do
|
2020-09-09 02:08:55 -04:00
|
|
|
let_it_be(:project) { create(:project) }
|
|
|
|
let(:member) { build(:project_member, source: project, invite_email: "user@example.com", user: nil) }
|
2015-04-10 09:09:37 -04:00
|
|
|
|
|
|
|
it "doesn't require a user" do
|
|
|
|
expect(member).to be_valid
|
|
|
|
end
|
|
|
|
|
|
|
|
it "requires a valid invite email" do
|
|
|
|
member.invite_email = "nope"
|
|
|
|
|
|
|
|
expect(member).not_to be_valid
|
|
|
|
end
|
|
|
|
|
|
|
|
it "requires a unique invite email scoped to this source" do
|
|
|
|
create(:project_member, source: member.source, invite_email: member.invite_email)
|
|
|
|
|
|
|
|
expect(member).not_to be_valid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when an invite email is not provided" do
|
|
|
|
let(:member) { build(:project_member) }
|
|
|
|
|
|
|
|
it "requires a user" do
|
|
|
|
member.user = nil
|
|
|
|
|
|
|
|
expect(member).not_to be_valid
|
|
|
|
end
|
|
|
|
|
|
|
|
it "is valid otherwise" do
|
|
|
|
expect(member).to be_valid
|
|
|
|
end
|
|
|
|
end
|
2018-12-06 08:15:29 -05:00
|
|
|
|
|
|
|
context "when a child member inherits its access level" do
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
let(:member) { create(:group_member, :developer, user: user) }
|
|
|
|
let(:child_group) { create(:group, parent: member.group) }
|
|
|
|
let(:child_member) { build(:group_member, group: child_group, user: user) }
|
|
|
|
|
|
|
|
it "requires a higher level" do
|
|
|
|
child_member.access_level = GroupMember::REPORTER
|
|
|
|
|
|
|
|
child_member.validate
|
|
|
|
|
|
|
|
expect(child_member).not_to be_valid
|
|
|
|
end
|
|
|
|
|
2019-04-30 14:39:15 -04:00
|
|
|
# Membership in a subgroup confers certain access rights, such as being
|
|
|
|
# able to merge or push code to protected branches.
|
|
|
|
it "is valid with an equal level" do
|
|
|
|
child_member.access_level = GroupMember::DEVELOPER
|
|
|
|
|
|
|
|
child_member.validate
|
|
|
|
|
|
|
|
expect(child_member).to be_valid
|
|
|
|
end
|
|
|
|
|
2018-12-06 08:15:29 -05:00
|
|
|
it "is valid with a higher level" do
|
|
|
|
child_member.access_level = GroupMember::MAINTAINER
|
|
|
|
|
|
|
|
child_member.validate
|
|
|
|
|
|
|
|
expect(child_member).to be_valid
|
|
|
|
end
|
|
|
|
end
|
2020-07-16 11:09:38 -04:00
|
|
|
|
|
|
|
context 'project bots' do
|
|
|
|
let_it_be(:project_bot) { create(:user, :project_bot) }
|
|
|
|
let(:new_member) { build(:project_member, user_id: project_bot.id) }
|
|
|
|
|
|
|
|
context 'not a member of any group or project' do
|
|
|
|
it 'is valid' do
|
|
|
|
expect(new_member).to be_valid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'already member of a project' do
|
|
|
|
before do
|
|
|
|
unrelated_project = create(:project)
|
|
|
|
unrelated_project.add_maintainer(project_bot)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is not valid' do
|
|
|
|
expect(new_member).not_to be_valid
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-09-14 10:54:10 -04:00
|
|
|
end
|
2014-09-14 12:32:51 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe 'Scopes & finders' do
|
2020-08-13 08:09:50 -04:00
|
|
|
let_it_be(:project) { create(:project, :public) }
|
|
|
|
let_it_be(:group) { create(:group) }
|
|
|
|
|
|
|
|
before_all do
|
2016-06-02 12:05:06 -04:00
|
|
|
@owner_user = create(:user).tap { |u| group.add_owner(u) }
|
|
|
|
@owner = group.members.find_by(user_id: @owner_user.id)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
@maintainer_user = create(:user).tap { |u| project.add_maintainer(u) }
|
|
|
|
@maintainer = project.members.find_by(user_id: @maintainer_user.id)
|
2016-06-02 12:05:06 -04:00
|
|
|
|
2016-09-05 11:37:26 -04:00
|
|
|
@blocked_user = create(:user).tap do |u|
|
2018-07-11 10:36:08 -04:00
|
|
|
project.add_maintainer(u)
|
2017-12-22 03:18:28 -05:00
|
|
|
project.add_developer(u)
|
2016-09-05 11:37:26 -04:00
|
|
|
|
|
|
|
u.block!
|
|
|
|
end
|
2018-07-11 10:36:08 -04:00
|
|
|
@blocked_maintainer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MAINTAINER)
|
2016-09-05 11:37:26 -04:00
|
|
|
@blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER)
|
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
@invited_member = create(:project_member, :developer,
|
|
|
|
project: project,
|
|
|
|
invite_token: '1234',
|
|
|
|
invite_email: 'toto1@example.com')
|
2016-06-02 12:05:06 -04:00
|
|
|
|
2016-09-05 11:37:26 -04:00
|
|
|
accepted_invite_user = build(:user, state: :active)
|
2016-09-16 11:54:21 -04:00
|
|
|
@accepted_invite_member = create(:project_member, :developer,
|
|
|
|
project: project,
|
|
|
|
invite_token: '1234',
|
2017-06-21 09:48:12 -04:00
|
|
|
invite_email: 'toto2@example.com')
|
|
|
|
.tap { |u| u.accept_invite!(accepted_invite_user) }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
|
|
|
requested_user = create(:user).tap { |u| project.request_access(u) }
|
2016-06-27 10:20:57 -04:00
|
|
|
@requested_member = project.requesters.find_by(user_id: requested_user.id)
|
2016-06-02 12:05:06 -04:00
|
|
|
|
2016-04-18 12:53:32 -04:00
|
|
|
accepted_request_user = create(:user).tap { |u| project.request_access(u) }
|
2016-06-27 10:20:57 -04:00
|
|
|
@accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
|
2020-09-10 14:08:54 -04:00
|
|
|
@member_with_minimal_access = create(:group_member, :minimal_access, source: group)
|
2016-04-18 12:53:32 -04:00
|
|
|
end
|
|
|
|
|
2016-07-26 20:20:19 -04:00
|
|
|
describe '.access_for_user_ids' do
|
|
|
|
it 'returns the right access levels' do
|
2018-07-11 10:36:08 -04:00
|
|
|
users = [@owner_user.id, @maintainer_user.id, @blocked_user.id]
|
2016-07-26 20:20:19 -04:00
|
|
|
expected = {
|
|
|
|
@owner_user.id => Gitlab::Access::OWNER,
|
2018-07-11 10:36:08 -04:00
|
|
|
@maintainer_user.id => Gitlab::Access::MAINTAINER
|
2016-07-26 20:20:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
expect(described_class.access_for_user_ids(users)).to eq(expected)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '.invite' do
|
2018-07-11 10:36:08 -04:00
|
|
|
it { expect(described_class.invite).not_to include @maintainer }
|
2016-04-18 12:53:32 -04:00
|
|
|
it { expect(described_class.invite).to include @invited_member }
|
|
|
|
it { expect(described_class.invite).not_to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.invite).not_to include @requested_member }
|
|
|
|
it { expect(described_class.invite).not_to include @accepted_request_member }
|
|
|
|
end
|
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '.non_invite' do
|
2018-07-11 10:36:08 -04:00
|
|
|
it { expect(described_class.non_invite).to include @maintainer }
|
2016-06-02 12:05:06 -04:00
|
|
|
it { expect(described_class.non_invite).not_to include @invited_member }
|
|
|
|
it { expect(described_class.non_invite).to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.non_invite).to include @requested_member }
|
|
|
|
it { expect(described_class.non_invite).to include @accepted_request_member }
|
|
|
|
end
|
|
|
|
|
2020-09-10 14:08:54 -04:00
|
|
|
describe '.non_minimal_access' do
|
|
|
|
it { expect(described_class.non_minimal_access).to include @maintainer }
|
|
|
|
it { expect(described_class.non_minimal_access).to include @invited_member }
|
|
|
|
it { expect(described_class.non_minimal_access).to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.non_minimal_access).to include @requested_member }
|
|
|
|
it { expect(described_class.non_minimal_access).to include @accepted_request_member }
|
|
|
|
it { expect(described_class.non_minimal_access).not_to include @member_with_minimal_access }
|
|
|
|
end
|
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '.request' do
|
2018-07-11 10:36:08 -04:00
|
|
|
it { expect(described_class.request).not_to include @maintainer }
|
2016-04-18 12:53:32 -04:00
|
|
|
it { expect(described_class.request).not_to include @invited_member }
|
|
|
|
it { expect(described_class.request).not_to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.request).to include @requested_member }
|
|
|
|
it { expect(described_class.request).not_to include @accepted_request_member }
|
|
|
|
end
|
|
|
|
|
2017-02-08 10:02:25 -05:00
|
|
|
describe '.non_request' do
|
2018-07-11 10:36:08 -04:00
|
|
|
it { expect(described_class.non_request).to include @maintainer }
|
2017-02-08 10:02:25 -05:00
|
|
|
it { expect(described_class.non_request).to include @invited_member }
|
|
|
|
it { expect(described_class.non_request).to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.non_request).not_to include @requested_member }
|
|
|
|
it { expect(described_class.non_request).to include @accepted_request_member }
|
|
|
|
end
|
|
|
|
|
2020-09-28 05:09:35 -04:00
|
|
|
describe '.not_accepted_invitations' do
|
|
|
|
let_it_be(:not_accepted_invitation) { create(:project_member, :invited) }
|
|
|
|
let_it_be(:accepted_invitation) { create(:project_member, :invited, invite_accepted_at: Date.today) }
|
|
|
|
|
|
|
|
subject { described_class.not_accepted_invitations }
|
|
|
|
|
|
|
|
it { is_expected.to include(not_accepted_invitation) }
|
|
|
|
it { is_expected.not_to include(accepted_invitation) }
|
|
|
|
end
|
|
|
|
|
2020-09-02 11:10:54 -04:00
|
|
|
describe '.not_accepted_invitations_by_user' do
|
|
|
|
let(:invited_by_user) { create(:project_member, :invited, project: project, created_by: @owner_user) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
create(:project_member, :invited, invite_email: 'test@test.com', project: project, created_by: @owner_user, invite_accepted_at: Time.zone.now)
|
|
|
|
create(:project_member, :invited, invite_email: 'test2@test.com', project: project, created_by: @maintainer_user)
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { described_class.not_accepted_invitations_by_user(@owner_user) }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(invited_by_user) }
|
|
|
|
end
|
|
|
|
|
2020-09-28 05:09:35 -04:00
|
|
|
describe '.not_expired' do
|
2020-09-28 11:09:44 -04:00
|
|
|
let_it_be(:expiring_yesterday) { create(:group_member, expires_at: 1.day.from_now) }
|
|
|
|
let_it_be(:expiring_today) { create(:group_member, expires_at: 2.days.from_now) }
|
|
|
|
let_it_be(:expiring_tomorrow) { create(:group_member, expires_at: 3.days.from_now) }
|
2020-09-28 05:09:35 -04:00
|
|
|
let_it_be(:not_expiring) { create(:group_member) }
|
|
|
|
|
|
|
|
subject { described_class.not_expired }
|
|
|
|
|
2020-09-28 11:09:44 -04:00
|
|
|
around do |example|
|
|
|
|
travel_to(2.days.from_now) { example.run }
|
|
|
|
end
|
|
|
|
|
2020-09-28 05:09:35 -04:00
|
|
|
it { is_expected.not_to include(expiring_yesterday, expiring_today) }
|
|
|
|
it { is_expected.to include(expiring_tomorrow, not_expiring) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.last_ten_days_excluding_today' do
|
2020-10-23 14:08:31 -04:00
|
|
|
let_it_be(:now) { Time.current }
|
|
|
|
let_it_be(:created_today) { create(:group_member, created_at: now.beginning_of_day) }
|
|
|
|
let_it_be(:created_yesterday) { create(:group_member, created_at: now - 1.day) }
|
|
|
|
let_it_be(:created_eleven_days_ago) { create(:group_member, created_at: now - 11.days) }
|
2020-09-28 05:09:35 -04:00
|
|
|
|
|
|
|
subject { described_class.last_ten_days_excluding_today }
|
|
|
|
|
2020-10-23 14:08:31 -04:00
|
|
|
before do
|
|
|
|
travel_to now
|
|
|
|
end
|
|
|
|
|
2020-09-28 05:09:35 -04:00
|
|
|
it { is_expected.to include(created_yesterday) }
|
|
|
|
it { is_expected.not_to include(created_today, created_eleven_days_ago) }
|
|
|
|
end
|
|
|
|
|
2019-08-09 03:33:42 -04:00
|
|
|
describe '.search_invite_email' do
|
2019-08-12 01:56:11 -04:00
|
|
|
it 'returns only members the matching e-mail' do
|
2019-05-18 11:06:20 -04:00
|
|
|
create(:group_member, :invited)
|
|
|
|
|
2019-08-09 03:33:42 -04:00
|
|
|
invited = described_class.search_invite_email(@invited_member.invite_email)
|
2019-05-18 11:06:20 -04:00
|
|
|
|
|
|
|
expect(invited.count).to eq(1)
|
|
|
|
expect(invited.first).to eq(@invited_member)
|
2019-08-09 03:33:42 -04:00
|
|
|
|
|
|
|
expect(described_class.search_invite_email('bad-email@example.com').count).to eq(0)
|
2019-05-18 11:06:20 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-05 11:37:26 -04:00
|
|
|
describe '.developers' do
|
|
|
|
subject { described_class.developers.to_a }
|
|
|
|
|
|
|
|
it { is_expected.not_to include @owner }
|
2018-07-11 10:36:08 -04:00
|
|
|
it { is_expected.not_to include @maintainer }
|
2016-09-05 11:37:26 -04:00
|
|
|
it { is_expected.to include @invited_member }
|
|
|
|
it { is_expected.to include @accepted_invite_member }
|
|
|
|
it { is_expected.not_to include @requested_member }
|
|
|
|
it { is_expected.to include @accepted_request_member }
|
2018-07-11 10:36:08 -04:00
|
|
|
it { is_expected.not_to include @blocked_maintainer }
|
2016-09-05 11:37:26 -04:00
|
|
|
it { is_expected.not_to include @blocked_developer }
|
|
|
|
end
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
describe '.owners_and_maintainers' do
|
|
|
|
it { expect(described_class.owners_and_maintainers).to include @owner }
|
|
|
|
it { expect(described_class.owners_and_maintainers).to include @maintainer }
|
|
|
|
it { expect(described_class.owners_and_maintainers).not_to include @invited_member }
|
|
|
|
it { expect(described_class.owners_and_maintainers).not_to include @accepted_invite_member }
|
|
|
|
it { expect(described_class.owners_and_maintainers).not_to include @requested_member }
|
|
|
|
it { expect(described_class.owners_and_maintainers).not_to include @accepted_request_member }
|
|
|
|
it { expect(described_class.owners_and_maintainers).not_to include @blocked_maintainer }
|
2016-09-05 11:37:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe '.has_access' do
|
|
|
|
subject { described_class.has_access.to_a }
|
|
|
|
|
|
|
|
it { is_expected.to include @owner }
|
2018-07-11 10:36:08 -04:00
|
|
|
it { is_expected.to include @maintainer }
|
2016-09-05 11:37:26 -04:00
|
|
|
it { is_expected.to include @invited_member }
|
|
|
|
it { is_expected.to include @accepted_invite_member }
|
|
|
|
it { is_expected.not_to include @requested_member }
|
|
|
|
it { is_expected.to include @accepted_request_member }
|
2018-07-11 10:36:08 -04:00
|
|
|
it { is_expected.not_to include @blocked_maintainer }
|
2016-09-05 11:37:26 -04:00
|
|
|
it { is_expected.not_to include @blocked_developer }
|
2016-06-02 12:05:06 -04:00
|
|
|
end
|
2020-09-10 14:08:54 -04:00
|
|
|
|
|
|
|
describe '.active' do
|
|
|
|
subject { described_class.active.to_a }
|
|
|
|
|
|
|
|
it { is_expected.to include @owner }
|
|
|
|
it { is_expected.to include @maintainer }
|
|
|
|
it { is_expected.to include @invited_member }
|
|
|
|
it { is_expected.to include @accepted_invite_member }
|
|
|
|
it { is_expected.not_to include @requested_member }
|
|
|
|
it { is_expected.to include @accepted_request_member }
|
|
|
|
it { is_expected.not_to include @blocked_maintainer }
|
|
|
|
it { is_expected.not_to include @blocked_developer }
|
|
|
|
it { is_expected.not_to include @member_with_minimal_access }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.active_without_invites_and_requests' do
|
|
|
|
subject { described_class.active_without_invites_and_requests.to_a }
|
|
|
|
|
|
|
|
it { is_expected.to include @owner }
|
|
|
|
it { is_expected.to include @maintainer }
|
|
|
|
it { is_expected.not_to include @invited_member }
|
|
|
|
it { is_expected.to include @accepted_invite_member }
|
|
|
|
it { is_expected.not_to include @requested_member }
|
|
|
|
it { is_expected.to include @accepted_request_member }
|
|
|
|
it { is_expected.not_to include @blocked_maintainer }
|
|
|
|
it { is_expected.not_to include @blocked_developer }
|
|
|
|
it { is_expected.not_to include @member_with_minimal_access }
|
|
|
|
end
|
2016-04-18 12:53:32 -04:00
|
|
|
end
|
|
|
|
|
2014-09-14 12:32:51 -04:00
|
|
|
describe "Delegate methods" do
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to respond_to(:user_name) }
|
|
|
|
it { is_expected.to respond_to(:user_email) }
|
2014-09-14 12:32:51 -04:00
|
|
|
end
|
2015-04-10 09:09:37 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
describe '.add_user' do
|
|
|
|
%w[project group].each do |source_type|
|
|
|
|
context "when source is a #{source_type}" do
|
2020-08-13 08:09:50 -04:00
|
|
|
let_it_be(:source, reload: true) { create(source_type, :public) }
|
|
|
|
let_it_be(:user) { create(:user) }
|
|
|
|
let_it_be(:admin) { create(:admin) }
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
it 'returns a <Source>Member object' do
|
2018-07-11 10:36:08 -04:00
|
|
|
member = described_class.add_user(source, user, :maintainer)
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
expect(member).to be_a "#{source_type.classify}Member".constantize
|
|
|
|
expect(member).to be_persisted
|
|
|
|
end
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2020-05-15 11:08:04 -04:00
|
|
|
context 'when admin mode is enabled', :enable_admin_mode do
|
|
|
|
it 'sets members.created_by to the given admin current_user' do
|
|
|
|
member = described_class.add_user(source, user, :maintainer, current_user: admin)
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2020-05-15 11:08:04 -04:00
|
|
|
expect(member.created_by).to eq(admin)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when admin mode is disabled' do
|
|
|
|
# Skipped because `Group#max_member_access_for_user` needs to be migrated to use admin mode
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/207950
|
|
|
|
xit 'rejects setting members.created_by to the given admin current_user' do
|
|
|
|
member = described_class.add_user(source, user, :maintainer, current_user: admin)
|
|
|
|
|
|
|
|
expect(member.created_by).not_to be_persisted
|
|
|
|
end
|
2016-09-16 11:54:21 -04:00
|
|
|
end
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
it 'sets members.expires_at to the given expires_at' do
|
2018-07-11 10:36:08 -04:00
|
|
|
member = described_class.add_user(source, user, :maintainer, expires_at: Date.new(2016, 9, 22))
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
expect(member.expires_at).to eq(Date.new(2016, 9, 22))
|
|
|
|
end
|
|
|
|
|
|
|
|
described_class.access_levels.each do |sym_key, int_access_level|
|
|
|
|
it "accepts the :#{sym_key} symbol as access level" do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
|
|
|
member = described_class.add_user(source, user.id, sym_key)
|
|
|
|
|
|
|
|
expect(member.access_level).to eq(int_access_level)
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts the #{int_access_level} integer as access level" do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
|
|
|
member = described_class.add_user(source, user.id, int_access_level)
|
|
|
|
|
|
|
|
expect(member.access_level).to eq(int_access_level)
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with no current_user' do
|
|
|
|
context 'when called with a known user id' do
|
|
|
|
it 'adds the user as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user.id, :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with an unknown user id' do
|
|
|
|
it 'adds the user as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2020-08-13 08:09:50 -04:00
|
|
|
described_class.add_user(source, non_existing_record_id, :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).not_to include(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with a user object' do
|
|
|
|
it 'adds the user as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with a requester user object' do
|
|
|
|
before do
|
|
|
|
source.request_access(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds the requester as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
expect { described_class.add_user(source, user, :maintainer) }
|
2017-06-21 09:48:12 -04:00
|
|
|
.to raise_error(Gitlab::Access::AccessDeniedError)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).not_to include(user)
|
|
|
|
expect(source.requesters.reload.exists?(user_id: user)).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with a known user email' do
|
|
|
|
it 'adds the user as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user.email, :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with an unknown user email' do
|
|
|
|
it 'creates an invited member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, 'user@example.com', :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.members.invite.pluck(:invite_email)).to include('user@example.com')
|
|
|
|
end
|
|
|
|
end
|
2020-03-31 08:08:09 -04:00
|
|
|
|
|
|
|
context 'when called with an unknown user email starting with a number' do
|
|
|
|
it 'creates an invited member', :aggregate_failures do
|
|
|
|
email_starting_with_number = "#{user.id}_email@example.com"
|
|
|
|
|
|
|
|
described_class.add_user(source, email_starting_with_number, :maintainer)
|
|
|
|
|
|
|
|
expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number)
|
|
|
|
expect(source.users.reload).not_to include(user)
|
|
|
|
end
|
|
|
|
end
|
2016-09-16 11:54:21 -04:00
|
|
|
end
|
|
|
|
|
2020-05-15 11:08:04 -04:00
|
|
|
context 'when current_user can update member', :enable_admin_mode do
|
2016-09-16 11:54:21 -04:00
|
|
|
it 'creates the member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer, current_user: admin)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with a requester user object' do
|
|
|
|
before do
|
|
|
|
source.request_access(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds the requester as a member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer, current_user: admin)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).to include(user)
|
|
|
|
expect(source.requesters.reload.exists?(user_id: user)).to be_falsy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when current_user cannot update member' do
|
|
|
|
it 'does not create the member' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
member = described_class.add_user(source, user, :maintainer, current_user: user)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).not_to include(user)
|
|
|
|
expect(member).not_to be_persisted
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when called with a requester user object' do
|
|
|
|
before do
|
|
|
|
source.request_access(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not destroy the requester' do
|
|
|
|
expect(source.users).not_to include(user)
|
|
|
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer, current_user: user)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
|
|
|
expect(source.users.reload).not_to include(user)
|
|
|
|
expect(source.requesters.exists?(user_id: user)).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when member already exists' do
|
|
|
|
before do
|
|
|
|
source.add_user(user, :developer)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with no current_user' do
|
|
|
|
it 'updates the member' do
|
|
|
|
expect(source.users).to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
2016-09-16 11:54:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-15 11:08:04 -04:00
|
|
|
context 'when current_user can update member', :enable_admin_mode do
|
2016-09-16 11:54:21 -04:00
|
|
|
it 'updates the member' do
|
|
|
|
expect(source.users).to include(user)
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer, current_user: admin)
|
2016-09-16 11:54:21 -04:00
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER)
|
2016-09-16 11:54:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when current_user cannot update member' do
|
|
|
|
it 'does not update the member' do
|
|
|
|
expect(source.users).to include(user)
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
described_class.add_user(source, user, :maintainer, current_user: user)
|
2015-04-14 06:33:27 -04:00
|
|
|
|
2016-09-16 11:54:21 -04:00
|
|
|
expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-04-14 06:33:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-04-21 10:07:42 -04:00
|
|
|
describe '.add_users' do
|
|
|
|
%w[project group].each do |source_type|
|
|
|
|
context "when source is a #{source_type}" do
|
2020-08-13 08:09:50 -04:00
|
|
|
let_it_be(:source) { create(source_type, :public) }
|
|
|
|
let_it_be(:admin) { create(:admin) }
|
|
|
|
let_it_be(:user1) { create(:user) }
|
|
|
|
let_it_be(:user2) { create(:user) }
|
2017-04-21 10:07:42 -04:00
|
|
|
|
|
|
|
it 'returns a <Source>Member objects' do
|
2018-07-11 10:36:08 -04:00
|
|
|
members = described_class.add_users(source, [user1, user2], :maintainer)
|
2017-04-21 10:07:42 -04:00
|
|
|
|
|
|
|
expect(members).to be_a Array
|
2017-04-28 04:50:11 -04:00
|
|
|
expect(members.size).to eq(2)
|
2017-04-21 10:07:42 -04:00
|
|
|
expect(members.first).to be_a "#{source_type.classify}Member".constantize
|
|
|
|
expect(members.first).to be_persisted
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns an empty array' do
|
2018-07-11 10:36:08 -04:00
|
|
|
members = described_class.add_users(source, [], :maintainer)
|
2017-04-21 10:07:42 -04:00
|
|
|
|
|
|
|
expect(members).to be_a Array
|
|
|
|
expect(members).to be_empty
|
|
|
|
end
|
2017-09-05 12:03:24 -04:00
|
|
|
|
|
|
|
it 'supports differents formats' do
|
|
|
|
list = ['joe@local.test', admin, user1.id, user2.id.to_s]
|
|
|
|
|
2018-07-11 10:36:08 -04:00
|
|
|
members = described_class.add_users(source, list, :maintainer)
|
2017-09-05 12:03:24 -04:00
|
|
|
|
|
|
|
expect(members.size).to eq(4)
|
|
|
|
expect(members.first).to be_invite
|
|
|
|
end
|
2017-04-21 10:07:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-18 12:53:32 -04:00
|
|
|
describe '#accept_request' do
|
2020-05-22 05:08:09 -04:00
|
|
|
let(:member) { create(:project_member, requested_at: Time.current.utc) }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
it { expect(member.accept_request).to be_truthy }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
|
|
|
it 'clears requested_at' do
|
|
|
|
member.accept_request
|
|
|
|
|
|
|
|
expect(member.requested_at).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'calls #after_accept_request' do
|
|
|
|
expect(member).to receive(:after_accept_request)
|
|
|
|
|
|
|
|
member.accept_request
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '#invite?' do
|
|
|
|
subject { create(:project_member, invite_email: "user@example.com", user: nil) }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
it { is_expected.to be_invite }
|
|
|
|
end
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '#request?' do
|
2020-05-22 05:08:09 -04:00
|
|
|
subject { create(:project_member, requested_at: Time.current.utc) }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
it { is_expected.to be_request }
|
|
|
|
end
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
describe '#pending?' do
|
|
|
|
let(:invited_member) { create(:project_member, invite_email: "user@example.com", user: nil) }
|
2020-05-22 05:08:09 -04:00
|
|
|
let(:requester) { create(:project_member, requested_at: Time.current.utc) }
|
2016-04-18 12:53:32 -04:00
|
|
|
|
2016-06-02 12:05:06 -04:00
|
|
|
it { expect(invited_member).to be_invite }
|
|
|
|
it { expect(requester).to be_pending }
|
2016-04-18 12:53:32 -04:00
|
|
|
end
|
|
|
|
|
2015-04-10 09:09:37 -04:00
|
|
|
describe "#accept_invite!" do
|
|
|
|
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
|
|
|
it "resets the invite token" do
|
|
|
|
member.accept_invite!(user)
|
|
|
|
|
|
|
|
expect(member.invite_token).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "sets the invite accepted timestamp" do
|
|
|
|
member.accept_invite!(user)
|
|
|
|
|
|
|
|
expect(member.invite_accepted_at).not_to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "sets the user" do
|
|
|
|
member.accept_invite!(user)
|
|
|
|
|
|
|
|
expect(member.user).to eq(user)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #after_accept_invite" do
|
|
|
|
expect(member).to receive(:after_accept_invite)
|
|
|
|
|
|
|
|
member.accept_invite!(user)
|
|
|
|
end
|
2016-10-11 08:25:17 -04:00
|
|
|
|
2018-01-17 06:30:25 -05:00
|
|
|
it "refreshes user's authorized projects", :delete do
|
2016-10-11 08:25:17 -04:00
|
|
|
project = member.source
|
|
|
|
|
|
|
|
expect(user.authorized_projects).not_to include(project)
|
|
|
|
|
|
|
|
member.accept_invite!(user)
|
|
|
|
|
|
|
|
expect(user.authorized_projects.reload).to include(project)
|
|
|
|
end
|
2015-04-10 09:09:37 -04:00
|
|
|
end
|
|
|
|
|
2015-04-10 10:37:02 -04:00
|
|
|
describe "#decline_invite!" do
|
|
|
|
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
|
|
|
|
|
|
|
|
it "destroys the member" do
|
|
|
|
member.decline_invite!
|
|
|
|
|
|
|
|
expect(member).to be_destroyed
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #after_decline_invite" do
|
|
|
|
expect(member).to receive(:after_decline_invite)
|
|
|
|
|
|
|
|
member.decline_invite!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-04-10 09:09:37 -04:00
|
|
|
describe "#generate_invite_token" do
|
|
|
|
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
|
2016-02-09 09:51:06 -05:00
|
|
|
|
2015-04-10 09:09:37 -04:00
|
|
|
it "sets the invite token" do
|
|
|
|
expect { member.generate_invite_token }.to change { member.invite_token}
|
|
|
|
end
|
|
|
|
end
|
2016-10-11 08:25:17 -04:00
|
|
|
|
2020-09-04 17:08:41 -04:00
|
|
|
describe '.find_by_invite_token' do
|
|
|
|
let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) }
|
|
|
|
|
|
|
|
it 'finds the member' do
|
|
|
|
expect(described_class.find_by_invite_token(member.raw_invite_token)).to eq member
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-25 11:09:36 -04:00
|
|
|
describe '#send_invitation_reminder' do
|
|
|
|
subject { member.send_invitation_reminder(0) }
|
|
|
|
|
|
|
|
context 'an invited group member' do
|
|
|
|
let!(:member) { create(:group_member, :invited) }
|
|
|
|
|
|
|
|
it 'sends a reminder' do
|
|
|
|
expect_any_instance_of(NotificationService).to receive(:invite_member_reminder).with(member, member.raw_invite_token, 0)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'an invited member without a raw invite token set' do
|
|
|
|
let!(:member) { create(:group_member, :invited) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
member.instance_variable_set(:@raw_invite_token, nil)
|
|
|
|
allow_any_instance_of(NotificationService).to receive(:invite_member_reminder)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'generates a new token' do
|
|
|
|
expect(member).to receive(:generate_invite_token!)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'an uninvited member' do
|
|
|
|
let!(:member) { create(:group_member) }
|
|
|
|
|
|
|
|
it 'does not send a reminder' do
|
|
|
|
expect_any_instance_of(NotificationService).not_to receive(:invite_member_reminder)
|
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-25 14:10:49 -04:00
|
|
|
describe "#invite_to_unknown_user?" do
|
|
|
|
subject { member.invite_to_unknown_user? }
|
|
|
|
|
|
|
|
let(:member) { create(:project_member, invite_email: "user@example.com", invite_token: '1234', user: user) }
|
|
|
|
|
|
|
|
context 'when user is nil' do
|
|
|
|
let(:user) { nil }
|
|
|
|
|
|
|
|
it { is_expected.to eq(true) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when user is set' do
|
|
|
|
let(:user) { build(:user) }
|
|
|
|
|
|
|
|
it { is_expected.to eq(false) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-17 06:30:25 -05:00
|
|
|
describe "destroying a record", :delete do
|
2016-10-11 08:25:17 -04:00
|
|
|
it "refreshes user's authorized projects" do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, :private)
|
2016-10-11 08:25:17 -04:00
|
|
|
user = create(:user)
|
2017-12-22 03:18:28 -05:00
|
|
|
member = project.add_reporter(user)
|
2016-10-11 08:25:17 -04:00
|
|
|
|
|
|
|
member.destroy
|
|
|
|
|
|
|
|
expect(user.authorized_projects).not_to include(project)
|
|
|
|
end
|
|
|
|
end
|
2020-03-26 05:07:52 -04:00
|
|
|
|
|
|
|
context 'when after_commit :update_highest_role' do
|
2020-04-07 11:09:30 -04:00
|
|
|
let!(:user) { create(:user) }
|
|
|
|
let(:user_id) { user.id }
|
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
where(:member_type, :source_type) do
|
|
|
|
:project_member | :project
|
|
|
|
:group_member | :group
|
|
|
|
end
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
with_them do
|
|
|
|
describe 'create member' do
|
2020-04-07 11:09:30 -04:00
|
|
|
let!(:source) { create(source_type) }
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-09-09 02:08:55 -04:00
|
|
|
subject { create(member_type, :guest, user: user, source: source) }
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-04-07 11:09:30 -04:00
|
|
|
include_examples 'update highest role with exclusive lease'
|
2020-04-01 11:07:45 -04:00
|
|
|
end
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
context 'when member exists' do
|
2020-04-07 11:09:30 -04:00
|
|
|
let!(:member) { create(member_type, user: user) }
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
describe 'update member' do
|
|
|
|
context 'when access level was changed' do
|
2020-04-07 11:09:30 -04:00
|
|
|
subject { member.update(access_level: Gitlab::Access::GUEST) }
|
2020-03-26 05:07:52 -04:00
|
|
|
|
2020-04-07 11:09:30 -04:00
|
|
|
include_examples 'update highest role with exclusive lease'
|
2020-03-26 05:07:52 -04:00
|
|
|
end
|
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
context 'when access level was not changed' do
|
2020-04-07 11:09:30 -04:00
|
|
|
subject { member.update(notification_level: NotificationSetting.levels[:disabled]) }
|
2020-03-26 11:08:16 -04:00
|
|
|
|
2020-04-07 11:09:30 -04:00
|
|
|
include_examples 'does not update the highest role'
|
2020-03-26 11:08:16 -04:00
|
|
|
end
|
2020-04-01 11:07:45 -04:00
|
|
|
end
|
2020-03-26 11:08:16 -04:00
|
|
|
|
2020-04-01 11:07:45 -04:00
|
|
|
describe 'destroy member' do
|
2020-04-07 11:09:30 -04:00
|
|
|
subject { member.destroy }
|
2020-03-26 11:08:16 -04:00
|
|
|
|
2020-04-07 11:09:30 -04:00
|
|
|
include_examples 'update highest role with exclusive lease'
|
2020-03-26 05:07:52 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-09-14 10:54:10 -04:00
|
|
|
end
|