diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index b8179f7dccfc9ca63389d001f75a57f836582e0d..221aa42a3896ff82beb3bf6126b590bdbc252a8f 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -37,11 +37,11 @@ class PostStatusService < BaseService
   def validate_media!(media_ids)
     return if media_ids.nil? || !media_ids.is_a?(Enumerable)
 
-    raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4
+    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4
 
     media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
 
-    raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?)
+    raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?)
 
     media
   end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 742219df99f7d84056f935621a33b2db49b9f5c6..aa3a732f96b967fda38df3ad9bbb5217c73430ed 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -163,3 +163,7 @@ en:
     invalid_otp_token: Invalid two-factor code
   will_paginate:
     page_gap: "&hellip;"
+  media_attachments:
+    validations:
+      too_many: Cannot attach more than 4 files
+      images_and_video: Cannot attach a video to a status that already contains images
diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb
index 59db2440d6ae5584912450b6b4126b9bc92113f2..dc91d708f3347d946688947506520ad6844415ed 100644
--- a/spec/fabricators/media_attachment_fabricator.rb
+++ b/spec/fabricators/media_attachment_fabricator.rb
@@ -1,3 +1,3 @@
 Fabricator(:media_attachment) do
-
+  account
 end
diff --git a/spec/fabricators/status_fabricator.rb b/spec/fabricators/status_fabricator.rb
index df222fc9d2cf6cafd41a47b422ef3b49f3388c27..8ec5f4ba7989c060eb995e47084f0cba6302cc45 100644
--- a/spec/fabricators/status_fabricator.rb
+++ b/spec/fabricators/status_fabricator.rb
@@ -1,3 +1,4 @@
 Fabricator(:status) do
+  account
   text "Lorem ipsum dolor sit amet"
 end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 9ee4daf6f5f2e2fde4e60ef5b9cf79550b8ccfca..0e39cd969a8d0c0e9b6619f36e80c3a9d885c321 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -3,8 +3,168 @@ require 'rails_helper'
 RSpec.describe PostStatusService do
   subject { PostStatusService.new }
 
-  it 'creates a new status'
-  it 'creates a new response status'
-  it 'processes mentions'
-  it 'pings PuSH hubs'
+  it 'creates a new status' do
+    account = Fabricate(:account)
+    text = "test status update"
+
+    status = subject.call(account, text)
+
+    expect(status).to be_persisted
+    expect(status.text).to eq text
+  end
+
+  it 'creates a new response status' do
+    in_reply_to_status = Fabricate(:status)
+    account = Fabricate(:account)
+    text = "test status update"
+
+    status = subject.call(account, text, in_reply_to_status)
+
+    expect(status).to be_persisted
+    expect(status.text).to eq text
+    expect(status.thread).to eq in_reply_to_status
+  end
+
+  it 'creates a sensitive status' do
+    status = create_status_with_options(sensitive: true)
+
+    expect(status).to be_persisted
+    expect(status).to be_sensitive
+  end
+
+  it 'creates a status with spoiler text' do
+    spoiler_text = "spoiler text"
+
+    status = create_status_with_options(spoiler_text: spoiler_text)
+
+    expect(status).to be_persisted
+    expect(status.spoiler_text).to eq spoiler_text
+  end
+
+  it 'creates a status with empty default spoiler text' do
+    status = create_status_with_options(spoiler_text: nil)
+
+    expect(status).to be_persisted
+    expect(status.spoiler_text).to eq ''
+  end
+
+  it 'creates a status with the given visibility' do
+    status = create_status_with_options(visibility: :private)
+
+    expect(status).to be_persisted
+    expect(status.visibility).to eq "private"
+  end
+
+  it 'creates a status for the given application' do
+    application = Fabricate(:application)
+
+    status = create_status_with_options(application: application)
+
+    expect(status).to be_persisted
+    expect(status.application).to eq application
+  end
+
+  it 'processes mentions' do
+    mention_service = double(:process_mentions_service)
+    allow(mention_service).to receive(:call)
+    allow(ProcessMentionsService).to receive(:new).and_return(mention_service)
+    account = Fabricate(:account)
+
+    status = subject.call(account, "test status update")
+
+    expect(ProcessMentionsService).to have_received(:new)
+    expect(mention_service).to have_received(:call).with(status)
+  end
+
+  it 'processes hashtags' do
+    hashtags_service = double(:process_hashtags_service)
+    allow(hashtags_service).to receive(:call)
+    allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service)
+    account = Fabricate(:account)
+
+    status = subject.call(account, "test status update")
+
+    expect(ProcessHashtagsService).to have_received(:new)
+    expect(hashtags_service).to have_received(:call).with(status)
+  end
+
+  it 'pings PuSH hubs' do
+    allow(DistributionWorker).to receive(:perform_async)
+    allow(Pubsubhubbub::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)
+  end
+
+  it 'crawls links' do
+    allow(LinkCrawlWorker).to receive(:perform_async)
+    account = Fabricate(:account)
+
+    status = subject.call(account, "test status update")
+
+    expect(LinkCrawlWorker).to have_received(:perform_async).with(status.id)
+  end
+
+  it 'attaches the given media to the created status' do
+    account = Fabricate(:account)
+    media = Fabricate(:media_attachment)
+
+    status = subject.call(
+      account,
+      "test status update",
+      nil,
+      media_ids: [media.id],
+    )
+
+    expect(media.reload.status).to eq status
+  end
+
+  it 'does not allow attaching more than 4 files' do
+    account = Fabricate(:account)
+
+    expect do
+      subject.call(
+        account,
+        "test status update",
+        nil,
+        media_ids: [
+          Fabricate(:media_attachment, account: account),
+          Fabricate(:media_attachment, account: account),
+          Fabricate(:media_attachment, account: account),
+          Fabricate(:media_attachment, account: account),
+          Fabricate(:media_attachment, account: account),
+        ].map(&:id),
+      )
+    end.to raise_error(
+      Mastodon::ValidationError,
+      I18n.t('media_attachments.validations.too_many'),
+    )
+  end
+
+  it 'does not allow attaching both videos and images' do
+    account = Fabricate(:account)
+
+    expect do
+      subject.call(
+        account,
+        "test status update",
+        nil,
+        media_ids: [
+          Fabricate(:media_attachment, type: :video, account: account),
+          Fabricate(:media_attachment, type: :image, account: account),
+        ].map(&:id),
+      )
+    end.to raise_error(
+      Mastodon::ValidationError,
+      I18n.t('media_attachments.validations.images_and_video'),
+    )
+  end
+
+  def create_status_with_options(options = {})
+    subject.call(Fabricate(:account), "test", nil, options)
+  end
 end