Add Fetch All Replies Part 1: Backend (#32615)
Signed-off-by: sneakers-the-rat <sneakers-the-rat@protonmail.com> Co-authored-by: jonny <j@nny.fyi> Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: Kouhai <66407198+kouhaidev@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										90
									
								
								spec/services/activitypub/fetch_all_replies_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								spec/services/activitypub/fetch_all_replies_service_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe ActivityPub::FetchAllRepliesService do
 | 
			
		||||
  subject { described_class.new }
 | 
			
		||||
 | 
			
		||||
  let(:actor)          { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
 | 
			
		||||
  let(:status)         { Fabricate(:status, account: actor) }
 | 
			
		||||
  let(:collection_uri) { 'http://example.com/replies/1' }
 | 
			
		||||
 | 
			
		||||
  let(:items) do
 | 
			
		||||
    [
 | 
			
		||||
      'http://example.com/self-reply-1',
 | 
			
		||||
      'http://example.com/self-reply-2',
 | 
			
		||||
      'http://example.com/self-reply-3',
 | 
			
		||||
      'http://other.com/other-reply-1',
 | 
			
		||||
      'http://other.com/other-reply-2',
 | 
			
		||||
      'http://other.com/other-reply-3',
 | 
			
		||||
      'http://example.com/self-reply-4',
 | 
			
		||||
      'http://example.com/self-reply-5',
 | 
			
		||||
      'http://example.com/self-reply-6',
 | 
			
		||||
    ]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  let(:payload) do
 | 
			
		||||
    {
 | 
			
		||||
      '@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
      type: 'Collection',
 | 
			
		||||
      id: collection_uri,
 | 
			
		||||
      items: items,
 | 
			
		||||
    }.with_indifferent_access
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#call' do
 | 
			
		||||
    it 'fetches more than the default maximum and from multiple domains' do
 | 
			
		||||
      allow(FetchReplyWorker).to receive(:push_bulk)
 | 
			
		||||
 | 
			
		||||
      subject.call(payload, status.uri)
 | 
			
		||||
 | 
			
		||||
      expect(FetchReplyWorker).to have_received(:push_bulk).with(%w(http://example.com/self-reply-1 http://example.com/self-reply-2 http://example.com/self-reply-3 http://other.com/other-reply-1 http://other.com/other-reply-2 http://other.com/other-reply-3 http://example.com/self-reply-4
 | 
			
		||||
                                                                    http://example.com/self-reply-5 http://example.com/self-reply-6))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with a recent status' do
 | 
			
		||||
      before do
 | 
			
		||||
        Fabricate(:status, uri: 'http://example.com/self-reply-2', fetched_replies_at: 1.second.ago, local: false)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'skips statuses that have been updated recently' do
 | 
			
		||||
        allow(FetchReplyWorker).to receive(:push_bulk)
 | 
			
		||||
 | 
			
		||||
        subject.call(payload, status.uri)
 | 
			
		||||
 | 
			
		||||
        expect(FetchReplyWorker).to have_received(:push_bulk).with(%w(http://example.com/self-reply-1 http://example.com/self-reply-3 http://other.com/other-reply-1 http://other.com/other-reply-2 http://other.com/other-reply-3 http://example.com/self-reply-4 http://example.com/self-reply-5 http://example.com/self-reply-6))
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with an old status' do
 | 
			
		||||
      before do
 | 
			
		||||
        Fabricate(:status, uri: 'http://other.com/other-reply-1', fetched_replies_at: 1.year.ago, created_at: 1.year.ago, account: actor)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'updates the time that fetched statuses were last fetched' do
 | 
			
		||||
        allow(FetchReplyWorker).to receive(:push_bulk)
 | 
			
		||||
 | 
			
		||||
        subject.call(payload, status.uri)
 | 
			
		||||
 | 
			
		||||
        expect(Status.find_by(uri: 'http://other.com/other-reply-1').fetched_replies_at).to be >= 1.minute.ago
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with unsubscribed replies' do
 | 
			
		||||
      before do
 | 
			
		||||
        remote_actor = Fabricate(:account, domain: 'other.com', uri: 'http://other.com/account')
 | 
			
		||||
        # reply not in the collection from the remote instance, but we know about anyway without anyone following the account
 | 
			
		||||
        Fabricate(:status, account: remote_actor, in_reply_to_id: status.id, uri: 'http://other.com/account/unsubscribed', fetched_replies_at: 1.year.ago, created_at: 1.year.ago)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'updates the unsubscribed replies' do
 | 
			
		||||
        allow(FetchReplyWorker).to receive(:push_bulk)
 | 
			
		||||
 | 
			
		||||
        subject.call(payload, status.uri)
 | 
			
		||||
 | 
			
		||||
        expect(FetchReplyWorker).to have_received(:push_bulk).with(%w(http://example.com/self-reply-1 http://example.com/self-reply-2 http://example.com/self-reply-3 http://other.com/other-reply-1 http://other.com/other-reply-2 http://other.com/other-reply-3 http://example.com/self-reply-4
 | 
			
		||||
                                                                      http://example.com/self-reply-5 http://example.com/self-reply-6 http://other.com/account/unsubscribed))
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -9,6 +9,9 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 | 
			
		||||
 | 
			
		||||
  let!(:sender) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar') }
 | 
			
		||||
 | 
			
		||||
  let(:follower) { Fabricate(:account, username: 'alice') }
 | 
			
		||||
  let(:follow) { nil }
 | 
			
		||||
  let(:response) { { body: Oj.dump(object), headers: { 'content-type': 'application/activity+json' } } }
 | 
			
		||||
  let(:existing_status) { nil }
 | 
			
		||||
 | 
			
		||||
  let(:note) do
 | 
			
		||||
@@ -23,13 +26,14 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    stub_request(:get, 'https://foo.bar/watch?v=12345').to_return(status: 404, body: '')
 | 
			
		||||
    stub_request(:get, object[:id]).to_return(body: Oj.dump(object))
 | 
			
		||||
    stub_request(:get, object[:id]).to_return(**response)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#call' do
 | 
			
		||||
    before do
 | 
			
		||||
      follow
 | 
			
		||||
      existing_status
 | 
			
		||||
      subject.call(object[:id], prefetched_body: Oj.dump(object))
 | 
			
		||||
      subject.call(object[:id])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'with Note object' do
 | 
			
		||||
@@ -254,6 +258,51 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 | 
			
		||||
          expect(existing_status.text).to eq 'Lorem ipsum'
 | 
			
		||||
          expect(existing_status.edits).to_not be_empty
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        context 'when the status appears to have been deleted at source' do
 | 
			
		||||
          let(:response) { { status: 404, body: '' } }
 | 
			
		||||
 | 
			
		||||
          shared_examples 'no delete' do
 | 
			
		||||
            it 'does not delete the status' do
 | 
			
		||||
              existing_status.reload
 | 
			
		||||
              expect(existing_status.text).to eq 'Foo'
 | 
			
		||||
              expect(existing_status.edits).to be_empty
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          context 'when the status is orphaned/unsubscribed' do
 | 
			
		||||
            it 'deletes the orphaned status' do
 | 
			
		||||
              expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          context 'when the status is from an account with only remote followers' do
 | 
			
		||||
            let(:follower) { Fabricate(:account, username: 'alice', domain: 'foo.bar') }
 | 
			
		||||
            let(:follow) { Fabricate(:follow, account: follower, target_account: sender, created_at: 2.days.ago) }
 | 
			
		||||
 | 
			
		||||
            it 'deletes the orphaned status' do
 | 
			
		||||
              expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            context 'when the status is private' do
 | 
			
		||||
              let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :private) }
 | 
			
		||||
 | 
			
		||||
              it_behaves_like 'no delete'
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            context 'when the status is direct' do
 | 
			
		||||
              let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :direct) }
 | 
			
		||||
 | 
			
		||||
              it_behaves_like 'no delete'
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
 | 
			
		||||
          context 'when the status is from an account with local followers' do
 | 
			
		||||
            let(:follow) { Fabricate(:follow, account: follower, target_account: sender, created_at: 2.days.ago) }
 | 
			
		||||
 | 
			
		||||
            it_behaves_like 'no delete'
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context 'with a Create activity' do
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user