0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/spec/requests/reviewables_controller_spec.rb
Alan Guo Xiang Tan b03428cfec
FIX: Resolve all of a deleted spammer's reviewables (#40780)
When a moderator picks "Delete User" or "Delete and Block User" on one
of the spammer's flagged posts, deleting the user is meant to settle all
of their reviewables, since there is no user left to act on. Previously
only part of that happened:

- `UserDestroyer#agree_with_flags` resolved the spammer's other flagged
posts by checking for the `agree_and_keep` action, but a flag on a
hidden post only offers `agree_and_keep_hidden`, so those flags were
skipped and stayed pending forever.
- Queued posts by the spammer were never touched, so they also stayed
pending.
- `UserDestroyer#destroy` skips resolving the spammer's account
reviewable when a `reviewable_id` option is present. That option is
meant to prevent the account reviewable from resolving itself twice when
the deletion starts from it, but the guard only checked presence, so a
deletion starting from a flagged post also skipped the account
reviewable.
- The browser updates review queue rows from message bus broadcasts.
Those can arrive after the page has stopped waiting, so the acting
moderator kept seeing the affected rows as "Pending" until a full
reload. Acting on one of those stale rows returns a 403 because the
target user no longer exists.

This PR resolves every reviewable tied to the deleted user and updates
the acting moderator's review queue without a reload.
2026-06-15 13:34:15 +08:00

1222 lines
41 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe ReviewablesController do
context "when anonymous" do
it "denies listing" do
get "/review.json"
expect(response.code).to eq("403")
end
it "denies performing" do
put "/review/123/perform/approve.json"
expect(response.code).to eq("403")
end
it "denies settings" do
get "/review/settings.json"
expect(response.code).to eq("403")
end
it "denies deleting" do
delete "/review/123"
expect(response.code).to eq("403")
end
it "denies count" do
get "/review/count.json"
expect(response.code).to eq("403")
end
end
context "when regular user" do
before { sign_in(Fabricate(:user)) }
it "does not allow settings" do
get "/review/settings.json"
expect(response.code).to eq("403")
end
end
context "when logged in" do
fab!(:admin)
before { sign_in(admin) }
describe "#index" do
it "returns empty JSON when nothing to review" do
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to eq([])
end
it "returns JSON with reviewable content" do
reviewable = Fabricate(:reviewable_flagged_post)
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
json_review = json["reviewables"][0]
expect(json_review["id"]).to eq(reviewable.id)
expect(json_review["created_by_id"]).to eq(reviewable.created_by_id)
expect(json_review["status"]).to eq(Reviewable.statuses[:pending])
expect(json_review["type"]).to eq("ReviewableFlaggedPost")
expect(json_review["target_created_by_id"]).to eq(reviewable.target_created_by_id)
expect(json_review["score"]).to eq(reviewable.score)
expect(json_review["version"]).to eq(reviewable.version)
expect(json["users"].any? { |u| u["id"] == reviewable.created_by_id }).to eq(true)
expect(json["users"].any? { |u| u["id"] == reviewable.target_created_by_id }).to eq(true)
expect(json["meta"]["reviewable_count"]).to eq(1)
expect(json["meta"]["unseen_reviewable_count"]).to eq(1)
expect(json["meta"]["status"]).to eq("pending")
end
context "with trashed topics and posts" do
fab!(:post1, :post)
fab!(:reviewable) do
Fabricate(
:reviewable,
target_id: post1.id,
target_type: "Post",
topic: post1.topic,
type: "ReviewableFlaggedPost",
category: post1.topic.category,
)
end
fab!(:moderator)
let(:topic) { post1.topic }
fab!(:category_mod, :user)
fab!(:group)
fab!(:group_user) { GroupUser.create!(group_id: group.id, user_id: category_mod.id) }
fab!(:mod_group) do
CategoryModerationGroup.create!(category_id: post1.topic.category.id, group_id: group.id)
end
it "supports returning information for trashed topics and posts to staff" do
sign_in(moderator)
topic.trash!
post1.trash!
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
reviewable_json = json["reviewables"].find { |r| r["id"] == reviewable.id }
topic_json = json["topics"].find { |t| t["id"] == topic.id }
expect(reviewable_json["raw"]).to eq(post1.raw)
expect(reviewable_json["deleted_at"]).to be_present
expect(topic_json["title"]).to eq(topic.title)
end
it "does not return information for trashed topics and posts to category mods" do
SiteSetting.enable_category_group_moderation = true
sign_in(category_mod)
post1.trash!
topic.trash!
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
reviewable_json = json["reviewables"].find { |r| r["id"] == reviewable.id }
expect(reviewable_json["raw"]).to be_blank
end
end
it "supports filtering by flag reason" do
# this is not flagged by the user
reviewable = Fabricate(:reviewable)
reviewable.reviewable_scores.create!(
user: admin,
score: 1000,
status: "pending",
reviewable_score_type: 1,
)
reviewable = Fabricate(:reviewable)
user = Fabricate(:user)
reviewable.reviewable_scores.create!(
user: user,
score: 1000,
status: "pending",
reviewable_score_type: 2,
)
get "/review.json?score_type=1"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"].length).to eq(1)
end
it "supports filtering by flagged_by" do
# this is not flagged by the user
reviewable = Fabricate(:reviewable)
reviewable.reviewable_scores.create!(
user: admin,
score: 1000,
status: "pending",
reviewable_score_type: 1,
)
reviewable = Fabricate(:reviewable)
user = Fabricate(:user)
reviewable.reviewable_scores.create!(
user: user,
score: 1000,
status: "pending",
reviewable_score_type: 1,
)
get "/review.json?flagged_by=#{user.username}"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"].length).to eq(1)
end
it "supports filtering by score" do
get "/review.json?min_score=1000"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_blank
end
it "supports offsets" do
get "/review.json?offset=100"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_blank
end
it "supports filtering by type" do
Fabricate(:reviewable)
get "/review.json?type=ReviewableUser"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
end
it "raises an error with an invalid type" do
get "/review.json?type=ReviewableMadeUp"
expect(response.code).to eq("400")
end
it "supports filtering by status" do
Fabricate(:reviewable, status: Reviewable.statuses[:approved])
get "/review.json?type=ReviewableUser&status=pending"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_blank
Fabricate(:reviewable, status: Reviewable.statuses[:approved])
get "/review.json?type=ReviewableUser&status=approved"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
get "/review.json?type=ReviewableUser&status=reviewed"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
get "/review.json?type=ReviewableUser&status=all"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
end
it "raises an error with an invalid status" do
get "/review.json?status=xyz"
expect(response.code).to eq("400")
end
it "supports filtering by category_id" do
other_category = Fabricate(:category)
r = Fabricate(:reviewable)
get "/review.json?category_id=#{other_category.id}"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_blank
get "/review.json?category_id=#{r.category_id}"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
# By default all categories are returned
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
end
it "will use the ReviewableUser serializer for its fields" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
get "/review.json"
expect(response.code).to eq("200")
json = response.parsed_body
json_review = json["reviewables"][0]
expect(json_review["id"]).to eq(reviewable.id)
expect(json_review["user_id"]).to eq(user.id)
end
it "returns correct error message if ReviewableUser not found" do
sign_in(admin)
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
put "/review/#{reviewable.id}/perform/delete_user.json?version=0"
expect(response.code).to eq("200")
put "/review/#{reviewable.id}/perform/delete_user.json?version=0&index=2"
expect(response.code).to eq("404")
json = response.parsed_body
expect(json["error_type"]).to eq("not_found")
expect(json["errors"][0]).to eq(I18n.t("reviewables.already_handled_and_user_not_exist"))
end
it "returns a readable error message if reject_reason is too long, does not send email, and does not delete the user" do
sign_in(admin)
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
expect {
put "/review/#{reviewable.id}/perform/delete_user.json?version=0",
params: {
send_email: true,
reject_reason: "a" * 3000,
}
}.to not_change { ActionMailer::Base.deliveries.size }.and not_change { User.count }
expect(response.code).to eq("422")
expect(response.parsed_body["errors"]).to eq(
["Reject reason " + I18n.t("errors.messages.too_long", count: 2000)],
)
end
context "when filtering by range" do
let(:from) { 3.days.ago.strftime("%F") }
let(:to) { 1.day.ago.strftime("%F") }
let(:reviewables) { response.parsed_body["reviewables"] }
it "returns an empty array when no reviewable matches the date range" do
Fabricate(:reviewable)
get "/review.json?from_date=#{from}&to_date=#{to}"
expect(reviewables).to eq([])
end
it "returns reviewable content that matches the date range" do
reviewable = Fabricate(:reviewable, created_at: 2.days.ago)
get "/review.json?from_date=#{from}&to_date=#{to}"
json_review = reviewables.first
expect(json_review["id"]).to eq(reviewable.id)
end
end
context "with user custom field" do
before do
plugin = Plugin::Instance.new
plugin.allow_public_user_custom_field :public_field
end
after { DiscoursePluginRegistry.reset! }
it "returns user data with custom fields" do
user = Fabricate(:user)
user.custom_fields["public_field"] = "public"
user.custom_fields["private_field"] = "private"
user.save!
reviewable = Fabricate(:reviewable_flagged_post, target_created_by: user)
get "/review.json"
json = response.parsed_body
expect(json["users"]).to be_present
expect(
json["users"].any? do |u|
u["id"] == reviewable.target_created_by_id &&
u["custom_fields"]["public_field"] == "public"
end,
).to eq(true)
expect(
json["users"].any? do |u|
u["id"] == reviewable.target_created_by_id &&
u["custom_fields"]["private_field"] == "private"
end,
).to eq(false)
end
end
it "supports filtering by id" do
reviewable_a = Fabricate(:reviewable)
_reviewable_b = Fabricate(:reviewable)
get "/review.json?ids[]=#{reviewable_a.id}"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewables"]).to be_present
expect(json["reviewables"].size).to eq(1)
end
it "doesn't cause N+1 queries when flagged posts have localizations" do
SiteSetting.content_localization_enabled = true
admin.update!(locale: "ja")
reviewables =
2.times.map do
post = Fabricate(:post, raw: "Original post", locale: "en")
Fabricate(
:post_localization,
post: post,
cooked: "<p>Translated post</p>",
locale: "ja",
)
Fabricate(
:reviewable_flagged_post,
target: post,
target_created_by: post.user,
topic: post.topic,
)
end
I18n.with_locale(:ja) do
queries =
track_sql_queries do
get "/review.json"
expect(response.status).to eq(200)
end
post_localization_queries =
queries.select { |query| query.match?(/FROM "?post_localizations"?/) }
expect(post_localization_queries.size).to eq(1)
expect(
response.parsed_body["reviewables"].map { |reviewable| reviewable["id"] },
).to include(*reviewables.map(&:id))
expect(
response.parsed_body["reviewables"]
.select { |reviewable| reviewables.map(&:id).include?(reviewable["id"]) }
.map { |reviewable| reviewable["cooked"] },
).to all(eq("<p>Translated post</p>"))
end
end
context "with reviewable notes" do
fab!(:moderator)
fab!(:reviewable)
it "doesn't cause N+1 queries when notes are added" do
# post sign-in warmup
get "/review.json"
post "/reviewables/#{reviewable.id}/notes.json",
params: {
reviewable_note: {
content: "This is a test note",
},
}
initial_sql_queries =
track_sql_queries do
get "/review.json"
expect(response.status).to eq(200)
end
sign_in(moderator)
post "/reviewables/#{reviewable.id}/notes.json",
params: {
reviewable_note: {
content: "This is another test note",
},
}
sign_in(admin)
# second post sign-in warmup
get "/review.json"
new_sql_queries =
track_sql_queries do
get "/review.json"
expect(response.status).to eq(200)
end
expect(new_sql_queries.count).to eq(initial_sql_queries.count)
end
end
end
describe "#user_menu_list" do
it "renders each reviewable using its basic serializers" do
reviewable_user = Fabricate(:reviewable_user, payload: { username: "someb0dy" })
reviewable_flagged_post = Fabricate(:reviewable_flagged_post)
reviewable_queued_post = Fabricate(:reviewable_queued_post)
get "/review/user-menu-list.json"
expect(response.status).to eq(200)
reviewables = response.parsed_body["reviewables"]
reviewable_queued_post_json = reviewables.find { |r| r["id"] == reviewable_queued_post.id }
expect(reviewable_queued_post_json["is_new_topic"]).to eq(false)
expect(reviewable_queued_post_json["topic_fancy_title"]).to eq(
reviewable_queued_post.topic.fancy_title,
)
reviewable_flagged_post_json =
reviewables.find { |r| r["id"] == reviewable_flagged_post.id }
expect(reviewable_flagged_post_json["post_number"]).to eq(
reviewable_flagged_post.post.post_number,
)
expect(reviewable_flagged_post_json["topic_fancy_title"]).to eq(
reviewable_flagged_post.topic.fancy_title,
)
reviewable_user_json = reviewables.find { |r| r["id"] == reviewable_user.id }
expect(reviewable_user_json["username"]).to eq("someb0dy")
end
it "returns JSON containing basic information of reviewables" do
reviewable = Fabricate(:reviewable)
get "/review/user-menu-list.json"
expect(response.status).to eq(200)
reviewables = response.parsed_body["reviewables"]
expect(reviewables.size).to eq(1)
expect(reviewables[0]["flagger_username"]).to eq(reviewable.created_by.username)
expect(reviewables[0]["id"]).to eq(reviewable.id)
expect(reviewables[0]["type"]).to eq(reviewable.type)
expect(reviewables[0]["pending"]).to eq(true)
end
it "responds with current user's reviewables count" do
_reviewable = Fabricate(:reviewable)
get "/review/user-menu-list.json"
expect(response.status).to eq(200)
expect(response.parsed_body["reviewables"].length).to eq(1)
expect(response.parsed_body["reviewable_count"]).to eq(1)
end
it "responds with pending reviewables only" do
Fabricate(:reviewable, status: Reviewable.statuses[:approved])
pending1 = Fabricate(:reviewable, status: Reviewable.statuses[:pending])
Fabricate(:reviewable, status: Reviewable.statuses[:approved])
pending2 = Fabricate(:reviewable, status: Reviewable.statuses[:pending])
get "/review/user-menu-list.json"
expect(response.status).to eq(200)
reviewables = response.parsed_body["reviewables"]
expect(reviewables.map { |r| r["id"] }).to eq([pending2.id, pending1.id])
end
end
describe "#show" do
context "with basics" do
fab!(:reviewable)
before { sign_in(Fabricate(:moderator)) }
it "returns the reviewable as json" do
get "/review/#{reviewable.id}.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable"]["id"]).to eq(reviewable.id)
end
it "returns 404 for a missing reviewable" do
get "/review/123456789.json"
expect(response.code).to eq("404")
end
end
context "with conversation" do
fab!(:post)
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:admin)
let(:result) { PostActionCreator.notify_moderators(user, post, "this is the first post") }
let(:reviewable) { result.reviewable }
before do
PostCreator.create(
admin,
topic_id: result.reviewable_score.meta_topic_id,
raw: "this is the second post",
)
PostCreator.create(
admin,
topic_id: result.reviewable_score.meta_topic_id,
raw: "this is the third post",
)
end
it "returns the conversation" do
get "/review/#{reviewable.id}.json"
expect(response.code).to eq("200")
json = response.parsed_body
score = json["reviewable_scores"][0]
conversation_id = score["reviewable_conversation_id"]
conversation = json["reviewable_conversations"].find { |c| c["id"] == conversation_id }
expect(conversation).to be_present
expect(conversation["has_more"]).to eq(true)
expect(conversation["permalink"]).to be_present
reply =
json["conversation_posts"].find do |cp|
cp["id"] == conversation["conversation_post_ids"][0]
end
expect(reply["excerpt"]).to be_present
expect(reply["user_id"]).to eq(user.id)
reply =
json["conversation_posts"].find do |cp|
cp["id"] == conversation["conversation_post_ids"][1]
end
expect(reply["excerpt"]).to be_present
expect(reply["user_id"]).to eq(admin.id)
end
end
end
describe "#explain" do
context "with basics" do
fab!(:reviewable)
before { sign_in(Fabricate(:moderator)) }
it "returns the explanation as json" do
get "/review/#{reviewable.id}/explain.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable_explanation"]["id"]).to eq(reviewable.id)
expect(json["reviewable_explanation"]["total_score"]).to eq(reviewable.score)
end
it "returns 404 for a missing reviewable" do
get "/review/123456789/explain.json"
expect(response.code).to eq("404")
end
end
end
describe "#perform" do
fab!(:reviewable)
before { sign_in(Fabricate(:moderator)) }
it "includes the statuses of the other reviewables resolved by the action in the response" do
sign_in(Fabricate(:admin))
spammer = Fabricate(:user, refresh_auto_groups: true)
flagger = Fabricate(:user, refresh_auto_groups: true)
acted_flag = PostActionCreator.spam(flagger, Fabricate(:post, user: spammer)).reviewable
queued_post =
Fabricate(
:reviewable_queued_post,
created_by: Discourse.system_user,
target_created_by: spammer,
)
put "/review/#{acted_flag.id}/perform/delete_user_block.json?version=#{acted_flag.version}"
expect(response.status).to eq(200)
expect(response.parsed_body.dig("reviewable_perform_result", "reviewable_updates")).to eq(
queued_post.id.to_s => {
"status" => Reviewable.statuses[:rejected],
},
)
end
it "returns 404 when the reviewable does not exist" do
put "/review/12345/perform/approve_user.json?version=0"
expect(response.code).to eq("404")
end
it "validates the presence of an action" do
put "/review/#{reviewable.id}/perform/nope.json?version=#{reviewable.version}"
expect(response.code).to eq("403")
end
it "ensures the user can see the reviewable" do
reviewable.update_column(:reviewable_by_moderator, false)
put "/review/#{reviewable.id}/perform/approve_user.json?version=#{reviewable.version}"
expect(response.code).to eq("404")
end
it "can properly return errors" do
qp = Fabricate(:reviewable_queued_post_topic, topic_id: -100)
version = qp.version
put "/review/#{qp.id}/perform/approve_post.json?version=#{version}"
expect(response.code).to eq("422")
result = response.parsed_body
expect(result["errors"]).to be_present
expect(qp.reload.version).to eq(version)
end
it "requires a version parameter" do
put "/review/#{reviewable.id}/perform/approve_user.json"
expect(response.code).to eq("422")
result = response.parsed_body
expect(result["errors"]).to be_present
end
it "succeeds for a valid action" do
other_reviewable = Fabricate(:reviewable)
SiteSetting.must_approve_users = true
put "/review/#{reviewable.id}/perform/approve_user.json?version=#{reviewable.version}"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable_perform_result"]["success"]).to eq(true)
expect(json["reviewable_perform_result"]["version"]).to eq(1)
expect(json["reviewable_perform_result"]["remove_reviewable_ids"]).to eq([reviewable.id])
expect(json["reviewable_perform_result"]["reviewable_count"]).to eq(1)
expect(reviewable.reload.version).to eq(1)
expect(other_reviewable.reload.version).to eq(0)
job = Jobs::CriticalUserEmail.jobs.first
expect(job).to be_present
expect(job["args"][0]["type"]).to eq("signup_after_approval")
end
it "doesn't send email when `send_email` is false" do
_other_reviewable = Fabricate(:reviewable)
SiteSetting.must_approve_users = true
put "/review/#{reviewable.id}/perform/approve_user.json?version=#{reviewable.version}&send_email=false"
job = Jobs::CriticalUserEmail.jobs.first
expect(job).to be_blank
end
context "with claims" do
fab!(:qp, :reviewable_queued_post)
it "fails when reviewables must be claimed" do
SiteSetting.reviewable_claiming = "required"
put "/review/#{qp.id}/perform/approve_post.json?version=#{qp.version}"
expect(response.code).to eq("422")
end
it "fails when optional claims are claimed by others" do
SiteSetting.reviewable_claiming = "optional"
ReviewableClaimedTopic.create!(topic_id: qp.topic_id, user: Fabricate(:admin))
put "/review/#{qp.id}/perform/approve_post.json?version=#{qp.version}"
expect(response.code).to eq("422")
expect(response.parsed_body["errors"]).to match_array(
["This item has been claimed by another user."],
)
end
it "works when claims are optional" do
SiteSetting.reviewable_claiming = "optional"
put "/review/#{qp.id}/perform/approve_post.json?version=#{qp.version}"
expect(response.code).to eq("200")
end
end
describe "simultaneous perform" do
it "fails when the version is wrong" do
put "/review/#{reviewable.id}/perform/approve_user.json?version=#{reviewable.version + 1}"
expect(response.code).to eq("409")
json = response.parsed_body
expect(json["errors"]).to be_present
end
end
end
describe "with reviewable params added via plugin API" do
class ::ReviewablePhony < Reviewable
def build_actions(actions, guardian, _args)
return [] unless pending?
actions.add(:approve_phony) { |action| action.label = "js.phony.review.approve" }
end
def perform_approve_phony(performed_by, args)
MessageBus.publish("/phony-reviewable-test", { args: args }, user_ids: [1])
create_result(:success, :approved)
end
end
before do
plugin = Plugin::Instance.new
plugin.add_permitted_reviewable_param(:reviewable_phony, :fake_id)
end
after { DiscoursePluginRegistry.reset! }
fab!(:reviewable_phony) { Fabricate(:reviewable, type: "ReviewablePhony") }
it "passes the added param into the reviewable class' perform method" do
MessageBus.expects(:publish).with(
"/phony-reviewable-test",
{ args: { :version => reviewable_phony.version, "fake_id" => "2" } },
user_ids: [1],
)
MessageBus.expects(:publish).with(
"/reviewable_action",
{
success: true,
remove_reviewable_ids: [reviewable_phony.id],
version: 1,
reviewable_count: 0,
unseen_reviewable_count: 0,
},
group_ids: [3],
)
put "/review/#{reviewable_phony.id}/perform/approve_phony.json?version=#{reviewable_phony.version}",
params: {
fake_id: 2,
}
expect(response.status).to eq(200)
end
end
describe "#topics" do
fab!(:post0, :post)
fab!(:post1) { Fabricate(:post, topic: post0.topic) }
fab!(:post2, :post)
fab!(:user0) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:user1) { Fabricate(:user, refresh_auto_groups: true) }
it "returns empty json for no reviewables" do
get "/review/topics.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable_topics"]).to be_blank
end
it "includes claimed information" do
SiteSetting.reviewable_claiming = "optional"
PostActionCreator.spam(user0, post0)
moderator = Fabricate(:moderator)
claim = ReviewableClaimedTopic.create!(user: moderator, topic: post0.topic)
get "/review/topics.json"
expect(response.code).to eq("200")
json = response.parsed_body
json_topic = json["reviewable_topics"].find { |rt| rt["id"] == post0.topic_id }
expect(json_topic["claimed_by_id"]).to eq(claim.id)
json_claim = json["claimed_bies"].find { |c| c["id"] == claim.id }
expect(json_claim["user_id"]).to eq(moderator.id)
json_user = json["users"].find { |u| u["id"] == json_claim["user_id"] }
expect(json_user).to be_present
end
it "returns json listing the topics" do
PostActionCreator.spam(user0, post0)
PostActionCreator.off_topic(user0, post1)
PostActionCreator.spam(user0, post2)
PostActionCreator.spam(user1, post2)
get "/review/topics.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable_topics"]).to be_present
json_topic = json["reviewable_topics"].find { |rt| rt["id"] == post0.topic_id }
expect(json_topic["stats"]["count"]).to eq(2)
expect(json_topic["stats"]["unique_users"]).to eq(1)
json_topic = json["reviewable_topics"].find { |rt| rt["id"] == post2.topic_id }
expect(json_topic["stats"]["count"]).to eq(2)
expect(json_topic["stats"]["unique_users"]).to eq(2)
end
end
describe "#settings" do
it "renders the settings as JSON" do
get "/review/settings.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["reviewable_settings"]).to be_present
expect(json["reviewable_score_types"]).to be_present
end
it "allows the settings to be updated" do
put "/review/settings.json",
params: {
reviewable_priorities: {
8 => Reviewable.priorities[:medium],
},
}
expect(response.code).to eq("200")
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:medium])
expect(pa.score_bonus).to eq(5.0)
put "/review/settings.json",
params: {
reviewable_priorities: {
8 => Reviewable.priorities[:low],
},
}
expect(response.code).to eq("200")
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:low])
expect(pa.score_bonus).to eq(0.0)
put "/review/settings.json",
params: {
reviewable_priorities: {
8 => Reviewable.priorities[:high],
},
}
expect(response.code).to eq("200")
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:high])
expect(pa.score_bonus).to eq(10.0)
end
end
describe "#update" do
fab!(:reviewable)
fab!(:reviewable_post, :reviewable_queued_post)
fab!(:reviewable_topic, :reviewable_queued_post_topic)
fab!(:moderator)
fab!(:reviewable_approved_post) do
Fabricate(:reviewable_queued_post, status: Reviewable.statuses[:approved])
end
before { sign_in(moderator) }
it "returns 404 when the reviewable does not exist" do
put "/review/12345.json?version=0"
expect(response.code).to eq("404")
end
it "returns access denied if there are no editable fields" do
put(
"/review/#{reviewable.id}.json?version=#{reviewable.version}",
params: {
reviewable: {
field: "value",
},
},
)
expect(response.code).to eq("403")
end
it "returns access denied if you try to update a field that doesn't exist" do
put(
"/review/#{reviewable_post.id}.json?version=#{reviewable_post.version}",
params: {
reviewable: {
field: "value",
},
},
)
expect(response.code).to eq("403")
end
it "requires a version parameter" do
put "/review/#{reviewable_post.id}.json"
expect(response.code).to eq("422")
end
it "fails if there is a version conflict" do
put "/review/#{reviewable_post.id}.json?version=#{reviewable_post.version + 2}",
params: {
reviewable: {
payload: {
raw: "new raw content",
},
},
}
expect(response.code).to eq("409")
end
it "allows you to update a queued post" do
put "/review/#{reviewable_post.id}.json?version=#{reviewable_post.version}",
params: {
reviewable: {
payload: {
raw: "new raw content",
},
},
}
expect(response.code).to eq("200")
reviewable_post.reload
expect(reviewable_post.payload["raw"]).to eq("new raw content")
history =
ReviewableHistory.find_by(
reviewable_id: reviewable_post.id,
created_by_id: moderator.id,
reviewable_history_type: ReviewableHistory.types[:edited],
)
expect(history).to be_present
json = response.parsed_body
expect(json["payload"]["raw"]).to eq("new raw content")
expect(json["version"] > 0).to eq(true)
end
it "prevents you from updating an approved post" do
put "/review/#{reviewable_approved_post.id}.json?version=#{reviewable_approved_post.version}",
params: {
reviewable: {
payload: {
raw: "new raw content",
},
},
}
expect(response.code).to eq("403")
end
it "allows you to update a queued post (for new topic)" do
new_category_id = Fabricate(:category).id
put "/review/#{reviewable_topic.id}.json?version=#{reviewable_topic.version}",
params: {
reviewable: {
payload: {
raw: "new topic op",
title: "new topic title",
tags: %w[t2 t3 t1],
},
category_id: new_category_id,
},
}
expect(response.code).to eq("200")
reviewable_topic.reload
expect(reviewable_topic.payload["raw"]).to eq("new topic op")
expect(reviewable_topic.payload["title"]).to eq("new topic title")
expect(reviewable_topic.payload["extra"]).to eq("some extra data")
expect(reviewable_topic.payload["tags"]).to eq(%w[t2 t3 t1])
expect(reviewable_topic.category_id).to eq(new_category_id)
json = response.parsed_body
expect(json["payload"]["raw"]).to eq("new topic op")
expect(json["payload"]["title"]).to eq("new topic title")
expect(json["payload"]["extra"]).to be_blank
expect(json["category_id"]).to eq(new_category_id.to_s)
end
end
describe "#destroy" do
fab!(:user)
it "returns 404 if the reviewable doesn't exist" do
sign_in(user)
delete "/review/1234.json"
expect(response.code).to eq("404")
end
it "returns 404 if the user can't see the reviewable" do
sign_in(user)
queued_post = Fabricate(:reviewable_queued_post)
delete "/review/#{queued_post.id}.json"
expect(response.code).to eq("404")
end
it "returns 200 if the user can delete the reviewable" do
sign_in(user)
queued_post = Fabricate(:reviewable_queued_post, target_created_by: user)
delete "/review/#{queued_post.id}.json"
expect(response.code).to eq("200")
expect(queued_post.reload).to be_deleted
end
it "denies attempts to destroy unowned reviewables" do
sign_in(admin)
queued_post = Fabricate(:reviewable_queued_post, target_created_by: user)
delete "/review/#{queued_post.id}.json"
expect(response.status).to eq(404)
# Reviewable is not deleted because request is not via API
expect(queued_post.reload).to be_present
end
describe "via API" do
it "admin can target another user with `username` param" do
api_key = Fabricate(:api_key, user: admin).key
queued_post = Fabricate(:reviewable_queued_post, target_created_by: user)
delete "/review/#{queued_post.id}.json",
params: {
username: user.username,
},
headers: {
HTTP_API_USERNAME: admin.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(200)
expect(queued_post.reload).to be_deleted
end
it "admin acts on self when `username` param is absent" do
api_key = Fabricate(:api_key, user: admin).key
queued_post = Fabricate(:reviewable_queued_post, target_created_by: admin)
delete "/review/#{queued_post.id}.json",
headers: {
HTTP_API_USERNAME: admin.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(200)
expect(queued_post.reload).to be_deleted
end
it "non-admin gets 403 when passing `username` to target another user" do
other_user = Fabricate(:user)
api_key = Fabricate(:api_key, user: user).key
queued_post = Fabricate(:reviewable_queued_post, target_created_by: other_user)
delete "/review/#{queued_post.id}.json",
params: {
username: other_user.username,
},
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(403)
expect(queued_post.reload).to be_present
end
it "non-admin acts on self when `username` param is absent" do
api_key = Fabricate(:api_key, user: user).key
queued_post = Fabricate(:reviewable_queued_post, target_created_by: user)
delete "/review/#{queued_post.id}.json",
headers: {
HTTP_API_USERNAME: user.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(200)
expect(queued_post.reload).to be_deleted
end
end
end
describe "#scrub" do
it "only allows admins to scrub reviewables" do
moderator = Fabricate(:moderator)
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
sign_in(moderator)
put "/review/#{reviewable.id}/perform/delete_user.json?version=0"
expect(response.status).to eq(200)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(403)
sign_in(admin)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(200)
end
it "doesn't allow scrubbing of reviewables that haven't been rejected" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
sign_in(admin)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(404)
end
it "doesn't allow scrubbing of reviewables that don't exist" do
sign_in(admin)
put "/review/123456789/scrub.json?reason=spam"
expect(response.status).to eq(404)
end
it "doesn't allow scrubbing of reviewables that aren't scrubbable" do
reviewable = Fabricate(:reviewable)
expect(Reviewable.scrubbable_types).not_to include(reviewable.type)
sign_in(admin)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(404)
end
it "doesn't allow scrubbing of reviewables that have already been scrubbed" do
Jobs.run_immediately!
SiteSetting.must_approve_users = true
user = Fabricate(:user)
user.activate
reviewable = ReviewableUser.find_by(target: user)
sign_in(admin)
put "/review/#{reviewable.id}/perform/delete_user.json?version=0"
expect(response.status).to eq(200)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(200)
put "/review/#{reviewable.id}/scrub.json?reason=spam"
expect(response.status).to eq(403)
end
end
describe "#count" do
fab!(:admin)
before { sign_in(admin) }
it "returns the number of reviewables" do
get "/review/count.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["count"]).to eq(0)
Fabricate(:reviewable_queued_post)
get "/review/count.json"
expect(response.code).to eq("200")
json = response.parsed_body
expect(json["count"]).to eq(1)
end
end
end
end