From 27965ce5edff20db2de1dd233c88f8393bb0da0b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 25 Feb 2022 00:34:14 +0100 Subject: Add trending statuses (#17431) * Add trending statuses * Fix dangling items with stale scores in localized sets * Various fixes and improvements - Change approve_all/reject_all to approve_accounts/reject_accounts - Change Trends::Query methods to not mutate the original query - Change Trends::Query#skip to offset - Change follow recommendations to be refreshed in a transaction * Add tests for trending statuses filtering behaviour * Fix not applying filtering scope in controller --- spec/models/trends/statuses_spec.rb | 110 ++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 spec/models/trends/statuses_spec.rb (limited to 'spec/models/trends/statuses_spec.rb') diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb new file mode 100644 index 000000000..9cc67acbe --- /dev/null +++ b/spec/models/trends/statuses_spec.rb @@ -0,0 +1,110 @@ +require 'rails_helper' + +RSpec.describe Trends::Statuses do + subject! { described_class.new(threshold: 5, review_threshold: 10, score_halflife: 8.hours) } + + let!(:at_time) { DateTime.new(2021, 11, 14, 10, 15, 0) } + + describe 'Trends::Statuses::Query' do + let!(:query) { subject.query } + let!(:today) { at_time } + + let!(:status1) { Fabricate(:status, text: 'Foo', trendable: true, created_at: today) } + let!(:status2) { Fabricate(:status, text: 'Bar', trendable: true, created_at: today) } + + before do + 15.times { reblog(status1, today) } + 12.times { reblog(status2, today) } + + subject.refresh(today) + end + + describe '#filtered_for' do + let(:account) { Fabricate(:account) } + + it 'returns a composable query scope' do + expect(query.filtered_for(account)).to be_a Trends::Query + end + + it 'filters out blocked accounts' do + account.block!(status1.account) + expect(query.filtered_for(account).to_a).to eq [status2] + end + + it 'filters out muted accounts' do + account.mute!(status2.account) + expect(query.filtered_for(account).to_a).to eq [status1] + end + + it 'filters out blocked-by accounts' do + status1.account.block!(account) + expect(query.filtered_for(account).to_a).to eq [status2] + end + end + end + + describe '#add' do + let(:status) { Fabricate(:status) } + + before do + subject.add(status, 1, at_time) + end + + it 'records use' do + expect(subject.send(:recently_used_ids, at_time)).to eq [status.id] + end + end + + describe '#query' do + it 'returns a composable query scope' do + expect(subject.query).to be_a Trends::Query + end + + it 'responds to filtered_for' do + expect(subject.query).to respond_to(:filtered_for) + end + end + + describe '#refresh' do + let!(:today) { at_time } + let!(:yesterday) { today - 1.day } + + let!(:status1) { Fabricate(:status, text: 'Foo', trendable: true, created_at: yesterday) } + let!(:status2) { Fabricate(:status, text: 'Bar', trendable: true, created_at: today) } + let!(:status3) { Fabricate(:status, text: 'Baz', trendable: true, created_at: today) } + + before do + 13.times { reblog(status1, today) } + 13.times { reblog(status2, today) } + 4.times { reblog(status3, today) } + end + + context do + before do + subject.refresh(today) + end + + it 'calculates and re-calculates scores' do + expect(subject.query.limit(10).to_a).to eq [status2, status1] + end + + it 'omits statuses below threshold' do + expect(subject.query.limit(10).to_a).to_not include(status3) + end + end + + it 'decays scores' do + subject.refresh(today) + original_score = subject.score(status2.id) + expect(original_score).to be_a Float + subject.refresh(today + subject.options[:score_halflife]) + decayed_score = subject.score(status2.id) + expect(decayed_score).to be <= original_score / 2 + end + end + + def reblog(status, at_time) + reblog = Fabricate(:status, reblog: status, created_at: at_time) + subject.add(status, reblog.account_id, at_time) + end +end -- cgit