Add notifications of severed relationships (#27511)
This commit is contained in:
		@@ -0,0 +1,6 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
Fabricator(:account_relationship_severance_event) do
 | 
			
		||||
  account
 | 
			
		||||
  relationship_severance_event
 | 
			
		||||
end
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
Fabricator(:relationship_severance_event) do
 | 
			
		||||
  type { :domain_block }
 | 
			
		||||
  target_name { 'example.com' }
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										8
									
								
								spec/fabricators/severed_relationship_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								spec/fabricators/severed_relationship_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
Fabricator(:severed_relationship) do
 | 
			
		||||
  local_account { Fabricate.build(:account) }
 | 
			
		||||
  remote_account { Fabricate.build(:account) }
 | 
			
		||||
  relationship_severance_event { Fabricate.build(:relationship_severance_event) }
 | 
			
		||||
  direction { :active }
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										49
									
								
								spec/models/relationship_severance_event_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								spec/models/relationship_severance_event_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe RelationshipSeveranceEvent do
 | 
			
		||||
  let(:local_account)  { Fabricate(:account) }
 | 
			
		||||
  let(:remote_account) { Fabricate(:account, domain: 'example.com') }
 | 
			
		||||
  let(:event)          { Fabricate(:relationship_severance_event) }
 | 
			
		||||
 | 
			
		||||
  describe '#import_from_active_follows!' do
 | 
			
		||||
    before do
 | 
			
		||||
      local_account.follow!(remote_account)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'imports the follow relationships with the expected direction' do
 | 
			
		||||
      event.import_from_active_follows!(local_account.active_relationships)
 | 
			
		||||
 | 
			
		||||
      relationships = event.severed_relationships.to_a
 | 
			
		||||
      expect(relationships.size).to eq 1
 | 
			
		||||
      expect(relationships[0].account).to eq local_account
 | 
			
		||||
      expect(relationships[0].target_account).to eq remote_account
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#import_from_passive_follows!' do
 | 
			
		||||
    before do
 | 
			
		||||
      remote_account.follow!(local_account)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'imports the follow relationships with the expected direction' do
 | 
			
		||||
      event.import_from_passive_follows!(local_account.passive_relationships)
 | 
			
		||||
 | 
			
		||||
      relationships = event.severed_relationships.to_a
 | 
			
		||||
      expect(relationships.size).to eq 1
 | 
			
		||||
      expect(relationships[0].account).to eq remote_account
 | 
			
		||||
      expect(relationships[0].target_account).to eq local_account
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#affected_local_accounts' do
 | 
			
		||||
    before do
 | 
			
		||||
      event.severed_relationships.create!(local_account: local_account, remote_account: remote_account, direction: :active)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'correctly lists local accounts' do
 | 
			
		||||
      expect(event.affected_local_accounts.to_a).to contain_exactly(local_account)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										45
									
								
								spec/models/severed_relationship_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								spec/models/severed_relationship_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
RSpec.describe SeveredRelationship do
 | 
			
		||||
  let(:local_account)  { Fabricate(:account) }
 | 
			
		||||
  let(:remote_account) { Fabricate(:account, domain: 'example.com') }
 | 
			
		||||
  let(:event)          { Fabricate(:relationship_severance_event) }
 | 
			
		||||
 | 
			
		||||
  describe '#account' do
 | 
			
		||||
    context 'when the local account is the follower' do
 | 
			
		||||
      let(:severed_relationship) { Fabricate(:severed_relationship, relationship_severance_event: event, local_account: local_account, remote_account: remote_account, direction: :active) }
 | 
			
		||||
 | 
			
		||||
      it 'returns the local account' do
 | 
			
		||||
        expect(severed_relationship.account).to eq local_account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when the local account is being followed' do
 | 
			
		||||
      let(:severed_relationship) { Fabricate(:severed_relationship, relationship_severance_event: event, local_account: local_account, remote_account: remote_account, direction: :passive) }
 | 
			
		||||
 | 
			
		||||
      it 'returns the remote account' do
 | 
			
		||||
        expect(severed_relationship.account).to eq remote_account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe '#target_account' do
 | 
			
		||||
    context 'when the local account is the follower' do
 | 
			
		||||
      let(:severed_relationship) { Fabricate(:severed_relationship, relationship_severance_event: event, local_account: local_account, remote_account: remote_account, direction: :active) }
 | 
			
		||||
 | 
			
		||||
      it 'returns the remote account' do
 | 
			
		||||
        expect(severed_relationship.target_account).to eq remote_account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when the local account is being followed' do
 | 
			
		||||
      let(:severed_relationship) { Fabricate(:severed_relationship, relationship_severance_event: event, local_account: local_account, remote_account: remote_account, direction: :passive) }
 | 
			
		||||
 | 
			
		||||
      it 'returns the local account' do
 | 
			
		||||
        expect(severed_relationship.target_account).to eq local_account
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										23
									
								
								spec/requests/severed_relationships_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								spec/requests/severed_relationships_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require 'rails_helper'
 | 
			
		||||
 | 
			
		||||
describe 'Severed relationships page' do
 | 
			
		||||
  include RoutingHelper
 | 
			
		||||
 | 
			
		||||
  describe 'GET severed_relationships#index' do
 | 
			
		||||
    let(:user) { Fabricate(:user) }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      sign_in user
 | 
			
		||||
 | 
			
		||||
      Fabricate(:severed_relationship, local_account: user.account)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'returns http success' do
 | 
			
		||||
      get severed_relationships_path
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_http_status(200)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -5,22 +5,33 @@ require 'rails_helper'
 | 
			
		||||
RSpec.describe AfterBlockDomainFromAccountService do
 | 
			
		||||
  subject { described_class.new }
 | 
			
		||||
 | 
			
		||||
  let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org', inbox_url: 'https://evil.org/inbox', protocol: :activitypub) }
 | 
			
		||||
  let!(:alice) { Fabricate(:account, username: 'alice') }
 | 
			
		||||
  let(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org', inbox_url: 'https://evil.org/wolf/inbox', protocol: :activitypub) }
 | 
			
		||||
  let(:dog)  { Fabricate(:account, username: 'dog', domain: 'evil.org', inbox_url: 'https://evil.org/dog/inbox', protocol: :activitypub) }
 | 
			
		||||
  let(:alice) { Fabricate(:account, username: 'alice') }
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
 | 
			
		||||
    wolf.follow!(alice)
 | 
			
		||||
    alice.follow!(dog)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'purge followers from blocked domain' do
 | 
			
		||||
    wolf.follow!(alice)
 | 
			
		||||
  around do |example|
 | 
			
		||||
    Sidekiq::Testing.fake! do
 | 
			
		||||
      example.run
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it 'purges followers from blocked domain, sends them Reject->Follow, and records severed relationships', :aggregate_failures do
 | 
			
		||||
    subject.call(alice, 'evil.org')
 | 
			
		||||
 | 
			
		||||
    expect(wolf.following?(alice)).to be false
 | 
			
		||||
  end
 | 
			
		||||
    expect(ActivityPub::DeliveryWorker.jobs.pluck('args')).to contain_exactly(
 | 
			
		||||
      [a_string_including('"type":"Reject"'), alice.id, wolf.inbox_url],
 | 
			
		||||
      [a_string_including('"type":"Undo"'), alice.id, dog.inbox_url]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
  it 'sends Reject->Follow to followers from blocked domain' do
 | 
			
		||||
    wolf.follow!(alice)
 | 
			
		||||
    subject.call(alice, 'evil.org')
 | 
			
		||||
    expect(ActivityPub::DeliveryWorker).to have_received(:perform_async).once
 | 
			
		||||
    severed_relationships = alice.severed_relationships.to_a
 | 
			
		||||
    expect(severed_relationships.count).to eq 2
 | 
			
		||||
    expect(severed_relationships[0].relationship_severance_event).to eq severed_relationships[1].relationship_severance_event
 | 
			
		||||
    expect(severed_relationships.map { |rel| [rel.account, rel.target_account] }).to contain_exactly([wolf, alice], [alice, dog])
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@ require 'rails_helper'
 | 
			
		||||
RSpec.describe BlockDomainService do
 | 
			
		||||
  subject { described_class.new }
 | 
			
		||||
 | 
			
		||||
  let(:local_account) { Fabricate(:account) }
 | 
			
		||||
  let(:bystander) { Fabricate(:account, domain: 'evil.org') }
 | 
			
		||||
  let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
 | 
			
		||||
  let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') }
 | 
			
		||||
  let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
 | 
			
		||||
@@ -13,62 +15,51 @@ RSpec.describe BlockDomainService do
 | 
			
		||||
 | 
			
		||||
  describe 'for a suspension' do
 | 
			
		||||
    before do
 | 
			
		||||
      local_account.follow!(bad_account)
 | 
			
		||||
      bystander.follow!(local_account)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'creates a domain block, suspends remote accounts with appropriate suspension date, records severed relationships', :aggregate_failures do
 | 
			
		||||
      subject.call(DomainBlock.create!(domain: 'evil.org', severity: :suspend))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'creates a domain block' do
 | 
			
		||||
      expect(DomainBlock.blocked?('evil.org')).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'removes remote accounts from that domain' do
 | 
			
		||||
      # Suspends account with appropriate suspension date
 | 
			
		||||
      expect(bad_account.reload.suspended?).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'records suspension date appropriately' do
 | 
			
		||||
      expect(bad_account.reload.suspended_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'keeps already-banned accounts banned' do
 | 
			
		||||
      # Keep already-suspended account without updating the suspension date
 | 
			
		||||
      expect(already_banned_account.reload.suspended?).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not overwrite suspension date of already-banned accounts' do
 | 
			
		||||
      expect(already_banned_account.reload.suspended_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'removes the remote accounts\'s statuses and media attachments' do
 | 
			
		||||
      # Removes content
 | 
			
		||||
      expect { bad_status_plain.reload }.to raise_exception ActiveRecord::RecordNotFound
 | 
			
		||||
      expect { bad_status_with_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
 | 
			
		||||
      expect { bad_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
 | 
			
		||||
 | 
			
		||||
      # Records severed relationships
 | 
			
		||||
      severed_relationships = local_account.severed_relationships.to_a
 | 
			
		||||
      expect(severed_relationships.count).to eq 2
 | 
			
		||||
      expect(severed_relationships[0].relationship_severance_event).to eq severed_relationships[1].relationship_severance_event
 | 
			
		||||
      expect(severed_relationships.map { |rel| [rel.account, rel.target_account] }).to contain_exactly([bystander, local_account], [local_account, bad_account])
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe 'for a silence with reject media' do
 | 
			
		||||
    before do
 | 
			
		||||
    it 'does not mark the domain as blocked, but silences accounts with an appropriate silencing date, clears media', :aggregate_failures, :sidekiq_inline do
 | 
			
		||||
      subject.call(DomainBlock.create!(domain: 'evil.org', severity: :silence, reject_media: true))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not create a domain block' do
 | 
			
		||||
      expect(DomainBlock.blocked?('evil.org')).to be false
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'silences remote accounts from that domain' do
 | 
			
		||||
      # Silences account with appropriate silecing date
 | 
			
		||||
      expect(bad_account.reload.silenced?).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'records suspension date appropriately' do
 | 
			
		||||
      expect(bad_account.reload.silenced_at).to eq DomainBlock.find_by(domain: 'evil.org').created_at
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'keeps already-banned accounts banned' do
 | 
			
		||||
      # Keeps already-silenced accounts without updating the silecing date
 | 
			
		||||
      expect(already_banned_account.reload.silenced?).to be true
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'does not overwrite suspension date of already-banned accounts' do
 | 
			
		||||
      expect(already_banned_account.reload.silenced_at).to_not eq DomainBlock.find_by(domain: 'evil.org').created_at
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'leaves the domains status and attachments, but clears media', :sidekiq_inline do
 | 
			
		||||
      # Leaves posts but clears media
 | 
			
		||||
      expect { bad_status_plain.reload }.to_not raise_error
 | 
			
		||||
      expect { bad_status_with_attachment.reload }.to_not raise_error
 | 
			
		||||
      expect { bad_attachment.reload }.to_not raise_error
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ RSpec.describe SuspendAccountService, :sidekiq_inline do
 | 
			
		||||
        remote_follower.follow!(account)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'sends an update actor to followers and reporters' do
 | 
			
		||||
      it 'sends an Update actor activity to followers and reporters' do
 | 
			
		||||
        subject
 | 
			
		||||
        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
 | 
			
		||||
        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
 | 
			
		||||
@@ -85,9 +85,14 @@ RSpec.describe SuspendAccountService, :sidekiq_inline do
 | 
			
		||||
        account.follow!(local_followee)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'sends a reject follow' do
 | 
			
		||||
      it 'sends a Reject Follow activity, and records severed relationships', :aggregate_failures do
 | 
			
		||||
        subject
 | 
			
		||||
 | 
			
		||||
        expect(a_request(:post, account.inbox_url).with { |req| match_reject_follow_request(req, account, local_followee) }).to have_been_made.once
 | 
			
		||||
 | 
			
		||||
        severed_relationships = local_followee.severed_relationships.to_a
 | 
			
		||||
        expect(severed_relationships.count).to eq 1
 | 
			
		||||
        expect(severed_relationships.map { |rel| [rel.account, rel.target_account] }).to contain_exactly([account, local_followee])
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user