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
discoursebot 58e45dc7c9
SECURITY: Reviewable conversations expose post-voting flag PM excerpts to category moderators outside the PM [backport 2026.1] (#41531)
Backport of #41527 to release/2026.1.

---

## Summary

Prevent unauthorized access to private message excerpts in reviewable
responses by ensuring companion conversations are only serialized when
the viewer has permission to see the underlying topic. The patch also
ensures category moderators receive legitimate access to post-voting
flag conversations by including the relevant moderation groups in the
private message recipient list.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/1282

Co-authored-by: discourse-patch-triage
<272280883+discourse-patch-triage[bot]@users.noreply.github.com>

Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
2026-07-07 15:37:32 -05:00

1201 lines
40 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
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
context "with an inaccessible conversation" do
it "does not serialize the conversation" do
SiteSetting.enable_category_group_moderation = true
category = Fabricate(:category)
group_user = Fabricate(:group_user)
Fabricate(:category_moderation_group, category: category, group: group_user.group)
flagger = Fabricate(:user, refresh_auto_groups: true)
topic = Fabricate(:topic, category: category)
post = Fabricate(:post, topic: topic)
meta_post =
PostCreator.create!(
flagger,
archetype: Archetype.private_message,
subtype: TopicSubtype.notify_moderators,
target_group_names: [Group[:moderators].name],
title: "A hidden flag conversation",
raw: "Sensitive flag reason for moderators only",
)
reviewable =
ReviewableFlaggedPost.needs_review!(
created_by: flagger,
target: post,
topic: topic,
reviewable_by_moderator: true,
)
reviewable_score =
reviewable.add_score(
flagger,
ReviewableScore.types[:notify_moderators],
meta_topic_id: meta_post.topic_id,
)
expect(Guardian.new(group_user.user).can_see?(meta_post.topic)).to eq(false)
sign_in(group_user.user)
get "/review/#{reviewable.id}.json"
expect(response.code).to eq("200")
json = response.parsed_body
score = json["reviewable_scores"].find { |item| item["id"] == reviewable_score.id }
expect(score).not_to have_key("reviewable_conversation_id")
expect(json["reviewable_conversations"]).to be_blank
expect(json["conversation_posts"]).to be_blank
expect(response.body).not_to include(meta_post.raw)
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 "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
shared_examples "for a passed user" do
it "deletes reviewable" do
api_key = Fabricate(:api_key).key
queued_post = Fabricate(:reviewable_queued_post, target_created_by: recipient)
delete "/review/#{queued_post.id}.json",
params: {
username: recipient.username,
},
headers: {
HTTP_API_USERNAME: caller.username,
HTTP_API_KEY: api_key,
}
expect(response.status).to eq(response_code)
if reviewable_deleted
expect(queued_post.reload).to be_deleted
else
expect(queued_post.reload).to be_present
end
end
end
describe "api called by admin" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:admin) }
let(:recipient) { user }
let(:response_code) { 200 }
let(:reviewable_deleted) { true }
end
end
describe "api called by tl4 user" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:trust_level_4) }
let(:recipient) { user }
let(:response_code) { 403 }
let(:reviewable_deleted) { false }
end
end
describe "api called by regular user" do
include_examples "for a passed user" do
let(:caller) { user }
let(:recipient) { Fabricate(:user) }
let(:response_code) { 403 }
let(:reviewable_deleted) { false }
end
end
describe "api called by admin for another admin" do
include_examples "for a passed user" do
let(:caller) { Fabricate(:admin) }
let(:recipient) { Fabricate(:admin) }
let(:response_code) { 200 }
let(:reviewable_deleted) { true }
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