0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/spec/models/topic_converter_spec.rb
Arpit Jalan d10e6799cd
FIX: keep private message conversion limited to staff (#41460)
Converting a topic between a private message and a regular topic is
meant to go through the dedicated staff-only conversion flow. The
generic topic update path did not apply that same restriction, so the
topic's archetype could be changed there without it.

Route archetype changes involving private messages through the same
`can_convert_topic?` permission the conversion flow already uses, so
both paths behave consistently.
2026-07-06 14:13:48 +05:30

329 lines
13 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe TopicConverter do
describe "convert_to_public_topic" do
fab!(:admin)
fab!(:author, :user)
fab!(:category) { Fabricate(:category, topic_count: 1) }
fab!(:private_message) { Fabricate(:private_message_topic, user: author) } # creates a topic without a first post
let(:first_post) do
create_post(user: author, topic: private_message, allow_uncategorized_topics: false)
end
let(:other_user) { private_message.topic_allowed_users.find { |u| u.user != author }.user }
let(:uncategorized_category) { Category.find(SiteSetting.uncategorized_category_id) }
context "with success" do
it "converts private message to regular topic" do
SiteSetting.allow_uncategorized_topics = true
topic = nil
_pm_post_2 = Fabricate(:post, topic: private_message, user: author)
_pm_post_3 = Fabricate(:post, topic: private_message, user: author)
other_pm = Fabricate(:private_message_post).topic
other_pm_post = Fabricate(:private_message_post, topic: other_pm)
other_pm_post_2 =
Fabricate(:private_message_post, topic: other_pm, user: other_pm_post.user)
expect do
topic = TopicConverter.new(first_post.topic, admin).convert_to_public_topic
topic.reload
end.to change { uncategorized_category.reload.topic_count }.by(1).and change {
author.reload.topic_count
}.from(0).to(1).and change { author.reload.post_count }.from(0).to(2)
# Ensure query does not affect users from other topics or posts as DB query to update count is quite complex.
expect(other_pm.user.topic_count).to eq(0)
expect(other_pm.user.post_count).to eq(0)
expect(other_pm_post.user.topic_count).to eq(0)
expect(other_pm_post.user.post_count).to eq(0)
expect(topic).to be_valid
expect(topic.archetype).to eq("regular")
expect(topic.category_id).to eq(SiteSetting.uncategorized_category_id)
end
context "when uncategorized category is not allowed" do
before do
SiteSetting.allow_uncategorized_topics = false
category.update!(read_restricted: false)
end
it "should convert private message into the right category" do
topic = TopicConverter.new(first_post.topic, admin).convert_to_public_topic
topic.reload
expect(topic).to be_valid
expect(topic.archetype).to eq("regular")
first_category =
Category
.where.not(id: SiteSetting.uncategorized_category_id)
.where(read_restricted: false)
.order("id asc")
.first
expect(topic.category_id).to eq(first_category.id)
expect(topic.category.topic_count).to eq(2)
end
end
context "when a custom category_id is given" do
it "should convert private message into the right category" do
topic = TopicConverter.new(first_post.topic, admin).convert_to_public_topic(category.id)
expect(topic.reload.category).to eq(category)
expect(topic.category.topic_count).to eq(2)
end
end
it "updates user stats" do
first_post
topic_user = TopicUser.find_by(user_id: author.id, topic_id: private_message.id)
expect(private_message.user.user_stat.topic_count).to eq(0)
expect(private_message.user.user_stat.post_count).to eq(0)
private_message.convert_to_public_topic(admin)
expect(private_message.reload.user.user_stat.topic_count).to eq(1)
expect(private_message.user.user_stat.post_count).to eq(0)
expect(topic_user.reload.notification_level).to eq(TopicUser.notification_levels[:watching])
end
context "with a reply" do
before do
Jobs.run_immediately!
UserActionManager.enable
first_post
create_post(topic: private_message, user: other_user)
private_message.reload
end
it "updates UserActions" do
TopicConverter.new(private_message, admin).convert_to_public_topic
expect(
author.user_actions.where(action_type: UserAction::NEW_PRIVATE_MESSAGE).count,
).to eq(0)
expect(author.user_actions.where(action_type: UserAction::NEW_TOPIC).count).to eq(1)
expect(
other_user.user_actions.where(action_type: UserAction::NEW_PRIVATE_MESSAGE).count,
).to eq(0)
expect(
other_user.user_actions.where(action_type: UserAction::GOT_PRIVATE_MESSAGE).count,
).to eq(0)
expect(other_user.user_actions.where(action_type: UserAction::REPLY).count).to eq(1)
end
end
it "creates small action and revision when not silent" do
Jobs.run_immediately!
first_post
expect do
TopicConverter.new(private_message, admin).convert_to_public_topic(category.id)
end.to change { PostRevision.count }.by(1)
expect(
private_message.posts.where(
post_type: Post.types[:small_action],
action_code: "public_topic",
),
).to be_present
end
it "records a revision but skips small action and bump when silent" do
Jobs.run_immediately!
first_post
bumped_at = private_message.reload.bumped_at
expect do
TopicConverter.new(private_message, admin, silent: true).convert_to_public_topic(
category.id,
)
end.to change { PostRevision.count }.by(1)
expect(private_message.first_post.revisions.last.hidden).to eq(true)
expect(private_message.posts.where(post_type: Post.types[:small_action])).to be_empty
expect(private_message.reload.bumped_at).to be_within(1.second).of(bumped_at)
end
it "deletes notifications for users not allowed to see the topic" do
staff_category = Fabricate(:private_category, group: Group[:staff])
user_notification =
Fabricate(:mentioned_notification, post: first_post, user: Fabricate(:user))
admin_notification =
Fabricate(:mentioned_notification, post: first_post, user: Fabricate(:admin))
Jobs.run_immediately!
TopicConverter.new(first_post.topic, admin).convert_to_public_topic(staff_category.id)
expect(Notification.exists?(user_notification.id)).to eq(false)
expect(Notification.exists?(admin_notification.id)).to eq(true)
end
end
end
describe "convert_to_private_message" do
fab!(:admin)
fab!(:author, :user)
fab!(:category)
fab!(:topic) { Fabricate(:topic, user: author, category_id: category.id) }
fab!(:post) { Fabricate(:post, topic: topic, user: topic.user) }
context "with success" do
it "converts regular topic to private message" do
private_message = topic.convert_to_private_message(admin)
expect(private_message).to be_valid
expect(topic.archetype).to eq("private_message")
expect(topic.category_id).to eq(nil)
expect(category.reload.topic_count).to eq(0)
end
it "converts unlisted topic to private message" do
topic.update_status("visible", false, admin)
private_message = topic.convert_to_private_message(admin)
expect(private_message).to be_valid
expect(topic.archetype).to eq("private_message")
expect(topic.category_id).to eq(nil)
expect(topic.user.post_count).to eq(0)
expect(topic.user.topic_count).to eq(0)
expect(category.reload.topic_count).to eq(0)
end
it "updates user stats when converting topic to private message" do
_post_2 = Fabricate(:post, topic: topic, user: author)
_post_3 = Fabricate(:post, topic: topic, user: author)
other_topic = Fabricate(:post).topic
other_post = Fabricate(:post, topic: other_topic)
topic_user = TopicUser.create!(user_id: author.id, topic_id: topic.id, posted: true)
expect do topic.convert_to_private_message(admin) end.to change {
author.reload.post_count
}.from(2).to(0).and change { author.reload.topic_count }.from(1).to(0)
# Ensure query does not affect users from other topics or posts as DB query to update count is quite complex.
expect(other_topic.user.post_count).to eq(0)
expect(other_topic.user.topic_count).to eq(1)
expect(other_post.user.post_count).to eq(1)
expect(other_post.user.topic_count).to eq(0)
expect(topic.reload.topic_allowed_users.where(user_id: author.id).count).to eq(1)
expect(topic_user.reload.notification_level).to eq(TopicUser.notification_levels[:watching])
end
it "invites only users with regular posts" do
post2 = Fabricate(:post, topic: topic)
Fabricate(:post, topic: topic, post_type: Post.types[:whisper])
Fabricate(:post, topic: topic, post_type: Post.types[:small_action])
topic.convert_to_private_message(admin)
expect(topic.reload.topic_allowed_users.pluck(:user_id)).to contain_exactly(
admin.id,
post.user_id,
post2.user_id,
)
end
it "changes user_action type" do
Jobs.run_immediately!
UserActionManager.enable
topic.convert_to_private_message(admin)
expect(author.user_actions.where(action_type: UserAction::NEW_TOPIC).count).to eq(0)
expect(author.user_actions.where(action_type: UserAction::NEW_PRIVATE_MESSAGE).count).to eq(
1,
)
end
it "deletes notifications for users not allowed to see the message" do
user_notification = Fabricate(:mentioned_notification, post: post, user: Fabricate(:user))
admin_notification = Fabricate(:mentioned_notification, post: post, user: Fabricate(:admin))
Jobs.run_immediately!
topic.convert_to_private_message(admin)
expect(Notification.exists?(user_notification.id)).to eq(false)
expect(Notification.exists?(admin_notification.id)).to eq(true)
end
it "fails with an error when posters exceed max_allowed_message_recipients" do
SiteSetting.max_allowed_message_recipients = 2
Fabricate(:post, topic: topic)
Fabricate(:post, topic: topic)
result = TopicConverter.new(topic, admin).convert_to_private_message
expect(result.errors[:base]).to be_present
expect(topic.reload.archetype).to eq(Archetype.default)
end
it "creates small action and revision when not silent" do
Jobs.run_immediately!
expect do TopicConverter.new(topic, admin).convert_to_private_message end.to change {
PostRevision.count
}.by(1)
expect(
topic.posts.where(post_type: Post.types[:small_action], action_code: "private_topic"),
).to be_present
end
it "records a revision but skips small action and bump when silent" do
Jobs.run_immediately!
bumped_at = topic.bumped_at
expect do
TopicConverter.new(topic, admin, silent: true).convert_to_private_message
end.to change { PostRevision.count }.by(1)
expect(topic.first_post.revisions.last.hidden).to eq(true)
expect(topic.posts.where(post_type: Post.types[:small_action])).to be_empty
expect(topic.reload.bumped_at).to be_within(1.second).of(bumped_at)
end
it "includes the poster of a single-post topic" do
moderator = Fabricate(:moderator)
private_message = topic.convert_to_private_message(moderator)
expect(private_message.allowed_users).to match_array([topic.user, moderator])
end
end
context "when topic has replies" do
let(:replied_user) { Fabricate(:coding_horror) }
before do
create_post(topic: topic, user: replied_user)
topic.reload
end
it "adds users who replied to topic in Private Message" do
topic.convert_to_private_message(admin)
expect(topic.reload.topic_allowed_users.where(user_id: replied_user.id).count).to eq(1)
expect(topic.reload.user.user_stat.post_count).to eq(0)
end
end
context "when user already exists in topic_allowed_users table" do
before { topic.topic_allowed_users.create!(user_id: admin.id) }
it "works" do
topic.convert_to_private_message(admin)
expect(topic.reload.archetype).to eq("private_message")
end
end
context "with user_profiles with newly converted PM as featured topic" do
it "sets all matching user_profile featured topic ids to nil" do
author.user_profile.update(featured_topic: topic)
topic.convert_to_private_message(admin)
expect(author.user_profile.reload.featured_topic).to eq(nil)
end
end
end
end