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 let!(:p1) { Fabricate(:post) } let!(:p2) { Fabricate(:post, topic: p1.topic, user: p1.user) } let!(: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) PostAction.act(user2, post, PostActionType.types[:like]) 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 describe 'avg times' do describe 'posts' do it 'has no avg_time by default' do expect(@post.avg_time).to be_blank end it "doesn't change when we calculate the avg time for the post because there's no timings" do Post.calculate_avg_time @post.reload expect(@post.avg_time).to be_blank end end describe 'topics' do it 'has no avg_time by default' do expect(@topic.avg_time).to be_blank end it "doesn't change when we calculate the avg time for the post because there's no timings" do Topic.calculate_avg_time @topic.reload expect(@topic.avg_time).to be_blank end end describe "it doesn't create an avg time for the same user" do it 'something' do PostTiming.record_timing(@timing_attrs.merge(user_id: @post.user_id)) Post.calculate_avg_time @post.reload expect(@post.avg_time).to be_blank end end describe 'with a timing for another user' do before do PostTiming.record_timing(@timing_attrs) Post.calculate_avg_time @post.reload end it 'has a post avg_time from the timing' do expect(@post.avg_time).to be_present end describe 'forum topics' do before do Topic.calculate_avg_time @topic.reload end it 'has an avg_time from the timing' do expect(@topic.avg_time).to be_present end end end end end end