mirror of
https://github.com/discourse/discourse.git
synced 2026-08-11 02:59:07 +08:00
Silenced users can now no longer like posts or use reactions, which closes a potential griefing vector that was difficult for moderators to monitor. The implementation adds a silenced check to the guardian's post_can_act? method for likes, and introduces a new can_use_reactions? guardian method in the discourse-reactions plugin that delegates to the same logic. This ensures both features share the same authorization path. Additionally, silenced users' custom status is now shadow-banned: visible to themselves and staff, but hidden from other users. A new `can_see_user_status?` guardian method centralizes the visibility logic, used by serializers and MessageBus publishing. Status updates from silenced users are now only broadcast to themselves and staff. Also includes minor CSS fixes for user status spacing and alignment. Chat reactions already had proper silenced user checks in place via the can_react? guardian method, so no changes were needed there. Ref - t/140084
242 lines
9.1 KiB
Ruby
Vendored
242 lines
9.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe DiscourseReactions::ReactionManager do
|
|
def reaction_manager(reaction_value)
|
|
described_class.new(reaction_value: reaction_value, user: user, post: post)
|
|
end
|
|
|
|
fab!(:user)
|
|
fab!(:post)
|
|
fab!(:reaction_plus_one) { Fabricate(:reaction, reaction_value: "+1", post: post) }
|
|
fab!(:reaction_minus_one) { Fabricate(:reaction, reaction_value: "-1", post: post) }
|
|
fab!(:reaction_clap) { Fabricate(:reaction, reaction_value: "clap", post: post) }
|
|
fab!(:reaction_hugs) { Fabricate(:reaction, reaction_value: "hugs", post: post) }
|
|
|
|
before { SiteSetting.discourse_reactions_reaction_for_like = "clap" }
|
|
|
|
describe ".toggle!" do
|
|
context "when user is silenced" do
|
|
before { user.update!(silenced_till: 1.year.from_now) }
|
|
|
|
it "raises InvalidAccess when trying to toggle reaction" do
|
|
expect { reaction_manager("clap").toggle! }.to raise_error(Discourse::InvalidAccess)
|
|
end
|
|
end
|
|
|
|
context "when switching to the default reaction" do
|
|
fab!(:topic)
|
|
fab!(:op_post) { Fabricate(:post, topic: topic) }
|
|
|
|
before do
|
|
SiteSetting.discourse_reactions_enabled_reactions = "+1|heart"
|
|
SiteSetting.discourse_reactions_reaction_for_like = "+1"
|
|
SiteSetting.discourse_reactions_like_icon = "thumbs-up"
|
|
end
|
|
|
|
it "persists the default reaction" do
|
|
reaction_manager = described_class.new(reaction_value: "heart", user: user, post: op_post)
|
|
reaction_manager.toggle!
|
|
|
|
expect(DiscourseReactions::ReactionUser.exists?(user: user, post: op_post)).to be_truthy
|
|
|
|
new_reaction_manager = described_class.new(reaction_value: "+1", user: user, post: op_post)
|
|
new_reaction_manager.toggle!
|
|
|
|
expect(
|
|
DiscourseReactions::ReactionUser.exists?(
|
|
user: user,
|
|
post: op_post,
|
|
reaction: DiscourseReactions::Reaction.find_by(reaction_value: "+1", post: op_post),
|
|
),
|
|
).to be_truthy
|
|
expect(PostAction.exists?(user: user, post: op_post)).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "when the user has not yet reacted to the post" do
|
|
context "when the new reaction matches discourse_reactions_reaction_for_like" do
|
|
it "does create a PostAction record" do
|
|
expect { reaction_manager("clap").toggle! }.to change { PostAction.count }.by(1)
|
|
end
|
|
|
|
it "does not create a ReactionUser record" do
|
|
expect { reaction_manager("clap").toggle! }.not_to change {
|
|
DiscourseReactions::ReactionUser.count
|
|
}
|
|
end
|
|
|
|
it "creates a reaction notification" do
|
|
expect { reaction_manager("clap").toggle! }.to change { Notification.count }.by(1)
|
|
end
|
|
end
|
|
|
|
context "when the new reaction does not match discourse_reactions_reaction_for_like" do
|
|
it "does create a PostAction record" do
|
|
expect { reaction_manager("+1").toggle! }.to change { PostAction.count }
|
|
end
|
|
|
|
it "does create a ReactionUser record" do
|
|
expect { reaction_manager("+1").toggle! }.to change {
|
|
DiscourseReactions::ReactionUser.count
|
|
}
|
|
end
|
|
|
|
it "creates a reaction notification" do
|
|
expect { reaction_manager("+1").toggle! }.to change { Notification.count }.by(1)
|
|
end
|
|
|
|
context "when the reaction is in discourse_reactions_excluded_from_like" do
|
|
before { SiteSetting.discourse_reactions_excluded_from_like = "+1" }
|
|
|
|
it "does not create a PostAction record" do
|
|
expect { reaction_manager("+1").toggle! }.not_to change { PostAction.count }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the user already reacted to the Post" do
|
|
context "when the existing reaction was a ReactionUser" do
|
|
let!(:reaction_user) do
|
|
Fabricate(:reaction_user, user: user, post: post, reaction: reaction_plus_one)
|
|
end
|
|
|
|
context "when the user has permission to delete the ReactionUser" do
|
|
it "removes the ReactionUser for the old +1 reaction" do
|
|
reaction_manager("-1").toggle!
|
|
expect(DiscourseReactions::ReactionUser.find_by(id: reaction_user.id)).to be_nil
|
|
end
|
|
|
|
it "removes any PostAction that exists as well" do
|
|
expect { reaction_manager("-1").toggle! }.to change { PostAction.count }.by(-1)
|
|
end
|
|
|
|
it "adds a new ReactionUser record for the new reaction -1 but not PostAction because of discourse_reactions_excluded_from_like" do
|
|
reaction_manager("-1").toggle!
|
|
expect(
|
|
DiscourseReactions::ReactionUser.find_by(
|
|
reaction: reaction_minus_one,
|
|
user: user,
|
|
post: post,
|
|
),
|
|
).to be_present
|
|
expect(
|
|
PostAction.find_by(
|
|
post: post,
|
|
user: user,
|
|
post_action_type_id: PostActionType::LIKE_POST_ACTION_ID,
|
|
),
|
|
).to be_nil
|
|
end
|
|
|
|
it "adds a new ReactionUser record and a PostAction record for reaction hugs" do
|
|
reaction_manager("hugs").toggle!
|
|
expect(
|
|
DiscourseReactions::ReactionUser.find_by(
|
|
reaction: reaction_hugs,
|
|
user: user,
|
|
post: post,
|
|
),
|
|
).to be_present
|
|
expect(
|
|
PostAction.find_by(
|
|
post: post,
|
|
user: user,
|
|
post_action_type_id: PostActionType::LIKE_POST_ACTION_ID,
|
|
),
|
|
).to be_present
|
|
end
|
|
|
|
it "deletes any notifications for the old Reaction and creates a notification for the new reaction" do
|
|
DiscourseReactions::ReactionNotification.new(reaction_plus_one, user).create
|
|
expect { reaction_manager("-1").toggle! }.not_to change { Notification.count }
|
|
expect(
|
|
Notification.where(
|
|
notification_type: Notification.types[:reaction],
|
|
topic_id: post.topic_id,
|
|
user_id: post.user_id,
|
|
post_number: post.post_number,
|
|
).count,
|
|
).to eq(1)
|
|
end
|
|
|
|
it "removes the Reaction record attached to the post when no more users have reacted to it" do
|
|
expect { reaction_manager("-1").toggle! }.to change {
|
|
DiscourseReactions::Reaction.where(id: reaction_plus_one).count
|
|
}.by(-1)
|
|
end
|
|
|
|
context "when the previous reaction is the same as the new one" do
|
|
before { reaction_user.update!(reaction: reaction_minus_one) }
|
|
|
|
it "does not add a new ReactionUser record, just removes the old one" do
|
|
expect { reaction_manager("-1").toggle! }.to change {
|
|
DiscourseReactions::ReactionUser.count
|
|
}.by(-1).and change { PostAction.count }.by(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the user does not have permission to delete the ReactionUser" do
|
|
before do
|
|
reaction_user.update!(
|
|
created_at: Time.zone.now - (SiteSetting.post_undo_action_window_mins + 1).minutes,
|
|
)
|
|
end
|
|
|
|
it "raises an error" do
|
|
expect { reaction_manager("-1").toggle! }.to raise_error(Discourse::InvalidAccess)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the existing reaction counted as a PostAction (Like) without a matching ReactionUser" do
|
|
let!(:post_action) do
|
|
Fabricate(
|
|
:post_action,
|
|
post: post,
|
|
user: user,
|
|
post_action_type_id: PostActionType::LIKE_POST_ACTION_ID,
|
|
)
|
|
end
|
|
|
|
context "when the user has permission to delete the PostAction" do
|
|
it "removes the PostAction" do
|
|
expect { reaction_manager("-1").toggle! }.to change { PostAction.count }.by(-1)
|
|
end
|
|
|
|
it "removes the Reaction record attached to the post" do
|
|
expect { reaction_manager("-1").toggle! }.to change {
|
|
DiscourseReactions::Reaction.where(id: reaction_clap).count
|
|
}.by(-1)
|
|
end
|
|
|
|
it "deletes any notifications for the old Reaction and creates a notification for the new reaction" do
|
|
DiscourseReactions::ReactionNotification.new(reaction_clap, user).create
|
|
expect { reaction_manager("-1").toggle! }.not_to change { Notification.count }
|
|
expect(
|
|
Notification.where(
|
|
notification_type: Notification.types[:reaction],
|
|
topic_id: post.topic_id,
|
|
user_id: post.user_id,
|
|
post_number: post.post_number,
|
|
).count,
|
|
).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "when the user does not have permission to delete the PostAction" do
|
|
before do
|
|
post_action.update!(
|
|
created_at: Time.zone.now - (SiteSetting.post_undo_action_window_mins + 1).minutes,
|
|
)
|
|
end
|
|
|
|
it "raises an error" do
|
|
expect { reaction_manager("-1").toggle! }.to raise_error(Discourse::InvalidAccess)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|