Ver código fonte

Add specs for `Instance` model scopes and add `with_domain_follows` scope (#28767)

Matt Jankowski 8 meses atrás
pai
commit
42ab855b23

+ 5 - 1
app/controllers/admin/export_domain_blocks_controller.rb

@@ -49,7 +49,7 @@ module Admin
         next
       end
 
-      @warning_domains = Instance.where(domain: @domain_blocks.map(&:domain)).where('EXISTS (SELECT 1 FROM follows JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id WHERE accounts.domain = instances.domain)').pluck(:domain)
+      @warning_domains = instances_from_imported_blocks.pluck(:domain)
     rescue ActionController::ParameterMissing
       flash.now[:alert] = I18n.t('admin.export_domain_blocks.no_file')
       set_dummy_import!
@@ -58,6 +58,10 @@ module Admin
 
     private
 
+    def instances_from_imported_blocks
+      Instance.with_domain_follows(@domain_blocks.map(&:domain))
+    end
+
     def export_filename
       'domain_blocks.csv'
     end

+ 14 - 0
app/models/instance.rb

@@ -25,11 +25,25 @@ class Instance < ApplicationRecord
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
   scope :domain_starts_with, ->(value) { where(arel_table[:domain].matches("#{sanitize_sql_like(value)}%", false, true)) }
   scope :by_domain_and_subdomains, ->(domain) { where("reverse('.' || domain) LIKE reverse(?)", "%.#{domain}") }
+  scope :with_domain_follows, ->(domains) { where(domain: domains).where(domain_account_follows) }
 
   def self.refresh
     Scenic.database.refresh_materialized_view(table_name, concurrently: true, cascade: false)
   end
 
+  def self.domain_account_follows
+    Arel.sql(
+      <<~SQL.squish
+        EXISTS (
+          SELECT 1
+          FROM follows
+          JOIN accounts ON follows.account_id = accounts.id OR follows.target_account_id = accounts.id
+          WHERE accounts.domain = instances.domain
+        )
+      SQL
+    )
+  end
+
   def readonly?
     true
   end

+ 104 - 0
spec/models/instance_spec.rb

@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Instance do
+  describe 'Scopes' do
+    before { described_class.refresh }
+
+    describe '#searchable' do
+      let(:expected_domain) { 'host.example' }
+      let(:blocked_domain) { 'other.example' }
+
+      before do
+        Fabricate :account, domain: expected_domain
+        Fabricate :account, domain: blocked_domain
+        Fabricate :domain_block, domain: blocked_domain
+      end
+
+      it 'returns records not domain blocked' do
+        results = described_class.searchable.pluck(:domain)
+
+        expect(results)
+          .to include(expected_domain)
+          .and not_include(blocked_domain)
+      end
+    end
+
+    describe '#matches_domain' do
+      let(:host_domain) { 'host.example.com' }
+      let(:host_under_domain) { 'host_under.example.com' }
+      let(:other_domain) { 'other.example' }
+
+      before do
+        Fabricate :account, domain: host_domain
+        Fabricate :account, domain: host_under_domain
+        Fabricate :account, domain: other_domain
+      end
+
+      it 'returns matching records' do
+        expect(described_class.matches_domain('host.exa').pluck(:domain))
+          .to include(host_domain)
+          .and not_include(other_domain)
+
+        expect(described_class.matches_domain('ple.com').pluck(:domain))
+          .to include(host_domain)
+          .and not_include(other_domain)
+
+        expect(described_class.matches_domain('example').pluck(:domain))
+          .to include(host_domain)
+          .and include(other_domain)
+
+        expect(described_class.matches_domain('host_').pluck(:domain)) # Preserve SQL wildcards
+          .to include(host_domain)
+          .and include(host_under_domain)
+          .and not_include(other_domain)
+      end
+    end
+
+    describe '#by_domain_and_subdomains' do
+      let(:exact_match_domain) { 'example.com' }
+      let(:subdomain_domain) { 'foo.example.com' }
+      let(:partial_domain) { 'grexample.com' }
+
+      before do
+        Fabricate(:account, domain: exact_match_domain)
+        Fabricate(:account, domain: subdomain_domain)
+        Fabricate(:account, domain: partial_domain)
+      end
+
+      it 'returns matching instances' do
+        results = described_class.by_domain_and_subdomains('example.com').pluck(:domain)
+
+        expect(results)
+          .to include(exact_match_domain)
+          .and include(subdomain_domain)
+          .and not_include(partial_domain)
+      end
+    end
+
+    describe '#with_domain_follows' do
+      let(:example_domain) { 'example.host' }
+      let(:other_domain) { 'other.host' }
+      let(:none_domain) { 'none.host' }
+
+      before do
+        example_account = Fabricate(:account, domain: example_domain)
+        other_account = Fabricate(:account, domain: other_domain)
+        Fabricate(:account, domain: none_domain)
+
+        Fabricate :follow, account: example_account
+        Fabricate :follow, target_account: other_account
+      end
+
+      it 'returns instances with domain accounts that have follows' do
+        results = described_class.with_domain_follows(['example.host', 'other.host', 'none.host']).pluck(:domain)
+
+        expect(results)
+          .to include(example_domain)
+          .and include(other_domain)
+          .and not_include(none_domain)
+      end
+    end
+  end
+end