From 41f78b31a940febc9ce6355c6a75ddcd48c4ef99 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Mon, 4 Mar 2024 09:30:30 +1100 Subject: [PATCH] FIX: down downgrade trust level if all requirements are met. (#25953) Currently, the trust level method is calculating trust level based on maximum value from: - locked trust level - group automatic trust level - previously granted trust level by admin https://github.com/discourse/discourse/blob/main/lib/trust_level.rb#L33 Let's say the user belongs to groups with automatic trust level 1 and in the meantime meets all criteria to get trust level 2. Each time, a user is removed from a group with automatic trust_level 1, they will be downgraded to trust_level 1 and promoted to trust_level 2 https://github.com/discourse/discourse/blob/120a2f70a9ea3b08a39fc1fbb251f59ecf968cde/lib/promotion.rb#L142 This will cause duplicated promotion messages. Therefore, we have to check if the user meets the criteria, before downgrading. --- lib/promotion.rb | 11 +++++++++++ spec/models/group_user_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/promotion.rb b/lib/promotion.rb index f18b4924c5d..7c22ede7c17 100644 --- a/lib/promotion.rb +++ b/lib/promotion.rb @@ -137,6 +137,9 @@ class Promotion TrustLevel.calculate(user, use_previous_trust_level: use_previous_trust_level) || TrustLevel[0] + granted_trust_level = user.trust_level if granted_trust_level < user.trust_level && + !can_downgrade_trust_level?(user) + # TrustLevel.calculate always returns a value, however we added extra protection just # in case this changes user.update_column(:trust_level, TrustLevel[granted_trust_level]) @@ -153,4 +156,12 @@ class Promotion user.change_trust_level!(TrustLevel[2], log_action_for: performed_by || Discourse.system_user) end end + + def self.can_downgrade_trust_level?(user) + return false if user.trust_level == TrustLevel[1] && tl1_met?(user) + return false if user.trust_level == TrustLevel[2] && tl2_met?(user) + return false if user.trust_level == TrustLevel[3] && tl3_met?(user) + + true + end end diff --git a/spec/models/group_user_spec.rb b/spec/models/group_user_spec.rb index 58f5252532d..05014c38474 100644 --- a/spec/models/group_user_spec.rb +++ b/spec/models/group_user_spec.rb @@ -303,5 +303,27 @@ RSpec.describe GroupUser do # keep in mind that we do not restore tl3, cause reqs can be lost expect(user.reload.trust_level).to eq(2) end + + it "protects user trust level if all requirements are met" do + Promotion.stubs(:tl2_met?).returns(true) + + user = Fabricate(:user) + expect(user.trust_level).to eq(1) + + group.update!(grant_trust_level: 1) + + Promotion.recalculate(user) + expect(user.reload.trust_level).to eq(2) + + group_user = Fabricate(:group_user, group: group, user: user) + expect_not_enqueued_with( + job: :send_system_message, + args: { + user_id: user.id, + message_type: "tl2_promotion_message", + }, + ) { group_user.destroy! } + expect(user.reload.trust_level).to eq(2) + end end end