mirror of
https://github.com/discourse/discourse.git
synced 2024-12-02 06:26:41 +08:00
9b75d95fc6
This optimization helps to filter away topics so that the joins on related tables when querying for unread messages is not expensive.
225 lines
7.4 KiB
Ruby
225 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe PostTiming do
|
|
|
|
it { is_expected.to validate_presence_of :post_number }
|
|
it { is_expected.to validate_presence_of :msecs }
|
|
|
|
describe 'pretend_read' do
|
|
fab!(:p1) { Fabricate(:post) }
|
|
fab!(:p2) { Fabricate(:post, topic: p1.topic, user: p1.user) }
|
|
fab!(:p3) { Fabricate(:post, topic: p1.topic, user: p1.user) }
|
|
|
|
let :topic_id do
|
|
p1.topic_id
|
|
end
|
|
|
|
def timing(user_id, post_number)
|
|
PostTiming.create!(topic_id: topic_id, user_id: user_id, post_number: post_number, msecs: 0)
|
|
end
|
|
|
|
def topic_user(user_id, last_read_post_number, highest_seen_post_number)
|
|
TopicUser.create!(
|
|
topic_id: topic_id,
|
|
user_id: user_id,
|
|
last_read_post_number: last_read_post_number,
|
|
highest_seen_post_number: highest_seen_post_number
|
|
)
|
|
end
|
|
|
|
it 'works correctly' do
|
|
timing(1, 1)
|
|
timing(2, 1)
|
|
timing(2, 2)
|
|
timing(3, 1)
|
|
timing(3, 2)
|
|
timing(3, 3)
|
|
|
|
_tu_one = topic_user(1, 1, 1)
|
|
_tu_two = topic_user(2, 2, 2)
|
|
_tu_three = topic_user(3, 3, 3)
|
|
|
|
PostTiming.pretend_read(topic_id, 2, 3)
|
|
|
|
expect(PostTiming.where(topic_id: topic_id, user_id: 1, post_number: 3).count).to eq(0)
|
|
expect(PostTiming.where(topic_id: topic_id, user_id: 2, post_number: 3).count).to eq(1)
|
|
expect(PostTiming.where(topic_id: topic_id, user_id: 3, post_number: 3).count).to eq(1)
|
|
|
|
tu = TopicUser.find_by(topic_id: topic_id, user_id: 1)
|
|
expect(tu.last_read_post_number).to eq(1)
|
|
expect(tu.highest_seen_post_number).to eq(1)
|
|
|
|
tu = TopicUser.find_by(topic_id: topic_id, user_id: 2)
|
|
expect(tu.last_read_post_number).to eq(3)
|
|
expect(tu.highest_seen_post_number).to eq(3)
|
|
|
|
tu = TopicUser.find_by(topic_id: topic_id, user_id: 3)
|
|
expect(tu.last_read_post_number).to eq(3)
|
|
expect(tu.highest_seen_post_number).to eq(3)
|
|
|
|
end
|
|
end
|
|
|
|
describe 'safeguard' do
|
|
it "doesn't store timings that are larger than the account lifetime" do
|
|
user = Fabricate(:user, created_at: 3.minutes.ago)
|
|
post = Fabricate(:post)
|
|
|
|
PostTiming.process_timings(user, post.topic_id, 1, [[post.post_number, 123]])
|
|
msecs = PostTiming.where(post_number: post.post_number, user_id: user.id).pluck(:msecs)[0]
|
|
expect(msecs).to eq(123)
|
|
|
|
PostTiming.process_timings(user, post.topic_id, 1, [[post.post_number, 10.minutes.to_i * 1000]])
|
|
msecs = PostTiming.where(post_number: post.post_number, user_id: user.id).pluck(:msecs)[0]
|
|
expect(msecs).to eq(123 + PostTiming::MAX_READ_TIME_PER_BATCH)
|
|
end
|
|
|
|
end
|
|
|
|
describe 'process_timings' do
|
|
|
|
# integration tests
|
|
|
|
it 'processes timings correctly' do
|
|
PostActionNotifier.enable
|
|
|
|
post = Fabricate(:post)
|
|
(2..5).each do |i|
|
|
Fabricate(:post, topic: post.topic, post_number: i)
|
|
end
|
|
user2 = Fabricate(:coding_horror, created_at: 1.day.ago)
|
|
|
|
PostActionCreator.like(user2, post)
|
|
|
|
expect(post.user.unread_notifications).to eq(1)
|
|
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
|
|
|
|
post.user.reload
|
|
expect(post.user.unread_notifications).to eq(0)
|
|
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 1.day]])
|
|
|
|
user_visit = post.user.user_visits.order('id DESC').first
|
|
expect(user_visit.posts_read).to eq(1)
|
|
|
|
# Skip to bottom
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[5, 100]])
|
|
expect(user_visit.reload.posts_read).to eq(2)
|
|
|
|
# Scroll up
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[4, 100]])
|
|
expect(user_visit.reload.posts_read).to eq(3)
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[2, 100], [3, 100]])
|
|
expect(user_visit.reload.posts_read).to eq(5)
|
|
end
|
|
|
|
it 'does not count private message posts read' do
|
|
pm = Fabricate(:private_message_topic, user: Fabricate(:admin))
|
|
user1, user2 = pm.topic_allowed_users.map(&:user)
|
|
|
|
(1..3).each do |i|
|
|
Fabricate(:post, topic: pm, user: user1)
|
|
end
|
|
|
|
PostTiming.process_timings(user2, pm.id, 10, [[1, 100]])
|
|
user_visit = user2.user_visits.last
|
|
expect(user_visit.posts_read).to eq(0)
|
|
|
|
PostTiming.process_timings(user2, pm.id, 10, [[2, 100], [3, 100]])
|
|
expect(user_visit.reload.posts_read).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe 'recording' do
|
|
before do
|
|
@post = Fabricate(:post)
|
|
@topic = @post.topic
|
|
@coding_horror = Fabricate(:coding_horror)
|
|
@timing_attrs = { msecs: 1234, topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number }
|
|
end
|
|
|
|
it 'adds a view to the post' do
|
|
expect {
|
|
PostTiming.record_timing(@timing_attrs)
|
|
@post.reload
|
|
}.to change(@post, :reads).by(1)
|
|
end
|
|
|
|
describe 'multiple calls' do
|
|
it 'correctly works' do
|
|
PostTiming.record_timing(@timing_attrs)
|
|
PostTiming.record_timing(@timing_attrs)
|
|
timing = PostTiming.find_by(topic_id: @post.topic_id, user_id: @coding_horror.id, post_number: @post.post_number)
|
|
|
|
expect(timing).to be_present
|
|
expect(timing.msecs).to eq(2468)
|
|
|
|
expect(@coding_horror.user_stat.posts_read_count).to eq(1)
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
describe 'decrementing posts read count when destroying post timings' do
|
|
let(:initial_read_count) { 0 }
|
|
let(:post) { Fabricate(:post, reads: initial_read_count) }
|
|
|
|
before do
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
|
|
end
|
|
|
|
it '#destroy_last_for decrements the reads count for a post' do
|
|
PostTiming.destroy_last_for(post.user, post.topic_id)
|
|
|
|
expect(post.reload.reads).to eq initial_read_count
|
|
end
|
|
|
|
it '#destroy_for decrements the reads count for a post' do
|
|
PostTiming.destroy_for(post.user, [post.topic_id])
|
|
|
|
expect(post.reload.reads).to eq initial_read_count
|
|
end
|
|
end
|
|
|
|
describe '.destroy_last_for' do
|
|
it 'updates first unread for a user correctly when topic is public' do
|
|
post = Fabricate(:post)
|
|
post.topic.update!(updated_at: 10.minutes.ago)
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
|
|
|
|
PostTiming.destroy_last_for(post.user, post.topic_id)
|
|
|
|
expect(post.user.user_stat.reload.first_unread_at).to eq_time(post.topic.updated_at)
|
|
end
|
|
|
|
it 'updates first unread for a user correctly when topic is a pm' do
|
|
post = Fabricate(:private_message_post)
|
|
post.topic.update!(updated_at: 10.minutes.ago)
|
|
PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]])
|
|
|
|
PostTiming.destroy_last_for(post.user, post.topic_id)
|
|
|
|
expect(post.user.user_stat.reload.first_unread_pm_at).to eq_time(post.topic.updated_at)
|
|
end
|
|
|
|
it 'updates first unread for a user correctly when topic is a group pm' do
|
|
topic = Fabricate(:private_message_topic, updated_at: 10.minutes.ago)
|
|
post = Fabricate(:post, topic: topic)
|
|
user = Fabricate(:user)
|
|
group = Fabricate(:group)
|
|
group.add(user)
|
|
topic.allowed_groups << group
|
|
PostTiming.process_timings(user, topic.id, 1, [[post.post_number, 100]])
|
|
|
|
PostTiming.destroy_last_for(user, topic.id)
|
|
|
|
expect(GroupUser.find_by(user: user, group: group).first_unread_pm_at)
|
|
.to eq_time(post.topic.updated_at)
|
|
end
|
|
end
|
|
end
|