about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2020-05-10 15:15:39 +0200
committerThibaut Girka <thib@sitedethib.com>2020-05-10 16:19:56 +0200
commit4a70792b4a8393a0cfd83a7e70f72179899111fa (patch)
treed340885b5dfb0f990c81b1a29f529a258c49d591 /spec
parentc6ff4c634caf718adf7280e04909c091d15add1d (diff)
parent4b766f984689311523b89e1b68d2a11dff3fc396 (diff)
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `Gemfile.lock`:
  Not a real conflict, just a glitch-soc-only dependency too close to a
  dependency that got updated upstream. Updated as well.
- `app/models/status.rb`:
  Not a real conflict, just a change too close to glitch-soc-changed code
  for optionally showing boosts in public timelines.
  Applied upstream changes.
- `app/views/layouts/application.html.haml`:
  Upstream a new, static CSS file, conflict due to glitch-soc's theming
  system, include the file regardless of the theme.
- `config/initializers/content_security_policy.rb`:
  Upstream dropped 'unsafe-inline' from the 'style-src' directive, but
  both files are very different. Removed 'unsafe-inline' as well.
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/accounts_controller_spec.rb632
-rw-r--r--spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb23
-rw-r--r--spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb23
-rw-r--r--spec/controllers/remote_follow_controller_spec.rb10
-rw-r--r--spec/controllers/settings/identity_proofs_controller_spec.rb20
-rw-r--r--spec/lib/rss/serializer_spec.rb63
-rw-r--r--spec/models/relationship_filter_spec.rb37
-rw-r--r--spec/models/status_spec.rb56
8 files changed, 763 insertions, 101 deletions
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 3d2a0665d..bd36f5494 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -3,108 +3,608 @@ require 'rails_helper'
 RSpec.describe AccountsController, type: :controller do
   render_views
 
-  let(:alice) { Fabricate(:account, username: 'alice', user: Fabricate(:user)) }
-  let(:eve) { Fabricate(:user) }
+  let(:account) { Fabricate(:user).account }
 
   describe 'GET #show' do
-    let!(:status1) { Status.create!(account: alice, text: 'Hello world') }
-    let!(:status2) { Status.create!(account: alice, text: 'Boop', thread: status1) }
-    let!(:status3) { Status.create!(account: alice, text: 'Picture!') }
-    let!(:status4) { Status.create!(account: alice, text: 'Mentioning @alice') }
-    let!(:status5) { Status.create!(account: alice, text: 'Kitsune') }
-    let!(:status6) { Status.create!(account: alice, text: 'Neko') }
-    let!(:status7) { Status.create!(account: alice, text: 'Tanuki') }
-
-    let!(:status_pin1) { StatusPin.create!(account: alice, status: status5, created_at: 5.days.ago) }
-    let!(:status_pin2) { StatusPin.create!(account: alice, status: status6, created_at: 2.years.ago) }
-    let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
+    let(:format) { 'html' }
+
+    let!(:status) { Fabricate(:status, account: account) }
+    let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
+    let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) }
+    let!(:status_media) { Fabricate(:status, account: account) }
+    let!(:status_pinned) { Fabricate(:status, account: account) }
+    let!(:status_private) { Fabricate(:status, account: account, visibility: :private) }
+    let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) }
+    let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) }
 
     before do
-      alice.block!(eve.account)
-      status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
+      status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image)
+      account.pinned_statuses << status_pinned
     end
 
-    shared_examples 'responses' do
-      before do
-        sign_in(current_user) if defined? current_user
-        get :show, params: {
-          username: alice.username,
-          max_id: (max_id if defined? max_id),
-          since_id: (since_id if defined? since_id),
-          current_user: (current_user if defined? current_user),
-        }, format: format
+    shared_examples 'preliminary checks' do
+      context 'when account is not approved' do
+        before do
+          account.user.update(approved: false)
+        end
+
+        it 'returns http not found' do
+          get :show, params: { username: account.username, format: format }
+          expect(response).to have_http_status(404)
+        end
+      end
+
+      context 'when account is suspended' do
+        before do
+          account.suspend!
+        end
+
+        it 'returns http gone' do
+          get :show, params: { username: account.username, format: format }
+          expect(response).to have_http_status(410)
+        end
       end
+    end
+
+    context 'as HTML' do
+      let(:format) { 'html' }
+
+      it_behaves_like 'preliminary checks'
+
+      shared_examples 'common response characteristics' do
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns Link header' do
+          expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account)
+        end
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
+        it 'renders show template' do
+          expect(response).to render_template(:show)
+        end
       end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(200)
+      context do
+        before do
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'renders public status' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'renders self-reply' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'renders reblog' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'renders pinned status' do
+          expect(response.body).to include(I18n.t('stream_entries.pinned'))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
       end
 
-      it 'returns correct format' do
-        expect(response.content_type).to eq content_type
+      context 'when signed-in' do
+        let(:user) { Fabricate(:user) }
+
+        before do
+          sign_in(user)
+        end
+
+        context 'when user follows account' do
+          before do
+            user.account.follow!(account)
+            get :show, params: { username: account.username, format: format }
+          end
+
+          it 'does not render private status' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+          end
+        end
+
+        context 'when user is blocked' do
+          before do
+            account.block!(user.account)
+            get :show, params: { username: account.username, format: format }
+          end
+
+          it 'renders unavailable message' do
+            expect(response.body).to include(I18n.t('accounts.unavailable'))
+          end
+
+          it 'does not render public status' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
+          end
+
+          it 'does not render self-reply' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+          end
+
+          it 'does not render status with media' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
+          end
+
+          it 'does not render reblog' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+          end
+
+          it 'does not render pinned status' do
+            expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
+          end
+
+          it 'does not render private status' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+          end
+
+          it 'does not render direct status' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+          end
+
+          it 'does not render reply to someone else' do
+            expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+          end
+        end
+      end
+
+      context 'with replies' do
+        before do
+          allow(controller).to receive(:replies_requested?).and_return(true)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'renders public status' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'renders self-reply' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'renders reblog' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render pinned status' do
+          expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'renders reply to someone else' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
+      end
+
+      context 'with media' do
+        before do
+          allow(controller).to receive(:media_requested?).and_return(true)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'does not render public status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'does not render self-reply' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render pinned status' do
+          expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
+      end
+
+      context 'with tag' do
+        let(:tag) { Fabricate(:tag) }
+
+        let!(:status_tag) { Fabricate(:status, account: account) }
+
+        before do
+          allow(controller).to receive(:tag_requested?).and_return(true)
+          status_tag.tags << tag
+          get :show, params: { username: account.username, format: format, tag: tag.to_param }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'does not render public status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'does not render self-reply' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'does not render status with media' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render pinned status' do
+          expect(response.body).to_not include(I18n.t('stream_entries.pinned'))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
+
+        it 'renders status with tag' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
+        end
       end
     end
 
-    context 'activitystreams2' do
+    context 'as JSON' do
+      let(:authorized_fetch_mode) { false }
       let(:format) { 'json' }
-      let(:content_type) { 'application/activity+json' }
 
-      include_examples 'responses'
-    end
+      before do
+        allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode)
+      end
+
+      it_behaves_like 'preliminary checks'
+
+      context do
+        before do
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns application/activity+json' do
+          expect(response.content_type).to eq 'application/activity+json'
+        end
+
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
+
+        it 'renders account' do
+          json = body_as_json
+          expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+        end
+
+        context 'in authorized fetch mode' do
+          let(:authorized_fetch_mode) { true }
 
-    context 'html' do
-      let(:format) { nil }
-      let(:content_type) { 'text/html' }
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+
+          it 'returns application/activity+json' do
+            expect(response.content_type).to eq 'application/activity+json'
+          end
+
+          it 'returns public Cache-Control header' do
+            expect(response.headers['Cache-Control']).to include 'public'
+          end
+
+          it 'returns Vary header with Signature' do
+            expect(response.headers['Vary']).to include 'Signature'
+          end
 
-      shared_examples 'responsed statuses' do
-        it 'assigns @pinned_statuses' do
-          pinned_statuses = assigns(:pinned_statuses).to_a
-          expect(pinned_statuses.size).to eq expected_pinned_statuses.size
-          pinned_statuses.each.zip(expected_pinned_statuses.each) do |pinned_status, expected_pinned_status|
-            expect(pinned_status).to eq expected_pinned_status
+          it 'renders bare minimum account' do
+            json = body_as_json
+            expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey)
+            expect(json).to_not include(:name, :summary)
           end
         end
+      end
+
+      context 'when signed in' do
+        let(:user) { Fabricate(:user) }
+
+        before do
+          sign_in(user)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns application/activity+json' do
+          expect(response.content_type).to eq 'application/activity+json'
+        end
+
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
+
+        it 'renders account' do
+          json = body_as_json
+          expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+        end
+      end
+
+      context 'with signature' do
+        let(:remote_account) { Fabricate(:account, domain: 'example.com') }
+
+        before do
+          allow(controller).to receive(:signed_request_account).and_return(remote_account)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
+
+        it 'returns application/activity+json' do
+          expect(response.content_type).to eq 'application/activity+json'
+        end
+
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
+
+        it 'renders account' do
+          json = body_as_json
+          expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+        end
+
+        context 'in authorized fetch mode' do
+          let(:authorized_fetch_mode) { true }
 
-        it 'assigns @statuses' do
-          statuses = assigns(:statuses).to_a
-          expect(statuses.size).to eq expected_statuses.size
-          statuses.each.zip(expected_statuses.each) do |status, expected_status|
-            expect(status).to eq expected_status
+          it 'returns http success' do
+            expect(response).to have_http_status(200)
+          end
+
+          it 'returns application/activity+json' do
+            expect(response.content_type).to eq 'application/activity+json'
+          end
+
+          it 'returns private Cache-Control header' do
+            expect(response.headers['Cache-Control']).to include 'private'
+          end
+
+          it 'returns Vary header with Signature' do
+            expect(response.headers['Vary']).to include 'Signature'
+          end
+
+          it 'renders account' do
+            json = body_as_json
+            expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
           end
         end
       end
+    end
+
+    context 'as RSS' do
+      let(:format) { 'rss' }
 
-      include_examples 'responses'
+      it_behaves_like 'preliminary checks'
+
+      shared_examples 'common response characteristics' do
+        it 'returns http success' do
+          expect(response).to have_http_status(200)
+        end
 
-      context 'with anonymous visitor' do
-        context 'without since_id nor max_id' do
-          let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
-          let(:expected_pinned_statuses) { [status7, status5, status6] }
+        it 'returns public Cache-Control header' do
+          expect(response.headers['Cache-Control']).to include 'public'
+        end
+      end
 
-          include_examples 'responsed statuses'
+      context do
+        before do
+          get :show, params: { username: account.username, format: format }
         end
 
-        context 'with since_id nor max_id' do
-          let(:max_id) { status4.id }
-          let(:since_id) { status1.id }
-          let(:expected_statuses) { [status3, status2] }
-          let(:expected_pinned_statuses) { [] }
+        it_behaves_like 'common response characteristics'
+
+        it 'renders public status' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
+        end
 
-          include_examples 'responsed statuses'
+        it 'renders self-reply' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
+      end
+
+      context 'with replies' do
+        before do
+          allow(controller).to receive(:replies_requested?).and_return(true)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'renders public status' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'renders self-reply' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'renders reply to someone else' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply))
         end
       end
 
-      context 'with blocked visitor' do
-        let(:current_user) { eve }
+      context 'with media' do
+        before do
+          allow(controller).to receive(:media_requested?).and_return(true)
+          get :show, params: { username: account.username, format: format }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'does not render public status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
+        end
 
-        context 'without since_id nor max_id' do
-          let(:expected_statuses) { [] }
-          let(:expected_pinned_statuses) { [] }
+        it 'does not render self-reply' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'renders status with media' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
+      end
+
+      context 'with tag' do
+        let(:tag) { Fabricate(:tag) }
+
+        let!(:status_tag) { Fabricate(:status, account: account) }
+
+        before do
+          allow(controller).to receive(:tag_requested?).and_return(true)
+          status_tag.tags << tag
+          get :show, params: { username: account.username, format: format, tag: tag.to_param }
+        end
+
+        it_behaves_like 'common response characteristics'
+
+        it 'does not render public status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status))
+        end
+
+        it 'does not render self-reply' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply))
+        end
+
+        it 'does not render status with media' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media))
+        end
+
+        it 'does not render reblog' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog))
+        end
+
+        it 'does not render private status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private))
+        end
+
+        it 'does not render direct status' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct))
+        end
+
+        it 'does not render reply to someone else' do
+          expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply))
+        end
 
-          include_examples 'responsed statuses'
+        it 'renders status with tag' do
+          expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag))
         end
       end
     end
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 54587187f..482a19ef2 100644
--- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
@@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowerAccountsController do
       expect(body_as_json.size).to eq 1
       expect(body_as_json[0][:id]).to eq alice.id.to_s
     end
+
+    context 'when requesting user is blocked' do
+      before do
+        account.block!(user.account)
+      end
+
+      it 'hides results' do
+        get :index, params: { account_id: account.id, limit: 2 }
+        expect(body_as_json.size).to eq 0
+      end
+    end
+
+    context 'when requesting user is the account owner' do
+      let(:user) { Fabricate(:user, account: account) }
+
+      it 'returns all accounts, including muted accounts' do
+        user.account.mute!(bob)
+        get :index, params: { account_id: account.id, limit: 2 }
+
+        expect(body_as_json.size).to eq 2
+        expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
+      end
+    end
   end
 end
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 a580a7368..e35b625fe 100644
--- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb
@@ -36,5 +36,28 @@ describe Api::V1::Accounts::FollowingAccountsController do
       expect(body_as_json.size).to eq 1
       expect(body_as_json[0][:id]).to eq alice.id.to_s
     end
+
+    context 'when requesting user is blocked' do
+      before do
+        account.block!(user.account)
+      end
+
+      it 'hides results' do
+        get :index, params: { account_id: account.id, limit: 2 }
+        expect(body_as_json.size).to eq 0
+      end
+    end
+
+    context 'when requesting user is the account owner' do
+      let(:user) { Fabricate(:user, account: account) }
+
+      it 'returns all accounts, including muted accounts' do
+        user.account.mute!(bob)
+        get :index, params: { account_id: account.id, limit: 2 }
+
+        expect(body_as_json.size).to eq 2
+        expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
+      end
+    end
   end
 end
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
index d79dd2949..3ef8f14d9 100644
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ b/spec/controllers/remote_follow_controller_spec.rb
@@ -35,7 +35,7 @@ describe RemoteFollowController do
       context 'when webfinger values are wrong' do
         it 'renders new when redirect url is nil' do
           resource_with_nil_link = double(link: nil)
-          allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_nil_link)
+          allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_nil_link)
           post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
 
           expect(response).to render_template(:new)
@@ -45,7 +45,7 @@ describe RemoteFollowController do
         it 'renders new when template is nil' do
           link_with_nil_template = double(template: nil)
           resource_with_link = double(link: link_with_nil_template)
-          allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
+          allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
           post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
 
           expect(response).to render_template(:new)
@@ -57,7 +57,7 @@ describe RemoteFollowController do
         before do
           link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
           resource_with_link = double(link: link_with_template)
-          allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_return(resource_with_link)
+          allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
           post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
         end
 
@@ -79,7 +79,7 @@ describe RemoteFollowController do
       end
 
       it 'renders new with error when goldfinger fails' do
-        allow(Goldfinger).to receive(:finger).with('acct:user@example.com').and_raise(Goldfinger::Error)
+        allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
         post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
 
         expect(response).to render_template(:new)
@@ -87,7 +87,7 @@ describe RemoteFollowController do
       end
 
       it 'renders new when occur HTTP::ConnectionError' do
-        allow(Goldfinger).to receive(:finger).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
+        allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
         post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
 
         expect(response).to render_template(:new)
diff --git a/spec/controllers/settings/identity_proofs_controller_spec.rb b/spec/controllers/settings/identity_proofs_controller_spec.rb
index 261e980d4..16f236227 100644
--- a/spec/controllers/settings/identity_proofs_controller_spec.rb
+++ b/spec/controllers/settings/identity_proofs_controller_spec.rb
@@ -151,7 +151,7 @@ describe Settings::IdentityProofsController do
         @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!) { }
+        allow_any_instance_of(AccountIdentityProof).to receive(:refresh!) {}
       end
 
       it 'has the first proof username on the page' do
@@ -165,4 +165,22 @@ describe Settings::IdentityProofsController do
       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/lib/rss/serializer_spec.rb b/spec/lib/rss/serializer_spec.rb
new file mode 100644
index 000000000..0364d13de
--- /dev/null
+++ b/spec/lib/rss/serializer_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe RSS::Serializer do
+  describe '#status_title' do
+    let(:text)      { 'This is a toot' }
+    let(:spoiler)   { '' }
+    let(:sensitive) { false }
+    let(:reblog)    { nil }
+    let(:account)   { Fabricate(:account) }
+    let(:status)    { Fabricate(:status, account: account, text: text, spoiler_text: spoiler, sensitive: sensitive, reblog: reblog) }
+
+    subject { RSS::Serializer.new.send(:status_title, status) }
+
+    context 'if destroyed?' do
+      it 'returns "#{account.acct} deleted status"' do
+        status.destroy!
+        expect(subject).to eq "#{account.acct} deleted status"
+      end
+    end
+
+    context 'on a toot with long text' do
+      let(:text) { "This toot's text is longer than the allowed number of characters" }
+
+      it 'truncates toot text appropriately' do
+        expect(subject).to eq "#{account.acct}: “This toot's text is longer tha…”"
+      end
+    end
+
+    context 'on a toot with long text with a newline' do
+      let(:text) { "This toot's text is longer\nthan the allowed number of characters" }
+
+      it 'truncates toot text appropriately' do
+        expect(subject).to eq "#{account.acct}: “This toot's text is longer…”"
+      end
+    end
+
+    context 'on a toot with a content warning' do
+      let(:spoiler) { 'long toot' }
+
+      it 'displays spoiler text instead of toot content' do
+        expect(subject).to eq "#{account.acct}: CW “long toot”"
+      end
+    end
+
+    context 'on a toot with sensitive media' do
+      let(:sensitive) { true }
+
+      it 'displays that the media is sensitive' do
+        expect(subject).to eq "#{account.acct}: “This is a toot” (sensitive)"
+      end
+    end
+
+    context 'on a reblog' do
+      let(:reblog) { Fabricate(:status, text: 'This is a toot') }
+
+      it 'display that the toot is a reblog' do
+        expect(subject).to eq "#{account.acct} boosted #{reblog.account.acct}: “This is a toot”"
+      end
+    end
+  end
+end
diff --git a/spec/models/relationship_filter_spec.rb b/spec/models/relationship_filter_spec.rb
new file mode 100644
index 000000000..7c0f37a06
--- /dev/null
+++ b/spec/models/relationship_filter_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe RelationshipFilter do
+  let(:account) { Fabricate(:account) }
+
+  describe '#results' do
+    context 'when default params are used' do
+      let(:subject) do
+        RelationshipFilter.new(account, 'order' => 'active').results
+      end
+
+      before do
+        add_following_account_with(last_status_at: 7.days.ago)
+        add_following_account_with(last_status_at: 1.day.ago)
+        add_following_account_with(last_status_at: 3.days.ago)
+      end
+
+      it 'returns followings ordered by last activity' do
+        expected_result = account.following.eager_load(:account_stat).reorder(nil).by_recent_status
+
+        expect(subject).to eq expected_result
+      end
+    end
+  end
+
+  def add_following_account_with(last_status_at:)
+    following_account = Fabricate(:account)
+    Fabricate(:account_stat, account: following_account,
+                             last_status_at: last_status_at,
+                             statuses_count: 1,
+                             following_count: 0,
+                             followers_count: 0)
+    Fabricate(:follow, account: account, target_account: following_account).account
+  end
+end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 02f533287..041021d34 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -82,35 +82,6 @@ RSpec.describe Status, type: :model do
     end
   end
 
-  describe '#title' do
-    # rubocop:disable Style/InterpolationCheck
-
-    let(:account) { subject.account }
-
-    context 'if destroyed?' do
-      it 'returns "#{account.acct} deleted status"' do
-        subject.destroy!
-        expect(subject.title).to eq "#{account.acct} deleted status"
-      end
-    end
-
-    context 'unless destroyed?' do
-      context 'if reblog?' do
-        it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do
-          reblog = subject.reblog = other
-          expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
-        end
-      end
-
-      context 'unless reblog?' do
-        it 'returns "New status by #{account.acct}"' do
-          subject.reblog = nil
-          expect(subject.title).to eq "New status by #{account.acct}"
-        end
-      end
-    end
-  end
-
   describe '#hidden?' do
     context 'if private_visibility?' do
       it 'returns true' do
@@ -490,6 +461,33 @@ RSpec.describe Status, type: :model do
       end
     end
 
+    context 'with a remote_only option set' do
+      let!(:local_account)  { Fabricate(:account, domain: nil) }
+      let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
+      let!(:local_status)   { Fabricate(:status, account: local_account) }
+      let!(:remote_status)  { Fabricate(:status, account: remote_account) }
+
+      subject { Status.as_public_timeline(viewer, :remote) }
+
+      context 'without a viewer' do
+        let(:viewer) { nil }
+
+        it 'does not include local instances statuses' do
+          expect(subject).not_to include(local_status)
+          expect(subject).to include(remote_status)
+        end
+      end
+
+      context 'with a viewer' do
+        let(:viewer) { Fabricate(:account, username: 'viewer') }
+
+        it 'does not include local instances statuses' do
+          expect(subject).not_to include(local_status)
+          expect(subject).to include(remote_status)
+        end
+      end
+    end
+
     describe 'with an account passed in' do
       before do
         @account = Fabricate(:account)