mirror of
https://github.com/discourse/discourse.git
synced 2026-03-03 23:54:20 +08:00
Previously, `UserSummary#bookmark_count` used a simple database count that included all bookmarks regardless of whether the user could still access the bookmarked content. This led to inflated counts when: - Topics were moved to private categories the user can't access - Topics or posts were soft-deleted - Access was revoked for any other reason The fix delegates counting to `BookmarkQuery#count_all`, which applies the same permission checks used when listing bookmarks. This ensures the count shown on user profiles matches what users can actually see. To avoid duplicating query logic, the existing `list_all` method was refactored to extract `build_list_queries` as a shared helper. The `unread_notifications` method was also cleaned up by breaking out several smaller private methods for better readability. Ref - https://meta.discourse.org/t/391822
163 lines
5.4 KiB
Ruby
163 lines
5.4 KiB
Ruby
# 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)) }
|
|
|
|
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, reply_to_post_number: topic3_post.post_number, user: topic3.user)
|
|
|
|
# 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
|