about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/accounts_controller_spec.rb3
-rw-r--r--spec/controllers/activitypub/collections_controller_spec.rb23
-rw-r--r--spec/controllers/activitypub/followers_synchronizations_controller_spec.rb4
-rw-r--r--spec/controllers/activitypub/outboxes_controller_spec.rb16
-rw-r--r--spec/controllers/admin/accounts_controller_spec.rb52
-rw-r--r--spec/controllers/admin/change_email_controller_spec.rb12
-rw-r--r--spec/controllers/admin/confirmations_controller_spec.rb10
-rw-r--r--spec/controllers/admin/instances_controller_spec.rb35
-rw-r--r--spec/controllers/admin/report_notes_controller_spec.rb8
-rw-r--r--spec/controllers/admin/reported_statuses_controller_spec.rb59
-rw-r--r--spec/controllers/admin/reports_controller_spec.rb22
-rw-r--r--spec/controllers/admin/resets_controller_spec.rb4
-rw-r--r--spec/controllers/admin/statuses_controller_spec.rb70
-rw-r--r--spec/controllers/admin/tags_controller_spec.rb12
-rw-r--r--spec/controllers/admin/two_factor_authentications_controller_spec.rb8
-rw-r--r--spec/controllers/api/base_controller_spec.rb2
-rw-r--r--spec/controllers/api/proofs_controller_spec.rb93
-rw-r--r--spec/controllers/api/v1/accounts/credentials_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/accounts/lists_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/notes_controller_spec.rb48
-rw-r--r--spec/controllers/api/v1/accounts/pins_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/accounts/relationships_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/accounts/search_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/accounts/statuses_controller_spec.rb37
-rw-r--r--spec/controllers/api/v1/accounts_controller_spec.rb38
-rw-r--r--spec/controllers/api/v1/admin/account_actions_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/admin/accounts_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/admin/reports_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/blocks_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/conversations_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/domain_blocks_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/follow_requests_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/instances_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/lists/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/lists_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/markers_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/media_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/mutes_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/notifications_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/polls/votes_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/polls_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/reports_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/favourites_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/histories_controller_spec.rb29
-rw-r--r--spec/controllers/api/v1/statuses/mutes_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/pins_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/reblogs_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/statuses/sources_controller_spec.rb29
-rw-r--r--spec/controllers/api/v1/statuses_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/timelines/home_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/timelines/list_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/timelines/public_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/timelines/tag_controller_spec.rb2
-rw-r--r--spec/controllers/api/v1/trends/tags_controller_spec.rb22
-rw-r--r--spec/controllers/api/v1/trends_controller_spec.rb18
-rw-r--r--spec/controllers/api/v2/search_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb6
-rw-r--r--spec/controllers/auth/registrations_controller_spec.rb2
-rw-r--r--spec/controllers/auth/sessions_controller_spec.rb149
-rw-r--r--spec/controllers/authorize_interactions_controller_spec.rb3
-rw-r--r--spec/controllers/concerns/account_controller_concern_spec.rb31
-rw-r--r--spec/controllers/concerns/accountable_concern_spec.rb8
-rw-r--r--spec/controllers/follower_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/following_accounts_controller_spec.rb2
-rw-r--r--spec/controllers/home_controller_spec.rb6
-rw-r--r--spec/controllers/media_controller_spec.rb63
-rw-r--r--spec/controllers/settings/deletes_controller_spec.rb8
-rw-r--r--spec/controllers/settings/identity_proofs_controller_spec.rb186
-rw-r--r--spec/controllers/settings/migrations_controller_spec.rb3
-rw-r--r--spec/controllers/settings/profiles_controller_spec.rb14
-rw-r--r--spec/controllers/statuses_cleanup_controller_spec.rb27
-rw-r--r--spec/controllers/well_known/keybase_proof_config_controller_spec.rb15
-rw-r--r--spec/controllers/well_known/webfinger_controller_spec.rb4
-rw-r--r--spec/fabricators/account_fabricator.rb1
-rw-r--r--spec/fabricators/account_identity_proof_fabricator.rb8
-rw-r--r--spec/fabricators/account_statuses_cleanup_policy_fabricator.rb3
-rw-r--r--spec/fabricators/preview_card_fabricator.rb6
-rw-r--r--spec/fabricators/report_fabricator.rb6
-rw-r--r--spec/fabricators/status_edit_fabricator.rb7
-rw-r--r--spec/fabricators/user_fabricator.rb2
-rw-r--r--spec/fixtures/files/boop.oggbin0 -> 11379 bytes
-rw-r--r--spec/fixtures/requests/oembed_youtube.html7
-rw-r--r--spec/fixtures/xml/mastodon.atom261
-rw-r--r--spec/helpers/languages_helper_spec.rb (renamed from spec/helpers/settings_helper_spec.rb)9
-rw-r--r--spec/lib/activitypub/activity/accept_spec.rb5
-rw-r--r--spec/lib/activitypub/activity/add_spec.rb42
-rw-r--r--spec/lib/activitypub/activity/update_spec.rb110
-rw-r--r--spec/lib/activitypub/tag_manager_spec.rb8
-rw-r--r--spec/lib/link_details_extractor_spec.rb29
-rw-r--r--spec/lib/permalink_redirector_spec.rb42
-rw-r--r--spec/lib/proof_provider/keybase/verifier_spec.rb82
-rw-r--r--spec/lib/status_reach_finder_spec.rb109
-rw-r--r--spec/mailers/admin_mailer_spec.rb8
-rw-r--r--spec/mailers/notification_mailer_spec.rb2
-rw-r--r--spec/mailers/previews/admin_mailer_preview.rb10
-rw-r--r--spec/mailers/previews/user_mailer_preview.rb2
-rw-r--r--spec/models/account_filter_spec.rb42
-rw-r--r--spec/models/account_spec.rb31
-rw-r--r--spec/models/account_statuses_cleanup_policy_spec.rb546
-rw-r--r--spec/models/admin/account_action_spec.rb4
-rw-r--r--spec/models/concerns/account_interactions_spec.rb78
-rw-r--r--spec/models/form/status_batch_spec.rb52
-rw-r--r--spec/models/media_attachment_spec.rb72
-rw-r--r--spec/models/public_feed_spec.rb10
-rw-r--r--spec/models/report_spec.rb16
-rw-r--r--spec/models/status_edit_spec.rb5
-rw-r--r--spec/models/status_pin_spec.rb4
-rw-r--r--spec/models/status_spec.rb81
-rw-r--r--spec/models/tag_feed_spec.rb2
-rw-r--r--spec/models/trending_tags_spec.rb68
-rw-r--r--spec/models/trends/tags_spec.rb67
-rw-r--r--spec/models/user_spec.rb34
-rw-r--r--spec/policies/account_moderation_note_policy_spec.rb4
-rw-r--r--spec/policies/account_policy_spec.rb6
-rw-r--r--spec/policies/backup_policy_spec.rb2
-rw-r--r--spec/policies/custom_emoji_policy_spec.rb2
-rw-r--r--spec/policies/domain_block_policy_spec.rb2
-rw-r--r--spec/policies/email_domain_block_policy_spec.rb2
-rw-r--r--spec/policies/instance_policy_spec.rb4
-rw-r--r--spec/policies/invite_policy_spec.rb2
-rw-r--r--spec/policies/relay_policy_spec.rb2
-rw-r--r--spec/policies/report_note_policy_spec.rb2
-rw-r--r--spec/policies/report_policy_spec.rb2
-rw-r--r--spec/policies/settings_policy_spec.rb2
-rw-r--r--spec/policies/tag_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb2
-rw-r--r--spec/services/account_statuses_cleanup_service_spec.rb101
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb47
-rw-r--r--spec/services/activitypub/process_account_service_spec.rb46
-rw-r--r--spec/services/authorize_follow_service_spec.rb4
-rw-r--r--spec/services/batched_remove_status_service_spec.rb2
-rw-r--r--spec/services/block_service_spec.rb4
-rw-r--r--spec/services/bootstrap_timeline_service_spec.rb33
-rw-r--r--spec/services/delete_account_service_spec.rb5
-rw-r--r--spec/services/fan_out_on_write_service_spec.rb107
-rw-r--r--spec/services/favourite_service_spec.rb4
-rw-r--r--spec/services/fetch_link_card_service_spec.rb2
-rw-r--r--spec/services/fetch_oembed_service_spec.rb41
-rw-r--r--spec/services/follow_service_spec.rb20
-rw-r--r--spec/services/notify_service_spec.rb17
-rw-r--r--spec/services/post_status_service_spec.rb50
-rw-r--r--spec/services/process_mentions_service_spec.rb32
-rw-r--r--spec/services/purge_domain_service_spec.rb27
-rw-r--r--spec/services/reject_follow_service_spec.rb4
-rw-r--r--spec/services/remove_from_follwers_service_spec.rb38
-rw-r--r--spec/services/remove_status_service_spec.rb2
-rw-r--r--spec/services/report_service_spec.rb2
-rw-r--r--spec/services/suspend_account_service_spec.rb85
-rw-r--r--spec/services/unblock_service_spec.rb4
-rw-r--r--spec/services/unfollow_service_spec.rb6
-rw-r--r--spec/services/unsuspend_account_service_spec.rb135
-rw-r--r--spec/services/update_account_service_spec.rb6
-rw-r--r--spec/validators/status_pin_validator_spec.rb8
-rw-r--r--spec/views/about/show.html.haml_spec.rb2
-rw-r--r--spec/views/statuses/show.html.haml_spec.rb44
-rw-r--r--spec/workers/activitypub/delivery_worker_spec.rb2
-rw-r--r--spec/workers/activitypub/distribution_worker_spec.rb7
-rw-r--r--spec/workers/admin/domain_purge_worker_spec.rb18
-rw-r--r--spec/workers/feed_insert_worker_spec.rb2
-rw-r--r--spec/workers/move_worker_spec.rb55
-rw-r--r--spec/workers/publish_scheduled_announcement_worker_spec.rb26
-rw-r--r--spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb127
-rw-r--r--spec/workers/unfollow_follow_worker_spec.rb2
168 files changed, 2902 insertions, 1407 deletions
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index ac426b01e..73d124029 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe AccountsController, type: :controller do
   render_views
 
-  let(:account) { Fabricate(:user).account }
+  let(:account) { Fabricate(:account) }
 
   shared_examples 'cachable response' do
     it 'does not set cookies' do
@@ -35,6 +35,7 @@ RSpec.describe AccountsController, type: :controller do
     before do
       status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
       account.pinned_statuses << status_pinned
+      account.pinned_statuses << status_private
     end
 
     shared_examples 'preliminary checks' do
diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb
index d584136ff..21a033945 100644
--- a/spec/controllers/activitypub/collections_controller_spec.rb
+++ b/spec/controllers/activitypub/collections_controller_spec.rb
@@ -4,6 +4,7 @@ require 'rails_helper'
 
 RSpec.describe ActivityPub::CollectionsController, type: :controller do
   let!(:account) { Fabricate(:account) }
+  let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
   let(:remote_account) { nil }
 
   shared_examples 'cachable response' do
@@ -27,6 +28,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 
     Fabricate(:status_pin, account: account)
     Fabricate(:status_pin, account: account)
+    Fabricate(:status_pin, account: account, status: private_pinned)
     Fabricate(:status, account: account, visibility: :private)
   end
 
@@ -50,7 +52,15 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 
         it 'returns orderedItems with pinned statuses' do
           expect(body[:orderedItems]).to be_an Array
-          expect(body[:orderedItems].size).to eq 2
+          expect(body[:orderedItems].size).to eq 3
+        end
+
+        it 'includes URI of private pinned status' do
+          expect(body[:orderedItems]).to include(ActivityPub::TagManager.instance.uri_for(private_pinned))
+        end
+
+        it 'does not include contents of private pinned status' do
+          expect(response.body).not_to include(private_pinned.text)
         end
 
         context 'when account is permanently suspended' do
@@ -96,7 +106,16 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
           it 'returns orderedItems with pinned statuses' do
             json = body_as_json
             expect(json[:orderedItems]).to be_an Array
-            expect(json[:orderedItems].size).to eq 2
+            expect(json[:orderedItems].size).to eq 3
+          end
+
+          it 'includes URI of private pinned status' do
+            json = body_as_json
+            expect(json[:orderedItems]).to include(ActivityPub::TagManager.instance.uri_for(private_pinned))
+          end
+
+          it 'does not include contents of private pinned status' do
+            expect(response.body).not_to include(private_pinned.text)
           end
         end
 
diff --git a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
index d373f56bd..3a382ff27 100644
--- a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
+++ b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
@@ -5,11 +5,13 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controll
   let!(:follower_1) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
   let!(:follower_2) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
   let!(:follower_3) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
+  let!(:follower_4) { Fabricate(:account, username: 'instance-actor', domain: 'example.com', uri: 'https://example.com') }
 
   before do
     follower_1.follow!(account)
     follower_2.follow!(account)
     follower_3.follow!(account)
+    follower_4.follow!(account)
   end
 
   before do
@@ -45,7 +47,7 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controll
 
       it 'returns orderedItems with followers from example.com' do
         expect(body[:orderedItems]).to be_an Array
-        expect(body[:orderedItems].sort).to eq [follower_1.uri, follower_2.uri]
+        expect(body[:orderedItems].sort).to eq [follower_4.uri, follower_1.uri, follower_2.uri]
       end
 
       it 'returns private Cache-Control header' do
diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
index d23f2c17c..1722690db 100644
--- a/spec/controllers/activitypub/outboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -55,6 +55,10 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 
         it_behaves_like 'cachable response'
 
+        it 'does not have a Vary header' do
+          expect(response.headers['Vary']).to be_nil
+        end
+
         context 'when account is permanently suspended' do
           before do
             account.suspend!
@@ -96,6 +100,10 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 
         it_behaves_like 'cachable response'
 
+        it 'returns Vary header with Signature' do
+          expect(response.headers['Vary']).to include 'Signature'
+        end
+
         context 'when account is permanently suspended' do
           before do
             account.suspend!
@@ -144,7 +152,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
         end
 
         it 'returns private Cache-Control header' do
-          expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
         end
       end
 
@@ -170,7 +178,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
         end
 
         it 'returns private Cache-Control header' do
-          expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
         end
       end
 
@@ -195,7 +203,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
         end
 
         it 'returns private Cache-Control header' do
-          expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
         end
       end
 
@@ -220,7 +228,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
         end
 
         it 'returns private Cache-Control header' do
-          expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+          expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
         end
       end
     end
diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb
index 608606ff9..0f71d697c 100644
--- a/spec/controllers/admin/accounts_controller_spec.rb
+++ b/spec/controllers/admin/accounts_controller_spec.rb
@@ -21,12 +21,9 @@ RSpec.describe Admin::AccountsController, type: :controller do
       expect(AccountFilter).to receive(:new) do |params|
         h = params.to_h
 
-        expect(h[:local]).to eq '1'
-        expect(h[:remote]).to eq '1'
+        expect(h[:origin]).to eq 'local'
         expect(h[:by_domain]).to eq 'domain'
-        expect(h[:active]).to eq '1'
-        expect(h[:silenced]).to eq '1'
-        expect(h[:suspended]).to eq '1'
+        expect(h[:status]).to eq 'active'
         expect(h[:username]).to eq 'username'
         expect(h[:display_name]).to eq 'display name'
         expect(h[:email]).to eq 'local-part@domain'
@@ -36,12 +33,9 @@ RSpec.describe Admin::AccountsController, type: :controller do
       end
 
       get :index, params: {
-        local: '1',
-        remote: '1',
+        origin: 'local',
         by_domain: 'domain',
-        active: '1',
-        silenced: '1',
-        suspended: '1',
+        status: 'active',
         username: 'username',
         display_name: 'display name',
         email: 'local-part@domain',
@@ -67,7 +61,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
 
   describe 'GET #show' do
     let(:current_user) { Fabricate(:user, admin: true) }
-    let(:account) { Fabricate(:account, username: 'bob') }
+    let(:account) { Fabricate(:account) }
 
     it 'returns http success' do
       get :show, params: { id: account.id }
@@ -79,7 +73,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
     subject { post :memorialize, params: { id: account.id } }
 
     let(:current_user) { Fabricate(:user, admin: current_user_admin) }
-    let(:account) { Fabricate(:account, user: user) }
+    let(:account) { user.account }
     let(:user) { Fabricate(:user, admin: target_user_admin) }
 
     context 'when user is admin' do
@@ -131,7 +125,7 @@ RSpec.describe Admin::AccountsController, type: :controller do
     subject { post :enable, params: { id: account.id } }
 
     let(:current_user) { Fabricate(:user, admin: admin) }
-    let(:account) { Fabricate(:account, user: user) }
+    let(:account) { user.account }
     let(:user) { Fabricate(:user, disabled: true) }
 
     context 'when user is admin' do
@@ -198,4 +192,36 @@ RSpec.describe Admin::AccountsController, type: :controller do
       end
     end
   end
+
+  describe 'POST #unblock_email' do
+    subject do
+      -> { post :unblock_email, params: { id: account.id } }
+    end
+
+    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:account) { Fabricate(:account, suspended: true) }
+    let!(:email_block) { Fabricate(:canonical_email_block, reference_account: account) }
+
+    context 'when user is admin' do
+      let(:admin) { true }
+
+      it 'succeeds in removing email blocks' do
+        is_expected.to change { CanonicalEmailBlock.where(reference_account: account).count }.from(1).to(0)
+      end
+
+      it 'redirects to admin account path' do
+        subject.call
+        expect(response).to redirect_to admin_account_path(account.id)
+      end
+    end
+
+    context 'when user is not admin' do
+      let(:admin) { false }
+
+      it 'fails to remove avatar' do
+        subject.call
+        expect(response).to have_http_status :forbidden
+      end
+    end
+  end
 end
diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb
index 31df0f0fc..e7f3f7c97 100644
--- a/spec/controllers/admin/change_email_controller_spec.rb
+++ b/spec/controllers/admin/change_email_controller_spec.rb
@@ -11,10 +11,9 @@ RSpec.describe Admin::ChangeEmailsController, type: :controller do
 
   describe "GET #show" do
     it "returns http success" do
-      account = Fabricate(:account)
-      user = Fabricate(:user, account: account)
+      user = Fabricate(:user)
 
-      get :show, params: { account_id: account.id }
+      get :show, params: { account_id: user.account.id }
 
       expect(response).to have_http_status(200)
     end
@@ -26,12 +25,11 @@ RSpec.describe Admin::ChangeEmailsController, type: :controller do
     end
 
     it "returns http success" do
-      account = Fabricate(:account)
-      user = Fabricate(:user, account: account)
+      user = Fabricate(:user)
 
       previous_email = user.email
 
-      post :update, params: { account_id: account.id, user: { unconfirmed_email: 'test@example.com' } }
+      post :update, params: { account_id: user.account.id, user: { unconfirmed_email: 'test@example.com' } }
 
       user.reload
 
@@ -41,7 +39,7 @@ RSpec.describe Admin::ChangeEmailsController, type: :controller do
 
       expect(UserMailer).to have_received(:confirmation_instructions).with(user, user.confirmation_token, { to: 'test@example.com' })
 
-      expect(response).to redirect_to(admin_account_path(account.id))
+      expect(response).to redirect_to(admin_account_path(user.account.id))
     end
   end
 end
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index eec2b2f5c..5b4f7e925 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -9,9 +9,8 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do
 
   describe 'POST #create' do
     it 'confirms the user' do
-      account = Fabricate(:account)
-      user = Fabricate(:user, confirmed_at: false, account: account)
-      post :create, params: { account_id: account.id }
+      user = Fabricate(:user, confirmed_at: false)
+      post :create, params: { account_id: user.account.id }
 
       expect(response).to redirect_to(admin_accounts_path)
       expect(user.reload).to be_confirmed
@@ -32,10 +31,9 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do
   end
 
   describe 'POST #resernd' do
-    subject { post :resend, params: { account_id: account.id } }
+    subject { post :resend, params: { account_id: user.account.id } }
 
-    let(:account) { Fabricate(:account) }
-    let!(:user) { Fabricate(:user, confirmed_at: confirmed_at, account: account) }
+    let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
 
     before do
       allow(UserMailer).to receive(:confirmation_instructions) { double(:email, deliver_later: nil) }
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
index 8c0b309f2..53427b874 100644
--- a/spec/controllers/admin/instances_controller_spec.rb
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -3,8 +3,14 @@ require 'rails_helper'
 RSpec.describe Admin::InstancesController, type: :controller do
   render_views
 
+  let(:current_user) { Fabricate(:user, admin: true) }
+
+  let!(:account)     { Fabricate(:account, domain: 'popular') }
+  let!(:account2)    { Fabricate(:account, domain: 'popular') }
+  let!(:account3)    { Fabricate(:account, domain: 'less.popular') }
+
   before do
-    sign_in Fabricate(:user, admin: true), scope: :user
+    sign_in current_user, scope: :user
   end
 
   describe 'GET #index' do
@@ -16,10 +22,6 @@ RSpec.describe Admin::InstancesController, type: :controller do
     end
 
     it 'renders instances' do
-      Fabricate(:account, domain: 'popular')
-      Fabricate(:account, domain: 'popular')
-      Fabricate(:account, domain: 'less.popular')
-
       get :index, params: { page: 2 }
 
       instances = assigns(:instances).to_a
@@ -29,4 +31,27 @@ RSpec.describe Admin::InstancesController, type: :controller do
       expect(response).to have_http_status(200)
     end
   end
+
+  describe 'DELETE #destroy' do
+    subject { delete :destroy, params: { id: Instance.first.id } }
+
+    let(:current_user) { Fabricate(:user, admin: admin) }
+    let(:account) { Fabricate(:account) }
+
+    context 'when user is admin' do
+      let(:admin) { true }
+
+      it 'succeeds in purging instance' do
+        is_expected.to redirect_to admin_instances_path
+      end
+    end
+
+    context 'when user is not admin' do
+      let(:admin) { false }
+
+      it 'fails to purge instance' do
+        is_expected.to have_http_status :forbidden
+      end
+    end
+  end
 end
diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb
index ec5872c7d..c0013f41a 100644
--- a/spec/controllers/admin/report_notes_controller_spec.rb
+++ b/spec/controllers/admin/report_notes_controller_spec.rb
@@ -12,11 +12,11 @@ describe Admin::ReportNotesController do
   describe 'POST #create' do
     subject { post :create, params: params }
 
-    let(:report) { Fabricate(:report, action_taken: action_taken, action_taken_by_account_id: account_id) }
+    let(:report) { Fabricate(:report, action_taken_at: action_taken, action_taken_by_account_id: account_id) }
 
     context 'when parameter is valid' do
       context 'when report is unsolved' do
-        let(:action_taken) { false }
+        let(:action_taken) { nil }
         let(:account_id) { nil }
 
         context 'when create_and_resolve flag is on' do
@@ -41,7 +41,7 @@ describe Admin::ReportNotesController do
       end
 
       context 'when report is resolved' do
-        let(:action_taken) { true }
+        let(:action_taken) { Time.now.utc }
         let(:account_id) { user.account.id }
 
         context 'when create_and_unresolve flag is on' do
@@ -68,7 +68,7 @@ describe Admin::ReportNotesController do
 
     context 'when parameter is invalid' do
       let(:params) { { report_note: { content: '', report_id: report.id } } }
-      let(:action_taken) { false }
+      let(:action_taken) { nil }
       let(:account_id) { nil }
 
       it 'renders admin/reports/show' do
diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb
deleted file mode 100644
index 2a1598123..000000000
--- a/spec/controllers/admin/reported_statuses_controller_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'rails_helper'
-
-describe Admin::ReportedStatusesController do
-  render_views
-
-  let(:user) { Fabricate(:user, admin: true) }
-  let(:report) { Fabricate(:report, status_ids: [status.id]) }
-  let(:status) { Fabricate(:status) }
-
-  before do
-    sign_in user, scope: :user
-  end
-
-  describe 'POST #create' do
-    subject do
-      -> { post :create, params: { :report_id => report, action => '', :form_status_batch => { status_ids: status_ids } } }
-    end
-
-    let(:action) { 'nsfw_on' }
-    let(:status_ids) { [status.id] }
-    let(:status) { Fabricate(:status, sensitive: !sensitive) }
-    let(:sensitive) { true }
-    let!(:media_attachment) { Fabricate(:media_attachment, status: status) }
-
-    context 'when action is nsfw_on' do
-      it 'updates sensitive column' do
-        is_expected.to change {
-          status.reload.sensitive
-        }.from(false).to(true)
-      end
-    end
-
-    context 'when action is nsfw_off' do
-      let(:action) { 'nsfw_off' }
-      let(:sensitive) { false }
-
-      it 'updates sensitive column' do
-        is_expected.to change {
-          status.reload.sensitive
-        }.from(true).to(false)
-      end
-    end
-
-    context 'when action is delete' do
-      let(:action) { 'delete' }
-
-      it 'removes a status' do
-        allow(RemovalWorker).to receive(:perform_async)
-        subject.call
-        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
-      end
-    end
-
-    it 'redirects to report page' do
-      subject.call
-      expect(response).to redirect_to(admin_report_path(report))
-    end
-  end
-end
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
index 49d3e9707..d421f0739 100644
--- a/spec/controllers/admin/reports_controller_spec.rb
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -10,8 +10,8 @@ describe Admin::ReportsController do
 
   describe 'GET #index' do
     it 'returns http success with no filters' do
-      specified = Fabricate(:report, action_taken: false)
-      Fabricate(:report, action_taken: true)
+      specified = Fabricate(:report, action_taken_at: nil)
+      Fabricate(:report, action_taken_at: Time.now.utc)
 
       get :index
 
@@ -22,10 +22,10 @@ describe Admin::ReportsController do
     end
 
     it 'returns http success with resolved filter' do
-      specified = Fabricate(:report, action_taken: true)
-      Fabricate(:report, action_taken: false)
+      specified = Fabricate(:report, action_taken_at: Time.now.utc)
+      Fabricate(:report, action_taken_at: nil)
 
-      get :index, params: { resolved: 1 }
+      get :index, params: { resolved: '1' }
 
       reports = assigns(:reports).to_a
       expect(reports.size).to eq 1
@@ -54,15 +54,7 @@ describe Admin::ReportsController do
       expect(response).to redirect_to(admin_reports_path)
       report.reload
       expect(report.action_taken_by_account).to eq user.account
-      expect(report.action_taken).to eq true
-    end
-
-    it 'sets trust level when the report is an antispam one' do
-      report = Fabricate(:report, account: Account.representative)
-
-      put :resolve, params: { id: report }
-      report.reload
-      expect(report.target_account.trust_level).to eq Account::TRUST_LEVELS[:trusted]
+      expect(report.action_taken?).to eq true
     end
   end
 
@@ -74,7 +66,7 @@ describe Admin::ReportsController do
       expect(response).to redirect_to(admin_report_path(report))
       report.reload
       expect(report.action_taken_by_account).to eq nil
-      expect(report.action_taken).to eq false
+      expect(report.action_taken?).to eq false
     end
   end
 
diff --git a/spec/controllers/admin/resets_controller_spec.rb b/spec/controllers/admin/resets_controller_spec.rb
index a20a460bd..28510b5af 100644
--- a/spec/controllers/admin/resets_controller_spec.rb
+++ b/spec/controllers/admin/resets_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Admin::ResetsController do
   render_views
 
-  let(:account) { Fabricate(:account, user: Fabricate(:user)) }
+  let(:account) { Fabricate(:account) }
   before do
     sign_in Fabricate(:user, admin: true), scope: :user
   end
@@ -16,7 +16,7 @@ describe Admin::ResetsController do
 
       post :create, params: { account_id: account.id }
 
-      expect(response).to redirect_to(admin_accounts_path)
+      expect(response).to redirect_to(admin_account_path(account.id))
     end
   end
 end
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index d9690d83f..de32fd18e 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -8,6 +8,9 @@ describe Admin::StatusesController do
   let!(:status) { Fabricate(:status, account: account) }
   let(:media_attached_status) { Fabricate(:status, account: account, sensitive: !sensitive) }
   let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: media_attached_status) }
+  let(:last_media_attached_status) { Fabricate(:status, account: account, sensitive: !sensitive) }
+  let!(:last_media_attachment) { Fabricate(:media_attachment, account: account, status: last_media_attached_status) }
+  let!(:last_status) { Fabricate(:status, account: account) }
   let(:sensitive) { true }
 
   before do
@@ -15,63 +18,46 @@ describe Admin::StatusesController do
   end
 
   describe 'GET #index' do
-    it 'returns http success with no media' do
-      get :index, params: { account_id: account.id }
+    context do
+      before do
+        get :index, params: { account_id: account.id }
+      end
 
-      statuses = assigns(:statuses).to_a
-      expect(statuses.size).to eq 2
-      expect(response).to have_http_status(200)
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
     end
 
-    it 'returns http success with media' do
-      get :index, params: { account_id: account.id, media: true }
+    context 'filtering by media' do
+      before do
+        get :index, params: { account_id: account.id, media: '1' }
+      end
 
-      statuses = assigns(:statuses).to_a
-      expect(statuses.size).to eq 1
-      expect(response).to have_http_status(200)
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
     end
   end
 
-  describe 'POST #create' do
-    subject do
-      -> { post :create, params: { :account_id => account.id, action => '', :form_status_batch => { status_ids: status_ids } } }
+  describe 'POST #batch' do
+    before do
+      post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } }
     end
 
-    let(:action) { 'nsfw_on' }
     let(:status_ids) { [media_attached_status.id] }
 
-    context 'when action is nsfw_on' do
-      it 'updates sensitive column' do
-        is_expected.to change {
-          media_attached_status.reload.sensitive
-        }.from(false).to(true)
-      end
-    end
+    context 'when action is report' do
+      let(:action) { 'report' }
 
-    context 'when action is nsfw_off' do
-      let(:action) { 'nsfw_off' }
-      let(:sensitive) { false }
-
-      it 'updates sensitive column' do
-        is_expected.to change {
-          media_attached_status.reload.sensitive
-        }.from(true).to(false)
+      it 'creates a report' do
+        report = Report.last
+        expect(report.target_account_id).to eq account.id
+        expect(report.status_ids).to eq status_ids
       end
-    end
-
-    context 'when action is delete' do
-      let(:action) { 'delete' }
 
-      it 'removes a status' do
-        allow(RemovalWorker).to receive(:perform_async)
-        subject.call
-        expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
+      it 'redirects to report page' do
+        expect(response).to redirect_to(admin_report_path(Report.last.id))
       end
     end
-
-    it 'redirects to account statuses page' do
-      subject.call
-      expect(response).to redirect_to(admin_account_statuses_path(account.id))
-    end
   end
 end
diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb
index 9145d887d..85c801a9c 100644
--- a/spec/controllers/admin/tags_controller_spec.rb
+++ b/spec/controllers/admin/tags_controller_spec.rb
@@ -9,18 +9,6 @@ RSpec.describe Admin::TagsController, type: :controller do
     sign_in Fabricate(:user, admin: true)
   end
 
-  describe 'GET #index' do
-    let!(:tag) { Fabricate(:tag) }
-
-    before do
-      get :index
-    end
-
-    it 'returns status 200' do
-      expect(response).to have_http_status(200)
-    end
-  end
-
   describe 'GET #show' do
     let!(:tag) { Fabricate(:tag) }
 
diff --git a/spec/controllers/admin/two_factor_authentications_controller_spec.rb b/spec/controllers/admin/two_factor_authentications_controller_spec.rb
index b0e82d3d6..c65095729 100644
--- a/spec/controllers/admin/two_factor_authentications_controller_spec.rb
+++ b/spec/controllers/admin/two_factor_authentications_controller_spec.rb
@@ -15,12 +15,12 @@ describe Admin::TwoFactorAuthenticationsController do
         user.update(otp_required_for_login: true)
       end
 
-      it 'redirects to admin accounts page' do
+      it 'redirects to admin account page' do
         delete :destroy, params: { user_id: user.id }
 
         user.reload
         expect(user.otp_enabled?).to eq false
-        expect(response).to redirect_to(admin_accounts_path)
+        expect(response).to redirect_to(admin_account_path(user.account_id))
       end
     end
 
@@ -38,13 +38,13 @@ describe Admin::TwoFactorAuthenticationsController do
                   nickname: 'Security Key')
       end
 
-      it 'redirects to admin accounts page' do
+      it 'redirects to admin account page' do
         delete :destroy, params: { user_id: user.id }
 
         user.reload
         expect(user.otp_enabled?).to eq false
         expect(user.webauthn_enabled?).to eq false
-        expect(response).to redirect_to(admin_accounts_path)
+        expect(response).to redirect_to(admin_account_path(user.account_id))
       end
     end
   end
diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb
index 05a42d1c1..c286b8cbf 100644
--- a/spec/controllers/api/base_controller_spec.rb
+++ b/spec/controllers/api/base_controller_spec.rb
@@ -28,7 +28,7 @@ describe Api::BaseController do
   end
 
   describe 'non-functional accounts handling' do
-    let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+    let(:user)  { Fabricate(:user) }
     let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
 
     controller do
diff --git a/spec/controllers/api/proofs_controller_spec.rb b/spec/controllers/api/proofs_controller_spec.rb
deleted file mode 100644
index 2fe615005..000000000
--- a/spec/controllers/api/proofs_controller_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'rails_helper'
-
-describe Api::ProofsController do
-  let(:alice) { Fabricate(:account, username: 'alice') }
-
-  before do
-    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":false}')
-    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=crypto_alice&sig_hash=111111111111111111111111111111111111111111111111111111111111111111&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
-    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_valid.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
-    stub_request(:get, 'https://keybase.io/_/api/1.0/sig/proof_live.json?domain=cb6e6126.ngrok.io&kb_username=hidden_alice&sig_hash=222222222222222222222222222222222222222222222222222222222222222222&username=alice').to_return(status: 200, body: '{"proof_valid":true,"proof_live":true}')
-  end
-
-  describe 'GET #index' do
-    describe 'with a non-existent username' do
-      it '404s' do
-        get :index, params: { username: 'nonexistent', provider: 'keybase' }
-
-        expect(response).to have_http_status(:not_found)
-      end
-    end
-
-    describe 'with a user that has no proofs' do
-      it 'is an empty list of signatures' do
-        get :index, params: { username: alice.username, provider: 'keybase' }
-
-        expect(body_as_json[:signatures]).to eq []
-      end
-    end
-
-    describe 'with a user that has a live, valid proof' do
-      let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
-      let(:kb_name1) { 'crypto_alice' }
-
-      before do
-        Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
-      end
-
-      it 'is a list with that proof in it' do
-        get :index, params: { username: alice.username, provider: 'keybase' }
-
-        expect(body_as_json[:signatures]).to eq [
-          { kb_username: kb_name1, sig_hash: token1 },
-        ]
-      end
-
-      describe 'add one that is neither live nor valid' do
-        let(:token2) { '222222222222222222222222222222222222222222222222222222222222222222' }
-        let(:kb_name2) { 'hidden_alice' }
-
-        before do
-          Fabricate(:account_identity_proof, account: alice, verified: false, live: false, token: token2, provider_username: kb_name2)
-        end
-
-        it 'is a list with both proofs' do
-          get :index, params: { username: alice.username, provider: 'keybase' }
-
-          expect(body_as_json[:signatures]).to eq [
-            { kb_username: kb_name1, sig_hash: token1 },
-            { kb_username: kb_name2, sig_hash: token2 },
-          ]
-        end
-      end
-    end
-
-    describe 'a user that has an avatar' do
-      let(:alice) { Fabricate(:account, username: 'alice', avatar: attachment_fixture('avatar.gif')) }
-
-      context 'and a proof' do
-        let(:token1) { '111111111111111111111111111111111111111111111111111111111111111111' }
-        let(:kb_name1) { 'crypto_alice' }
-
-        before do
-          Fabricate(:account_identity_proof, account: alice, verified: true, live: true, token: token1, provider_username: kb_name1)
-          get :index, params: { username: alice.username, provider: 'keybase' }
-        end
-
-        it 'has two keys: signatures and avatar' do
-          expect(body_as_json.keys).to match_array [:signatures, :avatar]
-        end
-
-        it 'has the correct signatures' do
-          expect(body_as_json[:signatures]).to eq [
-            { kb_username: kb_name1, sig_hash: token1 },
-          ]
-        end
-
-        it 'has the correct avatar url' do
-          expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}"
-        end
-      end
-    end
-  end
-end
diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
index 1b29772c3..aae35ce38 100644
--- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::CredentialsController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
   context 'with an oauth token' do
diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
index 482a19ef2..1e6e1d8e0 100644
--- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::FollowerAccountsController do
   render_views
 
-  let(:user)    { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)    { Fabricate(:user) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
   let(:account) { Fabricate(:account) }
   let(:alice)   { Fabricate(:account) }
@@ -49,10 +49,10 @@ describe Api::V1::Accounts::FollowerAccountsController do
     end
 
     context 'when requesting user is the account owner' do
-      let(:user) { Fabricate(:user, account: account) }
+      let(:user) { account.user }
 
       it 'returns all accounts, including muted accounts' do
-        user.account.mute!(bob)
+        account.mute!(bob)
         get :index, params: { account_id: account.id, limit: 2 }
 
         expect(body_as_json.size).to eq 2
diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
index e35b625fe..cc962c6ee 100644
--- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::FollowingAccountsController do
   render_views
 
-  let(:user)    { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)    { Fabricate(:user) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
   let(:account) { Fabricate(:account) }
   let(:alice)   { Fabricate(:account) }
@@ -49,10 +49,10 @@ describe Api::V1::Accounts::FollowingAccountsController do
     end
 
     context 'when requesting user is the account owner' do
-      let(:user) { Fabricate(:user, account: account) }
+      let(:user) { account.user }
 
       it 'returns all accounts, including muted accounts' do
-        user.account.mute!(bob)
+        account.mute!(bob)
         get :index, params: { account_id: account.id, limit: 2 }
 
         expect(body_as_json.size).to eq 2
diff --git a/spec/controllers/api/v1/accounts/lists_controller_spec.rb b/spec/controllers/api/v1/accounts/lists_controller_spec.rb
index baafea8e6..d71485633 100644
--- a/spec/controllers/api/v1/accounts/lists_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/lists_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::ListsController do
   render_views
 
-  let(:user)    { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)    { Fabricate(:user) }
   let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') }
   let(:account) { Fabricate(:account) }
   let(:list)    { Fabricate(:list, account: user.account) }
diff --git a/spec/controllers/api/v1/accounts/notes_controller_spec.rb b/spec/controllers/api/v1/accounts/notes_controller_spec.rb
new file mode 100644
index 000000000..47d595c70
--- /dev/null
+++ b/spec/controllers/api/v1/accounts/notes_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'rails_helper'
+
+describe Api::V1::Accounts::NotesController do
+  render_views
+
+  let(:user)    { Fabricate(:user) }
+  let(:token)   { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:accounts') }
+  let(:account) { Fabricate(:account) }
+  let(:comment) { 'foo' }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  describe 'POST #create' do
+    subject do
+      post :create, params: { account_id: account.id, comment: comment }
+    end
+
+    context 'when account note has reasonable length' do
+      let(:comment) { 'foo' }
+
+      it 'returns http success' do
+        subject
+        expect(response).to have_http_status(200)
+      end
+
+      it 'updates account note' do
+        subject
+        expect(AccountNote.find_by(account_id: user.account.id, target_account_id: account.id).comment).to eq comment
+      end
+    end
+
+    context 'when account note exceends allowed length' do
+      let(:comment) { 'a' * 2_001 }
+
+      it 'returns 422' do
+        subject
+        expect(response).to have_http_status(422)
+      end
+
+      it 'does not create account note' do
+        subject
+        expect(AccountNote.where(account_id: user.account.id, target_account_id: account.id).exists?).to be_falsey
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/accounts/pins_controller_spec.rb b/spec/controllers/api/v1/accounts/pins_controller_spec.rb
index c71935df2..19bba093e 100644
--- a/spec/controllers/api/v1/accounts/pins_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/pins_controller_spec.rb
@@ -3,8 +3,8 @@
 require 'rails_helper'
 
 RSpec.describe Api::V1::Accounts::PinsController, type: :controller do
-  let(:john)  { Fabricate(:user, account: Fabricate(:account, username: 'john')) }
-  let(:kevin) { Fabricate(:user, account: Fabricate(:account, username: 'kevin')) }
+  let(:john)  { Fabricate(:user) }
+  let(:kevin) { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: john.id, scopes: 'write:accounts') }
 
   before do
diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
index fe715ff62..69ad0d061 100644
--- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::RelationshipsController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:follows') }
 
   before do
@@ -11,8 +11,8 @@ describe Api::V1::Accounts::RelationshipsController do
   end
 
   describe 'GET #index' do
-    let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account }
-    let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account }
+    let(:simon) { Fabricate(:account) }
+    let(:lewis) { Fabricate(:account) }
 
     before do
       user.account.follow!(simon)
diff --git a/spec/controllers/api/v1/accounts/search_controller_spec.rb b/spec/controllers/api/v1/accounts/search_controller_spec.rb
index 8ff2b17de..5b23bff68 100644
--- a/spec/controllers/api/v1/accounts/search_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/search_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Accounts::SearchController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
 
   before do
diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
index 693cd1ac6..348de08c2 100644
--- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Accounts::StatusesController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') }
 
   before do
@@ -39,7 +39,7 @@ describe Api::V1::Accounts::StatusesController do
       end
     end
 
-    context 'with only pinned' do
+    context 'with only own pinned' do
       before do
         Fabricate(:status_pin, account: user.account, status: Fabricate(:status, account: user.account))
       end
@@ -50,5 +50,38 @@ describe Api::V1::Accounts::StatusesController do
         expect(response).to have_http_status(200)
       end
     end
+
+    context "with someone else's pinned statuses" do
+      let(:account)        { Fabricate(:account, username: 'bob', domain: 'example.com') }
+      let(:status)         { Fabricate(:status, account: account) }
+      let(:private_status) { Fabricate(:status, account: account, visibility: :private) }
+      let!(:pin)           { Fabricate(:status_pin, account: account, status: status) }
+      let!(:private_pin)   { Fabricate(:status_pin, account: account, status: private_status) }
+
+      it 'returns http success' do
+        get :index, params: { account_id: account.id, pinned: true }
+        expect(response).to have_http_status(200)
+      end
+
+      context 'when user does not follow account' do
+        it 'lists the public status only' do
+          get :index, params: { account_id: account.id, pinned: true }
+          json = body_as_json
+          expect(json.map { |item| item[:id].to_i }).to eq [status.id]
+        end
+      end
+
+      context 'when user follows account' do
+        before do
+          user.account.follow!(account)
+        end
+
+        it 'lists both the public and the private statuses' do
+          get :index, params: { account_id: account.id, pinned: true }
+          json = body_as_json
+          expect(json.map { |item| item[:id].to_i }.sort).to eq [status.id, private_status.id].sort
+        end
+      end
+    end
   end
 end
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index d9ee37ffa..5d5c245c5 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::AccountsController, type: :controller do
   render_views
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user) }
   let(:scopes) { '' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
@@ -69,7 +69,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #follow' do
     let(:scopes) { 'write:follows' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', locked: locked)).account }
+    let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
 
     context do
       before do
@@ -150,7 +150,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #unfollow' do
     let(:scopes) { 'write:follows' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.follow!(other_account)
@@ -168,9 +168,29 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
     it_behaves_like 'forbidden for wrong scope', 'read:accounts'
   end
 
+  describe 'POST #remove_from_followers' do
+    let(:scopes) { 'write:follows' }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
+
+    before do
+      other_account.follow!(user.account)
+      post :remove_from_followers, params: { id: other_account.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+
+    it 'removes the followed relation between user and target user' do
+      expect(user.account.followed_by?(other_account)).to be false
+    end
+
+    it_behaves_like 'forbidden for wrong scope', 'read:accounts'
+  end
+
   describe 'POST #block' do
     let(:scopes) { 'write:blocks' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.follow!(other_account)
@@ -194,7 +214,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #unblock' do
     let(:scopes) { 'write:blocks' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.block!(other_account)
@@ -214,7 +234,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #mute' do
     let(:scopes) { 'write:mutes' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.follow!(other_account)
@@ -242,7 +262,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #mute with notifications set to false' do
     let(:scopes) { 'write:mutes' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.follow!(other_account)
@@ -270,7 +290,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #mute with nonzero duration set' do
     let(:scopes) { 'write:mutes' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.follow!(other_account)
@@ -298,7 +318,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
 
   describe 'POST #unmute' do
     let(:scopes) { 'write:mutes' }
-    let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:other_account) { Fabricate(:account, username: 'bob') }
 
     before do
       user.account.mute!(other_account)
diff --git a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
index a5a8f4bb0..601290b82 100644
--- a/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/account_actions_controller_spec.rb
@@ -4,10 +4,10 @@ RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
   render_views
 
   let(:role)   { 'moderator' }
-  let(:user)   { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:account) { Fabricate(:user).account }
+  let(:account) { Fabricate(:account) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index f6be35f7f..bf79ee520 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -4,10 +4,10 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
   render_views
 
   let(:role)   { 'moderator' }
-  let(:user)   { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:account) { Fabricate(:user).account }
+  let(:account) { Fabricate(:account) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/admin/reports_controller_spec.rb b/spec/controllers/api/v1/admin/reports_controller_spec.rb
index 4ed3c5dc4..b6df53048 100644
--- a/spec/controllers/api/v1/admin/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/reports_controller_spec.rb
@@ -4,7 +4,7 @@ RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
   render_views
 
   let(:role)   { 'moderator' }
-  let(:user)   { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user, role: role) }
   let(:scopes) { 'admin:read admin:write' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:report) { Fabricate(:report) }
diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb
index 818f76c92..0e5c8296d 100644
--- a/spec/controllers/api/v1/blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/blocks_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::BlocksController, type: :controller do
   render_views
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user) }
   let(:scopes) { 'read:blocks' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
diff --git a/spec/controllers/api/v1/conversations_controller_spec.rb b/spec/controllers/api/v1/conversations_controller_spec.rb
index 070f65061..5add7cf1d 100644
--- a/spec/controllers/api/v1/conversations_controller_spec.rb
+++ b/spec/controllers/api/v1/conversations_controller_spec.rb
@@ -3,9 +3,9 @@ require 'rails_helper'
 RSpec.describe Api::V1::ConversationsController, type: :controller do
   render_views
 
-  let!(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let!(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
+  let(:other) { Fabricate(:user) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/domain_blocks_controller_spec.rb
index 6a7a35c7a..d9dc1bdbf 100644
--- a/spec/controllers/api/v1/domain_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/domain_blocks_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::DomainBlocksController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
   before do
diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb
index 1034faa32..856ba2a1c 100644
--- a/spec/controllers/api/v1/follow_requests_controller_spec.rb
+++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb
@@ -3,9 +3,9 @@ require 'rails_helper'
 RSpec.describe Api::V1::FollowRequestsController, type: :controller do
   render_views
 
-  let(:user)     { Fabricate(:user, account: Fabricate(:account, username: 'alice', locked: true)) }
+  let(:user)     { Fabricate(:user, account_attributes: { locked: true }) }
   let(:token)    { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:follower) { Fabricate(:account, username: 'bob') }
+  let(:follower) { Fabricate(:account) }
 
   before do
     FollowService.new.call(follower, user.account)
diff --git a/spec/controllers/api/v1/instances_controller_spec.rb b/spec/controllers/api/v1/instances_controller_spec.rb
index 7397d25d6..842669d96 100644
--- a/spec/controllers/api/v1/instances_controller_spec.rb
+++ b/spec/controllers/api/v1/instances_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::InstancesController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
 
   before do
diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb
index 08c22de56..526d8b561 100644
--- a/spec/controllers/api/v1/lists/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/lists/accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe Api::V1::Lists::AccountsController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let(:list)  { Fabricate(:list, account: user.account) }
 
diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb
index e92213789..71a8094e6 100644
--- a/spec/controllers/api/v1/lists_controller_spec.rb
+++ b/spec/controllers/api/v1/lists_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::ListsController, type: :controller do
   render_views
 
-  let!(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let!(:user)  { Fabricate(:user) }
   let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
   let!(:list)  { Fabricate(:list, account: user.account) }
 
diff --git a/spec/controllers/api/v1/markers_controller_spec.rb b/spec/controllers/api/v1/markers_controller_spec.rb
index 556a75b9b..ba0f3c322 100644
--- a/spec/controllers/api/v1/markers_controller_spec.rb
+++ b/spec/controllers/api/v1/markers_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::MarkersController, type: :controller do
   render_views
 
-  let!(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let!(:user)  { Fabricate(:user) }
   let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses write:statuses') }
 
   before { allow(controller).to receive(:doorkeeper_token) { token } }
diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb
index 3eb015a1c..d8d732630 100644
--- a/spec/controllers/api/v1/media_controller_spec.rb
+++ b/spec/controllers/api/v1/media_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::MediaController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:media') }
 
   before do
diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb
index a2b814a69..8176815d4 100644
--- a/spec/controllers/api/v1/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/mutes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::MutesController, type: :controller do
   render_views
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user) }
   let(:scopes) { 'read:mutes' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb
index 5a0b24bbf..f8df6589f 100644
--- a/spec/controllers/api/v1/notifications_controller_spec.rb
+++ b/spec/controllers/api/v1/notifications_controller_spec.rb
@@ -3,10 +3,10 @@ require 'rails_helper'
 RSpec.describe Api::V1::NotificationsController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user, account_attributes: { username: 'alice' }) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
-  let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
-  let(:third) { Fabricate(:user, account: Fabricate(:account, username: 'carol')) }
+  let(:other) { Fabricate(:user) }
+  let(:third) { Fabricate(:user) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/polls/votes_controller_spec.rb b/spec/controllers/api/v1/polls/votes_controller_spec.rb
index 0ee3aa040..d7a9c1970 100644
--- a/spec/controllers/api/v1/polls/votes_controller_spec.rb
+++ b/spec/controllers/api/v1/polls/votes_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Polls::VotesController, type: :controller do
   render_views
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user) }
   let(:scopes) { 'write:statuses' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
diff --git a/spec/controllers/api/v1/polls_controller_spec.rb b/spec/controllers/api/v1/polls_controller_spec.rb
index 851bccb7e..f0d9eaf92 100644
--- a/spec/controllers/api/v1/polls_controller_spec.rb
+++ b/spec/controllers/api/v1/polls_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::PollsController, type: :controller do
   render_views
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)   { Fabricate(:user) }
   let(:scopes) { 'read:statuses' }
   let(:token)  { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb
index a3596cf8a..a13de1370 100644
--- a/spec/controllers/api/v1/reports_controller_spec.rb
+++ b/spec/controllers/api/v1/reports_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::ReportsController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
 
   before do
diff --git a/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb b/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb
index 7c75a4f73..46d7b6c0a 100644
--- a/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/bookmarks_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Statuses::BookmarksController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:bookmarks', application: app) }
 
diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
index f053ae573..439a4738d 100644
--- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
   let(:alice) { Fabricate(:account) }
diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
index 4716ecae3..609957e3e 100644
--- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Statuses::FavouritesController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:favourites', application: app) }
 
diff --git a/spec/controllers/api/v1/statuses/histories_controller_spec.rb b/spec/controllers/api/v1/statuses/histories_controller_spec.rb
new file mode 100644
index 000000000..00677f1d2
--- /dev/null
+++ b/spec/controllers/api/v1/statuses/histories_controller_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Statuses::HistoriesController do
+  render_views
+
+  let(:user)  { Fabricate(:user) }
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
+
+  context 'with an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { token }
+    end
+
+    describe 'GET #show' do
+      let(:status) { Fabricate(:status, account: user.account) }
+
+      before do
+        get :show, params: { status_id: status.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
index 966398580..bffa9fe0d 100644
--- a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Statuses::MutesController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:mutes', application: app) }
 
diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb
index 13405d285..8bdaf8b54 100644
--- a/spec/controllers/api/v1/statuses/pins_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Statuses::PinsController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:accounts', application: app) }
 
diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
index 60908b7b3..31320349d 100644
--- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
   let(:alice) { Fabricate(:account) }
diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
index f1d3d949c..6eac02b23 100644
--- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Statuses::ReblogsController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses', application: app) }
 
diff --git a/spec/controllers/api/v1/statuses/sources_controller_spec.rb b/spec/controllers/api/v1/statuses/sources_controller_spec.rb
new file mode 100644
index 000000000..fbe6fa0be
--- /dev/null
+++ b/spec/controllers/api/v1/statuses/sources_controller_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Statuses::SourcesController do
+  render_views
+
+  let(:user)  { Fabricate(:user) }
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
+
+  context 'with an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { token }
+    end
+
+    describe 'GET #show' do
+      let(:status) { Fabricate(:status, account: user.account) }
+
+      before do
+        get :show, params: { status_id: status.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(200)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index df8037038..2679ab017 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe Api::V1::StatusesController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: scopes) }
 
diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb
index e953e4649..131c2d92f 100644
--- a/spec/controllers/api/v1/timelines/home_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/home_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Timelines::HomeController do
   render_views
 
-  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_at: 1.day.ago) }
+  let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/timelines/list_controller_spec.rb b/spec/controllers/api/v1/timelines/list_controller_spec.rb
index 45e4bf34c..526c66a05 100644
--- a/spec/controllers/api/v1/timelines/list_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/list_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Timelines::ListController do
   render_views
 
-  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user) { Fabricate(:user) }
   let(:list) { Fabricate(:list, account: user.account) }
 
   before do
@@ -30,7 +30,7 @@ describe Api::V1::Timelines::ListController do
   end
 
   context 'with the wrong user context' do
-    let(:other_user) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
+    let(:other_user) { Fabricate(:user) }
     let(:token)      { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') }
 
     describe 'GET #show' do
diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb
index b8e9d8674..0892d5db6 100644
--- a/spec/controllers/api/v1/timelines/public_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/public_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Timelines::PublicController do
   render_views
 
-  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user) { Fabricate(:user) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
index f71ca2a39..718911083 100644
--- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Timelines::TagController do
   render_views
 
-  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user) { Fabricate(:user) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/api/v1/trends/tags_controller_spec.rb b/spec/controllers/api/v1/trends/tags_controller_spec.rb
new file mode 100644
index 000000000..e2e26dcab
--- /dev/null
+++ b/spec/controllers/api/v1/trends/tags_controller_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Api::V1::Trends::TagsController, type: :controller do
+  render_views
+
+  describe 'GET #index' do
+    before do
+      trending_tags = double()
+
+      allow(trending_tags).to receive(:get).and_return(Fabricate.times(10, :tag))
+      allow(Trends).to receive(:tags).and_return(trending_tags)
+
+      get :index
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(200)
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/trends_controller_spec.rb b/spec/controllers/api/v1/trends_controller_spec.rb
deleted file mode 100644
index 91e0d18fe..000000000
--- a/spec/controllers/api/v1/trends_controller_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Api::V1::TrendsController, type: :controller do
-  render_views
-
-  describe 'GET #index' do
-    before do
-      allow(TrendingTags).to receive(:get).and_return(Fabricate.times(10, :tag))
-      get :index
-    end
-
-    it 'returns http success' do
-      expect(response).to have_http_status(200)
-    end
-  end
-end
diff --git a/spec/controllers/api/v2/search_controller_spec.rb b/spec/controllers/api/v2/search_controller_spec.rb
index 8ee8753de..fa20e1e51 100644
--- a/spec/controllers/api/v2/search_controller_spec.rb
+++ b/spec/controllers/api/v2/search_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe Api::V2::SearchController, type: :controller do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user)  { Fabricate(:user) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') }
 
   before do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 881ecb124..a6a6871f7 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -49,7 +49,7 @@ describe ApplicationController, type: :controller do
 
     it 'returns account if signed in' do
       account = Fabricate(:account)
-      sign_in(Fabricate(:user, account: account))
+      sign_in(account.user)
       expect(controller.view_context.current_account).to eq account
     end
   end
@@ -168,13 +168,13 @@ describe ApplicationController, type: :controller do
     end
 
     it 'does nothing if user who signed in is not suspended' do
-      sign_in(Fabricate(:user, account: Fabricate(:account, suspended: false)))
+      sign_in(Fabricate(:account, suspended: false).user)
       get 'success'
       expect(response).to have_http_status(200)
     end
 
     it 'redirects to account status page' do
-      sign_in(Fabricate(:user, account: Fabricate(:account, suspended: true)))
+      sign_in(Fabricate(:account, suspended: true).user)
       get 'success'
       expect(response).to redirect_to(edit_user_registration_path)
     end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index ccf304a93..0ebf6641f 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -228,7 +228,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
     end
 
     it 'does nothing if user already exists' do
-      Fabricate(:user, account: Fabricate(:account, username: 'test'))
+      Fabricate(:account, username: 'test')
       subject
     end
 
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index d03ae51e8..64ec7b794 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -37,8 +37,11 @@ RSpec.describe Auth::SessionsController, type: :controller do
     end
 
     context 'with a suspended user' do
+      before do
+        user.account.suspend!
+      end
+
       it 'redirects to home after sign out' do
-        Fabricate(:account, user: user, suspended: true)
         sign_in(user, scope: :user)
         delete :destroy
 
@@ -78,8 +81,8 @@ RSpec.describe Auth::SessionsController, type: :controller do
       end
 
       context 'using a valid email and existing user' do
-        let(:user) do
-          account = Fabricate.build(:account, username: 'pam_user1')
+        let!(:user) do
+          account = Fabricate.build(:account, username: 'pam_user1', user: nil)
           account.save!(validate: false)
           user = Fabricate(:user, email: 'pam@example.com', password: nil, account: account, external: true)
           user
@@ -206,6 +209,38 @@ RSpec.describe Auth::SessionsController, type: :controller do
           end
         end
 
+        context 'using email and password after an unfinished log-in attempt to a 2FA-protected account' do
+          let!(:other_user) do
+            Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+          end
+
+          before do
+            post :create, params: { user: { email: other_user.email, password: other_user.password } }
+            post :create, params: { user: { email: user.email, password: user.password } }
+          end
+
+          it 'renders two factor authentication page' do
+            expect(controller).to render_template("two_factor")
+            expect(controller).to render_template(partial: "_otp_authentication_form")
+          end
+        end
+
+        context 'using email and password after an unfinished log-in attempt with a sign-in token challenge' do
+          let!(:other_user) do
+            Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
+          end
+
+          before do
+            post :create, params: { user: { email: other_user.email, password: other_user.password } }
+            post :create, params: { user: { email: user.email, password: user.password } }
+          end
+
+          it 'renders two factor authentication page' do
+            expect(controller).to render_template("two_factor")
+            expect(controller).to render_template(partial: "_otp_authentication_form")
+          end
+        end
+
         context 'using upcase email and password' do
           before do
             post :create, params: { user: { email: user.email.upcase, password: user.password } }
@@ -231,6 +266,21 @@ RSpec.describe Auth::SessionsController, type: :controller do
           end
         end
 
+        context 'using a valid OTP, attempting to leverage previous half-login to bypass password auth' do
+          let!(:other_user) do
+            Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
+          end
+
+          before do
+            post :create, params: { user: { email: other_user.email, password: other_user.password } }
+            post :create, params: { user: { email: user.email, otp_attempt: user.current_otp } }, session: { attempt_user_updated_at: user.updated_at.to_s }
+          end
+
+          it "doesn't log the user in" do
+            expect(controller.current_user).to be_nil
+          end
+        end
+
         context 'when the server has an decryption error' do
           before do
             allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError)
@@ -353,7 +403,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
     end
 
     context 'when 2FA is disabled and IP is unfamiliar' do
-      let!(:user) { Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', current_sign_in_at: 3.weeks.ago, current_sign_in_ip: '0.0.0.0') }
+      let!(:user) { Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', current_sign_in_at: 3.weeks.ago) }
 
       before do
         request.remote_ip  = '10.10.10.10'
@@ -380,6 +430,52 @@ RSpec.describe Auth::SessionsController, type: :controller do
         end
       end
 
+      context 'using email and password after an unfinished log-in attempt to a 2FA-protected account' do
+        let!(:other_user) do
+          Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+        end
+
+        before do
+          post :create, params: { user: { email: other_user.email, password: other_user.password } }
+          post :create, params: { user: { email: user.email, password: user.password } }
+        end
+
+        it 'renders sign in token authentication page' do
+          expect(controller).to render_template("sign_in_token")
+        end
+
+        it 'generates sign in token' do
+          expect(user.reload.sign_in_token).to_not be_nil
+        end
+
+        it 'sends sign in token e-mail' do
+          expect(UserMailer).to have_received(:sign_in_token)
+        end
+      end
+
+      context 'using email and password after an unfinished log-in attempt with a sign-in token challenge' do
+        let!(:other_user) do
+          Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
+        end
+
+        before do
+          post :create, params: { user: { email: other_user.email, password: other_user.password } }
+          post :create, params: { user: { email: user.email, password: user.password } }
+        end
+
+        it 'renders sign in token authentication page' do
+          expect(controller).to render_template("sign_in_token")
+        end
+
+        it 'generates sign in token' do
+          expect(user.reload.sign_in_token).to_not be_nil
+        end
+
+        it 'sends sign in token e-mail' do
+          expect(UserMailer).to have_received(:sign_in_token).with(user, any_args)
+        end
+      end
+
       context 'using a valid sign in token' do
         before do
           user.generate_sign_in_token && user.save
@@ -395,6 +491,22 @@ RSpec.describe Auth::SessionsController, type: :controller do
         end
       end
 
+      context 'using a valid sign in token, attempting to leverage previous half-login to bypass password auth' do
+        let!(:other_user) do
+          Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
+        end
+
+        before do
+          user.generate_sign_in_token && user.save
+          post :create, params: { user: { email: other_user.email, password: other_user.password } }
+          post :create, params: { user: { email: user.email, sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_updated_at: user.updated_at.to_s }
+        end
+
+        it "doesn't log the user in" do
+          expect(controller.current_user).to be_nil
+        end
+      end
+
       context 'using an invalid sign in token' do
         before do
           post :create, params: { user: { sign_in_token_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
@@ -410,4 +522,33 @@ RSpec.describe Auth::SessionsController, type: :controller do
       end
     end
   end
+
+  describe 'GET #webauthn_options' do
+    context 'with WebAuthn and OTP enabled as second factor' do
+      let(:domain) { "#{Rails.configuration.x.use_https ? 'https' : 'http' }://#{Rails.configuration.x.web_domain}" }
+
+      let(:fake_client) { WebAuthn::FakeClient.new(domain) }
+
+      let!(:user) do
+        Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+      end
+
+      before do
+        user.update(webauthn_id: WebAuthn.generate_user_id)
+        public_key_credential = WebAuthn::Credential.from_create(fake_client.create)
+        user.webauthn_credentials.create(
+          nickname: 'SecurityKeyNickname',
+          external_id: public_key_credential.id,
+          public_key: public_key_credential.public_key,
+          sign_count: '1000'
+        )
+        post :create, params: { user: { email: user.email, password: user.password } }
+      end
+
+      it 'returns http success' do
+        get :webauthn_options
+        expect(response).to have_http_status :ok
+      end
+    end
+  end
 end
diff --git a/spec/controllers/authorize_interactions_controller_spec.rb b/spec/controllers/authorize_interactions_controller_spec.rb
index b4ce30cd7..99f3f6ffc 100644
--- a/spec/controllers/authorize_interactions_controller_spec.rb
+++ b/spec/controllers/authorize_interactions_controller_spec.rb
@@ -16,7 +16,6 @@ describe AuthorizeInteractionsController do
 
     describe 'when signed in' do
       let(:user) { Fabricate(:user) }
-      let(:account) { Fabricate(:account, user: user) }
 
       before do
         sign_in(user)
@@ -76,7 +75,7 @@ describe AuthorizeInteractionsController do
 
     describe 'when signed in' do
       let!(:user) { Fabricate(:user) }
-      let!(:account) { user.account }
+      let(:account) { user.account }
 
       before do
         sign_in(user)
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index 7ea214a7d..99975f4c4 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -11,13 +11,36 @@ describe ApplicationController, type: :controller do
     end
   end
 
+  around do |example|
+    registrations_mode = Setting.registrations_mode
+    example.run
+    Setting.registrations_mode = registrations_mode
+  end
+
   before do
     routes.draw { get 'success' => 'anonymous#success' }
   end
 
+  context 'when account is unconfirmed' do
+    it 'returns http not found' do
+      account = Fabricate(:user, confirmed_at: nil).account
+      get 'success', params: { account_username: account.username }
+      expect(response).to have_http_status(404)
+    end
+  end
+
+  context 'when account is not approved' do
+    it 'returns http not found' do
+      Setting.registrations_mode = 'approved'
+      account = Fabricate(:user, approved: false).account
+      get 'success', params: { account_username: account.username }
+      expect(response).to have_http_status(404)
+    end
+  end
+
   context 'when account is suspended' do
     it 'returns http gone' do
-      account = Fabricate(:account, suspended: true, user: Fabricate(:user))
+      account = Fabricate(:account, suspended: true)
       get 'success', params: { account_username: account.username }
       expect(response).to have_http_status(410)
     end
@@ -33,19 +56,19 @@ describe ApplicationController, type: :controller do
 
   context 'when account is not suspended' do
     it 'assigns @account' do
-      account = Fabricate(:account, user: Fabricate(:user))
+      account = Fabricate(:account)
       get 'success', params: { account_username: account.username }
       expect(assigns(:account)).to eq account
     end
 
     it 'sets link headers' do
-      account = Fabricate(:account, username: 'username', user: Fabricate(:user))
+      account = Fabricate(:account, username: 'username')
       get 'success', params: { account_username: 'username' }
       expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/jrd+json", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
     end
 
     it 'returns http success' do
-      account = Fabricate(:account, user: Fabricate(:user))
+      account = Fabricate(:account)
       get 'success', params: { account_username: account.username }
       expect(response).to have_http_status(200)
     end
diff --git a/spec/controllers/concerns/accountable_concern_spec.rb b/spec/controllers/concerns/accountable_concern_spec.rb
index e3c06b494..5c5180bc2 100644
--- a/spec/controllers/concerns/accountable_concern_spec.rb
+++ b/spec/controllers/concerns/accountable_concern_spec.rb
@@ -12,14 +12,14 @@ RSpec.describe AccountableConcern do
     end
   end
 
-  let(:user)   { Fabricate(:user, account: Fabricate(:account)) }
-  let(:target) { Fabricate(:user, account: Fabricate(:account)) }
-  let(:hoge)   { Hoge.new(user.account) }
+  let(:user)   { Fabricate(:account) }
+  let(:target) { Fabricate(:account) }
+  let(:hoge)   { Hoge.new(user) }
 
   describe '#log_action' do
     it 'creates Admin::ActionLog' do
       expect do
-        hoge.log_action(:create, target.account)
+        hoge.log_action(:create, target)
       end.to change { Admin::ActionLog.count }.by(1)
     end
   end
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
index 006274169..eb095cf30 100644
--- a/spec/controllers/follower_accounts_controller_spec.rb
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe FollowerAccountsController do
   render_views
 
-  let(:alice) { Fabricate(:user).account }
+  let(:alice) { Fabricate(:account) }
   let(:follower0) { Fabricate(:account) }
   let(:follower1) { Fabricate(:account) }
 
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
index 7ec0e3d06..af5ce0787 100644
--- a/spec/controllers/following_accounts_controller_spec.rb
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 describe FollowingAccountsController do
   render_views
 
-  let(:alice) { Fabricate(:user).account }
+  let(:alice) { Fabricate(:account) }
   let(:followee0) { Fabricate(:account) }
   let(:followee1) { Fabricate(:account) }
 
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
index 941f1dd91..70c5c42c5 100644
--- a/spec/controllers/home_controller_spec.rb
+++ b/spec/controllers/home_controller_spec.rb
@@ -8,8 +8,10 @@ RSpec.describe HomeController, type: :controller do
 
     context 'when not signed in' do
       context 'when requested path is tag timeline' do
-        before { @request.path = '/web/timelines/tag/name' }
-        it { is_expected.to redirect_to '/tags/name' }
+        it 'redirects to the tag\'s permalink' do
+          @request.path = '/web/timelines/tag/name'
+          is_expected.to redirect_to '/tags/name'
+        end
       end
 
       it 'redirects to about page' do
diff --git a/spec/controllers/media_controller_spec.rb b/spec/controllers/media_controller_spec.rb
index 2925aed59..efd15b5b4 100644
--- a/spec/controllers/media_controller_spec.rb
+++ b/spec/controllers/media_controller_spec.rb
@@ -6,33 +6,60 @@ describe MediaController do
   render_views
 
   describe '#show' do
-    it 'redirects to the file url when attached to a status' do
-      status = Fabricate(:status)
-      media_attachment = Fabricate(:media_attachment, status: status)
-      get :show, params: { id: media_attachment.to_param }
+    it 'raises when shortcode cant be found' do
+      get :show, params: { id: 'missing' }
 
-      expect(response).to redirect_to(media_attachment.file.url(:original))
+      expect(response).to have_http_status(404)
     end
 
-    it 'responds with missing when there is not an attached status' do
-      media_attachment = Fabricate(:media_attachment, status: nil)
-      get :show, params: { id: media_attachment.to_param }
+    context 'when the media attachment has a shortcode' do
+      it 'redirects to the file url when attached to a status' do
+        status = Fabricate(:status)
+        media_attachment = Fabricate(:media_attachment, status: status, shortcode: 'OI6IgDzG-nYTqvDQ994')
+        get :show, params: { id: media_attachment.to_param }
 
-      expect(response).to have_http_status(404)
-    end
+        expect(response).to redirect_to(media_attachment.file.url(:original))
+      end
 
-    it 'raises when shortcode cant be found' do
-      get :show, params: { id: 'missing' }
+      it 'responds with missing when there is not an attached status' do
+        media_attachment = Fabricate(:media_attachment, status: nil, shortcode: 'OI6IgDzG-nYTqvDQ994')
+        get :show, params: { id: media_attachment.to_param }
 
-      expect(response).to have_http_status(404)
+        expect(response).to have_http_status(404)
+      end
+
+      it 'raises when not permitted to view' do
+        status = Fabricate(:status, visibility: :direct)
+        media_attachment = Fabricate(:media_attachment, status: status, shortcode: 'OI6IgDzG-nYTqvDQ994')
+        get :show, params: { id: media_attachment.to_param }
+
+        expect(response).to have_http_status(404)
+      end
     end
 
-    it 'raises when not permitted to view' do
-      status = Fabricate(:status, visibility: :direct)
-      media_attachment = Fabricate(:media_attachment, status: status)
-      get :show, params: { id: media_attachment.to_param }
+    context 'when the media attachment has no shortcode' do
+      it 'redirects to the file url when attached to a status' do
+        status = Fabricate(:status)
+        media_attachment = Fabricate(:media_attachment, status: status)
+        get :show, params: { id: media_attachment.to_param }
 
-      expect(response).to have_http_status(404)
+        expect(response).to redirect_to(media_attachment.file.url(:original))
+      end
+
+      it 'responds with missing when there is not an attached status' do
+        media_attachment = Fabricate(:media_attachment, status: nil)
+        get :show, params: { id: media_attachment.to_param }
+
+        expect(response).to have_http_status(404)
+      end
+
+      it 'raises when not permitted to view' do
+        status = Fabricate(:status, visibility: :direct)
+        media_attachment = Fabricate(:media_attachment, status: status)
+        get :show, params: { id: media_attachment.to_param }
+
+        expect(response).to have_http_status(404)
+      end
     end
   end
 end
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
index 8d5c4774f..cd36ecc35 100644
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -17,7 +17,7 @@ describe Settings::DeletesController do
       end
 
       context 'when suspended' do
-        let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
+        let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
 
         it 'returns http forbidden' do
           get :show
@@ -59,8 +59,12 @@ describe Settings::DeletesController do
           expect(user.account.reload).to be_suspended
         end
 
+        it 'does not create an email block' do
+          expect(CanonicalEmailBlock.block?(user.email)).to be false
+        end
+
         context 'when suspended' do
-          let(:user) { Fabricate(:user, account_attributes: { username: 'alice', suspended_at: Time.now.utc }) }
+          let(:user) { Fabricate(:user, account_attributes: { suspended_at: Time.now.utc }) }
 
           it 'returns http forbidden' do
             expect(response).to have_http_status(403)
diff --git a/spec/controllers/settings/identity_proofs_controller_spec.rb b/spec/controllers/settings/identity_proofs_controller_spec.rb
deleted file mode 100644
index 16f236227..000000000
--- a/spec/controllers/settings/identity_proofs_controller_spec.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-require 'rails_helper'
-
-describe Settings::IdentityProofsController do
-  include RoutingHelper
-  render_views
-
-  let(:user) { Fabricate(:user) }
-  let(:valid_token) { '1'*66 }
-  let(:kbname) { 'kbuser' }
-  let(:provider) { 'keybase' }
-  let(:findable_id) { Faker::Number.number(digits: 5) }
-  let(:unfindable_id) { Faker::Number.number(digits: 5) }
-  let(:new_proof_params) do
-    { provider: provider, provider_username: kbname, token: valid_token, username: user.account.username }
-  end
-  let(:status_text) { "i just proved that i am also #{kbname} on #{provider}." }
-  let(:status_posting_params) do
-    { post_status: '0', status_text: status_text }
-  end
-  let(:postable_params) do
-    { account_identity_proof: new_proof_params.merge(status_posting_params) }
-  end
-
-  before do
-    allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:status) { { 'proof_valid' => true, 'proof_live' => true } }
-    sign_in user, scope: :user
-  end
-
-  describe 'new proof creation' do
-    context 'GET #new' do
-      before do
-        allow_any_instance_of(ProofProvider::Keybase::Badge).to receive(:avatar_url) { full_pack_url('media/images/void.png') }
-      end
-
-      context 'with all of the correct params' do
-        it 'renders the template' do
-          get :new, params: new_proof_params
-          expect(response).to render_template(:new)
-        end
-      end
-
-      context 'without any params' do
-        it 'redirects to :index' do
-          get :new, params: {}
-          expect(response).to redirect_to settings_identity_proofs_path
-        end
-      end
-
-      context 'with params to prove a different, not logged-in user' do
-        let(:wrong_user_params) { new_proof_params.merge(username: 'someone_else') }
-
-        it 'shows a helpful alert' do
-          get :new, params: wrong_user_params
-          expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.wrong_user', proving: 'someone_else', current: user.account.username)
-        end
-      end
-
-      context 'with params to prove the same username cased differently' do
-        let(:capitalized_username) { new_proof_params.merge(username: user.account.username.upcase) }
-
-        it 'renders the new template' do
-          get :new, params: capitalized_username
-          expect(response).to render_template(:new)
-        end
-      end
-    end
-
-    context 'POST #create' do
-      context 'when saving works' do
-        before do
-          allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
-          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
-          allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
-        end
-
-        it 'serializes a ProofProvider::Keybase::Worker' do
-          expect(ProofProvider::Keybase::Worker).to receive(:perform_async)
-          post :create, params: postable_params
-        end
-
-        it 'delegates redirection to the proof provider' do
-          expect_any_instance_of(AccountIdentityProof).to receive(:on_success_path)
-          post :create, params: postable_params
-          expect(response).to redirect_to root_url
-        end
-
-        it 'does not post a status' do
-          expect(PostStatusService).not_to receive(:new)
-          post :create, params: postable_params
-        end
-
-        context 'and the user has requested to post a status' do
-          let(:postable_params_with_status) do
-            postable_params.tap { |p| p[:account_identity_proof][:post_status] = '1' }
-          end
-
-          it 'posts a status' do
-            expect_any_instance_of(PostStatusService).to receive(:call).with(user.account, text: status_text)
-
-            post :create, params: postable_params_with_status
-          end
-        end
-      end
-
-      context 'when saving fails' do
-        before do
-          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { false }
-        end
-
-        it 'redirects to :index' do
-          post :create, params: postable_params
-          expect(response).to redirect_to settings_identity_proofs_path
-        end
-
-        it 'flashes a helpful message' do
-          post :create, params: postable_params
-          expect(flash[:alert]).to eq I18n.t('identity_proofs.errors.failed', provider: 'Keybase')
-        end
-      end
-
-      context 'it can also do an update if the provider and username match an existing proof' do
-        before do
-          allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
-          allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
-          Fabricate(:account_identity_proof, account: user.account, provider: provider, provider_username: kbname)
-          allow_any_instance_of(AccountIdentityProof).to receive(:on_success_path) { root_url }
-        end
-
-        it 'calls update with the new token' do
-          expect_any_instance_of(AccountIdentityProof).to receive(:save) do |proof|
-            expect(proof.token).to eq valid_token
-          end
-
-          post :create, params: postable_params
-        end
-      end
-    end
-  end
-
-  describe 'GET #index' do
-    context 'with no existing proofs' do
-      it 'shows the helpful explanation' do
-        get :index
-        expect(response.body).to match I18n.t('identity_proofs.explanation_html')
-      end
-    end
-
-    context 'with two proofs' do
-      before do
-        allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
-        @proof1 = Fabricate(:account_identity_proof, account: user.account)
-        @proof2 = Fabricate(:account_identity_proof, account: user.account)
-        allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
-        allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
-      end
-
-      it 'has the first proof username on the page' do
-        get :index
-        expect(response.body).to match /#{Regexp.quote(@proof1.provider_username)}/
-      end
-
-      it 'has the second proof username on the page' do
-        get :index
-        expect(response.body).to match /#{Regexp.quote(@proof2.provider_username)}/
-      end
-    end
-  end
-
-  describe 'DELETE #destroy' do
-    before do
-      allow_any_instance_of(ProofProvider::Keybase::Verifier).to receive(:valid?) { true }
-      @proof1 = Fabricate(:account_identity_proof, account: user.account)
-      allow_any_instance_of(AccountIdentityProof).to receive(:badge) { double(avatar_url: '', profile_url: '', proof_url: '') }
-      allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
-      delete :destroy, params: { id: @proof1.id }
-    end
-
-    it 'redirects to :index' do
-      expect(response).to redirect_to settings_identity_proofs_path
-    end
-
-    it 'removes the proof' do
-      expect(AccountIdentityProof.where(id: @proof1.id).count).to eq 0
-    end
-  end
-end
diff --git a/spec/controllers/settings/migrations_controller_spec.rb b/spec/controllers/settings/migrations_controller_spec.rb
index 048d9de8d..35c5747a0 100644
--- a/spec/controllers/settings/migrations_controller_spec.rb
+++ b/spec/controllers/settings/migrations_controller_spec.rb
@@ -19,8 +19,7 @@ describe Settings::MigrationsController do
     context 'when user is sign in' do
       subject { get :show }
 
-      let(:user) { Fabricate(:user, account: account) }
-      let(:account) { Fabricate(:account, moved_to_account: moved_to_account) }
+      let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user }
 
       before { sign_in user, scope: :user }
 
diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb
index 1ac286254..ee3aec815 100644
--- a/spec/controllers/settings/profiles_controller_spec.rb
+++ b/spec/controllers/settings/profiles_controller_spec.rb
@@ -3,9 +3,11 @@ require 'rails_helper'
 RSpec.describe Settings::ProfilesController, type: :controller do
   render_views
 
+  let!(:user) { Fabricate(:user) }
+  let(:account) { user.account }
+
   before do
-    @user = Fabricate(:user)
-    sign_in @user, scope: :user
+    sign_in user, scope: :user
   end
 
   describe "GET #show" do
@@ -16,10 +18,12 @@ RSpec.describe Settings::ProfilesController, type: :controller do
   end
 
   describe 'PUT #update' do
+    before do
+      user.account.update(display_name: 'Old name')
+    end
+
     it 'updates the user profile' do
       allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
-      account = Fabricate(:account, user: @user, display_name: 'Old name')
-
       put :update, params: { account: { display_name: 'New name' } }
       expect(account.reload.display_name).to eq 'New name'
       expect(response).to redirect_to(settings_profile_path)
@@ -30,7 +34,6 @@ RSpec.describe Settings::ProfilesController, type: :controller do
   describe 'PUT #update with new profile image' do
     it 'updates profile image' do
       allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
-      account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
       expect(account.avatar.instance.avatar_file_name).to be_nil
 
       put :update, params: { account: { avatar: fixture_file_upload('avatar.gif', 'image/gif') } }
@@ -43,7 +46,6 @@ RSpec.describe Settings::ProfilesController, type: :controller do
   describe 'PUT #update with oversized image' do
     it 'gives the user an error message' do
       allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
-      account = Fabricate(:account, user: @user, display_name: 'AvatarTest')
       put :update, params: { account: { avatar: fixture_file_upload('4096x4097.png', 'image/png') } }
       expect(response.body).to include('images are not supported')
     end
diff --git a/spec/controllers/statuses_cleanup_controller_spec.rb b/spec/controllers/statuses_cleanup_controller_spec.rb
new file mode 100644
index 000000000..924709260
--- /dev/null
+++ b/spec/controllers/statuses_cleanup_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'rails_helper'
+
+RSpec.describe StatusesCleanupController, type: :controller do
+  render_views
+
+  before do
+    @user = Fabricate(:user)
+    sign_in @user, scope: :user
+  end
+
+  describe "GET #show" do
+    it "returns http success" do
+      get :show
+      expect(response).to have_http_status(200)
+    end
+  end
+
+  describe 'PUT #update' do
+    it 'updates the account status cleanup policy' do
+      put :update, params: { account_statuses_cleanup_policy: { enabled: true, min_status_age: 2.weeks.seconds, keep_direct: false, keep_polls: true } }
+      expect(response).to redirect_to(statuses_cleanup_path)
+      expect(@user.account.statuses_cleanup_policy.enabled).to eq true
+      expect(@user.account.statuses_cleanup_policy.keep_direct).to eq false
+      expect(@user.account.statuses_cleanup_policy.keep_polls).to eq true
+    end
+  end
+end
diff --git a/spec/controllers/well_known/keybase_proof_config_controller_spec.rb b/spec/controllers/well_known/keybase_proof_config_controller_spec.rb
deleted file mode 100644
index 00f251c3c..000000000
--- a/spec/controllers/well_known/keybase_proof_config_controller_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'rails_helper'
-
-describe WellKnown::KeybaseProofConfigController, type: :controller do
-  render_views
-
-  describe 'GET #show' do
-    it 'renders json' do
-      get :show
-
-      expect(response).to have_http_status(200)
-      expect(response.media_type).to eq 'application/json'
-      expect { JSON.parse(response.body) }.not_to raise_exception
-    end
-  end
-end
diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb
index 1075456f3..8574d369d 100644
--- a/spec/controllers/well_known/webfinger_controller_spec.rb
+++ b/spec/controllers/well_known/webfinger_controller_spec.rb
@@ -24,6 +24,10 @@ describe WellKnown::WebfingerController, type: :controller do
         expect(response).to have_http_status(200)
       end
 
+      it 'does not set a Vary header' do
+        expect(response.headers['Vary']).to be_nil
+      end
+
       it 'returns application/jrd+json' do
         expect(response.media_type).to eq 'application/jrd+json'
       end
diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb
index ab900c5fa..f1cce281c 100644
--- a/spec/fabricators/account_fabricator.rb
+++ b/spec/fabricators/account_fabricator.rb
@@ -10,4 +10,5 @@ Fabricator(:account) do
   private_key         { private_key }
   suspended_at        { |attrs| attrs[:suspended] ? Time.now.utc : nil }
   silenced_at         { |attrs| attrs[:silenced] ? Time.now.utc : nil }
+  user                { |attrs| attrs[:domain].nil? ? Fabricate.build(:user, account: nil) : nil }
 end
diff --git a/spec/fabricators/account_identity_proof_fabricator.rb b/spec/fabricators/account_identity_proof_fabricator.rb
deleted file mode 100644
index 7b932fa96..000000000
--- a/spec/fabricators/account_identity_proof_fabricator.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-Fabricator(:account_identity_proof) do
-  account
-  provider 'keybase'
-  provider_username { sequence(:provider_username) { |i| "#{Faker::Lorem.characters(number: 15)}" } }
-  token { sequence(:token) { |i| "#{i}#{Faker::Crypto.sha1()*2}"[0..65] } }
-  verified false
-  live false
-end
diff --git a/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb b/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb
new file mode 100644
index 000000000..29cf1d133
--- /dev/null
+++ b/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:account_statuses_cleanup_policy) do
+  account
+end
diff --git a/spec/fabricators/preview_card_fabricator.rb b/spec/fabricators/preview_card_fabricator.rb
new file mode 100644
index 000000000..f119c117d
--- /dev/null
+++ b/spec/fabricators/preview_card_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:preview_card) do
+  url { Faker::Internet.url }
+  title { Faker::Lorem.sentence }
+  description { Faker::Lorem.paragraph }
+  type 'link'
+end
diff --git a/spec/fabricators/report_fabricator.rb b/spec/fabricators/report_fabricator.rb
index 5bd4a63f0..2c7101e09 100644
--- a/spec/fabricators/report_fabricator.rb
+++ b/spec/fabricators/report_fabricator.rb
@@ -1,6 +1,6 @@
 Fabricator(:report) do
   account
-  target_account { Fabricate(:account) }
-  comment      "You nasty"
-  action_taken false
+  target_account  { Fabricate(:account) }
+  comment         "You nasty"
+  action_taken_at nil
 end
diff --git a/spec/fabricators/status_edit_fabricator.rb b/spec/fabricators/status_edit_fabricator.rb
new file mode 100644
index 000000000..21b793747
--- /dev/null
+++ b/spec/fabricators/status_edit_fabricator.rb
@@ -0,0 +1,7 @@
+Fabricator(:status_edit) do
+  status                    nil
+  account                   nil
+  text                      "MyText"
+  spoiler_text              "MyText"
+  media_attachments_changed false
+end
\ No newline at end of file
diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb
index 8f5956501..10ad2c53a 100644
--- a/spec/fabricators/user_fabricator.rb
+++ b/spec/fabricators/user_fabricator.rb
@@ -1,5 +1,5 @@
 Fabricator(:user) do
-  account
+  account      { Fabricate.build(:account, user: nil) }
   email        { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } }
   password     "123456789"
   confirmed_at { Time.zone.now }
diff --git a/spec/fixtures/files/boop.ogg b/spec/fixtures/files/boop.ogg
new file mode 100644
index 000000000..23cbbedb1
--- /dev/null
+++ b/spec/fixtures/files/boop.ogg
Binary files differdiff --git a/spec/fixtures/requests/oembed_youtube.html b/spec/fixtures/requests/oembed_youtube.html
new file mode 100644
index 000000000..1508e4dd9
--- /dev/null
+++ b/spec/fixtures/requests/oembed_youtube.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link rel="alternate" type="application/json+oembed" href="https://www.youtube.com/oembed?format=json&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIPSbNdBmWKE" title="What is Mastodon?">
+  </head>
+  <body></body>
+</html>
diff --git a/spec/fixtures/xml/mastodon.atom b/spec/fixtures/xml/mastodon.atom
deleted file mode 100644
index 92921a938..000000000
--- a/spec/fixtures/xml/mastodon.atom
+++ /dev/null
@@ -1,261 +0,0 @@
-<?xml version="1.0"?>
-<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia">
-  <id>http://kickass.zone/users/localhost.atom</id>
-  <title>::1</title>
-  <updated>2016-10-10T13:29:56Z</updated>
-  <logo>http://kickass.zone/system/accounts/avatars/000/000/001/medium/eris.png</logo>
-  <author>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-    <uri>http://kickass.zone/users/localhost</uri>
-    <name>localhost</name>
-    <email>localhost@kickass.zone</email>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost"/>
-    <link rel="avatar" type="image/png" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/001/large/eris.png"/>
-    <link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/001/medium/eris.png"/>
-    <link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/001/small/eris.png"/>
-    <poco:preferredUsername>localhost</poco:preferredUsername>
-    <poco:displayName>::1</poco:displayName>
-  </author>
-  <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost"/>
-  <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost.atom"/>
-  <link rel="hub" href="https://pubsubhubbub.superfeedr.com"/>
-  <link rel="salmon" href="http://kickass.zone/api/salmon/1"/>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=7:objectType=Follow</id>
-    <published>2016-10-10T13:29:56Z</published>
-    <updated>2016-10-10T13:29:56Z</updated>
-    <title>localhost started following kat@mastodon.social</title>
-    <content type="html">localhost started following kat@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/12.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/12"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://mastodon.social/users/kat</uri>
-      <name>kat</name>
-      <email>kat@mastodon.social</email>
-      <summary>#trans #queer</summary>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/kat"/>
-      <link rel="avatar" type="image/jpeg" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/016/large/kat-20150403T124737-b2mbt44.jpg"/>
-      <link rel="avatar" type="image/jpeg" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/016/medium/kat-20150403T124737-b2mbt44.jpg"/>
-      <link rel="avatar" type="image/jpeg" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/016/small/kat-20150403T124737-b2mbt44.jpg"/>
-      <poco:preferredUsername>kat</poco:preferredUsername>
-      <poco:displayName>Kat</poco:displayName>
-      <poco:note>#trans #queer</poco:note>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=3:objectType=Favourite</id>
-    <published>2016-10-10T13:29:26Z</published>
-    <updated>2016-10-10T13:29:26Z</updated>
-    <title>localhost favourited a status by kat@mastodon.social</title>
-    <content type="html">localhost favourited a status by kat@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/11.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/11"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <thr:in-reply-to ref="tag:mastodon.social,2016-10-10:objectId=22833:objectType=Status" href="https://mastodon.social/users/kat/updates/16543" type="text/html"/>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
-      <id>tag:mastodon.social,2016-10-10:objectId=22833:objectType=Status</id>
-      <title>@localhost  oooh more mastodons 	&#x2764;</title>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/kat/updates/16543"/>
-      <content type="html">&lt;p&gt;&lt;a href="http://kickass.zone/users/localhost"&gt;@localhost&lt;/a&gt;  oooh more mastodons 	&#x2764;&lt;/p&gt;</content>
-      <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-      <published>2016-10-10T13:23:35Z</published>
-      <updated>2016-10-10T13:23:35Z</updated>
-      <author>
-        <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-        <uri>https://mastodon.social/users/kat</uri>
-        <name>kat</name>
-        <email>kat@mastodon.social</email>
-        <summary>#trans #queer</summary>
-        <link rel="alternate" type="text/html" href="https://mastodon.social/users/kat"/>
-        <link rel="avatar" type="image/jpeg" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/016/large/kat-20150403T124737-b2mbt44.jpg"/>
-        <link rel="avatar" type="image/jpeg" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/016/medium/kat-20150403T124737-b2mbt44.jpg"/>
-        <link rel="avatar" type="image/jpeg" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/016/small/kat-20150403T124737-b2mbt44.jpg"/>
-        <poco:preferredUsername>kat</poco:preferredUsername>
-        <poco:displayName>Kat</poco:displayName>
-        <poco:note>#trans #queer</poco:note>
-      </author>
-      <link rel="mentioned" href="http://kickass.zone/users/localhost"/>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=2:objectType=Favourite</id>
-    <published>2016-10-10T13:13:15Z</published>
-    <updated>2016-10-10T13:13:15Z</updated>
-    <title>localhost favourited a status by Gargron@mastodon.social</title>
-    <content type="html">localhost favourited a status by Gargron@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/10.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/10"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <thr:in-reply-to ref="tag:mastodon.social,2016-10-10:objectId=22825:objectType=Status" href="https://mastodon.social/users/Gargron/updates/16538" type="text/html"/>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-      <id>tag:mastodon.social,2016-10-10:objectId=22825:objectType=Status</id>
-      <title>Deployed some fixes</title>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/Gargron/updates/16538"/>
-      <content type="html">&lt;p&gt;Deployed some fixes&lt;/p&gt;</content>
-      <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-      <published>2016-10-10T13:10:37Z</published>
-      <updated>2016-10-10T13:10:37Z</updated>
-      <author>
-        <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-        <uri>https://mastodon.social/users/Gargron</uri>
-        <name>Gargron</name>
-        <email>Gargron@mastodon.social</email>
-        <summary>Developer of Mastodon, a GNU social alternative: https://github.com/tootsuite/mastodon</summary>
-        <link rel="alternate" type="text/html" href="https://mastodon.social/users/Gargron"/>
-        <link rel="avatar" type="image/png" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/003/large/4375_eugencommish.png"/>
-        <link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/003/medium/4375_eugencommish.png"/>
-        <link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/003/small/4375_eugencommish.png"/>
-        <poco:preferredUsername>Gargron</poco:preferredUsername>
-        <poco:displayName>Eugen</poco:displayName>
-        <poco:note>Developer of Mastodon, a GNU social alternative: https://github.com/tootsuite/mastodon</poco:note>
-      </author>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=17:objectType=Status</id>
-    <published>2016-10-10T00:41:31Z</published>
-    <updated>2016-10-10T00:41:31Z</updated>
-    <title>Social media needs MOAR cats! http://kickass.zone/media/3</title>
-    <content type="html">&lt;p&gt;Social media needs MOAR cats! &lt;a rel="nofollow noopener noreferrer" href="http://kickass.zone/media/3"&gt;http://kickass.zone/media/3&lt;/a&gt;&lt;/p&gt;</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/9.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/9"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-    <link rel="enclosure" href="http://kickass.zone/system/media_attachments/files/000/000/003/original/gizmo.jpg?1476060065" type="image/jpeg" length="108841"/>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=14:objectType=Status</id>
-    <published>2016-10-10T00:38:39Z</published>
-    <updated>2016-10-10T00:38:39Z</updated>
-    <title>http://kickass.zone/media/2</title>
-    <content type="html">&lt;p&gt;&lt;a rel="nofollow noopener noreferrer" href="http://kickass.zone/media/2"&gt;http://kickass.zone/media/2&lt;/a&gt;&lt;/p&gt;</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/8.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/8"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-    <link rel="enclosure" href="http://kickass.zone/system/media_attachments/files/000/000/002/original/morpheus_linux.jpg?1476059910" type="image/jpeg" length="191816"/>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=12:objectType=Status</id>
-    <published>2016-10-10T00:37:49Z</published>
-    <updated>2016-10-10T00:37:49Z</updated>
-    <title/>
-    <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/7.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/7"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=4:objectType=Follow</id>
-    <published>2016-10-10T00:23:07Z</published>
-    <updated>2016-10-10T00:23:07Z</updated>
-    <title>localhost started following bignimbus@mastodon.social</title>
-    <content type="html">localhost started following bignimbus@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/6.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/6"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://mastodon.social/users/bignimbus</uri>
-      <name>bignimbus</name>
-      <email>bignimbus@mastodon.social</email>
-      <summary>jdauriemma.com</summary>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/bignimbus"/>
-      <link rel="avatar" type="image/png" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/004/large/jeff_avatar.png"/>
-      <link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/004/medium/jeff_avatar.png"/>
-      <link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/004/small/jeff_avatar.png"/>
-      <poco:preferredUsername>bignimbus</poco:preferredUsername>
-      <poco:displayName>Jeff Auriemma</poco:displayName>
-      <poco:note>jdauriemma.com</poco:note>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=2:objectType=Follow</id>
-    <published>2016-10-10T00:14:18Z</published>
-    <updated>2016-10-10T00:14:18Z</updated>
-    <title>localhost started following Gargron@mastodon.social</title>
-    <content type="html">localhost started following Gargron@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/5.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/5"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://mastodon.social/users/Gargron</uri>
-      <name>Gargron</name>
-      <email>Gargron@mastodon.social</email>
-      <summary>Developer of Mastodon, a GNU social alternative: https://github.com/tootsuite/mastodon</summary>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/Gargron"/>
-      <link rel="avatar" type="image/png" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/003/large/4375_eugencommish.png"/>
-      <link rel="avatar" type="image/png" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/003/medium/4375_eugencommish.png"/>
-      <link rel="avatar" type="image/png" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/003/small/4375_eugencommish.png"/>
-      <poco:preferredUsername>Gargron</poco:preferredUsername>
-      <poco:displayName>Eugen</poco:displayName>
-      <poco:note>Developer of Mastodon, a GNU social alternative: https://github.com/tootsuite/mastodon</poco:note>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=1:objectType=Follow</id>
-    <published>2016-10-10T00:09:09Z</published>
-    <updated>2016-10-10T00:09:09Z</updated>
-    <title>localhost started following abc@mastodon.social</title>
-    <content type="html">localhost started following abc@mastodon.social</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/4.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/4"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-    <activity:object>
-      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
-      <uri>https://mastodon.social/users/abc</uri>
-      <name>abc</name>
-      <email>abc@mastodon.social</email>
-      <link rel="alternate" type="text/html" href="https://mastodon.social/users/abc"/>
-      <link rel="avatar" type="image/jpeg" media:width="300" media:height="300" href="http://kickass.zone/system/accounts/avatars/000/000/002/large/cbm64_80x80.jpg"/>
-      <link rel="avatar" type="image/jpeg" media:width="96" media:height="96" href="http://kickass.zone/system/accounts/avatars/000/000/002/medium/cbm64_80x80.jpg"/>
-      <link rel="avatar" type="image/jpeg" media:width="48" media:height="48" href="http://kickass.zone/system/accounts/avatars/000/000/002/small/cbm64_80x80.jpg"/>
-      <poco:preferredUsername>abc</poco:preferredUsername>
-      <poco:displayName>abc</poco:displayName>
-    </activity:object>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=3:objectType=Status</id>
-    <published>2016-10-10T00:02:47Z</published>
-    <updated>2016-10-10T00:02:47Z</updated>
-    <title/>
-    <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/3.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/3"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=2:objectType=Status</id>
-    <published>2016-10-10T00:02:18Z</published>
-    <updated>2016-10-10T00:02:18Z</updated>
-    <title>Yes, that was the obligatory first post. :)</title>
-    <content type="html">&lt;p&gt;Yes, that was the obligatory first post. :)&lt;/p&gt;</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/2.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/2"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
-    <thr:in-reply-to ref="tag:kickass.zone,2016-10-10:objectId=1:objectType=Status" href="http://kickass.zone/users/localhost/updates/1" type="text/html"/>
-  </entry>
-  <entry>
-    <id>tag:kickass.zone,2016-10-10:objectId=1:objectType=Status</id>
-    <published>2016-10-10T00:01:56Z</published>
-    <updated>2016-10-10T00:01:56Z</updated>
-    <title>Hello, world!</title>
-    <content type="html">&lt;p&gt;Hello, world!&lt;/p&gt;</content>
-    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
-    <link rel="self" type="application/atom+xml" href="http://kickass.zone/users/localhost/updates/1.atom"/>
-    <link rel="alternate" type="text/html" href="http://kickass.zone/users/localhost/updates/1"/>
-    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
-  </entry>
-</feed>
diff --git a/spec/helpers/settings_helper_spec.rb b/spec/helpers/languages_helper_spec.rb
index 092c37583..6db617824 100644
--- a/spec/helpers/settings_helper_spec.rb
+++ b/spec/helpers/languages_helper_spec.rb
@@ -2,20 +2,15 @@
 
 require 'rails_helper'
 
-describe SettingsHelper do
+describe LanguagesHelper do
   describe 'the HUMAN_LOCALES constant' do
     it 'includes all I18n locales' do
-      options = I18n.available_locales
-
-      expect(described_class::HUMAN_LOCALES.keys).to include(*options)
+      expect(described_class::HUMAN_LOCALES.keys).to include(*I18n.available_locales)
     end
   end
 
   describe 'human_locale' do
     it 'finds the human readable local description from a key' do
-      # Ensure the value is as we expect
-      expect(described_class::HUMAN_LOCALES[:en]).to eq('English')
-
       expect(helper.human_locale(:en)).to eq('English')
     end
   end
diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb
index 883bab6ac..304cf2208 100644
--- a/spec/lib/activitypub/activity/accept_spec.rb
+++ b/spec/lib/activitypub/activity/accept_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe ActivityPub::Activity::Accept do
     subject { described_class.new(json, sender) }
 
     before do
+      allow(RemoteAccountRefreshWorker).to receive(:perform_async)
       Fabricate(:follow_request, account: recipient, target_account: sender)
       subject.perform
     end
@@ -34,6 +35,10 @@ RSpec.describe ActivityPub::Activity::Accept do
     it 'removes the follow request' do
       expect(recipient.requested?(sender)).to be false
     end
+
+    it 'queues a refresh' do
+      expect(RemoteAccountRefreshWorker).to have_received(:perform_async).with(sender.id)
+    end
   end
 
   context 'given a relay' do
diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb
index 16db71c88..e6408b610 100644
--- a/spec/lib/activitypub/activity/add_spec.rb
+++ b/spec/lib/activitypub/activity/add_spec.rb
@@ -1,8 +1,8 @@
 require 'rails_helper'
 
 RSpec.describe ActivityPub::Activity::Add do
-  let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured') }
-  let(:status) { Fabricate(:status, account: sender) }
+  let(:sender) { Fabricate(:account, featured_collection_url: 'https://example.com/featured', domain: 'example.com') }
+  let(:status) { Fabricate(:status, account: sender, visibility: :private) }
 
   let(:json) do
     {
@@ -24,6 +24,8 @@ RSpec.describe ActivityPub::Activity::Add do
     end
 
     context 'when status was not known before' do
+      let(:service_stub) { double }
+
       let(:json) do
         {
           '@context': 'https://www.w3.org/ns/activitystreams',
@@ -36,12 +38,40 @@ RSpec.describe ActivityPub::Activity::Add do
       end
 
       before do
-        stub_request(:get, 'https://example.com/unknown').to_return(status: 410)
+        allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_stub)
+      end
+
+      context 'when there is a local follower' do
+        before do
+          account = Fabricate(:account)
+          account.follow!(sender)
+        end
+
+        it 'fetches the status and pins it' do
+          allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil|
+            expect(uri).to eq 'https://example.com/unknown'
+            expect(id).to eq true
+            expect(on_behalf_of&.following?(sender)).to eq true
+            status
+          end
+          subject.perform
+          expect(service_stub).to have_received(:call)
+          expect(sender.pinned?(status)).to be true
+        end
       end
 
-      it 'fetches the status' do
-        subject.perform
-        expect(a_request(:get, 'https://example.com/unknown')).to have_been_made.at_least_once
+      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|
+            expect(uri).to eq 'https://example.com/unknown'
+            expect(id).to eq true
+            expect(on_behalf_of).to eq nil
+            nil
+          end
+          subject.perform
+          expect(service_stub).to have_received(:call)
+          expect(sender.pinned?(status)).to be false
+        end
       end
     end
   end
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
index 1c9bcf43b..4cd853af2 100644
--- a/spec/lib/activitypub/activity/update_spec.rb
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -4,43 +4,97 @@ RSpec.describe ActivityPub::Activity::Update do
   let!(:sender) { Fabricate(:account) }
 
   before do
-    stub_request(:get, actor_json[:outbox]).to_return(status: 404)
-    stub_request(:get, actor_json[:followers]).to_return(status: 404)
-    stub_request(:get, actor_json[:following]).to_return(status: 404)
-    stub_request(:get, actor_json[:featured]).to_return(status: 404)
-
     sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
   end
 
-  let(:modified_sender) do
-    sender.tap do |modified_sender|
-      modified_sender.display_name = 'Totally modified now'
-    end
-  end
+  subject { described_class.new(json, sender) }
 
-  let(:actor_json) do
-    ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter).as_json
-  end
+  describe '#perform' do
+    context 'with an Actor object' do
+      let(:modified_sender) do
+        sender.tap do |modified_sender|
+          modified_sender.display_name = 'Totally modified now'
+        end
+      end
 
-  let(:json) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: 'foo',
-      type: 'Update',
-      actor: ActivityPub::TagManager.instance.uri_for(sender),
-      object: actor_json,
-    }.with_indifferent_access
-  end
+      let(:actor_json) do
+        ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter).as_json
+      end
 
-  describe '#perform' do
-    subject { described_class.new(json, sender) }
+      let(:json) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: 'foo',
+          type: 'Update',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: actor_json,
+        }.with_indifferent_access
+      end
 
-    before do
-      subject.perform
+      before do
+        stub_request(:get, actor_json[:outbox]).to_return(status: 404)
+        stub_request(:get, actor_json[:followers]).to_return(status: 404)
+        stub_request(:get, actor_json[:following]).to_return(status: 404)
+        stub_request(:get, actor_json[:featured]).to_return(status: 404)
+
+        subject.perform
+      end
+
+      it 'updates profile' do
+        expect(sender.reload.display_name).to eq 'Totally modified now'
+      end
     end
 
-    it 'updates profile' do
-      expect(sender.reload.display_name).to eq 'Totally modified now'
+    context 'with a Question object' do
+      let!(:at_time) { Time.now.utc }
+      let!(:status) { Fabricate(:status, account: sender, poll: Poll.new(account: sender, options: %w(Bar Baz), cached_tallies: [0, 0], expires_at: at_time + 5.days)) }
+
+      let(:json) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: 'foo',
+          type: 'Update',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: {
+            type: 'Question',
+            id: ActivityPub::TagManager.instance.uri_for(status),
+            content: 'Foo',
+            endTime: (at_time + 5.days).iso8601,
+            oneOf: [
+              {
+                type: 'Note',
+                name: 'Bar',
+                replies: {
+                  type: 'Collection',
+                  totalItems: 0,
+                },
+              },
+
+              {
+                type: 'Note',
+                name: 'Baz',
+                replies: {
+                  type: 'Collection',
+                  totalItems: 12,
+                },
+              },
+            ],
+          },
+        }.with_indifferent_access
+      end
+
+      before do
+        status.update!(uri: ActivityPub::TagManager.instance.uri_for(status))
+        subject.perform
+      end
+
+      it 'updates poll numbers' do
+        expect(status.preloadable_poll.cached_tallies).to eq [0, 12]
+      end
+
+      it 'does not set status as edited' do
+        expect(status.edited_at).to be_nil
+      end
     end
   end
 end
diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb
index 1c5c6f0ed..606a1de2e 100644
--- a/spec/lib/activitypub/tag_manager_spec.rb
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -42,6 +42,14 @@ RSpec.describe ActivityPub::TagManager do
       expect(subject.to(status)).to eq [subject.uri_for(mentioned)]
     end
 
+    it "returns URIs of mentioned group's followers for direct statuses to groups" do
+      status    = Fabricate(:status, visibility: :direct)
+      mentioned = Fabricate(:account, domain: 'remote.org', uri: 'https://remote.org/group', followers_url: 'https://remote.org/group/followers', actor_type: 'Group')
+      status.mentions.create(account: mentioned)
+      expect(subject.to(status)).to include(subject.uri_for(mentioned))
+      expect(subject.to(status)).to include(subject.followers_uri_for(mentioned))
+    end
+
     it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
       bob    = Fabricate(:account, username: 'bob')
       alice  = Fabricate(:account, username: 'alice')
diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb
new file mode 100644
index 000000000..850857b2d
--- /dev/null
+++ b/spec/lib/link_details_extractor_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe LinkDetailsExtractor do
+  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' }
+
+    context 'when canonical URL points to another host' do
+      let(:html) { '<!doctype html><link rel="canonical" href="https://bar.com/different-article" />' }
+
+      it 'ignores the canonical URLs' do
+        expect(subject.canonical_url).to eq original_url
+      end
+    end
+
+    context 'when canonical URL points to the same host' do
+      let(:html) { '<!doctype html><link rel="canonical" href="https://foo.com/article" />' }
+
+      it 'ignores the canonical URLs' do
+        expect(subject.canonical_url).to eq 'https://foo.com/article'
+      end
+    end
+  end
+end
diff --git a/spec/lib/permalink_redirector_spec.rb b/spec/lib/permalink_redirector_spec.rb
new file mode 100644
index 000000000..b916b33b2
--- /dev/null
+++ b/spec/lib/permalink_redirector_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe PermalinkRedirector do
+  describe '#redirect_url' do
+    before do
+      account = Fabricate(:account, username: 'alice', id: 1)
+      Fabricate(:status, account: account, id: 123)
+    end
+
+    it 'returns path for legacy account links' do
+      redirector = described_class.new('web/accounts/1')
+      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
+    end
+
+    it 'returns path for legacy status links' do
+      redirector = described_class.new('web/statuses/123')
+      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
+    end
+
+    it 'returns path for legacy tag links' do
+      redirector = described_class.new('web/timelines/tag/hoge')
+      expect(redirector.redirect_path).to eq '/tags/hoge'
+    end
+
+    it 'returns path for pretty account links' do
+      redirector = described_class.new('web/@alice')
+      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice'
+    end
+
+    it 'returns path for pretty status links' do
+      redirector = described_class.new('web/@alice/123')
+      expect(redirector.redirect_path).to eq 'https://cb6e6126.ngrok.io/@alice/123'
+    end
+
+    it 'returns path for pretty tag links' do
+      redirector = described_class.new('web/tags/hoge')
+      expect(redirector.redirect_path).to eq '/tags/hoge'
+    end
+  end
+end
diff --git a/spec/lib/proof_provider/keybase/verifier_spec.rb b/spec/lib/proof_provider/keybase/verifier_spec.rb
deleted file mode 100644
index 0081a735d..000000000
--- a/spec/lib/proof_provider/keybase/verifier_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-require 'rails_helper'
-
-describe ProofProvider::Keybase::Verifier do
-  let(:my_domain) { Rails.configuration.x.local_domain }
-
-  let(:keybase_proof) do
-    local_proof = AccountIdentityProof.new(
-      provider: 'Keybase',
-      provider_username: 'cryptoalice',
-      token: '11111111111111111111111111'
-    )
-
-    described_class.new('alice', 'cryptoalice', '11111111111111111111111111', my_domain)
-  end
-
-  let(:query_params) do
-    "domain=#{my_domain}&kb_username=cryptoalice&sig_hash=11111111111111111111111111&username=alice"
-  end
-
-  describe '#valid?' do
-    let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_valid.json' }
-
-    context 'when valid' do
-      before do
-        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":true}'
-        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
-      end
-
-      it 'calls out to keybase and returns true' do
-        expect(keybase_proof.valid?).to eq true
-      end
-    end
-
-    context 'when invalid' do
-      before do
-        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_valid":false}'
-        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
-      end
-
-      it 'calls out to keybase and returns false' do
-        expect(keybase_proof.valid?).to eq false
-      end
-    end
-
-    context 'with an unexpected api response' do
-      before do
-        json_response_body = '{"status":{"code":100,"desc":"wrong size hex_id","fields":{"sig_hash":"wrong size hex_id"},"name":"INPUT_ERROR"}}'
-        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
-      end
-
-      it 'swallows the error and returns false' do
-        expect(keybase_proof.valid?).to eq false
-      end
-    end
-  end
-
-  describe '#status' do
-    let(:base_url) { 'https://keybase.io/_/api/1.0/sig/proof_live.json' }
-
-    context 'with a normal response' do
-      before do
-        json_response_body = '{"status":{"code":0,"name":"OK"},"proof_live":false,"proof_valid":true}'
-        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
-      end
-
-      it 'calls out to keybase and returns the status fields as proof_valid and proof_live' do
-        expect(keybase_proof.status).to include({ 'proof_valid' => true, 'proof_live' => false })
-      end
-    end
-
-    context 'with an unexpected keybase response' do
-      before do
-        json_response_body = '{"status":{"code":100,"desc":"missing non-optional field sig_hash","fields":{"sig_hash":"missing non-optional field sig_hash"},"name":"INPUT_ERROR"}}'
-        stub_request(:get, "#{base_url}?#{query_params}").to_return(status: 200, body: json_response_body)
-      end
-
-      it 'raises a ProofProvider::Keybase::UnexpectedResponseError' do
-        expect { keybase_proof.status }.to raise_error ProofProvider::Keybase::UnexpectedResponseError
-      end
-    end
-  end
-end
diff --git a/spec/lib/status_reach_finder_spec.rb b/spec/lib/status_reach_finder_spec.rb
new file mode 100644
index 000000000..f0c22b165
--- /dev/null
+++ b/spec/lib/status_reach_finder_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe StatusReachFinder do
+  describe '#inboxes' do
+    context 'for a local status' do
+      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') }
+
+        before do
+          status.mentions.create!(account: bob)
+        end
+
+        it 'includes the inbox of the mentioned account' do
+          expect(subject.inboxes).to include 'https://foo.bar/inbox'
+        end
+      end
+
+      context 'when it has been reblogged by a remote account' do
+        let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
+
+        before do
+          bob.statuses.create!(reblog: status)
+        end
+
+        it 'includes the inbox of the reblogger' do
+          expect(subject.inboxes).to include 'https://foo.bar/inbox'
+        end
+
+        context 'when status is not public' do
+          let(:visibility) { :private }
+
+          it 'does not include the inbox of the reblogger' do
+            expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
+          end
+        end
+      end
+
+      context 'when it has been favourited by a remote account' do
+        let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
+
+        before do
+          bob.favourites.create!(status: status)
+        end
+
+        it 'includes the inbox of the favouriter' do
+          expect(subject.inboxes).to include 'https://foo.bar/inbox'
+        end
+
+        context 'when status is not public' do
+          let(:visibility) { :private }
+
+          it 'does not include the inbox of the favouriter' do
+            expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
+          end
+        end
+      end
+
+      context 'when it has been replied to by a remote account' do
+        let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
+
+        before do
+          bob.statuses.create!(thread: status, text: 'Hoge')
+        end
+
+        context do
+          it 'includes the inbox of the replier' do
+            expect(subject.inboxes).to include 'https://foo.bar/inbox'
+          end
+        end
+
+        context 'when status is not public' do
+          let(:visibility) { :private }
+
+          it 'does not include the inbox of the replier' do
+            expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
+          end
+        end
+      end
+
+      context 'when it is a reply to a remote account' do
+        let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
+        let(:parent_status) { Fabricate(:status, account: bob) }
+
+        context do
+          it 'includes the inbox of the replied-to account' do
+            expect(subject.inboxes).to include 'https://foo.bar/inbox'
+          end
+        end
+
+        context 'when status is not public and replied-to account is not mentioned' do
+          let(:visibility) { :private }
+
+          it 'does not include the inbox of the replied-to account' do
+            expect(subject.inboxes).to_not include 'https://foo.bar/inbox'
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb
index 4a8ef7b5e..29fb586a3 100644
--- a/spec/mailers/admin_mailer_spec.rb
+++ b/spec/mailers/admin_mailer_spec.rb
@@ -4,11 +4,15 @@ require 'rails_helper'
 
 RSpec.describe AdminMailer, type: :mailer do
   describe '.new_report' do
-    let(:sender)    { Fabricate(:account, username: 'John', user: Fabricate(:user)) }
-    let(:recipient) { Fabricate(:account, username: 'Mike', user: Fabricate(:user, locale: :en)) }
+    let(:sender)    { Fabricate(:account, username: 'John') }
+    let(:recipient) { Fabricate(:account, username: 'Mike') }
     let(:report)    { Fabricate(:report, account: sender, target_account: recipient) }
     let(:mail)      { described_class.new_report(recipient, report) }
 
+    before do
+      recipient.user.update(locale: :en)
+    end
+
     it 'renders the headers' do
       expect(mail.subject).to eq("New report for cb6e6126.ngrok.io (##{report.id})")
       expect(mail.to).to eq [recipient.user_email]
diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb
index 9b645bad8..2ca4e26fa 100644
--- a/spec/mailers/notification_mailer_spec.rb
+++ b/spec/mailers/notification_mailer_spec.rb
@@ -1,7 +1,7 @@
 require "rails_helper"
 
 RSpec.describe NotificationMailer, type: :mailer do
-  let(:receiver)       { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:receiver)       { Fabricate(:user) }
   let(:sender)         { Fabricate(:account, username: 'bob') }
   let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
   let(:own_status)     { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
diff --git a/spec/mailers/previews/admin_mailer_preview.rb b/spec/mailers/previews/admin_mailer_preview.rb
index 561a56b78..75ffbbf40 100644
--- a/spec/mailers/previews/admin_mailer_preview.rb
+++ b/spec/mailers/previews/admin_mailer_preview.rb
@@ -5,4 +5,14 @@ class AdminMailerPreview < ActionMailer::Preview
   def new_pending_account
     AdminMailer.new_pending_account(Account.first, User.pending.first)
   end
+
+  # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_trending_tags
+  def new_trending_tags
+    AdminMailer.new_trending_tags(Account.first, Tag.limit(3))
+  end
+
+  # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_trending_links
+  def new_trending_links
+    AdminMailer.new_trending_links(Account.first, PreviewCard.limit(3))
+  end
 end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index 6d87fd706..69b9b971e 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -79,7 +79,7 @@ class UserMailerPreview < ActionMailer::Preview
 
   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning
   def warning
-    UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id])
+    UserMailer.warning(User.first, AccountWarning.last)
   end
 
   # Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token
diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb
index 0cdb373f6..c2bd8c220 100644
--- a/spec/models/account_filter_spec.rb
+++ b/spec/models/account_filter_spec.rb
@@ -2,10 +2,10 @@ require 'rails_helper'
 
 describe AccountFilter do
   describe 'with empty params' do
-    it 'defaults to recent local not-suspended account list' do
+    it 'excludes instance actor by default' do
       filter = described_class.new({})
 
-      expect(filter.results).to eq Account.local.without_instance_actor.recent.without_suspended
+      expect(filter.results).to eq Account.without_instance_actor
     end
   end
 
@@ -16,42 +16,4 @@ describe AccountFilter do
       expect { filter.results }.to raise_error(/wrong/)
     end
   end
-
-  describe 'with valid params' do
-    it 'combines filters on Account' do
-      filter = described_class.new(
-        by_domain: 'test.com',
-        silenced: true,
-        username: 'test',
-        display_name: 'name',
-        email: 'user@example.com',
-      )
-
-      allow(Account).to receive(:where).and_return(Account.none)
-      allow(Account).to receive(:silenced).and_return(Account.none)
-      allow(Account).to receive(:matches_display_name).and_return(Account.none)
-      allow(Account).to receive(:matches_username).and_return(Account.none)
-      allow(User).to receive(:matches_email).and_return(User.none)
-
-      filter.results
-
-      expect(Account).to have_received(:where).with(domain: 'test.com')
-      expect(Account).to have_received(:silenced)
-      expect(Account).to have_received(:matches_username).with('test')
-      expect(Account).to have_received(:matches_display_name).with('name')
-      expect(User).to have_received(:matches_email).with('user@example.com')
-    end
-
-    describe 'that call account methods' do
-      %i(local remote silenced suspended).each do |option|
-        it "delegates the #{option} option" do
-          allow(Account).to receive(option).and_return(Account.none)
-          filter = described_class.new({ option => true })
-          filter.results
-
-          expect(Account).to have_received(option).at_least(1)
-        end
-      end
-    end
-  end
 end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 03d6f5fb0..681134d49 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -5,6 +5,37 @@ RSpec.describe Account, type: :model do
     let(:bob) { Fabricate(:account, username: 'bob') }
     subject { Fabricate(:account) }
 
+    describe '#suspend!' do
+      it 'marks the account as suspended' do
+        subject.suspend!
+        expect(subject.suspended?).to be true
+      end
+
+      it 'creates a deletion request' do
+        subject.suspend!
+        expect(AccountDeletionRequest.where(account: subject).exists?).to be true
+      end
+
+      context 'when the account is of a local user' do
+        let!(:subject) { Fabricate(:user, email: 'foo+bar@domain.org').account }
+
+        it 'creates a canonical domain block' do
+          subject.suspend!
+          expect(CanonicalEmailBlock.block?(subject.user_email)).to be true
+        end
+
+        context 'when a canonical domain block already exists for that email' do
+          before do
+            Fabricate(:canonical_email_block, email: subject.user_email)
+          end
+
+          it 'does not raise an error' do
+            expect { subject.suspend! }.not_to raise_error
+          end
+        end
+      end
+    end
+
     describe '#follow!' do
       it 'creates a follow' do
         follow = subject.follow!(bob)
diff --git a/spec/models/account_statuses_cleanup_policy_spec.rb b/spec/models/account_statuses_cleanup_policy_spec.rb
new file mode 100644
index 000000000..4732ad625
--- /dev/null
+++ b/spec/models/account_statuses_cleanup_policy_spec.rb
@@ -0,0 +1,546 @@
+require 'rails_helper'
+
+RSpec.describe AccountStatusesCleanupPolicy, type: :model do
+  let(:account) { Fabricate(:account, username: 'alice', domain: nil) }
+
+  describe 'validation' do
+    it 'disallow remote accounts' do
+      account.update(domain: 'example.com')
+      account_statuses_cleanup_policy = Fabricate.build(:account_statuses_cleanup_policy, account: account)
+      account_statuses_cleanup_policy.valid?
+      expect(account_statuses_cleanup_policy).to model_have_error_on_field(:account)
+    end
+  end
+
+  describe 'save hooks' do
+    context 'when widening a policy' do
+      let!(:account_statuses_cleanup_policy) do
+        Fabricate(:account_statuses_cleanup_policy,
+          account: account,
+          keep_direct: true,
+          keep_pinned: true,
+          keep_polls: true,
+          keep_media: true,
+          keep_self_fav: true,
+          keep_self_bookmark: true,
+          min_favs: 1,
+          min_reblogs: 1
+        )
+      end
+
+      before do
+        account_statuses_cleanup_policy.record_last_inspected(42)
+      end
+
+      it 'invalidates last_inspected when widened because of keep_direct' do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of keep_pinned' do
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of keep_polls' do
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of keep_media' do
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of keep_self_fav' do
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of keep_self_bookmark' do
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of higher min_favs' do
+        account_statuses_cleanup_policy.min_favs = 5
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of disabled min_favs' do
+        account_statuses_cleanup_policy.min_favs = nil
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of higher min_reblogs' do
+        account_statuses_cleanup_policy.min_reblogs = 5
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+
+      it 'invalidates last_inspected when widened because of disable min_reblogs' do
+        account_statuses_cleanup_policy.min_reblogs = nil
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to be nil
+      end
+    end
+
+    context 'when narrowing a policy' do
+      let!(:account_statuses_cleanup_policy) do
+        Fabricate(:account_statuses_cleanup_policy,
+          account: account,
+          keep_direct: false,
+          keep_pinned: false,
+          keep_polls: false,
+          keep_media: false,
+          keep_self_fav: false,
+          keep_self_bookmark: false,
+          min_favs: nil,
+          min_reblogs: nil
+        )
+      end
+
+      it 'does not unnecessarily invalidate last_inspected' do
+        account_statuses_cleanup_policy.record_last_inspected(42)
+        account_statuses_cleanup_policy.keep_direct = true
+        account_statuses_cleanup_policy.keep_pinned = true
+        account_statuses_cleanup_policy.keep_polls = true
+        account_statuses_cleanup_policy.keep_media = true
+        account_statuses_cleanup_policy.keep_self_fav = true
+        account_statuses_cleanup_policy.keep_self_bookmark = true
+        account_statuses_cleanup_policy.min_favs = 5
+        account_statuses_cleanup_policy.min_reblogs = 5
+        account_statuses_cleanup_policy.save
+        expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+      end
+    end
+  end
+
+  describe '#record_last_inspected' do
+    let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
+
+    it 'records the given id' do
+      account_statuses_cleanup_policy.record_last_inspected(42)
+      expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+    end
+  end
+
+  describe '#invalidate_last_inspected' do
+    let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
+    let(:status) { Fabricate(:status, id: 10, account: account) }
+    subject { account_statuses_cleanup_policy.invalidate_last_inspected(status, action) }
+
+    before do
+      account_statuses_cleanup_policy.record_last_inspected(42)
+    end
+
+    context 'when the action is :unbookmark' do
+      let(:action) { :unbookmark }
+
+      context 'when the policy is not to keep self-bookmarked toots' do
+        before do
+          account_statuses_cleanup_policy.keep_self_bookmark = false
+        end
+
+        it 'does not change the recorded id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+        end
+      end
+
+      context 'when the policy is to keep self-bookmarked toots' do
+        before do
+          account_statuses_cleanup_policy.keep_self_bookmark = true
+        end
+
+        it 'records the older id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
+        end
+      end
+    end
+
+    context 'when the action is :unfav' do
+      let(:action) { :unfav }
+
+      context 'when the policy is not to keep self-favourited toots' do
+        before do
+          account_statuses_cleanup_policy.keep_self_fav = false
+        end
+
+        it 'does not change the recorded id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+        end
+      end
+
+      context 'when the policy is to keep self-favourited toots' do
+        before do
+          account_statuses_cleanup_policy.keep_self_fav = true
+        end
+
+        it 'records the older id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
+        end
+      end
+    end
+
+    context 'when the action is :unpin' do
+      let(:action) { :unpin }
+
+      context 'when the policy is not to keep pinned toots' do
+        before do
+          account_statuses_cleanup_policy.keep_pinned = false
+        end
+
+        it 'does not change the recorded id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+        end
+      end
+
+      context 'when the policy is to keep pinned toots' do
+        before do
+          account_statuses_cleanup_policy.keep_pinned = true
+        end
+
+        it 'records the older id' do
+          subject
+          expect(account_statuses_cleanup_policy.last_inspected).to eq 10
+        end
+      end
+    end
+
+    context 'when the status is more recent than the recorded inspected id' do
+      let(:action) { :unfav }
+      let(:status) { Fabricate(:status, account: account) }
+
+      it 'does not change the recorded id' do
+        subject
+        expect(account_statuses_cleanup_policy.last_inspected).to eq 42
+      end
+    end
+  end
+
+  describe '#compute_cutoff_id' do
+    let!(:unrelated_status)  { Fabricate(:status, created_at: 3.years.ago) }
+    let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
+
+    subject { account_statuses_cleanup_policy.compute_cutoff_id }
+
+    context 'when the account has posted multiple toots' do
+      let!(:very_old_status)   { Fabricate(:status, created_at: 3.years.ago, account: account) }
+      let!(:old_status)        { Fabricate(:status, created_at: 3.weeks.ago, account: account) }
+      let!(:recent_status)     { Fabricate(:status, created_at: 2.days.ago, account: account) }
+
+      it 'returns the most recent id that is still below policy age' do
+        expect(subject).to eq old_status.id
+      end
+    end
+
+    context 'when the account has not posted anything' do
+      it 'returns nil' do
+        expect(subject).to be_nil
+      end
+    end
+  end
+
+  describe '#statuses_to_delete' do
+    let!(:unrelated_status)  { Fabricate(:status, created_at: 3.years.ago) }
+    let!(:very_old_status)   { Fabricate(:status, created_at: 3.years.ago, account: account) }
+    let!(:pinned_status)     { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:direct_message)    { Fabricate(:status, created_at: 1.year.ago, account: account, visibility: :direct) }
+    let!(:self_faved)        { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:self_bookmarked)   { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:status_with_poll)  { Fabricate(:status, created_at: 1.year.ago, account: account, poll_attributes: { account: account, voters_count: 0, options: ['a', 'b'], expires_in: 2.days }) }
+    let!(:status_with_media) { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:faved4)            { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:faved5)            { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:reblogged4)        { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:reblogged5)        { Fabricate(:status, created_at: 1.year.ago, account: account) }
+    let!(:recent_status)     { Fabricate(:status, created_at: 2.days.ago, account: account) }
+
+    let!(:media_attachment)  { Fabricate(:media_attachment, account: account, status: status_with_media) }
+    let!(:status_pin)        { Fabricate(:status_pin, account: account, status: pinned_status) }
+    let!(:favourite)         { Fabricate(:favourite, account: account, status: self_faved) }
+    let!(:bookmark)          { Fabricate(:bookmark, account: account, status: self_bookmarked) }
+
+    let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
+
+    subject { account_statuses_cleanup_policy.statuses_to_delete }
+
+    before do
+      4.times { faved4.increment_count!(:favourites_count) }
+      5.times { faved5.increment_count!(:favourites_count) }
+      4.times { reblogged4.increment_count!(:reblogs_count) }
+      5.times { reblogged5.increment_count!(:reblogs_count) }
+    end
+
+    context 'when passed a max_id' do
+      let!(:old_status)               { Fabricate(:status, created_at: 1.year.ago, account: account) }
+      let!(:slightly_less_old_status) { Fabricate(:status, created_at: 6.months.ago, account: account) }
+
+      subject { account_statuses_cleanup_policy.statuses_to_delete(50, old_status.id).pluck(:id) }
+
+      it 'returns statuses including max_id' do
+        expect(subject).to include(old_status.id)
+      end
+
+      it 'returns statuses including older than max_id' do
+        expect(subject).to include(very_old_status.id)
+      end
+
+      it 'does not return statuses newer than max_id' do
+        expect(subject).to_not include(slightly_less_old_status.id)
+      end
+    end
+
+    context 'when passed a min_id' do
+      let!(:old_status)               { Fabricate(:status, created_at: 1.year.ago, account: account) }
+      let!(:slightly_less_old_status) { Fabricate(:status, created_at: 6.months.ago, account: account) }
+
+      subject { account_statuses_cleanup_policy.statuses_to_delete(50, recent_status.id, old_status.id).pluck(:id) }
+
+      it 'returns statuses including min_id' do
+        expect(subject).to include(old_status.id)
+      end
+
+      it 'returns statuses including newer than max_id' do
+        expect(subject).to include(slightly_less_old_status.id)
+      end
+
+      it 'does not return statuses older than min_id' do
+        expect(subject).to_not include(very_old_status.id)
+      end
+    end
+
+    context 'when passed a low limit' do
+      it 'only returns the limited number of items' do
+        expect(account_statuses_cleanup_policy.statuses_to_delete(1).count).to eq 1
+      end
+    end
+
+    context 'when policy is set to keep statuses more recent than 2 years' do
+      before do
+        account_statuses_cleanup_policy.min_status_age = 2.years.seconds
+      end
+
+      it 'does not return unrelated old status' do
+        expect(subject.pluck(:id)).to_not include(unrelated_status.id)
+      end
+
+      it 'returns only oldest status for deletion' do
+        expect(subject.pluck(:id)).to eq [very_old_status.id]
+      end
+    end
+
+    context 'when policy is set to keep DMs and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = true
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the old direct message for deletion' do
+        expect(subject.pluck(:id)).to_not include(direct_message.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep self-bookmarked toots and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = true
+      end
+
+      it 'does not return the old self-bookmarked message for deletion' do
+        expect(subject.pluck(:id)).to_not include(self_bookmarked.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep self-faved toots and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = true
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the old self-bookmarked message for deletion' do
+        expect(subject.pluck(:id)).to_not include(self_faved.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep toots with media and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = true
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the old message with media for deletion' do
+        expect(subject.pluck(:id)).to_not include(status_with_media.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep toots with polls and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = true
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the old poll message for deletion' do
+        expect(subject.pluck(:id)).to_not include(status_with_poll.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep pinned toots and reject everything else' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = true
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the old pinned message for deletion' do
+        expect(subject.pluck(:id)).to_not include(pinned_status.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is to not keep any special messages' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = false
+        account_statuses_cleanup_policy.keep_pinned = false
+        account_statuses_cleanup_policy.keep_polls = false
+        account_statuses_cleanup_policy.keep_media = false
+        account_statuses_cleanup_policy.keep_self_fav = false
+        account_statuses_cleanup_policy.keep_self_bookmark = false
+      end
+
+      it 'does not return the recent toot' do
+        expect(subject.pluck(:id)).to_not include(recent_status.id)
+      end
+
+      it 'does not return the unrelated toot' do
+        expect(subject.pluck(:id)).to_not include(unrelated_status.id)
+      end
+
+      it 'returns every other old status for deletion' do
+        expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id)
+      end
+    end
+
+    context 'when policy is set to keep every category of toots' do
+      before do
+        account_statuses_cleanup_policy.keep_direct = true
+        account_statuses_cleanup_policy.keep_pinned = true
+        account_statuses_cleanup_policy.keep_polls = true
+        account_statuses_cleanup_policy.keep_media = true
+        account_statuses_cleanup_policy.keep_self_fav = true
+        account_statuses_cleanup_policy.keep_self_bookmark = true
+      end
+
+      it 'does not return unrelated old status' do
+        expect(subject.pluck(:id)).to_not include(unrelated_status.id)
+      end
+
+      it 'returns only normal statuses for deletion' do
+        expect(subject.pluck(:id).sort).to eq [very_old_status.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id].sort
+      end
+    end
+
+    context 'when policy is to keep statuses with at least 5 boosts' do
+      before do
+        account_statuses_cleanup_policy.min_reblogs = 5
+      end
+
+      it 'does not return the recent toot' do
+        expect(subject.pluck(:id)).to_not include(recent_status.id)
+      end
+
+      it 'does not return the toot reblogged 5 times' do
+        expect(subject.pluck(:id)).to_not include(reblogged5.id)
+      end
+
+      it 'does not return the unrelated toot' do
+        expect(subject.pluck(:id)).to_not include(unrelated_status.id)
+      end
+
+      it 'returns old statuses not reblogged as much' do
+        expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, faved5.id, reblogged4.id)
+      end
+    end
+
+    context 'when policy is to keep statuses with at least 5 favs' do
+      before do
+        account_statuses_cleanup_policy.min_favs = 5
+      end
+
+      it 'does not return the recent toot' do
+        expect(subject.pluck(:id)).to_not include(recent_status.id)
+      end
+
+      it 'does not return the toot faved 5 times' do
+        expect(subject.pluck(:id)).to_not include(faved5.id)
+      end
+
+      it 'does not return the unrelated toot' do
+        expect(subject.pluck(:id)).to_not include(unrelated_status.id)
+      end
+
+      it 'returns old statuses not faved as much' do
+        expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, reblogged4.id, reblogged5.id)
+      end
+    end
+  end
+end
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index 2366b9ca4..809c7fc46 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -5,8 +5,8 @@ RSpec.describe Admin::AccountAction, type: :model do
 
   describe '#save!' do
     subject              { account_action.save! }
-    let(:account)        { Fabricate(:account, user: Fabricate(:user, admin: true)) }
-    let(:target_account) { Fabricate(:account, user: Fabricate(:user)) }
+    let(:account)        { Fabricate(:user, admin: true).account }
+    let(:target_account) { Fabricate(:account) }
     let(:type)           { 'disable' }
 
     before do
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index db959280c..656dd66cc 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -367,6 +367,23 @@ describe AccountInteractions do
     end
   end
 
+  describe '#followed_by?' do
+    subject { account.followed_by?(target_account) }
+
+    context 'followed by target_account' do
+      it 'returns true' do
+        account.passive_relationships.create(account: target_account)
+        is_expected.to be true
+      end
+    end
+
+    context 'not followed by target_account' do
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+
   describe '#blocking?' do
     subject { account.blocking?(target_account) }
 
@@ -546,46 +563,57 @@ describe AccountInteractions do
     end
   end
 
-  describe '#followers_hash' do
+  describe '#remote_followers_hash' do
     let(:me) { Fabricate(:account, username: 'Me') }
     let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
     let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
-    let(:remote_3) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
+    let(:remote_3) { Fabricate(:account, username: 'instance-actor', domain: 'example.org', uri: 'https://example.org') }
+    let(:remote_4) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
 
     before do
       remote_1.follow!(me)
       remote_2.follow!(me)
       remote_3.follow!(me)
+      remote_4.follow!(me)
       me.follow!(remote_1)
     end
 
-    context 'on a local user' do
-      it 'returns correct hash for remote domains' do
-        expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
-        expect(me.remote_followers_hash('https://foo.org/')).to eq 'ccb9c18a67134cfff9d62c7f7e7eb88e6b803446c244b84265565f4eba29df0e'
-      end
+    it 'returns correct hash for remote domains' do
+      expect(me.remote_followers_hash('https://example.org/')).to eq '20aecbe774b3d61c25094370baf370012b9271c5b172ecedb05caff8d79ef0c7'
+      expect(me.remote_followers_hash('https://foo.org/')).to eq 'ccb9c18a67134cfff9d62c7f7e7eb88e6b803446c244b84265565f4eba29df0e'
+      expect(me.remote_followers_hash('https://foo.org.evil.com/')).to eq '0000000000000000000000000000000000000000000000000000000000000000'
+      expect(me.remote_followers_hash('https://foo')).to eq '0000000000000000000000000000000000000000000000000000000000000000'
+    end
 
-      it 'invalidates cache as needed when removing or adding followers' do
-        expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
-        remote_1.unfollow!(me)
-        expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
-        remote_1.follow!(me)
-        expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
-      end
+    it 'invalidates cache as needed when removing or adding followers' do
+      expect(me.remote_followers_hash('https://example.org/')).to eq '20aecbe774b3d61c25094370baf370012b9271c5b172ecedb05caff8d79ef0c7'
+      remote_3.unfollow!(me)
+      expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+      remote_1.unfollow!(me)
+      expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
+      remote_1.follow!(me)
+      expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
     end
+  end
 
-    context 'on a remote user' do
-      it 'returns correct hash for remote domains' do
-        expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
-      end
+  describe '#local_followers_hash' do
+    let(:me) { Fabricate(:account, username: 'Me') }
+    let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
 
-      it 'invalidates cache as needed when removing or adding followers' do
-        expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
-        me.unfollow!(remote_1)
-        expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
-        me.follow!(remote_1)
-        expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
-      end
+    before do
+      me.follow!(remote_1)
+    end
+
+    it 'returns correct hash for local users' do
+      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+    end
+
+    it 'invalidates cache as needed when removing or adding followers' do
+      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+      me.unfollow!(remote_1)
+      expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
+      me.follow!(remote_1)
+      expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
     end
   end
 
diff --git a/spec/models/form/status_batch_spec.rb b/spec/models/form/status_batch_spec.rb
deleted file mode 100644
index 68d84a737..000000000
--- a/spec/models/form/status_batch_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'rails_helper'
-
-describe Form::StatusBatch do
-  let(:form) { Form::StatusBatch.new(action: action, status_ids: status_ids) }
-  let(:status) { Fabricate(:status) }
-
-  describe 'with nsfw action' do
-    let(:status_ids) { [status.id, nonsensitive_status.id, sensitive_status.id] }
-    let(:nonsensitive_status) { Fabricate(:status, sensitive: false) }
-    let(:sensitive_status) { Fabricate(:status, sensitive: true) }
-    let!(:shown_media_attachment) { Fabricate(:media_attachment, status: nonsensitive_status) }
-    let!(:hidden_media_attachment) { Fabricate(:media_attachment, status: sensitive_status) }
-
-    context 'nsfw_on' do
-      let(:action) { 'nsfw_on' }
-
-      it { expect(form.save).to be true }
-      it { expect { form.save }.to change { nonsensitive_status.reload.sensitive }.from(false).to(true) }
-      it { expect { form.save }.not_to change { sensitive_status.reload.sensitive } }
-      it { expect { form.save }.not_to change { status.reload.sensitive } }
-    end
-
-    context 'nsfw_off' do
-      let(:action) { 'nsfw_off' }
-
-      it { expect(form.save).to be true }
-      it { expect { form.save }.to change { sensitive_status.reload.sensitive }.from(true).to(false) }
-      it { expect { form.save }.not_to change { nonsensitive_status.reload.sensitive } }
-      it { expect { form.save }.not_to change { status.reload.sensitive } }
-    end
-  end
-
-  describe 'with delete action' do
-    let(:status_ids) { [status.id] }
-    let(:action) { 'delete' }
-    let!(:another_status) { Fabricate(:status) }
-
-    before do
-      allow(RemovalWorker).to receive(:perform_async)
-    end
-
-    it 'call RemovalWorker' do
-      form.save
-      expect(RemovalWorker).to have_received(:perform_async).with(status.id, immediate: true)
-    end
-
-    it 'do not call RemovalWorker' do
-      form.save
-      expect(RemovalWorker).not_to have_received(:perform_async).with(another_status.id, immediate: true)
-    end
-  end
-end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 456bc4216..7360b23cf 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -62,11 +62,23 @@ RSpec.describe MediaAttachment, type: :model do
   end
 
   describe '#to_param' do
-    let(:media_attachment) { Fabricate(:media_attachment) }
-    let(:shortcode)        { media_attachment.shortcode }
+    let(:media_attachment) { Fabricate(:media_attachment, shortcode: shortcode) }
+    let(:shortcode)        { nil }
 
-    it 'returns shortcode' do
-      expect(media_attachment.to_param).to eq shortcode
+    context 'when media attachment has a shortcode' do
+      let(:shortcode) { 'foo' }
+
+      it 'returns shortcode' do
+        expect(media_attachment.to_param).to eq shortcode
+      end
+    end
+
+    context 'when media attachment does not have a shortcode' do
+      let(:shortcode) { nil }
+
+      it 'returns string representation of id' do
+        expect(media_attachment.to_param).to eq media_attachment.id.to_s
+      end
     end
   end
 
@@ -114,6 +126,30 @@ RSpec.describe MediaAttachment, type: :model do
     end
   end
 
+  describe 'ogg with cover art' do
+    let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('boop.ogg')) }
+
+    it 'detects it as an audio file' do
+      expect(media.type).to eq 'audio'
+    end
+
+    it 'sets meta for the duration' do
+      expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
+    end
+
+    it 'extracts thumbnail' do
+      expect(media.thumbnail.present?).to eq true
+    end
+
+    it 'extracts colors from thumbnail' do
+      expect(media.file.meta['colors']['background']).to eq '#3088d4'
+    end
+
+    it 'gives the file a random name' do
+      expect(media.file_file_name).to_not eq 'boop.ogg'
+    end
+  end
+
   describe 'jpeg' do
     let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) }
 
@@ -157,4 +193,32 @@ RSpec.describe MediaAttachment, type: :model do
       expect(media.description.size).to be <= 1_500
     end
   end
+
+  describe 'size limit validation' do
+    it 'rejects video files that are too large' do
+      stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes
+      stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte
+      expect { MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.webm')) }.to raise_error(ActiveRecord::RecordInvalid)
+    end
+
+    it 'accepts video files that are small enough' do
+      stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte
+      stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes
+      media = MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.webm'))
+      expect(media.valid?).to be true
+    end
+
+    it 'rejects image files that are too large' do
+      stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte
+      stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes
+      expect { MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) }.to raise_error(ActiveRecord::RecordInvalid)
+    end
+
+    it 'accepts image files that are small enough' do
+      stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes
+      stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte
+      media = MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.jpg'))
+      expect(media.valid?).to be true
+    end
+  end
 end
diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb
index c251953a4..23cc3ceea 100644
--- a/spec/models/public_feed_spec.rb
+++ b/spec/models/public_feed_spec.rb
@@ -31,7 +31,6 @@ RSpec.describe PublicFeed, type: :model do
     end
 
     it 'filters out silenced accounts' do
-      account = Fabricate(:account)
       silenced_account = Fabricate(:account, silenced: true)
       status = Fabricate(:status, account: account)
       silenced_status = Fabricate(:status, account: silenced_account)
@@ -238,8 +237,7 @@ RSpec.describe PublicFeed, type: :model do
 
       context 'with language preferences' do
         it 'excludes statuses in languages not allowed by the account user' do
-          user = Fabricate(:user, chosen_languages: [:en, :es])
-          @account.update(user: user)
+          @account.user.update(chosen_languages: [:en, :es])
           en_status = Fabricate(:status, language: 'en')
           es_status = Fabricate(:status, language: 'es')
           fr_status = Fabricate(:status, language: 'fr')
@@ -250,8 +248,7 @@ RSpec.describe PublicFeed, type: :model do
         end
 
         it 'includes all languages when user does not have a setting' do
-          user = Fabricate(:user, chosen_languages: nil)
-          @account.update(user: user)
+          @account.user.update(chosen_languages: nil)
 
           en_status = Fabricate(:status, language: 'en')
           es_status = Fabricate(:status, language: 'es')
@@ -261,7 +258,8 @@ RSpec.describe PublicFeed, type: :model do
         end
 
         it 'includes all languages when account does not have a user' do
-          expect(@account.user).to be_nil
+          @account.update(user: nil)
+
           en_status = Fabricate(:status, language: 'en')
           es_status = Fabricate(:status, language: 'es')
 
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 312954c9d..3d29c0219 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -54,7 +54,7 @@ describe Report do
   end
 
   describe 'resolve!' do
-    subject(:report) { Fabricate(:report, action_taken: false, action_taken_by_account_id: nil) }
+    subject(:report) { Fabricate(:report, action_taken_at: nil, action_taken_by_account_id: nil) }
 
     let(:acting_account) { Fabricate(:account) }
 
@@ -63,12 +63,13 @@ describe Report do
     end
 
     it 'records action taken' do
-      expect(report).to have_attributes(action_taken: true, action_taken_by_account_id: acting_account.id)
+      expect(report.action_taken?).to be true
+      expect(report.action_taken_by_account_id).to eq acting_account.id
     end
   end
 
   describe 'unresolve!' do
-    subject(:report) { Fabricate(:report, action_taken: true, action_taken_by_account_id: acting_account.id) }
+    subject(:report) { Fabricate(:report, action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id) }
 
     let(:acting_account) { Fabricate(:account) }
 
@@ -77,23 +78,24 @@ describe Report do
     end
 
     it 'unresolves' do
-      expect(report).to have_attributes(action_taken: false, action_taken_by_account_id: nil)
+      expect(report.action_taken?).to be false
+      expect(report.action_taken_by_account_id).to be_nil
     end
   end
 
   describe 'unresolved?' do
     subject { report.unresolved? }
 
-    let(:report) { Fabricate(:report, action_taken: action_taken) }
+    let(:report) { Fabricate(:report, action_taken_at: action_taken) }
 
     context 'if action is taken' do
-      let(:action_taken) { true }
+      let(:action_taken) { Time.now.utc }
 
       it { is_expected.to be false }
     end
 
     context 'if action not is taken' do
-      let(:action_taken) { false }
+      let(:action_taken) { nil }
 
       it { is_expected.to be true }
     end
diff --git a/spec/models/status_edit_spec.rb b/spec/models/status_edit_spec.rb
new file mode 100644
index 000000000..2ecafef73
--- /dev/null
+++ b/spec/models/status_edit_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe StatusEdit, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb
index 6f0b2feb8..c18faca78 100644
--- a/spec/models/status_pin_spec.rb
+++ b/spec/models/status_pin_spec.rb
@@ -24,11 +24,11 @@ RSpec.describe StatusPin, type: :model do
       expect(StatusPin.new(account: account, status: reblog).save).to be false
     end
 
-    it 'does not allow pins of private statuses' do
+    it 'does allow pins of direct statuses' do
       account = Fabricate(:account)
       status  = Fabricate(:status, account: account, visibility: :private)
 
-      expect(StatusPin.new(account: account, status: status).save).to be false
+      expect(StatusPin.new(account: account, status: status).save).to be true
     end
 
     it 'does not allow pins of direct statuses' do
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index c1375ea94..25c98d508 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -354,6 +354,87 @@ RSpec.describe Status, type: :model do
     end
   end
 
+  describe '.tagged_with' do
+    let(:tag1)     { Fabricate(:tag) }
+    let(:tag2)     { Fabricate(:tag) }
+    let(:tag3)     { Fabricate(:tag) }
+    let!(:status1) { Fabricate(:status, tags: [tag1]) }
+    let!(:status2) { Fabricate(:status, tags: [tag2]) }
+    let!(:status3) { Fabricate(:status, tags: [tag3]) }
+    let!(:status4) { Fabricate(:status, tags: []) }
+    let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) }
+
+    context 'when given one tag' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with([tag1.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status5.id]
+        expect(Status.tagged_with([tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status2.id, status5.id]
+        expect(Status.tagged_with([tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status3.id, status5.id]
+      end
+    end
+
+    context 'when given multiple tags' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status2.id, status5.id]
+        expect(Status.tagged_with([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status3.id, status5.id]
+        expect(Status.tagged_with([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status2.id, status3.id, status5.id]
+      end
+    end
+  end
+
+  describe '.tagged_with_all' do
+    let(:tag1)     { Fabricate(:tag) }
+    let(:tag2)     { Fabricate(:tag) }
+    let(:tag3)     { Fabricate(:tag) }
+    let!(:status1) { Fabricate(:status, tags: [tag1]) }
+    let!(:status2) { Fabricate(:status, tags: [tag2]) }
+    let!(:status3) { Fabricate(:status, tags: [tag3]) }
+    let!(:status4) { Fabricate(:status, tags: []) }
+    let!(:status5) { Fabricate(:status, tags: [tag1, tag2]) }
+
+    context 'when given one tag' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with_all([tag1.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status5.id]
+        expect(Status.tagged_with_all([tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status2.id, status5.id]
+        expect(Status.tagged_with_all([tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status3.id]
+      end
+    end
+
+    context 'when given multiple tags' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with_all([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status5.id]
+        expect(Status.tagged_with_all([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq []
+        expect(Status.tagged_with_all([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq []
+      end
+    end
+  end
+
+  describe '.tagged_with_none' do
+    let(:tag1)     { Fabricate(:tag) }
+    let(:tag2)     { Fabricate(:tag) }
+    let(:tag3)     { Fabricate(:tag) }
+    let!(:status1) { Fabricate(:status, tags: [tag1]) }
+    let!(:status2) { Fabricate(:status, tags: [tag2]) }
+    let!(:status3) { Fabricate(:status, tags: [tag3]) }
+    let!(:status4) { Fabricate(:status, tags: []) }
+    let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) }
+
+    context 'when given one tag' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with_none([tag1.id]).reorder(:id).pluck(:id).uniq).to eq [status2.id, status3.id, status4.id]
+        expect(Status.tagged_with_none([tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status3.id, status4.id]
+        expect(Status.tagged_with_none([tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status2.id, status4.id]
+      end
+    end
+
+    context 'when given multiple tags' do
+      it 'returns the expected statuses' do
+        expect(Status.tagged_with_none([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to eq [status3.id, status4.id]
+        expect(Status.tagged_with_none([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status2.id, status4.id]
+        expect(Status.tagged_with_none([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [status1.id, status4.id]
+      end
+    end
+  end
+
   describe '.permitted_for' do
     subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
 
diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb
index 76277c467..45f7c3329 100644
--- a/spec/models/tag_feed_spec.rb
+++ b/spec/models/tag_feed_spec.rb
@@ -37,7 +37,7 @@ describe TagFeed, type: :service do
       expect(results).to     include both
     end
 
-    it 'handles being passed non existant tag names' do
+    it 'handles being passed non existent tag names' do
       results = described_class.new(tag1, nil, any: ['wark']).get(20)
       expect(results).to     include status1
       expect(results).to_not include status2
diff --git a/spec/models/trending_tags_spec.rb b/spec/models/trending_tags_spec.rb
deleted file mode 100644
index dfbc7d6f8..000000000
--- a/spec/models/trending_tags_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe TrendingTags do
-  describe '.record_use!' do
-    pending
-  end
-
-  describe '.update!' do
-    let!(:at_time) { Time.now.utc }
-    let!(:tag1) { Fabricate(:tag, name: 'Catstodon', trendable: true) }
-    let!(:tag2) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) }
-    let!(:tag3) { Fabricate(:tag, name: 'OCs', trendable: true) }
-
-    before do
-      allow(Redis.current).to receive(:pfcount) do |key|
-        case key
-        when "activity:tags:#{tag1.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
-          2
-        when "activity:tags:#{tag1.id}:#{at_time.beginning_of_day.to_i}:accounts"
-          16
-        when "activity:tags:#{tag2.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
-          0
-        when "activity:tags:#{tag2.id}:#{at_time.beginning_of_day.to_i}:accounts"
-          4
-        when "activity:tags:#{tag3.id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts"
-          13
-        end
-      end
-
-      Redis.current.zadd('trending_tags', 0.9, tag3.id)
-      Redis.current.sadd("trending_tags:used:#{at_time.beginning_of_day.to_i}", [tag1.id, tag2.id])
-
-      tag3.update(max_score: 0.9, max_score_at: (at_time - 1.day).beginning_of_day + 12.hours)
-
-      described_class.update!(at_time)
-    end
-
-    it 'calculates and re-calculates scores' do
-      expect(described_class.get(10, filtered: false)).to eq [tag1, tag3]
-    end
-
-    it 'omits hashtags below threshold' do
-      expect(described_class.get(10, filtered: false)).to_not include(tag2)
-    end
-
-    it 'decays scores' do
-      expect(Redis.current.zscore('trending_tags', tag3.id)).to be < 0.9
-    end
-  end
-
-  describe '.trending?' do
-    let(:tag) { Fabricate(:tag) }
-
-    before do
-      10.times { |i| Redis.current.zadd('trending_tags', i + 1, Fabricate(:tag).id) }
-    end
-
-    it 'returns true if the hashtag is within limit' do
-      Redis.current.zadd('trending_tags', 11, tag.id)
-      expect(described_class.trending?(tag)).to be true
-    end
-
-    it 'returns false if the hashtag is outside the limit' do
-      Redis.current.zadd('trending_tags', 0, tag.id)
-      expect(described_class.trending?(tag)).to be false
-    end
-  end
-end
diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb
new file mode 100644
index 000000000..4f98c6aa4
--- /dev/null
+++ b/spec/models/trends/tags_spec.rb
@@ -0,0 +1,67 @@
+require 'rails_helper'
+
+RSpec.describe Trends::Tags do
+  subject { described_class.new(threshold: 5, review_threshold: 10) }
+
+  let!(:at_time) { DateTime.new(2021, 11, 14, 10, 15, 0) }
+
+  describe '#add' do
+    let(:tag) { Fabricate(:tag) }
+
+    before do
+      subject.add(tag, 1, at_time)
+    end
+
+    it 'records history' do
+      expect(tag.history.get(at_time).accounts).to eq 1
+    end
+
+    it 'records use' do
+      expect(subject.send(:recently_used_ids, at_time)).to eq [tag.id]
+    end
+  end
+
+  describe '#get' do
+    pending
+  end
+
+  describe '#refresh' do
+    let!(:today) { at_time }
+    let!(:yesterday) { today - 1.day }
+
+    let!(:tag1) { Fabricate(:tag, name: 'Catstodon', trendable: true) }
+    let!(:tag2) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) }
+    let!(:tag3) { Fabricate(:tag, name: 'OCs', trendable: true) }
+
+    before do
+      2.times  { |i| subject.add(tag1, i, yesterday) }
+      13.times { |i| subject.add(tag3, i, yesterday) }
+      16.times { |i| subject.add(tag1, i, today) }
+      4.times  { |i| subject.add(tag2, i, today) }
+    end
+
+    context do
+      before do
+        subject.refresh(yesterday + 12.hours)
+        subject.refresh(at_time)
+      end
+
+      it 'calculates and re-calculates scores' do
+        expect(subject.get(false, 10)).to eq [tag1, tag3]
+      end
+
+      it 'omits hashtags below threshold' do
+        expect(subject.get(false, 10)).to_not include(tag2)
+      end
+    end
+
+    it 'decays scores' do
+      subject.refresh(yesterday + 12.hours)
+      original_score = subject.score(tag3.id)
+      expect(original_score).to eq 144.0
+      subject.refresh(yesterday + 12.hours + subject.options[:max_score_halflife])
+      decayed_score = subject.score(tag3.id)
+      expect(decayed_score).to be <= original_score / 2
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 5db249be2..406438c22 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -194,12 +194,12 @@ RSpec.describe User, type: :model do
     end
 
     it "returns 'private' if user has not configured default privacy setting and account is locked" do
-      user = Fabricate(:user, account: Fabricate(:account, locked: true))
+      user = Fabricate(:account, locked: true).user
       expect(user.setting_default_privacy).to eq 'private'
     end
 
     it "returns 'public' if user has not configured default privacy setting and account is not locked" do
-      user = Fabricate(:user, account: Fabricate(:account, locked: false))
+      user = Fabricate(:account, locked: false).user
       expect(user.setting_default_privacy).to eq 'public'
     end
   end
@@ -248,7 +248,7 @@ RSpec.describe User, type: :model do
 
   it_behaves_like 'Settings-extended' do
     def create!
-      User.create!(account: Fabricate(:account), email: 'foo@mastodon.space', password: 'abcd1234', agreement: true)
+      User.create!(account: Fabricate(:account, user: nil), email: 'foo@mastodon.space', password: 'abcd1234', agreement: true)
     end
 
     def fabricate
@@ -344,6 +344,34 @@ RSpec.describe User, type: :model do
     end
   end
 
+  describe '#reset_password!' do
+    subject(:user) { Fabricate(:user, password: 'foobar12345') }
+
+    let!(:session_activation) { Fabricate(:session_activation, user: user) }
+    let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) }
+    let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
+
+    before do
+      user.reset_password!
+    end
+
+    it 'changes the password immediately' do
+      expect(user.external_or_valid_password?('foobar12345')).to be false
+    end
+
+    it 'deactivates all sessions' do
+      expect(user.session_activations.count).to eq 0
+    end
+
+    it 'revokes all access tokens' do
+      expect(Doorkeeper::AccessToken.active_for(user).count).to eq 0
+    end
+
+    it 'removes push subscriptions' do
+      expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0
+    end
+  end
+
   describe '#confirm!' do
     subject(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
 
diff --git a/spec/policies/account_moderation_note_policy_spec.rb b/spec/policies/account_moderation_note_policy_spec.rb
index bb7af94e4..39ec2008a 100644
--- a/spec/policies/account_moderation_note_policy_spec.rb
+++ b/spec/policies/account_moderation_note_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe AccountModerationNotePolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :create? do
     context 'staff' do
@@ -42,7 +42,7 @@ RSpec.describe AccountModerationNotePolicy do
     end
 
     context 'neither admin nor owner' do
-      let(:kevin) { Fabricate(:user).account }
+      let(:kevin) { Fabricate(:account) }
 
       it 'denies to destroy' do
         expect(subject).to_not permit(kevin, account_moderation_note)
diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb
index 1347ca4a0..b55eb65a7 100644
--- a/spec/policies/account_policy_spec.rb
+++ b/spec/policies/account_policy_spec.rb
@@ -6,8 +6,8 @@ require 'pundit/rspec'
 RSpec.describe AccountPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
-  let(:alice)   { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
+  let(:alice)   { Fabricate(:account) }
 
   permissions :index? do
     context 'staff' do
@@ -37,7 +37,7 @@ RSpec.describe AccountPolicy do
     end
   end
 
-  permissions :unsuspend? do
+  permissions :unsuspend?, :unblock_email? do
     before do
       alice.suspend!
     end
diff --git a/spec/policies/backup_policy_spec.rb b/spec/policies/backup_policy_spec.rb
index 80407e12f..6b31c6f7c 100644
--- a/spec/policies/backup_policy_spec.rb
+++ b/spec/policies/backup_policy_spec.rb
@@ -5,7 +5,7 @@ require 'pundit/rspec'
 
 RSpec.describe BackupPolicy do
   let(:subject) { described_class }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :create? do
     context 'not user_signed_in?' do
diff --git a/spec/policies/custom_emoji_policy_spec.rb b/spec/policies/custom_emoji_policy_spec.rb
index 8def88212..e4f1af3c1 100644
--- a/spec/policies/custom_emoji_policy_spec.rb
+++ b/spec/policies/custom_emoji_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe CustomEmojiPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :index?, :enable?, :disable? do
     context 'staff' do
diff --git a/spec/policies/domain_block_policy_spec.rb b/spec/policies/domain_block_policy_spec.rb
index aea50ec0f..b24ed9e3a 100644
--- a/spec/policies/domain_block_policy_spec.rb
+++ b/spec/policies/domain_block_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe DomainBlockPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :create?, :destroy? do
     context 'admin' do
diff --git a/spec/policies/email_domain_block_policy_spec.rb b/spec/policies/email_domain_block_policy_spec.rb
index a3e825e07..1ff55af8e 100644
--- a/spec/policies/email_domain_block_policy_spec.rb
+++ b/spec/policies/email_domain_block_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe EmailDomainBlockPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :index?, :create?, :destroy? do
     context 'admin' do
diff --git a/spec/policies/instance_policy_spec.rb b/spec/policies/instance_policy_spec.rb
index 77a3bde3f..71ef1fe50 100644
--- a/spec/policies/instance_policy_spec.rb
+++ b/spec/policies/instance_policy_spec.rb
@@ -6,9 +6,9 @@ require 'pundit/rspec'
 RSpec.describe InstancePolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
-  permissions :index? do
+  permissions :index?, :show?, :destroy? do
     context 'admin' do
       it 'permits' do
         expect(subject).to permit(admin, Instance)
diff --git a/spec/policies/invite_policy_spec.rb b/spec/policies/invite_policy_spec.rb
index e391455be..122137804 100644
--- a/spec/policies/invite_policy_spec.rb
+++ b/spec/policies/invite_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe InvitePolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :index? do
     context 'staff?' do
diff --git a/spec/policies/relay_policy_spec.rb b/spec/policies/relay_policy_spec.rb
index 640f27d54..139d945dc 100644
--- a/spec/policies/relay_policy_spec.rb
+++ b/spec/policies/relay_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe RelayPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :update? do
     context 'admin?' do
diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb
index 596d7d7a9..c34f99b71 100644
--- a/spec/policies/report_note_policy_spec.rb
+++ b/spec/policies/report_note_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe ReportNotePolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :create? do
     context 'staff?' do
diff --git a/spec/policies/report_policy_spec.rb b/spec/policies/report_policy_spec.rb
index c9ae1e87a..84c366d7f 100644
--- a/spec/policies/report_policy_spec.rb
+++ b/spec/policies/report_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe ReportPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :update?, :index?, :show? do
     context 'staff?' do
diff --git a/spec/policies/settings_policy_spec.rb b/spec/policies/settings_policy_spec.rb
index 92f1f4869..3fa183c50 100644
--- a/spec/policies/settings_policy_spec.rb
+++ b/spec/policies/settings_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe SettingsPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :update?, :show? do
     context 'admin?' do
diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb
index c63875dc0..256e6786a 100644
--- a/spec/policies/tag_policy_spec.rb
+++ b/spec/policies/tag_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe TagPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :index?, :show?, :update? do
     context 'staff?' do
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index e37904f04..1933ee014 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -6,7 +6,7 @@ require 'pundit/rspec'
 RSpec.describe UserPolicy do
   let(:subject) { described_class }
   let(:admin)   { Fabricate(:user, admin: true).account }
-  let(:john)    { Fabricate(:user).account }
+  let(:john)    { Fabricate(:account) }
 
   permissions :reset_password?, :change_email? do
     context 'staff?' do
diff --git a/spec/services/account_statuses_cleanup_service_spec.rb b/spec/services/account_statuses_cleanup_service_spec.rb
new file mode 100644
index 000000000..257655c41
--- /dev/null
+++ b/spec/services/account_statuses_cleanup_service_spec.rb
@@ -0,0 +1,101 @@
+require 'rails_helper'
+
+describe AccountStatusesCleanupService, type: :service do
+  let(:account)           { Fabricate(:account, username: 'alice', domain: nil) }
+  let(:account_policy)    { Fabricate(:account_statuses_cleanup_policy, account: account) }
+  let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) }
+
+  describe '#call' do
+    context 'when the account has not posted anything' do
+      it 'returns 0 deleted toots' do
+        expect(subject.call(account_policy)).to eq 0
+      end
+    end
+
+    context 'when the account has posted several old statuses' do
+      let!(:very_old_status)    { Fabricate(:status, created_at: 3.years.ago, account: account) }
+      let!(:old_status)         { Fabricate(:status, created_at: 1.year.ago, account: account) }
+      let!(:another_old_status) { Fabricate(:status, created_at: 1.year.ago, account: account) }
+      let!(:recent_status)      { Fabricate(:status, created_at: 1.day.ago, account: account) }
+
+      context 'given a budget of 1' do
+        it 'reports 1 deleted toot' do
+          expect(subject.call(account_policy, 1)).to eq 1
+        end
+      end
+
+      context 'given a normal budget of 10' do
+        it 'reports 3 deleted statuses' do
+          expect(subject.call(account_policy, 10)).to eq 3
+        end
+
+        it 'records the last deleted id' do
+          subject.call(account_policy, 10)
+          expect(account_policy.last_inspected).to eq [old_status.id, another_old_status.id].max
+        end
+
+        it 'actually deletes the statuses' do
+          subject.call(account_policy, 10)
+          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id])).to be_nil
+        end
+      end
+
+      context 'when called repeatedly with a budget of 2' do
+        it 'reports 2 then 1 deleted statuses' do
+         expect(subject.call(account_policy, 2)).to eq 2
+         expect(subject.call(account_policy, 2)).to eq 1
+        end
+
+        it 'actually deletes the statuses in the expected order' do
+          subject.call(account_policy, 2)
+          expect(Status.find_by(id: very_old_status.id)).to be_nil
+          subject.call(account_policy, 2)
+          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id])).to be_nil
+        end
+      end
+
+      context 'when a self-faved toot is unfaved' do
+        let!(:self_faved) { Fabricate(:status, created_at: 6.months.ago, account: account) }
+        let!(:favourite)  { Fabricate(:favourite, account: account, status: self_faved) }
+
+        it 'deletes it once unfaved' do
+          expect(subject.call(account_policy, 20)).to eq 3
+          expect(Status.find_by(id: self_faved.id)).to_not be_nil
+          expect(subject.call(account_policy, 20)).to eq 0
+          favourite.destroy!
+          expect(subject.call(account_policy, 20)).to eq 1
+          expect(Status.find_by(id: self_faved.id)).to be_nil
+        end
+      end
+
+      context 'when there are more un-deletable old toots than the early search cutoff' do
+        before do
+          stub_const 'AccountStatusesCleanupPolicy::EARLY_SEARCH_CUTOFF', 5
+          # Old statuses that should be cut-off
+          10.times do
+            Fabricate(:status, created_at: 4.years.ago, visibility: :direct, account: account)
+          end
+          # New statuses that prevent cut-off id to reach the last status
+          10.times do
+            Fabricate(:status, created_at: 4.seconds.ago, visibility: :direct, account: account)
+          end
+        end
+
+        it 'reports 0 deleted statuses then 0 then 3 then 0 again' do
+          expect(subject.call(account_policy, 10)).to eq 0
+          expect(subject.call(account_policy, 10)).to eq 0
+          expect(subject.call(account_policy, 10)).to eq 3
+          expect(subject.call(account_policy, 10)).to eq 0
+        end
+
+        it 'never causes the recorded id to get higher than oldest deletable toot' do
+          subject.call(account_policy, 10)
+          subject.call(account_policy, 10)
+          subject.call(account_policy, 10)
+          subject.call(account_policy, 10)
+          expect(account_policy.last_inspected).to be < Mastodon::Snowflake.id_at(account_policy.min_status_age.seconds.ago, with_random: false)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index 1ecc46952..94574aa7f 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
 
         expect(status).to_not be_nil
         expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
-        expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
+        expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://#{valid_domain}/watch?v=12345"
       end
     end
 
@@ -100,7 +100,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
 
         expect(status).to_not be_nil
         expect(status.url).to eq "https://#{valid_domain}/watch?v=12345"
-        expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remix https://#{valid_domain}/watch?v=12345"
+        expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://#{valid_domain}/watch?v=12345"
       end
     end
 
@@ -120,7 +120,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
 
         expect(status).to_not be_nil
         expect(status.url).to eq "https://#{valid_domain}/@foo/1234"
-        expect(strip_tags(status.text)).to eq "Let's change the world https://#{valid_domain}/@foo/1234"
+        expect(strip_tags(status.text)).to eq "Let's change the worldhttps://#{valid_domain}/@foo/1234"
       end
     end
 
@@ -145,5 +145,46 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
         expect(sender.statuses.first).to be_nil
       end
     end
+
+    context 'with a valid Create activity' do
+      let(:object) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: "https://#{valid_domain}/@foo/1234/create",
+          type: 'Create',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: note,
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.uri).to eq note[:id]
+        expect(status.text).to eq note[:content]
+      end
+    end
+
+    context 'with a Create activity with a mismatching id' do
+      let(:object) do
+        {
+          '@context': 'https://www.w3.org/ns/activitystreams',
+          id: "https://#{valid_domain}/@foo/1234/create",
+          type: 'Create',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: {
+            id: "https://real.address/@foo/1234",
+            type: 'Note',
+            content: 'Lorem ipsum',
+            attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
+          },
+        }
+      end
+
+      it 'does not create status' do
+        expect(sender.statuses.first).to be_nil
+      end
+    end
   end
 end
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index 56e7f8321..7728b9ba8 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
         attachment: [
           { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' },
           { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' },
+          { type: 'PropertyValue', name: 'non-string', value: ['foo', 'bar'] },
         ],
       }.with_indifferent_access
     end
@@ -29,51 +30,6 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
     end
   end
 
-  context 'identity proofs' do
-    let(:payload) do
-      {
-        id: 'https://foo.test',
-        type: 'Actor',
-        inbox: 'https://foo.test/inbox',
-        attachment: [
-          { type: 'IdentityProof', name: 'Alice', signatureAlgorithm: 'keybase', signatureValue: 'a' * 66 },
-        ],
-      }.with_indifferent_access
-    end
-
-    it 'parses out of attachment' do
-      allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
-
-      account = subject.call('alice', 'example.com', payload)
-
-      expect(account.identity_proofs.count).to eq 1
-
-      proof = account.identity_proofs.first
-
-      expect(proof.provider).to eq 'keybase'
-      expect(proof.provider_username).to eq 'Alice'
-      expect(proof.token).to eq 'a' * 66
-    end
-
-    it 'removes no longer present proofs' do
-      allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
-
-      account   = Fabricate(:account, username: 'alice', domain: 'example.com')
-      old_proof = Fabricate(:account_identity_proof, account: account, provider: 'keybase', provider_username: 'Bob', token: 'b' * 66)
-
-      subject.call('alice', 'example.com', payload)
-
-      expect(account.identity_proofs.count).to eq 1
-      expect(account.identity_proofs.find_by(id: old_proof.id)).to be_nil
-    end
-
-    it 'queues a validity check on the proof' do
-      allow(ProofProvider::Keybase::Worker).to receive(:perform_async)
-      account = subject.call('alice', 'example.com', payload)
-      expect(ProofProvider::Keybase::Worker).to have_received(:perform_async)
-    end
-  end
-
   context 'when account is not suspended' do
     let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') }
 
diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb
index 8e5d8fb03..888d694b6 100644
--- a/spec/services/authorize_follow_service_spec.rb
+++ b/spec/services/authorize_follow_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe AuthorizeFollowService, type: :service do
   subject { AuthorizeFollowService.new }
 
   describe 'local' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob) { Fabricate(:account, username: 'bob') }
 
     before do
       FollowRequest.create(account: bob, target_account: sender)
@@ -23,7 +23,7 @@ RSpec.describe AuthorizeFollowService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
 
     before do
       FollowRequest.create(account: bob, target_account: sender)
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index 4203952c6..8f38908cd 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
 
   let!(:alice)  { Fabricate(:account) }
   let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com') }
-  let!(:jeff)   { Fabricate(:user).account }
+  let!(:jeff)   { Fabricate(:account) }
   let!(:hank)   { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
   let(:status1) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') }
diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb
index 3714f09e9..a53e1f928 100644
--- a/spec/services/block_service_spec.rb
+++ b/spec/services/block_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe BlockService, type: :service do
   subject { BlockService.new }
 
   describe 'local' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob) { Fabricate(:account, username: 'bob') }
 
     before do
       subject.call(sender, bob)
@@ -18,7 +18,7 @@ RSpec.describe BlockService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
     before do
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb
index 880ca4f0d..16f3e9962 100644
--- a/spec/services/bootstrap_timeline_service_spec.rb
+++ b/spec/services/bootstrap_timeline_service_spec.rb
@@ -1,4 +1,37 @@
 require 'rails_helper'
 
 RSpec.describe BootstrapTimelineService, type: :service do
+  subject { BootstrapTimelineService.new }
+
+  context 'when the new user has registered from an invite' do
+    let(:service)    { double }
+    let(:autofollow) { false }
+    let(:inviter)    { Fabricate(:user, confirmed_at: 2.days.ago) }
+    let(:invite)     { Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now, autofollow: autofollow) }
+    let(:new_user)   { Fabricate(:user, invite_code: invite.code) }
+
+    before do
+      allow(FollowService).to receive(:new).and_return(service)
+      allow(service).to receive(:call)
+    end
+
+    context 'when the invite has auto-follow enabled' do
+      let(:autofollow) { true }
+
+      it 'calls FollowService to follow the inviter' do
+        subject.call(new_user.account)
+        expect(service).to have_received(:call).with(new_user.account, inviter.account)
+      end
+    end
+
+    context 'when the invite does not have auto-follow enable' do
+      let(:autofollow) { false }
+
+      it 'calls FollowService to follow the inviter' do
+        subject.call(new_user.account)
+        expect(service).to_not have_received(:call)
+      end
+    end
+
+  end
 end
diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb
index cd7d32d59..b1da97036 100644
--- a/spec/services/delete_account_service_spec.rb
+++ b/spec/services/delete_account_service_spec.rb
@@ -21,6 +21,8 @@ RSpec.describe DeleteAccountService, type: :service do
     let!(:favourite_notification) { Fabricate(:notification, account: local_follower, activity: favourite, type: :favourite) }
     let!(:follow_notification) { Fabricate(:notification, account: local_follower, activity: active_relationship, type: :follow) }
 
+    let!(:account_note) { Fabricate(:account_note, account: account) }
+
     subject do
       -> { described_class.new.call(account) }
     end
@@ -35,8 +37,9 @@ RSpec.describe DeleteAccountService, type: :service do
           account.active_relationships,
           account.passive_relationships,
           account.polls,
+          account.account_notes,
         ].map(&:count)
-      }.from([2, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
+      }.from([2, 1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0])
     end
 
     it 'deletes associated target records' do
diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb
index 538dc2592..aaf179ce5 100644
--- a/spec/services/fan_out_on_write_service_spec.rb
+++ b/spec/services/fan_out_on_write_service_spec.rb
@@ -1,37 +1,112 @@
 require 'rails_helper'
 
 RSpec.describe FanOutOnWriteService, type: :service do
-  let(:author)   { Fabricate(:account, username: 'tom') }
-  let(:status)   { Fabricate(:status, text: 'Hello @alice #test', account: author) }
-  let(:alice)    { Fabricate(:user, account: Fabricate(:account, username: 'alice')).account }
-  let(:follower) { Fabricate(:account, username: 'bob') }
+  let(:last_active_at) { Time.now.utc }
 
-  subject { FanOutOnWriteService.new }
+  let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account }
+  let!(:bob)   { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account }
+  let!(:tom)   { Fabricate(:user, current_sign_in_at: last_active_at).account }
+
+  subject { described_class.new }
+
+  let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') }
 
   before do
-    alice
-    follower.follow!(author)
+    bob.follow!(alice)
+    tom.follow!(alice)
 
     ProcessMentionsService.new.call(status)
     ProcessHashtagsService.new.call(status)
 
+    allow(Redis.current).to receive(:publish)
+
     subject.call(status)
   end
 
-  it 'delivers status to home timeline' do
-    expect(HomeFeed.new(author).get(10).map(&:id)).to include status.id
+  def home_feed_of(account)
+    HomeFeed.new(account).get(10).map(&:id)
+  end
+
+  context 'when status is public' do
+    let(:visibility) { 'public' }
+
+    it 'is added to the home feed of its author' do
+      expect(home_feed_of(alice)).to include status.id
+    end
+
+    it 'is added to the home feed of a follower' do
+      expect(home_feed_of(bob)).to include status.id
+      expect(home_feed_of(tom)).to include status.id
+    end
+
+    it 'is broadcast to the hashtag stream' do
+      expect(Redis.current).to have_received(:publish).with('timeline:hashtag:hoge', anything)
+      expect(Redis.current).to have_received(:publish).with('timeline:hashtag:hoge:local', anything)
+    end
+
+    it 'is broadcast to the public stream' do
+      expect(Redis.current).to have_received(:publish).with('timeline:public', anything)
+      expect(Redis.current).to have_received(:publish).with('timeline:public:local', anything)
+    end
   end
 
-  it 'delivers status to local followers' do
-    pending 'some sort of problem in test environment causes this to sometimes fail'
-    expect(HomeFeed.new(follower).get(10).map(&:id)).to include status.id
+  context 'when status is limited' do
+    let(:visibility) { 'limited' }
+
+    it 'is added to the home feed of its author' do
+      expect(home_feed_of(alice)).to include status.id
+    end
+
+    it 'is added to the home feed of the mentioned follower' do
+      expect(home_feed_of(bob)).to include status.id
+    end
+
+    it 'is not added to the home feed of the other follower' do
+      expect(home_feed_of(tom)).to_not include status.id
+    end
+
+    it 'is not broadcast publicly' do
+      expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
+      expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
+    end
   end
 
-  it 'delivers status to hashtag' do
-    expect(TagFeed.new(Tag.find_by(name: 'test'), alice).get(20).map(&:id)).to include status.id
+  context 'when status is private' do
+    let(:visibility) { 'private' }
+
+    it 'is added to the home feed of its author' do
+      expect(home_feed_of(alice)).to include status.id
+    end
+
+    it 'is added to the home feed of a follower' do
+      expect(home_feed_of(bob)).to include status.id
+      expect(home_feed_of(tom)).to include status.id
+    end
+
+    it 'is not broadcast publicly' do
+      expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
+      expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
+    end
   end
 
-  it 'delivers status to public timeline' do
-    expect(PublicFeed.new(alice).get(20).map(&:id)).to include status.id
+  context 'when status is direct' do
+    let(:visibility) { 'direct' }
+
+    it 'is added to the home feed of its author' do
+      expect(home_feed_of(alice)).to include status.id
+    end
+
+    it 'is added to the home feed of the mentioned follower' do
+      expect(home_feed_of(bob)).to include status.id
+    end
+
+    it 'is not added to the home feed of the other follower' do
+      expect(home_feed_of(tom)).to_not include status.id
+    end
+
+    it 'is not broadcast publicly' do
+      expect(Redis.current).to_not have_received(:publish).with('timeline:hashtag:hoge', anything)
+      expect(Redis.current).to_not have_received(:publish).with('timeline:public', anything)
+    end
   end
 end
diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb
index fc7f58eb4..94a8111dd 100644
--- a/spec/services/favourite_service_spec.rb
+++ b/spec/services/favourite_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe FavouriteService, type: :service do
   subject { FavouriteService.new }
 
   describe 'local' do
-    let(:bob)    { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob)    { Fabricate(:account) }
     let(:status) { Fabricate(:status, account: bob) }
 
     before do
@@ -19,7 +19,7 @@ RSpec.describe FavouriteService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob)    { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :activitypub, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:bob)    { Fabricate(:account, protocol: :activitypub, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/inbox') }
     let(:status) { Fabricate(:status, account: bob) }
 
     before do
diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index 736a6078d..4914c2753 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe FetchLinkCardService, type: :service do
-  subject { FetchLinkCardService.new }
+  subject { described_class.new }
 
   before do
     stub_request(:get, 'http://example.xn--fiqs8s/').to_return(request_fixture('idn.txt'))
diff --git a/spec/services/fetch_oembed_service_spec.rb b/spec/services/fetch_oembed_service_spec.rb
index a4262b040..88f0113ed 100644
--- a/spec/services/fetch_oembed_service_spec.rb
+++ b/spec/services/fetch_oembed_service_spec.rb
@@ -13,6 +13,32 @@ describe FetchOEmbedService, type: :service do
 
   describe 'discover_provider' do
     context 'when status code is 200 and MIME type is text/html' do
+      context 'when OEmbed endpoint contains URL as parameter' do
+        before do
+          stub_request(:get, 'https://www.youtube.com/watch?v=IPSbNdBmWKE').to_return(
+            status: 200,
+            headers: { 'Content-Type': 'text/html' },
+            body: request_fixture('oembed_youtube.html'),
+          )
+          stub_request(:get, 'https://www.youtube.com/oembed?format=json&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIPSbNdBmWKE').to_return(
+            status: 200,
+            headers: { 'Content-Type': 'text/html' },
+            body: request_fixture('oembed_json_empty.html')
+          )
+        end
+
+        it 'returns new OEmbed::Provider for JSON provider' do
+          subject.call('https://www.youtube.com/watch?v=IPSbNdBmWKE')
+          expect(subject.endpoint_url).to eq 'https://www.youtube.com/oembed?format=json&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIPSbNdBmWKE'
+          expect(subject.format).to eq :json
+        end
+
+        it 'stores URL template' do
+          subject.call('https://www.youtube.com/watch?v=IPSbNdBmWKE')
+          expect(Rails.cache.read('oembed_endpoint:www.youtube.com')[:endpoint]).to eq 'https://www.youtube.com/oembed?format=json&url={url}'
+        end
+      end
+
       context 'Both of JSON and XML provider are discoverable' do
         before do
           stub_request(:get, 'https://host.test/oembed.html').to_return(
@@ -33,6 +59,11 @@ describe FetchOEmbedService, type: :service do
           expect(subject.endpoint_url).to eq 'https://host.test/provider.xml'
           expect(subject.format).to eq :xml
         end
+
+        it 'does not cache OEmbed endpoint' do
+          subject.call('https://host.test/oembed.html', format: :xml)
+          expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false
+        end
       end
 
       context 'JSON provider is discoverable while XML provider is not' do
@@ -49,6 +80,11 @@ describe FetchOEmbedService, type: :service do
           expect(subject.endpoint_url).to eq 'https://host.test/provider.json'
           expect(subject.format).to eq :json
         end
+
+        it 'does not cache OEmbed endpoint' do
+          subject.call('https://host.test/oembed.html')
+          expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false
+        end
       end
 
       context 'XML provider is discoverable while JSON provider is not' do
@@ -65,6 +101,11 @@ describe FetchOEmbedService, type: :service do
           expect(subject.endpoint_url).to eq 'https://host.test/provider.xml'
           expect(subject.format).to eq :xml
         end
+
+        it 'does not cache OEmbed endpoint' do
+          subject.call('https://host.test/oembed.html')
+          expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false
+        end
       end
 
       context 'Invalid XML provider is discoverable while JSON provider is not' do
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index 63d6eb3bd..02bc87c58 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe FollowService, type: :service do
 
   context 'local account' do
     describe 'locked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, locked: true, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, locked: true, username: 'bob') }
 
       before do
         subject.call(sender, bob)
@@ -19,7 +19,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'locked account, no reblogs' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, locked: true, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, locked: true, username: 'bob') }
 
       before do
         subject.call(sender, bob, reblogs: false)
@@ -31,7 +31,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'unlocked account, from silenced account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         sender.touch(:silenced_at)
@@ -44,7 +44,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'unlocked account, from a muted account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         bob.mute!(sender)
@@ -58,7 +58,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'unlocked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         subject.call(sender, bob)
@@ -71,7 +71,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'unlocked account, no reblogs' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         subject.call(sender, bob, reblogs: false)
@@ -84,7 +84,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'already followed account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         sender.follow!(bob)
@@ -97,7 +97,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'already followed account, turning reblogs off' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         sender.follow!(bob, reblogs: true)
@@ -110,7 +110,7 @@ RSpec.describe FollowService, type: :service do
     end
 
     describe 'already followed account, turning reblogs on' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+      let(:bob) { Fabricate(:account, username: 'bob') }
 
       before do
         sender.follow!(bob, reblogs: false)
@@ -124,7 +124,7 @@ RSpec.describe FollowService, type: :service do
   end
 
   context 'remote ActivityPub account' do
-    let(:bob) { Fabricate(:user, account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
 
     before do
       stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 118436f8b..83e62ff36 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -64,8 +64,9 @@ RSpec.describe NotifyService, type: :service do
         is_expected.to_not change(Notification, :count)
       end
 
-      context 'if the message chain initiated by recipient, but is not direct message' do
+      context 'if the message chain is initiated by recipient, but is not direct message' do
         let(:reply_to) { Fabricate(:status, account: recipient) }
+        let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
         let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
 
         it 'does not notify' do
@@ -73,8 +74,20 @@ RSpec.describe NotifyService, type: :service do
         end
       end
 
-      context 'if the message chain initiated by recipient and is direct message' do
+      context 'if the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do
+        let(:reply_to) { Fabricate(:status, account: recipient) }
+        let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
+        let(:dummy_reply) { Fabricate(:status, account: sender, visibility: :direct, thread: reply_to) }
+        let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) }
+
+        it 'does not notify' do
+          is_expected.to_not change(Notification, :count)
+        end
+      end
+
+      context 'if the message chain is initiated by the recipient with a mention to the sender' do
         let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) }
+        let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
         let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
 
         it 'does notify' do
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 147a59fc3..d21270c79 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -25,29 +25,33 @@ RSpec.describe PostStatusService, type: :service do
     expect(status.thread).to eq in_reply_to_status
   end
 
-  it 'schedules a status' do
-    account = Fabricate(:account)
-    future  = Time.now.utc + 2.hours
-
-    status = subject.call(account, text: 'Hi future!', scheduled_at: future)
-
-    expect(status).to be_a ScheduledStatus
-    expect(status.scheduled_at).to eq future
-    expect(status.params['text']).to eq 'Hi future!'
-  end
-
-  it 'does not immediately create a status when scheduling a status' do
-    account = Fabricate(:account)
-    media = Fabricate(:media_attachment)
-    future  = Time.now.utc + 2.hours
-
-    status = subject.call(account, text: 'Hi future!', media_ids: [media.id], scheduled_at: future)
-
-    expect(status).to be_a ScheduledStatus
-    expect(status.scheduled_at).to eq future
-    expect(status.params['text']).to eq 'Hi future!'
-    expect(media.reload.status).to be_nil
-    expect(Status.where(text: 'Hi future!').exists?).to be_falsey
+  context 'when scheduling a status' do
+    let!(:account)         { Fabricate(:account) }
+    let!(:future)          { Time.now.utc + 2.hours }
+    let!(:previous_status) { Fabricate(:status, account: account) }
+
+    it 'schedules a status' do
+      status = subject.call(account, text: 'Hi future!', scheduled_at: future)
+      expect(status).to be_a ScheduledStatus
+      expect(status.scheduled_at).to eq future
+      expect(status.params['text']).to eq 'Hi future!'
+    end
+
+    it 'does not immediately create a status' do
+      media = Fabricate(:media_attachment, account: account)
+      status = subject.call(account, text: 'Hi future!', media_ids: [media.id], scheduled_at: future)
+
+      expect(status).to be_a ScheduledStatus
+      expect(status.scheduled_at).to eq future
+      expect(status.params['text']).to eq 'Hi future!'
+      expect(status.params['media_ids']).to eq [media.id]
+      expect(media.reload.status).to be_nil
+      expect(Status.where(text: 'Hi future!').exists?).to be_falsey
+    end
+
+    it 'does not change statuses count' do
+      expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.not_to change { [account.statuses_count, previous_status.replies_count] }
+    end
   end
 
   it 'creates response to the original status of boost' do
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 3b2f9d698..89b265e9a 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -9,57 +9,55 @@ RSpec.describe ProcessMentionsService, type: :service do
 
   context 'ActivityPub' do
     context do
-      let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+      let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
       before do
-        stub_request(:post, remote_user.inbox_url)
         subject.call(status)
       end
 
       it 'creates a mention' do
         expect(remote_user.mentions.where(status: status).count).to eq 1
       end
-
-      it 'sends activity to the inbox' do
-        expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
-      end
     end
 
     context 'with an IDN domain' do
-      let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
-      let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
+      let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
+      let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
 
       before do
-        stub_request(:post, remote_user.inbox_url)
         subject.call(status)
       end
 
       it 'creates a mention' do
         expect(remote_user.mentions.where(status: status).count).to eq 1
       end
+    end
+
+    context 'with an IDN TLD' do
+      let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') }
+      let!(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") }
+
+      before do
+        subject.call(status)
+      end
 
-      it 'sends activity to the inbox' do
-        expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
+      it 'creates a mention' do
+        expect(remote_user.mentions.where(status: status).count).to eq 1
       end
     end
   end
 
   context 'Temporarily-unreachable ActivityPub user' do
-    let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
+    let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
 
     before do
       stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
       stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)
-      stub_request(:post, remote_user.inbox_url)
       subject.call(status)
     end
 
     it 'creates a mention' do
       expect(remote_user.mentions.where(status: status).count).to eq 1
     end
-
-    it 'sends activity to the inbox' do
-      expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
-    end
   end
 end
diff --git a/spec/services/purge_domain_service_spec.rb b/spec/services/purge_domain_service_spec.rb
new file mode 100644
index 000000000..59285f126
--- /dev/null
+++ b/spec/services/purge_domain_service_spec.rb
@@ -0,0 +1,27 @@
+require 'rails_helper'
+
+RSpec.describe PurgeDomainService, type: :service do
+  let!(:old_account) { Fabricate(:account, domain: 'obsolete.org') }
+  let!(:old_status1) { Fabricate(:status, account: old_account) }
+  let!(:old_status2) { Fabricate(:status, account: old_account) }
+  let!(:old_attachment) { Fabricate(:media_attachment, account: old_account, status: old_status2, file: attachment_fixture('attachment.jpg')) }
+
+  subject { PurgeDomainService.new }
+
+  describe 'for a suspension' do
+    before do
+      subject.call('obsolete.org')
+    end
+
+    it 'removes the remote accounts\'s statuses and media attachments' do
+      expect { old_account.reload }.to raise_exception ActiveRecord::RecordNotFound
+      expect { old_status1.reload }.to raise_exception ActiveRecord::RecordNotFound
+      expect { old_status2.reload }.to raise_exception ActiveRecord::RecordNotFound
+      expect { old_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound
+    end
+
+    it 'refreshes instances view' do
+      expect(Instance.where(domain: 'obsolete.org').exists?).to be false
+    end
+  end
+end
diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb
index 732cb07f7..e14bfa78d 100644
--- a/spec/services/reject_follow_service_spec.rb
+++ b/spec/services/reject_follow_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe RejectFollowService, type: :service do
   subject { RejectFollowService.new }
 
   describe 'local' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob) { Fabricate(:account) }
 
     before do
       FollowRequest.create(account: bob, target_account: sender)
@@ -23,7 +23,7 @@ RSpec.describe RejectFollowService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
 
     before do
       FollowRequest.create(account: bob, target_account: sender)
diff --git a/spec/services/remove_from_follwers_service_spec.rb b/spec/services/remove_from_follwers_service_spec.rb
new file mode 100644
index 000000000..a83f6f49a
--- /dev/null
+++ b/spec/services/remove_from_follwers_service_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe RemoveFromFollowersService, type: :service do
+  let(:bob) { Fabricate(:account, username: 'bob') }
+
+  subject { RemoveFromFollowersService.new }
+
+  describe 'local' do
+    let(:sender) { Fabricate(:account, username: 'alice') }
+ 
+    before do
+      Follow.create(account: sender, target_account: bob)
+      subject.call(bob, sender)
+    end
+
+    it 'does not create follow relation' do
+      expect(bob.followed_by?(sender)).to be false
+    end
+  end
+
+  describe 'remote ActivityPub' do
+    let(:sender) { Fabricate(:account, username: 'alice', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
+
+    before do
+      Follow.create(account: sender, target_account: bob)
+      stub_request(:post, sender.inbox_url).to_return(status: 200)
+      subject.call(bob, sender)
+    end
+
+    it 'does not create follow relation' do
+      expect(bob.followed_by?(sender)).to be false
+    end
+
+    it 'sends a reject activity' do
+      expect(a_request(:post, sender.inbox_url)).to have_been_made.once
+    end
+  end
+end
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 21fb0cd35..fb7c6b462 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe RemoveStatusService, type: :service do
   subject { RemoveStatusService.new }
 
-  let!(:alice)  { Fabricate(:account, user: Fabricate(:user)) }
+  let!(:alice)  { Fabricate(:account) }
   let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com') }
   let!(:jeff)   { Fabricate(:account) }
   let!(:hank)   { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index 454e4d896..7e6a113e0 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
 RSpec.describe ReportService, type: :service do
   subject { described_class.new }
 
-  let(:source_account) { Fabricate(:user).account }
+  let(:source_account) { Fabricate(:account) }
 
   context 'for a remote account' do
     let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
new file mode 100644
index 000000000..cf7eb257a
--- /dev/null
+++ b/spec/services/suspend_account_service_spec.rb
@@ -0,0 +1,85 @@
+require 'rails_helper'
+
+RSpec.describe SuspendAccountService, type: :service do
+  shared_examples 'common behavior' do
+    let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
+    let!(:list)           { Fabricate(:list, account: local_follower) }
+
+    subject do
+      -> { described_class.new.call(account) }
+    end
+
+    before do
+      allow(FeedManager.instance).to receive(:unmerge_from_home).and_return(nil)
+      allow(FeedManager.instance).to receive(:unmerge_from_list).and_return(nil)
+
+      local_follower.follow!(account)
+      list.accounts << account
+    end
+
+    it "unmerges from local followers' feeds" do
+      subject.call
+      expect(FeedManager.instance).to have_received(:unmerge_from_home).with(account, local_follower)
+      expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list)
+    end
+
+    it 'marks account as suspended' do
+      is_expected.to change { account.suspended? }.from(false).to(true)
+    end
+  end
+
+  describe 'suspending a local account' do
+    def match_update_actor_request(req, account)
+      json = JSON.parse(req.body)
+      actor_id = ActivityPub::TagManager.instance.uri_for(account)
+      json['type'] == 'Update' && json['actor'] == actor_id && json['object']['id'] == actor_id && json['object']['suspended']
+    end
+
+    before do
+      stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
+      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+    end
+
+    include_examples 'common behavior' do
+      let!(:account)         { Fabricate(:account) }
+      let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
+      let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
+      let!(:report)          { Fabricate(:report, account: remote_reporter, target_account: account) }
+
+      before do
+        remote_follower.follow!(account)
+      end
+
+      it 'sends an update actor to followers and reporters' do
+        subject.call
+        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
+        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
+      end
+    end
+  end
+
+  describe 'suspending a remote account' do
+    def match_reject_follow_request(req, account, followee)
+      json = JSON.parse(req.body)
+      json['type'] == 'Reject' && json['actor'] == ActivityPub::TagManager.instance.uri_for(followee) && json['object']['actor'] == account.uri
+    end
+
+    before do
+      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+    end
+
+    include_examples 'common behavior' do
+      let!(:account)        { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
+      let!(:local_followee) { Fabricate(:account) }
+
+      before do
+        account.follow!(local_followee)
+      end
+
+      it 'sends a reject follow' do
+        subject.call
+        expect(a_request(:post, account.inbox_url).with { |req| match_reject_follow_request(req, account, local_followee) }).to have_been_made.once
+      end
+    end
+  end
+end
diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb
index c43ab24b0..10448b340 100644
--- a/spec/services/unblock_service_spec.rb
+++ b/spec/services/unblock_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe UnblockService, type: :service do
   subject { UnblockService.new }
 
   describe 'local' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob) { Fabricate(:account) }
 
     before do
       sender.block!(bob)
@@ -19,7 +19,7 @@ RSpec.describe UnblockService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
     before do
       sender.block!(bob)
diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb
index 7f0b575e4..bb5bef5c9 100644
--- a/spec/services/unfollow_service_spec.rb
+++ b/spec/services/unfollow_service_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe UnfollowService, type: :service do
   subject { UnfollowService.new }
 
   describe 'local' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+    let(:bob) { Fabricate(:account, username: 'bob') }
 
     before do
       sender.follow!(bob)
@@ -19,7 +19,7 @@ RSpec.describe UnfollowService, type: :service do
   end
 
   describe 'remote ActivityPub' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
     before do
       sender.follow!(bob)
@@ -37,7 +37,7 @@ RSpec.describe UnfollowService, type: :service do
   end
 
   describe 'remote ActivityPub (reverse)' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
     before do
       bob.follow!(sender)
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
new file mode 100644
index 000000000..d52cb6cc0
--- /dev/null
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -0,0 +1,135 @@
+require 'rails_helper'
+
+RSpec.describe UnsuspendAccountService, type: :service do
+  shared_examples 'common behavior' do
+    let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account }
+    let!(:list)           { Fabricate(:list, account: local_follower) }
+
+    subject do
+      -> { described_class.new.call(account) }
+    end
+
+    before do
+      allow(FeedManager.instance).to receive(:merge_into_home).and_return(nil)
+      allow(FeedManager.instance).to receive(:merge_into_list).and_return(nil)
+
+      local_follower.follow!(account)
+      list.accounts << account
+
+      account.suspend!(origin: :local)
+    end
+  end
+
+  describe 'unsuspending a local account' do
+    def match_update_actor_request(req, account)
+      json = JSON.parse(req.body)
+      actor_id = ActivityPub::TagManager.instance.uri_for(account)
+      json['type'] == 'Update' && json['actor'] == actor_id && json['object']['id'] == actor_id && !json['object']['suspended']
+    end
+
+    before do
+      stub_request(:post, 'https://alice.com/inbox').to_return(status: 201)
+      stub_request(:post, 'https://bob.com/inbox').to_return(status: 201)
+    end
+
+    it 'marks account as unsuspended' do
+      is_expected.to change { account.suspended? }.from(true).to(false)
+    end
+
+    include_examples 'common behavior' do
+      let!(:account)         { Fabricate(:account) }
+      let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
+      let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
+      let!(:report)          { Fabricate(:report, account: remote_reporter, target_account: account) }
+
+      before do
+        remote_follower.follow!(account)
+      end
+
+      it "merges back into local followers' feeds" do
+        subject.call
+        expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
+        expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
+      end
+
+      it 'sends an update actor to followers and reporters' do
+        subject.call
+        expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
+        expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
+      end
+    end
+  end
+
+  describe 'unsuspending a remote account' do
+    include_examples 'common behavior' do
+      let!(:account)                 { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
+      let!(:reslove_account_service) { double }
+
+      before do
+        allow(ResolveAccountService).to receive(:new).and_return(reslove_account_service)
+      end
+
+      context 'when the account is not remotely suspended' do
+        before do
+          allow(reslove_account_service).to receive(:call).with(account).and_return(account)
+        end
+
+        it 're-fetches the account' do
+          subject.call
+          expect(reslove_account_service).to have_received(:call).with(account)
+        end
+
+        it "merges back into local followers' feeds" do
+          subject.call
+          expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
+          expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
+        end
+
+        it 'marks account as unsuspended' do
+          is_expected.to change { account.suspended? }.from(true).to(false)
+        end
+      end
+
+      context 'when the account is remotely suspended' do
+        before do
+          allow(reslove_account_service).to receive(:call).with(account) do |account|
+            account.suspend!(origin: :remote)
+            account
+          end
+        end
+
+        it 're-fetches the account' do
+          subject.call
+          expect(reslove_account_service).to have_received(:call).with(account)
+        end
+
+        it "does not merge back into local followers' feeds" do
+          subject.call
+          expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
+          expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
+        end
+
+        it 'does not mark the account as unsuspended' do
+          is_expected.not_to change { account.suspended? }
+        end
+      end
+
+      context 'when the account is remotely deleted' do
+        before do
+          allow(reslove_account_service).to receive(:call).with(account).and_return(nil)
+        end
+
+        it 're-fetches the account' do
+          subject.call
+          expect(reslove_account_service).to have_received(:call).with(account)
+        end
+
+        it "does not merge back into local followers' feeds" do
+          subject.call
+          expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
+          expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb
index 960b26891..c2dc791e4 100644
--- a/spec/services/update_account_service_spec.rb
+++ b/spec/services/update_account_service_spec.rb
@@ -5,9 +5,9 @@ RSpec.describe UpdateAccountService, type: :service do
 
   describe 'switching form locked to unlocked accounts' do
     let(:account) { Fabricate(:account, locked: true) }
-    let(:alice)   { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
-    let(:bob)     { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
-    let(:eve)     { Fabricate(:user, email: 'eve@example.com', account: Fabricate(:account, username: 'eve')).account }
+    let(:alice)   { Fabricate(:account) }
+    let(:bob)     { Fabricate(:account) }
+    let(:eve)     { Fabricate(:account) }
 
     before do
       bob.touch(:silenced_at)
diff --git a/spec/validators/status_pin_validator_spec.rb b/spec/validators/status_pin_validator_spec.rb
index 06532e5b3..d5bd0d1b8 100644
--- a/spec/validators/status_pin_validator_spec.rb
+++ b/spec/validators/status_pin_validator_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe StatusPinValidator, type: :validator do
     end
 
     let(:pin) { double(account: account, errors: errors, status: status, account_id: pin_account_id) }
-    let(:status) { double(reblog?: reblog, account_id: status_account_id, visibility: visibility) }
+    let(:status) { double(reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') }
     let(:account)     { double(status_pins: status_pins, local?: local) }
     let(:status_pins) { double(count: count) }
     let(:errors)      { double(add: nil) }
@@ -37,11 +37,11 @@ RSpec.describe StatusPinValidator, type: :validator do
       end
     end
 
-    context 'unless %w(public unlisted).include?(pin.status.visibility)' do
-      let(:visibility) { '' }
+    context 'if pin.status.direct_visibility?' do
+      let(:visibility) { 'direct' }
 
       it 'calls errors.add' do
-        expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.private'))
+        expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.direct'))
       end
     end
 
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index 26b131977..d608bbf5d 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -19,7 +19,7 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
       site_short_description: 'something',
       site_description: 'something',
       version_number: '1.0',
-      source_url: 'https://github.com/tootsuite/mastodon',
+      source_url: 'https://github.com/mastodon/mastodon',
       open_registrations: false,
       thumbnail: nil,
       hero: nil,
diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb
index dbda3b665..879a26959 100644
--- a/spec/views/statuses/show.html.haml_spec.rb
+++ b/spec/views/statuses/show.html.haml_spec.rb
@@ -16,10 +16,11 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
   end
 
   it 'has valid author h-card and basic data for a detailed_status' do
-    alice  =  Fabricate(:account, username: 'alice', display_name: 'Alice')
-    bob    =  Fabricate(:account, username: 'bob', display_name: 'Bob')
-    status =  Fabricate(:status, account: alice, text: 'Hello World')
-    reply  =  Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
+    alice  = Fabricate(:account, username: 'alice', display_name: 'Alice')
+    bob    = Fabricate(:account, username: 'bob', display_name: 'Bob')
+    status = Fabricate(:status, account: alice, text: 'Hello World')
+    media  = Fabricate(:media_attachment, account: alice, status: status, type: :video)
+    reply  = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
 
     assign(:status, status)
     assign(:account, alice)
@@ -35,12 +36,13 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
   end
 
   it 'has valid h-cites for p-in-reply-to and p-comment' do
-    alice   =  Fabricate(:account, username: 'alice', display_name: 'Alice')
-    bob     =  Fabricate(:account, username: 'bob', display_name: 'Bob')
-    carl    =  Fabricate(:account, username: 'carl', display_name: 'Carl')
-    status  =  Fabricate(:status, account: alice, text: 'Hello World')
-    reply   =  Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
-    comment =  Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
+    alice   = Fabricate(:account, username: 'alice', display_name: 'Alice')
+    bob     = Fabricate(:account, username: 'bob', display_name: 'Bob')
+    carl    = Fabricate(:account, username: 'carl', display_name: 'Carl')
+    status  = Fabricate(:status, account: alice, text: 'Hello World')
+    media   = Fabricate(:media_attachment, account: alice, status: status, type: :video)
+    reply   = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
+    comment = Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
 
     assign(:status, reply)
     assign(:account, alice)
@@ -62,8 +64,9 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
   end
 
   it 'has valid opengraph tags' do
-    alice   =  Fabricate(:account, username: 'alice', display_name: 'Alice')
-    status  =  Fabricate(:status, account: alice, text: 'Hello World')
+    alice  = Fabricate(:account, username: 'alice', display_name: 'Alice')
+    status = Fabricate(:status, account: alice, text: 'Hello World')
+    media  = Fabricate(:media_attachment, account: alice, status: status, type: :video)
 
     assign(:status, status)
     assign(:account, alice)
@@ -78,4 +81,21 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
     expect(header_tags).to match(%r{<meta content=".+" property="og:image" />})
     expect(header_tags).to match(%r{<meta content="http://.+" property="og:url" />})
   end
+
+  it 'has twitter player tag' do
+    alice  = Fabricate(:account, username: 'alice', display_name: 'Alice')
+    status = Fabricate(:status, account: alice, text: 'Hello World')
+    media  = Fabricate(:media_attachment, account: alice, status: status, type: :video)
+
+    assign(:status, status)
+    assign(:account, alice)
+    assign(:descendant_threads, [])
+
+    render
+
+    header_tags = view.content_for(:header_tags)
+
+    expect(header_tags).to match(%r{<meta content="http://.+/media/.+/player" property="twitter:player" />})
+    expect(header_tags).to match(%r{<meta content="player" property="twitter:card" />})
+  end
 end
diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb
index f4633731e..d39393d50 100644
--- a/spec/workers/activitypub/delivery_worker_spec.rb
+++ b/spec/workers/activitypub/delivery_worker_spec.rb
@@ -11,7 +11,7 @@ describe ActivityPub::DeliveryWorker do
   let(:payload) { 'test' }
 
   before do
-    allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/').and_return('somehash')
+    allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/api').and_return('somehash')
   end
 
   describe 'perform' do
diff --git a/spec/workers/activitypub/distribution_worker_spec.rb b/spec/workers/activitypub/distribution_worker_spec.rb
index 368ca025a..c017b4da1 100644
--- a/spec/workers/activitypub/distribution_worker_spec.rb
+++ b/spec/workers/activitypub/distribution_worker_spec.rb
@@ -35,13 +35,16 @@ describe ActivityPub::DistributionWorker do
     end
 
     context 'with direct status' do
+      let(:mentioned_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/inbox')}
+
       before do
         status.update(visibility: :direct)
+        status.mentions.create!(account: mentioned_account)
       end
 
-      it 'does nothing' do
+      it 'delivers to mentioned accounts' do
         subject.perform(status.id)
-        expect(ActivityPub::DeliveryWorker).to_not have_received(:push_bulk)
+        expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['https://foo.bar/inbox'])
       end
     end
   end
diff --git a/spec/workers/admin/domain_purge_worker_spec.rb b/spec/workers/admin/domain_purge_worker_spec.rb
new file mode 100644
index 000000000..b67c58b23
--- /dev/null
+++ b/spec/workers/admin/domain_purge_worker_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::DomainPurgeWorker do
+  subject { described_class.new }
+
+  describe 'perform' do
+    it 'calls domain purge service for relevant domain block' do
+      service = double(call: nil)
+      allow(PurgeDomainService).to receive(:new).and_return(service)
+      result = subject.perform('example.com')
+
+      expect(result).to be_nil
+      expect(service).to have_received(:call).with('example.com')
+    end
+  end
+end
diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb
index 3509f1f50..fb34970fc 100644
--- a/spec/workers/feed_insert_worker_spec.rb
+++ b/spec/workers/feed_insert_worker_spec.rb
@@ -45,7 +45,7 @@ describe FeedInsertWorker do
         result = subject.perform(status.id, follower.id)
 
         expect(result).to be_nil
-        expect(instance).to have_received(:push_to_home).with(follower, status)
+        expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
       end
     end
   end
diff --git a/spec/workers/move_worker_spec.rb b/spec/workers/move_worker_spec.rb
index 8ab4f182f..4db5810f1 100644
--- a/spec/workers/move_worker_spec.rb
+++ b/spec/workers/move_worker_spec.rb
@@ -3,13 +3,14 @@
 require 'rails_helper'
 
 describe MoveWorker do
-  let(:local_follower)   { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
-  let(:blocking_account) { Fabricate(:user, email: 'bar@example.com', account: Fabricate(:account, username: 'bar')).account }
-  let(:muting_account)   { Fabricate(:user, email: 'foo@example.com', account: Fabricate(:account, username: 'foo')).account }
+  let(:local_follower)   { Fabricate(:account) }
+  let(:blocking_account) { Fabricate(:account) }
+  let(:muting_account)   { Fabricate(:account) }
   let(:source_account)   { Fabricate(:account, protocol: :activitypub, domain: 'example.com') }
   let(:target_account)   { Fabricate(:account, protocol: :activitypub, domain: 'example.com') }
   let(:local_user)       { Fabricate(:user) }
-  let!(:account_note)    { Fabricate(:account_note, account: local_user.account, target_account: source_account) }
+  let(:comment)          { 'old note prior to move' }
+  let!(:account_note)    { Fabricate(:account_note, account: local_user.account, target_account: source_account, comment: comment) }
 
   let(:block_service) { double }
 
@@ -26,19 +27,37 @@ describe MoveWorker do
   end
 
   shared_examples 'user note handling' do
-    it 'copies user note' do
-      subject.perform(source_account.id, target_account.id)
-      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
-      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+    context 'when user notes are short enough' do
+      it 'copies user note with prelude' do
+        subject.perform(source_account.id, target_account.id)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+      end
+
+      it 'merges user notes when needed' do
+        new_account_note = AccountNote.create!(account: account_note.account, target_account: target_account, comment: 'new note prior to move')
+
+        subject.perform(source_account.id, target_account.id)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(new_account_note.comment)
+      end
     end
 
-    it 'merges user notes when needed' do
-      new_account_note = AccountNote.create!(account: account_note.account, target_account: target_account, comment: 'new note prior to move')
+    context 'when user notes are too long' do
+      let(:comment) { 'abc' * 333 }
 
-      subject.perform(source_account.id, target_account.id)
-      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(source_account.acct)
-      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
-      expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(new_account_note.comment)
+      it 'copies user note without prelude' do
+        subject.perform(source_account.id, target_account.id)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(account_note.comment)
+      end
+
+      it 'keeps user notes unchanged' do
+        new_account_note = AccountNote.create!(account: account_note.account, target_account: target_account, comment: 'new note prior to move')
+
+        subject.perform(source_account.id, target_account.id)
+        expect(AccountNote.find_by(account: account_note.account, target_account: target_account).comment).to include(new_account_note.comment)
+      end
     end
   end
 
@@ -69,7 +88,7 @@ describe MoveWorker do
   end
 
   context 'target account is local' do
-    let(:target_account) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
+    let(:target_account) { Fabricate(:account) }
 
     describe 'perform' do
       it 'calls UnfollowFollowWorker' do
@@ -83,8 +102,8 @@ describe MoveWorker do
   end
 
   context 'both target and source accounts are local' do
-    let(:target_account) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
-    let(:source_account) { Fabricate(:user, email: 'alice_@example.com', account: Fabricate(:account, username: 'alice_')).account }
+    let(:target_account) { Fabricate(:account) }
+    let(:source_account) { Fabricate(:account) }
 
     describe 'perform' do
       it 'calls makes local followers follow the target account' do
@@ -96,7 +115,7 @@ describe MoveWorker do
       include_examples 'block and mute handling'
 
       it 'does not fail when a local user is already following both accounts' do
-        double_follower = Fabricate(:user, email: 'eve@example.com', account: Fabricate(:account, username: 'eve')).account
+        double_follower = Fabricate(:account)
         double_follower.follow!(source_account)
         double_follower.follow!(target_account)
         subject.perform(source_account.id, target_account.id)
diff --git a/spec/workers/publish_scheduled_announcement_worker_spec.rb b/spec/workers/publish_scheduled_announcement_worker_spec.rb
new file mode 100644
index 000000000..0977bba1e
--- /dev/null
+++ b/spec/workers/publish_scheduled_announcement_worker_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe PublishScheduledAnnouncementWorker do
+  subject { described_class.new }
+
+  let!(:remote_account) { Fabricate(:account, domain: 'domain.com', username: 'foo', uri: 'https://domain.com/users/foo') }
+  let!(:remote_status)  { Fabricate(:status, uri: 'https://domain.com/users/foo/12345', account: remote_account) }
+  let!(:local_status)   { Fabricate(:status) }
+  let(:scheduled_announcement) { Fabricate(:announcement, text: "rebooting very soon, see #{ActivityPub::TagManager.instance.uri_for(remote_status)} and #{ActivityPub::TagManager.instance.uri_for(local_status)}") }
+
+  describe 'perform' do
+    before do
+      service = double
+      allow(FetchRemoteStatusService).to receive(:new).and_return(service)
+      allow(service).to receive(:call).with('https://domain.com/users/foo/12345') { remote_status.reload }
+
+      subject.perform(scheduled_announcement.id)
+    end
+
+    it 'updates the linked statuses' do
+      expect(scheduled_announcement.reload.status_ids).to eq [remote_status.id, local_status.id]
+    end
+  end
+end
diff --git a/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb
new file mode 100644
index 000000000..8f20725c8
--- /dev/null
+++ b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb
@@ -0,0 +1,127 @@
+require 'rails_helper'
+
+describe Scheduler::AccountsStatusesCleanupScheduler do
+  subject { described_class.new }
+
+  let!(:account1)  { Fabricate(:account, domain: nil) }
+  let!(:account2)  { Fabricate(:account, domain: nil) }
+  let!(:account3)  { Fabricate(:account, domain: nil) }
+  let!(:account4)  { Fabricate(:account, domain: nil) }
+  let!(:remote)    { Fabricate(:account) }
+
+  let!(:policy1)   { Fabricate(:account_statuses_cleanup_policy, account: account1) }
+  let!(:policy2)   { Fabricate(:account_statuses_cleanup_policy, account: account3) }
+  let!(:policy3)   { Fabricate(:account_statuses_cleanup_policy, account: account4, enabled: false) }
+
+  let(:queue_size)       { 0 }
+  let(:queue_latency)    { 0 }
+  let(:process_set_stub) do
+    [
+      {
+        'concurrency' => 2,
+        'queues' => ['push', 'default'],
+      },
+    ]
+  end
+  let(:retry_size) { 0 }
+
+  before do
+    queue_stub = double
+    allow(queue_stub).to receive(:size).and_return(queue_size)
+    allow(queue_stub).to receive(:latency).and_return(queue_latency)
+    allow(Sidekiq::Queue).to receive(:new).and_return(queue_stub)
+    allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set_stub)
+
+    sidekiq_stats_stub = double
+    allow(sidekiq_stats_stub).to receive(:retry_size).and_return(retry_size)
+    allow(Sidekiq::Stats).to receive(:new).and_return(sidekiq_stats_stub)
+
+    # Create a bunch of old statuses
+    10.times do
+      Fabricate(:status, account: account1, created_at: 3.years.ago)
+      Fabricate(:status, account: account2, created_at: 3.years.ago)
+      Fabricate(:status, account: account3, created_at: 3.years.ago)
+      Fabricate(:status, account: account4, created_at: 3.years.ago)
+      Fabricate(:status, account: remote, created_at: 3.years.ago)
+    end
+
+    # Create a bunch of newer statuses
+    5.times do
+      Fabricate(:status, account: account1, created_at: 3.minutes.ago)
+      Fabricate(:status, account: account2, created_at: 3.minutes.ago)
+      Fabricate(:status, account: account3, created_at: 3.minutes.ago)
+      Fabricate(:status, account: account4, created_at: 3.minutes.ago)
+      Fabricate(:status, account: remote, created_at: 3.minutes.ago)
+    end
+  end
+
+  describe '#under_load?' do
+    context 'when nothing is queued' do
+      it 'returns false' do
+        expect(subject.under_load?).to be false
+      end
+    end
+
+    context 'when numerous jobs are queued' do
+      let(:queue_size)    { 5 }
+      let(:queue_latency) { 120 }
+
+      it 'returns true' do
+        expect(subject.under_load?).to be true
+      end
+    end
+
+    context 'when there is a huge amount of jobs to retry' do
+      let(:retry_size) { 1_000_000 }
+
+      it 'returns true' do
+        expect(subject.under_load?).to be true
+      end
+    end
+  end
+
+  describe '#get_budget' do
+    context 'on a single thread' do
+      let(:process_set_stub) { [ { 'concurrency' => 1, 'queues' => ['push', 'default'] } ] }
+
+      it 'returns a low value' do
+        expect(subject.compute_budget).to be < 10
+      end
+    end
+
+    context 'on a lot of threads' do
+      let(:process_set_stub) do
+        [
+          { 'concurrency' => 2, 'queues' => ['push', 'default'] },
+          { 'concurrency' => 2, 'queues' => ['push'] },
+          { 'concurrency' => 2, 'queues' => ['push'] },
+          { 'concurrency' => 2, 'queues' => ['push'] },
+        ]
+      end
+
+      it 'returns a larger value' do
+        expect(subject.compute_budget).to be > 10
+      end
+    end
+  end
+
+  describe '#perform' do
+    context 'when the budget is lower than the number of toots to delete' do
+      it 'deletes as many statuses as the given budget' do
+        expect { subject.perform }.to change { Status.count }.by(-subject.compute_budget)
+      end
+
+      it 'does not delete from accounts with no cleanup policy' do
+        expect { subject.perform }.to_not change { account2.statuses.count }
+      end
+
+      it 'does not delete from accounts with disabled cleanup policies' do
+        expect { subject.perform }.to_not change { account4.statuses.count }
+      end
+
+      it 'eventually deletes every deletable toot' do
+        expect { subject.perform; subject.perform; subject.perform; subject.perform }.to change { Status.count }.by(-20)
+      end
+    end
+  end
+end
diff --git a/spec/workers/unfollow_follow_worker_spec.rb b/spec/workers/unfollow_follow_worker_spec.rb
index 5052c5616..5ea4256a9 100644
--- a/spec/workers/unfollow_follow_worker_spec.rb
+++ b/spec/workers/unfollow_follow_worker_spec.rb
@@ -3,7 +3,7 @@
 require 'rails_helper'
 
 describe UnfollowFollowWorker do
-  let(:local_follower)   { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
+  let(:local_follower)   { Fabricate(:account) }
   let(:source_account)   { Fabricate(:account) }
   let(:target_account)   { Fabricate(:account) }
   let(:show_reblogs)     { true }