about summary refs log tree commit diff
path: root/spec/lib
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib')
-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.rb350
-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.rb45
-rw-r--r--spec/lib/activitypub/linked_data_signature_spec.rb82
-rw-r--r--spec/lib/activitypub/tag_manager_spec.rb125
-rw-r--r--spec/lib/emoji_spec.rb15
-rw-r--r--spec/lib/formatter_spec.rb130
-rw-r--r--spec/lib/language_detector_spec.rb34
-rw-r--r--spec/lib/ostatus/atom_serializer_spec.rb146
-rw-r--r--spec/lib/ostatus/tag_manager_spec.rb70
-rw-r--r--spec/lib/status_finder_spec.rb (renamed from spec/lib/stream_entry_finder_spec.rb)24
-rw-r--r--spec/lib/tag_manager_spec.rb86
19 files changed, 1289 insertions, 189 deletions
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..cdd499150
--- /dev/null
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -0,0 +1,350 @@
+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'))
+    stub_request(:get, 'http://example.com/emoji.png').to_return(body: attachment_fixture('emojo.png'))
+  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 mentions missing href' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          tag: [
+            {
+              type: 'Mention',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+        expect(status).to_not be_nil
+      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 media attachments missing url' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          attachment: [
+            {
+              type: 'Document',
+              mime_type: 'image/png',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+        expect(status).to_not be_nil
+      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
+
+    context 'with hashtags missing name' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum',
+          tag: [
+            {
+              type: 'Hashtag',
+              href: 'http://example.com/blah',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+        expect(status).to_not be_nil
+      end
+    end
+
+    context 'with emojis' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum :tinking:',
+          tag: [
+            {
+              type: 'Emoji',
+              href: 'http://example.com/emoji.png',
+              name: 'tinking',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+
+        expect(status).to_not be_nil
+        expect(status.emojis.map(&:shortcode)).to include('tinking')
+      end
+    end
+
+    context 'with emojis missing name' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum :tinking:',
+          tag: [
+            {
+              type: 'Emoji',
+              href: 'http://example.com/emoji.png',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+        expect(status).to_not be_nil
+      end
+    end
+
+    context 'with emojis missing href' do
+      let(:object_json) do
+        {
+          id: 'bar',
+          type: 'Note',
+          content: 'Lorem ipsum :tinking:',
+          tag: [
+            {
+              type: 'Emoji',
+              name: 'tinking',
+            },
+          ],
+        }
+      end
+
+      it 'creates status' do
+        status = sender.statuses.first
+        expect(status).to_not be_nil
+      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..38254e31c
--- /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, domain: 'example.com') }
+  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..14c68efe5
--- /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, domain: 'example.com') }
+
+  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..ea308e35c
--- /dev/null
+++ b/spec/lib/activitypub/activity/update_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::Activity::Update do
+  let!(:sender) { Fabricate(:account) }
+
+  before do
+    stub_request(:get, actor_json[:outbox]).to_return(status: 404)
+    stub_request(:get, actor_json[:followers]).to_return(status: 404)
+    stub_request(:get, actor_json[:following]).to_return(status: 404)
+
+    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..0d1665216
--- /dev/null
+++ b/spec/lib/activitypub/tag_manager_spec.rb
@@ -0,0 +1,125 @@
+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 account' do
+      account = Fabricate(:account)
+      expect(subject.uri_to_resource(subject.uri_for(account), Account)).to eq account
+    end
+
+    it 'returns the remote account by matching URI without fragment part' do
+      account = Fabricate(:account, uri: 'https://example.com/123')
+      expect(subject.uri_to_resource('https://example.com/123#456', Account)).to eq account
+    end
+
+    it 'returns the local status for ActivityPub URI' do
+      status = Fabricate(:status)
+      expect(subject.uri_to_resource(subject.uri_for(status), Status)).to eq status
+    end
+
+    it 'returns the local status for OStatus tag: URI' do
+      status = Fabricate(:status)
+      expect(subject.uri_to_resource(OStatus::TagManager.instance.uri_for(status), Status)).to eq status
+    end
+
+    it 'returns the local status for OStatus StreamEntry URL' do
+      status = Fabricate(:status)
+      stream_entry_url = account_stream_entry_url(status.account, status.stream_entry)
+      expect(subject.uri_to_resource(stream_entry_url, Status)).to eq status
+    end
+
+    it 'returns the remote status by matching URI without fragment part' do
+      status = Fabricate(:status, uri: 'https://example.com/123')
+      expect(subject.uri_to_resource('https://example.com/123#456', Status)).to eq status
+    end
+  end
+end
diff --git a/spec/lib/emoji_spec.rb b/spec/lib/emoji_spec.rb
deleted file mode 100644
index 04931ccfb..000000000
--- a/spec/lib/emoji_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Emoji do
-  describe '#unicode' do
-    it 'returns a unicode for a shortcode' do
-      expect(Emoji.instance.unicode(':joy:')).to eq '😂'
-    end
-  end
-
-  describe '#names' do
-    it 'returns an array' do
-      expect(Emoji.instance.names).to be_an Array
-    end
-  end
-end
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index dfe1d8b8f..71b6b78d2 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -89,6 +89,54 @@ RSpec.describe Formatter do
       end
     end
 
+    context 'matches a URL with Japanese path string' do
+      let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
+
+      it 'has valid URL' do
+        is_expected.to include 'href="https://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC"'
+      end
+    end
+
+    context 'matches a URL with Korean path string' do
+      let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
+
+      it 'has valid URL' do
+        is_expected.to include 'href="https://ko.wikipedia.org/wiki/%EB%8C%80%ED%95%9C%EB%AF%BC%EA%B5%AD"'
+      end
+    end
+
+    context 'matches a URL with Simplified Chinese path string' do
+      let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
+
+      it 'has valid URL' do
+        is_expected.to include 'href="https://baike.baidu.com/item/%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD"'
+      end
+    end
+
+    context 'matches a URL with Traditional Chinese path string' do
+      let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
+
+      it 'has valid URL' do
+        is_expected.to include 'href="https://zh.wikipedia.org/wiki/%E8%87%BA%E7%81%A3"'
+      end
+    end
+
+    context 'contains unsafe URL (XSS attack, visible part)' do
+      let(:text) { %q{http://example.com/b<del>b</del>} }
+
+      it 'has escaped HTML' do
+        is_expected.to include '&lt;del&gt;b&lt;/del&gt;'
+      end
+    end
+
+    context 'contains unsafe URL (XSS attack, invisible part)' do
+      let(:text) { %q{http://example.com/blahblahblahblah/a<script>alert("Hello")</script>} }
+
+      it 'has escaped HTML' do
+        is_expected.to include '&lt;script&gt;alert(&quot;Hello&quot;)&lt;/script&gt;'
+      end
+    end
+
     context 'contains HTML (script tag)' do
       let(:text) { '<script>alert("Hello")</script>' }
 
@@ -175,14 +223,92 @@ RSpec.describe Formatter do
 
         include_examples 'encode and link URLs'
       end
+
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji) }
+        let(:status) { Fabricate(:status, account: local_account, text: text) }
+
+        subject { Formatter.instance.format(status, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { ':coolcat: Beep boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { 'Beep :coolcat: boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { ':coolcat::coolcat:' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/:coolcat::coolcat:/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { 'Beep boop :coolcat:' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
     end
 
     context 'with remote status' do
-      let(:status) { Fabricate(:status, text: 'Beep boop', uri: 'beepboop') }
+      let(:status) { Fabricate(:status, account: remote_account, text: 'Beep boop') }
 
       it 'reformats' do
         is_expected.to eq 'Beep boop'
       end
+
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
+        let(:status) { Fabricate(:status, account: remote_account, text: text) }
+
+        subject { Formatter.instance.format(status, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { '<p>:coolcat: Beep boop<br />' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { '<p>Beep :coolcat: boop</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { '<p>:coolcat::coolcat:</p>' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
     end
   end
 
@@ -226,7 +352,7 @@ RSpec.describe Formatter do
     end
 
     context 'with remote status' do
-      let(:status)  { Fabricate(:status, text: '<script>alert("Hello")</script>', uri: 'beep boop') }
+      let(:status)  { Fabricate(:status, account: remote_account, text: '<script>alert("Hello")</script>') }
 
       it 'returns tag-stripped text' do
         is_expected.to eq ''
diff --git a/spec/lib/language_detector_spec.rb b/spec/lib/language_detector_spec.rb
index ec39cb6a0..d17026511 100644
--- a/spec/lib/language_detector_spec.rb
+++ b/spec/lib/language_detector_spec.rb
@@ -3,10 +3,10 @@
 require 'rails_helper'
 
 describe LanguageDetector do
-  describe 'prepared_text' do
+  describe 'prepare_text' do
     it 'returns unmodified string without special cases' do
       string = 'just a regular string'
-      result = described_class.new(string).prepared_text
+      result = described_class.instance.send(:prepare_text, string)
 
       expect(result).to eq string
     end
@@ -14,33 +14,35 @@ describe LanguageDetector do
     it 'collapses spacing in strings' do
       string = 'The formatting   in    this is very        odd'
 
-      result = described_class.new(string).prepared_text
+      result = described_class.instance.send(:prepare_text, string)
       expect(result).to eq 'The formatting in this is very odd'
     end
 
     it 'strips usernames from strings before detection' do
       string = '@username Yeah, very surreal...! also @friend'
 
-      result = described_class.new(string).prepared_text
+      result = described_class.instance.send(:prepare_text, string)
       expect(result).to eq 'Yeah, very surreal...! also'
     end
 
     it 'strips URLs from strings before detection' do
       string = 'Our website is https://example.com and also http://localhost.dev'
 
-      result = described_class.new(string).prepared_text
+      result = described_class.instance.send(:prepare_text, string)
       expect(result).to eq 'Our website is and also'
     end
 
     it 'strips #hashtags from strings before detection' do
       string = 'Hey look at all the #animals and #fish'
 
-      result = described_class.new(string).prepared_text
+      result = described_class.instance.send(:prepare_text, string)
       expect(result).to eq 'Hey look at all the and'
     end
   end
 
-  describe 'to_iso_s' do
+  describe 'detect' do
+    let(:account_without_user_locale) { Fabricate(:user, locale: nil).account }
+
     it 'detects english language for basic strings' do
       strings = [
         "Hello and welcome to mastodon how are you today?",
@@ -48,7 +50,7 @@ describe LanguageDetector do
         "a lot of people just want to feel righteous all the time and that's all that matters",
       ]
       strings.each do |string|
-        result = described_class.new(string).to_iso_s
+        result = described_class.instance.detect(string, account_without_user_locale)
 
         expect(result).to eq(:en), string
       end
@@ -56,14 +58,14 @@ describe LanguageDetector do
 
     it 'detects spanish language' do
       string = 'Obtener un Hola y bienvenidos a Mastodon'
-      result = described_class.new(string).to_iso_s
+      result = described_class.instance.detect(string, account_without_user_locale)
 
       expect(result).to eq :es
     end
 
     describe 'when language can\'t be detected' do
       it 'uses nil when sent an empty document' do
-        result = described_class.new('').to_iso_s
+        result = described_class.instance.detect('', account_without_user_locale)
         expect(result).to eq nil
       end
 
@@ -73,7 +75,7 @@ describe LanguageDetector do
           cld_result = CLD3::NNetLanguageIdentifier.new(0, 2048).find_language(string)
           expect(cld_result).not_to eq :en
 
-          result = described_class.new(string).to_iso_s
+          result = described_class.instance.detect(string, account_without_user_locale)
 
           expect(result).to eq nil
         end
@@ -82,14 +84,13 @@ describe LanguageDetector do
       describe 'with an account' do
         it 'uses the account locale when present' do
           account = double(user_locale: 'fr')
-          result  = described_class.new('', account).to_iso_s
+          result  = described_class.instance.detect('', account)
 
           expect(result).to eq :fr
         end
 
         it 'uses nil when account is present but has no locale' do
-          account = double(user_locale: nil)
-          result  = described_class.new('', account).to_iso_s
+          result  = described_class.instance.detect('', account_without_user_locale)
 
           expect(result).to eq nil
         end
@@ -97,8 +98,7 @@ describe LanguageDetector do
 
       describe 'with an `en` default locale' do
         it 'uses nil for undetectable string' do
-          string = ''
-          result = described_class.new(string).to_iso_s
+          result = described_class.instance.detect('', account_without_user_locale)
 
           expect(result).to eq nil
         end
@@ -114,7 +114,7 @@ describe LanguageDetector do
 
         it 'uses nil for undetectable string' do
           string = ''
-          result = described_class.new(string).to_iso_s
+          result = described_class.instance.detect(string, account_without_user_locale)
 
           expect(result).to eq nil
         end
diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb
index b0cb8f019..00e6f09dc 100644
--- a/spec/lib/ostatus/atom_serializer_spec.rb
+++ b/spec/lib/ostatus/atom_serializer_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe OStatus::AtomSerializer do
       follow_request_salmon = serialize(follow_request)
 
       object_type = follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with request_friend type' do
@@ -26,7 +26,7 @@ RSpec.describe OStatus::AtomSerializer do
       follow_request_salmon = serialize(follow_request)
 
       verb = follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:request_friend]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:request_friend]
     end
 
     it 'appends activity:object with target account' do
@@ -44,13 +44,13 @@ RSpec.describe OStatus::AtomSerializer do
     it 'adds namespaces' do
       element = serialize
 
-      expect(element['xmlns']).to eq TagManager::XMLNS
-      expect(element['xmlns:thr']).to eq TagManager::THR_XMLNS
-      expect(element['xmlns:activity']).to eq TagManager::AS_XMLNS
-      expect(element['xmlns:poco']).to eq TagManager::POCO_XMLNS
-      expect(element['xmlns:media']).to eq TagManager::MEDIA_XMLNS
-      expect(element['xmlns:ostatus']).to eq TagManager::OS_XMLNS
-      expect(element['xmlns:mastodon']).to eq TagManager::MTDN_XMLNS
+      expect(element['xmlns']).to eq OStatus::TagManager::XMLNS
+      expect(element['xmlns:thr']).to eq OStatus::TagManager::THR_XMLNS
+      expect(element['xmlns:activity']).to eq OStatus::TagManager::AS_XMLNS
+      expect(element['xmlns:poco']).to eq OStatus::TagManager::POCO_XMLNS
+      expect(element['xmlns:media']).to eq OStatus::TagManager::MEDIA_XMLNS
+      expect(element['xmlns:ostatus']).to eq OStatus::TagManager::OS_XMLNS
+      expect(element['xmlns:mastodon']).to eq OStatus::TagManager::MTDN_XMLNS
     end
   end
 
@@ -97,11 +97,23 @@ RSpec.describe OStatus::AtomSerializer do
 
       mentioned = element.nodes.find do |node|
         node.name == 'link' &&
-        node[:rel] == 'mentioned' &&
-        node['ostatus:object-type'] == TagManager::TYPES[:person]
+          node[:rel] == 'mentioned' &&
+          node['ostatus:object-type'] == OStatus::TagManager::TYPES[:person]
       end
+
       expect(mentioned[:href]).to eq 'https://cb6e6126.ngrok.io/users/username'
     end
+
+    it 'appends link elements for emojis' do
+      Fabricate(:custom_emoji)
+
+      status  = Fabricate(:status, text: ':coolcat:')
+      element = serialize(status)
+      emoji   = element.nodes.find { |node| node.name == 'link' && node[:rel] == 'emoji' }
+
+      expect(emoji[:name]).to eq 'coolcat'
+      expect(emoji[:href]).to_not be_blank
+    end
   end
 
   describe 'render' do
@@ -176,7 +188,7 @@ RSpec.describe OStatus::AtomSerializer do
       author = OStatus::AtomSerializer.new.author(account)
 
       object_type = author.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:person]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:person]
     end
 
     it 'appends email element with username and domain for local account' do
@@ -196,7 +208,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'
@@ -346,9 +358,9 @@ RSpec.describe OStatus::AtomSerializer do
         mentioned_person = entry.nodes.find do |node|
           node.name == 'link' &&
           node[:rel] == 'mentioned' &&
-          node['ostatus:object-type'] == TagManager::TYPES[:collection]
+          node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection]
         end
-        expect(mentioned_person[:href]).to eq TagManager::COLLECTIONS[:public]
+        expect(mentioned_person[:href]).to eq OStatus::TagManager::COLLECTIONS[:public]
       end
 
       it 'does not append link element for the public collection if status is not publicly visible' do
@@ -359,8 +371,8 @@ RSpec.describe OStatus::AtomSerializer do
         entry.nodes.each do |node|
           if node.name == 'link' &&
              node[:rel] == 'mentioned' &&
-             node['ostatus:object-type'] == TagManager::TYPES[:collection]
-            expect(mentioned_collection[:href]).not_to eq TagManager::COLLECTIONS[:public]
+             node['ostatus:object-type'] == OStatus::TagManager::TYPES[:collection]
+            expect(mentioned_collection[:href]).not_to eq OStatus::TagManager::COLLECTIONS[:public]
           end
         end
       end
@@ -403,10 +415,10 @@ RSpec.describe OStatus::AtomSerializer do
 
       it 'returns element whose rendered view triggers creation when processed' do
         remote_account = Account.create!(username: 'username')
-        remote_status = Fabricate(:status, account: remote_account)
-        remote_status.stream_entry.update!(created_at: '2000-01-01T00:00:00Z')
+        remote_status = Fabricate(:status, account: remote_account, 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,12 +427,12 @@ 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)
 
-        expect(Status.find_by(uri: "tag:remote,2000-01-01:objectId=#{remote_status.id}:objectType=Status")).to be_instance_of Status
+        expect(Status.find_by(uri: "https://remote/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status
       end
     end
 
@@ -464,12 +476,11 @@ RSpec.describe OStatus::AtomSerializer do
     end
 
     it 'appends id element with unique tag' do
-      status = Fabricate(:status, reblog_of_id: nil)
-      status.stream_entry.update!(created_at: '2000-01-01T00:00:00Z')
+      status = Fabricate(:status, reblog_of_id: nil, created_at: '2000-01-01T00:00:00Z')
 
       entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
 
-      expect(entry.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(entry.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
     end
 
     it 'appends published element with created date' do
@@ -495,7 +506,7 @@ RSpec.describe OStatus::AtomSerializer do
       status = Fabricate(:status)
       entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
       object_type = entry.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:note]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:note]
     end
 
     it 'appends activity:verb element with object type' do
@@ -504,7 +515,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.entry(status.stream_entry)
 
       object_type = entry.nodes.find { |node| node.name == 'activity:verb' }
-      expect(object_type.text).to eq TagManager::VERBS[:post]
+      expect(object_type.text).to eq OStatus::TagManager::VERBS[:post]
     end
 
     it 'appends activity:object element with target if present' do
@@ -514,7 +525,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.entry(reblog.stream_entry)
 
       object = entry.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{reblogged.id}:objectType=Status"
+      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{reblogged.account.to_param}/statuses/#{reblogged.id}"
     end
 
     it 'does not append activity:object element if target is not present' do
@@ -529,9 +540,9 @@ 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}"
+      expect(link[:href]).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
     end
 
     it 'appends link element for itself' do
@@ -552,7 +563,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.entry(reply_status.stream_entry)
 
       in_reply_to = entry.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to[:ref]).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{in_reply_to_status.id}:objectType=Status"
+      expect(in_reply_to[:ref]).to eq "https://cb6e6126.ngrok.io/users/#{in_reply_to_status.account.to_param}/statuses/#{in_reply_to_status.id}"
     end
 
     it 'does not append thr:in-reply-to element if not threaded' do
@@ -642,7 +653,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
@@ -728,8 +739,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(block_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
-          .or(eq(TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
+          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
       )
     end
 
@@ -758,7 +769,7 @@ RSpec.describe OStatus::AtomSerializer do
       block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
 
       object_type = block_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with block' do
@@ -767,7 +778,7 @@ RSpec.describe OStatus::AtomSerializer do
       block_salmon = OStatus::AtomSerializer.new.block_salmon(block)
 
       verb = block_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:block]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:block]
     end
 
     it 'appends activity:object element with target account' do
@@ -815,8 +826,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(unblock_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
-          .or(eq(TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, block.id, 'Block'))
+          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, block.id, 'Block')))
       )
     end
 
@@ -845,7 +856,7 @@ RSpec.describe OStatus::AtomSerializer do
       unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
 
       object_type = unblock_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with block' do
@@ -854,7 +865,7 @@ RSpec.describe OStatus::AtomSerializer do
       unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block)
 
       verb = unblock_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:unblock]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:unblock]
     end
 
     it 'appends activity:object element with target account' do
@@ -923,7 +934,7 @@ RSpec.describe OStatus::AtomSerializer do
       favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
 
       verb = favourite_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:favorite]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:favorite]
     end
 
     it 'appends activity:object element with status' do
@@ -933,7 +944,7 @@ RSpec.describe OStatus::AtomSerializer do
       favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
 
       object = favourite_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
     end
 
     it 'appends thr:in-reply-to element for status' do
@@ -944,7 +955,7 @@ RSpec.describe OStatus::AtomSerializer do
       favourite_salmon = OStatus::AtomSerializer.new.favourite_salmon(favourite)
 
       in_reply_to = favourite_salmon.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
       expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
     end
 
@@ -994,8 +1005,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(unfavourite_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, favourite.id, 'Favourite'))
-          .or(eq(TagManager.instance.unique_tag(time_after.utc, favourite.id, 'Favourite')))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, favourite.id, 'Favourite'))
+          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, favourite.id, 'Favourite')))
       )
     end
 
@@ -1023,7 +1034,7 @@ RSpec.describe OStatus::AtomSerializer do
       unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
 
       verb = unfavourite_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:unfavorite]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:unfavorite]
     end
 
     it 'appends activity:object element with status' do
@@ -1033,7 +1044,7 @@ RSpec.describe OStatus::AtomSerializer do
       unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
 
       object = unfavourite_salmon.nodes.find { |node| node.name == 'activity:object' }
-      expect(object.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
     end
 
     it 'appends thr:in-reply-to element for status' do
@@ -1044,7 +1055,7 @@ RSpec.describe OStatus::AtomSerializer do
       unfavourite_salmon = OStatus::AtomSerializer.new.unfavourite_salmon(favourite)
 
       in_reply_to = unfavourite_salmon.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
       expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{status.id}"
     end
 
@@ -1106,7 +1117,7 @@ RSpec.describe OStatus::AtomSerializer do
       follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
 
       object_type = follow_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with follow' do
@@ -1115,7 +1126,7 @@ RSpec.describe OStatus::AtomSerializer do
       follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow)
 
       verb = follow_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:follow]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:follow]
     end
 
     it 'appends activity:object element with target account' do
@@ -1179,8 +1190,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(unfollow_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, follow.id, 'Follow'))
-          .or(eq(TagManager.instance.unique_tag(time_after.utc, follow.id, 'Follow')))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow.id, 'Follow'))
+          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow.id, 'Follow')))
       )
     end
 
@@ -1223,7 +1234,7 @@ RSpec.describe OStatus::AtomSerializer do
       unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
 
       object_type = unfollow_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with follow' do
@@ -1233,7 +1244,7 @@ RSpec.describe OStatus::AtomSerializer do
       unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow)
 
       verb = unfollow_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:unfollow]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:unfollow]
     end
 
     it 'appends activity:object element with target account' do
@@ -1327,8 +1338,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(authorize_follow_request_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
-          .or(eq(TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest')))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
+          .or(eq(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest')))
       )
     end
 
@@ -1348,7 +1359,7 @@ RSpec.describe OStatus::AtomSerializer do
       authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
 
       object_type = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with authorize' do
@@ -1357,7 +1368,7 @@ RSpec.describe OStatus::AtomSerializer do
       authorize_follow_request_salmon = OStatus::AtomSerializer.new.authorize_follow_request_salmon(follow_request)
 
       verb = authorize_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:authorize]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:authorize]
     end
 
     it 'returns element whose rendered view creates follow from follow request when processed' do
@@ -1396,8 +1407,8 @@ RSpec.describe OStatus::AtomSerializer do
       time_after = Time.now
 
       expect(reject_follow_request_salmon.id.text).to(
-        eq(TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
-          .or(TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest'))
+        eq(OStatus::TagManager.instance.unique_tag(time_before.utc, follow_request.id, 'FollowRequest'))
+          .or(OStatus::TagManager.instance.unique_tag(time_after.utc, follow_request.id, 'FollowRequest'))
       )
     end
 
@@ -1413,14 +1424,14 @@ RSpec.describe OStatus::AtomSerializer do
       follow_request = Fabricate(:follow_request)
       reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
       object_type = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:activity]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:activity]
     end
 
     it 'appends activity:verb element with authorize' do
       follow_request = Fabricate(:follow_request)
       reject_follow_request_salmon = OStatus::AtomSerializer.new.reject_follow_request_salmon(follow_request)
       verb = reject_follow_request_salmon.nodes.find { |node| node.name == 'activity:verb' }
-      expect(verb.text).to eq TagManager::VERBS[:reject]
+      expect(verb.text).to eq OStatus::TagManager::VERBS[:reject]
     end
 
     it 'returns element whose rendered view deletes follow request when processed' do
@@ -1452,7 +1463,7 @@ RSpec.describe OStatus::AtomSerializer do
     it 'appends id element with URL for status' do
       status = Fabricate(:status, created_at: '2000-01-01T00:00:00Z')
       object = OStatus::AtomSerializer.new.object(status)
-      expect(object.id.text).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{status.id}:objectType=Status"
+      expect(object.id.text).to eq "https://cb6e6126.ngrok.io/users/#{status.account.to_param}/statuses/#{status.id}"
     end
 
     it 'appends published element with created date' do
@@ -1462,7 +1473,8 @@ RSpec.describe OStatus::AtomSerializer do
     end
 
     it 'appends updated element with updated date' do
-      status = Fabricate(:status, updated_at: '2000-01-01T00:00:00Z')
+      status = Fabricate(:status)
+      status.updated_at = '2000-01-01T00:00:00Z'
       object = OStatus::AtomSerializer.new.object(status)
       expect(object.updated.text).to eq '2000-01-01T00:00:00Z'
     end
@@ -1491,7 +1503,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.object(status)
 
       object_type = entry.nodes.find { |node| node.name == 'activity:object-type' }
-      expect(object_type.text).to eq TagManager::TYPES[:note]
+      expect(object_type.text).to eq OStatus::TagManager::TYPES[:note]
     end
 
     it 'appends activity:verb element with verb' do
@@ -1500,7 +1512,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.object(status)
 
       object_type = entry.nodes.find { |node| node.name == 'activity:verb' }
-      expect(object_type.text).to eq TagManager::VERBS[:post]
+      expect(object_type.text).to eq OStatus::TagManager::VERBS[:post]
     end
 
     it 'appends link element for an alternative' do
@@ -1509,7 +1521,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
@@ -1522,7 +1534,7 @@ RSpec.describe OStatus::AtomSerializer do
       entry = OStatus::AtomSerializer.new.object(reply)
 
       in_reply_to = entry.nodes.find { |node| node.name == 'thr:in-reply-to' }
-      expect(in_reply_to.ref).to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{thread.id}:objectType=Status"
+      expect(in_reply_to.ref).to eq "https://cb6e6126.ngrok.io/users/#{thread.account.to_param}/statuses/#{thread.id}"
       expect(in_reply_to.href).to eq "https://cb6e6126.ngrok.io/@username/#{thread.id}"
     end
 
diff --git a/spec/lib/ostatus/tag_manager_spec.rb b/spec/lib/ostatus/tag_manager_spec.rb
new file mode 100644
index 000000000..31195bae2
--- /dev/null
+++ b/spec/lib/ostatus/tag_manager_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe OStatus::TagManager do
+  describe '#unique_tag' do
+    it 'returns a unique tag' do
+      expect(OStatus::TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status'
+    end
+  end
+
+  describe '#unique_tag_to_local_id' do
+    it 'returns the ID part' do
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12'
+    end
+
+    it 'returns nil if it is not local id' do
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil
+    end
+
+    it 'returns nil if it is not expected type' do
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil
+    end
+
+    it 'returns nil if it does not have object ID' do
+      expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil
+    end
+  end
+
+  describe '#local_id?' do
+    it 'returns true for a local ID' do
+      expect(OStatus::TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true
+    end
+
+    it 'returns false for a foreign ID' do
+      expect(OStatus::TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false
+    end
+  end
+
+  describe '#uri_for' do
+    subject { OStatus::TagManager.instance.uri_for(target) }
+
+    context 'comment object' do
+      let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) }
+
+      it 'returns the unique tag for status' do
+        expect(target.object_type).to eq :comment
+        is_expected.to eq target.uri
+      end
+    end
+
+    context 'note object' do
+      let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: false, thread: nil) }
+
+      it 'returns the unique tag for status' do
+        expect(target.object_type).to eq :note
+        is_expected.to eq target.uri
+      end
+    end
+
+    context 'person object' do
+      let(:target) { Fabricate(:account, username: 'alice') }
+
+      it 'returns the URL for account' do
+        expect(target.object_type).to eq :person
+        is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice'
+      end
+    end
+  end
+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/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb
index 1fae6bec4..5427a2929 100644
--- a/spec/lib/tag_manager_spec.rb
+++ b/spec/lib/tag_manager_spec.rb
@@ -63,23 +63,23 @@ RSpec.describe TagManager do
 
   describe '#local_url?' do
     around do |example|
-      original_local_domain = Rails.configuration.x.local_domain
+      original_web_domain = Rails.configuration.x.web_domain
       example.run
-      Rails.configuration.x.local_domain = original_local_domain
+      Rails.configuration.x.web_domain = original_web_domain
     end
 
     it 'returns true if the normalized string with port is local URL' do
-      Rails.configuration.x.local_domain = 'domain:42'
+      Rails.configuration.x.web_domain = 'domain:42'
       expect(TagManager.instance.local_url?('https://DoMaIn:42/')).to eq true
     end
 
     it 'returns true if the normalized string without port is local URL' do
-      Rails.configuration.x.local_domain = 'domain'
+      Rails.configuration.x.web_domain = 'domain'
       expect(TagManager.instance.local_url?('https://DoMaIn/')).to eq true
     end
 
     it 'returns false for string with irrelevant characters' do
-      Rails.configuration.x.local_domain = 'domain'
+      Rails.configuration.x.web_domain = 'domain'
       expect(TagManager.instance.local_url?('https://domainn/')).to eq false
     end
   end
@@ -120,82 +120,6 @@ RSpec.describe TagManager do
     end
   end
 
-  describe '#unique_tag' do
-    it 'returns a unique tag' do
-      expect(TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status'
-    end
-  end
-
-  describe '#unique_tag_to_local_id' do
-    it 'returns the ID part' do
-      expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12'
-    end
-
-    it 'returns nil if it is not local id' do
-      expect(TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil
-    end
-
-    it 'returns nil if it is not expected type' do
-      expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil
-    end
-
-    it 'returns nil if it does not have object ID' do
-      expect(TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil
-    end
-  end
-
-  describe '#local_id?' do
-    it 'returns true for a local ID' do
-      expect(TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true
-    end
-
-    it 'returns false for a foreign ID' do
-      expect(TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false
-    end
-  end
-
-  describe '#uri_for' do
-    subject { TagManager.instance.uri_for(target) }
-
-    context 'activity object' do
-      let(:target) { Fabricate(:status, reblog: Fabricate(:status)).stream_entry }
-
-      before { target.update!(created_at: '2000-01-01T00:00:00Z') }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :activity
-        is_expected.to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{target.id}:objectType=Status"
-      end
-    end
-
-    context 'comment object' do
-      let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :comment
-        is_expected.to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{target.id}:objectType=Status"
-      end
-    end
-
-    context 'note object' do
-      let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: false, thread: nil) }
-
-      it 'returns the unique tag for status' do
-        expect(target.object_type).to eq :note
-        is_expected.to eq "tag:cb6e6126.ngrok.io,2000-01-01:objectId=#{target.id}:objectType=Status"
-      end
-    end
-
-    context 'person object' do
-      let(:target) { Fabricate(:account, username: 'alice') }
-
-      it 'returns the URL for account' do
-        expect(target.object_type).to eq :person
-        is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice'
-      end
-    end
-  end
-
   describe '#url_for' do
     let(:alice) { Fabricate(:account, username: 'alice') }