about summary refs log tree commit diff
path: root/spec
diff options
context:
space:
mode:
authorDavid Yip <yipdw@member.fsf.org>2017-09-09 14:27:47 -0500
committerDavid Yip <yipdw@member.fsf.org>2017-09-09 14:27:47 -0500
commitb9f7bc149b2a6abfbdaee83e6992b617b8bdb18e (patch)
tree355225f4424a6ea1b40c66c5540ccab42096e3bf /spec
parente18ed4bbc7ab4e258d05a3e2a5db0790f67a8f37 (diff)
parent5d170587e3b6c1a3b3ebe0910b62a4c526e2900d (diff)
Merge branch 'origin/master' into sync/upstream
 Conflicts:
	app/javascript/mastodon/components/status_list.js
	app/javascript/mastodon/features/notifications/index.js
	app/javascript/mastodon/features/ui/components/modal_root.js
	app/javascript/mastodon/features/ui/components/onboarding_modal.js
	app/javascript/mastodon/features/ui/index.js
	app/javascript/styles/about.scss
	app/javascript/styles/accounts.scss
	app/javascript/styles/components.scss
	app/presenters/instance_presenter.rb
	app/services/post_status_service.rb
	app/services/reblog_service.rb
	app/views/about/more.html.haml
	app/views/about/show.html.haml
	app/views/accounts/_header.html.haml
	config/webpack/loaders/babel.js
	spec/controllers/api/v1/accounts/credentials_controller_spec.rb
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/accounts_controller_spec.rb19
-rw-r--r--spec/controllers/activitypub/inboxes_controller_spec.rb7
-rw-r--r--spec/controllers/activitypub/outboxes_controller_spec.rb23
-rw-r--r--spec/controllers/api/oembed_controller_spec.rb1
-rw-r--r--spec/controllers/api/subscriptions_controller_spec.rb26
-rw-r--r--spec/controllers/api/v1/accounts/credentials_controller_spec.rb93
-rw-r--r--spec/controllers/api/v1/accounts/relationships_controller_spec.rb4
-rw-r--r--spec/controllers/api/v1/accounts/statuses_controller_spec.rb36
-rw-r--r--spec/controllers/api/v1/favourites_controller_spec.rb3
-rw-r--r--spec/controllers/api/v1/statuses/pins_controller_spec.rb57
-rw-r--r--spec/controllers/concerns/account_controller_concern_spec.rb2
-rw-r--r--spec/controllers/concerns/signature_verification_spec.rb78
-rw-r--r--spec/controllers/remote_follow_controller_spec.rb8
-rw-r--r--spec/controllers/settings/applications_controller_spec.rb188
-rw-r--r--spec/controllers/settings/profiles_controller_spec.rb2
-rw-r--r--spec/controllers/statuses_controller_spec.rb12
-rw-r--r--spec/controllers/stream_entries_controller_spec.rb8
-rw-r--r--spec/fabricators/status_pin_fabricator.rb4
-rw-r--r--spec/helpers/jsonld_helper_spec.rb35
-rw-r--r--spec/helpers/routing_helper_spec.rb43
-rw-r--r--spec/lib/activitypub/activity/accept_spec.rb38
-rw-r--r--spec/lib/activitypub/activity/announce_spec.rb29
-rw-r--r--spec/lib/activitypub/activity/block_spec.rb28
-rw-r--r--spec/lib/activitypub/activity/create_spec.rb221
-rw-r--r--spec/lib/activitypub/activity/delete_spec.rb53
-rw-r--r--spec/lib/activitypub/activity/follow_spec.rb49
-rw-r--r--spec/lib/activitypub/activity/like_spec.rb29
-rw-r--r--spec/lib/activitypub/activity/reject_spec.rb38
-rw-r--r--spec/lib/activitypub/activity/undo_spec.rb107
-rw-r--r--spec/lib/activitypub/activity/update_spec.rb41
-rw-r--r--spec/lib/activitypub/linked_data_signature_spec.rb82
-rw-r--r--spec/lib/activitypub/tag_manager_spec.rb99
-rw-r--r--spec/lib/ostatus/atom_serializer_spec.rb11
-rw-r--r--spec/lib/status_finder_spec.rb (renamed from spec/lib/stream_entry_finder_spec.rb)24
-rw-r--r--spec/models/account_spec.rb1
-rw-r--r--spec/models/import_spec.rb19
-rw-r--r--spec/models/status_pin_spec.rb41
-rw-r--r--spec/models/user_spec.rb20
-rw-r--r--spec/services/activitypub/fetch_remote_account_service_spec.rb123
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb75
-rw-r--r--spec/services/activitypub/process_account_service_spec.rb5
-rw-r--r--spec/services/activitypub/process_collection_service_spec.rb55
-rw-r--r--spec/services/authorize_follow_service_spec.rb24
-rw-r--r--spec/services/batched_remove_status_service_spec.rb14
-rw-r--r--spec/services/block_service_spec.rb19
-rw-r--r--spec/services/favourite_service_spec.rb22
-rw-r--r--spec/services/fetch_link_card_service_spec.rb6
-rw-r--r--spec/services/fetch_remote_resource_service_spec.rb4
-rw-r--r--spec/services/follow_service_spec.rb25
-rw-r--r--spec/services/post_status_service_spec.rb8
-rw-r--r--spec/services/process_feed_service_spec.rb5
-rw-r--r--spec/services/process_mentions_service_spec.rb46
-rw-r--r--spec/services/reblog_service_spec.rb49
-rw-r--r--spec/services/reject_follow_service_spec.rb24
-rw-r--r--spec/services/remove_status_service_spec.rb15
-rw-r--r--spec/services/resolve_remote_account_service_spec.rb66
-rw-r--r--spec/services/unblock_service_spec.rb22
-rw-r--r--spec/services/unfollow_service_spec.rb22
-rw-r--r--spec/spec_helper.rb17
-rw-r--r--spec/views/about/show.html.haml_spec.rb1
-rw-r--r--spec/workers/activitypub/delivery_worker_spec.rb23
-rw-r--r--spec/workers/activitypub/distribution_worker_spec.rb48
-rw-r--r--spec/workers/activitypub/processing_worker_spec.rb15
-rw-r--r--spec/workers/activitypub/update_distribution_worker_spec.rb20
-rw-r--r--spec/workers/pubsubhubbub/distribution_worker_spec.rb64
65 files changed, 2204 insertions, 192 deletions
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index d61c8c9bd..4e37b1b5f 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -10,6 +10,13 @@ RSpec.describe AccountsController, type: :controller do
     let!(:status2) { Status.create!(account: alice, text: 'Boop', thread: status1) }
     let!(:status3) { Status.create!(account: alice, text: 'Picture!') }
     let!(:status4) { Status.create!(account: alice, text: 'Mentioning @alice') }
+    let!(:status5) { Status.create!(account: alice, text: 'Kitsune') }
+    let!(:status6) { Status.create!(account: alice, text: 'Neko') }
+    let!(:status7) { Status.create!(account: alice, text: 'Tanuki') }
+
+    let!(:status_pin1) { StatusPin.create!(account: alice, status: status5, created_at: 5.days.ago) }
+    let!(:status_pin2) { StatusPin.create!(account: alice, status: status6, created_at: 2.years.ago) }
+    let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
 
     before do
       status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
@@ -48,6 +55,10 @@ RSpec.describe AccountsController, type: :controller do
       it 'returns http success with Activity Streams 2.0' do
         expect(response).to have_http_status(:success)
       end
+
+      it 'returns application/activity+json' do
+        expect(response.content_type).to eq 'application/activity+json'
+      end
     end
 
     context 'html' do
@@ -66,6 +77,14 @@ RSpec.describe AccountsController, type: :controller do
         expect(statuses[1]).to eq status2
       end
 
+      it 'assigns @pinned_statuses' do
+        pinned_statuses = assigns(:pinned_statuses).to_a
+        expect(pinned_statuses.size).to eq 3
+        expect(pinned_statuses[0]).to eq status7
+        expect(pinned_statuses[1]).to eq status5
+        expect(pinned_statuses[2]).to eq status6
+      end
+
       it 'returns http success' do
         expect(response).to have_http_status(:success)
       end
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
new file mode 100644
index 000000000..5c12fea7d
--- /dev/null
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -0,0 +1,7 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::InboxesController, type: :controller do
+  describe 'POST #create' do
+    pending
+  end
+end
diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
new file mode 100644
index 000000000..a25998021
--- /dev/null
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -0,0 +1,23 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::OutboxesController, type: :controller do
+  let!(:account) { Fabricate(:account) }
+
+  before do
+    Fabricate(:status, account: account)
+  end
+
+  describe 'GET #show' do
+    before do
+      get :show, params: { account_username: account.username }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'returns application/activity+json' do
+      expect(response.content_type).to eq 'application/activity+json'
+    end
+  end
+end
diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb
index 43631a7e5..7af4a6a5b 100644
--- a/spec/controllers/api/oembed_controller_spec.rb
+++ b/spec/controllers/api/oembed_controller_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Api::OEmbedController, type: :controller do
 
   describe 'GET #show' do
     before do
+      request.host = Rails.configuration.x.local_domain
       get :show, params: { url: account_stream_entry_url(alice, status.stream_entry) }, format: :json
     end
 
diff --git a/spec/controllers/api/subscriptions_controller_spec.rb b/spec/controllers/api/subscriptions_controller_spec.rb
index 76f9740ca..d90da9e32 100644
--- a/spec/controllers/api/subscriptions_controller_spec.rb
+++ b/spec/controllers/api/subscriptions_controller_spec.rb
@@ -38,19 +38,19 @@ RSpec.describe Api::SubscriptionsController, type: :controller do
     before do
       stub_request(:post, "https://quitter.no/main/push/hub").to_return(:status => 200, :body => "", :headers => {})
       stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
-      stub_request(:head, "https://quitter.no/notice/1269244").to_return(status: 404)
-      stub_request(:head, "https://quitter.no/notice/1265331").to_return(status: 404)
-      stub_request(:head, "https://community.highlandarrow.com/notice/54411").to_return(status: 404)
-      stub_request(:head, "https://community.highlandarrow.com/notice/53857").to_return(status: 404)
-      stub_request(:head, "https://community.highlandarrow.com/notice/51852").to_return(status: 404)
-      stub_request(:head, "https://social.umeahackerspace.se/notice/424348").to_return(status: 404)
-      stub_request(:head, "https://community.highlandarrow.com/notice/50467").to_return(status: 404)
-      stub_request(:head, "https://quitter.no/notice/1243309").to_return(status: 404)
-      stub_request(:head, "https://quitter.no/user/7477").to_return(status: 404)
-      stub_request(:head, "https://community.highlandarrow.com/user/1").to_return(status: 404)
-      stub_request(:head, "https://social.umeahackerspace.se/user/2").to_return(status: 404)
-      stub_request(:head, "https://gs.kawa-kun.com/user/2").to_return(status: 404)
-      stub_request(:head, "https://mastodon.social/users/Gargron").to_return(status: 404)
+      stub_request(:get, "https://quitter.no/notice/1269244").to_return(status: 404)
+      stub_request(:get, "https://quitter.no/notice/1265331").to_return(status: 404)
+      stub_request(:get, "https://community.highlandarrow.com/notice/54411").to_return(status: 404)
+      stub_request(:get, "https://community.highlandarrow.com/notice/53857").to_return(status: 404)
+      stub_request(:get, "https://community.highlandarrow.com/notice/51852").to_return(status: 404)
+      stub_request(:get, "https://social.umeahackerspace.se/notice/424348").to_return(status: 404)
+      stub_request(:get, "https://community.highlandarrow.com/notice/50467").to_return(status: 404)
+      stub_request(:get, "https://quitter.no/notice/1243309").to_return(status: 404)
+      stub_request(:get, "https://quitter.no/user/7477").to_return(status: 404)
+      stub_request(:any, "https://community.highlandarrow.com/user/1").to_return(status: 404)
+      stub_request(:any, "https://social.umeahackerspace.se/user/2").to_return(status: 404)
+      stub_request(:any, "https://gs.kawa-kun.com/user/2").to_return(status: 404)
+      stub_request(:any, "https://mastodon.social/users/Gargron").to_return(status: 404)
 
       request.env['HTTP_X_HUB_SIGNATURE'] = "sha1=#{OpenSSL::HMAC.hexdigest('sha1', 'abc', feed)}"
       request.env['RAW_POST_DATA'] = feed
diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
index 3f655c7b2..461b8b34b 100644
--- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
@@ -4,52 +4,79 @@ describe Api::V1::Accounts::CredentialsController do
   render_views
 
   let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
-  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read write') }
 
-  before do
-    allow(controller).to receive(:doorkeeper_token) { token }
-  end
-
-  describe 'GET #show' do
-    it 'returns http success' do
-      get :show
-      expect(response).to have_http_status(:success)
+  context 'with an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { token }
     end
-  end
-
-  describe 'PATCH #update' do
-    describe 'with valid data' do
-      before do
-        patch :update, params: {
-          display_name: "Alice Isn't Dead",
-          note: "Hi!\n\nToot toot!",
-          avatar: fixture_file_upload('files/avatar.gif', 'image/gif'),
-          header: fixture_file_upload('files/attachment.jpg', 'image/jpeg'),
-        }
-      end
 
+    describe 'GET #show' do
       it 'returns http success' do
+        get :show
         expect(response).to have_http_status(:success)
       end
+    end
+
+    describe 'PATCH #update' do
+      describe 'with valid data' do
+        before do
+          allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
+
+          patch :update, params: {
+            display_name: "Alice Isn't Dead",
+            note: "Hi!\n\nToot toot!",
+            avatar: fixture_file_upload('files/avatar.gif', 'image/gif'),
+            header: fixture_file_upload('files/attachment.jpg', 'image/jpeg'),
+          }
+        end
+
+        it 'returns http success' do
+          expect(response).to have_http_status(:success)
+        end
 
-      it 'updates account info' do
-        user.account.reload
+        it 'updates account info' do
+          user.account.reload
 
-        expect(user.account.display_name).to eq("Alice Isn't Dead")
-        expect(user.account.note).to eq("Hi!\n\nToot toot!")
-        expect(user.account.avatar).to exist
-        expect(user.account.header).to exist
+          expect(user.account.display_name).to eq("Alice Isn't Dead")
+          expect(user.account.note).to eq("Hi!\n\nToot toot!")
+          expect(user.account.avatar).to exist
+          expect(user.account.header).to exist
+        end
+
+        it 'queues up an account update distribution' do
+          expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id)
+        end
+      end
+
+      describe 'with invalid data' do
+        before do
+          patch :update, params: { note: 'This is too long. ' * 10 }
+        end
+
+        it 'returns http unprocessable entity' do
+          expect(response).to have_http_status(:unprocessable_entity)
+        end
       end
     end
+  end
+
+  context 'without an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { nil }
+    end
 
-    describe 'with invalid data' do
-      before do
-        # note length limit is 501, presently hardcoded, so give it 510 to fail
-        patch :update, params: { note: '1234567890' * 51 }
+    describe 'GET #show' do
+      it 'returns http unauthorized' do
+        get :show
+        expect(response).to have_http_status(:unauthorized)
       end
+    end
 
-      it 'returns http unprocessable entity' do
-        expect(response).to have_http_status(:unprocessable_entity)
+    describe 'PATCH #update' do
+      it 'returns http unauthorized' do
+        patch :update, params: { note: 'Foo' }
+        expect(response).to have_http_status(:unauthorized)
       end
     end
   end
diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
index 3a9607317..a9073b197 100644
--- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
@@ -50,14 +50,14 @@ describe Api::V1::Accounts::RelationshipsController do
         json = body_as_json
 
         expect(json).to be_a Enumerable
-        expect(json.first[:id]).to be simon.id
+        expect(json.first[:id]).to eq simon.id
         expect(json.first[:following]).to be true
         expect(json.first[:followed_by]).to be false
         expect(json.first[:muting]).to be false
         expect(json.first[:requested]).to be false
         expect(json.first[:domain_blocking]).to be false
 
-        expect(json.second[:id]).to be lewis.id
+        expect(json.second[:id]).to eq lewis.id
         expect(json.second[:following]).to be false
         expect(json.second[:followed_by]).to be true
         expect(json.second[:muting]).to be false
diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
index 8b4fd6a5b..c49a77ac3 100644
--- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
@@ -18,21 +18,37 @@ describe Api::V1::Accounts::StatusesController do
       expect(response).to have_http_status(:success)
       expect(response.headers['Link'].links.size).to eq(2)
     end
-  end
 
-  describe 'GET #index with only media' do
-    it 'returns http success' do
-      get :index, params: { account_id: user.account.id, only_media: true }
+    context 'with only media' do
+      it 'returns http success' do
+        get :index, params: { account_id: user.account.id, only_media: true }
 
-      expect(response).to have_http_status(:success)
+        expect(response).to have_http_status(:success)
+      end
     end
-  end
 
-  describe 'GET #index with exclude replies' do
-    it 'returns http success' do
-      get :index, params: { account_id: user.account.id, exclude_replies: true }
+    context 'with exclude replies' do
+      before do
+        Fabricate(:status, account: user.account, thread: Fabricate(:status))
+      end
 
-      expect(response).to have_http_status(:success)
+      it 'returns http success' do
+        get :index, params: { account_id: user.account.id, exclude_replies: true }
+
+        expect(response).to have_http_status(:success)
+      end
+    end
+
+    context 'with only pinned' do
+      before do
+        Fabricate(:status_pin, account: user.account, status: Fabricate(:status, account: user.account))
+      end
+
+      it 'returns http success' do
+        get :index, params: { account_id: user.account.id, pinned: true }
+
+        expect(response).to have_http_status(:success)
+      end
     end
   end
 end
diff --git a/spec/controllers/api/v1/favourites_controller_spec.rb b/spec/controllers/api/v1/favourites_controller_spec.rb
index 3de045377..46cf70f4d 100644
--- a/spec/controllers/api/v1/favourites_controller_spec.rb
+++ b/spec/controllers/api/v1/favourites_controller_spec.rb
@@ -70,8 +70,7 @@ RSpec.describe Api::V1::FavouritesController, type: :controller do
         it 'does not add pagination headers if not necessary' do
           get :index
 
-          expect(response.headers['Link'].find_link(['rel', 'next'])).to eq nil
-          expect(response.headers['Link'].find_link(['rel', 'prev'])).to eq nil
+          expect(response.headers['Link']).to eq nil
         end
       end
     end
diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb
new file mode 100644
index 000000000..2e170da24
--- /dev/null
+++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Statuses::PinsController do
+  render_views
+
+  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write', application: app) }
+
+  context 'with an oauth token' do
+    before do
+      allow(controller).to receive(:doorkeeper_token) { token }
+    end
+
+    describe 'POST #create' do
+      let(:status) { Fabricate(:status, account: user.account) }
+
+      before do
+        post :create, params: { status_id: status.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'updates the pinned attribute' do
+        expect(user.account.pinned?(status)).to be true
+      end
+
+      it 'return json with updated attributes' do
+        hash_body = body_as_json
+
+        expect(hash_body[:id]).to eq status.id
+        expect(hash_body[:pinned]).to be true
+      end
+    end
+
+    describe 'POST #destroy' do
+      let(:status) { Fabricate(:status, account: user.account) }
+
+      before do
+        Fabricate(:status_pin, status: status, account: user.account)
+        post :destroy, params: { status_id: status.id }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'updates the pinned attribute' do
+        expect(user.account.pinned?(status)).to be false
+      end
+    end
+  end
+end
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index bdc181edc..ae46f9ba6 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -33,7 +33,7 @@ describe ApplicationController, type: :controller do
     it 'sets link headers' do
       account = Fabricate(:account, username: 'username')
       get 'success', params: { account_username: 'username' }
-      expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/xrd+xml", <http://test.host/users/username.atom>; rel="alternate"; type="application/atom+xml"'
+      expect(response.headers['Link'].to_s).to eq '<http://test.host/.well-known/webfinger?resource=acct%3Ausername%40cb6e6126.ngrok.io>; rel="lrdd"; type="application/xrd+xml", <http://test.host/users/username.atom>; rel="alternate"; type="application/atom+xml", <https://cb6e6126.ngrok.io/users/username>; rel="alternate"; type="application/activity+json"'
     end
 
     it 'returns http success' do
diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb
index b371795ab..64648621e 100644
--- a/spec/controllers/concerns/signature_verification_spec.rb
+++ b/spec/controllers/concerns/signature_verification_spec.rb
@@ -16,7 +16,7 @@ describe ApplicationController, type: :controller do
   end
 
   before do
-    routes.draw { get 'success' => 'anonymous#success' }
+    routes.draw { match via: [:get, :post], 'success' => 'anonymous#success' }
   end
 
   context 'without signature header' do
@@ -40,34 +40,74 @@ describe ApplicationController, type: :controller do
   context 'with signature header' do
     let!(:author) { Fabricate(:account) }
 
-    before do
-      get :success
+    context 'without body' do
+      before do
+        get :success
 
-      fake_request = Request.new(:get, request.url)
-      fake_request.on_behalf_of(author)
+        fake_request = Request.new(:get, request.url)
+        fake_request.on_behalf_of(author)
 
-      request.headers.merge!(fake_request.headers)
-    end
+        request.headers.merge!(fake_request.headers)
+      end
 
-    describe '#signed_request?' do
-      it 'returns true' do
-        expect(controller.signed_request?).to be true
+      describe '#signed_request?' do
+        it 'returns true' do
+          expect(controller.signed_request?).to be true
+        end
+      end
+
+      describe '#signed_request_account' do
+        it 'returns an account' do
+          expect(controller.signed_request_account).to eq author
+        end
+
+        it 'returns nil when path does not match' do
+          request.path = '/alternative-path'
+          expect(controller.signed_request_account).to be_nil
+        end
+
+        it 'returns nil when method does not match' do
+          post :success
+          expect(controller.signed_request_account).to be_nil
+        end
       end
     end
 
-    describe '#signed_request_account' do
-      it 'returns an account' do
-        expect(controller.signed_request_account).to eq author
+    context 'with body' do
+      before do
+        post :success, body: 'Hello world'
+
+        fake_request = Request.new(:post, request.url, body: 'Hello world')
+        fake_request.on_behalf_of(author)
+
+        request.headers.merge!(fake_request.headers)
       end
 
-      it 'returns nil when path does not match' do
-        request.path = '/alternative-path'
-        expect(controller.signed_request_account).to be_nil
+      describe '#signed_request?' do
+        it 'returns true' do
+          expect(controller.signed_request?).to be true
+        end
       end
 
-      it 'returns nil when method does not match' do
-        post :success
-        expect(controller.signed_request_account).to be_nil
+      describe '#signed_request_account' do
+        it 'returns an account' do
+          expect(controller.signed_request_account).to eq author
+        end
+
+        it 'returns nil when path does not match' do
+          request.path = '/alternative-path'
+          expect(controller.signed_request_account).to be_nil
+        end
+
+        it 'returns nil when method does not match' do
+          get :success
+          expect(controller.signed_request_account).to be_nil
+        end
+
+        it 'returns nil when body has been tampered' do
+          request.headers['RAW_POST_DATA'] = 'doo doo doo'
+          expect(controller.signed_request_account).to be_nil
+        end
       end
     end
   end
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
index 915c86f8e..86b1eb8d0 100644
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ b/spec/controllers/remote_follow_controller_spec.rb
@@ -87,6 +87,14 @@ describe RemoteFollowController do
         expect(response).to render_template(:new)
         expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
       end
+
+      it 'renders new when occur HTTP::ConnectionError' do
+        allow(Goldfinger).to receive(:finger).with('acct:user@unknown').and_raise(HTTP::ConnectionError)
+        post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@unknown' } }
+
+        expect(response).to render_template(:new)
+        expect(response.body).to include(I18n.t('remote_follow.missing_resource'))
+      end
     end
   end
 
diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb
new file mode 100644
index 000000000..ca66f8d23
--- /dev/null
+++ b/spec/controllers/settings/applications_controller_spec.rb
@@ -0,0 +1,188 @@
+require 'rails_helper'
+
+describe Settings::ApplicationsController do
+  render_views
+  
+  let!(:user) { Fabricate(:user) }
+  let!(:app) { Fabricate(:application, owner: user) }
+  
+  before do
+    sign_in user, scope: :user
+  end
+
+  describe 'GET #index' do
+    let!(:other_app) { Fabricate(:application) }
+
+    it 'shows apps' do
+      get :index
+      expect(response).to have_http_status(:success)
+      expect(assigns(:applications)).to include(app)
+      expect(assigns(:applications)).to_not include(other_app)
+    end
+  end
+
+  
+  describe 'GET #show' do
+    it 'returns http success' do
+      get :show, params: { id: app.id }
+      expect(response).to have_http_status(:success)
+      expect(assigns[:application]).to eql(app)
+    end
+
+    it 'returns 404 if you dont own app' do
+      app.update!(owner: nil)
+
+      get :show, params: { id: app.id }
+      expect(response.status).to eq 404
+    end
+  end
+
+  describe 'GET #new' do
+    it 'works' do
+      get :new
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'POST #create' do
+    context 'success (passed scopes as a String)' do
+      def call_create
+        post :create, params: {
+               doorkeeper_application: {
+                 name: 'My New App',
+                 redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
+                 website: 'http://google.com',
+                 scopes: 'read write follow'
+               }
+             }
+        response
+      end
+
+      it 'creates an entry in the database' do
+        expect { call_create }.to change(Doorkeeper::Application, :count)
+      end
+
+      it 'redirects back to applications page' do
+        expect(call_create).to redirect_to(settings_applications_path)
+      end
+    end
+
+    context 'success (passed scopes as an Array)' do
+      def call_create
+        post :create, params: {
+               doorkeeper_application: {
+                 name: 'My New App',
+                 redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
+                 website: 'http://google.com',
+                 scopes: [ 'read', 'write', 'follow' ]
+               }
+             }
+        response
+      end
+
+      it 'creates an entry in the database' do
+        expect { call_create }.to change(Doorkeeper::Application, :count)
+      end
+
+      it 'redirects back to applications page' do
+        expect(call_create).to redirect_to(settings_applications_path)
+      end
+    end
+
+    context 'failure' do
+      before do
+        post :create, params: {
+               doorkeeper_application: {
+                 name: '',
+                 redirect_uri: '',
+                 website: '',
+                 scopes: []
+               }
+             }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'renders form again' do
+        expect(response).to render_template(:new)
+      end
+    end
+  end
+  
+  describe 'PATCH #update' do
+    context 'success' do
+      let(:opts) {
+        {
+          website: 'https://foo.bar/'
+        }
+      }
+
+      def call_update
+        patch :update, params: {
+                id: app.id,
+                doorkeeper_application: opts
+              }
+        response
+      end
+
+      it 'updates existing application' do
+        call_update
+        expect(app.reload.website).to eql(opts[:website])
+      end
+      
+      it 'redirects back to applications page' do
+        expect(call_update).to redirect_to(settings_applications_path)
+      end
+    end
+
+    context 'failure' do
+      before do
+        patch :update, params: {
+                id: app.id,
+                doorkeeper_application: {
+                  name: '',
+                  redirect_uri: '',
+                  website: '',
+                  scopes: []
+                }
+              }
+      end
+
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
+      end
+
+      it 'renders form again' do
+        expect(response).to render_template(:show)
+      end
+    end
+  end
+
+  describe 'destroy' do
+    before do
+      post :destroy, params: { id: app.id }
+    end
+
+    it 'redirects back to applications page' do
+      expect(response).to redirect_to(settings_applications_path)
+    end
+
+    it 'removes the app' do
+      expect(Doorkeeper::Application.find_by(id: app.id)).to be_nil
+    end
+  end
+
+  describe 'regenerate' do
+    let(:token) { user.token_for_app(app) }
+    before do
+      expect(token).to_not be_nil
+      post :regenerate, params: { id: app.id }
+    end
+
+    it 'should create new token' do
+      expect(user.token_for_app(app)).to_not eql(token)
+    end
+  end
+end
diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb
index e502dbda7..ee3315be6 100644
--- a/spec/controllers/settings/profiles_controller_spec.rb
+++ b/spec/controllers/settings/profiles_controller_spec.rb
@@ -17,11 +17,13 @@ RSpec.describe Settings::ProfilesController, type: :controller do
 
   describe 'PUT #update' do
     it 'updates the user profile' do
+      allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
       account = Fabricate(:account, user: @user, display_name: 'Old name')
 
       put :update, params: { account: { display_name: 'New name' } }
       expect(account.reload.display_name).to eq 'New name'
       expect(response).to redirect_to(settings_profile_path)
+      expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
     end
   end
 end
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index 88d365624..95fb4d594 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -30,6 +30,18 @@ describe StatusesController do
       end
     end
 
+    context 'status is a reblog' do
+      it 'redirects to the original status' do
+        original_account = Fabricate(:account, domain: 'example.com')
+        original_status = Fabricate(:status, account: original_account, uri: 'tag:example.com,2017:foo', url: 'https://example.com/123')
+        status = Fabricate(:status, reblog: original_status)
+
+        get :show, params: { account_username: status.account.username, id: status.id }
+
+        expect(response).to redirect_to(original_status.url)
+      end
+    end
+
     context 'account is not suspended and status is permitted' do
       it 'assigns @account' do
         status = Fabricate(:status)
diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb
index 2cc428e0c..f81e2be7b 100644
--- a/spec/controllers/stream_entries_controller_spec.rb
+++ b/spec/controllers/stream_entries_controller_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe StreamEntriesController, type: :controller do
 
         get route, params: { account_username: alice.username, id: status.stream_entry.id }
 
-        expect(response.headers['Link'].to_s).to eq "<http://test.host/users/alice/updates/#{status.stream_entry.id}.atom>; rel=\"alternate\"; type=\"application/atom+xml\""
+        expect(response.headers['Link'].to_s).to eq "<http://test.host/users/alice/updates/#{status.stream_entry.id}.atom>; rel=\"alternate\"; type=\"application/atom+xml\", <https://cb6e6126.ngrok.io/users/alice/statuses/#{status.id}>; rel=\"alternate\"; type=\"application/activity+json\""
       end
     end
 
@@ -88,14 +88,12 @@ RSpec.describe StreamEntriesController, type: :controller do
   describe 'GET #embed' do
     include_examples 'before_action', :embed
 
-    it 'returns embedded view of status' do
+    it 'redirects to new embed page' do
       status = Fabricate(:status)
 
       get :embed, params: { account_username: status.account.username, id: status.stream_entry.id }
 
-      expect(response).to have_http_status(:success)
-      expect(response.headers['X-Frame-Options']).to eq 'ALLOWALL'
-      expect(response).to render_template(layout: 'embedded')
+      expect(response).to redirect_to(embed_short_account_status_url(status.account, status))
     end
   end
 end
diff --git a/spec/fabricators/status_pin_fabricator.rb b/spec/fabricators/status_pin_fabricator.rb
new file mode 100644
index 000000000..6a9006c9f
--- /dev/null
+++ b/spec/fabricators/status_pin_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:status_pin) do
+  account
+  status
+end
diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb
new file mode 100644
index 000000000..7d3912e6c
--- /dev/null
+++ b/spec/helpers/jsonld_helper_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe JsonLdHelper do
+  describe '#equals_or_includes?' do
+    it 'returns true when value equals' do
+      expect(helper.equals_or_includes?('foo', 'foo')).to be true
+    end
+
+    it 'returns false when value does not equal' do
+      expect(helper.equals_or_includes?('foo', 'bar')).to be false
+    end
+
+    it 'returns true when value is included' do
+      expect(helper.equals_or_includes?(%w(foo baz), 'foo')).to be true
+    end
+
+    it 'returns false when value is not included' do
+      expect(helper.equals_or_includes?(%w(foo baz), 'bar')).to be false
+    end
+  end
+
+  describe '#first_of_value' do
+    pending
+  end
+
+  describe '#supported_context?' do
+    pending
+  end
+
+  describe '#fetch_resource' do
+    pending
+  end
+end
diff --git a/spec/helpers/routing_helper_spec.rb b/spec/helpers/routing_helper_spec.rb
new file mode 100644
index 000000000..940392c9b
--- /dev/null
+++ b/spec/helpers/routing_helper_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe RoutingHelper, type: :helper do
+  describe '.full_asset_url' do
+    around do |example|
+      use_s3 = Rails.configuration.x.use_s3
+      example.run
+      Rails.configuration.x.use_s3 = use_s3
+    end
+
+    shared_examples 'returns full path URL' do
+      it 'with host' do
+        url = helper.full_asset_url('https://example.com/avatars/000/000/002/original/icon.png')
+
+        expect(url).to eq 'https://example.com/avatars/000/000/002/original/icon.png'
+      end
+
+      it 'without host' do
+        url = helper.full_asset_url('/avatars/original/missing.png', skip_pipeline: true)
+
+        expect(url).to eq 'http://test.host/avatars/original/missing.png'
+      end
+    end
+
+    context 'Do not use S3' do
+      before do
+        Rails.configuration.x.use_s3 = false
+      end
+
+      it_behaves_like 'returns full path URL'
+    end
+
+    context 'Use S3' do
+      before do
+        Rails.configuration.x.use_s3 = true
+      end
+
+      it_behaves_like 'returns full path URL'
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb
new file mode 100644
index 000000000..6503c83e3
--- /dev/null
+++ b/spec/lib/activitypub/activity/accept_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Accept do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Accept',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: {
+        id: 'bar',
+        type: 'Follow',
+        actor: ActivityPub::TagManager.instance.uri_for(recipient),
+        object: ActivityPub::TagManager.instance.uri_for(sender),
+      },
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      Fabricate(:follow_request, account: recipient, target_account: sender)
+      subject.perform
+    end
+
+    it 'creates a follow relationship' do
+      expect(recipient.following?(sender)).to be true
+    end
+
+    it 'removes the follow request' do
+      expect(recipient.requested?(sender)).to be false
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb
new file mode 100644
index 000000000..54dd52a60
--- /dev/null
+++ b/spec/lib/activitypub/activity/announce_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Announce do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+  let(:status)    { Fabricate(:status, account: recipient) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Announce',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(status),
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a reblog by sender of status' do
+      expect(sender.reblogged?(status)).to be true
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/block_spec.rb b/spec/lib/activitypub/activity/block_spec.rb
new file mode 100644
index 000000000..23c8cc31c
--- /dev/null
+++ b/spec/lib/activitypub/activity/block_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Block do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Block',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(recipient),
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a block from sender to recipient' do
+      expect(sender.blocking?(recipient)).to be true
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
new file mode 100644
index 000000000..fcb044ebc
--- /dev/null
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -0,0 +1,221 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Create do
+  let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Create',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: object_json,
+    }.with_indifferent_access
+  end
+
+  subject { described_class.new(json, sender) }
+
+  before do
+    stub_request(:get, 'http://example.com/attachment.png').to_return(request_fixture('avatar.txt'))
+  end
+
+  describe '#perform' do
+    before do
+      subject.perform
+    end
+
+    context 'standalone' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.text).to eq 'Lorem ipsum'
+      end
+
+      it 'missing to/cc defaults to direct privacy' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.visibility).to eq 'direct'
+      end
+    end
+
+    context 'public' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          to: 'https://www.w3.org/ns/activitystreams#Public',
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.visibility).to eq 'public'
+      end
+    end
+
+    context 'unlisted' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          cc: 'https://www.w3.org/ns/activitystreams#Public',
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.visibility).to eq 'unlisted'
+      end
+    end
+
+    context 'private' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          to: 'http://example.com/followers',
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.visibility).to eq 'private'
+      end
+    end
+
+    context 'direct' do
+      let(:recipient) { Fabricate(:account) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          to: ActivityPub::TagManager.instance.uri_for(recipient),
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.visibility).to eq 'direct'
+      end
+    end
+
+    context 'as a reply' do
+      let(:original_status) { Fabricate(:status) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status),
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.thread).to eq original_status
+        expect(status.reply?).to be true
+        expect(status.in_reply_to_account).to eq original_status.account
+        expect(status.conversation).to eq original_status.conversation
+      end
+    end
+
+    context 'with mentions' do
+      let(:recipient) { Fabricate(:account) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          tag: [
+            {
+              type: 'Mention',
+              href: ActivityPub::TagManager.instance.uri_for(recipient),
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.mentions.map(&:account)).to include(recipient)
+      end
+    end
+
+    context 'with media attachments' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          attachment: [
+            {
+              type: 'Document',
+              mime_type: 'image/png',
+              url: 'http://example.com/attachment.png',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.media_attachments.map(&:remote_url)).to include('http://example.com/attachment.png')
+      end
+    end
+
+    context 'with hashtags' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          tag: [
+            {
+              type: 'Hashtag',
+              href: 'http://example.com/blah',
+              name: '#test',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.tags.map(&:name)).to include('test')
+      end
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb
new file mode 100644
index 000000000..65e743abb
--- /dev/null
+++ b/spec/lib/activitypub/activity/delete_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Delete do
+  let(:sender)    { Fabricate(:account) }
+  let(:status)    { Fabricate(:status, account: sender, uri: 'foobar') }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Delete',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(status),
+      signature: 'foo',
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'deletes sender\'s status' do
+      expect(Status.find_by(id: status.id)).to be_nil
+    end
+  end
+
+  context 'when the status has been reblogged' do
+    describe '#perform' do
+      subject { described_class.new(json, sender) }
+      let(:reblogger) { Fabricate(:account) }
+      let(:follower)   { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+
+      before do
+        stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+        follower.follow!(reblogger)
+        Fabricate(:status, account: reblogger, reblog: status)
+        subject.perform
+      end
+
+      it 'deletes sender\'s status' do
+        expect(Status.find_by(id: status.id)).to be_nil
+      end
+
+      it 'sends delete activity to followers of rebloggers' do
+        # one for Delete original post, and one for Undo reblog (normal delivery)
+        expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
+      end
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb
new file mode 100644
index 000000000..6bbacdbe6
--- /dev/null
+++ b/spec/lib/activitypub/activity/follow_spec.rb
@@ -0,0 +1,49 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Follow do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Follow',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(recipient),
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    context 'unlocked account' do
+      before do
+        subject.perform
+      end
+
+      it 'creates a follow from sender to recipient' do
+        expect(sender.following?(recipient)).to be true
+      end
+
+      it 'does not create a follow request' do
+        expect(sender.requested?(recipient)).to be false
+      end
+    end
+
+    context 'locked account' do
+      before do
+        recipient.update(locked: true)
+        subject.perform
+      end
+
+      it 'does not create a follow from sender to recipient' do
+        expect(sender.following?(recipient)).to be false
+      end
+
+      it 'creates a follow request' do
+        expect(sender.requested?(recipient)).to be true
+      end
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb
new file mode 100644
index 000000000..b69615a9d
--- /dev/null
+++ b/spec/lib/activitypub/activity/like_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Like do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+  let(:status)    { Fabricate(:status, account: recipient) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Like',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: ActivityPub::TagManager.instance.uri_for(status),
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'creates a favourite from sender to status' do
+      expect(sender.favourited?(status)).to be true
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb
new file mode 100644
index 000000000..7fd95bcc6
--- /dev/null
+++ b/spec/lib/activitypub/activity/reject_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Reject do
+  let(:sender)    { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Reject',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: {
+        id: 'bar',
+        type: 'Follow',
+        actor: ActivityPub::TagManager.instance.uri_for(recipient),
+        object: ActivityPub::TagManager.instance.uri_for(sender),
+      },
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      Fabricate(:follow_request, account: recipient, target_account: sender)
+      subject.perform
+    end
+
+    it 'does not create a follow relationship' do
+      expect(recipient.following?(sender)).to be false
+    end
+
+    it 'removes the follow request' do
+      expect(recipient.requested?(sender)).to be false
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/undo_spec.rb b/spec/lib/activitypub/activity/undo_spec.rb
new file mode 100644
index 000000000..4629a033f
--- /dev/null
+++ b/spec/lib/activitypub/activity/undo_spec.rb
@@ -0,0 +1,107 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Undo do
+  let(:sender) { Fabricate(:account) }
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Undo',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: object_json,
+    }.with_indifferent_access
+  end
+
+  subject { described_class.new(json, sender) }
+
+  describe '#perform' do
+    context 'with Announce' do
+      let(:status) { Fabricate(:status) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Announce',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: ActivityPub::TagManager.instance.uri_for(status),
+        }
+      end
+
+      before do
+        Fabricate(:status, reblog: status, account: sender, uri: 'bar')
+      end
+
+      it 'deletes the reblog' do
+        subject.perform
+        expect(sender.reblogged?(status)).to be false
+      end
+    end
+
+    context 'with Block' do
+      let(:recipient) { Fabricate(:account) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Block',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: ActivityPub::TagManager.instance.uri_for(recipient),
+        }
+      end
+
+      before do
+        sender.block!(recipient)
+      end
+
+      it 'deletes block from sender to recipient' do
+        subject.perform
+        expect(sender.blocking?(recipient)).to be false
+      end
+    end
+
+    context 'with Follow' do
+      let(:recipient) { Fabricate(:account) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Follow',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: ActivityPub::TagManager.instance.uri_for(recipient),
+        }
+      end
+
+      before do
+        sender.follow!(recipient)
+      end
+
+      it 'deletes follow from sender to recipient' do
+        subject.perform
+        expect(sender.following?(recipient)).to be false
+      end
+    end
+
+    context 'with Like' do
+      let(:status) { Fabricate(:status) }
+
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Like',
+          actor: ActivityPub::TagManager.instance.uri_for(sender),
+          object: ActivityPub::TagManager.instance.uri_for(status),
+        }
+      end
+
+      before do
+        Fabricate(:favourite, account: sender, status: status)
+      end
+
+      it 'deletes favourite from sender to status' do
+        subject.perform
+        expect(sender.favourited?(status)).to be false
+      end
+    end
+  end
+end
diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb
new file mode 100644
index 000000000..0bd6d00d9
--- /dev/null
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -0,0 +1,41 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Update do
+  let!(:sender) { Fabricate(:account) }
+  
+  before do
+    sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender))
+  end
+
+  let(:modified_sender) do 
+    sender.dup.tap do |modified_sender|
+      modified_sender.display_name = 'Totally modified now'
+    end
+  end
+
+  let(:actor_json) do
+    ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, key_transform: :camel_lower).as_json
+  end
+
+  let(:json) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Update',
+      actor: ActivityPub::TagManager.instance.uri_for(sender),
+      object: actor_json,
+    }.with_indifferent_access
+  end
+
+  describe '#perform' do
+    subject { described_class.new(json, sender) }
+
+    before do
+      subject.perform
+    end
+
+    it 'updates profile' do
+      expect(sender.reload.display_name).to eq 'Totally modified now'
+    end
+  end
+end
diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb
new file mode 100644
index 000000000..a4d6fe8c3
--- /dev/null
+++ b/spec/lib/activitypub/linked_data_signature_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::LinkedDataSignature do
+  include JsonLdHelper
+
+  let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice') }
+
+  let(:raw_json) do
+    {
+      '@context' => 'https://www.w3.org/ns/activitystreams',
+      'id' => 'http://example.com/hello-world',
+    }
+  end
+
+  let(:json) { raw_json.merge('signature' => signature) }
+
+  subject { described_class.new(json) }
+
+  describe '#verify_account!' do
+    context 'when signature matches' do
+      let(:raw_signature) do
+        {
+          'creator' => 'http://example.com/alice',
+          'created' => '2017-09-23T20:21:34Z',
+        }
+      end
+
+      let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
+
+      it 'returns creator' do
+        expect(subject.verify_account!).to eq sender
+      end
+    end
+
+    context 'when signature is missing' do
+      let(:signature) { nil }
+
+      it 'returns nil' do
+        expect(subject.verify_account!).to be_nil
+      end
+    end
+
+    context 'when signature is tampered' do
+      let(:raw_signature) do
+        {
+          'creator' => 'http://example.com/alice',
+          'created' => '2017-09-23T20:21:34Z',
+        }
+      end
+
+      let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => 's69F3mfddd99dGjmvjdjjs81e12jn121Gkm1') }
+
+      it 'returns nil' do
+        expect(subject.verify_account!).to be_nil
+      end
+    end
+  end
+
+  describe '#sign!' do
+    subject { described_class.new(raw_json).sign!(sender) }
+
+    it 'returns a hash' do
+      expect(subject).to be_a Hash
+    end
+
+    it 'contains signature' do
+      expect(subject['signature']).to be_a Hash
+      expect(subject['signature']['signatureValue']).to be_present
+    end
+
+    it 'can be verified again' do
+      expect(described_class.new(subject).verify_account!).to eq sender
+    end
+  end
+
+  def sign(from_account, options, document)
+    options_hash   = Digest::SHA256.hexdigest(canonicalize(options.merge('@context' => ActivityPub::LinkedDataSignature::CONTEXT)))
+    document_hash  = Digest::SHA256.hexdigest(canonicalize(document))
+    to_be_verified = options_hash + document_hash
+    Base64.strict_encode64(from_account.keypair.sign(OpenSSL::Digest::SHA256.new, to_be_verified))
+  end
+end
diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb
new file mode 100644
index 000000000..8f7662e24
--- /dev/null
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -0,0 +1,99 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::TagManager do
+  include RoutingHelper
+
+  subject { described_class.instance }
+
+  describe '#url_for' do
+    it 'returns a string' do
+      account = Fabricate(:account)
+      expect(subject.url_for(account)).to be_a String
+    end
+  end
+
+  describe '#uri_for' do
+    it 'returns a string' do
+      account = Fabricate(:account)
+      expect(subject.uri_for(account)).to be_a String
+    end
+  end
+
+  describe '#to' do
+    it 'returns public collection for public status' do
+      status = Fabricate(:status, visibility: :public)
+      expect(subject.to(status)).to eq ['https://www.w3.org/ns/activitystreams#Public']
+    end
+
+    it 'returns followers collection for unlisted status' do
+      status = Fabricate(:status, visibility: :unlisted)
+      expect(subject.to(status)).to eq [account_followers_url(status.account)]
+    end
+
+    it 'returns followers collection for private status' do
+      status = Fabricate(:status, visibility: :private)
+      expect(subject.to(status)).to eq [account_followers_url(status.account)]
+    end
+
+    it 'returns URIs of mentions for direct status' do
+      status    = Fabricate(:status, visibility: :direct)
+      mentioned = Fabricate(:account)
+      status.mentions.create(account: mentioned)
+      expect(subject.to(status)).to eq [subject.uri_for(mentioned)]
+    end
+  end
+
+  describe '#cc' do
+    it 'returns followers collection for public status' do
+      status = Fabricate(:status, visibility: :public)
+      expect(subject.cc(status)).to eq [account_followers_url(status.account)]
+    end
+
+    it 'returns public collection for unlisted status' do
+      status = Fabricate(:status, visibility: :unlisted)
+      expect(subject.cc(status)).to eq ['https://www.w3.org/ns/activitystreams#Public']
+    end
+
+    it 'returns empty array for private status' do
+      status = Fabricate(:status, visibility: :private)
+      expect(subject.cc(status)).to eq []
+    end
+
+    it 'returns empty array for direct status' do
+      status = Fabricate(:status, visibility: :direct)
+      expect(subject.cc(status)).to eq []
+    end
+
+    it 'returns URIs of mentions for non-direct status' do
+      status    = Fabricate(:status, visibility: :public)
+      mentioned = Fabricate(:account)
+      status.mentions.create(account: mentioned)
+      expect(subject.cc(status)).to include(subject.uri_for(mentioned))
+    end
+  end
+
+  describe '#local_uri?' do
+    it 'returns false for non-local URI' do
+      expect(subject.local_uri?('http://example.com/123')).to be false
+    end
+
+    it 'returns true for local URIs' do
+      account = Fabricate(:account)
+      expect(subject.local_uri?(subject.uri_for(account))).to be true
+    end
+  end
+
+  describe '#uri_to_local_id' do
+    it 'returns the local ID' do
+      account = Fabricate(:account)
+      expect(subject.uri_to_local_id(subject.uri_for(account), :username)).to eq account.username
+    end
+  end
+
+  describe '#uri_to_resource' do
+    it 'returns the local resource' do
+      account = Fabricate(:account)
+      expect(subject.uri_to_resource(subject.uri_for(account), Account)).to eq account
+    end
+  end
+end
diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb
index b0cb8f019..301a0ce30 100644
--- a/spec/lib/ostatus/atom_serializer_spec.rb
+++ b/spec/lib/ostatus/atom_serializer_spec.rb
@@ -196,7 +196,7 @@ RSpec.describe OStatus::AtomSerializer do
 
       author = OStatus::AtomSerializer.new.author(account)
 
-      link = author.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' }
+      link = author.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
       expect(link[:type]).to eq 'text/html'
       expect(link[:rel]).to eq 'alternate'
       expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/@username'
@@ -407,6 +407,7 @@ RSpec.describe OStatus::AtomSerializer do
         remote_status.stream_entry.update!(created_at: '2000-01-01T00:00:00Z')
 
         entry = OStatus::AtomSerializer.new.entry(remote_status.stream_entry, true)
+        entry.nodes.delete_if { |node| node[:type] == 'application/activity+json' } # Remove ActivityPub link to simplify test
         xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote')
 
         remote_status.destroy!
@@ -415,7 +416,7 @@ RSpec.describe OStatus::AtomSerializer do
         account = Account.create!(
           domain: 'remote',
           username: 'username',
-          last_webfingered_at: Time.now.utc,
+          last_webfingered_at: Time.now.utc
         )
 
         ProcessFeedService.new.call(xml, account)
@@ -529,7 +530,7 @@ RSpec.describe OStatus::AtomSerializer do
 
       entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
 
-      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' }
+      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
       expect(link[:type]).to eq 'text/html'
       expect(link[:href]).to eq "https://cb6e6126.ngrok.io/users/username/updates/#{status.stream_entry.id}"
     end
@@ -642,7 +643,7 @@ RSpec.describe OStatus::AtomSerializer do
 
       feed = OStatus::AtomSerializer.new.feed(account, [])
 
-      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' }
+      link = feed.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
       expect(link[:type]).to eq 'text/html'
       expect(link[:href]).to eq 'https://cb6e6126.ngrok.io/@username'
     end
@@ -1509,7 +1510,7 @@ RSpec.describe OStatus::AtomSerializer do
 
       entry = OStatus::AtomSerializer.new.object(status)
 
-      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' }
+      link = entry.nodes.find { |node| node.name == 'link' && node[:rel] == 'alternate' && node[:type] == 'text/html' }
       expect(link[:type]).to eq 'text/html'
       expect(link[:href]).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
     end
diff --git a/spec/lib/stream_entry_finder_spec.rb b/spec/lib/status_finder_spec.rb
index 64e03c36a..3ef086736 100644
--- a/spec/lib/stream_entry_finder_spec.rb
+++ b/spec/lib/status_finder_spec.rb
@@ -2,17 +2,17 @@
 
 require 'rails_helper'
 
-describe StreamEntryFinder do
+describe StatusFinder do
   include RoutingHelper
 
-  describe '#stream_entry' do
+  describe '#status' do
     context 'with a status url' do
       let(:status) { Fabricate(:status) }
       let(:url) { short_account_status_url(account_username: status.account.username, id: status.id) }
       subject { described_class.new(url) }
 
       it 'finds the stream entry' do
-        expect(subject.stream_entry).to eq(status.stream_entry)
+        expect(subject.status).to eq(status)
       end
 
       it 'raises an error if action is not :show' do
@@ -20,7 +20,7 @@ describe StreamEntryFinder do
         expect(recognized).to receive(:[]).with(:action).and_return(:create)
         expect(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized)
 
-        expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound)
+        expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound)
       end
     end
 
@@ -30,7 +30,17 @@ describe StreamEntryFinder do
       subject { described_class.new(url) }
 
       it 'finds the stream entry' do
-        expect(subject.stream_entry).to eq(stream_entry)
+        expect(subject.status).to eq(stream_entry.status)
+      end
+    end
+
+    context 'with a remote url even if id exists on local' do
+      let(:status) { Fabricate(:status) }
+      let(:url) { "https://example.com/users/test/statuses/#{status.id}" }
+      subject { described_class.new(url) }
+
+      it 'raises an error' do
+        expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound)
       end
     end
 
@@ -39,7 +49,7 @@ describe StreamEntryFinder do
       subject { described_class.new(url) }
 
       it 'raises an error' do
-        expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound)
+        expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound)
       end
     end
 
@@ -48,7 +58,7 @@ describe StreamEntryFinder do
       subject { described_class.new(url) }
 
       it 'raises an error' do
-        expect { subject.stream_entry }.to raise_error(ActiveRecord::RecordNotFound)
+        expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound)
       end
     end
   end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 17e2d8499..361577eff 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -642,7 +642,6 @@ RSpec.describe Account, type: :model do
       it 'returns remote accounts with followers whose subscription expiration date is past or not given' do
         local = Fabricate(:account, domain: nil)
         matches = [
-          { domain: 'remote', subscription_expires_at: nil },
           { domain: 'remote', subscription_expires_at: '2000-01-01T00:00:00Z' },
         ].map(&method(:Fabricate).curry(2).call(:account))
         matches.each(&local.method(:follow!))
diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb
index fa52077cd..321761166 100644
--- a/spec/models/import_spec.rb
+++ b/spec/models/import_spec.rb
@@ -1,5 +1,24 @@
 require 'rails_helper'
 
 RSpec.describe Import, type: :model do
+  let (:account) { Fabricate(:account) }
+  let (:type) { 'following' }
+  let (:data) { attachment_fixture('imports.txt') }
 
+  describe 'validations' do
+    it 'has a valid parameters' do
+      import = Import.create(account: account, type: type, data: data)
+      expect(import).to be_valid
+    end
+
+    it 'is invalid without an type' do
+      import = Import.create(account: account, data: data)
+      expect(import).to model_have_error_on_field(:type)
+    end
+
+    it 'is invalid without a data' do
+      import = Import.create(account: account, type: type)
+      expect(import).to model_have_error_on_field(:data)
+    end
+  end
 end
diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb
new file mode 100644
index 000000000..6f54f80f9
--- /dev/null
+++ b/spec/models/status_pin_spec.rb
@@ -0,0 +1,41 @@
+require 'rails_helper'
+
+RSpec.describe StatusPin, type: :model do
+  describe 'validations' do
+    it 'allows pins of own statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account)
+
+      expect(StatusPin.new(account: account, status: status).save).to be true
+    end
+
+    it 'does not allow pins of statuses by someone else' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status)
+
+      expect(StatusPin.new(account: account, status: status).save).to be false
+    end
+
+    it 'does not allow pins of reblogs' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account)
+      reblog  = Fabricate(:status, reblog: status)
+
+      expect(StatusPin.new(account: account, status: reblog).save).to be false
+    end
+
+    it 'does not allow pins of private statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account, visibility: :private)
+
+      expect(StatusPin.new(account: account, status: status).save).to be false
+    end
+
+    it 'does not allow pins of direct statuses' do
+      account = Fabricate(:account)
+      status  = Fabricate(:status, account: account, visibility: :direct)
+
+      expect(StatusPin.new(account: account, status: status).save).to be false
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ef45818b9..99aeca01b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -286,4 +286,24 @@ RSpec.describe User, type: :model do
       Fabricate(:user)
     end
   end
+
+  describe 'token_for_app' do
+    let(:user) { Fabricate(:user) }
+    let(:app) { Fabricate(:application, owner: user) }
+
+    it 'returns a token' do
+      expect(user.token_for_app(app)).to be_a(Doorkeeper::AccessToken)
+    end
+
+    it 'persists a token' do
+      t = user.token_for_app(app)
+      expect(user.token_for_app(app)).to eql(t)
+    end
+
+    it 'is nil if user does not own app' do
+      app.update!(owner: nil)
+
+      expect(user.token_for_app(app)).to be_nil
+    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
new file mode 100644
index 000000000..391d051c1
--- /dev/null
+++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb
@@ -0,0 +1,123 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::FetchRemoteAccountService do
+  subject { ActivityPub::FetchRemoteAccountService.new }
+
+  let!(:actor) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'https://example.com/alice',
+      type: 'Person',
+      preferredUsername: 'alice',
+      name: 'Alice',
+      summary: 'Foo bar',
+      inbox: 'http://example.com/alice/inbox',
+    }
+  end
+
+  describe '#call' do
+    let(:account) { subject.call('https://example.com/alice') }
+
+    shared_examples 'sets profile data' do
+      it 'returns an account' do
+        expect(account).to be_an Account
+      end
+
+      it 'sets display name' do
+        expect(account.display_name).to eq 'Alice'
+      end
+
+      it 'sets note' do
+        expect(account.note).to eq 'Foo bar'
+      end
+
+      it 'sets URL' do
+        expect(account.url).to eq 'https://example.com/alice'
+      end
+    end
+
+    context 'when the account does not have a inbox' do
+      let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
+
+      before do
+        actor[:inbox] = nil
+        
+        stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
+        stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+      end
+
+      it 'fetches resource' do
+        account
+        expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
+      end
+
+      it 'looks up webfinger' do
+        account
+        expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
+      end
+
+      it 'returns nil' do
+        expect(account).to be_nil
+      end
+
+    end
+
+    context 'when URI and WebFinger share the same host' do
+      let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
+
+      before do
+        stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
+        stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+      end
+
+      it 'fetches resource' do
+        account
+        expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
+      end
+
+      it 'looks up webfinger' do
+        account
+        expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
+      end
+
+      it 'sets username and domain from webfinger' do
+        expect(account.username).to eq 'alice'
+        expect(account.domain).to eq 'example.com'
+      end
+
+      include_examples 'sets profile data'
+    end
+
+    context 'when WebFinger presents different domain than URI' do
+      let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
+
+      before do
+        stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
+        stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+        stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
+      end
+
+      it 'fetches resource' do
+        account
+        expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once
+      end
+
+      it 'looks up webfinger' do
+        account
+        expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made.once
+      end
+
+      it 'looks up "redirected" webfinger' do
+        account
+        expect(a_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af')).to have_been_made.once
+      end
+
+      it 'sets username and domain from final webfinger' do
+        expect(account.username).to eq 'alice'
+        expect(account.domain).to eq 'iscool.af'
+      end
+
+      include_examples 'sets profile data'
+    end
+  end
+end
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
new file mode 100644
index 000000000..3b22257ed
--- /dev/null
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -0,0 +1,75 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::FetchRemoteStatusService do
+  let(:sender) { Fabricate(:account) }
+  let(:recipient) { Fabricate(:account) }
+  let(:valid_domain) { Rails.configuration.x.local_domain }
+
+  let(:note) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: "https://#{valid_domain}/@foo/1234",
+      type: 'Note',
+      content: 'Lorem ipsum',
+      attributedTo: ActivityPub::TagManager.instance.uri_for(sender),
+    }
+  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))
+    end
+
+    context 'with Note object' do
+      let(:object) { note }
+
+      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 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_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
new file mode 100644
index 000000000..84a74c231
--- /dev/null
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::ProcessAccountService do
+  pending
+end
diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb
new file mode 100644
index 000000000..249b12470
--- /dev/null
+++ b/spec/services/activitypub/process_collection_service_spec.rb
@@ -0,0 +1,55 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::ProcessCollectionService do
+  let(:actor) { Fabricate(:account) }
+
+  let(:payload) do
+    {
+      '@context': 'https://www.w3.org/ns/activitystreams',
+      id: 'foo',
+      type: 'Create',
+      actor: ActivityPub::TagManager.instance.uri_for(actor),
+      object: {
+        id: 'bar',
+        type: 'Note',
+        content: 'Lorem ipsum',
+      },
+    }
+  end
+
+  let(:json) { Oj.dump(payload) }
+
+  subject { described_class.new }
+
+  describe '#call' do
+    context 'when actor is the sender'
+    context 'when actor differs from sender' do
+      let(:forwarder) { Fabricate(:account) }
+
+      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)
+
+        subject.call(json, forwarder)
+      end
+
+      it 'processes payload with actor if valid signature exists' 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)
+
+        subject.call(json, forwarder)
+      end
+
+      it 'does not process payload if invalid signature exists' do
+        payload['signature'] = {'type' => 'RsaSignature2017'}
+
+        expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_account!).and_return(nil)
+        expect(ActivityPub::Activity).not_to receive(:factory)
+
+        subject.call(json, forwarder)
+      end
+    end
+  end
+end
diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb
index 3f3a2bc56..d74eb41a2 100644
--- a/spec/services/authorize_follow_service_spec.rb
+++ b/spec/services/authorize_follow_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe AuthorizeFollowService do
     end
   end
 
-  describe 'remote' do
+  describe 'remote OStatus' do
     let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
     before do
@@ -46,4 +46,26 @@ RSpec.describe AuthorizeFollowService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      FollowRequest.create(account: bob, target_account: sender)
+      stub_request(:post, bob.inbox_url).to_return(status: 200)
+      subject.call(bob, sender)
+    end
+
+    it 'removes follow request' do
+      expect(bob.requested?(sender)).to be false
+    end
+
+    it 'creates follow relation' do
+      expect(bob.following?(sender)).to be true
+    end
+
+    it 'sends an accept activity' do
+      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index c20085e25..b1e9ac567 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -6,6 +6,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!(: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') }
   let(:status2) { PostStatusService.new.call(alice, 'Another status') }
@@ -15,9 +16,11 @@ RSpec.describe BatchedRemoveStatusService do
 
     stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
     stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
+    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.follow!(alice)
+    hank.follow!(alice)
 
     status1
     status2
@@ -45,11 +48,10 @@ RSpec.describe BatchedRemoveStatusService do
     expect(Redis.current).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
   end
 
-  it 'sends PuSH update to PuSH subscribers with two payloads united' do
+  it 'sends PuSH update to PuSH subscribers' do
     expect(a_request(:post, 'http://example.com/push').with { |req|
-      matches = req.body.scan(TagManager::VERBS[:delete])
-      matches.size == 2
-    }).to have_been_made
+      matches = req.body.match(TagManager::VERBS[:delete])
+    }).to have_been_made.at_least_once
   end
 
   it 'sends Salmon slap to previously mentioned users' do
@@ -58,4 +60,8 @@ RSpec.describe BatchedRemoveStatusService do
       xml.match(TagManager::VERBS[:delete])
     }).to have_been_made.once
   end
+
+  it 'sends delete activity to followers' do
+    expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.at_least_once
+  end
 end
diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb
index 2a54e032e..bd2ab3d53 100644
--- a/spec/services/block_service_spec.rb
+++ b/spec/services/block_service_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe BlockService do
     end
   end
 
-  describe 'remote' do
+  describe 'remote OStatus' do
     let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
     before do
@@ -36,4 +36,21 @@ RSpec.describe BlockService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+      subject.call(sender, bob)
+    end
+
+    it 'creates a blocking relation' do
+      expect(sender.blocking?(bob)).to be true
+    end
+
+    it 'sends a block activity' do
+      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb
index 36f1b64d4..2ab1f32ca 100644
--- a/spec/services/favourite_service_spec.rb
+++ b/spec/services/favourite_service_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe FavouriteService do
     end
   end
 
-  describe 'remote' do
-    let(:bob)    { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
+  describe 'remote OStatus' do
+    let(:bob)    { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
     let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com:blahblah') }
 
     before do
@@ -38,4 +38,22 @@ RSpec.describe FavouriteService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob)    { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :activitypub, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+    let(:status) { Fabricate(:status, account: bob) }
+
+    before do
+      stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
+      subject.call(sender, status)
+    end
+
+    it 'creates a favourite' do
+      expect(status.favourites.first).to_not be_nil
+    end
+
+    it 'sends a like activity' do
+      expect(a_request(:post, "http://example.com/inbox")).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index 698eb0324..3a0786d03 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe FetchLinkCardService do
 
       it 'works with SJIS' do
         expect(a_request(:get, 'http://example.com/sjis')).to have_been_made.at_least_once
-        expect(status.preview_card.title).to eq("SJISのページ")
+        expect(status.preview_cards.first.title).to eq("SJISのページ")
       end
     end
 
@@ -40,7 +40,7 @@ RSpec.describe FetchLinkCardService do
 
       it 'works with SJIS even with wrong charset header' do
         expect(a_request(:get, 'http://example.com/sjis_with_wrong_charset')).to have_been_made.at_least_once
-        expect(status.preview_card.title).to eq("SJISのページ")
+        expect(status.preview_cards.first.title).to eq("SJISのページ")
       end
     end
 
@@ -49,7 +49,7 @@ RSpec.describe FetchLinkCardService do
 
       it 'works with koi8-r' do
         expect(a_request(:get, 'http://example.com/koi8-r')).to have_been_made.at_least_once
-        expect(status.preview_card.title).to eq("Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.")
+        expect(status.preview_cards.first.title).to eq("Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.")
       end
     end
   end
diff --git a/spec/services/fetch_remote_resource_service_spec.rb b/spec/services/fetch_remote_resource_service_spec.rb
index 81b0e48e3..c14fcfc4e 100644
--- a/spec/services/fetch_remote_resource_service_spec.rb
+++ b/spec/services/fetch_remote_resource_service_spec.rb
@@ -30,7 +30,7 @@ describe FetchRemoteResourceService do
 
       _result = subject.call(url)
 
-      expect(account_service).to have_received(:call).with(feed_url, feed_content)
+      expect(account_service).to have_received(:call).with(feed_url, feed_content, nil)
     end
 
     it 'fetches remote statuses for entry types' do
@@ -47,7 +47,7 @@ describe FetchRemoteResourceService do
 
       _result = subject.call(url)
 
-      expect(account_service).to have_received(:call).with(feed_url, feed_content)
+      expect(account_service).to have_received(:call).with(feed_url, feed_content, nil)
     end
   end
 end
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index 32dedb3ad..1e2378031 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -44,9 +44,9 @@ RSpec.describe FollowService do
     end
   end
 
-  context 'remote account' do
+  context 'remote OStatus account' do
     describe 'locked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, locked: true, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, locked: true, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
       before do
         stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
@@ -66,7 +66,7 @@ RSpec.describe FollowService do
     end
 
     describe 'unlocked account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
 
       before do
         stub_request(:post, "http://salmon.example.com/").to_return(:status => 200, :body => "", :headers => {})
@@ -91,7 +91,7 @@ RSpec.describe FollowService do
     end
 
     describe 'already followed account' do
-      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
+      let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, protocol: :ostatus, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com', hub_url: 'http://hub.example.com')).account }
 
       before do
         sender.follow!(bob)
@@ -111,4 +111,21 @@ RSpec.describe FollowService do
       end
     end
   end
+
+  context 'remote ActivityPub account' do
+    let(:bob) { Fabricate(:user, account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
+      subject.call(sender, bob.acct)
+    end
+
+    it 'creates follow request' do
+      expect(FollowRequest.find_by(account: sender, target_account: bob)).to_not be_nil
+    end
+
+    it 'sends a follow activity to the inbox' do
+      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 57876dcc2..4182c4e1f 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -100,16 +100,18 @@ RSpec.describe PostStatusService do
     expect(hashtags_service).to have_received(:call).with(status)
   end
 
-  it 'pings PuSH hubs' do
+  it 'gets distributed' do
     allow(DistributionWorker).to receive(:perform_async)
     allow(Pubsubhubbub::DistributionWorker).to receive(:perform_async)
+    allow(ActivityPub::DistributionWorker).to receive(:perform_async)
+
     account = Fabricate(:account)
 
     status = subject.call(account, "test status update")
 
     expect(DistributionWorker).to have_received(:perform_async).with(status.id)
-    expect(Pubsubhubbub::DistributionWorker).
-      to have_received(:perform_async).with(status.stream_entry.id)
+    expect(Pubsubhubbub::DistributionWorker).to have_received(:perform_async).with(status.stream_entry.id)
+    expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id)
   end
 
   it 'crawls links' do
diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb
index 5e34370ee..aca675dc6 100644
--- a/spec/services/process_feed_service_spec.rb
+++ b/spec/services/process_feed_service_spec.rb
@@ -124,8 +124,7 @@ RSpec.describe ProcessFeedService do
 </entry>
 XML
 
-    stub_request(:head, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 200, headers: { 'Content-Type' => 'application/atom+xml' })
-    stub_request(:get, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 200, body: real_body)
+    stub_request(:get, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 200, body: real_body, headers: { 'Content-Type' => 'application/atom+xml' })
 
     bad_actor = Fabricate(:account, username: 'sombra', domain: 'talon.xyz')
 
@@ -168,7 +167,7 @@ XML
   end
 
   it 'ignores reblogs if it failed to retreive reblogged statuses' do
-    stub_request(:head, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 404)
+    stub_request(:get, 'https://overwatch.com/users/tracer/updates/1').to_return(status: 404)
 
     actor = Fabricate(:account, username: 'tracer', domain: 'overwatch.com')
 
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 984d13746..09f8fa45b 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -1,22 +1,44 @@
 require 'rails_helper'
 
 RSpec.describe ProcessMentionsService do
-  let(:account)     { Fabricate(:account, username: 'alice') }
-  let(:remote_user) { Fabricate(:account, username: 'remote_user', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
-  let(:status)      { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") }
+  let(:account) { Fabricate(:account, username: 'alice') }
+  let(:status)  { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") }
 
-  subject { ProcessMentionsService.new }
+  context 'OStatus' do
+    let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
 
-  before do
-    stub_request(:post, remote_user.salmon_url)
-    subject.(status)
-  end
+    subject { ProcessMentionsService.new }
+
+    before do
+      stub_request(:post, remote_user.salmon_url)
+      subject.call(status)
+    end
 
-  it 'creates a mention' do
-    expect(remote_user.mentions.where(status: status).count).to eq 1
+    it 'creates a mention' do
+      expect(remote_user.mentions.where(status: status).count).to eq 1
+    end
+
+    it 'posts to remote user\'s Salmon end point' do
+      expect(a_request(:post, remote_user.salmon_url)).to have_been_made.once
+    end
   end
 
-  it 'posts to remote user\'s Salmon end point' do
-    expect(a_request(:post, remote_user.salmon_url)).to have_been_made
+  context 'ActivityPub' do
+    let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+
+    subject { ProcessMentionsService.new }
+
+    before do
+      stub_request(:post, remote_user.inbox_url)
+      subject.call(status)
+    end
+
+    it 'creates a mention' do
+      expect(remote_user.mentions.where(status: status).count).to eq 1
+    end
+
+    it 'sends activity to the inbox' do
+      expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
+    end
   end
 end
diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb
index 5f89169e9..0ad5c5f6b 100644
--- a/spec/services/reblog_service_spec.rb
+++ b/spec/services/reblog_service_spec.rb
@@ -2,22 +2,49 @@ require 'rails_helper'
 
 RSpec.describe ReblogService do
   let(:alice)  { Fabricate(:account, username: 'alice') }
-  let(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
-  let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }
 
-  subject { ReblogService.new }
+  context 'OStatus' do
+    let(:bob)    { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com') }
+    let(:status) { Fabricate(:status, account: bob, uri: 'tag:example.com;something:something') }
 
-  before do
-    stub_request(:post, 'http://salmon.example.com')
+    subject { ReblogService.new }
 
-    subject.(alice, status)
-  end
+    before do
+      stub_request(:post, 'http://salmon.example.com')
+      subject.call(alice, status)
+    end
+
+    it 'creates a reblog' do
+      expect(status.reblogs.count).to eq 1
+    end
 
-  it 'creates a reblog' do
-    expect(status.reblogs.count).to eq 1
+    it 'sends a Salmon slap for a remote reblog' do
+      expect(a_request(:post, 'http://salmon.example.com')).to have_been_made
+    end
   end
 
-  it 'sends a Salmon slap for a remote reblog' do
-    expect(a_request(:post, 'http://salmon.example.com')).to have_been_made
+  context 'ActivityPub' do
+    let(:bob)    { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+    let(:status) { Fabricate(:status, account: bob) }
+
+    subject { ReblogService.new }
+
+    before do
+      stub_request(:post, bob.inbox_url)
+      allow(ActivityPub::DistributionWorker).to receive(:perform_async)
+      subject.call(alice, status)
+    end
+
+    it 'creates a reblog' do
+      expect(status.reblogs.count).to eq 1
+    end
+
+    it 'distributes to followers' do
+      expect(ActivityPub::DistributionWorker).to have_received(:perform_async)
+    end
+
+    it 'sends an announce activity to the author' do
+      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
+    end
   end
 end
diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb
index 50749b633..2e06345b3 100644
--- a/spec/services/reject_follow_service_spec.rb
+++ b/spec/services/reject_follow_service_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe RejectFollowService do
     end
   end
 
-  describe 'remote' do
+  describe 'remote OStatus' do
     let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
     before do
@@ -46,4 +46,26 @@ RSpec.describe RejectFollowService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      FollowRequest.create(account: bob, target_account: sender)
+      stub_request(:post, bob.inbox_url).to_return(status: 200)
+      subject.call(bob, sender)
+    end
+
+    it 'removes follow request' do
+      expect(bob.requested?(sender)).to be false
+    end
+
+    it 'does not create follow relation' do
+      expect(bob.following?(sender)).to be false
+    end
+
+    it 'sends a reject activity' do
+      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index a3bce7613..8b34bdb6b 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -6,14 +6,21 @@ RSpec.describe RemoveStatusService 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!(:hank)   { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+  let!(:bill)   { Fabricate(:account, username: 'bill', protocol: :activitypub, domain: 'example2.com', inbox_url: 'http://example2.com/inbox') }
 
   before do
     stub_request(:post, 'http://example.com/push').to_return(status: 200, body: '', headers: {})
     stub_request(:post, 'http://example.com/salmon').to_return(status: 200, body: '', headers: {})
+    stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    stub_request(:post, 'http://example2.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.follow!(alice)
+    hank.follow!(alice)
+
     @status = PostStatusService.new.call(alice, 'Hello @bob@example.com')
+    Fabricate(:status, account: bill, reblog: @status, uri: 'hoge')
     subject.call(@status)
   end
 
@@ -31,10 +38,18 @@ RSpec.describe RemoveStatusService do
     }).to have_been_made
   end
 
+  it 'sends delete activity to followers' do
+    expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
+  end
+
   it 'sends Salmon slap to previously mentioned users' do
     expect(a_request(:post, "http://example.com/salmon").with { |req|
       xml = OStatus2::Salmon.new.unpack(req.body)
       xml.match(TagManager::VERBS[:delete])
     }).to have_been_made.once
   end
+
+  it 'sends delete activity to rebloggers' do
+    expect(a_request(:post, 'http://example2.com/inbox')).to have_been_made
+  end
 end
diff --git a/spec/services/resolve_remote_account_service_spec.rb b/spec/services/resolve_remote_account_service_spec.rb
index c3b902b34..d0eab2310 100644
--- a/spec/services/resolve_remote_account_service_spec.rb
+++ b/spec/services/resolve_remote_account_service_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe ResolveRemoteAccountService do
-  subject { ResolveRemoteAccountService.new }
+  subject { described_class.new }
 
   before do
     stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
@@ -29,29 +29,6 @@ RSpec.describe ResolveRemoteAccountService do
     expect(subject.call('catsrgr8@example.com')).to be_nil
   end
 
-  it 'returns an already existing remote account' do
-    old_account      = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
-    returned_account = subject.call('gargron@quitter.no')
-
-    expect(old_account.id).to eq returned_account.id
-  end
-
-  it 'returns a new remote account' do
-    account = subject.call('gargron@quitter.no')
-
-    expect(account.username).to eq 'gargron'
-    expect(account.domain).to eq 'quitter.no'
-    expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
-  end
-
-  it 'follows a legitimate account redirection' do
-    account = subject.call('gargron@redirected.com')
-
-    expect(account.username).to eq 'gargron'
-    expect(account.domain).to eq 'quitter.no'
-    expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
-  end
-
   it 'prevents hijacking existing accounts' do
     account = subject.call('hacker1@redirected.com')
     expect(account.salmon_url).to_not eq 'https://hacker.com/main/salmon/user/7477'
@@ -61,12 +38,41 @@ RSpec.describe ResolveRemoteAccountService do
     expect(subject.call('hacker2@redirected.com')).to be_nil
   end
 
-  it 'returns a new remote account' do
-    account = subject.call('foo@localdomain.com')
+  context 'with an OStatus account' do
+    it 'returns an already existing remote account' do
+      old_account      = Fabricate(:account, username: 'gargron', domain: 'quitter.no')
+      returned_account = subject.call('gargron@quitter.no')
+
+      expect(old_account.id).to eq returned_account.id
+    end
+
+    it 'returns a new remote account' do
+      account = subject.call('gargron@quitter.no')
+
+      expect(account.username).to eq 'gargron'
+      expect(account.domain).to eq 'quitter.no'
+      expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
+    end
+
+    it 'follows a legitimate account redirection' do
+      account = subject.call('gargron@redirected.com')
+
+      expect(account.username).to eq 'gargron'
+      expect(account.domain).to eq 'quitter.no'
+      expect(account.remote_url).to eq 'https://quitter.no/api/statuses/user_timeline/7477.atom'
+    end
+
+    it 'returns a new remote account' do
+      account = subject.call('foo@localdomain.com')
+
+      expect(account.username).to eq 'foo'
+      expect(account.domain).to eq 'localdomain.com'
+      expect(account.remote_url).to eq 'https://webdomain.com/users/foo.atom'
+    end
+  end
 
-    expect(account.username).to eq 'foo'
-    expect(account.domain).to eq 'localdomain.com'
-    expect(account.remote_url).to eq 'https://webdomain.com/users/foo.atom'
+  context 'with an ActivityPub account' do
+    pending
   end
 
   it 'processes one remote account at a time using locks' do
@@ -78,7 +84,7 @@ RSpec.describe ResolveRemoteAccountService do
       Thread.new do
         true while wait_for_start
         begin
-          return_values << ResolveRemoteAccountService.new.call('foo@localdomain.com')
+          return_values << described_class.new.call('foo@localdomain.com')
         rescue ActiveRecord::RecordNotUnique
           fail_occurred = true
         end
diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb
index 1b9ae1239..def4981e7 100644
--- a/spec/services/unblock_service_spec.rb
+++ b/spec/services/unblock_service_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe UnblockService do
     end
   end
 
-  describe 'remote' do
+  describe 'remote OStatus' do
     let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
     before do
@@ -28,7 +28,7 @@ RSpec.describe UnblockService do
     end
 
     it 'destroys the blocking relation' do
-      expect(sender.following?(bob)).to be false
+      expect(sender.blocking?(bob)).to be false
     end
 
     it 'sends an unblock salmon slap' do
@@ -38,4 +38,22 @@ RSpec.describe UnblockService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      sender.block!(bob)
+      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+      subject.call(sender, bob)
+    end
+
+    it 'destroys the blocking relation' do
+      expect(sender.blocking?(bob)).to be false
+    end
+
+    it 'sends an unblock activity' do
+      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb
index 8ec2148a1..29040431e 100644
--- a/spec/services/unfollow_service_spec.rb
+++ b/spec/services/unfollow_service_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe UnfollowService do
     end
   end
 
-  describe 'remote' do
-    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
+  describe 'remote OStatus' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com')).account }
 
     before do
       sender.follow!(bob)
@@ -38,4 +38,22 @@ RSpec.describe UnfollowService do
       }).to have_been_made.once
     end
   end
+
+  describe 'remote ActivityPub' do
+    let(:bob) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox')).account }
+
+    before do
+      sender.follow!(bob)
+      stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+      subject.call(sender, bob)
+    end
+
+    it 'destroys the following relation' do
+      expect(sender.following?(bob)).to be false
+    end
+
+    it 'sends an unfollow activity' do
+      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+    end
+  end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 2bc462121..eecaec4ac 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,11 +1,15 @@
 require 'simplecov'
 
+GC.disable
+
 SimpleCov.start 'rails' do
   add_group 'Services', 'app/services'
   add_group 'Presenters', 'app/presenters'
   add_group 'Validators', 'app/validators'
 end
 
+gc_counter = -1
+
 RSpec.configure do |config|
   config.expect_with :rspec do |expectations|
     expectations.include_chain_clauses_in_custom_matcher_descriptions = true
@@ -22,8 +26,21 @@ RSpec.configure do |config|
   end
 
   config.after :suite do
+    gc_counter = 0
     FileUtils.rm_rf(Dir["#{Rails.root}/spec/test_files/"])
   end
+
+  config.after :each do
+    gc_counter += 1
+
+    if gc_counter > 19
+      GC.enable
+      GC.start
+      GC.disable
+
+      gc_counter = 0
+    end
+  end
 end
 
 def body_as_json
diff --git a/spec/views/about/show.html.haml_spec.rb b/spec/views/about/show.html.haml_spec.rb
index d460adfe5..95a8a6323 100644
--- a/spec/views/about/show.html.haml_spec.rb
+++ b/spec/views/about/show.html.haml_spec.rb
@@ -15,6 +15,7 @@ describe 'about/show.html.haml', without_verify_partial_doubles: true do
                                 site_title: 'something',
                                 site_description: 'something',
                                 version_number: '1.0',
+                                source_url: 'https://github.com/tootsuite/mastodon',
                                 open_registrations: false,
                                 closed_registrations_message: 'yes',
                                 commit_hash: commit_hash)
diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb
new file mode 100644
index 000000000..351be185c
--- /dev/null
+++ b/spec/workers/activitypub/delivery_worker_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe ActivityPub::DeliveryWorker do
+  subject { described_class.new }
+
+  let(:sender)  { Fabricate(:account) }
+  let(:payload) { 'test' }
+
+  describe 'perform' do
+    it 'performs a request' do
+      stub_request(:post, 'https://example.com/api').to_return(status: 200)
+      subject.perform(payload, sender.id, 'https://example.com/api')
+      expect(a_request(:post, 'https://example.com/api')).to have_been_made.once
+    end
+
+    it 'raises when request fails' do
+      stub_request(:post, 'https://example.com/api').to_return(status: 500)
+      expect { subject.perform(payload, sender.id, 'https://example.com/api') }.to raise_error Mastodon::UnexpectedResponseError
+    end
+  end
+end
diff --git a/spec/workers/activitypub/distribution_worker_spec.rb b/spec/workers/activitypub/distribution_worker_spec.rb
new file mode 100644
index 000000000..368ca025a
--- /dev/null
+++ b/spec/workers/activitypub/distribution_worker_spec.rb
@@ -0,0 +1,48 @@
+require 'rails_helper'
+
+describe ActivityPub::DistributionWorker do
+  subject { described_class.new }
+
+  let(:status)   { Fabricate(:status) }
+  let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') }
+
+  describe '#perform' do
+    before do
+      allow(ActivityPub::DeliveryWorker).to receive(:push_bulk)
+      follower.follow!(status.account)
+    end
+
+    context 'with public status' do
+      before do
+        status.update(visibility: :public)
+      end
+
+      it 'delivers to followers' do
+        subject.perform(status.id)
+        expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
+      end
+    end
+
+    context 'with private status' do
+      before do
+        status.update(visibility: :private)
+      end
+
+      it 'delivers to followers' do
+        subject.perform(status.id)
+        expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
+      end
+    end
+
+    context 'with direct status' do
+      before do
+        status.update(visibility: :direct)
+      end
+
+      it 'does nothing' do
+        subject.perform(status.id)
+        expect(ActivityPub::DeliveryWorker).to_not have_received(:push_bulk)
+      end
+    end
+  end
+end
diff --git a/spec/workers/activitypub/processing_worker_spec.rb b/spec/workers/activitypub/processing_worker_spec.rb
new file mode 100644
index 000000000..b42c0bdbc
--- /dev/null
+++ b/spec/workers/activitypub/processing_worker_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe ActivityPub::ProcessingWorker do
+  subject { described_class.new }
+
+  let(:account) { Fabricate(:account) }
+
+  describe '#perform' do
+    it 'delegates to ActivityPub::ProcessCollectionService' do
+      allow(ActivityPub::ProcessCollectionService).to receive(:new).and_return(double(:service, call: nil))
+      subject.perform(account.id, '')
+      expect(ActivityPub::ProcessCollectionService).to have_received(:new)
+    end
+  end
+end
diff --git a/spec/workers/activitypub/update_distribution_worker_spec.rb b/spec/workers/activitypub/update_distribution_worker_spec.rb
new file mode 100644
index 000000000..688a424d5
--- /dev/null
+++ b/spec/workers/activitypub/update_distribution_worker_spec.rb
@@ -0,0 +1,20 @@
+require 'rails_helper'
+
+describe ActivityPub::UpdateDistributionWorker do
+  subject { described_class.new }
+
+  let(:account)  { Fabricate(:account) }
+  let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') }
+
+  describe '#perform' do
+    before do
+      allow(ActivityPub::DeliveryWorker).to receive(:push_bulk)
+      follower.follow!(account)
+    end
+
+    it 'delivers to followers' do
+      subject.perform(account.id)
+      expect(ActivityPub::DeliveryWorker).to have_received(:push_bulk).with(['http://example.com'])
+    end
+  end
+end
diff --git a/spec/workers/pubsubhubbub/distribution_worker_spec.rb b/spec/workers/pubsubhubbub/distribution_worker_spec.rb
index 89191c084..5c22e7fa8 100644
--- a/spec/workers/pubsubhubbub/distribution_worker_spec.rb
+++ b/spec/workers/pubsubhubbub/distribution_worker_spec.rb
@@ -22,24 +22,62 @@ describe Pubsubhubbub::DistributionWorker do
     end
   end
 
-  describe 'with private status' do
-    let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :private) }
+  context 'when OStatus privacy is used' do
+    around do |example|
+      before_val = Rails.configuration.x.use_ostatus_privacy
+      Rails.configuration.x.use_ostatus_privacy = true
+      example.run
+      Rails.configuration.x.use_ostatus_privacy = before_val
+    end
 
-    it 'delivers payload only to subscriptions with followers' do
-      allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
-      subject.perform(status.stream_entry.id)
-      expect(Pubsubhubbub::DeliveryWorker).to have_received(:push_bulk).with([subscription_with_follower])
-      expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk).with([anonymous_subscription])
+    describe 'with private status' do
+      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :private) }
+
+      it 'delivers payload only to subscriptions with followers' do
+        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
+        subject.perform(status.stream_entry.id)
+        expect(Pubsubhubbub::DeliveryWorker).to have_received(:push_bulk).with([subscription_with_follower])
+        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk).with([anonymous_subscription])
+      end
+    end
+
+    describe 'with direct status' do
+      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :direct) }
+
+      it 'does not deliver payload' do
+        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
+        subject.perform(status.stream_entry.id)
+        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
+      end
     end
   end
 
-  describe 'with direct status' do
-    let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :direct) }
+  context 'when OStatus privacy is not used' do
+    around do |example|
+      before_val = Rails.configuration.x.use_ostatus_privacy
+      Rails.configuration.x.use_ostatus_privacy = false
+      example.run
+      Rails.configuration.x.use_ostatus_privacy = before_val
+    end
 
-    it 'does not deliver payload' do
-      allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
-      subject.perform(status.stream_entry.id)
-      expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
+    describe 'with private status' do
+      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :private) }
+
+      it 'does not deliver anything' do
+        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
+        subject.perform(status.stream_entry.id)
+        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
+      end
+    end
+
+    describe 'with direct status' do
+      let(:status) { Fabricate(:status, account: alice, text: 'Hello', visibility: :direct) }
+
+      it 'does not deliver payload' do
+        allow(Pubsubhubbub::DeliveryWorker).to receive(:push_bulk)
+        subject.perform(status.stream_entry.id)
+        expect(Pubsubhubbub::DeliveryWorker).to_not have_received(:push_bulk)
+      end
     end
   end
 end