discourse/spec/models/user_stat_spec.rb
Loïc Guitaut a5fbb90df4 FEATURE: Display pending posts on user’s page
Currently when a user creates posts that are moderated (for whatever
reason), a popup is displayed saying the post needs approval and the
total number of the user’s pending posts. But then this piece of
information is kind of lost and there is nowhere for the user to know
what are their pending posts or how many there are.

This patch solves this issue by adding a new “Pending” section to the
user’s activity page when there are some pending posts to display. When
there are none, then the “Pending” section isn’t displayed at all.
2021-11-29 10:26:33 +01:00

288 lines
9.3 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
describe UserStat do
it "is created automatically when a user is created" do
user = Fabricate(:evil_trout)
expect(user.user_stat).to be_present
# It populates the `new_since` field by default
expect(user.user_stat.new_since).to be_present
end
context '#update_view_counts' do
let(:user) { Fabricate(:user) }
let(:stat) { user.user_stat }
context 'topics_entered' do
context 'without any views' do
it "doesn't increase the user's topics_entered" do
expect { UserStat.update_view_counts; stat.reload }.not_to change(stat, :topics_entered)
end
end
context 'with a view' do
fab!(:topic) { Fabricate(:topic) }
let!(:view) { TopicViewItem.add(topic.id, '127.0.0.1', user.id) }
before do
user.update_column :last_seen_at, 1.second.ago
end
it "adds one to the topics entered" do
UserStat.update_view_counts
stat.reload
expect(stat.topics_entered).to eq(1)
end
it "won't record a second view as a different topic" do
TopicViewItem.add(topic.id, '127.0.0.1', user.id)
UserStat.update_view_counts
stat.reload
expect(stat.topics_entered).to eq(1)
end
end
end
context 'posts_read_count' do
context 'without any post timings' do
it "doesn't increase the user's posts_read_count" do
expect { UserStat.update_view_counts; stat.reload }.not_to change(stat, :posts_read_count)
end
end
context 'with a post timing' do
let!(:post) { Fabricate(:post) }
let!(:post_timings) do
PostTiming.record_timing(msecs: 1234, topic_id: post.topic_id, user_id: user.id, post_number: post.post_number)
end
before do
user.update_column :last_seen_at, 1.second.ago
end
it "increases posts_read_count" do
UserStat.update_view_counts
stat.reload
expect(stat.posts_read_count).to eq(1)
end
end
end
end
describe 'ensure consistency!' do
it 'can update first unread' do
post = create_post
freeze_time 10.minutes.from_now
create_post(topic_id: post.topic_id)
post.user.update!(last_seen_at: Time.now)
UserStat.ensure_consistency!
post.user.user_stat.reload
expect(post.user.user_stat.first_unread_at).to eq_time(Time.zone.now)
end
it 'updates first unread pm timestamp correctly' do
user = Fabricate(:user, last_seen_at: Time.zone.now)
user_2 = Fabricate(:user, last_seen_at: Time.zone.now)
pm_topic = Fabricate(:private_message_topic, user: user, recipient: user_2)
create_post(user: user, topic_id: pm_topic.id)
TopicUser.change(user.id, pm_topic.id,
notification_level: TopicUser.notification_levels[:tracking]
)
# user that is not tracking PM topic
TopicUser.change(user_2.id, pm_topic.id,
notification_level: TopicUser.notification_levels[:regular]
)
# User that has not been seen recently
user_3 = Fabricate(:user, last_seen_at: 1.year.ago)
pm_topic.allowed_users << user_3
TopicUser.change(user_3.id, pm_topic.id,
notification_level: TopicUser.notification_levels[:tracking]
)
user_3_orig_first_unread_pm_at = user_3.user_stat.first_unread_pm_at
# User that is not related to the PM
user_4 = Fabricate(:user, last_seen_at: Time.zone.now)
user_4_orig_first_unread_pm_at = user_4.user_stat.first_unread_pm_at
# User for another PM topic
user_5 = Fabricate(:user, last_seen_at: Time.zone.now)
user_6 = Fabricate(:user, last_seen_at: 10.minutes.ago)
pm_topic_2 = Fabricate(:private_message_topic, user: user_5, recipient: user_6)
create_post(user: user_5, topic_id: pm_topic_2.id)
TopicUser.change(user_5.id, pm_topic_2.id,
notification_level: TopicUser.notification_levels[:tracking]
)
# User out of last seen limit
TopicUser.change(user_6.id, pm_topic_2.id,
notification_level: TopicUser.notification_levels[:tracking]
)
create_post(user: user_6, topic_id: pm_topic_2.id)
user_6_orig_first_unread_pm_at = user_6.user_stat.first_unread_pm_at
create_post(user: user_2, topic_id: pm_topic.id)
create_post(user: user_6, topic_id: pm_topic_2.id)
pm_topic.update!(updated_at: 10.minutes.from_now)
pm_topic_2.update!(updated_at: 20.minutes.from_now)
stub_const(UserStat, "UPDATE_UNREAD_USERS_LIMIT", 4) do
UserStat.ensure_consistency!(1.hour.ago)
end
# User affected
expect(user.user_stat.reload.first_unread_pm_at).to be_within(1.seconds).of(pm_topic.reload.updated_at)
expect(user_2.user_stat.reload.first_unread_pm_at).to be_within(1.seconds).of(UserStat::UPDATE_UNREAD_MINUTES_AGO.minutes.ago)
expect(user_3.user_stat.reload.first_unread_pm_at).to eq_time(user_3_orig_first_unread_pm_at)
expect(user_4.user_stat.reload.first_unread_pm_at).to be_within(1.seconds).of(UserStat::UPDATE_UNREAD_MINUTES_AGO.minutes.ago)
expect(user_5.user_stat.reload.first_unread_pm_at).to eq_time(pm_topic_2.reload.updated_at)
expect(user_6.user_stat.reload.first_unread_pm_at).to eq_time(user_6_orig_first_unread_pm_at)
end
end
describe 'update_time_read!' do
fab!(:user) { Fabricate(:user) }
let(:stat) { user.user_stat }
it 'always expires redis key' do
# this tests implementation which is not 100% ideal
# that said, redis key leaks are not good
stat.update_time_read!
ttl = Discourse.redis.ttl(UserStat.last_seen_key(user.id))
expect(ttl).to be > 0
expect(ttl).to be <= UserStat::MAX_TIME_READ_DIFF
end
it 'makes no changes if nothing is cached' do
Discourse.redis.del(UserStat.last_seen_key(user.id))
stat.update_time_read!
stat.reload
expect(stat.time_read).to eq(0)
end
it 'makes a change if time read is below threshold' do
freeze_time
UserStat.cache_last_seen(user.id, (Time.now - 10).to_f)
stat.update_time_read!
stat.reload
expect(stat.time_read).to eq(10)
end
it 'makes no change if time read is above threshold' do
freeze_time
t = Time.now - 1 - UserStat::MAX_TIME_READ_DIFF
UserStat.cache_last_seen(user.id, t.to_f)
stat.update_time_read!
stat.reload
expect(stat.time_read).to eq(0)
end
end
describe 'update_distinct_badge_count' do
fab!(:user) { Fabricate(:user) }
let(:stat) { user.user_stat }
fab!(:badge1) { Fabricate(:badge) }
fab!(:badge2) { Fabricate(:badge) }
it "updates counts correctly" do
expect(stat.reload.distinct_badge_count).to eq(0)
BadgeGranter.grant(badge1, user)
expect(stat.reload.distinct_badge_count).to eq(1)
BadgeGranter.grant(badge1, user)
expect(stat.reload.distinct_badge_count).to eq(1)
BadgeGranter.grant(badge2, user)
expect(stat.reload.distinct_badge_count).to eq(2)
user.reload.user_badges.destroy_all
expect(stat.reload.distinct_badge_count).to eq(0)
end
it "can update counts for all users simultaneously" do
user2 = Fabricate(:user)
stat2 = user2.user_stat
BadgeGranter.grant(badge1, user)
BadgeGranter.grant(badge1, user)
BadgeGranter.grant(badge2, user)
BadgeGranter.grant(badge1, user2)
# Set some clearly incorrect values
stat.update(distinct_badge_count: 999)
stat2.update(distinct_badge_count: 999)
UserStat.ensure_consistency!
expect(stat.reload.distinct_badge_count).to eq(2)
expect(stat2.reload.distinct_badge_count).to eq(1)
end
it "ignores disabled badges" do
BadgeGranter.grant(badge1, user)
BadgeGranter.grant(badge2, user)
expect(stat.reload.distinct_badge_count).to eq(2)
badge2.update(enabled: false)
expect(stat.reload.distinct_badge_count).to eq(1)
badge2.update(enabled: true)
expect(stat.reload.distinct_badge_count).to eq(2)
end
end
describe '.update_draft_count' do
fab!(:user) { Fabricate(:user) }
it 'updates draft_count' do
Draft.create!(user: user, draft_key: "topic_1", data: {})
Draft.create!(user: user, draft_key: "new_topic", data: {})
Draft.create!(user: user, draft_key: "topic_2", data: {})
UserStat.update_all(draft_count: 0)
UserStat.update_draft_count(user.id)
expect(user.user_stat.draft_count).to eq(3)
end
end
describe "#update_pending_posts" do
subject(:update_pending_posts) { stat.update_pending_posts }
let!(:reviewable) { Fabricate(:reviewable_queued_post) }
let(:user) { reviewable.created_by }
let(:stat) { user.user_stat }
before do
stat.update!(pending_posts_count: 0) # the reviewable callback will have set this to 1 already.
end
it "sets 'pending_posts_count'" do
expect { update_pending_posts }.to change { stat.pending_posts_count }.to 1
end
it "publishes a message to clients" do
MessageBus.expects(:publish).with("/u/#{user.username_lower}/counters",
{ pending_posts_count: 1 },
user_ids: [user.id], group_ids: [Group::AUTO_GROUPS[:staff]])
update_pending_posts
end
end
end