about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
authorkibigo! <marrus-sh@users.noreply.github.com>2017-10-11 10:43:10 -0700
committerkibigo! <marrus-sh@users.noreply.github.com>2017-10-11 10:43:10 -0700
commit8d6b9ba4946b5b159af0fbd130637a226a286796 (patch)
tree9def26711682d29338cfa1b081822029a01669eb /spec
parentf0a2a6c875e9294f0ea1d4c6bc90529e41a2dc37 (diff)
parent476e79b8e340c9103352a0799e102e4aca1a5593 (diff)
Merge upstream 2.0ish #165
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/admin/account_moderation_notes_controller_spec.rb4
-rw-r--r--spec/controllers/admin/email_domain_blocks_controller_spec.rb59
-rw-r--r--spec/controllers/api/salmon_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/apps/credentials_controller_spec.rb43
-rw-r--r--spec/controllers/api/v1/blocks_controller_spec.rb42
-rw-r--r--spec/controllers/api/v1/media_controller_spec.rb29
-rw-r--r--spec/controllers/manifests_controller_spec.rb4
-rw-r--r--spec/controllers/settings/follower_domains_controller_spec.rb67
-rw-r--r--spec/controllers/settings/notifications_controller_spec.rb37
-rw-r--r--spec/controllers/settings/preferences_controller_spec.rb6
-rw-r--r--spec/controllers/tags_controller_spec.rb42
-rw-r--r--spec/fabricators/account_moderation_note_fabricator.rb4
-rw-r--r--spec/fabricators/email_domain_block_fabricator.rb3
-rw-r--r--spec/helpers/admin/account_moderation_notes_helper_spec.rb15
-rw-r--r--spec/helpers/jsonld_helper_spec.rb35
-rw-r--r--spec/javascript/components/avatar.test.js14
-rw-r--r--spec/javascript/components/avatar_overlay.test.js10
-rw-r--r--spec/javascript/components/button.test.js23
-rw-r--r--spec/javascript/components/display_name.test.js7
-rw-r--r--spec/javascript/components/emoji_index.test.js111
-rw-r--r--spec/javascript/components/emojify.test.js16
-rw-r--r--spec/javascript/setup.js8
-rw-r--r--spec/lib/activitypub/activity/create_spec.rb10
-rw-r--r--spec/lib/delivery_failure_tracker_spec.rb71
-rw-r--r--spec/lib/feed_manager_spec.rb109
-rw-r--r--spec/models/account_moderation_note_spec.rb5
-rw-r--r--spec/models/email_domain_block_spec.rb21
-rw-r--r--spec/models/feed_spec.rb2
-rw-r--r--spec/models/media_attachment_spec.rb9
-rw-r--r--spec/services/activitypub/fetch_remote_account_service_spec.rb2
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb41
-rw-r--r--spec/services/activitypub/process_collection_service_spec.rb4
-rw-r--r--spec/services/batched_remove_status_service_spec.rb3
-rw-r--r--spec/services/fetch_remote_resource_service_spec.rb4
-rw-r--r--spec/services/precompute_feed_service_spec.rb2
35 files changed, 722 insertions, 144 deletions
diff --git a/spec/controllers/admin/account_moderation_notes_controller_spec.rb b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
new file mode 100644
index 000000000..ca4e55c4d
--- /dev/null
+++ b/spec/controllers/admin/account_moderation_notes_controller_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe Admin::AccountModerationNotesController, type: :controller do
+end
diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
new file mode 100644
index 000000000..295de9073
--- /dev/null
+++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Admin::EmailDomainBlocksController, type: :controller do
+  render_views
+
+  before do
+    sign_in Fabricate(:user, admin: true), scope: :user
+  end
+
+  describe 'GET #index' do
+    around do |example|
+      default_per_page = EmailDomainBlock.default_per_page
+      EmailDomainBlock.paginates_per 1
+      example.run
+      EmailDomainBlock.paginates_per default_per_page
+    end
+
+    it 'renders email blacks' do
+      2.times { Fabricate(:email_domain_block) }
+
+      get :index, params: { page: 2 }
+
+      assigned = assigns(:email_domain_blocks)
+      expect(assigned.count).to eq 1
+      expect(assigned.klass).to be EmailDomainBlock
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #new' do
+    it 'assigns a new email black' do
+      get :new
+
+      expect(assigns(:email_domain_block)).to be_instance_of(EmailDomainBlock)
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'POST #create' do
+    it 'blocks the domain when succeeded to save' do
+      post :create, params: { email_domain_block: { domain: 'example.com'} }
+
+      expect(flash[:notice]).to eq I18n.t('admin.email_domain_blocks.created_msg')
+      expect(response).to redirect_to(admin_email_domain_blocks_path)
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    it 'unblocks the domain' do
+      email_domain_block = Fabricate(:email_domain_block)
+      delete :destroy, params: { id: email_domain_block.id } 
+
+      expect(flash[:notice]).to eq I18n.t('admin.email_domain_blocks.destroyed_msg')
+      expect(response).to redirect_to(admin_email_domain_blocks_path)
+    end
+  end
+end
diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb
index 3e4686200..323d85b61 100644
--- a/spec/controllers/api/salmon_controller_spec.rb
+++ b/spec/controllers/api/salmon_controller_spec.rb
@@ -46,8 +46,8 @@ RSpec.describe Api::SalmonController, type: :controller do
         post :update, params: { id: account.id }
       end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(202)
+      it 'returns http client error' do
+        expect(response).to have_http_status(400)
       end
     end
   end
diff --git a/spec/controllers/api/v1/apps/credentials_controller_spec.rb b/spec/controllers/api/v1/apps/credentials_controller_spec.rb
new file mode 100644
index 000000000..38f2a4e10
--- /dev/null
+++ b/spec/controllers/api/v1/apps/credentials_controller_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+describe Api::V1::Apps::CredentialsController do
+  render_views
+
+  let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) }
+
+  context 'with an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { token }
+    end
+
+    describe 'GET #show' do
+      before do
+        get :show
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'does not contain client credentials' do
+        json = body_as_json
+
+        expect(json).to_not have_key(:client_secret)
+        expect(json).to_not have_key(:client_id)
+      end
+    end
+  end
+
+  context 'without an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { nil }
+    end
+
+    describe 'GET #show' do
+      it 'returns http unauthorized' do
+        get :show
+        expect(response).to have_http_status(:unauthorized)
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb
index f25a7e878..9b2bbdf0e 100644
--- a/spec/controllers/api/v1/blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/blocks_controller_spec.rb
@@ -6,15 +6,47 @@ RSpec.describe Api::V1::BlocksController, type: :controller do
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
   let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'follow') }
 
-  before do
-    Fabricate(:block, account: user.account)
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
 
   describe 'GET #index' do
-    it 'returns http success' do
+    it 'limits according to limit parameter' do
+      2.times.map { Fabricate(:block, account: user.account) }
       get :index, params: { limit: 1 }
+      expect(body_as_json.size).to eq 1
+    end
+
+    it 'queries blocks in range according to max_id' do
+      blocks = 2.times.map { Fabricate(:block, account: user.account) }
+
+      get :index, params: { max_id: blocks[1] }
+
+      expect(body_as_json.size).to eq 1
+      expect(body_as_json[0][:id]).to eq blocks[0].target_account_id.to_s
+    end
+
+    it 'queries blocks in range according to since_id' do
+      blocks = 2.times.map { Fabricate(:block, account: user.account) }
 
+      get :index, params: { since_id: blocks[0] }
+
+      expect(body_as_json.size).to eq 1
+      expect(body_as_json[0][:id]).to eq blocks[1].target_account_id.to_s
+    end
+
+    it 'sets pagination header for next path' do
+      blocks = 2.times.map { Fabricate(:block, account: user.account) }
+      get :index, params: { limit: 1, since_id: blocks[0] }
+      expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq api_v1_blocks_url(limit: 1, max_id: blocks[1])
+    end
+
+    it 'sets pagination header for previous path' do
+      block = Fabricate(:block, account: user.account)
+      get :index
+      expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq api_v1_blocks_url(since_id: block)
+    end
+
+    it 'returns http success' do
+      get :index
       expect(response).to have_http_status(:success)
     end
   end
diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb
index baa22d7e4..0e494638f 100644
--- a/spec/controllers/api/v1/media_controller_spec.rb
+++ b/spec/controllers/api/v1/media_controller_spec.rb
@@ -101,4 +101,33 @@ RSpec.describe Api::V1::MediaController, type: :controller do
       end
     end
   end
+
+  describe 'PUT #update' do
+    context 'when somebody else\'s' do
+      let(:media) { Fabricate(:media_attachment, status: nil) }
+
+      it 'returns http not found' do
+        put :update, params: { id: media.id, description: 'Lorem ipsum!!!' }
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+
+    context 'when not attached to a status' do
+      let(:media) { Fabricate(:media_attachment, status: nil, account: user.account) }
+
+      it 'updates the description' do
+        put :update, params: { id: media.id, description: 'Lorem ipsum!!!' }
+        expect(media.reload.description).to eq 'Lorem ipsum!!!'
+      end
+    end
+
+    context 'when attached to a status' do
+      let(:media) { Fabricate(:media_attachment, status: Fabricate(:status), account: user.account) }
+
+      it 'returns http not found' do
+        put :update, params: { id: media.id, description: 'Lorem ipsum!!!' }
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+  end
 end
diff --git a/spec/controllers/manifests_controller_spec.rb b/spec/controllers/manifests_controller_spec.rb
index 6f188fa35..71967e4f0 100644
--- a/spec/controllers/manifests_controller_spec.rb
+++ b/spec/controllers/manifests_controller_spec.rb
@@ -8,10 +8,6 @@ describe ManifestsController do
       get :show, format: :json
     end
 
-    it 'assigns @instance_presenter' do
-      expect(assigns(:instance_presenter)).to be_kind_of InstancePresenter
-    end
-
     it 'returns http success' do
       expect(response).to have_http_status(:success)
     end
diff --git a/spec/controllers/settings/follower_domains_controller_spec.rb b/spec/controllers/settings/follower_domains_controller_spec.rb
index d48c3e68c..333223c61 100644
--- a/spec/controllers/settings/follower_domains_controller_spec.rb
+++ b/spec/controllers/settings/follower_domains_controller_spec.rb
@@ -5,15 +5,41 @@ describe Settings::FollowerDomainsController do
 
   let(:user) { Fabricate(:user) }
 
-  before do
-    sign_in user, scope: :user
+  shared_examples 'authenticate user' do
+    it 'redirects when not signed in' do
+      is_expected.to redirect_to '/auth/sign_in'
+    end
   end
 
   describe 'GET #show' do
+    subject { get :show, params: { page: 2 } }
+
+    it 'assigns @account' do
+      sign_in user, scope: :user
+      subject
+      expect(assigns(:account)).to eq user.account
+    end
+
+    it 'assigns @domains' do
+      Fabricate(:account, domain: 'old').follow!(user.account)
+      Fabricate(:account, domain: 'recent').follow!(user.account)
+
+      sign_in user, scope: :user
+      subject
+
+      assigned = assigns(:domains).per(1).to_a
+      expect(assigned.size).to eq 1
+      expect(assigned[0].accounts_from_domain).to eq 1
+      expect(assigned[0].domain).to eq 'old'
+    end
+
     it 'returns http success' do
-      get :show
+      sign_in user, scope: :user
+      subject
       expect(response).to have_http_status(:success)
     end
+
+    include_examples 'authenticate user'
   end
 
   describe 'PATCH #update' do
@@ -21,16 +47,39 @@ describe Settings::FollowerDomainsController do
 
     before do
       stub_request(:post, 'http://example.com/salmon').to_return(status: 200)
-      poopfeast.follow!(user.account)
-      patch :update, params: { select: ['example.com'] }
     end
 
-    it 'redirects back to followers page' do
-      expect(response).to redirect_to(settings_follower_domains_path)
+    shared_examples 'redirects back to followers page' do |notice|
+      it 'redirects back to followers page' do
+        poopfeast.follow!(user.account)
+
+        sign_in user, scope: :user
+        subject
+
+        expect(flash[:notice]).to eq notice
+        expect(response).to redirect_to(settings_follower_domains_path)
+      end
+    end
+
+    context 'when select parameter is not provided' do
+      subject { patch :update }
+      include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from 0 domains...'
     end
 
-    it 'soft-blocks followers from selected domains' do
-      expect(poopfeast.following?(user.account)).to be false
+    context 'when select parameter is provided' do
+      subject { patch :update, params: { select: ['example.com'] } }
+
+      it 'soft-blocks followers from selected domains' do
+        poopfeast.follow!(user.account)
+
+        sign_in user, scope: :user
+        subject
+
+        expect(poopfeast.following?(user.account)).to be false
+      end
+
+      include_examples 'authenticate user'
+      include_examples 'redirects back to followers page', 'In the process of soft-blocking followers from one domain...'
     end
   end
 end
diff --git a/spec/controllers/settings/notifications_controller_spec.rb b/spec/controllers/settings/notifications_controller_spec.rb
new file mode 100644
index 000000000..0bd993448
--- /dev/null
+++ b/spec/controllers/settings/notifications_controller_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Settings::NotificationsController do
+  render_views
+
+  let(:user) { Fabricate(:user) }
+
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #show' do
+    it 'returns http success' do
+      get :show
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'PUT #update' do
+    it 'updates notifications settings' do
+      user.settings['notification_emails'] = user.settings['notification_emails'].merge('follow' => false)
+      user.settings['interactions'] = user.settings['interactions'].merge('must_be_follower' => true)
+
+      put :update, params: {
+        user: {
+          notification_emails: { follow: '1' },
+          interactions: { must_be_follower: '0' },
+        }
+      }
+
+      expect(response).to redirect_to(settings_notifications_path)
+      user.reload
+      expect(user.settings['notification_emails']['follow']).to be true
+      expect(user.settings['interactions']['must_be_follower']).to be false
+    end
+  end
+end
diff --git a/spec/controllers/settings/preferences_controller_spec.rb b/spec/controllers/settings/preferences_controller_spec.rb
index 60fa42302..0f9431673 100644
--- a/spec/controllers/settings/preferences_controller_spec.rb
+++ b/spec/controllers/settings/preferences_controller_spec.rb
@@ -29,15 +29,11 @@ describe Settings::PreferencesController do
     it 'updates user settings' do
       user.settings['boost_modal'] = false
       user.settings['delete_modal'] = true
-      user.settings['notification_emails'] = user.settings['notification_emails'].merge('follow' => false)
-      user.settings['interactions'] = user.settings['interactions'].merge('must_be_follower' => true)
 
       put :update, params: {
         user: {
           setting_boost_modal: '1',
           setting_delete_modal: '0',
-          notification_emails: { follow: '1' },
-          interactions: { must_be_follower: '0' },
         }
       }
 
@@ -45,8 +41,6 @@ describe Settings::PreferencesController do
       user.reload
       expect(user.settings['boost_modal']).to be true
       expect(user.settings['delete_modal']).to be false
-      expect(user.settings['notification_emails']['follow']).to be true
-      expect(user.settings['interactions']['must_be_follower']).to be false
     end
   end
 end
diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb
index 3f46c14c0..b04666c0f 100644
--- a/spec/controllers/tags_controller_spec.rb
+++ b/spec/controllers/tags_controller_spec.rb
@@ -5,9 +5,9 @@ RSpec.describe TagsController, type: :controller do
 
   describe 'GET #show' do
     let!(:tag)     { Fabricate(:tag, name: 'test') }
-    let!(:local)  { Fabricate(:status, tags: [ tag ], text: 'local #test') }
-    let!(:remote) { Fabricate(:status, tags: [ tag ], text: 'remote #test', account: Fabricate(:account, domain: 'remote')) }
-    let!(:late)  { Fabricate(:status, tags: [ tag ], text: 'late #test') }
+    let!(:local)   { Fabricate(:status, tags: [tag], text: 'local #test') }
+    let!(:remote)  { Fabricate(:status, tags: [tag], text: 'remote #test', account: Fabricate(:account, domain: 'remote')) }
+    let!(:late)    { Fabricate(:status, tags: [tag], text: 'late #test') }
 
     context 'when tag exists' do
       it 'returns http success' do
@@ -15,41 +15,9 @@ RSpec.describe TagsController, type: :controller do
         expect(response).to have_http_status(:success)
       end
 
-      it 'renders public layout' do
+      it 'renders application layout' do
         get :show, params: { id: 'test', max_id: late.id }
-        expect(response).to render_template layout: 'public'
-      end
-
-      it 'renders only local statuses if local parameter is specified' do
-        get :show, params: { id: 'test', local: true, max_id: late.id }
-
-        expect(assigns(:tag)).to eq tag
-        statuses = assigns(:statuses).to_a
-        expect(statuses.size).to eq 1
-        expect(statuses[0]).to eq local
-      end
-
-      it 'renders local and remote statuses if local parameter is not specified' do
-        get :show, params: { id: 'test', max_id: late.id }
-
-        expect(assigns(:tag)).to eq tag
-        statuses = assigns(:statuses).to_a
-        expect(statuses.size).to eq 2
-        expect(statuses[0]).to eq remote
-        expect(statuses[1]).to eq local
-      end
-
-      it 'filters statuses by the current account' do
-        user = Fabricate(:user)
-        user.account.block!(remote.account)
-
-        sign_in(user)
-        get :show, params: { id: 'test', max_id: late.id }
-
-        expect(assigns(:tag)).to eq tag
-        statuses = assigns(:statuses).to_a
-        expect(statuses.size).to eq 1
-        expect(statuses[0]).to eq local
+        expect(response).to render_template layout: 'application'
       end
     end
 
diff --git a/spec/fabricators/account_moderation_note_fabricator.rb b/spec/fabricators/account_moderation_note_fabricator.rb
new file mode 100644
index 000000000..9277af165
--- /dev/null
+++ b/spec/fabricators/account_moderation_note_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:account_moderation_note) do
+  content "MyText"
+  account nil
+end
diff --git a/spec/fabricators/email_domain_block_fabricator.rb b/spec/fabricators/email_domain_block_fabricator.rb
new file mode 100644
index 000000000..d18af6433
--- /dev/null
+++ b/spec/fabricators/email_domain_block_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:email_domain_block) do
+  domain { sequence(:domain) { |i| "#{i}#{Faker::Internet.domain_name}" } }
+end
diff --git a/spec/helpers/admin/account_moderation_notes_helper_spec.rb b/spec/helpers/admin/account_moderation_notes_helper_spec.rb
new file mode 100644
index 000000000..01b60c851
--- /dev/null
+++ b/spec/helpers/admin/account_moderation_notes_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the Admin::AccountModerationNotesHelper. For example:
+#
+# describe Admin::AccountModerationNotesHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb
index 7d3912e6c..48bfdc306 100644
--- a/spec/helpers/jsonld_helper_spec.rb
+++ b/spec/helpers/jsonld_helper_spec.rb
@@ -30,6 +30,39 @@ describe JsonLdHelper do
   end
 
   describe '#fetch_resource' do
-    pending
+    context 'when the second argument is false' do
+      it 'returns resource even if the retrieved ID and the given URI does not match' do
+        stub_request(:get, 'https://bob/').to_return body: '{"id": "https://alice/"}'
+        stub_request(:get, 'https://alice/').to_return body: '{"id": "https://alice/"}'
+
+        expect(fetch_resource('https://bob/', false)).to eq({ 'id' => 'https://alice/' })
+      end
+
+      it 'returns nil if the object identified by the given URI and the object identified by the retrieved ID does not match' do
+        stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://marvin/"}'
+        stub_request(:get, 'https://marvin/').to_return body: '{"id": "https://alice/"}'
+
+        expect(fetch_resource('https://mallory/', false)).to eq nil
+      end
+    end
+
+    context 'when the second argument is true' do
+      it 'returns nil if the retrieved ID and the given URI does not match' do
+        stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://alice/"}'
+        expect(fetch_resource('https://mallory/', true)).to eq nil
+      end
+    end
+  end
+
+  describe '#fetch_resource_without_id_validation' do
+    it 'returns nil if the status code is not 200' do
+      stub_request(:get, 'https://host/').to_return status: 400, body: '{}'
+      expect(fetch_resource_without_id_validation('https://host/')).to eq nil
+    end
+
+    it 'returns hash' do
+      stub_request(:get, 'https://host/').to_return status: 200, body: '{}'
+      expect(fetch_resource_without_id_validation('https://host/')).to eq({})
+    end
   end
 end
diff --git a/spec/javascript/components/avatar.test.js b/spec/javascript/components/avatar.test.js
index ee40812ca..34949f2b5 100644
--- a/spec/javascript/components/avatar.test.js
+++ b/spec/javascript/components/avatar.test.js
@@ -1,8 +1,9 @@
+import React from 'react';
+import Avatar from '../../../app/javascript/mastodon/components/avatar';
+
 import { expect } from 'chai';
 import { render } from 'enzyme';
 import { fromJS }  from 'immutable';
-import React from 'react';
-import Avatar from '../../../app/javascript/mastodon/components/avatar';
 
 describe('<Avatar />', () => {
   const account = fromJS({
@@ -12,27 +13,28 @@ describe('<Avatar />', () => {
     avatar: '/animated/alice.gif',
     avatar_static: '/static/alice.jpg',
   });
+
   const size = 100;
   const animated = render(<Avatar account={account} animate size={size} />);
   const still = render(<Avatar account={account} size={size} />);
 
   // Autoplay
-  it('renders a div element with the given src as background', () => {
+  xit('renders a div element with the given src as background', () => {
     expect(animated.find('div')).to.have.style('background-image', `url(${account.get('avatar')})`);
   });
 
-  it('renders a div element of the given size', () => {
+  xit('renders a div element of the given size', () => {
     ['width', 'height'].map((attr) => {
       expect(animated.find('div')).to.have.style(attr, `${size}px`);
     });
   });
 
   // Still
-  it('renders a div element with the given static src as background if not autoplay', () => {
+  xit('renders a div element with the given static src as background if not autoplay', () => {
     expect(still.find('div')).to.have.style('background-image', `url(${account.get('avatar_static')})`);
   });
 
-  it('renders a div element of the given size if not autoplay', () => {
+  xit('renders a div element of the given size if not autoplay', () => {
     ['width', 'height'].map((attr) => {
       expect(still.find('div')).to.have.style(attr, `${size}px`);
     });
diff --git a/spec/javascript/components/avatar_overlay.test.js b/spec/javascript/components/avatar_overlay.test.js
index a8f0e13d5..fe1d3a012 100644
--- a/spec/javascript/components/avatar_overlay.test.js
+++ b/spec/javascript/components/avatar_overlay.test.js
@@ -1,8 +1,9 @@
+import React from 'react';
+import AvatarOverlay from '../../../app/javascript/mastodon/components/avatar_overlay';
+
 import { expect } from 'chai';
 import { render } from 'enzyme';
 import { fromJS }  from 'immutable';
-import React from 'react';
-import AvatarOverlay from '../../../app/javascript/mastodon/components/avatar_overlay';
 
 describe('<Avatar />', () => {
   const account = fromJS({
@@ -12,6 +13,7 @@ describe('<Avatar />', () => {
     avatar: '/animated/alice.gif',
     avatar_static: '/static/alice.jpg',
   });
+
   const friend = fromJS({
     username: 'eve',
     acct: 'eve@blackhat.lair',
@@ -22,12 +24,12 @@ describe('<Avatar />', () => {
 
   const overlay = render(<AvatarOverlay account={account} friend={friend} />);
 
-  it('renders account static src as base of overlay avatar', () => {
+  xit('renders account static src as base of overlay avatar', () => {
     expect(overlay.find('.account__avatar-overlay-base'))
       .to.have.style('background-image', `url(${account.get('avatar_static')})`);
   });
 
-  it('renders friend static src as overlay of overlay avatar', () => {
+  xit('renders friend static src as overlay of overlay avatar', () => {
     expect(overlay.find('.account__avatar-overlay-overlay'))
       .to.have.style('background-image', `url(${friend.get('avatar_static')})`);
   });
diff --git a/spec/javascript/components/button.test.js b/spec/javascript/components/button.test.js
index 9cf8b1eed..d2cd0b4e7 100644
--- a/spec/javascript/components/button.test.js
+++ b/spec/javascript/components/button.test.js
@@ -1,16 +1,17 @@
+import React from 'react';
+import Button from '../../../app/javascript/mastodon/components/button';
+
 import { expect } from 'chai';
 import { shallow } from 'enzyme';
 import sinon from 'sinon';
-import React from 'react';
-import Button from '../../../app/javascript/mastodon/components/button';
 
 describe('<Button />', () => {
-  it('renders a button element', () => {
+  xit('renders a button element', () => {
     const wrapper = shallow(<Button />);
     expect(wrapper).to.match('button');
   });
 
-  it('renders the given text', () => {
+  xit('renders the given text', () => {
     const text = 'foo';
     const wrapper = shallow(<Button text={text} />);
     expect(wrapper.find('button')).to.have.text(text);
@@ -30,18 +31,18 @@ describe('<Button />', () => {
     expect(handler.called).to.equal(false);
   });
 
-  it('renders a disabled attribute if props.disabled given', () => {
+  xit('renders a disabled attribute if props.disabled given', () => {
     const wrapper = shallow(<Button disabled />);
     expect(wrapper.find('button')).to.be.disabled();
   });
 
-  it('renders the children', () => {
+  xit('renders the children', () => {
     const children = <p>children</p>;
     const wrapper = shallow(<Button>{children}</Button>);
     expect(wrapper.find('button')).to.contain(children);
   });
 
-  it('renders the props.text instead of children', () => {
+  xit('renders the props.text instead of children', () => {
     const text = 'foo';
     const children = <p>children</p>;
     const wrapper = shallow(<Button text={text}>{children}</Button>);
@@ -49,22 +50,22 @@ describe('<Button />', () => {
     expect(wrapper.find('button')).to.not.contain(children);
   });
 
-  it('renders style="display: block; width: 100%;" if props.block given', () => {
+  xit('renders style="display: block; width: 100%;" if props.block given', () => {
     const wrapper = shallow(<Button block />);
     expect(wrapper.find('button')).to.have.className('button--block');
   });
 
-  it('renders style="display: inline-block; width: auto;" by default', () => {
+  xit('renders style="display: inline-block; width: auto;" by default', () => {
     const wrapper = shallow(<Button />);
     expect(wrapper.find('button')).to.not.have.className('button--block');
   });
 
-  it('adds class "button-secondary" if props.secondary given', () => {
+  xit('adds class "button-secondary" if props.secondary given', () => {
     const wrapper = shallow(<Button secondary />);
     expect(wrapper.find('button')).to.have.className('button-secondary');
   });
 
-  it('does not add class "button-secondary" by default', () => {
+  xit('does not add class "button-secondary" by default', () => {
     const wrapper = shallow(<Button />);
     expect(wrapper.find('button')).to.not.have.className('button-secondary');
   });
diff --git a/spec/javascript/components/display_name.test.js b/spec/javascript/components/display_name.test.js
index ab484cf3e..97a111894 100644
--- a/spec/javascript/components/display_name.test.js
+++ b/spec/javascript/components/display_name.test.js
@@ -1,11 +1,12 @@
+import React from 'react';
+import DisplayName from '../../../app/javascript/mastodon/components/display_name';
+
 import { expect } from 'chai';
 import { render } from 'enzyme';
 import { fromJS }  from 'immutable';
-import React from 'react';
-import DisplayName from '../../../app/javascript/mastodon/components/display_name';
 
 describe('<DisplayName />', () => {
-  it('renders display name + account name', () => {
+  xit('renders display name + account name', () => {
     const account = fromJS({
       username: 'bar',
       acct: 'bar@baz',
diff --git a/spec/javascript/components/emoji_index.test.js b/spec/javascript/components/emoji_index.test.js
new file mode 100644
index 000000000..cdb50cb8c
--- /dev/null
+++ b/spec/javascript/components/emoji_index.test.js
@@ -0,0 +1,111 @@
+import { expect } from 'chai';
+import { search } from '../../../app/javascript/mastodon/features/emoji/emoji_mart_search_light';
+import { emojiIndex } from 'emoji-mart';
+import { pick } from 'lodash';
+
+const trimEmojis = emoji => pick(emoji, ['id', 'unified', 'native', 'custom']);
+
+// hack to fix https://github.com/chaijs/type-detect/issues/98
+// see: https://github.com/chaijs/type-detect/issues/98#issuecomment-325010785
+import jsdom from 'jsdom';
+global.window = new jsdom.JSDOM().window;
+global.document = window.document;
+global.HTMLElement = window.HTMLElement;
+
+describe('emoji_index', () => {
+
+  it('should give same result for emoji_index_light and emoji-mart', () => {
+    let expected = [{
+      id: 'pineapple',
+      unified: '1f34d',
+      native: '🍍',
+    }];
+    expect(search('pineapple').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('pineapple').map(trimEmojis)).to.deep.equal(expected);
+  });
+
+  it('orders search results correctly', () => {
+    let expected = [{
+      id: 'apple',
+      unified: '1f34e',
+      native: '🍎',
+    }, {
+      id: 'pineapple',
+      unified: '1f34d',
+      native: '🍍',
+    }, {
+      id: 'green_apple',
+      unified: '1f34f',
+      native: '🍏',
+    }, {
+      id: 'iphone',
+      unified: '1f4f1',
+      native: '📱',
+    }];
+    expect(search('apple').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('apple').map(trimEmojis)).to.deep.equal(expected);
+  });
+
+  it('handles custom emoji', () => {
+    let custom = [{
+      id: 'mastodon',
+      name: 'mastodon',
+      short_names: ['mastodon'],
+      text: '',
+      emoticons: [],
+      keywords: ['mastodon'],
+      imageUrl: 'http://example.com',
+      custom: true,
+    }];
+    search('', { custom });
+    emojiIndex.search('', { custom });
+    let expected = [ { id: 'mastodon', custom: true } ];
+    expect(search('masto').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('masto').map(trimEmojis)).to.deep.equal(expected);
+  });
+
+  it('should filter only emojis we care about, exclude pineapple', () => {
+    let emojisToShowFilter = (unified) => unified !== '1F34D';
+    expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id))
+      .not.to.contain('pineapple');
+    expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id))
+      .not.to.contain('pineapple');
+  });
+
+  it('can include/exclude categories', () => {
+    expect(search('flag', { include: ['people'] }))
+      .to.deep.equal([]);
+    expect(emojiIndex.search('flag', { include: ['people'] }))
+      .to.deep.equal([]);
+  });
+
+  it('does an emoji whose unified name is irregular', () => {
+    let expected = [{
+      'id': 'water_polo',
+      'unified': '1f93d',
+      'native': '🤽',
+    }, {
+      'id': 'man-playing-water-polo',
+      'unified': '1f93d-200d-2642-fe0f',
+      'native': '🤽‍♂️',
+    }, {
+      'id': 'woman-playing-water-polo',
+      'unified': '1f93d-200d-2640-fe0f',
+      'native': '🤽‍♀️',
+    }];
+    expect(search('polo').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('polo').map(trimEmojis)).to.deep.equal(expected);
+  });
+
+  it('can search for thinking_face', () => {
+    let expected = [ { id: 'thinking_face', unified: '1f914', native: '🤔' } ];
+    expect(search('thinking_fac').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('thinking_fac').map(trimEmojis)).to.deep.equal(expected);
+  });
+
+  it('can search for woman-facepalming', () => {
+    let expected = [ { id: 'woman-facepalming', unified: '1f926-200d-2640-fe0f', native: '🤦‍♀️' } ];
+    expect(search('woman-facep').map(trimEmojis)).to.deep.equal(expected);
+    expect(emojiIndex.search('woman-facep').map(trimEmojis)).deep.equal(expected);
+  });
+});
diff --git a/spec/javascript/components/emojify.test.js b/spec/javascript/components/emojify.test.js
index 6e73c9251..3105c8e3f 100644
--- a/spec/javascript/components/emojify.test.js
+++ b/spec/javascript/components/emojify.test.js
@@ -1,5 +1,5 @@
 import { expect } from 'chai';
-import emojify from '../../../app/javascript/mastodon/emoji';
+import emojify from '../../../app/javascript/mastodon/features/emoji/emoji';
 
 describe('emojify', () => {
   it('ignores unknown shortcodes', () => {
@@ -44,4 +44,18 @@ describe('emojify', () => {
   it('ignores unicode inside of tags', () => {
     expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).to.equal('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
   });
+
+  it('does multiple emoji properly (issue 5188)', () => {
+    expect(emojify('👌🌈💕')).to.equal('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
+    expect(emojify('👌 🌈 💕')).to.equal('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg" /> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg" /> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg" />');
+  });
+
+  it('does an emoji that has no shortcode', () => {
+    expect(emojify('🕉️')).to.equal('<img draggable="false" class="emojione" alt="🕉️" title="" src="/emoji/1f549.svg" />');
+  });
+
+  it('does an emoji whose filename is irregular', () => {
+    expect(emojify('↙️')).to.equal('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
+  });
+
 });
diff --git a/spec/javascript/setup.js b/spec/javascript/setup.js
index c9c8aed07..ab8a36b95 100644
--- a/spec/javascript/setup.js
+++ b/spec/javascript/setup.js
@@ -1,11 +1,13 @@
 import { JSDOM } from 'jsdom';
-import chai from 'chai';
-import chaiEnzyme from 'chai-enzyme';
-chai.use(chaiEnzyme());
+import Enzyme from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+
+Enzyme.configure({ adapter: new Adapter() });
 
 const { window } = new JSDOM('', {
   userAgent: 'node.js',
 });
+
 Object.keys(window).forEach(property => {
   if (typeof global[property] === 'undefined') {
     global[property] = window[property];
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index cdd499150..3c3991c13 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -290,7 +290,9 @@ RSpec.describe ActivityPub::Activity::Create do
           tag: [
             {
               type: 'Emoji',
-              href: 'http://example.com/emoji.png',
+              icon: {
+                url: 'http://example.com/emoji.png',
+              },
               name: 'tinking',
             },
           ],
@@ -314,7 +316,9 @@ RSpec.describe ActivityPub::Activity::Create do
           tag: [
             {
               type: 'Emoji',
-              href: 'http://example.com/emoji.png',
+              icon: {
+                url: 'http://example.com/emoji.png',
+              },
             },
           ],
         }
@@ -326,7 +330,7 @@ RSpec.describe ActivityPub::Activity::Create do
       end
     end
 
-    context 'with emojis missing href' do
+    context 'with emojis missing icon' do
       let(:object_json) do
         {
           id: 'bar',
diff --git a/spec/lib/delivery_failure_tracker_spec.rb b/spec/lib/delivery_failure_tracker_spec.rb
new file mode 100644
index 000000000..39c8c7aaf
--- /dev/null
+++ b/spec/lib/delivery_failure_tracker_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe DeliveryFailureTracker do
+  subject { described_class.new('http://example.com/inbox') }
+
+  describe '#track_success!' do
+    before do
+      subject.track_failure!
+      subject.track_success!
+    end
+
+    it 'marks URL as available again' do
+      expect(described_class.available?('http://example.com/inbox')).to be true
+    end
+
+    it 'resets days to 0' do
+      expect(subject.days).to be_zero
+    end
+  end
+
+  describe '#track_failure!' do
+    it 'marks URL as unavailable after 7 days of being called' do
+      6.times { |i| Redis.current.sadd('exhausted_deliveries:http://example.com/inbox', i) }
+      subject.track_failure!
+
+      expect(subject.days).to eq 7
+      expect(described_class.unavailable?('http://example.com/inbox')).to be true
+    end
+
+    it 'repeated calls on the same day do not count' do
+      subject.track_failure!
+      subject.track_failure!
+
+      expect(subject.days).to eq 1
+    end
+  end
+
+  describe '.filter' do
+    before do
+      Redis.current.sadd('unavailable_inboxes', 'http://example.com/unavailable/inbox')
+    end
+
+    it 'removes URLs that are unavailable' do
+      result = described_class.filter(['http://example.com/good/inbox', 'http://example.com/unavailable/inbox'])
+
+      expect(result).to include('http://example.com/good/inbox')
+      expect(result).to_not include('http://example.com/unavailable/inbox')
+    end
+  end
+
+  describe '.track_inverse_success!' do
+    let(:from_account) { Fabricate(:account, inbox_url: 'http://example.com/inbox', shared_inbox_url: 'http://example.com/shared/inbox') }
+
+    before do
+      Redis.current.sadd('unavailable_inboxes', 'http://example.com/inbox')
+      Redis.current.sadd('unavailable_inboxes', 'http://example.com/shared/inbox')
+
+      described_class.track_inverse_success!(from_account)
+    end
+
+    it 'marks inbox URL as available again' do
+      expect(described_class.available?('http://example.com/inbox')).to be true
+    end
+
+    it 'marks shared inbox URL as available again' do
+      expect(described_class.available?('http://example.com/shared/inbox')).to be true
+    end
+  end
+end
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 22439cf35..923894ccb 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -1,6 +1,10 @@
 require 'rails_helper'
 
 RSpec.describe FeedManager do
+  it 'tracks at least as many statuses as reblogs' do
+    expect(FeedManager::REBLOG_FALLOFF).to be <= FeedManager::MAX_ITEMS
+  end
+
   describe '#key' do
     subject { FeedManager.instance.key(:home, 1) }
 
@@ -150,5 +154,110 @@ RSpec.describe FeedManager do
 
       expect(Redis.current.zcard("feed:type:#{account.id}")).to eq FeedManager::MAX_ITEMS
     end
+
+    it 'sends push updates for non-home timelines' do
+      account = Fabricate(:account)
+      status = Fabricate(:status)
+      allow(Redis.current).to receive_messages(publish: nil)
+
+      FeedManager.instance.push('type', account, status)
+
+      expect(Redis.current).to have_received(:publish).with("timeline:#{account.id}", any_args).at_least(:once)
+    end
+
+    context 'reblogs' do
+      it 'saves reblogs of unseen statuses' do
+        account = Fabricate(:account)
+        reblogged = Fabricate(:status)
+        reblog = Fabricate(:status, reblog: reblogged)
+
+        expect(FeedManager.instance.push('type', account, reblog)).to be true
+      end
+
+      it 'does not save a new reblog of a recent status' do
+        account = Fabricate(:account)
+        reblogged = Fabricate(:status)
+        reblog = Fabricate(:status, reblog: reblogged)
+
+        FeedManager.instance.push('type', account, reblogged)
+
+        expect(FeedManager.instance.push('type', account, reblog)).to be false
+      end
+
+      it 'saves a new reblog of an old status' do
+        account = Fabricate(:account)
+        reblogged = Fabricate(:status)
+        reblog = Fabricate(:status, reblog: reblogged)
+
+        FeedManager.instance.push('type', account, reblogged)
+
+        # Fill the feed with intervening statuses
+        FeedManager::REBLOG_FALLOFF.times do
+          FeedManager.instance.push('type', account, Fabricate(:status))
+        end
+
+        expect(FeedManager.instance.push('type', account, reblog)).to be true
+      end
+
+      it 'does not save a new reblog of a recently-reblogged status' do
+        account = Fabricate(:account)
+        reblogged = Fabricate(:status)
+        reblogs = 2.times.map { Fabricate(:status, reblog: reblogged) }
+
+        # The first reblog will be accepted
+        FeedManager.instance.push('type', account, reblogs.first)
+
+        # The second reblog should be ignored
+        expect(FeedManager.instance.push('type', account, reblogs.last)).to be false
+      end
+
+      it 'saves a new reblog of a long-ago-reblogged status' do
+        account = Fabricate(:account)
+        reblogged = Fabricate(:status)
+        reblogs = 2.times.map { Fabricate(:status, reblog: reblogged) }
+
+        # The first reblog will be accepted
+        FeedManager.instance.push('type', account, reblogs.first)
+
+        # Fill the feed with intervening statuses
+        FeedManager::REBLOG_FALLOFF.times do
+          FeedManager.instance.push('type', account, Fabricate(:status))
+        end
+
+        # The second reblog should also be accepted
+        expect(FeedManager.instance.push('type', account, reblogs.last)).to be true
+      end
+    end
+  end
+
+  describe '#unpush' do
+    it 'leaves a reblogged status when deleting the reblog' do
+      account = Fabricate(:account)
+      reblogged = Fabricate(:status)
+      status = Fabricate(:status, reblog: reblogged)
+
+      FeedManager.instance.push('type', account, status)
+
+      # The reblogging status should show up under normal conditions.
+      expect(Redis.current.zrange("feed:type:#{account.id}", 0, -1)).to eq [status.id.to_s]
+
+      FeedManager.instance.unpush('type', account, status)
+
+      # Because we couldn't tell if the status showed up any other way,
+      # we had to stick the reblogged status in by itself.
+      expect(Redis.current.zrange("feed:type:#{account.id}", 0, -1)).to eq [reblogged.id.to_s]
+    end
+
+    it 'sends push updates' do
+      account = Fabricate(:account)
+      status = Fabricate(:status)
+      FeedManager.instance.push('type', account, status)
+
+      allow(Redis.current).to receive_messages(publish: nil)
+      FeedManager.instance.unpush('type', account, status)
+
+      deletion = Oj.dump(event: :delete, payload: status.id.to_s)
+      expect(Redis.current).to have_received(:publish).with("timeline:#{account.id}", deletion)
+    end
   end
 end
diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb
new file mode 100644
index 000000000..c4be8c4af
--- /dev/null
+++ b/spec/models/account_moderation_note_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AccountModerationNote, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb
new file mode 100644
index 000000000..5f5d189d9
--- /dev/null
+++ b/spec/models/email_domain_block_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+RSpec.describe EmailDomainBlock, type: :model do
+  describe 'validations' do
+    it 'has a valid fabricator' do
+      email_domain_block = Fabricate.build(:email_domain_block)
+      expect(email_domain_block).to be_valid
+    end
+  end
+
+  describe 'block?' do
+    it 'returns true if the domain is registed' 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
+    end
+  end
+end
diff --git a/spec/models/feed_spec.rb b/spec/models/feed_spec.rb
index 1c377c17f..5433f44bd 100644
--- a/spec/models/feed_spec.rb
+++ b/spec/models/feed_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Feed, type: :model do
       Fabricate(:status, account: account, id: 3)
       Fabricate(:status, account: account, id: 10)
       Redis.current.zadd(FeedManager.instance.key(:home, account.id),
-                        [[4, 'deleted'], [3, 'val3'], [2, 'val2'], [1, 'val1']])
+                        [[4, 4], [3, 3], [2, 2], [1, 1]])
 
       feed = Feed.new(:home, account)
       results = feed.get(3)
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index f6717b7d5..9fce5bc4f 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe MediaAttachment, type: :model do
       expect(media.file.meta["original"]["height"]).to eq 128
       expect(media.file.meta["original"]["aspect"]).to eq 1.0
     end
-
   end
 
   describe 'non-animated gif non-conversion' do
@@ -50,4 +49,12 @@ RSpec.describe MediaAttachment, type: :model do
       expect(media.file.meta["small"]["aspect"]).to eq 400.0/267
     end
   end
+
+  describe 'descriptions for remote attachments' do
+    it 'are cut off at 140 characters' do
+      media = Fabricate(:media_attachment, description: 'foo' * 1000, remote_url: 'http://example.com/blah.jpg')
+
+      expect(media.description.size).to be <= 420
+    end
+  end
 end
diff --git a/spec/services/activitypub/fetch_remote_account_service_spec.rb b/spec/services/activitypub/fetch_remote_account_service_spec.rb
index ed7e9bba8..c50d3fb97 100644
--- a/spec/services/activitypub/fetch_remote_account_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService do
   end
 
   describe '#call' do
-    let(:account) { subject.call('https://example.com/alice') }
+    let(:account) { subject.call('https://example.com/alice', id: true) }
 
     shared_examples 'sets profile data' do
       it 'returns an account' do
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index 3b22257ed..ebf422392 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -15,21 +15,11 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
     }
   end
 
-  let(:create) do
-    {
-      '@context': 'https://www.w3.org/ns/activitystreams',
-      id: "https://#{valid_domain}/@foo/1234/activity",
-      type: 'Create',
-      actor: ActivityPub::TagManager.instance.uri_for(sender),
-      object: note,
-    }
-  end
-
   subject { described_class.new }
 
   describe '#call' do
     before do
-      subject.call(object[:id], Oj.dump(object))
+      subject.call(object[:id], prefetched_body: Oj.dump(object))
     end
 
     context 'with Note object' do
@@ -42,34 +32,5 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
         expect(status.text).to eq 'Lorem ipsum'
       end
     end
-
-    context 'with Create activity' do
-      let(:object) { create }
-
-      it 'creates status' do
-        status = sender.statuses.first
-        
-        expect(status).to_not be_nil
-        expect(status.text).to eq 'Lorem ipsum'
-      end
-    end
-
-    context 'with Announce activity' do
-      let(:status) { Fabricate(:status, account: recipient) }
-
-      let(:object) do
-        {
-          '@context': 'https://www.w3.org/ns/activitystreams',
-          id: "https://#{valid_domain}/@foo/1234/activity",
-          type: 'Announce',
-          actor: ActivityPub::TagManager.instance.uri_for(sender),
-          object: ActivityPub::TagManager.instance.uri_for(status),
-        }
-      end
-
-      it 'creates a reblog by sender of status' do
-        expect(sender.reblogged?(status)).to be true
-      end
-    end
   end
 end
diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb
index c1cc22523..3cea970cf 100644
--- a/spec/services/activitypub/process_collection_service_spec.rb
+++ b/spec/services/activitypub/process_collection_service_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe ActivityPub::ProcessCollectionService do
 
       it 'processes payload with sender if no signature exists' do
         expect_any_instance_of(ActivityPub::LinkedDataSignature).not_to receive(:verify_account!)
-        expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), forwarder)
+        expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), forwarder, instance_of(Hash))
 
         subject.call(json, forwarder)
       end
@@ -37,7 +37,7 @@ RSpec.describe ActivityPub::ProcessCollectionService do
         payload['signature'] = {'type' => 'RsaSignature2017'}
 
         expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_account!).and_return(actor)
-        expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor)
+        expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash))
 
         subject.call(json, forwarder)
       end
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index f5c9adfb5..c82c45e09 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe BatchedRemoveStatusService do
 
   let!(:alice)  { Fabricate(:account) }
   let!(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
-  let!(:jeff)   { Fabricate(:account) }
+  let!(:jeff)   { Fabricate(:user).account }
   let!(:hank)   { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
   let(:status1) { PostStatusService.new.call(alice, 'Hello @bob@example.com') }
@@ -19,6 +19,7 @@ RSpec.describe BatchedRemoveStatusService do
     stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
 
     Fabricate(:subscription, account: alice, callback_url: 'http://example.com/push', confirmed: true, expires_at: 30.days.from_now)
+    jeff.user.update(current_sign_in_at: Time.now)
     jeff.follow!(alice)
     hank.follow!(alice)
 
diff --git a/spec/services/fetch_remote_resource_service_spec.rb b/spec/services/fetch_remote_resource_service_spec.rb
index c14fcfc4e..b80fb2475 100644
--- a/spec/services/fetch_remote_resource_service_spec.rb
+++ b/spec/services/fetch_remote_resource_service_spec.rb
@@ -22,7 +22,7 @@ describe FetchRemoteResourceService do
       allow(FetchAtomService).to receive(:new).and_return service
       feed_url = 'http://feed-url'
       feed_content = '<feed>contents</feed>'
-      allow(service).to receive(:call).with(url).and_return([feed_url, feed_content])
+      allow(service).to receive(:call).with(url).and_return([feed_url, { prefetched_body: feed_content }])
 
       account_service = double
       allow(FetchRemoteAccountService).to receive(:new).and_return(account_service)
@@ -39,7 +39,7 @@ describe FetchRemoteResourceService do
       allow(FetchAtomService).to receive(:new).and_return service
       feed_url = 'http://feed-url'
       feed_content = '<entry>contents</entry>'
-      allow(service).to receive(:call).with(url).and_return([feed_url, feed_content])
+      allow(service).to receive(:call).with(url).and_return([feed_url, { prefetched_body: feed_content }])
 
       account_service = double
       allow(FetchRemoteStatusService).to receive(:new).and_return(account_service)
diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb
index dbd08ac1b..d1ef6c184 100644
--- a/spec/services/precompute_feed_service_spec.rb
+++ b/spec/services/precompute_feed_service_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe PrecomputeFeedService do
 
       subject.call(account)
 
-      expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to eq status.id
+      expect(Redis.current.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to eq status.id.to_f
     end
 
     it 'does not raise an error even if it could not find any status' do