diff options
Diffstat (limited to 'spec/models/user_spec.rb')
-rw-r--r-- | spec/models/user_spec.rb | 374 |
1 files changed, 349 insertions, 25 deletions
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a643dd0b4e5..690c0be3b7a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe User, feature_category: :user_profile do + using RSpec::Parameterized::TableSyntax + include ProjectForksHelper include TermsHelper include ExclusiveLeaseHelpers @@ -60,6 +62,9 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to delegate_method(:setup_for_company).to(:user_preference) } it { is_expected.to delegate_method(:setup_for_company=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:project_shortcut_buttons).to(:user_preference) } + it { is_expected.to delegate_method(:project_shortcut_buttons=).to(:user_preference).with_arguments(:args) } + it { is_expected.to delegate_method(:render_whitespace_in_code).to(:user_preference) } it { is_expected.to delegate_method(:render_whitespace_in_code=).to(:user_preference).with_arguments(:args) } @@ -152,7 +157,11 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to have_many(:chat_names).dependent(:destroy) } it { is_expected.to have_many(:saved_replies).class_name('::Users::SavedReply') } it { is_expected.to have_many(:uploads) } - it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') } + it { is_expected.to have_many(:abuse_reports).dependent(:nullify).inverse_of(:user) } + it { is_expected.to have_many(:reported_abuse_reports).dependent(:nullify).class_name('AbuseReport').inverse_of(:reporter) } + it { is_expected.to have_many(:assigned_abuse_reports).class_name('AbuseReport').inverse_of(:assignee) } + it { is_expected.to have_many(:resolved_abuse_reports).class_name('AbuseReport').inverse_of(:resolved_by) } + it { is_expected.to have_many(:abuse_events).class_name('Abuse::Event').inverse_of(:user) } it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') } it { is_expected.to have_many(:releases).dependent(:nullify) } it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) } @@ -2081,7 +2090,7 @@ RSpec.describe User, feature_category: :user_profile do user = create(:user) - expect(user.incoming_email_token).to eql('gitlab') + expect(user.incoming_email_token).to eql("glimt-gitlab") end end @@ -2128,6 +2137,12 @@ RSpec.describe User, feature_category: :user_profile do expect(user.reload.feed_token).to eq feed_token end + it 'returns feed tokens with a prefix' do + user = create(:user) + + expect(user.feed_token).to start_with('glft-') + end + it 'ensures no feed token when disabled' do allow(Gitlab::CurrentSettings).to receive(:disable_feed_token).and_return(true) @@ -2175,7 +2190,7 @@ RSpec.describe User, feature_category: :user_profile do describe 'enabled_static_object_token' do let_it_be(:static_object_token) { 'ilqx6jm1u945macft4eff0nw' } - it 'returns incoming email token when supported' do + it 'returns static object token when supported' do allow(Gitlab::CurrentSettings).to receive(:static_objects_external_storage_enabled?).and_return(true) user = create(:user, static_object_token: static_object_token) @@ -2203,6 +2218,14 @@ RSpec.describe User, feature_category: :user_profile do expect(user.enabled_incoming_email_token).to eq(incoming_email_token) end + it 'returns incoming mail tokens with a prefix' do + allow(Gitlab::Email::IncomingEmail).to receive(:supports_issue_creation?).and_return(true) + + user = create(:user) + + expect(user.enabled_incoming_email_token).to start_with('glimt-') + end + it 'returns `nil` when not supported' do allow(Gitlab::Email::IncomingEmail).to receive(:supports_issue_creation?).and_return(false) @@ -2927,6 +2950,56 @@ RSpec.describe User, feature_category: :user_profile do end end + describe '#spammer?' do + let_it_be(:user) { create(:user) } + + context 'when the user is a spammer' do + before do + allow(user).to receive(:spam_score).and_return(0.9) + end + + it 'classifies the user as a spammer' do + expect(user).to be_spammer + end + end + + context 'when the user is not a spammer' do + before do + allow(user).to receive(:spam_score).and_return(0.1) + end + + it 'does not classify the user as a spammer' do + expect(user).not_to be_spammer + end + end + end + + describe '#spam_score' do + let_it_be(:user) { create(:user) } + + context 'when the user is a spammer' do + before do + create(:abuse_trust_score, user: user, score: 0.8) + create(:abuse_trust_score, user: user, score: 0.9) + end + + it 'returns the expected score' do + expect(user.spam_score).to be_within(0.01).of(0.85) + end + end + + context 'when the user is not a spammer' do + before do + create(:abuse_trust_score, user: user, score: 0.1) + create(:abuse_trust_score, user: user, score: 0.0) + end + + it 'returns the expected score' do + expect(user.spam_score).to be_within(0.01).of(0.05) + end + end + end + describe '.find_for_database_authentication' do it 'strips whitespace from login' do user = create(:user) @@ -4112,8 +4185,6 @@ RSpec.describe User, feature_category: :user_profile do end describe '#following_users_allowed?' do - using RSpec::Parameterized::TableSyntax - let_it_be(:user) { create(:user) } let_it_be(:followee) { create(:user) } @@ -4498,6 +4569,18 @@ RSpec.describe User, feature_category: :user_profile do it { is_expected.to include shared_group } it { is_expected.not_to include other_group } end + + context 'when a new column is added to namespaces table' do + before do + ApplicationRecord.connection.execute "ALTER TABLE namespaces ADD COLUMN _test_column_xyz INT NULL" + end + + # We sanity check that we don't get: + # ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: each UNION query must have the same number of columns + it 'will not raise errors' do + expect { subject.count }.not_to raise_error + end + end end describe '#membership_groups' do @@ -5950,35 +6033,187 @@ RSpec.describe User, feature_category: :user_profile do end describe '#delete_async' do - let(:user) { create(:user) } + let(:user) { create(:user, note: "existing note") } let(:deleted_by) { create(:user) } - it 'blocks the user then schedules them for deletion if a hard delete is specified' do - expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, { hard_delete: true }) + shared_examples 'schedules user for deletion without delay' do + it 'schedules user for deletion without delay' do + expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, {}) + expect(DeleteUserWorker).not_to receive(:perform_in) + user.delete_async(deleted_by: deleted_by) + end + end + + shared_examples 'it does not block the user' do + it 'does not block the user' do + user.delete_async(deleted_by: deleted_by) + + expect(user).not_to be_blocked + end + end + + it 'blocks the user if hard delete is specified' do user.delete_async(deleted_by: deleted_by, params: { hard_delete: true }) expect(user).to be_blocked end - it 'schedules user for deletion without blocking them' do - expect(DeleteUserWorker).to receive(:perform_async).with(deleted_by.id, user.id, {}) - - user.delete_async(deleted_by: deleted_by) + it_behaves_like 'schedules user for deletion without delay' - expect(user).not_to be_blocked - end + it_behaves_like 'it does not block the user' context 'when target user is the same as deleted_by' do let(:deleted_by) { user } - it 'blocks the user and schedules the record for deletion with the correct delay' do - freeze_time do - expect(DeleteUserWorker).to receive(:perform_in).with(7.days, user.id, user.id, {}) + subject { user.delete_async(deleted_by: deleted_by) } + + before do + allow(user).to receive(:has_possible_spam_contributions?).and_return(true) + end + + shared_examples 'schedules the record for deletion with the correct delay' do + it 'schedules the record for deletion with the correct delay' do + freeze_time do + expect(DeleteUserWorker).to receive(:perform_in).with(7.days, user.id, user.id, {}) + + subject + end + end + end + + it_behaves_like 'schedules the record for deletion with the correct delay' + + it 'blocks the user' do + subject + + expect(user).to be_blocked + expect(user).not_to be_banned + end + + context 'with possible spam contribution' do + context 'with comments' do + it_behaves_like 'schedules the record for deletion with the correct delay' do + before do + allow(user).to receive(:has_possible_spam_contributions?).and_call_original + + note = create(:note_on_issue, author: user) + create(:event, :commented, target: note, author: user) + end + end + end + + context 'with other types' do + where(:resource, :action, :delayed) do + 'Issue' | :created | true + 'MergeRequest' | :created | true + 'Issue' | :closed | false + 'MergeRequest' | :closed | false + 'WorkItem' | :created | false + end + + with_them do + before do + allow(user).to receive(:has_possible_spam_contributions?).and_call_original + + case resource + when 'Issue' + create(:event, action, :for_issue, author: user) + when 'MergeRequest' + create(:event, action, :for_merge_request, author: user) + when 'WorkItem' + create(:event, action, :for_work_item, author: user) + end + end + + if params[:delayed] + it_behaves_like 'schedules the record for deletion with the correct delay' + else + it_behaves_like 'schedules user for deletion without delay' + end + end + end + end + + context 'when user has no possible spam contributions' do + before do + allow(user).to receive(:has_possible_spam_contributions?).and_return(false) + end - user.delete_async(deleted_by: deleted_by) + it_behaves_like 'schedules user for deletion without delay' + end - expect(user).to be_blocked + context 'when the user is a spammer' do + before do + allow(user).to receive(:spammer?).and_return(true) + end + + context 'when the user account is less than 7 days old' do + it_behaves_like 'schedules the record for deletion with the correct delay' + + it 'creates an abuse report with the correct data' do + expect { subject }.to change { AbuseReport.count }.from(0).to(1) + expect(AbuseReport.last.attributes).to include({ + reporter_id: User.security_bot.id, + user_id: user.id, + category: "spam", + message: 'Potential spammer account deletion' + }.stringify_keys) + end + + it 'adds custom attribute to the user with the correct values' do + subject + + custom_attribute = user.custom_attributes.by_key(UserCustomAttribute::AUTO_BANNED_BY_ABUSE_REPORT_ID).first + expect(custom_attribute.value).to eq(AbuseReport.last.id.to_s) + end + + it 'bans the user' do + subject + + expect(user).to be_banned + end + + context 'when there is an existing abuse report' do + let!(:abuse_report) { create(:abuse_report, user: user, reporter: User.security_bot, message: 'Existing') } + + it 'updates the abuse report' do + subject + abuse_report.reload + + expect(abuse_report.message).to eq("Existing\n\nPotential spammer account deletion") + end + + it 'adds custom attribute to the user with the correct values' do + subject + + custom_attribute = user.custom_attributes.by_key(UserCustomAttribute::AUTO_BANNED_BY_ABUSE_REPORT_ID).first + expect(custom_attribute.value).to eq(abuse_report.id.to_s) + end + end + end + + context 'when the user acount is greater than 7 days old' do + before do + allow(user).to receive(:account_age_in_days).and_return(8) + end + + it_behaves_like 'schedules the record for deletion with the correct delay' + + it 'blocks the user' do + subject + + expect(user).to be_blocked + expect(user).not_to be_banned + end + end + end + + it 'updates note to indicate the action (account was deleted by the user) and timestamp' do + freeze_time do + expected_note = "User deleted own account on #{Time.zone.now}\n#{user.note}" + + expect { user.delete_async(deleted_by: deleted_by) }.to change { user.note }.to(expected_note) end end @@ -5987,12 +6222,12 @@ RSpec.describe User, feature_category: :user_profile do stub_feature_flags(delay_delete_own_user: false) end - it 'schedules user for deletion without blocking them' do - expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id, {}) + it_behaves_like 'schedules user for deletion without delay' - user.delete_async(deleted_by: deleted_by) + it_behaves_like 'it does not block the user' - expect(user).not_to be_blocked + it 'does not update the note' do + expect { user.delete_async(deleted_by: deleted_by) }.not_to change { user.note } end end end @@ -6801,6 +7036,31 @@ RSpec.describe User, feature_category: :user_profile do end end + describe '#dismissed_callout_before?' do + let_it_be(:user, refind: true) { create(:user) } + let_it_be(:feature_name) { Users::Callout.feature_names.each_key.first } + + context 'when no callout dismissal record exists' do + it 'returns false' do + expect(user.dismissed_callout_before?(feature_name, 1.day.ago)).to eq false + end + end + + context 'when dismissed callout exists' do + before_all do + create(:callout, user: user, feature_name: feature_name, dismissed_at: 4.months.ago) + end + + it 'returns false when dismissed_before is earlier than dismissed_at' do + expect(user.dismissed_callout_before?(feature_name, 6.months.ago)).to eq false + end + + it 'returns true when dismissed_before is later than dismissed_at' do + expect(user.dismissed_callout_before?(feature_name, 3.months.ago)).to eq true + end + end + end + describe '#find_or_initialize_callout' do let_it_be(:user, refind: true) { create(:user) } let_it_be(:feature_name) { Users::Callout.feature_names.each_key.first } @@ -7121,8 +7381,6 @@ RSpec.describe User, feature_category: :user_profile do let(:user_id) { user.id } describe 'update user' do - using RSpec::Parameterized::TableSyntax - where(:attributes) do [ { state: 'blocked' }, @@ -7852,4 +8110,70 @@ RSpec.describe User, feature_category: :user_profile do end end end + + describe '#telesign_score' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + context 'when the user has a telesign risk score' do + before do + create(:abuse_trust_score, user: user1, score: 12.0, source: :telesign) + create(:abuse_trust_score, user: user1, score: 24.0, source: :telesign) + end + + it 'returns the latest score' do + expect(user1.telesign_score).to be(24.0) + end + end + + context 'when the user does not have a telesign risk score' do + it 'defaults to zero' do + expect(user2.telesign_score).to be(0.0) + end + end + end + + describe '#arkose_global_score' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + context 'when the user has an arkose global risk score' do + before do + create(:abuse_trust_score, user: user1, score: 12.0, source: :arkose_global_score) + create(:abuse_trust_score, user: user1, score: 24.0, source: :arkose_global_score) + end + + it 'returns the latest score' do + expect(user1.arkose_global_score).to be(24.0) + end + end + + context 'when the user does not have an arkose global risk score' do + it 'defaults to zero' do + expect(user2.arkose_global_score).to be(0.0) + end + end + end + + describe '#arkose_custom_score' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + context 'when the user has an arkose custom risk score' do + before do + create(:abuse_trust_score, user: user1, score: 12.0, source: :arkose_custom_score) + create(:abuse_trust_score, user: user1, score: 24.0, source: :arkose_custom_score) + end + + it 'returns the latest score' do + expect(user1.arkose_custom_score).to be(24.0) + end + end + + context 'when the user does not have an arkose custom risk score' do + it 'defaults to zero' do + expect(user2.arkose_custom_score).to be(0.0) + end + end + end end |