about summary refs log tree commit diff
path: root/spec/models
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models')
-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
16 files changed, 1010 insertions, 81 deletions
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