mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 17:53:55 +08:00
Backport of #42321 to release/2026.6. --- ## Summary Topic bookmark creation, listing, search, and reminder eligibility now require the first post to be visible to the user. The fix adds an inner join on the first post with hidden-post filtering to the list query and delegates creation and visibility checks to `guardian.can_see_post?` on the first post, preventing an authenticated user from bookmarking a topic or searching its metadata after the first post is hidden. ## Source - Patch Triage: https://patch.discourse.org/patch-triage/1530 Co-authored-by: discourse-patch-triage <272280883+discourse-patch-triage[bot]@users.noreply.github.com> --------- Co-authored-by: Chris Alberti <christo@discourse.org>
166 lines
5.5 KiB
Ruby
Vendored
166 lines
5.5 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe UserSummary do
|
|
it "produces secure summaries" do
|
|
topic = create_post.topic
|
|
user = topic.user
|
|
_reply = create_post(user: topic.user, topic: topic)
|
|
|
|
summary = UserSummary.new(user, Guardian.new)
|
|
|
|
expect(summary.topics.length).to eq(1)
|
|
expect(summary.replies.length).to eq(1)
|
|
expect(summary.top_categories.length).to eq(1)
|
|
expect(summary.top_categories.first[:topic_count]).to eq(1)
|
|
expect(summary.top_categories.first[:post_count]).to eq(1)
|
|
|
|
topic.update_columns(deleted_at: Time.now)
|
|
|
|
expect(summary.topics.length).to eq(0)
|
|
expect(summary.replies.length).to eq(0)
|
|
expect(summary.top_categories.length).to eq(0)
|
|
|
|
topic.update_columns(deleted_at: nil, visible: false)
|
|
|
|
expect(summary.topics.length).to eq(0)
|
|
expect(summary.replies.length).to eq(0)
|
|
expect(summary.top_categories.length).to eq(0)
|
|
|
|
category = Fabricate(:category)
|
|
topic.update_columns(category_id: category.id, deleted_at: nil, visible: true)
|
|
|
|
category.set_permissions(staff: :full)
|
|
category.save
|
|
|
|
expect(summary.topics.length).to eq(0)
|
|
expect(summary.replies.length).to eq(0)
|
|
expect(summary.top_categories.length).to eq(0)
|
|
end
|
|
|
|
it "is robust enough to handle bad data" do
|
|
UserActionManager.enable
|
|
|
|
liked_post = create_post
|
|
user = Fabricate(:user)
|
|
PostActionCreator.like(user, liked_post)
|
|
|
|
users = UserSummary.new(user, Guardian.new).most_liked_users
|
|
|
|
expect(users.map(&:id)).to eq([liked_post.user_id])
|
|
|
|
# really we should not be corrupting stuff like this
|
|
# but in production dbs this can happens sometimes I guess
|
|
liked_post.user.delete
|
|
|
|
users = UserSummary.new(user, Guardian.new).most_liked_users
|
|
expect(users).to eq([])
|
|
end
|
|
|
|
it "includes ordered top categories" do
|
|
u = Fabricate(:user)
|
|
|
|
UserSummary::MAX_SUMMARY_RESULTS.times do
|
|
c = Fabricate(:category)
|
|
t = Fabricate(:topic, category: c, user: u)
|
|
Fabricate(:post, user: u, topic: t)
|
|
end
|
|
|
|
top_category = Fabricate(:category)
|
|
t = Fabricate(:topic, category: top_category, user: u)
|
|
Fabricate(:post, user: u, topic: t)
|
|
Fabricate(:post, user: u, topic: t)
|
|
|
|
summary = UserSummary.new(u, Guardian.new)
|
|
|
|
expect(summary.top_categories.length).to eq(UserSummary::MAX_SUMMARY_RESULTS)
|
|
expect(summary.top_categories.first[:id]).to eq(top_category.id)
|
|
end
|
|
|
|
it "excludes moderator action posts" do
|
|
topic = create_post.topic
|
|
user = topic.user
|
|
create_post(user: user, topic: topic)
|
|
Fabricate(:small_action, topic: topic, user: user)
|
|
|
|
summary = UserSummary.new(user, Guardian.new)
|
|
|
|
expect(summary.topics.length).to eq(1)
|
|
expect(summary.replies.length).to eq(1)
|
|
expect(summary.top_categories.length).to eq(1)
|
|
expect(summary.top_categories.first[:topic_count]).to eq(1)
|
|
expect(summary.top_categories.first[:post_count]).to eq(1)
|
|
end
|
|
|
|
describe "#bookmark_count" do
|
|
fab!(:user)
|
|
fab!(:post_bookmark) { Fabricate(:bookmark, user:, bookmarkable: Fabricate(:post)) }
|
|
fab!(:topic_bookmark) { Fabricate(:bookmark, user:, bookmarkable: Fabricate(:topic_with_op)) }
|
|
|
|
let(:summary) { UserSummary.new(user, Guardian.new(user)) }
|
|
|
|
before do
|
|
Fabricate(:topic_user, user:, topic: post_bookmark.bookmarkable.topic)
|
|
Fabricate(:topic_user, user:, topic: topic_bookmark.bookmarkable)
|
|
end
|
|
|
|
it "counts accessible bookmarks" do
|
|
expect(summary.bookmark_count).to eq(2)
|
|
end
|
|
|
|
it "excludes bookmarks in inaccessible categories" do
|
|
post_bookmark.bookmarkable.topic.update!(
|
|
category: Fabricate(:private_category, group: Fabricate(:group)),
|
|
)
|
|
expect(summary.bookmark_count).to eq(1)
|
|
end
|
|
|
|
it "excludes bookmarks for deleted topics" do
|
|
post_bookmark.bookmarkable.topic.trash!
|
|
expect(summary.bookmark_count).to eq(1)
|
|
end
|
|
end
|
|
|
|
it "returns the most replied to users" do
|
|
topic1 = create_post.topic
|
|
topic1_post = create_post(topic: topic1)
|
|
topic1_reply =
|
|
create_post(topic: topic1, reply_to_post_number: topic1_post.post_number, user: topic1.user)
|
|
|
|
# Create a second topic by the same user as topic1
|
|
topic2 = create_post(user: topic1.user).topic
|
|
topic2_post = create_post(topic: topic2)
|
|
topic2_reply =
|
|
create_post(topic: topic2, reply_to_post_number: topic2_post.post_number, user: topic2.user)
|
|
|
|
# Don't include replies to whispers
|
|
topic3 = create_post(user: topic1.user).topic
|
|
topic3_post = create_post(topic: topic3, post_type: Post.types[:whisper])
|
|
topic3_reply = create_post(topic: topic3, user: topic1.user)
|
|
topic3_reply.update_columns(
|
|
reply_to_post_number: topic3_post.post_number,
|
|
post_type: Post.types[:whisper],
|
|
)
|
|
|
|
# Don't include replies to private messages
|
|
replied_to_user = Fabricate(:user)
|
|
topic4 =
|
|
create_post(
|
|
user: topic1.user,
|
|
archetype: Archetype.private_message,
|
|
target_usernames: [replied_to_user.username],
|
|
).topic
|
|
topic4_post = create_post(topic: topic4, user: replied_to_user)
|
|
topic4_reply =
|
|
create_post(topic: topic4, reply_to_post_number: topic4_post.post_number, user: topic4.user)
|
|
|
|
user_summary = UserSummary.new(topic1.user, Guardian.new(topic1.user))
|
|
most_replied_to_users = user_summary.most_replied_to_users
|
|
|
|
counts =
|
|
most_replied_to_users
|
|
.index_by { |user_with_count| user_with_count[:id] }
|
|
.transform_values { |c| c[:count] }
|
|
|
|
expect(counts).to eq({ topic1_post.user_id => 1, topic2_post.user_id => 1 })
|
|
end
|
|
end
|