mirror of
https://github.com/discourse/discourse.git
synced 2026-08-09 21:45:25 +08:00
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.
1222 lines
41 KiB
Ruby
Vendored
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
|