Files
discourse/spec/requests/reviewable_claimed_topics_controller_spec.rb
Gary Pendergast b77d0f7589 FEATURE: Sync Reviewable Status (#31901)
When multiple admins are working in the review queue, it's quite easy for two people to try and handle the same reviewable at the same time. This change addresses the two major situations where this can occur.

The `ReviewableClaimedTopic` model has been extended to allow the system to mark a reviewable as claimed as soon as the first moderator starts handling the reviewable, even when the `reviewable_claiming` setting is disabled. This ensures that reviewable actions with client-site activity (for example, `agree_and_suspend`) will lock the reviewable before another moderator starts working on it.

When someone handles handles a reviewable, we now use `MessageBus` to inform other moderators that it's changed. If any of the other moderator have that reviewable open (either individually, or on the list screen), it will automatically refresh that data.
2025-03-24 14:27:18 +11:00

237 lines
7.6 KiB
Ruby

# frozen_string_literal: true
RSpec.describe ReviewableClaimedTopicsController do
fab!(:moderator)
fab!(:topic)
fab!(:automatic_topic) { Fabricate(:topic) }
fab!(:reviewable) { Fabricate(:reviewable_flagged_post, topic: topic) }
fab!(:automatic_reviewable) { Fabricate(:reviewable_flagged_post, topic: automatic_topic) }
describe "#create" do
let(:params) { { reviewable_claimed_topic: { topic_id: topic.id } } }
it "requires user to be logged in" do
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(403)
end
context "when logged in" do
before do
SiteSetting.reviewable_claiming = "optional"
sign_in(moderator)
end
it "works" do
messages =
MessageBus.track_publish("/reviewable_claimed") do
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(200)
end
expect(
ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: topic.id).exists?,
).to eq(true)
expect(
topic
.reviewables
.first
.history
.where(reviewable_history_type: ReviewableHistory.types[:claimed])
.size,
).to eq(1)
expect(messages.size).to eq(1)
message = messages[0]
expect(message.data[:topic_id]).to eq(topic.id)
expect(message.data[:user][:id]).to eq(moderator.id)
expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
end
it "publishes reviewable claimed changes to the category moderators of the topic's category" do
SiteSetting.enable_category_group_moderation = true
SiteSetting.reviewable_claiming = "optional"
group = Fabricate(:group)
Fabricate(:category_moderation_group, category: topic.category, group:)
messages =
MessageBus.track_publish("/reviewable_claimed") do
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(200)
end
expect(messages.size).to eq(1)
message = messages[0]
expect(message.data[:topic_id]).to eq(topic.id)
expect(message.data[:user][:id]).to eq(moderator.id)
expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff], group.id)
end
it "works with deleted topics" do
first_post = topic.first_post || Fabricate(:post, topic: topic)
PostDestroyer.new(Discourse.system_user, first_post).destroy
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(200)
expect(
ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: topic.id).exists?,
).to eq(true)
end
it "raises an error if user cannot claim the topic" do
SiteSetting.reviewable_claiming = "disabled"
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(403)
end
it "allows claiming when automatic param is present" do
SiteSetting.reviewable_claiming = "disabled"
params[:reviewable_claimed_topic][:topic_id] = automatic_topic.id
params[:reviewable_claimed_topic][:automatic] = "true"
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(200)
expect(
ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: automatic_topic.id).exists?,
).to eq(true)
end
it "raises an error if topic is already claimed" do
post "/reviewable_claimed_topics.json", params: params
expect(
ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: topic.id).exists?,
).to eq(true)
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(409)
end
it "queues a sidekiq job to refresh reviewable counts for users who can see the reviewable" do
SiteSetting.navigation_menu = "sidebar"
SiteSetting.enable_category_group_moderation = true
not_notified = Fabricate(:user)
group = Fabricate(:group)
Fabricate(:category_moderation_group, category: topic.category, group:)
notified = Fabricate(:user)
group.add(notified)
expect_enqueued_with(
job: :refresh_users_reviewable_counts,
args: {
group_ids: [Group::AUTO_GROUPS[:staff], group.id],
},
) do
post "/reviewable_claimed_topics.json", params: params
expect(response.status).to eq(200)
end
end
end
end
describe "#destroy" do
fab!(:claimed) { Fabricate(:reviewable_claimed_topic, topic: topic) }
fab!(:automatic_claimed) do
Fabricate(:reviewable_claimed_topic, topic: automatic_topic, automatic: true)
end
before { sign_in(moderator) }
it "works" do
SiteSetting.reviewable_claiming = "optional"
messages =
MessageBus.track_publish("/reviewable_claimed") do
delete "/reviewable_claimed_topics/#{claimed.topic_id}.json"
expect(response.status).to eq(200)
end
expect(ReviewableClaimedTopic.where(topic_id: claimed.topic_id).exists?).to eq(false)
expect(
topic
.reviewables
.first
.history
.where(reviewable_history_type: ReviewableHistory.types[:unclaimed])
.size,
).to eq(1)
expect(messages.size).to eq(1)
message = messages[0]
expect(message.data[:topic_id]).to eq(topic.id)
expect(message.data[:user]).to eq(nil)
expect(message.group_ids).to contain_exactly(Group::AUTO_GROUPS[:staff])
end
it "works with deleted topics" do
SiteSetting.reviewable_claiming = "optional"
first_post = topic.first_post || Fabricate(:post, topic: topic)
PostDestroyer.new(Discourse.system_user, first_post).destroy
delete "/reviewable_claimed_topics/#{claimed.topic_id}.json"
expect(response.status).to eq(200)
expect(ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: topic.id).exists?).to eq(
false,
)
end
it "raises an error if topic is missing" do
delete "/reviewable_claimed_topics/111111111.json"
expect(response.status).to eq(404)
end
it "raises an error if user cannot claim the topic" do
delete "/reviewable_claimed_topics/#{claimed.topic_id}.json"
expect(response.status).to eq(403)
end
it "allows unclaiming when automatic param is present" do
SiteSetting.reviewable_claiming = "disabled"
delete "/reviewable_claimed_topics/#{automatic_claimed.topic_id}.json?automatic=true"
expect(response.status).to eq(200)
expect(
ReviewableClaimedTopic.where(user_id: moderator.id, topic_id: automatic_topic.id).exists?,
).to eq(false)
end
it "queues a sidekiq job to refresh reviewable counts for users who can see the reviewable" do
SiteSetting.reviewable_claiming = "optional"
SiteSetting.navigation_menu = "sidebar"
SiteSetting.enable_category_group_moderation = true
not_notified = Fabricate(:user)
group = Fabricate(:group)
Fabricate(:category_moderation_group, category: topic.category, group:)
notified = Fabricate(:user)
group.add(notified)
expect_enqueued_with(
job: :refresh_users_reviewable_counts,
args: {
group_ids: [Group::AUTO_GROUPS[:staff], group.id],
},
) do
delete "/reviewable_claimed_topics/#{claimed.topic_id}.json"
expect(response.status).to eq(200)
end
end
end
end