about summary refs log tree commit diff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-rw-r--r--spec/lib/activitypub/activity/accept_spec.rb6
-rw-r--r--spec/lib/activitypub/activity/add_spec.rb14
-rw-r--r--spec/lib/activitypub/activity/announce_spec.rb20
-rw-r--r--spec/lib/activitypub/activity/block_spec.rb2
-rw-r--r--spec/lib/activitypub/activity/create_spec.rb59
-rw-r--r--spec/lib/activitypub/activity/delete_spec.rb4
-rw-r--r--spec/lib/activitypub/activity/flag_spec.rb5
-rw-r--r--spec/lib/activitypub/activity/follow_spec.rb2
-rw-r--r--spec/lib/activitypub/activity/like_spec.rb2
-rw-r--r--spec/lib/activitypub/activity/move_spec.rb2
-rw-r--r--spec/lib/activitypub/activity/reject_spec.rb6
-rw-r--r--spec/lib/activitypub/activity/remove_spec.rb2
-rw-r--r--spec/lib/activitypub/activity/undo_spec.rb6
-rw-r--r--spec/lib/activitypub/activity/update_spec.rb6
-rw-r--r--spec/lib/activitypub/adapter_spec.rb6
-rw-r--r--spec/lib/activitypub/dereferencer_spec.rb6
-rw-r--r--spec/lib/activitypub/linked_data_signature_spec.rb6
-rw-r--r--spec/lib/activitypub/tag_manager_spec.rb2
-rw-r--r--spec/lib/admin/system_check/base_check_spec.rb27
-rw-r--r--spec/lib/admin/system_check/database_schema_check_spec.rb45
-rw-r--r--spec/lib/admin/system_check/elasticsearch_check_spec.rb100
-rw-r--r--spec/lib/admin/system_check/media_privacy_check_spec.rb33
-rw-r--r--spec/lib/admin/system_check/message_spec.rb14
-rw-r--r--spec/lib/admin/system_check/rules_check_spec.rb53
-rw-r--r--spec/lib/admin/system_check/sidekiq_process_check_spec.rb45
-rw-r--r--spec/lib/admin/system_check_spec.rb15
-rw-r--r--spec/lib/advanced_text_formatter_spec.rb82
-rw-r--r--spec/lib/emoji_formatter_spec.rb10
-rw-r--r--spec/lib/entity_cache_spec.rb4
-rw-r--r--spec/lib/extractor_spec.rb8
-rw-r--r--spec/lib/fast_ip_map_spec.rb2
-rw-r--r--spec/lib/feed_manager_spec.rb30
-rw-r--r--spec/lib/html_aware_formatter_spec.rb10
-rw-r--r--spec/lib/importer/accounts_index_importer_spec.rb16
-rw-r--r--spec/lib/importer/base_importer_spec.rb14
-rw-r--r--spec/lib/importer/statuses_index_importer_spec.rb16
-rw-r--r--spec/lib/importer/tags_index_importer_spec.rb16
-rw-r--r--spec/lib/link_details_extractor_spec.rb130
-rw-r--r--spec/lib/mastodon/cli_spec.rb14
-rw-r--r--spec/lib/ostatus/tag_manager_spec.rb12
-rw-r--r--spec/lib/plain_text_formatter_spec.rb65
-rw-r--r--spec/lib/request_pool_spec.rb2
-rw-r--r--spec/lib/request_spec.rb16
-rw-r--r--spec/lib/search_query_transformer_spec.rb18
-rw-r--r--spec/lib/settings/extend_spec.rb16
-rw-r--r--spec/lib/settings/scoped_settings_spec.rb35
-rw-r--r--spec/lib/status_filter_spec.rb7
-rw-r--r--spec/lib/status_reach_finder_spec.rb4
-rw-r--r--spec/lib/suspicious_sign_in_detector_spec.rb6
-rw-r--r--spec/lib/tag_manager_spec.rb22
-rw-r--r--spec/lib/text_formatter_spec.rb88
-rw-r--r--spec/lib/translation_service/deepl_spec.rb82
-rw-r--r--spec/lib/translation_service/libre_translate_spec.rb54
-rw-r--r--spec/lib/user_settings_decorator_spec.rb84
-rw-r--r--spec/lib/vacuum/access_tokens_vacuum_spec.rb2
-rw-r--r--spec/lib/vacuum/backups_vacuum_spec.rb6
-rw-r--r--spec/lib/vacuum/feeds_vacuum_spec.rb2
-rw-r--r--spec/lib/vacuum/media_attachments_vacuum_spec.rb5
-rw-r--r--spec/lib/vacuum/preview_cards_vacuum_spec.rb6
-rw-r--r--spec/lib/vacuum/statuses_vacuum_spec.rb6
-rw-r--r--spec/lib/vacuum/system_keys_vacuum_spec.rb2
-rw-r--r--spec/lib/webfinger_resource_spec.rb30
62 files changed, 979 insertions, 431 deletions
diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb
index 304cf2208..890a07be5 100644
--- a/spec/lib/activitypub/activity/accept_spec.rb
+++ b/spec/lib/activitypub/activity/accept_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Accept do
@@ -42,6 +44,8 @@ RSpec.describe ActivityPub::Activity::Accept do
   end
 
   context 'given a relay' do
+    subject { described_class.new(json, sender) }
+
     let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
 
     let(:json) do
@@ -59,8 +63,6 @@ RSpec.describe ActivityPub::Activity::Accept do
       }.with_indifferent_access
     end
 
-    subject { described_class.new(json, sender) }
-
     it 'marks the relay as accepted' do
       subject.perform
       expect(relay.reload.accepted?).to be true
diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb
index e6408b610..9c45e465e 100644
--- a/spec/lib/activitypub/activity/add_spec.rb
+++ b/spec/lib/activitypub/activity/add_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Add do
@@ -48,10 +50,10 @@ RSpec.describe ActivityPub::Activity::Add do
         end
 
         it 'fetches the status and pins it' do
-          allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil|
+          allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
             expect(uri).to eq 'https://example.com/unknown'
-            expect(id).to eq true
-            expect(on_behalf_of&.following?(sender)).to eq true
+            expect(id).to be true
+            expect(on_behalf_of&.following?(sender)).to be true
             status
           end
           subject.perform
@@ -62,10 +64,10 @@ RSpec.describe ActivityPub::Activity::Add do
 
       context 'when there is no local follower' do
         it 'tries to fetch the status' do
-          allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil|
+          allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
             expect(uri).to eq 'https://example.com/unknown'
-            expect(id).to eq true
-            expect(on_behalf_of).to eq nil
+            expect(id).to be true
+            expect(on_behalf_of).to be_nil
             nil
           end
           subject.perform
diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb
index e9cd6c68c..394b1d7b9 100644
--- a/spec/lib/activitypub/activity/announce_spec.rb
+++ b/spec/lib/activitypub/activity/announce_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Announce do
+  subject { described_class.new(json, sender) }
+
   let(:sender)    { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor') }
   let(:recipient) { Fabricate(:account) }
   let(:status)    { Fabricate(:status, account: recipient) }
@@ -27,8 +31,6 @@ RSpec.describe ActivityPub::Activity::Announce do
     }
   end
 
-  subject { described_class.new(json, sender) }
-
   describe '#perform' do
     context 'when sender is followed by a local account' do
       before do
@@ -82,10 +84,10 @@ RSpec.describe ActivityPub::Activity::Announce do
             content: 'Lorem ipsum',
             attributedTo: 'https://example.com/actor',
             to: {
-              'type': 'OrderedCollection',
-              'id': 'http://example.com/followers',
-              'first': 'http://example.com/followers?page=true',
-            }
+              type: 'OrderedCollection',
+              id: 'http://example.com/followers',
+              first: 'http://example.com/followers?page=true',
+            },
           }
         end
 
@@ -110,13 +112,13 @@ RSpec.describe ActivityPub::Activity::Announce do
     end
 
     context 'when the sender is relayed' do
+      subject { described_class.new(json, sender, relayed_through_actor: relay_account) }
+
       let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') }
       let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') }
 
       let(:object_json) { 'https://example.com/actor/hello-world' }
 
-      subject { described_class.new(json, sender, relayed_through_actor: relay_account) }
-
       before do
         stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json))
       end
@@ -139,7 +141,7 @@ RSpec.describe ActivityPub::Activity::Announce do
         end
 
         it 'does not fetch the remote status' do
-          expect(a_request(:get, 'https://example.com/actor/hello-world')).not_to have_been_made
+          expect(a_request(:get, 'https://example.com/actor/hello-world')).to_not have_been_made
           expect(Status.find_by(uri: 'https://example.com/actor/hello-world')).to be_nil
         end
 
diff --git a/spec/lib/activitypub/activity/block_spec.rb b/spec/lib/activitypub/activity/block_spec.rb
index 42bdfdc81..6f6898401 100644
--- a/spec/lib/activitypub/activity/block_spec.rb
+++ b/spec/lib/activitypub/activity/block_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Block do
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index 738e644c5..1226cfd8e 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Create do
@@ -51,7 +53,7 @@ RSpec.describe ActivityPub::Activity::Create do
           status = sender.statuses.first
 
           expect(status).to_not be_nil
-          expect(status.edited?).to eq true
+          expect(status.edited?).to be true
         end
       end
 
@@ -77,7 +79,7 @@ RSpec.describe ActivityPub::Activity::Create do
           status = sender.statuses.first
 
           expect(status).to_not be_nil
-          expect(status.edited?).to eq false
+          expect(status.edited?).to be false
         end
       end
 
@@ -252,10 +254,10 @@ RSpec.describe ActivityPub::Activity::Create do
             type: 'Note',
             content: 'Lorem ipsum',
             to: {
-              'type': 'OrderedCollection',
-              'id': 'http://example.com/followers',
-              'first': 'http://example.com/followers?page=true',
-            }
+              type: 'OrderedCollection',
+              id: 'http://example.com/followers',
+              first: 'http://example.com/followers?page=true',
+            },
           }
         end
 
@@ -454,7 +456,6 @@ RSpec.describe ActivityPub::Activity::Create do
         end
       end
 
-
       context 'with media attachments with long description' do
         let(:object_json) do
           {
@@ -733,7 +734,7 @@ RSpec.describe ActivityPub::Activity::Create do
                 replies: {
                   type: 'Collection',
                   totalItems: 3,
-                }
+                },
               },
             ],
           }
@@ -763,7 +764,7 @@ RSpec.describe ActivityPub::Activity::Create do
             id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
             type: 'Note',
             name: 'Yellow',
-            inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
+            inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status),
           }
         end
 
@@ -788,7 +789,7 @@ RSpec.describe ActivityPub::Activity::Create do
             id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
             type: 'Note',
             name: 'Yellow',
-            inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
+            inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status),
           }
         end
 
@@ -799,11 +800,9 @@ RSpec.describe ActivityPub::Activity::Create do
     end
 
     context 'with an encrypted message' do
-      let(:recipient) { Fabricate(:account) }
-      let(:target_device) { Fabricate(:device, account: recipient) }
-
       subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) }
 
+      let(:recipient) { Fabricate(:account) }
       let(:object_json) do
         {
           id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
@@ -825,6 +824,7 @@ RSpec.describe ActivityPub::Activity::Create do
           },
         }
       end
+      let(:target_device) { Fabricate(:device, account: recipient) }
 
       before do
         subject.perform
@@ -879,14 +879,9 @@ RSpec.describe ActivityPub::Activity::Create do
     end
 
     context 'when sender replies to local status' do
-      let!(:local_status) { Fabricate(:status) }
-
       subject { described_class.new(json, sender, delivery: true) }
 
-      before do
-        subject.perform
-      end
-
+      let!(:local_status) { Fabricate(:status) }
       let(:object_json) do
         {
           id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
@@ -896,6 +891,10 @@ RSpec.describe ActivityPub::Activity::Create do
         }
       end
 
+      before do
+        subject.perform
+      end
+
       it 'creates status' do
         status = sender.statuses.first
 
@@ -905,14 +904,9 @@ RSpec.describe ActivityPub::Activity::Create do
     end
 
     context 'when sender targets a local user' do
-      let!(:local_account) { Fabricate(:account) }
-
       subject { described_class.new(json, sender, delivery: true) }
 
-      before do
-        subject.perform
-      end
-
+      let!(:local_account) { Fabricate(:account) }
       let(:object_json) do
         {
           id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
@@ -922,6 +916,10 @@ RSpec.describe ActivityPub::Activity::Create do
         }
       end
 
+      before do
+        subject.perform
+      end
+
       it 'creates status' do
         status = sender.statuses.first
 
@@ -931,14 +929,9 @@ RSpec.describe ActivityPub::Activity::Create do
     end
 
     context 'when sender cc\'s a local user' do
-      let!(:local_account) { Fabricate(:account) }
-
       subject { described_class.new(json, sender, delivery: true) }
 
-      before do
-        subject.perform
-      end
-
+      let!(:local_account) { Fabricate(:account) }
       let(:object_json) do
         {
           id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
@@ -948,6 +941,10 @@ RSpec.describe ActivityPub::Activity::Create do
         }
       end
 
+      before do
+        subject.perform
+      end
+
       it 'creates status' do
         status = sender.statuses.first
 
diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb
index 9dfb8a61b..3a73b3726 100644
--- a/spec/lib/activitypub/activity/delete_spec.rb
+++ b/spec/lib/activitypub/activity/delete_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Delete do
@@ -30,6 +32,7 @@ RSpec.describe ActivityPub::Activity::Delete do
   context 'when the status has been reblogged' do
     describe '#perform' do
       subject { described_class.new(json, sender) }
+
       let!(:reblogger) { Fabricate(:account) }
       let!(:follower)  { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
       let!(:reblog)    { Fabricate(:status, account: reblogger, reblog: status) }
@@ -53,6 +56,7 @@ RSpec.describe ActivityPub::Activity::Delete do
   context 'when the status has been reported' do
     describe '#perform' do
       subject { described_class.new(json, sender) }
+
       let!(:reporter) { Fabricate(:account) }
 
       before do
diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb
index 2f2d13876..005e185e6 100644
--- a/spec/lib/activitypub/activity/flag_spec.rb
+++ b/spec/lib/activitypub/activity/flag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Flag do
@@ -110,7 +112,8 @@ RSpec.describe ActivityPub::Activity::Flag do
 
   describe '#perform with a defined uri' do
     subject { described_class.new(json, sender) }
-    let (:flag_id) { 'http://example.com/reports/1' }
+
+    let(:flag_id) { 'http://example.com/reports/1' }
 
     before do
       subject.perform
diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb
index fd4ede82b..eb8b17d61 100644
--- a/spec/lib/activitypub/activity/follow_spec.rb
+++ b/spec/lib/activitypub/activity/follow_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Follow do
diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb
index b69615a9d..640d61ab3 100644
--- a/spec/lib/activitypub/activity/like_spec.rb
+++ b/spec/lib/activitypub/activity/like_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Like do
diff --git a/spec/lib/activitypub/activity/move_spec.rb b/spec/lib/activitypub/activity/move_spec.rb
index c468fdeff..8bd23aa7b 100644
--- a/spec/lib/activitypub/activity/move_spec.rb
+++ b/spec/lib/activitypub/activity/move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Move do
diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb
index fed4cd8cd..5e0f09bfe 100644
--- a/spec/lib/activitypub/activity/reject_spec.rb
+++ b/spec/lib/activitypub/activity/reject_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Reject do
@@ -121,6 +123,8 @@ RSpec.describe ActivityPub::Activity::Reject do
   end
 
   context 'given a relay' do
+    subject { described_class.new(json, sender) }
+
     let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
 
     let(:json) do
@@ -138,8 +142,6 @@ RSpec.describe ActivityPub::Activity::Reject do
       }.with_indifferent_access
     end
 
-    subject { described_class.new(json, sender) }
-
     it 'marks the relay as rejected' do
       subject.perform
       expect(relay.reload.rejected?).to be true
diff --git a/spec/lib/activitypub/activity/remove_spec.rb b/spec/lib/activitypub/activity/remove_spec.rb
index 4209dfde2..fc12aec8c 100644
--- a/spec/lib/activitypub/activity/remove_spec.rb
+++ b/spec/lib/activitypub/activity/remove_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Remove do
diff --git a/spec/lib/activitypub/activity/undo_spec.rb b/spec/lib/activitypub/activity/undo_spec.rb
index c0309e49d..b4cbc7196 100644
--- a/spec/lib/activitypub/activity/undo_spec.rb
+++ b/spec/lib/activitypub/activity/undo_spec.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Undo do
+  subject { described_class.new(json, sender) }
+
   let(:sender) { Fabricate(:account, domain: 'example.com') }
 
   let(:json) do
@@ -13,8 +17,6 @@ RSpec.describe ActivityPub::Activity::Undo do
     }.with_indifferent_access
   end
 
-  subject { described_class.new(json, sender) }
-
   describe '#perform' do
     context 'with Announce' do
       let(:status) { Fabricate(:status) }
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
index 4cd853af2..f77279c02 100644
--- a/spec/lib/activitypub/activity/update_spec.rb
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Update do
+  subject { described_class.new(json, sender) }
+
   let!(:sender) { Fabricate(:account) }
 
   before do
     sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
   end
 
-  subject { described_class.new(json, sender) }
-
   describe '#perform' do
     context 'with an Actor object' do
       let(:modified_sender) do
diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb
index ea03797aa..b981ea9c6 100644
--- a/spec/lib/activitypub/adapter_spec.rb
+++ b/spec/lib/activitypub/adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Adapter do
@@ -41,10 +43,10 @@ RSpec.describe ActivityPub::Adapter do
   end
 
   describe '#serializable_hash' do
-    let(:serializer_class) {}
-
     subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json }
 
+    let(:serializer_class) {}
+
     context 'when serializer defines no context' do
       let(:serializer_class) { TestWithBasicContextSerializer }
 
diff --git a/spec/lib/activitypub/dereferencer_spec.rb b/spec/lib/activitypub/dereferencer_spec.rb
index e50b497c7..11078de86 100644
--- a/spec/lib/activitypub/dereferencer_spec.rb
+++ b/spec/lib/activitypub/dereferencer_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Dereferencer do
   describe '#object' do
+    subject { described_class.new(uri, permitted_origin: permitted_origin, signature_actor: signature_actor).object }
+
     let(:object) { { '@context': 'https://www.w3.org/ns/activitystreams', id: 'https://example.com/foo', type: 'Note', content: 'Hoge' } }
     let(:permitted_origin) { 'https://example.com' }
     let(:signature_actor) { nil }
     let(:uri) { nil }
 
-    subject { described_class.new(uri, permitted_origin: permitted_origin, signature_actor: signature_actor).object }
-
     before do
       stub_request(:get, 'https://example.com/foo').to_return(body: Oj.dump(object), headers: { 'Content-Type' => 'application/activity+json' })
     end
diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb
index d55a7c7fa..619d6df12 100644
--- a/spec/lib/activitypub/linked_data_signature_spec.rb
+++ b/spec/lib/activitypub/linked_data_signature_spec.rb
@@ -1,8 +1,12 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::LinkedDataSignature do
   include JsonLdHelper
 
+  subject { described_class.new(json) }
+
   let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice') }
 
   let(:raw_json) do
@@ -14,8 +18,6 @@ RSpec.describe ActivityPub::LinkedDataSignature do
 
   let(:json) { raw_json.merge('signature' => signature) }
 
-  subject { described_class.new(json) }
-
   before do
     stub_jsonld_contexts!
   end
diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb
index 606a1de2e..596e91e95 100644
--- a/spec/lib/activitypub/tag_manager_spec.rb
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe ActivityPub::TagManager do
diff --git a/spec/lib/admin/system_check/base_check_spec.rb b/spec/lib/admin/system_check/base_check_spec.rb
new file mode 100644
index 000000000..fdd9f6b6c
--- /dev/null
+++ b/spec/lib/admin/system_check/base_check_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::BaseCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  describe 'skip?' do
+    it 'returns false' do
+      expect(check.skip?).to be false
+    end
+  end
+
+  describe 'pass?' do
+    it 'raises not implemented error' do
+      expect { check.pass? }.to raise_error(NotImplementedError)
+    end
+  end
+
+  describe 'message' do
+    it 'raises not implemented error' do
+      expect { check.message }.to raise_error(NotImplementedError)
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check/database_schema_check_spec.rb b/spec/lib/admin/system_check/database_schema_check_spec.rb
new file mode 100644
index 000000000..db1dcb52f
--- /dev/null
+++ b/spec/lib/admin/system_check/database_schema_check_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::DatabaseSchemaCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  it_behaves_like 'a check available to devops users'
+
+  describe 'pass?' do
+    context 'when database needs migration' do
+      before do
+        context = instance_double(ActiveRecord::MigrationContext, needs_migration?: true)
+        allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context)
+      end
+
+      it 'returns false' do
+        expect(check.pass?).to be false
+      end
+    end
+
+    context 'when database does not need migration' do
+      before do
+        context = instance_double(ActiveRecord::MigrationContext, needs_migration?: false)
+        allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context)
+      end
+
+      it 'returns true' do
+        expect(check.pass?).to be true
+      end
+    end
+  end
+
+  describe 'message' do
+    it 'sends class name symbol to message instance' do
+      allow(Admin::SystemCheck::Message).to receive(:new).with(:database_schema_check)
+
+      check.message
+
+      expect(Admin::SystemCheck::Message).to have_received(:new).with(:database_schema_check)
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check/elasticsearch_check_spec.rb b/spec/lib/admin/system_check/elasticsearch_check_spec.rb
new file mode 100644
index 000000000..1ffac89ee
--- /dev/null
+++ b/spec/lib/admin/system_check/elasticsearch_check_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::ElasticsearchCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  it_behaves_like 'a check available to devops users'
+
+  describe 'pass?' do
+    context 'when chewy is enabled' do
+      before { allow(Chewy).to receive(:enabled?).and_return(true) }
+
+      context 'when running version is present and high enough' do
+        before do
+          allow(Chewy.client).to receive(:info)
+            .and_return({ 'version' => { 'number' => '999.99.9' } })
+        end
+
+        it 'returns true' do
+          expect(check.pass?).to be true
+        end
+      end
+
+      context 'when running version is present and too low' do
+        context 'when compatible version is too low' do
+          before do
+            allow(Chewy.client).to receive(:info)
+              .and_return({ 'version' => { 'number' => '1.2.3', 'minimum_wire_compatibility_version' => '1.0' } })
+          end
+
+          it 'returns false' do
+            expect(check.pass?).to be false
+          end
+        end
+
+        context 'when compatible version is high enough' do
+          before do
+            allow(Chewy.client).to receive(:info)
+              .and_return({ 'version' => { 'number' => '1.2.3', 'minimum_wire_compatibility_version' => '99.9' } })
+          end
+
+          it 'returns true' do
+            expect(check.pass?).to be true
+          end
+        end
+      end
+
+      context 'when running version is missing' do
+        before do
+          client = instance_double(Elasticsearch::Transport::Client)
+          allow(client).to receive(:info).and_raise(Elasticsearch::Transport::Transport::Error)
+          allow(Chewy).to receive(:client).and_return(client)
+        end
+
+        it 'returns false' do
+          expect(check.pass?).to be false
+        end
+      end
+    end
+
+    context 'when chewy is not enabled' do
+      before { allow(Chewy).to receive(:enabled?).and_return(false) }
+
+      it 'returns true' do
+        expect(check.pass?).to be true
+      end
+    end
+  end
+
+  describe 'message' do
+    context 'when running version is present' do
+      before { allow(Chewy.client).to receive(:info).and_return({ 'version' => { 'number' => '999.99.9' } }) }
+
+      it 'sends class name symbol to message instance' do
+        allow(Admin::SystemCheck::Message).to receive(:new)
+          .with(:elasticsearch_version_check, anything)
+
+        check.message
+
+        expect(Admin::SystemCheck::Message).to have_received(:new)
+          .with(:elasticsearch_version_check, 'Elasticsearch 999.99.9 is running while 7.x is required')
+      end
+    end
+
+    context 'when running version is missing' do
+      it 'sends class name symbol to message instance' do
+        allow(Admin::SystemCheck::Message).to receive(:new)
+          .with(:elasticsearch_running_check)
+
+        check.message
+
+        expect(Admin::SystemCheck::Message).to have_received(:new)
+          .with(:elasticsearch_running_check)
+      end
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check/media_privacy_check_spec.rb b/spec/lib/admin/system_check/media_privacy_check_spec.rb
new file mode 100644
index 000000000..316bf1215
--- /dev/null
+++ b/spec/lib/admin/system_check/media_privacy_check_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::MediaPrivacyCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  it_behaves_like 'a check available to devops users'
+
+  describe 'pass?' do
+    context 'when the media cannot be listed' do
+      before do
+        stub_request(:get, /ngrok.io/).to_return(status: 200, body: 'a list of no files')
+      end
+
+      it 'returns true' do
+        expect(check.pass?).to be true
+      end
+    end
+  end
+
+  describe 'message' do
+    it 'sends values to message instance' do
+      allow(Admin::SystemCheck::Message).to receive(:new).with(nil, nil, nil, true)
+
+      check.message
+
+      expect(Admin::SystemCheck::Message).to have_received(:new).with(nil, nil, nil, true)
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check/message_spec.rb b/spec/lib/admin/system_check/message_spec.rb
new file mode 100644
index 000000000..c0671f345
--- /dev/null
+++ b/spec/lib/admin/system_check/message_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::Message do
+  subject(:check) { described_class.new(:key_value, :value_value, :action_value, :critical_value) }
+
+  it 'providers readers when initialized' do
+    expect(check.key).to eq :key_value
+    expect(check.value).to eq :value_value
+    expect(check.action).to eq :action_value
+    expect(check.critical).to eq :critical_value
+  end
+end
diff --git a/spec/lib/admin/system_check/rules_check_spec.rb b/spec/lib/admin/system_check/rules_check_spec.rb
new file mode 100644
index 000000000..fb3293fb2
--- /dev/null
+++ b/spec/lib/admin/system_check/rules_check_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::RulesCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  describe 'skip?' do
+    context 'when user can manage rules' do
+      before { allow(user).to receive(:can?).with(:manage_rules).and_return(true) }
+
+      it 'returns false' do
+        expect(check.skip?).to be false
+      end
+    end
+
+    context 'when user cannot manage rules' do
+      before { allow(user).to receive(:can?).with(:manage_rules).and_return(false) }
+
+      it 'returns true' do
+        expect(check.skip?).to be true
+      end
+    end
+  end
+
+  describe 'pass?' do
+    context 'when there is not a kept rule' do
+      it 'returns false' do
+        expect(check.pass?).to be false
+      end
+    end
+
+    context 'when there is a kept rule' do
+      before { Fabricate(:rule) }
+
+      it 'returns true' do
+        expect(check.pass?).to be true
+      end
+    end
+  end
+
+  describe 'message' do
+    it 'sends class name symbol to message instance' do
+      allow(Admin::SystemCheck::Message).to receive(:new).with(:rules_check, nil, '/admin/rules')
+
+      check.message
+
+      expect(Admin::SystemCheck::Message).to have_received(:new).with(:rules_check, nil, '/admin/rules')
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check/sidekiq_process_check_spec.rb b/spec/lib/admin/system_check/sidekiq_process_check_spec.rb
new file mode 100644
index 000000000..9bd9daddf
--- /dev/null
+++ b/spec/lib/admin/system_check/sidekiq_process_check_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck::SidekiqProcessCheck do
+  subject(:check) { described_class.new(user) }
+
+  let(:user) { Fabricate(:user) }
+
+  it_behaves_like 'a check available to devops users'
+
+  describe 'pass?' do
+    context 'when missing queues is empty' do
+      before do
+        process_set = instance_double(Sidekiq::ProcessSet, reduce: [])
+        allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set)
+      end
+
+      it 'returns true' do
+        expect(check.pass?).to be true
+      end
+    end
+
+    context 'when missing queues is not empty' do
+      before do
+        process_set = instance_double(Sidekiq::ProcessSet, reduce: [:something])
+        allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set)
+      end
+
+      it 'returns false' do
+        expect(check.pass?).to be false
+      end
+    end
+  end
+
+  describe 'message' do
+    it 'sends values to message instance' do
+      allow(Admin::SystemCheck::Message).to receive(:new).with(:sidekiq_process_check, 'default, push, mailers, pull, scheduler, ingress')
+
+      check.message
+
+      expect(Admin::SystemCheck::Message).to have_received(:new).with(:sidekiq_process_check, 'default, push, mailers, pull, scheduler, ingress')
+    end
+  end
+end
diff --git a/spec/lib/admin/system_check_spec.rb b/spec/lib/admin/system_check_spec.rb
new file mode 100644
index 000000000..30048fd3a
--- /dev/null
+++ b/spec/lib/admin/system_check_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::SystemCheck do
+  let(:user) { Fabricate(:user) }
+
+  describe 'perform' do
+    let(:result) { described_class.perform(user) }
+
+    it 'runs all the checks' do
+      expect(result).to be_an(Array)
+    end
+  end
+end
diff --git a/spec/lib/advanced_text_formatter_spec.rb b/spec/lib/advanced_text_formatter_spec.rb
index c1e469606..8b27b56a1 100644
--- a/spec/lib/advanced_text_formatter_spec.rb
+++ b/spec/lib/advanced_text_formatter_spec.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe AdvancedTextFormatter do
   describe '#to_s' do
+    subject { described_class.new(text, preloaded_accounts: preloaded_accounts, content_type: content_type).to_s }
+
     let(:preloaded_accounts) { nil }
     let(:content_type) { 'text/markdown' }
 
-    subject { described_class.new(text, preloaded_accounts: preloaded_accounts, content_type: content_type).to_s }
-
     context 'given a markdown source' do
       let(:content_type) { 'text/markdown' }
 
@@ -14,7 +16,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'text' }
 
         it 'paragraphizes the text' do
-          is_expected.to eq '<p>text</p>'
+          expect(subject).to eq '<p>text</p>'
         end
       end
 
@@ -22,7 +24,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { "line\nfeed" }
 
         it 'removes line feeds' do
-          is_expected.not_to include "\n"
+          expect(subject).to_not include "\n"
         end
       end
 
@@ -30,7 +32,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'test `foo` bar' }
 
         it 'formats code using <code>' do
-          is_expected.to include 'test <code>foo</code> bar'
+          expect(subject).to include 'test <code>foo</code> bar'
         end
       end
 
@@ -38,15 +40,15 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { "test\n\n```\nint main(void) {\n  return 0; // https://joinmastodon.org/foo\n}\n```\n" }
 
         it 'formats code using <pre> and <code>' do
-          is_expected.to include '<pre><code>int main'
+          expect(subject).to include '<pre><code>int main'
         end
 
         it 'does not strip leading spaces' do
-          is_expected.to include '>  return 0'
+          expect(subject).to include '>  return 0'
         end
 
         it 'does not format links' do
-          is_expected.to include 'return 0; // https://joinmastodon.org/foo'
+          expect(subject).to include 'return 0; // https://joinmastodon.org/foo'
         end
       end
 
@@ -54,7 +56,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'test `https://foo.bar/bar` bar' }
 
         it 'does not rewrite the link' do
-          is_expected.to include 'test <code>https://foo.bar/bar</code> bar'
+          expect(subject).to include 'test <code>https://foo.bar/bar</code> bar'
         end
       end
 
@@ -62,7 +64,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'foo https://cb6e6126.ngrok.io/about/more' }
 
         it 'creates a link' do
-          is_expected.to include '<a href="https://cb6e6126.ngrok.io/about/more"'
+          expect(subject).to include '<a href="https://cb6e6126.ngrok.io/about/more"'
         end
       end
 
@@ -71,7 +73,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '@alice' }
 
         it 'creates a mention link' do
-          is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
+          expect(subject).to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
         end
       end
 
@@ -80,7 +82,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '@alice' }
 
         it 'does not create a mention link' do
-          is_expected.to include '@alice'
+          expect(subject).to include '@alice'
         end
       end
 
@@ -88,7 +90,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
+          expect(subject).to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
         end
       end
 
@@ -96,7 +98,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'http://google.com' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="http://google.com"'
+          expect(subject).to include 'href="http://google.com"'
         end
       end
 
@@ -104,7 +106,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'http://example.gay' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="http://example.gay"'
+          expect(subject).to include 'href="http://example.gay"'
         end
       end
 
@@ -112,11 +114,11 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://nic.みんな/' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://nic.みんな/"'
+          expect(subject).to include 'href="https://nic.みんな/"'
         end
 
         it 'has display URL' do
-          is_expected.to include '<span class="">nic.みんな/</span>'
+          expect(subject).to include '<span class="">nic.みんな/</span>'
         end
       end
 
@@ -124,7 +126,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
 
         it 'matches the full URL but not the period' do
-          is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
+          expect(subject).to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
         end
       end
 
@@ -132,7 +134,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '(http://google.com/)' }
 
         it 'matches the full URL but not the parentheses' do
-          is_expected.to include 'href="http://google.com/"'
+          expect(subject).to include 'href="http://google.com/"'
         end
       end
 
@@ -140,7 +142,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'http://www.google.com!' }
 
         it 'matches the full URL but not the exclamation point' do
-          is_expected.to include 'href="http://www.google.com"'
+          expect(subject).to include 'href="http://www.google.com"'
         end
       end
 
@@ -148,7 +150,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { "http://www.google.com'" }
 
         it 'matches the full URL but not the single quote' do
-          is_expected.to include 'href="http://www.google.com"'
+          expect(subject).to include 'href="http://www.google.com"'
         end
       end
     end
@@ -157,7 +159,7 @@ RSpec.describe AdvancedTextFormatter do
       let(:text) { 'http://www.google.com>' }
 
       it 'matches the full URL but not the angle bracket' do
-        is_expected.to include 'href="http://www.google.com"'
+        expect(subject).to include 'href="http://www.google.com"'
       end
     end
 
@@ -166,7 +168,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
         end
       end
 
@@ -174,7 +176,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
         end
       end
 
@@ -182,7 +184,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
         end
       end
 
@@ -190,7 +192,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
 
         it 'preserves escaped unicode characters' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
         end
       end
 
@@ -198,7 +200,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
+          expect(subject).to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
         end
       end
 
@@ -206,7 +208,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '"https://example.com/"' }
 
         it 'does not match the quotation marks' do
-          is_expected.to include 'href="https://example.com/"'
+          expect(subject).to include 'href="https://example.com/"'
         end
       end
 
@@ -214,19 +216,19 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '<https://example.com/>' }
 
         it 'does not match the angle brackets' do
-          is_expected.to include 'href="https://example.com/"'
+          expect(subject).to include 'href="https://example.com/"'
         end
       end
 
       context 'given a URL containing unsafe code (XSS attack, invisible part)' do
-        let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
+        let(:text) { 'http://example.com/blahblahblahblah/a<script>alert("Hello")</script>' }
 
         it 'does not include the HTML in the URL' do
-          is_expected.to include '"http://example.com/blahblahblahblah/a"'
+          expect(subject).to include '"http://example.com/blahblahblahblah/a"'
         end
 
         it 'does not include a script tag' do
-          is_expected.to_not include '<script>'
+          expect(subject).to_not include '<script>'
         end
       end
 
@@ -234,7 +236,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { '<script>alert("Hello")</script>' }
 
         it 'does not include a script tag' do
-          is_expected.to_not include '<script>'
+          expect(subject).to_not include '<script>'
         end
       end
 
@@ -242,7 +244,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { %q{<img src="javascript:alert('XSS');">} }
 
         it 'does not include the javascript' do
-          is_expected.to_not include 'href="javascript:'
+          expect(subject).to_not include 'href="javascript:'
         end
       end
 
@@ -250,7 +252,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'http://www\.google\.com' }
 
         it 'outputs the raw URL' do
-          is_expected.to eq '<p>http://www\.google\.com</p>'
+          expect(subject).to eq '<p>http://www\.google\.com</p>'
         end
       end
 
@@ -258,7 +260,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text)  { '#hashtag' }
 
         it 'creates a hashtag link' do
-          is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
+          expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
         end
       end
 
@@ -266,7 +268,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text)  { '#hashtagタグ' }
 
         it 'creates a hashtag link' do
-          is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
+          expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
         end
       end
 
@@ -274,7 +276,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'xmpp:user@instance.com' }
 
         it 'matches the full URI' do
-          is_expected.to include 'href="xmpp:user@instance.com"'
+          expect(subject).to include 'href="xmpp:user@instance.com"'
         end
       end
 
@@ -282,7 +284,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'please join xmpp:muc@instance.com?join right now' }
 
         it 'matches the full URI' do
-          is_expected.to include 'href="xmpp:muc@instance.com?join"'
+          expect(subject).to include 'href="xmpp:muc@instance.com?join"'
         end
       end
 
@@ -290,7 +292,7 @@ RSpec.describe AdvancedTextFormatter do
         let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
 
         it 'matches the full URI' do
-          is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
+          expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
         end
       end
     end
diff --git a/spec/lib/emoji_formatter_spec.rb b/spec/lib/emoji_formatter_spec.rb
index e1747bdd9..b73d5be4b 100644
--- a/spec/lib/emoji_formatter_spec.rb
+++ b/spec/lib/emoji_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe EmojiFormatter do
@@ -24,7 +26,7 @@ RSpec.describe EmojiFormatter do
       let(:text) { preformat_text(':coolcat: Beep boop') }
 
       it 'converts the shortcode to an image tag' do
-        is_expected.to match(/<img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
+        expect(subject).to match(/<img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
       end
     end
 
@@ -32,7 +34,7 @@ RSpec.describe EmojiFormatter do
       let(:text) { preformat_text('Beep :coolcat: boop') }
 
       it 'converts the shortcode to an image tag' do
-        is_expected.to match(/Beep <img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
+        expect(subject).to match(/Beep <img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
       end
     end
 
@@ -40,7 +42,7 @@ RSpec.describe EmojiFormatter do
       let(:text) { preformat_text(':coolcat::coolcat:') }
 
       it 'does not touch the shortcodes' do
-        is_expected.to match(/:coolcat::coolcat:/)
+        expect(subject).to match(/:coolcat::coolcat:/)
       end
     end
 
@@ -48,7 +50,7 @@ RSpec.describe EmojiFormatter do
       let(:text) { preformat_text('Beep boop :coolcat:') }
 
       it 'converts the shortcode to an image tag' do
-        is_expected.to match(/boop <img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
+        expect(subject).to match(/boop <img rel="emoji" draggable="false" width="16" height="16" class="emojione custom-emoji" alt=":coolcat:"/)
       end
     end
   end
diff --git a/spec/lib/entity_cache_spec.rb b/spec/lib/entity_cache_spec.rb
index 43494bd92..c750cddf3 100644
--- a/spec/lib/entity_cache_spec.rb
+++ b/spec/lib/entity_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe EntityCache do
@@ -12,7 +14,7 @@ RSpec.describe EntityCache do
       let(:domain)     { 'example.org' }
 
       it 'returns an empty array' do
-        is_expected.to eq []
+        expect(subject).to eq []
       end
     end
   end
diff --git a/spec/lib/extractor_spec.rb b/spec/lib/extractor_spec.rb
index dba4bd0bb..560617ed7 100644
--- a/spec/lib/extractor_spec.rb
+++ b/spec/lib/extractor_spec.rb
@@ -20,7 +20,7 @@ describe Extractor do
       text = '@screen_name'
       extracted = Extractor.extract_mentions_or_lists_with_indices(text)
       expect(extracted).to eq [
-        { screen_name: 'screen_name', indices: [ 0, 12 ] }
+        { screen_name: 'screen_name', indices: [0, 12] },
       ]
     end
 
@@ -44,19 +44,19 @@ describe Extractor do
     it 'does not exclude normal hash text before ://' do
       text = '#hashtag://'
       extracted = Extractor.extract_hashtags_with_indices(text)
-      expect(extracted).to eq [ { hashtag: 'hashtag', indices: [ 0, 8 ] } ]
+      expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
     end
 
     it 'excludes http://' do
       text = '#hashtaghttp://'
       extracted = Extractor.extract_hashtags_with_indices(text)
-      expect(extracted).to eq [ { hashtag: 'hashtag', indices: [ 0, 8 ] } ]
+      expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
     end
 
     it 'excludes https://' do
       text = '#hashtaghttps://'
       extracted = Extractor.extract_hashtags_with_indices(text)
-      expect(extracted).to eq [ { hashtag: 'hashtag', indices: [ 0, 8 ] } ]
+      expect(extracted).to eq [{ hashtag: 'hashtag', indices: [0, 8] }]
     end
 
     it 'yields hashtags if a block is given' do
diff --git a/spec/lib/fast_ip_map_spec.rb b/spec/lib/fast_ip_map_spec.rb
index c66f64828..78b3ddb05 100644
--- a/spec/lib/fast_ip_map_spec.rb
+++ b/spec/lib/fast_ip_map_spec.rb
@@ -4,7 +4,7 @@ require 'rails_helper'
 
 describe FastIpMap do
   describe '#include?' do
-    subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')])}
+    subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')]) }
 
     it 'returns true for an exact match' do
       expect(subject.include?(IPAddr.new('189.45.86.3'))).to be true
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index f2ab2570d..d1e0d60e0 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe FeedManager do
@@ -304,7 +306,7 @@ RSpec.describe FeedManager do
       status = Fabricate(:status, reblog: reblog)
       FeedManager.instance.push_to_home(account, status)
 
-      expect(FeedManager.instance.push_to_home(account, reblog)).to eq false
+      expect(FeedManager.instance.push_to_home(account, reblog)).to be false
     end
   end
 
@@ -329,7 +331,7 @@ RSpec.describe FeedManager do
       status = Fabricate(:status, reblog: reblog)
       FeedManager.instance.push_to_list(list, status)
 
-      expect(FeedManager.instance.push_to_list(list, reblog)).to eq false
+      expect(FeedManager.instance.push_to_list(list, reblog)).to be false
     end
 
     context 'when replies policy is set to no replies' do
@@ -339,19 +341,19 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(FeedManager.instance.push_to_list(list, status)).to eq true
+        expect(FeedManager.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
         status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
 
       it 'does not push replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq false
+        expect(FeedManager.instance.push_to_list(list, reply)).to be false
       end
     end
 
@@ -362,25 +364,25 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(FeedManager.instance.push_to_list(list, status)).to eq true
+        expect(FeedManager.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
         status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
 
       it 'does not push replies to someone not a member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: eve)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq false
+        expect(FeedManager.instance.push_to_list(list, reply)).to be false
       end
     end
 
@@ -391,25 +393,25 @@ RSpec.describe FeedManager do
 
       it 'pushes statuses that are not replies' do
         status = Fabricate(:status, text: 'Hello world', account: bob)
-        expect(FeedManager.instance.push_to_list(list, status)).to eq true
+        expect(FeedManager.instance.push_to_list(list, status)).to be true
       end
 
       it 'pushes statuses that are replies to list owner' do
         status = Fabricate(:status, text: 'Hello world', account: owner)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to another member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: alice)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
 
       it 'pushes replies to someone not a member of the list' do
         status = Fabricate(:status, text: 'Hello world', account: eve)
         reply  = Fabricate(:status, text: 'Nay', thread: status, account: bob)
-        expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+        expect(FeedManager.instance.push_to_list(list, reply)).to be true
       end
     end
   end
@@ -423,7 +425,7 @@ RSpec.describe FeedManager do
 
       FeedManager.instance.merge_into_home(account, reblog.account)
 
-      expect(redis.zscore("feed:home:0", reblog.id)).to eq nil
+      expect(redis.zscore('feed:home:0', reblog.id)).to be_nil
     end
   end
 
diff --git a/spec/lib/html_aware_formatter_spec.rb b/spec/lib/html_aware_formatter_spec.rb
index 18d23abf5..315035957 100644
--- a/spec/lib/html_aware_formatter_spec.rb
+++ b/spec/lib/html_aware_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe HtmlAwareFormatter do
@@ -9,7 +11,7 @@ RSpec.describe HtmlAwareFormatter do
       let(:text) { 'Foo bar' }
 
       it 'returns formatted text' do
-        is_expected.to eq '<p>Foo bar</p>'
+        expect(subject).to eq '<p>Foo bar</p>'
       end
     end
 
@@ -20,7 +22,7 @@ RSpec.describe HtmlAwareFormatter do
         let(:text) { 'Beep boop' }
 
         it 'keeps the plain text' do
-          is_expected.to include 'Beep boop'
+          expect(subject).to include 'Beep boop'
         end
       end
 
@@ -28,7 +30,7 @@ RSpec.describe HtmlAwareFormatter do
         let(:text) { '<script>alert("Hello")</script>' }
 
         it 'strips the scripts' do
-          is_expected.to_not include '<script>alert("Hello")</script>'
+          expect(subject).to_not include '<script>alert("Hello")</script>'
         end
       end
 
@@ -36,7 +38,7 @@ RSpec.describe HtmlAwareFormatter do
         let(:text) { '<span class="mention  status__content__spoiler-link">Show more</span>' }
 
         it 'strips the malicious classes' do
-          is_expected.to_not include 'status__content__spoiler-link'
+          expect(subject).to_not include 'status__content__spoiler-link'
         end
       end
     end
diff --git a/spec/lib/importer/accounts_index_importer_spec.rb b/spec/lib/importer/accounts_index_importer_spec.rb
new file mode 100644
index 000000000..73f9bce39
--- /dev/null
+++ b/spec/lib/importer/accounts_index_importer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Importer::AccountsIndexImporter do
+  describe 'import!' do
+    let(:pool) { Concurrent::FixedThreadPool.new(5) }
+    let(:importer) { described_class.new(batch_size: 123, executor: pool) }
+
+    before { Fabricate(:account) }
+
+    it 'indexes relevant accounts' do
+      expect { importer.import! }.to update_index(AccountsIndex)
+    end
+  end
+end
diff --git a/spec/lib/importer/base_importer_spec.rb b/spec/lib/importer/base_importer_spec.rb
new file mode 100644
index 000000000..78e9a869b
--- /dev/null
+++ b/spec/lib/importer/base_importer_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Importer::BaseImporter do
+  describe 'import!' do
+    let(:pool) { Concurrent::FixedThreadPool.new(5) }
+    let(:importer) { described_class.new(batch_size: 123, executor: pool) }
+
+    it 'raises an error' do
+      expect { importer.import! }.to raise_error(NotImplementedError)
+    end
+  end
+end
diff --git a/spec/lib/importer/statuses_index_importer_spec.rb b/spec/lib/importer/statuses_index_importer_spec.rb
new file mode 100644
index 000000000..d5e1c9f2c
--- /dev/null
+++ b/spec/lib/importer/statuses_index_importer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Importer::StatusesIndexImporter do
+  describe 'import!' do
+    let(:pool) { Concurrent::FixedThreadPool.new(5) }
+    let(:importer) { described_class.new(batch_size: 123, executor: pool) }
+
+    before { Fabricate(:status) }
+
+    it 'indexes relevant statuses' do
+      expect { importer.import! }.to update_index(StatusesIndex)
+    end
+  end
+end
diff --git a/spec/lib/importer/tags_index_importer_spec.rb b/spec/lib/importer/tags_index_importer_spec.rb
new file mode 100644
index 000000000..348990c01
--- /dev/null
+++ b/spec/lib/importer/tags_index_importer_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Importer::TagsIndexImporter do
+  describe 'import!' do
+    let(:pool) { Concurrent::FixedThreadPool.new(5) }
+    let(:importer) { described_class.new(batch_size: 123, executor: pool) }
+
+    before { Fabricate(:tag) }
+
+    it 'indexes relevant tags' do
+      expect { importer.import! }.to update_index(TagsIndex)
+    end
+  end
+end
diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb
index 7ea867c61..a46dd743a 100644
--- a/spec/lib/link_details_extractor_spec.rb
+++ b/spec/lib/link_details_extractor_spec.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe LinkDetailsExtractor do
+  subject { described_class.new(original_url, html, html_charset) }
+
   let(:original_url) { '' }
   let(:html) { '' }
   let(:html_charset) { nil }
 
-  subject { described_class.new(original_url, html, html_charset) }
-
   describe '#canonical_url' do
     let(:original_url) { 'https://foo.com/article?bar=baz123' }
 
@@ -39,17 +41,17 @@ RSpec.describe LinkDetailsExtractor do
     let(:original_url) { 'https://example.com/page.html' }
 
     context 'and is wrapped in CDATA tags' do
-      let(:html) { <<-HTML }
-<!doctype html>
-<html>
-<head>
-  <script type="application/ld+json">
-  //<![CDATA[
-  {"@context":"http://schema.org","@type":"NewsArticle","mainEntityOfPage":"https://example.com/page.html","headline":"Foo","datePublished":"2022-01-31T19:53:00+00:00","url":"https://example.com/page.html","description":"Bar","author":{"@type":"Person","name":"Hoge"},"publisher":{"@type":"Organization","name":"Baz"}}
-  //]]>
-  </script>
-</head>
-</html>
+      let(:html) { <<~HTML }
+        <!doctype html>
+        <html>
+        <head>
+          <script type="application/ld+json">
+          //<![CDATA[
+          {"@context":"http://schema.org","@type":"NewsArticle","mainEntityOfPage":"https://example.com/page.html","headline":"Foo","datePublished":"2022-01-31T19:53:00+00:00","url":"https://example.com/page.html","description":"Bar","author":{"@type":"Person","name":"Hoge"},"publisher":{"@type":"Organization","name":"Baz"}}
+          //]]>
+          </script>
+        </head>
+        </html>
       HTML
 
       describe '#title' do
@@ -78,57 +80,57 @@ RSpec.describe LinkDetailsExtractor do
     end
 
     context 'but the first tag is invalid JSON' do
-      let(:html) { <<-HTML }
-<!doctype html>
-<html>
-<body>
-  <script type="application/ld+json">
-    {
-      "@context":"https://schema.org",
-      "@type":"ItemList",
-      "url":"https://example.com/page.html",
-      "name":"Foo",
-      "description":"Bar"
-    },
-    {
-      "@context": "https://schema.org",
-      "@type": "BreadcrumbList",
-      "itemListElement":[
-        {
-          "@type":"ListItem",
-          "position":1,
-          "item":{
-            "@id":"https://www.example.com",
-            "name":"Baz"
-          }
-        }
-      ]
-    }
-  </script>
-  <script type="application/ld+json">
-    {
-      "@context":"https://schema.org",
-      "@type":"NewsArticle",
-      "mainEntityOfPage": {
-        "@type":"WebPage",
-        "@id": "http://example.com/page.html"
-      },
-      "headline": "Foo",
-      "description": "Bar",
-      "datePublished": "2022-01-31T19:46:00+00:00",
-      "author": {
-        "@type": "Organization",
-        "name": "Hoge"
-      },
-      "publisher": {
-        "@type": "NewsMediaOrganization",
-        "name":"Baz",
-        "url":"https://example.com/"
-      }
-    }
-  </script>
-</body>
-</html>
+      let(:html) { <<~HTML }
+        <!doctype html>
+        <html>
+        <body>
+          <script type="application/ld+json">
+            {
+              "@context":"https://schema.org",
+              "@type":"ItemList",
+              "url":"https://example.com/page.html",
+              "name":"Foo",
+              "description":"Bar"
+            },
+            {
+              "@context": "https://schema.org",
+              "@type": "BreadcrumbList",
+              "itemListElement":[
+                {
+                  "@type":"ListItem",
+                  "position":1,
+                  "item":{
+                    "@id":"https://www.example.com",
+                    "name":"Baz"
+                  }
+                }
+              ]
+            }
+          </script>
+          <script type="application/ld+json">
+            {
+              "@context":"https://schema.org",
+              "@type":"NewsArticle",
+              "mainEntityOfPage": {
+                "@type":"WebPage",
+                "@id": "http://example.com/page.html"
+              },
+              "headline": "Foo",
+              "description": "Bar",
+              "datePublished": "2022-01-31T19:46:00+00:00",
+              "author": {
+                "@type": "Organization",
+                "name": "Hoge"
+              },
+              "publisher": {
+                "@type": "NewsMediaOrganization",
+                "name":"Baz",
+                "url":"https://example.com/"
+              }
+            }
+          </script>
+        </body>
+        </html>
       HTML
 
       describe '#title' do
diff --git a/spec/lib/mastodon/cli_spec.rb b/spec/lib/mastodon/cli_spec.rb
new file mode 100644
index 000000000..419f8b864
--- /dev/null
+++ b/spec/lib/mastodon/cli_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'cli'
+
+describe Mastodon::CLI do
+  describe 'version' do
+    it 'returns the Mastodon version' do
+      expect { described_class.new.invoke(:version) }.to output(
+        a_string_including(Mastodon::Version.to_s)
+      ).to_stdout
+    end
+  end
+end
diff --git a/spec/lib/ostatus/tag_manager_spec.rb b/spec/lib/ostatus/tag_manager_spec.rb
index 31195bae2..8104a7e79 100644
--- a/spec/lib/ostatus/tag_manager_spec.rb
+++ b/spec/lib/ostatus/tag_manager_spec.rb
@@ -15,15 +15,15 @@ describe OStatus::TagManager do
     end
 
     it 'returns nil if it is not local id' do
-      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to be_nil
     end
 
     it 'returns nil if it is not expected type' do
-      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to be_nil
     end
 
     it 'returns nil if it does not have object ID' do
-      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to be_nil
     end
   end
 
@@ -45,7 +45,7 @@ describe OStatus::TagManager do
 
       it 'returns the unique tag for status' do
         expect(target.object_type).to eq :comment
-        is_expected.to eq target.uri
+        expect(subject).to eq target.uri
       end
     end
 
@@ -54,7 +54,7 @@ describe OStatus::TagManager do
 
       it 'returns the unique tag for status' do
         expect(target.object_type).to eq :note
-        is_expected.to eq target.uri
+        expect(subject).to eq target.uri
       end
     end
 
@@ -63,7 +63,7 @@ describe OStatus::TagManager do
 
       it 'returns the URL for account' do
         expect(target.object_type).to eq :person
-        is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice'
+        expect(subject).to eq 'https://cb6e6126.ngrok.io/users/alice'
       end
     end
   end
diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb
index c3d0ee630..80b3c331a 100644
--- a/spec/lib/plain_text_formatter_spec.rb
+++ b/spec/lib/plain_text_formatter_spec.rb
@@ -1,23 +1,76 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe PlainTextFormatter do
   describe '#to_s' do
     subject { described_class.new(status.text, status.local?).to_s }
 
-    context 'given a post with local status' do
+    context 'when status is local' do
       let(:status) { Fabricate(:status, text: '<p>a text by a nerd who uses an HTML tag in text</p>', uri: nil) }
 
       it 'returns the raw text' do
-        is_expected.to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
+        expect(subject).to eq '<p>a text by a nerd who uses an HTML tag in text</p>'
       end
     end
 
-    context 'given a post with remote status' do
+    context 'when status is remote' do
       let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
-      let(:status) { Fabricate(:status, account: remote_account, text: '<p>Hello</p><script>alert("Hello")</script>') }
 
-      it 'returns tag-stripped text' do
-        is_expected.to eq 'Hello'
+      context 'when text contains inline HTML tags' do
+        let(:status) { Fabricate(:status, account: remote_account, text: '<b>Lorem</b> <em>ipsum</em>') }
+
+        it 'strips the tags' do
+          expect(subject).to eq 'Lorem ipsum'
+        end
+      end
+
+      context 'when text contains <p> tags' do
+        let(:status) { Fabricate(:status, account: remote_account, text: '<p>Lorem</p><p>ipsum</p>') }
+
+        it 'inserts a newline' do
+          expect(subject).to eq "Lorem\nipsum"
+        end
+      end
+
+      context 'when text contains a single <br> tag' do
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem<br>ipsum') }
+
+        it 'inserts a newline' do
+          expect(subject).to eq "Lorem\nipsum"
+        end
+      end
+
+      context 'when text contains consecutive <br> tag' do
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem<br><br><br>ipsum') }
+
+        it 'inserts a single newline' do
+          expect(subject).to eq "Lorem\nipsum"
+        end
+      end
+
+      context 'when text contains HTML entity' do
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem &amp; ipsum &#x2764;') }
+
+        it 'unescapes the entity' do
+          expect(subject).to eq 'Lorem & ipsum ❤'
+        end
+      end
+
+      context 'when text contains <script> tag' do
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem <script> alert("Booh!") </script>ipsum') }
+
+        it 'strips the tag and its contents' do
+          expect(subject).to eq 'Lorem ipsum'
+        end
+      end
+
+      context 'when text contains an HTML comment tags' do
+        let(:status) { Fabricate(:status, account: remote_account, text: 'Lorem <!-- Booh! -->ipsum') }
+
+        it 'strips the comment' do
+          expect(subject).to eq 'Lorem ipsum'
+        end
       end
     end
   end
diff --git a/spec/lib/request_pool_spec.rb b/spec/lib/request_pool_spec.rb
index 4a144d7c7..63dc9c5dd 100644
--- a/spec/lib/request_pool_spec.rb
+++ b/spec/lib/request_pool_spec.rb
@@ -33,7 +33,7 @@ describe RequestPool do
 
       subject
 
-      threads = 20.times.map do |i|
+      threads = 20.times.map do |_i|
         Thread.new do
           20.times do
             subject.with('http://example.com') do |http_client|
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index 8539944e2..25fe9ed37 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -43,7 +43,7 @@ describe Request do
       before { stub_request(:get, 'http://example.com') }
 
       it 'executes a HTTP request' do
-        expect { |block| subject.perform &block }.to yield_control
+        expect { |block| subject.perform(&block) }.to yield_control
         expect(a_request(:get, 'http://example.com')).to have_been_made.once
       end
 
@@ -54,18 +54,18 @@ describe Request do
         allow(resolver).to receive(:timeouts=).and_return(nil)
         allow(Resolv::DNS).to receive(:open).and_yield(resolver)
 
-        expect { |block| subject.perform &block }.to yield_control
+        expect { |block| subject.perform(&block) }.to yield_control
         expect(a_request(:get, 'http://example.com')).to have_been_made.once
       end
 
       it 'sets headers' do
-        expect { |block| subject.perform &block }.to yield_control
+        expect { |block| subject.perform(&block) }.to yield_control
         expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
       end
 
       it 'closes underlying connection' do
         expect_any_instance_of(HTTP::Client).to receive(:close)
-        expect { |block| subject.perform &block }.to yield_control
+        expect { |block| subject.perform(&block) }.to yield_control
       end
 
       it 'returns response which implements body_with_limit' do
@@ -97,12 +97,12 @@ describe Request do
   describe "response's body_with_limit method" do
     it 'rejects body more than 1 megabyte by default' do
       stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes))
-      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
+      expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
     end
 
     it 'accepts body less than 1 megabyte by default' do
       stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes))
-      expect { subject.perform { |response| response.body_with_limit } }.not_to raise_error
+      expect { subject.perform(&:body_with_limit) }.to_not raise_error
     end
 
     it 'rejects body by given size' do
@@ -112,12 +112,12 @@ describe Request do
 
     it 'rejects too large chunked body' do
       stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' })
-      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
+      expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
     end
 
     it 'rejects too large monolithic body' do
       stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes })
-      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
+      expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
     end
 
     it 'truncates large monolithic body' do
diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb
new file mode 100644
index 000000000..109533469
--- /dev/null
+++ b/spec/lib/search_query_transformer_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe SearchQueryTransformer do
+  describe 'initialization' do
+    let(:parser) { SearchQueryParser.new.parse('query') }
+
+    it 'sets attributes' do
+      transformer = described_class.new.apply(parser)
+
+      expect(transformer.should_clauses.first).to be_a(SearchQueryTransformer::TermClause)
+      expect(transformer.must_clauses.first).to be_nil
+      expect(transformer.must_not_clauses.first).to be_nil
+      expect(transformer.filter_clauses.first).to be_nil
+    end
+  end
+end
diff --git a/spec/lib/settings/extend_spec.rb b/spec/lib/settings/extend_spec.rb
deleted file mode 100644
index 83ced4230..000000000
--- a/spec/lib/settings/extend_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Settings::Extend do
-  class User
-    include Settings::Extend
-  end
-
-  describe '#settings' do
-    it 'sets @settings as an instance of Settings::ScopedSettings' do
-      user = Fabricate(:user)
-      expect(user.settings).to be_kind_of Settings::ScopedSettings
-    end
-  end
-end
diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb
deleted file mode 100644
index 7566685b4..000000000
--- a/spec/lib/settings/scoped_settings_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Settings::ScopedSettings do
-  let(:object)         { Fabricate(:user) }
-  let(:scoped_setting) { described_class.new(object) }
-  let(:val)            { 'whatever' }
-  let(:methods)        { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) }
-
-  describe '.initialize' do
-    it 'sets @object' do
-      scoped_setting = described_class.new(object)
-      expect(scoped_setting.instance_variable_get(:@object)).to be object
-    end
-  end
-
-  describe '#method_missing' do
-    it 'sets scoped_setting.method_name = val' do
-      methods.each do |key|
-        scoped_setting.send("#{key}=", val)
-        expect(scoped_setting.send(key)).to eq val
-      end
-    end
-  end
-
-  describe '#[]= and #[]' do
-    it 'sets [key] = val' do
-      methods.each do |key|
-        scoped_setting[key] = val
-        expect(scoped_setting[key]).to eq val
-      end
-    end
-  end
-end
diff --git a/spec/lib/status_filter_spec.rb b/spec/lib/status_filter_spec.rb
index a851014d9..08519bc59 100644
--- a/spec/lib/status_filter_spec.rb
+++ b/spec/lib/status_filter_spec.rb
@@ -10,7 +10,7 @@ describe StatusFilter do
       subject { described_class.new(status, nil) }
 
       context 'when there are no connections' do
-        it { is_expected.not_to be_filtered }
+        it { is_expected.to_not be_filtered }
       end
 
       context 'when status account is silenced' do
@@ -31,11 +31,12 @@ describe StatusFilter do
     end
 
     context 'with real account' do
-      let(:account) { Fabricate(:account) }
       subject { described_class.new(status, account) }
 
+      let(:account) { Fabricate(:account) }
+
       context 'when there are no connections' do
-        it { is_expected.not_to be_filtered }
+        it { is_expected.to_not be_filtered }
       end
 
       context 'when status account is blocked' do
diff --git a/spec/lib/status_reach_finder_spec.rb b/spec/lib/status_reach_finder_spec.rb
index f0c22b165..785ce28a0 100644
--- a/spec/lib/status_reach_finder_spec.rb
+++ b/spec/lib/status_reach_finder_spec.rb
@@ -5,13 +5,13 @@ require 'rails_helper'
 describe StatusReachFinder do
   describe '#inboxes' do
     context 'for a local status' do
+      subject { described_class.new(status) }
+
       let(:parent_status) { nil }
       let(:visibility) { :public }
       let(:alice) { Fabricate(:account, username: 'alice') }
       let(:status) { Fabricate(:status, account: alice, thread: parent_status, visibility: visibility) }
 
-      subject { described_class.new(status) }
-
       context 'when it contains mentions of remote accounts' do
         let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
 
diff --git a/spec/lib/suspicious_sign_in_detector_spec.rb b/spec/lib/suspicious_sign_in_detector_spec.rb
index 101a18aa0..c61b1ef1e 100644
--- a/spec/lib/suspicious_sign_in_detector_spec.rb
+++ b/spec/lib/suspicious_sign_in_detector_spec.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SuspiciousSignInDetector do
   describe '#suspicious?' do
+    subject { described_class.new(user).suspicious?(request) }
+
     let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) }
     let(:request) { double(remote_ip: remote_ip) }
     let(:remote_ip) { nil }
 
-    subject { described_class.new(user).suspicious?(request) }
-
     context 'when user has 2FA enabled' do
       before do
         user.update!(otp_required_for_login: true)
diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb
index cd9fb936c..8de290541 100644
--- a/spec/lib/tag_manager_spec.rb
+++ b/spec/lib/tag_manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe TagManager do
@@ -14,15 +16,15 @@ RSpec.describe TagManager do
     end
 
     it 'returns true for nil' do
-      expect(TagManager.instance.local_domain?(nil)).to eq true
+      expect(TagManager.instance.local_domain?(nil)).to be true
     end
 
     it 'returns true if the slash-stripped string equals to local domain' do
-      expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to eq true
+      expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to be true
     end
 
     it 'returns false for irrelevant string' do
-      expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to eq false
+      expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to be false
     end
   end
 
@@ -39,21 +41,21 @@ RSpec.describe TagManager do
     end
 
     it 'returns true for nil' do
-      expect(TagManager.instance.web_domain?(nil)).to eq true
+      expect(TagManager.instance.web_domain?(nil)).to be true
     end
 
     it 'returns true if the slash-stripped string equals to web domain' do
-      expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to eq true
+      expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to be true
     end
 
     it 'returns false for string with irrelevant characters' do
-      expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to eq false
+      expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to be false
     end
   end
 
   describe '#normalize_domain' do
     it 'returns nil if the given parameter is nil' do
-      expect(TagManager.instance.normalize_domain(nil)).to eq nil
+      expect(TagManager.instance.normalize_domain(nil)).to be_nil
     end
 
     it 'returns normalized domain' do
@@ -70,17 +72,17 @@ RSpec.describe TagManager do
 
     it 'returns true if the normalized string with port is local URL' do
       Rails.configuration.x.web_domain = 'domain.example.com:42'
-      expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to eq true
+      expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to be true
     end
 
     it 'returns true if the normalized string without port is local URL' do
       Rails.configuration.x.web_domain = 'domain.example.com'
-      expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to eq true
+      expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to be true
     end
 
     it 'returns false for string with irrelevant characters' do
       Rails.configuration.x.web_domain = 'domain.example.com'
-      expect(TagManager.instance.local_url?('https://domain.example.net/')).to eq false
+      expect(TagManager.instance.local_url?('https://domain.example.net/')).to be false
     end
   end
 end
diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb
index 52a9d2498..3417b450c 100644
--- a/spec/lib/text_formatter_spec.rb
+++ b/spec/lib/text_formatter_spec.rb
@@ -1,16 +1,18 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe TextFormatter do
   describe '#to_s' do
-    let(:preloaded_accounts) { nil }
-
     subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s }
 
+    let(:preloaded_accounts) { nil }
+
     context 'given text containing plain text' do
       let(:text) { 'text' }
 
       it 'paragraphizes the text' do
-        is_expected.to eq '<p>text</p>'
+        expect(subject).to eq '<p>text</p>'
       end
     end
 
@@ -18,7 +20,7 @@ RSpec.describe TextFormatter do
       let(:text) { "line\nfeed" }
 
       it 'removes line feeds' do
-        is_expected.not_to include "\n"
+        expect(subject).to_not include "\n"
       end
     end
 
@@ -27,7 +29,7 @@ RSpec.describe TextFormatter do
       let(:text) { '@alice' }
 
       it 'creates a mention link' do
-        is_expected.to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
+        expect(subject).to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>'
       end
     end
 
@@ -36,7 +38,7 @@ RSpec.describe TextFormatter do
       let(:text) { '@alice' }
 
       it 'does not create a mention link' do
-        is_expected.to include '@alice'
+        expect(subject).to include '@alice'
       end
     end
 
@@ -44,7 +46,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
+        expect(subject).to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
       end
     end
 
@@ -52,7 +54,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://google.com' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="http://google.com"'
+        expect(subject).to include 'href="http://google.com"'
       end
     end
 
@@ -60,7 +62,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://example.gay' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="http://example.gay"'
+        expect(subject).to include 'href="http://example.gay"'
       end
     end
 
@@ -68,11 +70,11 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://nic.みんな/' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://nic.みんな/"'
+        expect(subject).to include 'href="https://nic.みんな/"'
       end
 
       it 'has display URL' do
-        is_expected.to include '<span class="">nic.みんな/</span>'
+        expect(subject).to include '<span class="">nic.みんな/</span>'
       end
     end
 
@@ -80,7 +82,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
 
       it 'matches the full URL but not the period' do
-        is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
+        expect(subject).to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
       end
     end
 
@@ -88,7 +90,7 @@ RSpec.describe TextFormatter do
       let(:text) { '(http://google.com/)' }
 
       it 'matches the full URL but not the parentheses' do
-        is_expected.to include 'href="http://google.com/"'
+        expect(subject).to include 'href="http://google.com/"'
       end
     end
 
@@ -96,7 +98,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://www.google.com!' }
 
       it 'matches the full URL but not the exclamation point' do
-        is_expected.to include 'href="http://www.google.com"'
+        expect(subject).to include 'href="http://www.google.com"'
       end
     end
 
@@ -104,7 +106,7 @@ RSpec.describe TextFormatter do
       let(:text) { "http://www.google.com'" }
 
       it 'matches the full URL but not the single quote' do
-        is_expected.to include 'href="http://www.google.com"'
+        expect(subject).to include 'href="http://www.google.com"'
       end
     end
 
@@ -112,7 +114,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://www.google.com>' }
 
       it 'matches the full URL but not the angle bracket' do
-        is_expected.to include 'href="http://www.google.com"'
+        expect(subject).to include 'href="http://www.google.com"'
       end
     end
 
@@ -121,7 +123,7 @@ RSpec.describe TextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;q=autolink"'
         end
       end
 
@@ -129,7 +131,7 @@ RSpec.describe TextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&amp;q=autolink"'
         end
       end
 
@@ -137,7 +139,7 @@ RSpec.describe TextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
 
         it 'matches the full URL' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
         end
       end
 
@@ -145,7 +147,7 @@ RSpec.describe TextFormatter do
         let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
 
         it 'preserves escaped unicode characters' do
-          is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
+          expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&amp;utf81=✓&amp;q=autolink"'
         end
       end
     end
@@ -154,7 +156,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
+        expect(subject).to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
       end
     end
 
@@ -162,7 +164,7 @@ RSpec.describe TextFormatter do
       let(:text) { '"https://example.com/"' }
 
       it 'does not match the quotation marks' do
-        is_expected.to include 'href="https://example.com/"'
+        expect(subject).to include 'href="https://example.com/"'
       end
     end
 
@@ -170,7 +172,7 @@ RSpec.describe TextFormatter do
       let(:text) { '<https://example.com/>' }
 
       it 'does not match the angle brackets' do
-        is_expected.to include 'href="https://example.com/"'
+        expect(subject).to include 'href="https://example.com/"'
       end
     end
 
@@ -178,7 +180,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
+        expect(subject).to include 'href="https://ja.wikipedia.org/wiki/日本"'
       end
     end
 
@@ -186,7 +188,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
+        expect(subject).to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
       end
     end
 
@@ -194,7 +196,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://example.com/ abc123' }
 
       it 'does not match the full-width space' do
-        is_expected.to include 'href="https://example.com/"'
+        expect(subject).to include 'href="https://example.com/"'
       end
     end
 
@@ -202,7 +204,7 @@ RSpec.describe TextFormatter do
       let(:text) { '「[https://example.org/」' }
 
       it 'does not match the quotation marks' do
-        is_expected.to include 'href="https://example.org/"'
+        expect(subject).to include 'href="https://example.org/"'
       end
     end
 
@@ -210,7 +212,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
+        expect(subject).to include 'href="https://baike.baidu.com/item/中华人民共和国"'
       end
     end
 
@@ -218,31 +220,31 @@ RSpec.describe TextFormatter do
       let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
 
       it 'matches the full URL' do
-        is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
+        expect(subject).to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
       end
     end
 
     context 'given a URL containing unsafe code (XSS attack, visible part)' do
-      let(:text) { %q{http://example.com/b<del>b</del>} }
+      let(:text) { 'http://example.com/b<del>b</del>' }
 
       it 'does not include the HTML in the URL' do
-        is_expected.to include '"http://example.com/b"'
+        expect(subject).to include '"http://example.com/b"'
       end
 
       it 'escapes the HTML' do
-        is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
+        expect(subject).to include '&lt;del&gt;b&lt;/del&gt;'
       end
     end
 
     context 'given a URL containing unsafe code (XSS attack, invisible part)' do
-      let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
+      let(:text) { 'http://example.com/blahblahblahblah/a<script>alert("Hello")</script>' }
 
       it 'does not include the HTML in the URL' do
-        is_expected.to include '"http://example.com/blahblahblahblah/a"'
+        expect(subject).to include '"http://example.com/blahblahblahblah/a"'
       end
 
       it 'escapes the HTML' do
-        is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
+        expect(subject).to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
       end
     end
 
@@ -250,7 +252,7 @@ RSpec.describe TextFormatter do
       let(:text) { '<script>alert("Hello")</script>' }
 
       it 'escapes the HTML' do
-        is_expected.to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
+        expect(subject).to include '<p>&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;</p>'
       end
     end
 
@@ -258,7 +260,7 @@ RSpec.describe TextFormatter do
       let(:text) { %q{<img src="javascript:alert('XSS');">} }
 
       it 'escapes the HTML' do
-        is_expected.to include '<p>&lt;img src=&quot;javascript:alert(&#39;XSS&#39;);&quot;&gt;</p>'
+        expect(subject).to include '<p>&lt;img src=&quot;javascript:alert(&#39;XSS&#39;);&quot;&gt;</p>'
       end
     end
 
@@ -266,7 +268,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'http://www\.google\.com' }
 
       it 'outputs the raw URL' do
-        is_expected.to eq '<p>http://www\.google\.com</p>'
+        expect(subject).to eq '<p>http://www\.google\.com</p>'
       end
     end
 
@@ -274,7 +276,7 @@ RSpec.describe TextFormatter do
       let(:text)  { '#hashtag' }
 
       it 'creates a hashtag link' do
-        is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
+        expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>'
       end
     end
 
@@ -282,7 +284,7 @@ RSpec.describe TextFormatter do
       let(:text)  { '#hashtagタグ' }
 
       it 'creates a hashtag link' do
-        is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
+        expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>'
       end
     end
 
@@ -290,7 +292,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'xmpp:user@instance.com' }
 
       it 'matches the full URI' do
-        is_expected.to include 'href="xmpp:user@instance.com"'
+        expect(subject).to include 'href="xmpp:user@instance.com"'
       end
     end
 
@@ -298,7 +300,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'please join xmpp:muc@instance.com?join right now' }
 
       it 'matches the full URI' do
-        is_expected.to include 'href="xmpp:muc@instance.com?join"'
+        expect(subject).to include 'href="xmpp:muc@instance.com?join"'
       end
     end
 
@@ -306,7 +308,7 @@ RSpec.describe TextFormatter do
       let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
 
       it 'matches the full URI' do
-        is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
+        expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
       end
     end
   end
diff --git a/spec/lib/translation_service/deepl_spec.rb b/spec/lib/translation_service/deepl_spec.rb
new file mode 100644
index 000000000..2363f8f13
--- /dev/null
+++ b/spec/lib/translation_service/deepl_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe TranslationService::DeepL do
+  subject(:service) { described_class.new(plan, 'my-api-key') }
+
+  let(:plan) { 'advanced' }
+
+  before do
+    stub_request(:get, 'https://api.deepl.com/v2/languages?type=source').to_return(
+      body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
+    )
+    stub_request(:get, 'https://api.deepl.com/v2/languages?type=target').to_return(
+      body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
+    )
+  end
+
+  describe '#translate' do
+    it 'returns translation with specified source language' do
+      stub_request(:post, 'https://api.deepl.com/v2/translate')
+        .with(body: 'text=Hasta+la+vista&source_lang=ES&target_lang=en&tag_handling=html')
+        .to_return(body: '{"translations":[{"detected_source_language":"ES","text":"See you soon"}]}')
+
+      translation = service.translate('Hasta la vista', 'es', 'en')
+      expect(translation.detected_source_language).to eq 'es'
+      expect(translation.provider).to eq 'DeepL.com'
+      expect(translation.text).to eq 'See you soon'
+    end
+
+    it 'returns translation with auto-detected source language' do
+      stub_request(:post, 'https://api.deepl.com/v2/translate')
+        .with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html')
+        .to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good Morning"}]}')
+
+      translation = service.translate('Guten Tag', nil, 'en')
+      expect(translation.detected_source_language).to eq 'de'
+      expect(translation.provider).to eq 'DeepL.com'
+      expect(translation.text).to eq 'Good Morning'
+    end
+  end
+
+  describe '#languages' do
+    it 'returns source languages' do
+      expect(service.languages.keys).to eq [nil, 'en', 'uk']
+    end
+
+    it 'returns target languages for each source language' do
+      expect(service.languages['en']).to eq %w(pt en-GB zh)
+      expect(service.languages['uk']).to eq %w(en pt en-GB zh)
+    end
+
+    it 'returns target languages for auto-detection' do
+      expect(service.languages[nil]).to eq %w(en pt en-GB zh)
+    end
+  end
+
+  describe '#request' do
+    before do
+      stub_request(:any, //)
+      # rubocop:disable Lint/EmptyBlock
+      service.send(:request, :get, '/v2/languages') { |res| }
+      # rubocop:enable Lint/EmptyBlock
+    end
+
+    it 'uses paid plan base URL' do
+      expect(a_request(:get, 'https://api.deepl.com/v2/languages')).to have_been_made.once
+    end
+
+    context 'with free plan' do
+      let(:plan) { 'free' }
+
+      it 'uses free plan base URL' do
+        expect(a_request(:get, 'https://api-free.deepl.com/v2/languages')).to have_been_made.once
+      end
+    end
+
+    it 'sends API key' do
+      expect(a_request(:get, 'https://api.deepl.com/v2/languages').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
+    end
+  end
+end
diff --git a/spec/lib/translation_service/libre_translate_spec.rb b/spec/lib/translation_service/libre_translate_spec.rb
new file mode 100644
index 000000000..fbd726a7e
--- /dev/null
+++ b/spec/lib/translation_service/libre_translate_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe TranslationService::LibreTranslate do
+  subject(:service) { described_class.new('https://libretranslate.example.com', 'my-api-key') }
+
+  before do
+    stub_request(:get, 'https://libretranslate.example.com/languages').to_return(
+      body: '[{"code": "en","name": "English","targets": ["de","en","es"]},{"code": "da","name": "Danish","targets": ["en","pt"]}]'
+    )
+  end
+
+  describe '#languages' do
+    subject(:languages) { service.languages }
+
+    it 'returns source languages' do
+      expect(languages.keys).to eq ['en', 'da', nil]
+    end
+
+    it 'returns target languages for each source language' do
+      expect(languages['en']).to eq %w(de es)
+      expect(languages['da']).to eq %w(en pt)
+    end
+
+    it 'returns target languages for auto-detected language' do
+      expect(languages[nil]).to eq %w(de en es pt)
+    end
+  end
+
+  describe '#translate' do
+    it 'returns translation with specified source language' do
+      stub_request(:post, 'https://libretranslate.example.com/translate')
+        .with(body: '{"q":"Hasta la vista","source":"es","target":"en","format":"html","api_key":"my-api-key"}')
+        .to_return(body: '{"translatedText": "See you"}')
+
+      translation = service.translate('Hasta la vista', 'es', 'en')
+      expect(translation.detected_source_language).to eq 'es'
+      expect(translation.provider).to eq 'LibreTranslate'
+      expect(translation.text).to eq 'See you'
+    end
+
+    it 'returns translation with auto-detected source language' do
+      stub_request(:post, 'https://libretranslate.example.com/translate')
+        .with(body: '{"q":"Guten Morgen","source":"auto","target":"en","format":"html","api_key":"my-api-key"}')
+        .to_return(body: '{"detectedLanguage":{"confidence":92,"language":"de"},"translatedText":"Good morning"}')
+
+      translation = service.translate('Guten Morgen', nil, 'en')
+      expect(translation.detected_source_language).to be_nil
+      expect(translation.provider).to eq 'LibreTranslate'
+      expect(translation.text).to eq 'Good morning'
+    end
+  end
+end
diff --git a/spec/lib/user_settings_decorator_spec.rb b/spec/lib/user_settings_decorator_spec.rb
deleted file mode 100644
index 462c5b124..000000000
--- a/spec/lib/user_settings_decorator_spec.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe UserSettingsDecorator do
-  describe 'update' do
-    let(:user) { Fabricate(:user) }
-    let(:settings) { described_class.new(user) }
-
-    it 'updates the user settings value for email notifications' do
-      values = { 'notification_emails' => { 'follow' => '1' } }
-
-      settings.update(values)
-      expect(user.settings['notification_emails']['follow']).to eq true
-    end
-
-    it 'updates the user settings value for interactions' do
-      values = { 'interactions' => { 'must_be_follower' => '0' } }
-
-      settings.update(values)
-      expect(user.settings['interactions']['must_be_follower']).to eq false
-    end
-
-    it 'updates the user settings value for privacy' do
-      values = { 'setting_default_privacy' => 'public' }
-
-      settings.update(values)
-      expect(user.settings['default_privacy']).to eq 'public'
-    end
-
-    it 'updates the user settings value for sensitive' do
-      values = { 'setting_default_sensitive' => '1' }
-
-      settings.update(values)
-      expect(user.settings['default_sensitive']).to eq true
-    end
-
-    it 'updates the user settings value for unfollow modal' do
-      values = { 'setting_unfollow_modal' => '0' }
-
-      settings.update(values)
-      expect(user.settings['unfollow_modal']).to eq false
-    end
-
-    it 'updates the user settings value for boost modal' do
-      values = { 'setting_boost_modal' => '1' }
-
-      settings.update(values)
-      expect(user.settings['boost_modal']).to eq true
-    end
-
-    it 'updates the user settings value for delete toot modal' do
-      values = { 'setting_delete_modal' => '0' }
-
-      settings.update(values)
-      expect(user.settings['delete_modal']).to eq false
-    end
-
-    it 'updates the user settings value for gif auto play' do
-      values = { 'setting_auto_play_gif' => '0' }
-
-      settings.update(values)
-      expect(user.settings['auto_play_gif']).to eq false
-    end
-
-    it 'updates the user settings value for system font in UI' do
-      values = { 'setting_system_font_ui' => '0' }
-
-      settings.update(values)
-      expect(user.settings['system_font_ui']).to eq false
-    end
-
-    it 'decoerces setting values before applying' do
-      values = {
-        'setting_delete_modal' => 'false',
-        'setting_boost_modal' => 'true',
-      }
-
-      settings.update(values)
-      expect(user.settings['delete_modal']).to eq false
-      expect(user.settings['boost_modal']).to eq true
-    end
-  end
-end
diff --git a/spec/lib/vacuum/access_tokens_vacuum_spec.rb b/spec/lib/vacuum/access_tokens_vacuum_spec.rb
index 0244c3449..6b7234065 100644
--- a/spec/lib/vacuum/access_tokens_vacuum_spec.rb
+++ b/spec/lib/vacuum/access_tokens_vacuum_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::AccessTokensVacuum do
diff --git a/spec/lib/vacuum/backups_vacuum_spec.rb b/spec/lib/vacuum/backups_vacuum_spec.rb
index 4e2de083f..867dbe402 100644
--- a/spec/lib/vacuum/backups_vacuum_spec.rb
+++ b/spec/lib/vacuum/backups_vacuum_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::BackupsVacuum do
-  let(:retention_period) { 7.days }
-
   subject { described_class.new(retention_period) }
 
+  let(:retention_period) { 7.days }
+
   describe '#perform' do
     let!(:expired_backup) { Fabricate(:backup, created_at: (retention_period + 1.day).ago) }
     let!(:current_backup) { Fabricate(:backup) }
diff --git a/spec/lib/vacuum/feeds_vacuum_spec.rb b/spec/lib/vacuum/feeds_vacuum_spec.rb
index 0aec26740..ede1e3c36 100644
--- a/spec/lib/vacuum/feeds_vacuum_spec.rb
+++ b/spec/lib/vacuum/feeds_vacuum_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::FeedsVacuum do
diff --git a/spec/lib/vacuum/media_attachments_vacuum_spec.rb b/spec/lib/vacuum/media_attachments_vacuum_spec.rb
index be8458d9b..3c17ecb00 100644
--- a/spec/lib/vacuum/media_attachments_vacuum_spec.rb
+++ b/spec/lib/vacuum/media_attachments_vacuum_spec.rb
@@ -1,10 +1,11 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::MediaAttachmentsVacuum do
-  let(:retention_period) { 7.days }
-
   subject { described_class.new(retention_period) }
 
+  let(:retention_period) { 7.days }
   let(:remote_status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com')) }
   let(:local_status) { Fabricate(:status) }
 
diff --git a/spec/lib/vacuum/preview_cards_vacuum_spec.rb b/spec/lib/vacuum/preview_cards_vacuum_spec.rb
index 275f9ba92..c1b7f7e9c 100644
--- a/spec/lib/vacuum/preview_cards_vacuum_spec.rb
+++ b/spec/lib/vacuum/preview_cards_vacuum_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::PreviewCardsVacuum do
-  let(:retention_period) { 7.days }
-
   subject { described_class.new(retention_period) }
 
+  let(:retention_period) { 7.days }
+
   describe '#perform' do
     let!(:orphaned_preview_card) { Fabricate(:preview_card, created_at: 2.days.ago) }
     let!(:old_preview_card) { Fabricate(:preview_card, updated_at: (retention_period + 1.day).ago) }
diff --git a/spec/lib/vacuum/statuses_vacuum_spec.rb b/spec/lib/vacuum/statuses_vacuum_spec.rb
index 83f3c5c9f..d5c013950 100644
--- a/spec/lib/vacuum/statuses_vacuum_spec.rb
+++ b/spec/lib/vacuum/statuses_vacuum_spec.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::StatusesVacuum do
+  subject { described_class.new(retention_period) }
+
   let(:retention_period) { 7.days }
 
   let(:remote_account) { Fabricate(:account, domain: 'example.com') }
 
-  subject { described_class.new(retention_period) }
-
   describe '#perform' do
     let!(:remote_status_old) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) }
     let!(:remote_status_recent) { Fabricate(:status, account: remote_account, created_at: (retention_period - 2.days).ago) }
diff --git a/spec/lib/vacuum/system_keys_vacuum_spec.rb b/spec/lib/vacuum/system_keys_vacuum_spec.rb
index 565892f02..84cae3041 100644
--- a/spec/lib/vacuum/system_keys_vacuum_spec.rb
+++ b/spec/lib/vacuum/system_keys_vacuum_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe Vacuum::SystemKeysVacuum do
diff --git a/spec/lib/webfinger_resource_spec.rb b/spec/lib/webfinger_resource_spec.rb
index 5c7f475d6..8ec6dd205 100644
--- a/spec/lib/webfinger_resource_spec.rb
+++ b/spec/lib/webfinger_resource_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 describe WebfingerResource do
@@ -14,9 +16,9 @@ describe WebfingerResource do
       it 'raises with a route whose controller is not AccountsController' do
         resource = 'https://example.com/users/alice/other'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(ActiveRecord::RecordNotFound)
+        end.to raise_error(ActiveRecord::RecordNotFound)
       end
 
       it 'raises with a route whose action is not show' do
@@ -29,17 +31,17 @@ describe WebfingerResource do
 
         expect(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized).at_least(:once)
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(ActiveRecord::RecordNotFound)
+        end.to raise_error(ActiveRecord::RecordNotFound)
       end
 
       it 'raises with a string that doesnt start with URL' do
         resource = 'website for http://example.com/users/alice/other'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(WebfingerResource::InvalidRequest)
+        end.to raise_error(WebfingerResource::InvalidRequest)
       end
 
       it 'finds the username in a valid https route' do
@@ -68,9 +70,9 @@ describe WebfingerResource do
       it 'raises on a non-local domain' do
         resource = 'user@remote-host.com'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(ActiveRecord::RecordNotFound)
+        end.to raise_error(ActiveRecord::RecordNotFound)
       end
 
       it 'finds username for a local domain' do
@@ -94,17 +96,17 @@ describe WebfingerResource do
       it 'raises on a non-local domain' do
         resource = 'acct:user@remote-host.com'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(ActiveRecord::RecordNotFound)
+        end.to raise_error(ActiveRecord::RecordNotFound)
       end
 
       it 'raises on a nonsense domain' do
         resource = 'acct:user@remote-host@remote-hostess.remote.local@remote'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(ActiveRecord::RecordNotFound)
+        end.to raise_error(ActiveRecord::RecordNotFound)
       end
 
       it 'finds the username for a local account if the domain is the local one' do
@@ -128,9 +130,9 @@ describe WebfingerResource do
       it 'raises InvalidRequest' do
         resource = 'df/:dfkj'
 
-        expect {
+        expect do
           WebfingerResource.new(resource).username
-        }.to raise_error(WebfingerResource::InvalidRequest)
+        end.to raise_error(WebfingerResource::InvalidRequest)
       end
     end
   end