mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 13:45:29 +08:00
On https://github.com/discourse/discourse/pull/37694, we ended up removing one of the nice things of the bootstrap mode, the auto moderation for the first admin. I've brought back the job that used to run for the bootstrap mode – maybe we could just run this code in the `default_current_user_provider` --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
483 lines
17 KiB
Ruby
Vendored
483 lines
17 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe Admin::StaffActionLogsController do
|
|
fab!(:admin)
|
|
fab!(:moderator)
|
|
fab!(:user)
|
|
|
|
describe "#index" do
|
|
shared_examples "staff action logs accessible" do
|
|
it "returns logs" do
|
|
topic = Fabricate(:topic)
|
|
StaffActionLogger.new(Discourse.system_user).log_topic_delete_recover(topic, "delete_topic")
|
|
|
|
get "/admin/logs/staff_action_logs.json",
|
|
params: {
|
|
action_id: UserHistory.actions[:delete_topic],
|
|
}
|
|
|
|
json = response.parsed_body
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(json["staff_action_logs"].length).to eq(1)
|
|
expect(json["staff_action_logs"][0]["action_name"]).to eq("delete_topic")
|
|
|
|
expect(json["extras"]["user_history_actions"]).to include(
|
|
"id" => "delete_topic",
|
|
"action_id" => UserHistory.actions[:delete_topic],
|
|
)
|
|
end
|
|
|
|
describe "filter logs by date" do
|
|
before do
|
|
freeze_time
|
|
topic = Fabricate(:topic)
|
|
StaffActionLogger.new(Discourse.system_user).log_topic_delete_recover(
|
|
topic,
|
|
"delete_topic",
|
|
)
|
|
freeze_time 3.days.from_now
|
|
StaffActionLogger.new(Discourse.system_user).log_silence_user(user, details: "test")
|
|
freeze_time 2.days.from_now
|
|
StaffActionLogger.new(Discourse.system_user).log_user_suspend(user, "reason")
|
|
end
|
|
|
|
it "filter logs by start_date" do
|
|
get "/admin/logs/staff_action_logs.json", params: { start_date: 3.days.ago.iso8601 }
|
|
|
|
json = response.parsed_body
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(json["staff_action_logs"].length).to eq(2)
|
|
expect(json["staff_action_logs"][0]["action_name"]).to eq("suspend_user")
|
|
expect(json["staff_action_logs"][1]["action_name"]).to eq("silence_user")
|
|
end
|
|
|
|
it "filter logs by end_date" do
|
|
get "/admin/logs/staff_action_logs.json", params: { end_date: 1.day.ago.iso8601 }
|
|
|
|
json = response.parsed_body
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(json["staff_action_logs"].length).to eq(2)
|
|
expect(json["staff_action_logs"][0]["action_name"]).to eq("silence_user")
|
|
expect(json["staff_action_logs"][1]["action_name"]).to eq("delete_topic")
|
|
end
|
|
|
|
it "filter logs by start_date and end_date" do
|
|
get "/admin/logs/staff_action_logs.json",
|
|
params: {
|
|
start_date: 3.days.ago.iso8601,
|
|
end_date: 1.day.ago.iso8601,
|
|
}
|
|
|
|
json = response.parsed_body
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(json["staff_action_logs"].length).to eq(1)
|
|
expect(json["staff_action_logs"][0]["action_name"]).to eq("silence_user")
|
|
end
|
|
end
|
|
|
|
describe "when limit params is invalid" do
|
|
include_examples "invalid limit params",
|
|
"/admin/logs/staff_action_logs.json",
|
|
described_class::INDEX_LIMIT
|
|
end
|
|
end
|
|
|
|
context "when logged in as an admin" do
|
|
before do
|
|
admin.update!(last_seen_at: 1.day.ago)
|
|
sign_in(admin)
|
|
end
|
|
|
|
include_examples "staff action logs accessible"
|
|
|
|
it "generates logs with pages" do
|
|
4.times do |idx|
|
|
StaffActionLogger.new(Discourse.system_user).log_site_setting_change(
|
|
"title",
|
|
"value #{idx}",
|
|
"value #{idx + 1}",
|
|
)
|
|
end
|
|
|
|
get "/admin/logs/staff_action_logs.json", params: { limit: 3 }
|
|
expect(response.parsed_body["staff_action_logs"].length).to eq(3)
|
|
expect(response.parsed_body["staff_action_logs"][0]["new_value"]).to eq("value 4")
|
|
|
|
get "/admin/logs/staff_action_logs.json", params: { limit: 3, page: 1 }
|
|
expect(response.parsed_body["staff_action_logs"].length).to eq(1)
|
|
expect(response.parsed_body["staff_action_logs"][0]["new_value"]).to eq("value 1")
|
|
end
|
|
|
|
it "sees admin-only actions" do
|
|
StaffActionLogger.new(admin).log_site_setting_change("title", "old", "new")
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].map { |l| l["action_name"] }).to include(
|
|
"change_site_setting",
|
|
)
|
|
end
|
|
|
|
it "sees full content for private topics" do
|
|
pm = Fabricate(:private_message_topic)
|
|
StaffActionLogger.new(admin).log_topic_delete_recover(pm, "delete_topic")
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["details"]).to include(pm.title)
|
|
end
|
|
|
|
describe "reviewable_id" do
|
|
it "is included when the staff action log is linked to a reviewable" do
|
|
reviewable = Fabricate(:reviewable_queued_post_topic)
|
|
UserHistory.create!(
|
|
action: UserHistory.actions[:post_approved],
|
|
acting_user_id: admin.id,
|
|
reviewable_id: reviewable.id,
|
|
)
|
|
|
|
get "/admin/logs/staff_action_logs.json",
|
|
params: {
|
|
action_id: UserHistory.actions[:post_approved],
|
|
}
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["reviewable_id"]).to eq(
|
|
reviewable.id,
|
|
)
|
|
end
|
|
|
|
it "is omitted when the staff action log has no reviewable" do
|
|
StaffActionLogger.new(admin).log_site_setting_change("title", "old", "new")
|
|
|
|
get "/admin/logs/staff_action_logs.json",
|
|
params: {
|
|
action_id: UserHistory.actions[:change_site_setting],
|
|
}
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first).not_to have_key("reviewable_id")
|
|
end
|
|
end
|
|
|
|
context "when staff actions are extended" do
|
|
let(:plugin_extended_action) { :confirmed_ham }
|
|
before { UserHistory.stubs(:staff_actions).returns([plugin_extended_action]) }
|
|
after { UserHistory.unstub(:staff_actions) }
|
|
|
|
it "uses custom_staff id for unknown actions" do
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
action = response.parsed_body["extras"]["user_history_actions"].first
|
|
expect(action["id"]).to eq(plugin_extended_action.to_s)
|
|
expect(action["action_id"]).to eq(UserHistory.actions[:custom_staff])
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when logged in as a moderator" do
|
|
before { sign_in(moderator) }
|
|
|
|
include_examples "staff action logs accessible"
|
|
|
|
it "does not see admin-only actions" do
|
|
StaffActionLogger.new(admin).log_site_setting_change("title", "old", "new")
|
|
StaffActionLogger.new(admin).log_web_hook(
|
|
Fabricate(:web_hook),
|
|
UserHistory.actions[:web_hook_create],
|
|
)
|
|
StaffActionLogger.new(admin).log_api_key(
|
|
Fabricate(:api_key),
|
|
UserHistory.actions[:api_key_create],
|
|
)
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
action_names = response.parsed_body["staff_action_logs"].map { |l| l["action_name"] }
|
|
expect(action_names).not_to include(
|
|
"change_site_setting",
|
|
"web_hook_create",
|
|
"api_key_create",
|
|
)
|
|
end
|
|
|
|
it "sees full content for public topics" do
|
|
topic = Fabricate(:topic)
|
|
StaffActionLogger.new(admin).log_topic_delete_recover(topic, "delete_topic")
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["details"]).to include(topic.title)
|
|
end
|
|
|
|
it "redacts content for private topics" do
|
|
pm = Fabricate(:private_message_topic)
|
|
StaffActionLogger.new(admin).log_topic_delete_recover(pm, "delete_topic")
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
log = response.parsed_body["staff_action_logs"].first
|
|
expect(log["details"]).to eq(I18n.t("staff_action_logs.redacted"))
|
|
expect(log["context"]).to be_nil
|
|
end
|
|
|
|
it "redacts content for restricted categories" do
|
|
SiteSetting.moderators_manage_categories = true
|
|
category = Fabricate(:private_category, group: Fabricate(:group))
|
|
StaffActionLogger.new(admin).log_category_creation(category)
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["details"]).to eq(
|
|
I18n.t("staff_action_logs.redacted"),
|
|
)
|
|
end
|
|
|
|
it "redacts content when referenced topic is deleted" do
|
|
topic = Fabricate(:topic)
|
|
StaffActionLogger.new(admin).log_topic_delete_recover(topic, "delete_topic")
|
|
topic.destroy!
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
log = response.parsed_body["staff_action_logs"].first
|
|
expect(log["details"]).to eq(I18n.t("staff_action_logs.redacted"))
|
|
expect(log["context"]).to be_nil
|
|
end
|
|
|
|
it "redacts content when referenced post is deleted" do
|
|
post = Fabricate(:post)
|
|
StaffActionLogger.new(admin).log_post_edit(post, old_raw: "old content")
|
|
post.destroy!
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["details"]).to eq(
|
|
I18n.t("staff_action_logs.redacted"),
|
|
)
|
|
end
|
|
|
|
it "redacts content when referenced category is deleted" do
|
|
SiteSetting.moderators_manage_categories = true
|
|
category = Fabricate(:category)
|
|
StaffActionLogger.new(admin).log_category_creation(category)
|
|
category.destroy!
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
expect(response.parsed_body["staff_action_logs"].first["details"]).to eq(
|
|
I18n.t("staff_action_logs.redacted"),
|
|
)
|
|
end
|
|
|
|
it "hides category actions when moderators_manage_categories is disabled" do
|
|
SiteSetting.moderators_manage_categories = false
|
|
category = Fabricate(:category)
|
|
StaffActionLogger.new(admin).log_category_creation(category)
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
action_names = response.parsed_body["staff_action_logs"].map { |l| l["action_name"] }
|
|
expect(action_names).not_to include("create_category")
|
|
end
|
|
|
|
it "shows category actions when moderators_manage_categories is enabled" do
|
|
SiteSetting.moderators_manage_categories = true
|
|
category = Fabricate(:category)
|
|
StaffActionLogger.new(admin).log_category_creation(category)
|
|
|
|
get "/admin/logs/staff_action_logs.json"
|
|
|
|
action_names = response.parsed_body["staff_action_logs"].map { |l| l["action_name"] }
|
|
expect(action_names).to include("create_category")
|
|
end
|
|
end
|
|
|
|
context "when logged in as a non-staff user" do
|
|
before { sign_in(user) }
|
|
|
|
it "denies access with a 404 response" do
|
|
get "/admin/logs/staff_action_logs.json",
|
|
params: {
|
|
action_id: UserHistory.actions[:delete_topic],
|
|
}
|
|
|
|
expect(response.status).to eq(404)
|
|
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#diff" do
|
|
shared_examples "theme diffs accessible" do
|
|
it "generates diffs for theme changes" do
|
|
theme = Fabricate(:theme)
|
|
theme.set_field(target: :mobile, name: :scss, value: "body {.up}")
|
|
theme.set_field(target: :common, name: :scss, value: "omit-dupe")
|
|
|
|
original_json =
|
|
ThemeSerializer.new(theme, root: false, include_theme_field_values: true).to_json
|
|
|
|
theme.set_field(target: :mobile, name: :scss, value: "body {.down}")
|
|
|
|
record = StaffActionLogger.new(Discourse.system_user).log_theme_change(original_json, theme)
|
|
|
|
get "/admin/logs/staff_action_logs/#{record.id}/diff.json"
|
|
expect(response.status).to eq(200)
|
|
|
|
parsed = response.parsed_body
|
|
expect(parsed["side_by_side"]).to include("up")
|
|
expect(parsed["side_by_side"]).to include("down")
|
|
|
|
expect(parsed["side_by_side"]).not_to include("omit-dupe")
|
|
end
|
|
end
|
|
|
|
shared_examples "tag_group diffs accessible" do
|
|
it "generates diffs for tag_group changes" do
|
|
tag1 = Fabricate(:tag)
|
|
tag2 = Fabricate(:tag)
|
|
tag3 = Fabricate(:tag)
|
|
tag_group1 = Fabricate(:tag_group, tags: [tag1, tag2])
|
|
|
|
old_json = TagGroupSerializer.new(tag_group1, root: false).to_json
|
|
|
|
tag_group2 = Fabricate(:tag_group, tags: [tag2, tag3])
|
|
|
|
new_json = TagGroupSerializer.new(tag_group2, root: false).to_json
|
|
|
|
record =
|
|
StaffActionLogger.new(Discourse.system_user).log_tag_group_change(
|
|
tag_group2.name,
|
|
old_json,
|
|
new_json,
|
|
)
|
|
|
|
get "/admin/logs/staff_action_logs/#{record.id}/diff.json"
|
|
expect(response.status).to eq(200)
|
|
|
|
parsed = response.parsed_body
|
|
|
|
name_diff = <<-HTML
|
|
<h3>name</h3><p></p><table class="markdown"><tr><td class="--previous diff-del"><del>#{tag_group1.name}</del></td><td class="--current diff-ins"><ins>#{tag_group2.name}</ins></td></tr></table>
|
|
HTML
|
|
expect(parsed["side_by_side"]).to include(name_diff.strip)
|
|
expect(parsed["side_by_side"]).to include("<del>#{tag1.name}</del>")
|
|
expect(parsed["side_by_side"]).to include("<ins>#{tag3.name}</ins>")
|
|
end
|
|
|
|
it "generates diffs for old tag_group format with tag_names" do
|
|
tag1 = Fabricate(:tag)
|
|
tag2 = Fabricate(:tag)
|
|
tag3 = Fabricate(:tag)
|
|
|
|
old_json = {
|
|
name: "old_group",
|
|
tag_names: [tag1.name, tag2.name],
|
|
parent_tag_name: [],
|
|
one_per_topic: false,
|
|
permissions: {
|
|
"0" => 1,
|
|
},
|
|
}.to_json
|
|
|
|
new_json = {
|
|
name: "new_group",
|
|
tag_names: [tag2.name, tag3.name],
|
|
parent_tag_name: [],
|
|
one_per_topic: false,
|
|
permissions: {
|
|
"0" => 1,
|
|
},
|
|
}.to_json
|
|
|
|
record =
|
|
StaffActionLogger.new(Discourse.system_user).log_tag_group_change(
|
|
"new_group",
|
|
old_json,
|
|
new_json,
|
|
)
|
|
|
|
get "/admin/logs/staff_action_logs/#{record.id}/diff.json"
|
|
expect(response.status).to eq(200)
|
|
|
|
parsed = response.parsed_body
|
|
expect(parsed["side_by_side"]).to include("<h3>tag_names</h3>")
|
|
expect(parsed["side_by_side"]).to include("<del>#{tag1.name}</del>")
|
|
expect(parsed["side_by_side"]).to include("<ins>#{tag3.name}</ins>")
|
|
end
|
|
end
|
|
|
|
context "when logged in as an admin" do
|
|
before do
|
|
admin.update!(last_seen_at: 1.day.ago)
|
|
sign_in(admin)
|
|
end
|
|
|
|
include_examples "theme diffs accessible"
|
|
include_examples "tag_group diffs accessible"
|
|
|
|
it "is not erroring when current value is empty" do
|
|
theme = Fabricate(:theme)
|
|
StaffActionLogger.new(admin).log_theme_destroy(theme)
|
|
get "/admin/logs/staff_action_logs/#{UserHistory.last.id}/diff.json"
|
|
expect(response.status).to eq(200)
|
|
end
|
|
|
|
it "falls back when diff generation exceeds the comparison budget" do
|
|
theme = Fabricate(:theme)
|
|
theme.set_field(target: :mobile, name: :scss, value: "body {.up}")
|
|
original_json =
|
|
ThemeSerializer.new(theme, root: false, include_theme_field_values: true).to_json
|
|
theme.set_field(target: :mobile, name: :scss, value: "body {.down}")
|
|
record = StaffActionLogger.new(Discourse.system_user).log_theme_change(original_json, theme)
|
|
|
|
ONPDiff
|
|
.any_instance
|
|
.stubs(:compose)
|
|
.raises(
|
|
ONPDiff::DiffLimitExceeded.new(
|
|
comparisons_used: 1,
|
|
comparison_budget: 0,
|
|
left_size: 1,
|
|
right_size: 1,
|
|
),
|
|
)
|
|
|
|
get "/admin/logs/staff_action_logs/#{record.id}/diff.json"
|
|
expect(response.status).to eq(200)
|
|
expect(response.parsed_body["side_by_side"]).to include(I18n.t("errors.diff_too_complex"))
|
|
end
|
|
end
|
|
|
|
context "when logged in as a moderator" do
|
|
before { sign_in(moderator) }
|
|
|
|
include_examples "tag_group diffs accessible"
|
|
|
|
it "denies access to theme diffs (admin-only action)" do
|
|
theme = Fabricate(:theme)
|
|
record = StaffActionLogger.new(Discourse.system_user).log_theme_change("{}", theme)
|
|
|
|
get "/admin/logs/staff_action_logs/#{record.id}/diff.json"
|
|
expect(response.status).to eq(404)
|
|
end
|
|
end
|
|
|
|
context "when logged in as a non-staff user" do
|
|
before { sign_in(user) }
|
|
|
|
it "denies access with a 404 response" do
|
|
theme = Fabricate(:theme)
|
|
StaffActionLogger.new(admin).log_theme_destroy(theme)
|
|
|
|
get "/admin/logs/staff_action_logs/#{UserHistory.last.id}/diff.json"
|
|
|
|
expect(response.status).to eq(404)
|
|
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
|
|
end
|
|
end
|
|
end
|
|
end
|