about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
authorSurinna Curtis <ekiru.0@gmail.com>2017-11-16 01:38:26 -0600
committerGitHub <noreply@github.com>2017-11-16 01:38:26 -0600
commitee560abdbe7a2caf0f7ac6137faf248bbaff9a93 (patch)
treefcd9bdb5ba49ab7a6a79590c74db858ae77b4239 /spec
parent88627fd7aa2493a6890d60a5965459e4c7fe6fe9 (diff)
parent35fbdc36f92b610e8a73e2acb220e87cf5fc83b0 (diff)
Merge pull request #216 from glitch-soc/merge-upstream-3023725
Merge upstream at commit 3023725
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/accounts_controller_spec.rb139
-rw-r--r--spec/fabricators/session_activation_fabricator.rb2
-rw-r--r--spec/fabricators/setting_fabricator.rb4
-rw-r--r--spec/lib/settings/scoped_settings_spec.rb35
-rw-r--r--spec/models/account_spec.rb100
-rw-r--r--spec/models/concerns/account_interactions_spec.rb18
-rw-r--r--spec/models/custom_emoji_spec.rb29
-rw-r--r--spec/models/email_domain_block_spec.rb5
-rw-r--r--spec/models/follow_request_spec.rb32
-rw-r--r--spec/models/media_attachment_spec.rb77
-rw-r--r--spec/models/notification_spec.rb113
-rw-r--r--spec/models/remote_follow_spec.rb67
-rw-r--r--spec/models/remote_profile_spec.rb143
-rw-r--r--spec/models/session_activation_spec.rb124
-rw-r--r--spec/models/setting_spec.rb184
-rw-r--r--spec/models/site_upload_spec.rb8
-rw-r--r--spec/models/status_spec.rb30
-rw-r--r--spec/models/stream_entry_spec.rb115
-rw-r--r--spec/models/tag_spec.rb7
-rw-r--r--spec/models/user_spec.rb39
-rw-r--r--spec/services/notify_service_spec.rb33
21 files changed, 1168 insertions, 136 deletions
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 92f888590..a8ade790c 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -4,6 +4,7 @@ RSpec.describe AccountsController, type: :controller do
   render_views
 
   let(:alice)  { Fabricate(:account, username: 'alice') }
+  let(:eve)  { Fabricate(:user) }
 
   describe 'GET #show' do
     let!(:status1) { Status.create!(account: alice, text: 'Hello world') }
@@ -19,93 +20,123 @@ RSpec.describe AccountsController, type: :controller do
     let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
 
     before do
+      alice.block!(eve.account)
       status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
     end
 
-    context 'atom' do
+    shared_examples 'responses' do
       before do
-        get :show, params: { username: alice.username, max_id: status4.stream_entry.id, since_id: status1.stream_entry.id }, format: 'atom'
+        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
       end
 
       it 'assigns @account' do
         expect(assigns(:account)).to eq alice
       end
 
-      it 'assigns @entries' do
-        entries = assigns(:entries).to_a
-        expect(entries.size).to eq 2
-        expect(entries[0].status).to eq status3
-        expect(entries[1].status).to eq status2
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
       end
 
-      it 'returns http success with Atom' do
-        expect(response).to have_http_status(:success)
+      it 'returns correct format' do
+        expect(response.content_type).to eq content_type
       end
     end
 
-    context 'activitystreams2' do
-      before do
-        get :show, params: { username: alice.username }, format: 'json'
-      end
+    context 'atom' do
+      let(:format) { 'atom' }
+      let(:content_type) { 'application/atom+xml' }
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
+      shared_examples 'responsed streams' do
+        it 'assigns @entries' do
+          entries = assigns(:entries).to_a
+          expect(entries.size).to eq expected_statuses.size
+          entries.each.zip(expected_statuses.each) do |entry, expected_status|
+            expect(entry.status).to eq expected_status
+          end
+        end
       end
 
-      it 'returns http success with Activity Streams 2.0' do
-        expect(response).to have_http_status(:success)
-      end
+      include_examples 'responses'
 
-      it 'returns application/activity+json' do
-        expect(response.content_type).to eq 'application/activity+json'
-      end
-    end
+      context 'without max_id nor since_id' do
+        let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
 
-    context 'html without since_id nor max_id' do
-      before do
-        get :show, params: { username: alice.username }
+        include_examples 'responsed streams'
       end
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
-      end
+      context 'with max_id and since_id' do
+        let(:max_id) { status4.stream_entry.id }
+        let(:since_id) { status1.stream_entry.id }
+        let(:expected_statuses) { [status3, status2] }
 
-      it 'assigns @pinned_statuses' do
-        pinned_statuses = assigns(:pinned_statuses).to_a
-        expect(pinned_statuses.size).to eq 3
-        expect(pinned_statuses[0]).to eq status7
-        expect(pinned_statuses[1]).to eq status5
-        expect(pinned_statuses[2]).to eq status6
+        include_examples 'responsed streams'
       end
+    end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(:success)
-      end
+    context 'activitystreams2' do
+      let(:format) { 'json' }
+      let(:content_type) { 'application/activity+json' }
+
+      include_examples 'responses'
     end
 
-    context 'html with since_id and max_id' do
-      before do
-        get :show, params: { username: alice.username, max_id: status4.id, since_id: status1.id }
-      end
+    context 'html' do
+      let(:format) { nil }
+      let(:content_type) { 'text/html' }
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
-      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
+          end
+        end
 
-      it 'assigns @statuses' do
-        statuses = assigns(:statuses).to_a
-        expect(statuses.size).to eq 2
-        expect(statuses[0]).to eq status3
-        expect(statuses[1]).to eq status2
+        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
+          end
+        end
       end
 
-      it 'assigns an empty array to @pinned_statuses' do
-        pinned_statuses = assigns(:pinned_statuses).to_a
-        expect(pinned_statuses.size).to eq 0
+      include_examples 'responses'
+
+      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] }
+
+          include_examples 'responsed statuses'
+        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) { [] }
+
+          include_examples 'responsed statuses'
+        end
       end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(:success)
+      context 'with blocked visitor' do
+        let(:current_user) { eve }
+
+        context 'without since_id nor max_id' do
+          let(:expected_statuses) { [] }
+          let(:expected_pinned_statuses) { [] }
+
+          include_examples 'responsed statuses'
+        end
       end
     end
   end
diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb
index 46050bdab..526faaec2 100644
--- a/spec/fabricators/session_activation_fabricator.rb
+++ b/spec/fabricators/session_activation_fabricator.rb
@@ -1,4 +1,4 @@
 Fabricator(:session_activation) do
-  user_id    1
+  user
   session_id "MyString"
 end
diff --git a/spec/fabricators/setting_fabricator.rb b/spec/fabricators/setting_fabricator.rb
new file mode 100644
index 000000000..336d7c355
--- /dev/null
+++ b/spec/fabricators/setting_fabricator.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+Fabricator(:setting) do
+end
diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb
new file mode 100644
index 000000000..7566685b4
--- /dev/null
+++ b/spec/lib/settings/scoped_settings_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::ScopedSettings do
+  let(:object)         { Fabricate(:user) }
+  let(:scoped_setting) { described_class.new(object) }
+  let(:val)            { 'whatever' }
+  let(:methods)        { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) }
+
+  describe '.initialize' do
+    it 'sets @object' do
+      scoped_setting = described_class.new(object)
+      expect(scoped_setting.instance_variable_get(:@object)).to be object
+    end
+  end
+
+  describe '#method_missing' do
+    it 'sets scoped_setting.method_name = val' do
+      methods.each do |key|
+        scoped_setting.send("#{key}=", val)
+        expect(scoped_setting.send(key)).to eq val
+      end
+    end
+  end
+
+  describe '#[]= and #[]' do
+    it 'sets [key] = val' do
+      methods.each do |key|
+        scoped_setting[key] = val
+        expect(scoped_setting[key]).to eq val
+      end
+    end
+  end
+end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 361577eff..7501c498c 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -93,21 +93,44 @@ RSpec.describe Account, type: :model do
   end
 
   describe '#save_with_optional_media!' do
-    it 'sets default avatar, header, avatar_remote_url, and header_remote_url if some of them are invalid' do
+    before do
       stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt'))
       stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt'))
-      account = Fabricate(:account,
-                          avatar_remote_url: 'https://remote/valid_avatar',
-                          header_remote_url: 'https://remote/valid_avatar')
+    end
+
+    let(:account) do
+      Fabricate(:account,
+                avatar_remote_url: 'https://remote/valid_avatar',
+                header_remote_url: 'https://remote/valid_avatar')
+    end
+
+    let!(:expectation) { account.dup }
+
+    context 'with valid properties' do
+      before do
+        account.save_with_optional_media!
+      end
+
+      it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do
+        expect(account.avatar_remote_url).to eq expectation.avatar_remote_url
+        expect(account.header_remote_url).to eq expectation.header_remote_url
+        expect(account.avatar_file_name).to  eq expectation.avatar_file_name
+        expect(account.header_file_name).to  eq expectation.header_file_name
+      end
+    end
 
-      account.avatar_remote_url = 'https://remote/invalid_avatar'
-      account.save_with_optional_media!
+    context 'with invalid properties' do
+      before do
+        account.avatar_remote_url = 'https://remote/invalid_avatar'
+        account.save_with_optional_media!
+      end
 
-      account.reload
-      expect(account.avatar_remote_url).to eq ''
-      expect(account.header_remote_url).to eq ''
-      expect(account.avatar_file_name).to eq nil
-      expect(account.header_file_name).to eq nil
+      it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
+        expect(account.avatar_remote_url).to eq ''
+        expect(account.header_remote_url).to eq ''
+        expect(account.avatar_file_name).to  eq nil
+        expect(account.header_file_name).to  eq nil
+      end
     end
   end
 
@@ -123,6 +146,61 @@ RSpec.describe Account, type: :model do
     end
   end
 
+  describe '#possibly_stale?' do
+    let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) }
+
+    context 'last_webfingered_at is nil' do
+      let(:last_webfingered_at) { nil }
+
+      it 'returns true' do
+        expect(account.possibly_stale?).to be true
+      end
+    end
+
+    context 'last_webfingered_at is more than 24 hours before' do
+      let(:last_webfingered_at) { 25.hours.ago }
+
+      it 'returns true' do
+        expect(account.possibly_stale?).to be true
+      end
+    end
+
+    context 'last_webfingered_at is less than 24 hours before' do
+      let(:last_webfingered_at) { 23.hours.ago }
+
+      it 'returns false' do
+        expect(account.possibly_stale?).to be false
+      end
+    end
+  end
+
+  describe '#refresh!' do
+    let(:account) { Fabricate(:account, domain: domain) }
+    let(:acct)    { account.acct }
+
+    context 'domain is nil' do
+      let(:domain) { nil }
+
+      it 'returns nil' do
+        expect(account.refresh!).to be_nil
+      end
+
+      it 'calls not ResolveRemoteAccountService#call' do
+        expect_any_instance_of(ResolveRemoteAccountService).not_to receive(:call).with(acct)
+        account.refresh!
+      end
+    end
+
+    context 'domain is present' do
+      let(:domain) { 'example.com' }
+
+      it 'calls ResolveRemoteAccountService#call' do
+        expect_any_instance_of(ResolveRemoteAccountService).to receive(:call).with(acct).once
+        account.refresh!
+      end
+    end
+  end
+
   describe '#to_param' do
     it 'returns username' do
       account = Fabricate(:account, username: 'alice')
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index f47d9d057..1e238e27c 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -2,38 +2,36 @@ require 'rails_helper'
 
 describe AccountInteractions do
   describe 'muting an account' do
-    before do
-      @me = Fabricate(:account, username: 'Me')
-      @you = Fabricate(:account, username: 'You')
-    end
+    let(:me) { Fabricate(:account, username: 'Me') }
+    let(:you) { Fabricate(:account, username: 'You') }
 
     context 'with the notifications option unspecified' do
       before do
-        @me.mute!(@you)
+        me.mute!(you)
       end
 
       it 'defaults to muting notifications' do
-        expect(@me.muting_notifications?(@you)).to be(true)
+        expect(me.muting_notifications?(you)).to be true
       end
     end
 
     context 'with the notifications option set to false' do
       before do
-        @me.mute!(@you, notifications: false)
+        me.mute!(you, notifications: false)
       end
 
       it 'does not mute notifications' do
-        expect(@me.muting_notifications?(@you)).to be(false)
+        expect(me.muting_notifications?(you)).to be false
       end
     end
 
     context 'with the notifications option set to true' do
       before do
-        @me.mute!(@you, notifications: true)
+        me.mute!(you, notifications: true)
       end
 
       it 'does mute notifications' do
-        expect(@me.muting_notifications?(@you)).to be(true)
+        expect(me.muting_notifications?(you)).to be true 
       end
     end
   end
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
index cb51e9519..bb150b837 100644
--- a/spec/models/custom_emoji_spec.rb
+++ b/spec/models/custom_emoji_spec.rb
@@ -1,6 +1,35 @@
 require 'rails_helper'
 
 RSpec.describe CustomEmoji, type: :model do
+  describe '#local?' do
+    let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) }
+
+    subject { custom_emoji.local? }
+
+    context 'domain is nil' do
+      let(:domain) { nil }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'domain is present' do
+      let(:domain) { 'example.com' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe '#object_type' do
+    it 'returns :emoji' do
+      custom_emoji = Fabricate(:custom_emoji)
+      expect(custom_emoji.object_type).to be :emoji
+    end
+  end
+
   describe '.from_text' do
     let!(:emojo) { Fabricate(:custom_emoji) }
 
diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb
index 5f5d189d9..efd2853a9 100644
--- a/spec/models/email_domain_block_spec.rb
+++ b/spec/models/email_domain_block_spec.rb
@@ -13,9 +13,10 @@ RSpec.describe EmailDomainBlock, type: :model do
       Fabricate(:email_domain_block, domain: 'example.com')
       expect(EmailDomainBlock.block?('nyarn@example.com')).to eq true
     end
+
     it 'returns true if the domain is not registed' do
-      Fabricate(:email_domain_block, domain: 'domain')
-      expect(EmailDomainBlock.block?('example')).to eq false
+      Fabricate(:email_domain_block, domain: 'example.com')
+      expect(EmailDomainBlock.block?('nyarn@example.net')).to eq false
     end
   end
 end
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index 62bd724d7..7bc93a2aa 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -2,6 +2,17 @@ require 'rails_helper'
 
 RSpec.describe FollowRequest, type: :model do
   describe '#authorize!' do
+    let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) }
+    let(:account)        { Fabricate(:account) }
+    let(:target_account) { Fabricate(:account) }
+
+    it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
+      expect(account).to        receive(:follow!).with(target_account, reblogs: true)
+      expect(MergeWorker).to    receive(:perform_async).with(target_account.id, account.id)
+      expect(follow_request).to receive(:destroy!)
+      follow_request.authorize!
+    end
+
     it 'generates a Follow' do
       follow_request = Fabricate.create(:follow_request)
       follow_request.authorize!
@@ -23,25 +34,4 @@ RSpec.describe FollowRequest, type: :model do
       expect(follow_request.account.muting_reblogs?(target)).to be true
     end
   end
-
-  describe '#reject!'
-
-  describe 'validations' do
-    it 'has a valid fabricator' do
-      follow_request = Fabricate.build(:follow_request)
-      expect(follow_request).to be_valid
-    end
-
-    it 'is invalid without an account' do
-      follow_request = Fabricate.build(:follow_request, account: nil)
-      follow_request.valid?
-      expect(follow_request).to model_have_error_on_field(:account)
-    end
-
-    it 'is invalid without a target account' do
-      follow_request = Fabricate.build(:follow_request, target_account: nil)
-      follow_request.valid?
-      expect(follow_request).to model_have_error_on_field(:target_account)      
-    end
-  end
 end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 435b4f326..b40a641f7 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -1,6 +1,83 @@
 require 'rails_helper'
 
 RSpec.describe MediaAttachment, type: :model do
+  describe 'local?' do
+    let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url) }
+
+    subject { media_attachment.local? }
+
+    context 'remote_url is blank' do
+      let(:remote_url) { '' }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'remote_url is present' do
+      let(:remote_url) { 'remote_url' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe 'needs_redownload?' do
+    let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url, file: file) }
+
+    subject { media_attachment.needs_redownload? }
+
+    context 'file is blank' do
+      let(:file) { nil }
+
+      context 'remote_url is blank' do
+        let(:remote_url) { '' }
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+
+      context 'remote_url is present' do
+        let(:remote_url) { 'remote_url' }
+
+        it 'returns true' do
+          is_expected.to be true
+        end
+      end
+    end
+
+    context 'file is present' do
+      let(:file) { attachment_fixture('avatar.gif') }
+
+      context 'remote_url is blank' do
+        let(:remote_url) { '' }
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+
+      context 'remote_url is present' do
+        let(:remote_url) { 'remote_url' }
+
+        it 'returns true' do
+          is_expected.to be false
+        end
+      end
+    end
+  end
+
+  describe '#to_param' do
+    let(:media_attachment) { Fabricate(:media_attachment) }
+    let(:shortcode)        { media_attachment.shortcode }
+
+    it 'returns shortcode' do
+      expect(media_attachment.to_param).to eq shortcode
+    end
+  end
+
   describe 'animated gif conversion' do
     let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) }
 
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 97e8095cd..763b1523f 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -5,6 +5,74 @@ RSpec.describe Notification, type: :model do
     pending
   end
 
+  describe '#target_status' do
+    before do
+      allow(notification).to receive(:type).and_return(type)
+      allow(notification).to receive(:activity).and_return(activity)
+    end
+
+    let(:notification) { Fabricate(:notification) }
+    let(:status)       { instance_double('Status') }
+    let(:favourite)    { instance_double('Favourite') }
+    let(:mention)      { instance_double('Mention') }
+
+    context 'type is :reblog' do
+      let(:type)     { :reblog }
+      let(:activity) { status }
+
+      it 'calls activity.reblog' do
+        expect(activity).to receive(:reblog)
+        notification.target_status
+      end
+    end
+
+    context 'type is :favourite' do
+      let(:type)     { :favourite }
+      let(:activity) { favourite }
+
+      it 'calls activity.status' do
+        expect(activity).to receive(:status)
+        notification.target_status
+      end
+    end
+
+    context 'type is :mention' do
+      let(:type)     { :mention }
+      let(:activity) { mention }
+
+      it 'calls activity.status' do
+        expect(activity).to receive(:status)
+        notification.target_status
+      end
+    end
+  end
+
+  describe '#browserable?' do
+    let(:notification) { Fabricate(:notification) }
+
+    subject { notification.browserable? }
+
+    context 'type is :follow_request' do
+      before do
+        allow(notification).to receive(:type).and_return(:follow_request)
+      end
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+
+    context 'type is not :follow_request' do
+      before do
+        allow(notification).to receive(:type).and_return(:else)
+      end
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+  end
+
   describe '#type' do
     it 'returns :reblog for a Status' do
       notification = Notification.new(activity: Status.new)
@@ -26,4 +94,49 @@ RSpec.describe Notification, type: :model do
       expect(notification.type).to eq :follow
     end
   end
+
+  describe '.reload_stale_associations!' do
+    context 'account_ids are empty' do
+      let(:cached_items) { [] }
+
+      subject { described_class.reload_stale_associations!(cached_items) }
+
+      it 'returns nil' do
+        is_expected.to be nil
+      end
+    end
+
+    context 'account_ids are present' do
+      before do
+        allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1)
+        allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2)
+        allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids)
+      end
+
+      let(:cached_items) do
+        [
+          Fabricate(:notification, activity: Fabricate(:status)),
+          Fabricate(:notification, activity: Fabricate(:follow)),
+        ]
+      end
+
+      let(:stale_account1) { cached_items[0].from_account }
+      let(:stale_account2) { cached_items[1].from_account }
+
+      let(:account1) { Fabricate(:account) }
+      let(:account2) { Fabricate(:account) }
+
+      let(:accounts_with_ids) { { account1.id => account1, account2.id => account2 } }
+
+      it 'reloads associations' do
+        expect(cached_items[0].from_account).to be stale_account1
+        expect(cached_items[1].from_account).to be stale_account2
+
+        described_class.reload_stale_associations!(cached_items)
+
+        expect(cached_items[0].from_account).to be account1
+        expect(cached_items[1].from_account).to be account2
+      end
+    end
+  end
 end
diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb
new file mode 100644
index 000000000..7a4597ee7
--- /dev/null
+++ b/spec/models/remote_follow_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe RemoteFollow do
+  before do
+    stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no').to_return(request_fixture('webfinger.txt'))
+  end
+
+  let(:attrs)         { nil }
+  let(:remote_follow) { described_class.new(attrs) }
+
+  describe '.initialize' do
+    subject { remote_follow.acct }
+
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' } }
+
+      it 'returns acct' do
+        is_expected.to eq 'gargron@quitter.no'
+      end
+    end
+
+    context 'attrs without acct' do
+      let(:attrs) { {} }
+
+      it do
+        is_expected.to be_nil
+      end
+    end
+  end
+
+  describe '#valid?' do
+    subject { remote_follow.valid? }
+
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' }}
+      
+      it do
+        is_expected.to be true
+      end
+    end
+
+    context 'attrs without acct' do
+      let(:attrs) { { } }
+
+      it do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe '#subscribe_address_for' do
+    before do
+      remote_follow.valid?
+    end
+
+    let(:attrs)   { { acct: 'gargron@quitter.no' } }
+    let(:account) { Fabricate(:account, username: 'alice') }
+
+    subject { remote_follow.subscribe_address_for(account) }
+
+    it 'returns subscribe address' do
+      is_expected.to eq 'https://quitter.no/main/ostatussub?profile=alice%40cb6e6126.ngrok.io'
+    end
+  end
+end
diff --git a/spec/models/remote_profile_spec.rb b/spec/models/remote_profile_spec.rb
new file mode 100644
index 000000000..da5048f0a
--- /dev/null
+++ b/spec/models/remote_profile_spec.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe RemoteProfile do
+  let(:remote_profile) { RemoteProfile.new(body) }
+  let(:body) do
+    <<-XML
+      <feed xmlns="http://www.w3.org/2005/Atom">
+      <author>John</author>
+    XML
+  end
+
+  describe '.initialize' do
+    it 'calls Nokogiri::XML.parse' do
+      expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8')
+      RemoteProfile.new(body)
+    end
+
+    it 'sets document' do
+      remote_profile = RemoteProfile.new(body)
+      expect(remote_profile).not_to be nil
+    end
+  end
+
+  describe '#root' do
+    let(:document) { remote_profile.document }
+
+    it 'callse document.at_xpath' do
+      expect(document).to receive(:at_xpath).with(
+        '/atom:feed|/atom:entry',
+        atom: OStatus::TagManager::XMLNS
+      )
+
+      remote_profile.root
+    end
+  end
+
+  describe '#author' do
+    let(:root) { remote_profile.root }
+
+    it 'calls root.at_xpath' do
+      expect(root).to receive(:at_xpath).with(
+        './atom:author|./dfrn:owner',
+        atom: OStatus::TagManager::XMLNS,
+        dfrn: OStatus::TagManager::DFRN_XMLNS
+      )
+
+      remote_profile.author
+    end
+  end
+
+  describe '#hub_link' do
+    let(:root) { remote_profile.root }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub')
+      remote_profile.hub_link
+    end
+  end
+
+  describe '#display_name' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './poco:displayName',
+        poco: OStatus::TagManager::POCO_XMLNS
+      ).with(no_args)
+
+      remote_profile.display_name
+    end
+  end
+
+  describe '#note' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './atom:summary|./poco:note',
+        atom: OStatus::TagManager::XMLNS,
+        poco: OStatus::TagManager::POCO_XMLNS
+      ).with(no_args)
+
+      remote_profile.note
+    end
+  end
+
+  describe '#scope' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './mastodon:scope',
+        mastodon: OStatus::TagManager::MTDN_XMLNS
+      ).with(no_args)
+
+      remote_profile.scope
+    end
+  end
+
+  describe '#avatar' do
+    let(:author) { remote_profile.author }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar')
+      remote_profile.avatar
+    end
+  end
+
+  describe '#header' do
+    let(:author) { remote_profile.author }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header')
+      remote_profile.header
+    end
+  end
+
+  describe '#locked?' do
+    before do
+      allow(remote_profile).to receive(:scope).and_return(scope)
+    end
+
+    subject { remote_profile.locked? }
+
+    context 'scope is private' do
+      let(:scope) { 'private' }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'scope is not private' do
+      let(:scope) { 'public' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+end
diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb
index 49c72fbd4..2aa695037 100644
--- a/spec/models/session_activation_spec.rb
+++ b/spec/models/session_activation_spec.rb
@@ -1,5 +1,127 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SessionActivation, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+  describe '#detection' do
+    let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') }
+
+    it 'sets a Browser instance as detection' do
+      expect(session_activation.detection).to be_kind_of Browser::Chrome
+    end
+  end
+
+  describe '#browser' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:detection)          { double(id: 1) }
+    let(:session_activation) { Fabricate(:session_activation) }
+
+    it 'returns detection.id' do
+      expect(session_activation.browser).to be 1
+    end
+  end
+
+  describe '#platform' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:session_activation) { Fabricate(:session_activation) }
+    let(:detection)          { double(platform: double(id: 1)) }
+
+    it 'returns detection.platform.id' do
+      expect(session_activation.platform).to be 1
+    end
+  end
+
+  describe '.active?' do
+    subject { described_class.active?(id) }
+
+    context 'id is absent' do
+      let(:id) { nil }
+
+      it 'returns nil' do
+        is_expected.to be nil
+      end
+    end
+
+    context 'id is present' do
+      let(:id) { '1' }
+      let!(:session_activation) { Fabricate(:session_activation, session_id: id) }
+
+      context 'id exists as session_id' do
+        it 'returns true' do
+          is_expected.to be true
+        end
+      end
+
+      context 'id does not exist as session_id' do
+        before do
+          session_activation.update!(session_id: '2')
+        end
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+    end
+  end
+
+  describe '.activate' do
+    let(:options) { { user: Fabricate(:user), session_id: '1' } }
+
+    it 'calls create! and purge_old' do
+      expect(described_class).to receive(:create!).with(options)
+      expect(described_class).to receive(:purge_old)
+      described_class.activate(options)
+    end
+
+    it 'returns an instance of SessionActivation' do
+      expect(described_class.activate(options)).to be_kind_of SessionActivation
+    end
+  end
+
+  describe '.deactivate' do
+    context 'id is absent' do
+      let(:id) { nil }
+
+      it 'returns nil' do
+        expect(described_class.deactivate(id)).to be nil
+      end
+    end
+
+    context 'id exists' do
+      let(:id) { '1' }
+
+      it 'calls where.destroy_all' do
+        expect(described_class).to receive_message_chain(:where, :destroy_all)
+          .with(session_id: id).with(no_args)
+
+        described_class.deactivate(id)
+      end
+    end
+  end
+
+  describe '.purge_old' do
+    it 'calls order.offset.destroy_all' do
+      expect(described_class).to receive_message_chain(:order, :offset, :destroy_all)
+        .with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args)
+
+      described_class.purge_old
+    end
+  end
+
+  describe '.exclusive' do
+    let(:id) { '1' }
+
+    it 'calls where.destroy_all' do
+      expect(described_class).to receive_message_chain(:where, :destroy_all)
+        .with('session_id != ?', id).with(no_args)
+
+      described_class.exclusive(id)
+    end
+  end
 end
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
new file mode 100644
index 000000000..e99dfc0d7
--- /dev/null
+++ b/spec/models/setting_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Setting, type: :model do
+  describe '#to_param' do
+    let(:setting) { Fabricate(:setting, var: var) }
+    let(:var)     { 'var' }
+
+    it 'returns setting.var' do
+      expect(setting.to_param).to eq var
+    end
+  end
+
+  describe '.[]' do
+    before do
+      allow(described_class).to receive(:rails_initialized?).and_return(rails_initialized)
+    end
+
+    let(:key) { 'key' }
+
+    context 'rails_initialized? is falsey' do
+      let(:rails_initialized) { false }
+
+      it 'calls RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).to receive(:[]).with(key)
+        described_class[key]
+      end
+    end
+
+    context 'rails_initialized? is truthy' do
+      before do
+        allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key)
+      end
+
+      let(:rails_initialized) { true }
+      let(:cache_key)         { 'cache-key' }
+      let(:cache_value)       { 'cache-value' }
+
+      it 'calls not RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).not_to receive(:[]).with(key)
+        described_class[key]
+      end
+
+      it 'calls Rails.cache.fetch' do
+        expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key)
+        described_class[key]
+      end
+
+      context 'Rails.cache does not exists' do
+        before do
+          allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
+          allow(described_class).to receive(:default_settings).and_return(default_settings)
+          allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+          Rails.cache.clear(cache_key)
+        end
+
+        let(:object)           { nil }
+        let(:default_value)    { 'default_value' }
+        let(:default_settings) { { key => default_value } }
+        let(:records)          { [Fabricate(:setting, var: key, value: nil)] }
+
+        it 'calls RailsSettings::Settings.object' do
+          expect(RailsSettings::Settings).to receive(:object).with(key)
+          described_class[key]
+        end
+
+        context 'RailsSettings::Settings.object returns truthy' do
+          let(:object) { db_val }
+          let(:db_val) { double(value: 'db_val') }
+
+          context 'default_value is a Hash' do
+            let(:default_value) { { default_value: 'default_value' } }
+
+            it 'calls default_value.with_indifferent_access.merge!' do
+              expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!)
+                .with(db_val.value)
+
+              described_class[key]
+            end
+          end
+
+          context 'default_value is not a Hash' do
+            let(:default_value) { 'default_value' }
+
+            it 'returns db_val.value' do
+              expect(described_class[key]).to be db_val.value
+            end
+          end
+        end
+
+        context 'RailsSettings::Settings.object returns falsey' do
+          let(:object) { nil }
+
+          it 'returns default_settings[key]' do
+            expect(described_class[key]).to be default_settings[key]
+          end
+        end
+      end
+
+      context 'Rails.cache exists' do
+        before do
+          Rails.cache.write(cache_key, cache_value)
+        end
+
+        it 'returns the cached value' do
+          expect(described_class[key]).to eq cache_value
+        end
+      end
+    end
+  end
+
+  describe '.all_as_records' do
+    before do
+      allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+      allow(described_class).to receive(:default_settings).and_return(default_settings)
+    end
+
+    let(:key)              { 'key' }
+    let(:default_value)    { 'default_value' }
+    let(:default_settings) { { key => default_value } }
+    let(:original_setting) { Fabricate(:setting, var: key, value: nil) }
+    let(:records)          { [original_setting] }
+
+    it 'returns a Hash' do
+      expect(described_class.all_as_records).to be_kind_of Hash
+    end
+
+    context 'records includes Setting with var as the key' do
+      let(:records) { [original_setting] }
+
+      it 'includes the original Setting' do
+        setting = described_class.all_as_records[key]
+        expect(setting).to eq original_setting
+      end
+    end
+
+    context 'records includes nothing' do
+      let(:records) { [] }
+
+      context 'default_value is not a Hash' do
+        it 'includes Setting with value of default_value' do
+          setting = described_class.all_as_records[key]
+
+          expect(setting).to be_kind_of Setting
+          expect(setting).to have_attributes(var: key)
+          expect(setting).to have_attributes(value: 'default_value')
+        end
+      end
+
+      context 'default_value is a Hash' do
+        let(:default_value) { { 'foo' => 'fuga' } }
+
+        it 'returns {}' do
+          expect(described_class.all_as_records).to eq({})
+        end
+      end
+    end
+  end
+
+  describe '.default_settings' do
+    before do
+      allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled)
+    end
+
+    subject { described_class.default_settings }
+
+    context 'RailsSettings::Default.enabled? is false' do
+      let(:enabled) { false }
+
+      it 'returns {}' do
+        is_expected.to eq({})
+      end
+    end
+
+    context 'RailsSettings::Settings.enabled? is true' do
+      let(:enabled) { true }
+
+      it 'returns instance of RailsSettings::Default' do
+        is_expected.to be_kind_of RailsSettings::Default
+      end
+    end
+  end
+end
diff --git a/spec/models/site_upload_spec.rb b/spec/models/site_upload_spec.rb
index 8745d54b8..f7ea06921 100644
--- a/spec/models/site_upload_spec.rb
+++ b/spec/models/site_upload_spec.rb
@@ -1,5 +1,13 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SiteUpload, type: :model do
+  describe '#cache_key' do
+    let(:site_upload) { SiteUpload.new(var: 'var') }
 
+    it 'returns cache_key' do
+      expect(site_upload.cache_key).to eq 'site_uploads/var'
+    end
+  end
 end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 12e857169..91fd13c94 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -69,6 +69,36 @@ RSpec.describe Status, type: :model do
     end
   end
 
+  describe '#hidden?' do
+    context 'if private_visibility?' do
+      it 'returns true' do
+        subject.visibility = :private
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if direct_visibility?' do
+      it 'returns true' do
+        subject.visibility = :direct
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if public_visibility?' do
+      it 'returns false' do
+        subject.visibility = :public
+        expect(subject.hidden?).to be false
+      end
+    end
+
+    context 'if unlisted_visibility?' do
+      it 'returns false' do
+        subject.visibility = :unlisted
+        expect(subject.hidden?).to be false
+      end
+    end
+  end
+
   describe '#content' do
     it 'returns the text of the status if it is not a reblog' do
       expect(subject.content).to eql subject.text
diff --git a/spec/models/stream_entry_spec.rb b/spec/models/stream_entry_spec.rb
index 3b7ff5143..8f8bfbd58 100644
--- a/spec/models/stream_entry_spec.rb
+++ b/spec/models/stream_entry_spec.rb
@@ -6,6 +6,121 @@ RSpec.describe StreamEntry, type: :model do
   let(:status)    { Fabricate(:status, account: alice) }
   let(:reblog)    { Fabricate(:status, account: bob, reblog: status) }
   let(:reply)     { Fabricate(:status, account: bob, thread: status) }
+  let(:stream_entry) { Fabricate(:stream_entry, activity: activity) }
+  let(:activity)     { reblog }
+
+  describe '#object_type' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+      allow(stream_entry).to receive(:targeted?).and_return(targeted)
+    end
+
+    subject { stream_entry.object_type }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+      let(:targeted) { false }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'targeted? is true' do
+      let(:orphaned) { false }
+      let(:targeted) { true }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'orphaned? and targeted? are false' do
+      let(:orphaned) { false }
+      let(:targeted) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :note' do
+          is_expected.to be :note
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :comment' do
+          is_expected.to be :comment
+        end
+      end
+    end
+  end
+
+  describe '#verb' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.verb }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns :delete' do
+        is_expected.to be :delete
+      end
+    end
+
+    context 'orphaned? is false' do
+      let(:orphaned) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :share' do
+          is_expected.to be :share
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :post' do
+          is_expected.to be :post
+        end
+      end
+    end
+  end
+
+  describe '#mentions' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.mentions }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns []' do
+        is_expected.to eq []
+      end
+    end
+
+    context 'orphaned? is false' do
+      before do
+        reblog.mentions << Fabricate(:mention, account: alice)
+        reblog.mentions << Fabricate(:mention, account: bob)
+      end
+
+      let(:orphaned) { false }
+
+      it 'returns [Account] includes alice and bob' do
+        is_expected.to eq [alice, bob]
+      end
+    end
+  end
 
   describe '#targeted?' do
     it 'returns true for a reblog' do
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index f727fa1dd..1ca50cc29 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -35,6 +35,13 @@ RSpec.describe Tag, type: :model do
     end
   end
 
+  describe '#to_param' do
+    it 'returns name' do
+      tag = Fabricate(:tag, name: 'foo')
+      expect(tag.to_param).to eq 'foo'
+    end
+  end
+
   describe '.search_for' do
     it 'finds tag records with matching names' do
       tag = Fabricate(:tag, name: "match")
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 99aeca01b..77a12c26d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -177,27 +177,10 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_auto_play_gif' do
-    it 'returns auto-play gif setting' do
+  describe 'settings' do
+    it 'is instance of Settings::ScopedSettings' do
       user = Fabricate(:user)
-      user.settings[:auto_play_gif] = false
-      expect(user.setting_auto_play_gif).to eq false
-    end
-  end
-  
-  describe '#setting_system_font_ui' do
-    it 'returns system font ui setting' do
-      user = Fabricate(:user)
-      user.settings[:system_font_ui] = false
-      expect(user.setting_system_font_ui).to eq false
-    end
-  end
-
-  describe '#setting_boost_modal' do
-    it 'returns boost modal setting' do
-      user = Fabricate(:user)
-      user.settings[:boost_modal] = false
-      expect(user.setting_boost_modal).to eq false
+      expect(user.settings).to be_kind_of Settings::ScopedSettings
     end
   end
 
@@ -219,22 +202,6 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_unfollow_modal' do
-    it 'returns unfollow modal setting' do
-      user = Fabricate(:user)
-      user.settings[:unfollow_modal] = true
-      expect(user.setting_unfollow_modal).to eq true
-    end
-  end
-
-  describe '#setting_delete_modal' do
-    it 'returns delete modal setting' do
-      user = Fabricate(:user)
-      user.settings[:delete_modal] = false
-      expect(user.setting_delete_modal).to eq false
-    end
-  end
-
   describe 'whitelist' do
     around(:each) do |example|
       old_whitelist = Rails.configuration.x.email_whitelist
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 250a880a2..a8ebc16b8 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -67,6 +67,39 @@ RSpec.describe NotifyService do
       is_expected.to_not change(Notification, :count)
     end
   end
+  
+  context 'for direct messages' do
+    let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) }
+
+    before do
+      user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled)
+    end
+
+    context 'if recipient is supposed to be following sender' do
+      let(:enabled) { true }
+
+      it 'does not notify' do
+        is_expected.to_not change(Notification, :count)
+      end
+
+      context 'if the message chain initiated by recipient' do
+        let(:reply_to) { Fabricate(:status, account: recipient) }
+        let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
+
+        it 'does notify' do
+          is_expected.to change(Notification, :count)
+        end
+      end
+    end
+
+    context 'if recipient is NOT supposed to be following sender' do
+      let(:enabled) { false }
+
+      it 'does notify' do
+        is_expected.to change(Notification, :count)
+      end
+    end
+  end
 
   context do
     let(:asshole)  { Fabricate(:account, username: 'asshole') }