From b2877d1427a01744ece44e05f83da8e7b3423844 Mon Sep 17 00:00:00 2001 From: mo khan Date: Fri, 17 Oct 2025 16:50:24 -0600 Subject: [PATCH] Remove usages of `abuse_trust_scores` code Changelog: changed --- .rubocop_todo/rspec/be_eq.yml | 1 - app/models/anti_abuse/user_trust_score.rb | 55 ------- app/models/user.rb | 9 +- app/services/spam/spam_verdict_service.rb | 7 - .../anti_abuse/trust_score_cleanup_worker.rb | 10 +- app/workers/anti_abuse/trust_score_worker.rb | 11 +- .../gitlab_com_derisk/remove_trust_scores.yml | 10 -- .../arkose/record_user_data_service.rb | 5 +- .../users/record_user_data_service.rb | 4 +- .../arkose/record_user_data_service_spec.rb | 28 ---- .../users/record_user_data_service_spec.rb | 17 --- .../anti_abuse/user_trust_score_spec.rb | 138 ------------------ spec/models/user_spec.rb | 74 ---------- .../spam/spam_verdict_service_spec.rb | 8 - .../trust_score_cleanup_worker_spec.rb | 61 -------- .../anti_abuse/trust_score_worker_spec.rb | 48 ------ 16 files changed, 5 insertions(+), 481 deletions(-) delete mode 100644 app/models/anti_abuse/user_trust_score.rb delete mode 100644 config/feature_flags/gitlab_com_derisk/remove_trust_scores.yml delete mode 100644 spec/models/anti_abuse/user_trust_score_spec.rb delete mode 100644 spec/workers/anti_abuse/trust_score_cleanup_worker_spec.rb delete mode 100644 spec/workers/anti_abuse/trust_score_worker_spec.rb diff --git a/.rubocop_todo/rspec/be_eq.yml b/.rubocop_todo/rspec/be_eq.yml index 3d060a09a27752..fd742987aa8fc7 100644 --- a/.rubocop_todo/rspec/be_eq.yml +++ b/.rubocop_todo/rspec/be_eq.yml @@ -955,7 +955,6 @@ RSpec/BeEq: - 'spec/migrations/re_add_tags_name_unique_index_spec.rb' - 'spec/models/analytics/cycle_analytics/aggregation_spec.rb' - 'spec/models/anti_abuse/reports/note_spec.rb' - - 'spec/models/anti_abuse/user_trust_score_spec.rb' - 'spec/models/appearance_spec.rb' - 'spec/models/authentication_event_spec.rb' - 'spec/models/batched_git_ref_updates/deletion_spec.rb' diff --git a/app/models/anti_abuse/user_trust_score.rb b/app/models/anti_abuse/user_trust_score.rb deleted file mode 100644 index 928472bb22944b..00000000000000 --- a/app/models/anti_abuse/user_trust_score.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module AntiAbuse - class UserTrustScore - MAX_EVENTS = 100 - SPAMCHECK_HAM_THRESHOLD = 0.5 - - def initialize(user) - @user = user - end - - def spammer? - spam_score > SPAMCHECK_HAM_THRESHOLD - end - - def spam_score - user_scores.spamcheck.average(:score) || 0.0 - end - - def telesign_score - user_scores.telesign.order_created_at_desc.first&.score || 0.0 - end - - def arkose_global_score - user_scores.arkose_global_score.order_created_at_desc.first&.score || 0.0 - end - - def arkose_custom_score - user_scores.arkose_custom_score.order_created_at_desc.first&.score || 0.0 - end - - def trust_scores_for_source(source) - user_scores.where(source: source) - end - - def remove_old_scores(source) - count = trust_scores_for_source(source).count - return unless count > MAX_EVENTS - - AntiAbuse::TrustScore.delete( - trust_scores_for_source(source) - .order_created_at_asc - .limit(count - MAX_EVENTS) - ) - end - - private - - def user_scores - return AntiAbuse::TrustScore.none if Feature.enabled?(:remove_trust_scores, @user) - - AntiAbuse::TrustScore.where(user_id: @user.id) - end - end -end diff --git a/app/models/user.rb b/app/models/user.rb index ccd7fc0402472a..35c25b6f21d29d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2855,14 +2855,7 @@ def optional_namespace? end def block_or_ban - return block if Feature.enabled?(:remove_trust_scores, self) - - user_scores = AntiAbuse::UserTrustScore.new(self) - if user_scores.spammer? && account_age_in_days < 7 - ban_and_report - else - block - end + block end def ban_and_report diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb index 85464f511859a5..6c2becc2767fbc 100644 --- a/app/services/spam/spam_verdict_service.rb +++ b/app/services/spam/spam_verdict_service.rb @@ -68,14 +68,7 @@ def get_spamcheck_verdict begin result = spamcheck_client.spam?(spammable: target, user: user, context: context, extra_features: extra_features) - - if result.evaluated? && Feature.disabled?(:remove_trust_scores, user) - correlation_id = Labkit::Correlation::CorrelationId.current_id || '' - AntiAbuse::TrustScoreWorker.perform_async(user.id, :spamcheck, result.score, correlation_id) - end - result.verdict - rescue StandardError => e Gitlab::ErrorTracking.log_exception(e, error: ERROR_TYPE) nil diff --git a/app/workers/anti_abuse/trust_score_cleanup_worker.rb b/app/workers/anti_abuse/trust_score_cleanup_worker.rb index 6568fe03886b10..298a1b786469ea 100644 --- a/app/workers/anti_abuse/trust_score_cleanup_worker.rb +++ b/app/workers/anti_abuse/trust_score_cleanup_worker.rb @@ -11,15 +11,7 @@ class TrustScoreCleanupWorker urgency :low def perform(user_id, source) - user = User.find_by_id(user_id) - return unless user - return if Feature.enabled?(:remove_trust_scores, user) - - cache_key = "abuse:trust_score_cleanup_worker:#{user.id}:#{source}" - return if Rails.cache.exist?(cache_key) - - AntiAbuse::UserTrustScore.new(user).remove_old_scores(source) - Rails.cache.write(cache_key, true, expires_in: 5.minutes) + # nop end end end diff --git a/app/workers/anti_abuse/trust_score_worker.rb b/app/workers/anti_abuse/trust_score_worker.rb index 55f37406dcb6b3..f6aea8d8845701 100644 --- a/app/workers/anti_abuse/trust_score_worker.rb +++ b/app/workers/anti_abuse/trust_score_worker.rb @@ -11,16 +11,7 @@ class TrustScoreWorker urgency :low def perform(user_id, source, score, correlation_id = '') - user = User.find_by_id(user_id) - unless user - logger.info(structured_payload(message: "User not found.", user_id: user_id)) - return - end - - return if Feature.enabled?(:remove_trust_scores, user) - - AntiAbuse::TrustScore.create!(user: user, source: source, score: score.to_f, correlation_id_value: correlation_id) - AntiAbuse::TrustScoreCleanupWorker.perform_async(user.id, source) + # nop end end end diff --git a/config/feature_flags/gitlab_com_derisk/remove_trust_scores.yml b/config/feature_flags/gitlab_com_derisk/remove_trust_scores.yml deleted file mode 100644 index 08f0025e723803..00000000000000 --- a/config/feature_flags/gitlab_com_derisk/remove_trust_scores.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: remove_trust_scores -description: -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/553436 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/207883 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/574950 -milestone: '18.5' -group: group::authorization -type: gitlab_com_derisk -default_enabled: false diff --git a/ee/app/services/arkose/record_user_data_service.rb b/ee/app/services/arkose/record_user_data_service.rb index 42ca4603c7faf8..bf976a4b1fb8d9 100644 --- a/ee/app/services/arkose/record_user_data_service.rb +++ b/ee/app/services/arkose/record_user_data_service.rb @@ -61,10 +61,7 @@ def custom_attributes end def store_risk_scores - return if Feature.enabled?(:remove_trust_scores, user) - - AntiAbuse::TrustScoreWorker.perform_async(user.id, :arkose_global_score, response.global_score.to_f) - AntiAbuse::TrustScoreWorker.perform_async(user.id, :arkose_custom_score, response.custom_score.to_f) + # nop end def logger diff --git a/ee/app/services/phone_verification/users/record_user_data_service.rb b/ee/app/services/phone_verification/users/record_user_data_service.rb index 27cad17a187e74..b97a24940ac5c4 100644 --- a/ee/app/services/phone_verification/users/record_user_data_service.rb +++ b/ee/app/services/phone_verification/users/record_user_data_service.rb @@ -31,9 +31,7 @@ def error_related_to_high_risk_user end def store_risk_score(risk_score) - return if Feature.enabled?(:remove_trust_scores, user) - - AntiAbuse::TrustScoreWorker.perform_async(user.id, :telesign, risk_score.to_f) + # nop end def assume_high_risk? diff --git a/ee/spec/services/arkose/record_user_data_service_spec.rb b/ee/spec/services/arkose/record_user_data_service_spec.rb index 25b998ccda38da..f5c57739a07f99 100644 --- a/ee/spec/services/arkose/record_user_data_service_spec.rb +++ b/ee/spec/services/arkose/record_user_data_service_spec.rb @@ -30,19 +30,6 @@ expect(user.custom_attributes.find_by(key: 'arkose_custom_score').value).to eq('0') end - it 'executes abuse trust score workers' do - stub_feature_flags(remove_trust_scores: false) - - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.ordered.with( - user.id, :arkose_global_score, 0.0 - ) - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.ordered.with( - user.id, :arkose_custom_score, 0.0 - ) - - service.execute - end - it 'logs user risk band assignment event' do init_args = { session_token: nil, user: user, verify_response: response } expect_next_instance_of(::Arkose::Logger, init_args) do |logger| @@ -71,21 +58,6 @@ expect { service.execute }.not_to change { user.custom_attributes.count } end - it 'does not store the arkose risk scores in abuse trust scores' do - stub_feature_flags(remove_trust_scores: false) - - # Create and store initial scores - create(:abuse_trust_score, user: user, score: 13.0, source: :arkose_global_score) - create(:abuse_trust_score, user: user, score: 11.0, source: :arkose_custom_score) - service.execute - - # Due to failed verification, there are no returned scores in arkose_verify_response, - # we should expect `arkose_global_score` and `arkose_custom_score` not to be overwritten - # and remain as the initial scores - expect(user_scores.arkose_global_score).to eq(13.0) - expect(user_scores.arkose_custom_score).to eq(11.0) - end - it 'does not create a Users::ArkoseSession for the user' do expect { service.execute }.not_to change { user.arkose_sessions.count } end diff --git a/ee/spec/services/phone_verification/users/record_user_data_service_spec.rb b/ee/spec/services/phone_verification/users/record_user_data_service_spec.rb index af07a604a1d24e..84315091f27892 100644 --- a/ee/spec/services/phone_verification/users/record_user_data_service_spec.rb +++ b/ee/spec/services/phone_verification/users/record_user_data_service_spec.rb @@ -35,29 +35,12 @@ expect(phone_verification_record).not_to be_persisted end - it 'executes the abuse trust score worker' do - stub_feature_flags(remove_trust_scores: false) - - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.with(user.id, :telesign, - low_risk_score.to_f) - - service.execute - end - context 'when the risk score is 0' do let(:risk_score) { 0 } it 'changes the phone validation record risk score to 1' do expect { service.execute }.to change { phone_verification_record.risk_score }.from(0).to(1) end - - it 'executes the abuse trust score worker with a risk score of 1.0' do - stub_feature_flags(remove_trust_scores: false) - - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.with(user.id, :telesign, 1.0) - - service.execute - end end context 'when the user is high risk' do diff --git a/spec/models/anti_abuse/user_trust_score_spec.rb b/spec/models/anti_abuse/user_trust_score_spec.rb deleted file mode 100644 index d2e691fed824df..00000000000000 --- a/spec/models/anti_abuse/user_trust_score_spec.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AntiAbuse::UserTrustScore, feature_category: :instance_resiliency do - let_it_be(:user1) { create(:user) } - let_it_be(:user2) { create(:user) } - let(:user_1_scores) { described_class.new(user1) } - let(:user_2_scores) { described_class.new(user2) } - - before do - stub_feature_flags(remove_trust_scores: false) - end - - describe '#spammer?' do - context 'when the user is a spammer' do - before do - allow(user_1_scores).to receive(:spam_score).and_return(0.9) - end - - it 'classifies the user as a spammer' do - expect(user_1_scores).to be_spammer - end - end - - context 'when the user is not a spammer' do - before do - allow(user_1_scores).to receive(:spam_score).and_return(0.1) - end - - it 'does not classify the user as a spammer' do - expect(user_1_scores).not_to be_spammer - end - end - end - - describe '#spam_score' do - context 'when the user is a spammer' do - before do - create(:abuse_trust_score, user: user1, score: 0.8) - create(:abuse_trust_score, user: user1, score: 0.9) - end - - it 'returns the expected score' do - expect(user_1_scores.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: user1, score: 0.1) - create(:abuse_trust_score, user: user1, score: 0.0) - end - - it 'returns the expected score' do - expect(user_1_scores.spam_score).to be_within(0.01).of(0.05) - end - end - end - - describe '#telesign_score' do - 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(user_1_scores.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(user_2_scores.telesign_score).to be(0.0) - end - end - end - - describe '#arkose_global_score' do - 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(user_1_scores.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(user_2_scores.arkose_global_score).to be(0.0) - end - end - end - - describe '#arkose_custom_score' do - 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(user_1_scores.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(user_2_scores.arkose_custom_score).to be(0.0) - end - end - end - - describe '#remove_old_scores' do - let(:source) { :spamcheck } - - subject(:remove_old_scores) { described_class.new(user1).remove_old_scores(source) } - - context 'if max events is exceeded' do - before do - stub_const('AntiAbuse::UserTrustScore::MAX_EVENTS', 2) - end - - it 'removes the oldest events' do - first = create(:abuse_trust_score, source: source, user: user1) - create(:abuse_trust_score, source: source, user: user1) - create(:abuse_trust_score, source: source, user: user1) - - expect { remove_old_scores }.to change { user1.abuse_trust_scores.count }.from(3).to(2) - expect(AntiAbuse::TrustScore.find_by_id(first.id)).to eq(nil) - end - end - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5ca4f40736dd47..33c3b66c6a3579 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -278,7 +278,6 @@ it { is_expected.to have_many(:achievements).through(:user_achievements).class_name('Achievements::Achievement').inverse_of(:users) } it { is_expected.to have_many(:namespace_commit_emails).class_name('Users::NamespaceCommitEmail') } it { is_expected.to have_many(:audit_events).with_foreign_key(:author_id).inverse_of(:user) } - it { is_expected.to have_many(:abuse_trust_scores).class_name('AntiAbuse::TrustScore').dependent(:destroy) } it { is_expected.to have_many(:issue_assignment_events).class_name('ResourceEvents::IssueAssignmentEvent') } it { is_expected.to have_many(:merge_request_assignment_events).class_name('ResourceEvents::MergeRequestAssignmentEvent') } it { is_expected.to have_many(:early_access_program_tracking_events).class_name('EarlyAccessProgram::TrackingEvent') } @@ -7561,79 +7560,6 @@ def add_user it_behaves_like 'schedules user for deletion without delay' end - context 'when the user is a spammer' do - before do - stub_feature_flags(remove_trust_scores: false) - - user_scores = AntiAbuse::UserTrustScore.new(user) - allow(AntiAbuse::UserTrustScore).to receive(:new).and_return(user_scores) - allow(user_scores).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 { delete_async }.to change { AbuseReport.count }.from(0).to(1) - expect(AbuseReport.last.attributes).to include({ - reporter_id: Users::Internal.for_organization(user.organization).security_bot.id, - organization_id: Users::Internal.security_bot.organization_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 - delete_async - - 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 - delete_async - - expect(user).to be_banned - end - - context 'when there is an existing abuse report' do - let!(:abuse_report) do - create(:abuse_report, user: user, reporter: Users::Internal.for_organization(user.organization).security_bot, message: 'Existing') - end - - it 'updates the abuse report' do - delete_async - 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 - delete_async - - 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 - delete_async - - 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}" diff --git a/spec/services/spam/spam_verdict_service_spec.rb b/spec/services/spam/spam_verdict_service_spec.rb index d52d95e4ec5928..51a347ff3c4d8e 100644 --- a/spec/services/spam/spam_verdict_service_spec.rb +++ b/spec/services/spam/spam_verdict_service_spec.rb @@ -274,7 +274,6 @@ let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::NOOP } it 'returns the verdict' do - expect(AntiAbuse::TrustScoreWorker).not_to receive(:perform_async) is_expected.to eq(NOOP) end end @@ -285,9 +284,6 @@ context 'the result was evaluated' do it 'returns the verdict and updates the spam score' do - stub_feature_flags(remove_trust_scores: false) - - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.with(user.id, :spamcheck, instance_of(Float), 'cid') is_expected.to eq(ALLOW) end end @@ -296,7 +292,6 @@ let(:verdict_evaluated) { false } it 'returns the verdict and does not update the spam score' do - expect(AntiAbuse::TrustScoreWorker).not_to receive(:perform_async) expect(subject).to eq(ALLOW) end end @@ -318,9 +313,6 @@ with_them do it "returns expected spam constant and updates the spam score" do - stub_feature_flags(remove_trust_scores: false) - - expect(AntiAbuse::TrustScoreWorker).to receive(:perform_async).once.with(user.id, :spamcheck, instance_of(Float), 'cid') is_expected.to eq(expected) end end diff --git a/spec/workers/anti_abuse/trust_score_cleanup_worker_spec.rb b/spec/workers/anti_abuse/trust_score_cleanup_worker_spec.rb deleted file mode 100644 index ce7a566aa7f7d7..00000000000000 --- a/spec/workers/anti_abuse/trust_score_cleanup_worker_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AntiAbuse::TrustScoreCleanupWorker, :clean_gitlab_redis_shared_state, feature_category: :instance_resiliency do - let(:worker) { described_class.new } - let(:source) { Enums::Abuse::Source.sources[:telesign] } - let_it_be(:user) { create(:user) } - - subject(:perform) { worker.perform(user.id, source) } - - it_behaves_like 'an idempotent worker' do - let(:job_args) { [user.id, source] } - end - - context "when the user does not exist" do - before do - allow(User).to receive(:find_by_id).with(user.id).and_return(nil) - end - - it 'returns early' do - expect(Rails.cache).not_to receive(:exist?) - expect(AntiAbuse::UserTrustScore).not_to receive(:new) - - perform - end - end - - context "when the user exists" do - before do - stub_feature_flags(remove_trust_scores: false) - end - - context "when the cache key exists" do - it 'returns early' do - expect(Rails.cache).to receive(:exist?).and_return(true) - expect(AntiAbuse::UserTrustScore).not_to receive(:new) - - perform - end - end - - context "when the cache key does not exist" do - it 'removes old scores for the user' do - expect(Rails.cache).to receive(:exist?).and_return(false) - expect_next_instance_of(AntiAbuse::UserTrustScore, user) do |instance| - expect(instance).to receive(:remove_old_scores).with(source) - end - - perform - end - - it 'sets the cache_key' do - cache_key = "abuse:trust_score_cleanup_worker:#{user.id}:#{source}" - expect(Rails.cache).to receive(:write).with(cache_key, true, expires_in: 5.minutes) - - perform - end - end - end -end diff --git a/spec/workers/anti_abuse/trust_score_worker_spec.rb b/spec/workers/anti_abuse/trust_score_worker_spec.rb deleted file mode 100644 index a2aa74a037799f..00000000000000 --- a/spec/workers/anti_abuse/trust_score_worker_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AntiAbuse::TrustScoreWorker, :clean_gitlab_redis_shared_state, feature_category: :instance_resiliency do - let(:worker) { described_class.new } - let_it_be(:user) { create(:user) } - - subject(:perform) { worker.perform(user.id, :telesign, 0.85, 'foo') } - - it_behaves_like 'an idempotent worker' do - let(:job_args) { [user.id, :telesign, 0.5] } - end - - context "when the user does not exist" do - let(:log_payload) { { 'message' => 'User not found.', 'user_id' => user.id } } - - before do - allow(User).to receive(:find_by_id).with(user.id).and_return(nil) - end - - it 'logs an error' do - expect(Sidekiq.logger).to receive(:info).with(hash_including(log_payload)) - - expect { perform }.not_to raise_exception - end - - it 'does not attempt to create the trust score' do - expect(AntiAbuse::TrustScore).not_to receive(:create!) - - perform - end - end - - context "when the user exists" do - it 'creates an abuse trust score with the correct data' do - stub_feature_flags(remove_trust_scores: false) - - expect { perform }.to change { AntiAbuse::TrustScore.count }.from(0).to(1) - expect(AntiAbuse::TrustScore.last.attributes).to include({ - user_id: user.id, - source: "telesign", - score: 0.85, - correlation_id_value: 'foo' - }.stringify_keys) - end - end -end -- GitLab