~cytrogen/masto-fe

4c5aa0e470fb92f8f9a5521357f2cf59263a49b5 — Matt Jankowski 2 years ago 24015ef
Update rubocop-rspec to version 2.22.0, fix `RSpec/IndexedLet` cop (#24698)

M .rubocop_todo.yml => .rubocop_todo.yml +0 -5
@@ 199,14 199,9 @@ Naming/VariableNumber:
    - 'db/migrate/20190820003045_update_statuses_index.rb'
    - 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
    - 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
    - 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb'
    - 'spec/lib/feed_manager_spec.rb'
    - 'spec/models/account_spec.rb'
    - 'spec/models/concerns/account_interactions_spec.rb'
    - 'spec/models/custom_emoji_filter_spec.rb'
    - 'spec/models/domain_block_spec.rb'
    - 'spec/models/user_spec.rb'
    - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb'

# This cop supports unsafe autocorrection (--autocorrect-all).
Performance/UnfreezeString:

M Gemfile.lock => Gemfile.lock +4 -1
@@ 606,6 606,8 @@ GEM
      parser (>= 3.2.1.0)
    rubocop-capybara (2.18.0)
      rubocop (~> 1.41)
    rubocop-factory_bot (2.23.1)
      rubocop (~> 1.33)
    rubocop-performance (1.18.0)
      rubocop (>= 1.7.0, < 2.0)
      rubocop-ast (>= 0.4.0)


@@ 613,9 615,10 @@ GEM
      activesupport (>= 4.2.0)
      rack (>= 1.1)
      rubocop (>= 1.33.0, < 2.0)
    rubocop-rspec (2.19.0)
    rubocop-rspec (2.22.0)
      rubocop (~> 1.33)
      rubocop-capybara (~> 2.17)
      rubocop-factory_bot (~> 2.22)
    ruby-progressbar (1.13.0)
    ruby-saml (1.13.0)
      nokogiri (>= 1.10.5)

M spec/controllers/activitypub/followers_synchronizations_controller_spec.rb => spec/controllers/activitypub/followers_synchronizations_controller_spec.rb +14 -10
@@ 3,17 3,17 @@
require 'rails_helper'

RSpec.describe ActivityPub::FollowersSynchronizationsController do
  let!(:account)    { Fabricate(:account) }
  let!(:follower_1) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
  let!(:follower_2) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
  let!(:follower_3) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
  let!(:follower_4) { Fabricate(:account, username: 'instance-actor', domain: 'example.com', uri: 'https://example.com') }
  let!(:account) { Fabricate(:account) }
  let!(:follower_example_com_user_a) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
  let!(:follower_example_com_user_b) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
  let!(:follower_foo_com_user_a) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
  let!(:follower_example_com_instance_actor) { Fabricate(:account, username: 'instance-actor', domain: 'example.com', uri: 'https://example.com') }

  before do
    follower_1.follow!(account)
    follower_2.follow!(account)
    follower_3.follow!(account)
    follower_4.follow!(account)
    follower_example_com_user_a.follow!(account)
    follower_example_com_user_b.follow!(account)
    follower_foo_com_user_a.follow!(account)
    follower_example_com_instance_actor.follow!(account)

    allow(controller).to receive(:signed_request_actor).and_return(remote_account)
  end


@@ 47,7 47,11 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController do

      it 'returns orderedItems with followers from example.com' do
        expect(body[:orderedItems]).to be_an Array
        expect(body[:orderedItems]).to contain_exactly(follower_4.uri, follower_1.uri, follower_2.uri)
        expect(body[:orderedItems]).to contain_exactly(
          follower_example_com_instance_actor.uri,
          follower_example_com_user_a.uri,
          follower_example_com_user_b.uri
        )
      end

      it 'returns private Cache-Control header' do

M spec/controllers/admin/instances_controller_spec.rb => spec/controllers/admin/instances_controller_spec.rb +3 -3
@@ 7,9 7,9 @@ RSpec.describe Admin::InstancesController do

  let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }

  let!(:account)     { Fabricate(:account, domain: 'popular') }
  let!(:account2)    { Fabricate(:account, domain: 'popular') }
  let!(:account3)    { Fabricate(:account, domain: 'less.popular') }
  let!(:account_popular_main) { Fabricate(:account, domain: 'popular') }
  let!(:account_popular_other) { Fabricate(:account, domain: 'popular') }
  let!(:account_less_popular) { Fabricate(:account, domain: 'less.popular') }

  before do
    sign_in current_user, scope: :user

M spec/controllers/follower_accounts_controller_spec.rb => spec/controllers/follower_accounts_controller_spec.rb +5 -5
@@ 5,13 5,13 @@ require 'rails_helper'
describe FollowerAccountsController do
  render_views

  let(:alice) { Fabricate(:account) }
  let(:follower0) { Fabricate(:account) }
  let(:follower1) { Fabricate(:account) }
  let(:alice) { Fabricate(:account, username: 'alice') }
  let(:follower_bob) { Fabricate(:account, username: 'bob') }
  let(:follower_chris) { Fabricate(:account, username: 'curt') }

  describe 'GET #index' do
    let!(:follow0) { follower0.follow!(alice) }
    let!(:follow1) { follower1.follow!(alice) }
    let!(:follow_from_bob) { follower_bob.follow!(alice) }
    let!(:follow_from_chris) { follower_chris.follow!(alice) }

    context 'when format is html' do
      subject(:response) { get :index, params: { account_username: alice.username, format: :html } }

M spec/controllers/following_accounts_controller_spec.rb => spec/controllers/following_accounts_controller_spec.rb +5 -5
@@ 5,13 5,13 @@ require 'rails_helper'
describe FollowingAccountsController do
  render_views

  let(:alice) { Fabricate(:account) }
  let(:followee0) { Fabricate(:account) }
  let(:followee1) { Fabricate(:account) }
  let(:alice) { Fabricate(:account, username: 'alice') }
  let(:followee_bob) { Fabricate(:account, username: 'bob') }
  let(:followee_chris) { Fabricate(:account, username: 'chris') }

  describe 'GET #index' do
    let!(:follow0) { alice.follow!(followee0) }
    let!(:follow1) { alice.follow!(followee1) }
    let!(:follow_of_bob) { alice.follow!(followee_bob) }
    let!(:follow_of_chris) { alice.follow!(followee_chris) }

    context 'when format is html' do
      subject(:response) { get :index, params: { account_username: alice.username, format: :html } }

M spec/lib/account_reach_finder_spec.rb => spec/lib/account_reach_finder_spec.rb +15 -15
@@ 5,31 5,31 @@ require 'rails_helper'
RSpec.describe AccountReachFinder do
  let(:account) { Fabricate(:account) }

  let(:follower1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1') }
  let(:follower2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-2') }
  let(:follower3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', shared_inbox_url: 'https://foo.bar/inbox') }
  let(:ap_follower_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1') }
  let(:ap_follower_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-2') }
  let(:ap_follower_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', shared_inbox_url: 'https://foo.bar/inbox') }

  let(:mentioned1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', shared_inbox_url: 'https://foo.bar/inbox') }
  let(:mentioned2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3') }
  let(:mentioned3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-4') }
  let(:ap_mentioned_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', shared_inbox_url: 'https://foo.bar/inbox') }
  let(:ap_mentioned_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3') }
  let(:ap_mentioned_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-4') }

  let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox') }

  before do
    follower1.follow!(account)
    follower2.follow!(account)
    follower3.follow!(account)
    ap_follower_example_com.follow!(account)
    ap_follower_example_org.follow!(account)
    ap_follower_with_shared.follow!(account)

    Fabricate(:status, account: account).tap do |status|
      status.mentions << Mention.new(account: follower1)
      status.mentions << Mention.new(account: mentioned1)
      status.mentions << Mention.new(account: ap_follower_example_com)
      status.mentions << Mention.new(account: ap_mentioned_with_shared)
    end

    Fabricate(:status, account: account)

    Fabricate(:status, account: account).tap do |status|
      status.mentions << Mention.new(account: mentioned2)
      status.mentions << Mention.new(account: mentioned3)
      status.mentions << Mention.new(account: ap_mentioned_example_com)
      status.mentions << Mention.new(account: ap_mentioned_example_org)
    end

    Fabricate(:status).tap do |status|


@@ 39,11 39,11 @@ RSpec.describe AccountReachFinder do

  describe '#inboxes' do
    it 'includes the preferred inbox URL of followers' do
      expect(described_class.new(account).inboxes).to include(*[follower1, follower2, follower3].map(&:preferred_inbox_url))
      expect(described_class.new(account).inboxes).to include(*[ap_follower_example_com, ap_follower_example_org, ap_follower_with_shared].map(&:preferred_inbox_url))
    end

    it 'includes the preferred inbox URL of recently-mentioned accounts' do
      expect(described_class.new(account).inboxes).to include(*[mentioned1, mentioned2, mentioned3].map(&:preferred_inbox_url))
      expect(described_class.new(account).inboxes).to include(*[ap_mentioned_with_shared, ap_mentioned_example_com, ap_mentioned_example_org].map(&:preferred_inbox_url))
    end

    it 'does not include the inbox of unrelated users' do

M spec/lib/feed_manager_spec.rb => spec/lib/feed_manager_spec.rb +17 -11
@@ 526,19 526,25 @@ RSpec.describe FeedManager do
  end

  describe '#clear_from_home' do
    let(:account)          { Fabricate(:account) }
    let(:account) { Fabricate(:account) }
    let(:followed_account) { Fabricate(:account) }
    let(:target_account)   { Fabricate(:account) }
    let(:status_1)         { Fabricate(:status, account: followed_account) }
    let(:status_2)         { Fabricate(:status, account: target_account) }
    let(:status_3)         { Fabricate(:status, account: followed_account, mentions: [Fabricate(:mention, account: target_account)]) }
    let(:status_4)         { Fabricate(:status, mentions: [Fabricate(:mention, account: target_account)]) }
    let(:status_5)         { Fabricate(:status, account: followed_account, reblog: status_4) }
    let(:status_6)         { Fabricate(:status, account: followed_account, reblog: status_2) }
    let(:status_7)         { Fabricate(:status, account: followed_account) }
    let(:target_account) { Fabricate(:account) }
    let(:status_from_followed_account_first) { Fabricate(:status, account: followed_account) }
    let(:status_from_target_account) { Fabricate(:status, account: target_account) }
    let(:status_from_followed_account_mentions_target_account) { Fabricate(:status, account: followed_account, mentions: [Fabricate(:mention, account: target_account)]) }
    let(:status_mentions_target_account) { Fabricate(:status, mentions: [Fabricate(:mention, account: target_account)]) }
    let(:status_from_followed_account_reblogs_status_mentions_target_account) { Fabricate(:status, account: followed_account, reblog: status_mentions_target_account) }
    let(:status_from_followed_account_reblogs_status_from_target_account) { Fabricate(:status, account: followed_account, reblog: status_from_target_account) }
    let(:status_from_followed_account_next) { Fabricate(:status, account: followed_account) }

    before do
      [status_1, status_3, status_5, status_6, status_7].each do |status|
      [
        status_from_followed_account_first,
        status_from_followed_account_mentions_target_account,
        status_from_followed_account_reblogs_status_mentions_target_account,
        status_from_followed_account_reblogs_status_from_target_account,
        status_from_followed_account_next,
      ].each do |status|
        redis.zadd("feed:home:#{account.id}", status.id, status.id)
      end
    end


@@ 546,7 552,7 @@ RSpec.describe FeedManager do
    it 'correctly cleans the home timeline' do
      described_class.instance.clear_from_home(account, target_account)

      expect(redis.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_1.id.to_s, status_7.id.to_s]
      expect(redis.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_from_followed_account_first.id.to_s, status_from_followed_account_next.id.to_s]
    end
  end
end

M spec/lib/mastodon/cli/ip_blocks_spec.rb => spec/lib/mastodon/cli/ip_blocks_spec.rb +13 -13
@@ 189,22 189,22 @@ describe Mastodon::CLI::IpBlocks do
    end

    context 'with --force option' do
      let!(:block1) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
      let!(:block2) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
      let!(:block3) { IpBlock.create(ip: '172.16.0.0/20', severity: :no_access) }
      let!(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
      let!(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
      let!(:third_ip_range_block) { IpBlock.create(ip: '172.16.0.0/20', severity: :no_access) }
      let(:arguments) { ['192.168.0.5', '10.0.1.50'] }
      let(:options) { { force: true } }

      it 'removes blocks for IP ranges that cover given IP(s)' do
        cli.invoke(:remove, arguments, options)

        expect(IpBlock.where(id: [block1.id, block2.id])).to_not exist
        expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist
      end

      it 'does not remove other IP ranges' do
        cli.invoke(:remove, arguments, options)

        expect(IpBlock.where(id: block3.id)).to exist
        expect(IpBlock.where(id: third_ip_range_block.id)).to exist
      end
    end



@@ 251,22 251,22 @@ describe Mastodon::CLI::IpBlocks do
  end

  describe '#export' do
    let(:block1) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
    let(:block2) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
    let(:block3) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }
    let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
    let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
    let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }

    context 'when --format option is set to "plain"' do
      let(:options) { { format: 'plain' } }

      it 'exports blocked IPs with "no_access" severity in plain format' do
        expect { cli.invoke(:export, nil, options) }.to output(
          a_string_including("#{block1.ip}/#{block1.ip.prefix}\n#{block2.ip}/#{block2.ip.prefix}")
          a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
        ).to_stdout
      end

      it 'does not export bloked IPs with different severities' do
        expect { cli.invoke(:export, nil, options) }.to_not output(
          a_string_including("#{block3.ip}/#{block1.ip.prefix}")
          a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
        ).to_stdout
      end
    end


@@ 276,13 276,13 @@ describe Mastodon::CLI::IpBlocks do

      it 'exports blocked IPs with "no_access" severity in plain format' do
        expect { cli.invoke(:export, nil, options) }.to output(
          a_string_including("deny #{block1.ip}/#{block1.ip.prefix};\ndeny #{block2.ip}/#{block2.ip.prefix};")
          a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
        ).to_stdout
      end

      it 'does not export bloked IPs with different severities' do
        expect { cli.invoke(:export, nil, options) }.to_not output(
          a_string_including("deny #{block3.ip}/#{block1.ip.prefix};")
          a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
        ).to_stdout
      end
    end


@@ 290,7 290,7 @@ describe Mastodon::CLI::IpBlocks do
    context 'when --format option is not provided' do
      it 'exports blocked IPs in plain format by default' do
        expect { cli.export }.to output(
          a_string_including("#{block1.ip}/#{block1.ip.prefix}\n#{block2.ip}/#{block2.ip.prefix}")
          a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
        ).to_stdout
      end
    end

M spec/models/account_statuses_cleanup_policy_spec.rb => spec/models/account_statuses_cleanup_policy_spec.rb +20 -20
@@ 266,10 266,10 @@ RSpec.describe AccountStatusesCleanupPolicy do
    let!(:self_bookmarked)   { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:status_with_poll)  { Fabricate(:status, created_at: 1.year.ago, account: account, poll_attributes: { account: account, voters_count: 0, options: %w(a b), expires_in: 2.days }) }
    let!(:status_with_media) { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:faved4)            { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:faved5)            { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:reblogged4)        { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:reblogged5)        { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:faved_primary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:faved_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:reblogged_primary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:reblogged_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) }
    let!(:recent_status)     { Fabricate(:status, created_at: 2.days.ago, account: account) }

    let!(:media_attachment)  { Fabricate(:media_attachment, account: account, status: status_with_media) }


@@ 280,10 280,10 @@ RSpec.describe AccountStatusesCleanupPolicy do
    let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }

    before do
      4.times { faved4.increment_count!(:favourites_count) }
      5.times { faved5.increment_count!(:favourites_count) }
      4.times { reblogged4.increment_count!(:reblogs_count) }
      5.times { reblogged5.increment_count!(:reblogs_count) }
      4.times { faved_primary.increment_count!(:favourites_count) }
      5.times { faved_secondary.increment_count!(:favourites_count) }
      4.times { reblogged_primary.increment_count!(:reblogs_count) }
      5.times { reblogged_secondary.increment_count!(:reblogs_count) }
    end

    context 'when passed a max_id' do


@@ 359,7 359,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 378,7 378,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 397,7 397,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 416,7 416,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 435,7 435,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 454,7 454,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 477,7 477,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns every other old status for deletion' do
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 496,7 496,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns only normal statuses for deletion' do
        expect(subject.pluck(:id)).to contain_exactly(very_old_status.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to contain_exactly(very_old_status.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end



@@ 510,7 510,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'does not return the toot reblogged 5 times' do
        expect(subject.pluck(:id)).to_not include(reblogged5.id)
        expect(subject.pluck(:id)).to_not include(reblogged_secondary.id)
      end

      it 'does not return the unrelated toot' do


@@ 518,7 518,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns old statuses not reblogged as much' do
        expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, faved5.id, reblogged4.id)
        expect(subject.pluck(:id)).to include(very_old_status.id, faved_primary.id, faved_secondary.id, reblogged_primary.id)
      end
    end



@@ 532,7 532,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'does not return the toot faved 5 times' do
        expect(subject.pluck(:id)).to_not include(faved5.id)
        expect(subject.pluck(:id)).to_not include(faved_secondary.id)
      end

      it 'does not return the unrelated toot' do


@@ 540,7 540,7 @@ RSpec.describe AccountStatusesCleanupPolicy do
      end

      it 'returns old statuses not faved as much' do
        expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, reblogged4.id, reblogged5.id)
        expect(subject.pluck(:id)).to include(very_old_status.id, faved_primary.id, reblogged_primary.id, reblogged_secondary.id)
      end
    end
  end

M spec/models/concerns/account_interactions_spec.rb => spec/models/concerns/account_interactions_spec.rb +20 -20
@@ 560,17 560,17 @@ describe AccountInteractions do

  describe '#remote_followers_hash' do
    let(:me) { Fabricate(:account, username: 'Me') }
    let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
    let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
    let(:remote_3) { Fabricate(:account, username: 'instance-actor', domain: 'example.org', uri: 'https://example.org') }
    let(:remote_4) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
    let(:remote_alice) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
    let(:remote_bob) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
    let(:remote_instance_actor) { Fabricate(:account, username: 'instance-actor', domain: 'example.org', uri: 'https://example.org') }
    let(:remote_eve) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }

    before do
      remote_1.follow!(me)
      remote_2.follow!(me)
      remote_3.follow!(me)
      remote_4.follow!(me)
      me.follow!(remote_1)
      remote_alice.follow!(me)
      remote_bob.follow!(me)
      remote_instance_actor.follow!(me)
      remote_eve.follow!(me)
      me.follow!(remote_alice)
    end

    it 'returns correct hash for remote domains' do


@@ 582,33 582,33 @@ describe AccountInteractions do

    it 'invalidates cache as needed when removing or adding followers' do
      expect(me.remote_followers_hash('https://example.org/')).to eq '20aecbe774b3d61c25094370baf370012b9271c5b172ecedb05caff8d79ef0c7'
      remote_3.unfollow!(me)
      remote_instance_actor.unfollow!(me)
      expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
      remote_1.unfollow!(me)
      remote_alice.unfollow!(me)
      expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
      remote_1.follow!(me)
      remote_alice.follow!(me)
      expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
    end
  end

  describe '#local_followers_hash' do
    let(:me) { Fabricate(:account, username: 'Me') }
    let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
    let(:remote_alice) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }

    before do
      me.follow!(remote_1)
      me.follow!(remote_alice)
    end

    it 'returns correct hash for local users' do
      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
      expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
    end

    it 'invalidates cache as needed when removing or adding followers' do
      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
      me.unfollow!(remote_1)
      expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
      me.follow!(remote_1)
      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
      expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
      me.unfollow!(remote_alice)
      expect(remote_alice.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
      me.follow!(remote_alice)
      expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
    end
  end


M spec/models/concerns/status_threading_concern_spec.rb => spec/models/concerns/status_threading_concern_spec.rb +21 -21
@@ 8,40 8,40 @@ describe StatusThreadingConcern do
    let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com') }
    let!(:jeff)   { Fabricate(:account, username: 'jeff') }
    let!(:status) { Fabricate(:status, account: alice) }
    let!(:reply1) { Fabricate(:status, thread: status, account: jeff) }
    let!(:reply2) { Fabricate(:status, thread: reply1, account: bob) }
    let!(:reply3) { Fabricate(:status, thread: reply2, account: alice) }
    let!(:reply_to_status) { Fabricate(:status, thread: status, account: jeff) }
    let!(:reply_to_first_reply) { Fabricate(:status, thread: reply_to_status, account: bob) }
    let!(:reply_to_second_reply) { Fabricate(:status, thread: reply_to_first_reply, account: alice) }
    let!(:viewer) { Fabricate(:account, username: 'viewer') }

    it 'returns conversation history' do
      expect(reply3.ancestors(4)).to include(status, reply1, reply2)
      expect(reply_to_second_reply.ancestors(4)).to include(status, reply_to_status, reply_to_first_reply)
    end

    it 'does not return conversation history user is not allowed to see' do
      reply1.update(visibility: :private)
      reply_to_status.update(visibility: :private)
      status.update(visibility: :direct)

      expect(reply3.ancestors(4, viewer)).to_not include(reply1, status)
      expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status, status)
    end

    it 'does not return conversation history from blocked users' do
      viewer.block!(jeff)
      expect(reply3.ancestors(4, viewer)).to_not include(reply1)
      expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status)
    end

    it 'does not return conversation history from muted users' do
      viewer.mute!(jeff)
      expect(reply3.ancestors(4, viewer)).to_not include(reply1)
      expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status)
    end

    it 'does not return conversation history from silenced and not followed users' do
      jeff.silence!
      expect(reply3.ancestors(4, viewer)).to_not include(reply1)
      expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status)
    end

    it 'does not return conversation history from blocked domains' do
      viewer.block_domain!('example.com')
      expect(reply3.ancestors(4, viewer)).to_not include(reply2)
      expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_first_reply)
    end

    it 'ignores deleted records' do


@@ 83,40 83,40 @@ describe StatusThreadingConcern do
    let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com') }
    let!(:jeff)   { Fabricate(:account, username: 'jeff') }
    let!(:status) { Fabricate(:status, account: alice) }
    let!(:reply1) { Fabricate(:status, thread: status, account: alice) }
    let!(:reply2) { Fabricate(:status, thread: status, account: bob) }
    let!(:reply3) { Fabricate(:status, thread: reply1, account: jeff) }
    let!(:reply_to_status_from_alice) { Fabricate(:status, thread: status, account: alice) }
    let!(:reply_to_status_from_bob) { Fabricate(:status, thread: status, account: bob) }
    let!(:reply_to_alice_reply_from_jeff) { Fabricate(:status, thread: reply_to_status_from_alice, account: jeff) }
    let!(:viewer) { Fabricate(:account, username: 'viewer') }

    it 'returns replies' do
      expect(status.descendants(4)).to include(reply1, reply2, reply3)
      expect(status.descendants(4)).to include(reply_to_status_from_alice, reply_to_status_from_bob, reply_to_alice_reply_from_jeff)
    end

    it 'does not return replies user is not allowed to see' do
      reply1.update(visibility: :private)
      reply3.update(visibility: :direct)
      reply_to_status_from_alice.update(visibility: :private)
      reply_to_alice_reply_from_jeff.update(visibility: :direct)

      expect(status.descendants(4, viewer)).to_not include(reply1, reply3)
      expect(status.descendants(4, viewer)).to_not include(reply_to_status_from_alice, reply_to_alice_reply_from_jeff)
    end

    it 'does not return replies from blocked users' do
      viewer.block!(jeff)
      expect(status.descendants(4, viewer)).to_not include(reply3)
      expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff)
    end

    it 'does not return replies from muted users' do
      viewer.mute!(jeff)
      expect(status.descendants(4, viewer)).to_not include(reply3)
      expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff)
    end

    it 'does not return replies from silenced and not followed users' do
      jeff.silence!
      expect(status.descendants(4, viewer)).to_not include(reply3)
      expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff)
    end

    it 'does not return replies from blocked domains' do
      viewer.block_domain!('example.com')
      expect(status.descendants(4, viewer)).to_not include(reply2)
      expect(status.descendants(4, viewer)).to_not include(reply_to_status_from_bob)
    end

    it 'promotes self-replies to the top while leaving the rest in order' do

M spec/models/custom_emoji_filter_spec.rb => spec/models/custom_emoji_filter_spec.rb +8 -8
@@ 6,9 6,9 @@ RSpec.describe CustomEmojiFilter do
  describe '#results' do
    subject { described_class.new(params).results }

    let!(:custom_emoji_0) { Fabricate(:custom_emoji, domain: 'a') }
    let!(:custom_emoji_1) { Fabricate(:custom_emoji, domain: 'b') }
    let!(:custom_emoji_2) { Fabricate(:custom_emoji, domain: nil, shortcode: 'hoge') }
    let!(:custom_emoji_domain_a) { Fabricate(:custom_emoji, domain: 'a') }
    let!(:custom_emoji_domain_b) { Fabricate(:custom_emoji, domain: 'b') }
    let!(:custom_emoji_domain_nil) { Fabricate(:custom_emoji, domain: nil, shortcode: 'hoge') }

    context 'when params have values' do
      context 'when local' do


@@ 16,7 16,7 @@ RSpec.describe CustomEmojiFilter do

        it 'returns ActiveRecord::Relation' do
          expect(subject).to be_a(ActiveRecord::Relation)
          expect(subject).to contain_exactly(custom_emoji_2)
          expect(subject).to contain_exactly(custom_emoji_domain_nil)
        end
      end



@@ 25,7 25,7 @@ RSpec.describe CustomEmojiFilter do

        it 'returns ActiveRecord::Relation' do
          expect(subject).to be_a(ActiveRecord::Relation)
          expect(subject).to contain_exactly(custom_emoji_0, custom_emoji_1)
          expect(subject).to contain_exactly(custom_emoji_domain_a, custom_emoji_domain_b)
        end
      end



@@ 34,7 34,7 @@ RSpec.describe CustomEmojiFilter do

        it 'returns ActiveRecord::Relation' do
          expect(subject).to be_a(ActiveRecord::Relation)
          expect(subject).to contain_exactly(custom_emoji_0)
          expect(subject).to contain_exactly(custom_emoji_domain_a)
        end
      end



@@ 43,7 43,7 @@ RSpec.describe CustomEmojiFilter do

        it 'returns ActiveRecord::Relation' do
          expect(subject).to be_a(ActiveRecord::Relation)
          expect(subject).to contain_exactly(custom_emoji_2)
          expect(subject).to contain_exactly(custom_emoji_domain_nil)
        end
      end



@@ 63,7 63,7 @@ RSpec.describe CustomEmojiFilter do

      it 'returns ActiveRecord::Relation' do
        expect(subject).to be_a(ActiveRecord::Relation)
        expect(subject).to contain_exactly(custom_emoji_0, custom_emoji_1, custom_emoji_2)
        expect(subject).to contain_exactly(custom_emoji_domain_a, custom_emoji_domain_b, custom_emoji_domain_nil)
      end
    end
  end

M spec/models/status_spec.rb => spec/models/status_spec.rb +42 -42
@@ 254,82 254,82 @@ RSpec.describe Status do
  end

  describe '.tagged_with' do
    let(:tag1)     { Fabricate(:tag) }
    let(:tag2)     { Fabricate(:tag) }
    let(:tag3)     { Fabricate(:tag) }
    let!(:status1) { Fabricate(:status, tags: [tag1]) }
    let!(:status2) { Fabricate(:status, tags: [tag2]) }
    let!(:status3) { Fabricate(:status, tags: [tag3]) }
    let!(:status4) { Fabricate(:status, tags: []) }
    let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) }
    let(:tag_cats) { Fabricate(:tag, name: 'cats') }
    let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
    let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
    let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
    let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
    let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
    let!(:status_without_tags) { Fabricate(:status, tags: []) }
    let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) }

    context 'when given one tag' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with([tag1.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status5.id)
        expect(described_class.tagged_with([tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status2.id, status5.id)
        expect(described_class.tagged_with([tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status3.id, status5.id)
        expect(described_class.tagged_with([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id)
        expect(described_class.tagged_with([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id)
        expect(described_class.tagged_with([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_with_all_tags.id)
      end
    end

    context 'when given multiple tags' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status2.id, status5.id)
        expect(described_class.tagged_with([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status3.id, status5.id)
        expect(described_class.tagged_with([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status2.id, status3.id, status5.id)
        expect(described_class.tagged_with([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_with_all_tags.id)
        expect(described_class.tagged_with([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_with_all_tags.id)
        expect(described_class.tagged_with([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_with_all_tags.id)
      end
    end
  end

  describe '.tagged_with_all' do
    let(:tag1)     { Fabricate(:tag) }
    let(:tag2)     { Fabricate(:tag) }
    let(:tag3)     { Fabricate(:tag) }
    let!(:status1) { Fabricate(:status, tags: [tag1]) }
    let!(:status2) { Fabricate(:status, tags: [tag2]) }
    let!(:status3) { Fabricate(:status, tags: [tag3]) }
    let!(:status4) { Fabricate(:status, tags: []) }
    let!(:status5) { Fabricate(:status, tags: [tag1, tag2]) }
    let(:tag_cats) { Fabricate(:tag, name: 'cats') }
    let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
    let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
    let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
    let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
    let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
    let!(:status_without_tags) { Fabricate(:status, tags: []) }
    let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs]) }

    context 'when given one tag' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with_all([tag1.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status5.id)
        expect(described_class.tagged_with_all([tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status2.id, status5.id)
        expect(described_class.tagged_with_all([tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status3.id)
        expect(described_class.tagged_with_all([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id)
        expect(described_class.tagged_with_all([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id)
        expect(described_class.tagged_with_all([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id)
      end
    end

    context 'when given multiple tags' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with_all([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status5.id)
        expect(described_class.tagged_with_all([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq []
        expect(described_class.tagged_with_all([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq []
        expect(described_class.tagged_with_all([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_all_tags.id)
        expect(described_class.tagged_with_all([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq []
        expect(described_class.tagged_with_all([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq []
      end
    end
  end

  describe '.tagged_with_none' do
    let(:tag1)     { Fabricate(:tag) }
    let(:tag2)     { Fabricate(:tag) }
    let(:tag3)     { Fabricate(:tag) }
    let!(:status1) { Fabricate(:status, tags: [tag1]) }
    let!(:status2) { Fabricate(:status, tags: [tag2]) }
    let!(:status3) { Fabricate(:status, tags: [tag3]) }
    let!(:status4) { Fabricate(:status, tags: []) }
    let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) }
    let(:tag_cats) { Fabricate(:tag, name: 'cats') }
    let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
    let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
    let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
    let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
    let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
    let!(:status_without_tags) { Fabricate(:status, tags: []) }
    let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) }

    context 'when given one tag' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with_none([tag1.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status2.id, status3.id, status4.id)
        expect(described_class.tagged_with_none([tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status3.id, status4.id)
        expect(described_class.tagged_with_none([tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status2.id, status4.id)
        expect(described_class.tagged_with_none([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_without_tags.id)
        expect(described_class.tagged_with_none([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_without_tags.id)
        expect(described_class.tagged_with_none([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_without_tags.id)
      end
    end

    context 'when given multiple tags' do
      it 'returns the expected statuses' do
        expect(described_class.tagged_with_none([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status3.id, status4.id)
        expect(described_class.tagged_with_none([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status2.id, status4.id)
        expect(described_class.tagged_with_none([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status1.id, status4.id)
        expect(described_class.tagged_with_none([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_without_tags.id)
        expect(described_class.tagged_with_none([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_without_tags.id)
        expect(described_class.tagged_with_none([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_without_tags.id)
      end
    end
  end

M spec/models/tag_feed_spec.rb => spec/models/tag_feed_spec.rb +29 -29
@@ 5,65 5,65 @@ require 'rails_helper'
describe TagFeed, type: :service do
  describe '#get' do
    let(:account) { Fabricate(:account) }
    let(:tag1) { Fabricate(:tag) }
    let(:tag2) { Fabricate(:tag) }
    let!(:status1) { Fabricate(:status, tags: [tag1]) }
    let!(:status2) { Fabricate(:status, tags: [tag2]) }
    let!(:both) { Fabricate(:status, tags: [tag1, tag2]) }
    let(:tag_cats) { Fabricate(:tag, name: 'cats') }
    let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
    let!(:status_tagged_with_cats) { Fabricate(:status, tags: [tag_cats]) }
    let!(:status_tagged_with_dogs) { Fabricate(:status, tags: [tag_dogs]) }
    let!(:both) { Fabricate(:status, tags: [tag_cats, tag_dogs]) }

    it 'can add tags in "any" mode' do
      results = described_class.new(tag1, nil, any: [tag2.name]).get(20)
      expect(results).to include status1
      expect(results).to include status2
      results = described_class.new(tag_cats, nil, any: [tag_dogs.name]).get(20)
      expect(results).to include status_tagged_with_cats
      expect(results).to include status_tagged_with_dogs
      expect(results).to include both
    end

    it 'can remove tags in "all" mode' do
      results = described_class.new(tag1, nil, all: [tag2.name]).get(20)
      expect(results).to_not include status1
      expect(results).to_not include status2
      results = described_class.new(tag_cats, nil, all: [tag_dogs.name]).get(20)
      expect(results).to_not include status_tagged_with_cats
      expect(results).to_not include status_tagged_with_dogs
      expect(results).to     include both
    end

    it 'can remove tags in "none" mode' do
      results = described_class.new(tag1, nil, none: [tag2.name]).get(20)
      expect(results).to     include status1
      expect(results).to_not include status2
      results = described_class.new(tag_cats, nil, none: [tag_dogs.name]).get(20)
      expect(results).to     include status_tagged_with_cats
      expect(results).to_not include status_tagged_with_dogs
      expect(results).to_not include both
    end

    it 'ignores an invalid mode' do
      results = described_class.new(tag1, nil, wark: [tag2.name]).get(20)
      expect(results).to     include status1
      expect(results).to_not include status2
      results = described_class.new(tag_cats, nil, wark: [tag_dogs.name]).get(20)
      expect(results).to     include status_tagged_with_cats
      expect(results).to_not include status_tagged_with_dogs
      expect(results).to     include both
    end

    it 'handles being passed non existent tag names' do
      results = described_class.new(tag1, nil, any: ['wark']).get(20)
      expect(results).to     include status1
      expect(results).to_not include status2
      results = described_class.new(tag_cats, nil, any: ['wark']).get(20)
      expect(results).to     include status_tagged_with_cats
      expect(results).to_not include status_tagged_with_dogs
      expect(results).to     include both
    end

    it 'can restrict to an account' do
      BlockService.new.call(account, status1.account)
      results = described_class.new(tag1, account, none: [tag2.name]).get(20)
      expect(results).to_not include status1
      BlockService.new.call(account, status_tagged_with_cats.account)
      results = described_class.new(tag_cats, account, none: [tag_dogs.name]).get(20)
      expect(results).to_not include status_tagged_with_cats
    end

    it 'can restrict to local' do
      status1.account.update(domain: 'example.com')
      status1.update(local: false, uri: 'example.com/toot')
      results = described_class.new(tag1, nil, any: [tag2.name], local: true).get(20)
      expect(results).to_not include status1
      status_tagged_with_cats.account.update(domain: 'example.com')
      status_tagged_with_cats.update(local: false, uri: 'example.com/toot')
      results = described_class.new(tag_cats, nil, any: [tag_dogs.name], local: true).get(20)
      expect(results).to_not include status_tagged_with_cats
    end

    it 'allows replies to be included' do
      original = Fabricate(:status)
      status = Fabricate(:status, tags: [tag1], in_reply_to_id: original.id)
      status = Fabricate(:status, tags: [tag_cats], in_reply_to_id: original.id)

      results = described_class.new(tag1, nil).get(20)
      results = described_class.new(tag_cats, nil).get(20)
      expect(results).to include(status)
    end
  end

M spec/models/trends/statuses_spec.rb => spec/models/trends/statuses_spec.rb +20 -20
@@ 11,12 11,12 @@ RSpec.describe Trends::Statuses do
    let!(:query) { subject.query }
    let!(:today) { at_time }

    let!(:status1) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: today) }
    let!(:status2) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) }
    let!(:status_foo) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: today) }
    let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) }

    before do
      default_threshold_value.times { reblog(status1, today) }
      default_threshold_value.times { reblog(status2, today) }
      default_threshold_value.times { reblog(status_foo, today) }
      default_threshold_value.times { reblog(status_bar, today) }

      subject.refresh(today)
    end


@@ 29,18 29,18 @@ RSpec.describe Trends::Statuses do
      end

      it 'filters out blocked accounts' do
        account.block!(status1.account)
        expect(query.filtered_for(account).to_a).to eq [status2]
        account.block!(status_foo.account)
        expect(query.filtered_for(account).to_a).to eq [status_bar]
      end

      it 'filters out muted accounts' do
        account.mute!(status2.account)
        expect(query.filtered_for(account).to_a).to eq [status1]
        account.mute!(status_bar.account)
        expect(query.filtered_for(account).to_a).to eq [status_foo]
      end

      it 'filters out blocked-by accounts' do
        status1.account.block!(account)
        expect(query.filtered_for(account).to_a).to eq [status2]
        status_foo.account.block!(account)
        expect(query.filtered_for(account).to_a).to eq [status_bar]
      end
    end
  end


@@ 71,14 71,14 @@ RSpec.describe Trends::Statuses do
    let!(:today) { at_time }
    let!(:yesterday) { today - 1.day }

    let!(:status1) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: yesterday) }
    let!(:status2) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) }
    let!(:status3) { Fabricate(:status, text: 'Baz', language: 'en', trendable: true, created_at: today) }
    let!(:status_foo) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: yesterday) }
    let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) }
    let!(:status_baz) { Fabricate(:status, text: 'Baz', language: 'en', trendable: true, created_at: today) }

    before do
      default_threshold_value.times { reblog(status1, today) }
      default_threshold_value.times { reblog(status2, today) }
      (default_threshold_value - 1).times { reblog(status3, today) }
      default_threshold_value.times { reblog(status_foo, today) }
      default_threshold_value.times { reblog(status_bar, today) }
      (default_threshold_value - 1).times { reblog(status_baz, today) }
    end

    context 'when status trends are refreshed' do


@@ 89,17 89,17 @@ RSpec.describe Trends::Statuses do
      it 'returns correct statuses from query' do
        results = subject.query.limit(10).to_a

        expect(results).to eq [status2, status1]
        expect(results).to_not include(status3)
        expect(results).to eq [status_bar, status_foo]
        expect(results).to_not include(status_baz)
      end
    end

    it 'decays scores' do
      subject.refresh(today)
      original_score = status2.trend.score
      original_score = status_bar.trend.score
      expect(original_score).to be_a Float
      subject.refresh(today + subject.options[:score_halflife])
      decayed_score = status2.trend.reload.score
      decayed_score = status_bar.trend.reload.score
      expect(decayed_score).to be <= original_score / 2
    end
  end

M spec/models/trends/tags_spec.rb => spec/models/trends/tags_spec.rb +11 -11
@@ 33,15 33,15 @@ RSpec.describe Trends::Tags do
    let!(:today) { at_time }
    let!(:yesterday) { today - 1.day }

    let!(:tag1) { Fabricate(:tag, name: 'Catstodon', trendable: true) }
    let!(:tag2) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) }
    let!(:tag3) { Fabricate(:tag, name: 'OCs', trendable: true) }
    let!(:tag_cats) { Fabricate(:tag, name: 'Catstodon', trendable: true) }
    let!(:tag_dogs) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) }
    let!(:tag_ocs) { Fabricate(:tag, name: 'OCs', trendable: true) }

    before do
      2.times  { |i| subject.add(tag1, i, yesterday) }
      13.times { |i| subject.add(tag3, i, yesterday) }
      16.times { |i| subject.add(tag1, i, today) }
      4.times  { |i| subject.add(tag2, i, today) }
      2.times  { |i| subject.add(tag_cats, i, yesterday) }
      13.times { |i| subject.add(tag_ocs, i, yesterday) }
      16.times { |i| subject.add(tag_cats, i, today) }
      4.times  { |i| subject.add(tag_dogs, i, today) }
    end

    context 'when tag trends are refreshed' do


@@ 51,20 51,20 @@ RSpec.describe Trends::Tags do
      end

      it 'calculates and re-calculates scores' do
        expect(subject.query.limit(10).to_a).to eq [tag1, tag3]
        expect(subject.query.limit(10).to_a).to eq [tag_cats, tag_ocs]
      end

      it 'omits hashtags below threshold' do
        expect(subject.query.limit(10).to_a).to_not include(tag2)
        expect(subject.query.limit(10).to_a).to_not include(tag_dogs)
      end
    end

    it 'decays scores' do
      subject.refresh(yesterday + 12.hours)
      original_score = subject.score(tag3.id)
      original_score = subject.score(tag_ocs.id)
      expect(original_score).to eq 144.0
      subject.refresh(yesterday + 12.hours + subject.options[:max_score_halflife])
      decayed_score = subject.score(tag3.id)
      decayed_score = subject.score(tag_ocs.id)
      expect(decayed_score).to be <= original_score / 2
    end
  end

M spec/serializers/activitypub/note_serializer_spec.rb => spec/serializers/activitypub/note_serializer_spec.rb +10 -10
@@ 6,13 6,13 @@ describe ActivityPub::NoteSerializer do
  subject { JSON.parse(@serialization.to_json) }

  let!(:account) { Fabricate(:account) }
  let!(:other)   { Fabricate(:account) }
  let!(:parent)  { Fabricate(:status, account: account, visibility: :public) }
  let!(:reply1)  { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply2)  { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply3)  { Fabricate(:status, account: other, thread: parent, visibility: :public) }
  let!(:reply4)  { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply5)  { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
  let!(:other) { Fabricate(:account) }
  let!(:parent) { Fabricate(:status, account: account, visibility: :public) }
  let!(:reply_by_account_first) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply_by_account_next) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) }
  let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
  let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }

  before(:each) do
    @serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter)


@@ 31,14 31,14 @@ describe ActivityPub::NoteSerializer do
  end

  it 'includes public self-replies in its replies collection' do
    expect(subject['replies']['first']['items']).to include(reply1.uri, reply2.uri, reply4.uri)
    expect(subject['replies']['first']['items']).to include(reply_by_account_first.uri, reply_by_account_next.uri, reply_by_account_third.uri)
  end

  it 'does not include replies from others in its replies collection' do
    expect(subject['replies']['first']['items']).to_not include(reply3.uri)
    expect(subject['replies']['first']['items']).to_not include(reply_by_other_first.uri)
  end

  it 'does not include replies with direct visibility in its replies collection' do
    expect(subject['replies']['first']['items']).to_not include(reply5.uri)
    expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri)
  end
end

M spec/services/activitypub/fetch_featured_collection_service_spec.rb => spec/services/activitypub/fetch_featured_collection_service_spec.rb +19 -15
@@ 9,33 9,33 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do

  let!(:known_status) { Fabricate(:status, account: actor, uri: 'https://example.com/account/pinned/1') }

  let(:status_json_1) do
  let(:status_json_pinned_known) do
    {
      '@context': 'https://www.w3.org/ns/activitystreams',
      type: 'Note',
      id: 'https://example.com/account/pinned/1',
      id: 'https://example.com/account/pinned/known',
      content: 'foo',
      attributedTo: actor.uri,
      to: 'https://www.w3.org/ns/activitystreams#Public',
    }
  end

  let(:status_json_2) do
  let(:status_json_pinned_unknown_inlined) do
    {
      '@context': 'https://www.w3.org/ns/activitystreams',
      type: 'Note',
      id: 'https://example.com/account/pinned/2',
      id: 'https://example.com/account/pinned/unknown-inlined',
      content: 'foo',
      attributedTo: actor.uri,
      to: 'https://www.w3.org/ns/activitystreams#Public',
    }
  end

  let(:status_json_4) do
  let(:status_json_pinned_unknown_unreachable) do
    {
      '@context': 'https://www.w3.org/ns/activitystreams',
      type: 'Note',
      id: 'https://example.com/account/pinned/4',
      id: 'https://example.com/account/pinned/unknown-reachable',
      content: 'foo',
      attributedTo: actor.uri,
      to: 'https://www.w3.org/ns/activitystreams#Public',


@@ 44,10 44,10 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do

  let(:items) do
    [
      'https://example.com/account/pinned/1', # known
      status_json_2, # unknown inlined
      'https://example.com/account/pinned/3', # unknown unreachable
      'https://example.com/account/pinned/4', # unknown reachable
      'https://example.com/account/pinned/known', # known
      status_json_pinned_unknown_inlined, # unknown inlined
      'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable
      'https://example.com/account/pinned/unknown-reachable', # unknown reachable
    ]
  end



@@ 62,16 62,20 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do

  shared_examples 'sets pinned posts' do
    before do
      stub_request(:get, 'https://example.com/account/pinned/1').to_return(status: 200, body: Oj.dump(status_json_1))
      stub_request(:get, 'https://example.com/account/pinned/2').to_return(status: 200, body: Oj.dump(status_json_2))
      stub_request(:get, 'https://example.com/account/pinned/3').to_return(status: 404)
      stub_request(:get, 'https://example.com/account/pinned/4').to_return(status: 200, body: Oj.dump(status_json_4))
      stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known))
      stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined))
      stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404)
      stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_unreachable))

      subject.call(actor, note: true, hashtag: false)
    end

    it 'sets expected posts as pinned posts' do
      expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly('https://example.com/account/pinned/1', 'https://example.com/account/pinned/2', 'https://example.com/account/pinned/4')
      expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly(
        'https://example.com/account/pinned/known',
        'https://example.com/account/pinned/unknown-inlined',
        'https://example.com/account/pinned/unknown-reachable'
      )
    end
  end


M spec/services/batched_remove_status_service_spec.rb => spec/services/batched_remove_status_service_spec.rb +9 -9
@@ 10,8 10,8 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
  let!(:jeff)   { Fabricate(:account) }
  let!(:hank)   { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }

  let(:status1) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') }
  let(:status2) { PostStatusService.new.call(alice, text: 'Another status') }
  let(:status_alice_hello) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') }
  let(:status_alice_other) { PostStatusService.new.call(alice, text: 'Another status') }

  before do
    allow(redis).to receive_messages(publish: nil)


@@ 22,23 22,23 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
    jeff.follow!(alice)
    hank.follow!(alice)

    status1
    status2
    status_alice_hello
    status_alice_other

    subject.call([status1, status2])
    subject.call([status_alice_hello, status_alice_other])
  end

  it 'removes statuses' do
    expect { Status.find(status1.id) }.to raise_error ActiveRecord::RecordNotFound
    expect { Status.find(status2.id) }.to raise_error ActiveRecord::RecordNotFound
    expect { Status.find(status_alice_hello.id) }.to raise_error ActiveRecord::RecordNotFound
    expect { Status.find(status_alice_other.id) }.to raise_error ActiveRecord::RecordNotFound
  end

  it 'removes statuses from author\'s home feed' do
    expect(HomeFeed.new(alice).get(10)).to_not include([status1.id, status2.id])
    expect(HomeFeed.new(alice).get(10)).to_not include([status_alice_hello.id, status_alice_other.id])
  end

  it 'removes statuses from local follower\'s home feed' do
    expect(HomeFeed.new(jeff).get(10)).to_not include([status1.id, status2.id])
    expect(HomeFeed.new(jeff).get(10)).to_not include([status_alice_hello.id, status_alice_other.id])
  end

  it 'notifies streaming API of followers' do

M spec/services/block_domain_service_spec.rb => spec/services/block_domain_service_spec.rb +7 -7
@@ 6,9 6,9 @@ RSpec.describe BlockDomainService, type: :service do
  subject { described_class.new }

  let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
  let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) }
  let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_with_attachment, file: attachment_fixture('attachment.jpg')) }
  let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) }

  describe 'for a suspension' do


@@ 37,8 37,8 @@ RSpec.describe BlockDomainService, type: :service do
    end

    it 'removes the remote accounts\'s statuses and media attachments' do
      expect { bad_status1.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { bad_status2.reload }.to raise_exception ActiveRecord::RecordNotFound
      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
    end
  end


@@ 69,8 69,8 @@ RSpec.describe BlockDomainService, type: :service do
    end

    it 'leaves the domains status and attachments, but clears media' do
      expect { bad_status1.reload }.to_not raise_error
      expect { bad_status2.reload }.to_not raise_error
      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
      expect(bad_attachment.file.exists?).to be false
    end

M spec/services/clear_domain_media_service_spec.rb => spec/services/clear_domain_media_service_spec.rb +5 -5
@@ 6,9 6,9 @@ RSpec.describe ClearDomainMediaService, type: :service do
  subject { described_class.new }

  let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
  let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) }
  let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_with_attachment, file: attachment_fixture('attachment.jpg')) }

  describe 'for a silence with reject media' do
    before do


@@ 16,8 16,8 @@ RSpec.describe ClearDomainMediaService, type: :service do
    end

    it 'leaves the domains status and attachments, but clears media' do
      expect { bad_status1.reload }.to_not raise_error
      expect { bad_status2.reload }.to_not raise_error
      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
      expect(bad_attachment.file.exists?).to be false
    end

M spec/services/purge_domain_service_spec.rb => spec/services/purge_domain_service_spec.rb +5 -5
@@ 6,9 6,9 @@ RSpec.describe PurgeDomainService, type: :service do
  subject { described_class.new }

  let!(:old_account) { Fabricate(:account, domain: 'obsolete.org') }
  let!(:old_status1) { Fabricate(:status, account: old_account) }
  let!(:old_status2) { Fabricate(:status, account: old_account) }
  let!(:old_attachment) { Fabricate(:media_attachment, account: old_account, status: old_status2, file: attachment_fixture('attachment.jpg')) }
  let!(:old_status_plain) { Fabricate(:status, account: old_account) }
  let!(:old_status_with_attachment) { Fabricate(:status, account: old_account) }
  let!(:old_attachment) { Fabricate(:media_attachment, account: old_account, status: old_status_with_attachment, file: attachment_fixture('attachment.jpg')) }

  describe 'for a suspension' do
    before do


@@ 17,8 17,8 @@ RSpec.describe PurgeDomainService, type: :service do

    it 'removes the remote accounts\'s statuses and media attachments' do
      expect { old_account.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { old_status1.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { old_status2.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { old_status_plain.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { old_status_with_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
      expect { old_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
    end


M spec/services/unallow_domain_service_spec.rb => spec/services/unallow_domain_service_spec.rb +7 -7
@@ 6,9 6,9 @@ RSpec.describe UnallowDomainService, type: :service do
  subject { described_class.new }

  let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
  let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) }
  let!(:bad_status_harassment) { Fabricate(:status, account: bad_account, text: 'You suck') }
  let!(:bad_status_mean) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
  let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_mean, file: attachment_fixture('attachment.jpg')) }
  let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) }
  let!(:domain_allow) { Fabricate(:domain_allow, domain: 'evil.org') }



@@ 31,8 31,8 @@ RSpec.describe UnallowDomainService, type: :service do
      end

      it 'removes the remote accounts\'s statuses and media attachments' do
        expect { bad_status1.reload }.to raise_exception ActiveRecord::RecordNotFound
        expect { bad_status2.reload }.to raise_exception ActiveRecord::RecordNotFound
        expect { bad_status_harassment.reload }.to raise_exception ActiveRecord::RecordNotFound
        expect { bad_status_mean.reload }.to raise_exception ActiveRecord::RecordNotFound
        expect { bad_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
      end
    end


@@ 57,8 57,8 @@ RSpec.describe UnallowDomainService, type: :service do
      end

      it 'removes the remote accounts\'s statuses and media attachments' do
        expect { bad_status1.reload }.to_not raise_error
        expect { bad_status2.reload }.to_not raise_error
        expect { bad_status_harassment.reload }.to_not raise_error
        expect { bad_status_mean.reload }.to_not raise_error
        expect { bad_attachment.reload }.to_not raise_error
      end
    end

M spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb => spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb +25 -25
@@ 5,12 5,12 @@ require 'rails_helper'
describe Scheduler::AccountsStatusesCleanupScheduler do
  subject { described_class.new }

  let!(:account1)  { Fabricate(:account, domain: nil) }
  let!(:account2)  { Fabricate(:account, domain: nil) }
  let!(:account3)  { Fabricate(:account, domain: nil) }
  let!(:account4)  { Fabricate(:account, domain: nil) }
  let!(:account5)  { Fabricate(:account, domain: nil) }
  let!(:remote)    { Fabricate(:account) }
  let!(:account_alice) { Fabricate(:account, domain: nil, username: 'alice') }
  let!(:account_bob) { Fabricate(:account, domain: nil, username: 'bob') }
  let!(:account_chris) { Fabricate(:account, domain: nil, username: 'chris') }
  let!(:account_dave) { Fabricate(:account, domain: nil, username: 'dave') }
  let!(:account_erin) { Fabricate(:account, domain: nil, username: 'erin') }
  let!(:remote) { Fabricate(:account) }

  let(:queue_size)       { 0 }
  let(:queue_latency)    { 0 }


@@ 77,26 77,26 @@ describe Scheduler::AccountsStatusesCleanupScheduler do
  describe '#perform' do
    before do
      # Policies for the accounts
      Fabricate(:account_statuses_cleanup_policy, account: account1)
      Fabricate(:account_statuses_cleanup_policy, account: account3)
      Fabricate(:account_statuses_cleanup_policy, account: account4, enabled: false)
      Fabricate(:account_statuses_cleanup_policy, account: account5)
      Fabricate(:account_statuses_cleanup_policy, account: account_alice)
      Fabricate(:account_statuses_cleanup_policy, account: account_chris)
      Fabricate(:account_statuses_cleanup_policy, account: account_dave, enabled: false)
      Fabricate(:account_statuses_cleanup_policy, account: account_erin)

      # Create a bunch of old statuses
      4.times do
        Fabricate(:status, account: account1, created_at: 3.years.ago)
        Fabricate(:status, account: account2, created_at: 3.years.ago)
        Fabricate(:status, account: account3, created_at: 3.years.ago)
        Fabricate(:status, account: account4, created_at: 3.years.ago)
        Fabricate(:status, account: account5, created_at: 3.years.ago)
        Fabricate(:status, account: account_alice, created_at: 3.years.ago)
        Fabricate(:status, account: account_bob, created_at: 3.years.ago)
        Fabricate(:status, account: account_chris, created_at: 3.years.ago)
        Fabricate(:status, account: account_dave, created_at: 3.years.ago)
        Fabricate(:status, account: account_erin, created_at: 3.years.ago)
        Fabricate(:status, account: remote, created_at: 3.years.ago)
      end

      # Create a bunch of newer statuses
      Fabricate(:status, account: account1, created_at: 3.minutes.ago)
      Fabricate(:status, account: account2, created_at: 3.minutes.ago)
      Fabricate(:status, account: account3, created_at: 3.minutes.ago)
      Fabricate(:status, account: account4, created_at: 3.minutes.ago)
      Fabricate(:status, account: account_alice, created_at: 3.minutes.ago)
      Fabricate(:status, account: account_bob, created_at: 3.minutes.ago)
      Fabricate(:status, account: account_chris, created_at: 3.minutes.ago)
      Fabricate(:status, account: account_dave, created_at: 3.minutes.ago)
      Fabricate(:status, account: remote, created_at: 3.minutes.ago)
    end



@@ 106,8 106,8 @@ describe Scheduler::AccountsStatusesCleanupScheduler do

        expect { subject.perform }
          .to change(Status, :count).by(-subject.compute_budget) # Cleanable statuses
          .and (not_change { account2.statuses.count }) # No cleanup policy for account
          .and(not_change { account4.statuses.count }) # Disabled cleanup policy
          .and (not_change { account_bob.statuses.count }) # No cleanup policy for account
          .and(not_change { account_dave.statuses.count }) # Disabled cleanup policy
      end

      it 'eventually deletes every deletable toot given enough runs' do


@@ 122,9 122,9 @@ describe Scheduler::AccountsStatusesCleanupScheduler do

        expect { 3.times { subject.perform } }
          .to change(Status, :count).by(-3 * 3)
          .and change { account1.statuses.count }
          .and change { account3.statuses.count }
          .and(change { account5.statuses.count })
          .and change { account_alice.statuses.count }
          .and change { account_chris.statuses.count }
          .and(change { account_erin.statuses.count })
      end

      context 'when given a big budget' do


@@ 156,7 156,7 @@ describe Scheduler::AccountsStatusesCleanupScheduler do

      def cleanable_statuses_count
        Status
          .where(account_id: [account1, account3, account5]) # Accounts with enabled policies
          .where(account_id: [account_alice, account_chris, account_erin]) # Accounts with enabled policies
          .where('created_at < ?', 2.weeks.ago) # Policy defaults is 2.weeks
          .count
      end