mirror of
https://github.com/discourse/discourse.git
synced 2024-12-30 15:33:42 +08:00
1c67917367
This ensures we only ever store correct post and topic timing when the client notifies. Previous to this change we would blindly trust the client. Additionally this has error correction code that will correct the last seen post number when you visit a topic with incorrect timings.
231 lines
7.4 KiB
Ruby
231 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
RSpec.describe PostTiming do
|
|
fab!(:post)
|
|
|
|
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)
|
|
TopicUser.create!(
|
|
topic_id: topic_id,
|
|
user_id: user_id,
|
|
last_read_post_number: last_read_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)
|
|
_tu_two = topic_user(2, 2)
|
|
_tu_three = topic_user(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)
|
|
|
|
tu = TopicUser.find_by(topic_id: topic_id, user_id: 2)
|
|
expect(tu.last_read_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)
|
|
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)
|
|
|
|
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
|
|
|
|
(2..5).each { |i| Fabricate(:post, topic: post.topic, post_number: i) }
|
|
user2 = Fabricate(:coding_horror, created_at: 1.day.ago)
|
|
Topic.reset_highest(post.topic.id)
|
|
|
|
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 { |i| Fabricate(:post, topic: pm, user: user1) }
|
|
|
|
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
|
|
@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
|
|
|
|
it "doesn't update the posts read count if the topic is a PM" do
|
|
pm = Fabricate(:private_message_post).topic
|
|
@timing_attrs = @timing_attrs.merge(topic_id: pm.id)
|
|
|
|
PostTiming.record_timing(@timing_attrs)
|
|
|
|
expect(@coding_horror.user_stat.posts_read_count).to eq(0)
|
|
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 { PostTiming.process_timings(post.user, post.topic_id, 1, [[post.post_number, 100]]) }
|
|
|
|
it "#destroy_last_for decrements the reads count for a post" do
|
|
PostTiming.destroy_last_for(post.user, topic_id: 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.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, topic_id: 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, topic_id: 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: topic.id)
|
|
|
|
expect(GroupUser.find_by(user: user, group: group).first_unread_pm_at).to eq_time(
|
|
post.topic.updated_at,
|
|
)
|
|
end
|
|
end
|
|
end
|