mastodon-glitch/spec/services/post_status_service_spec.rb

305 lines
9.5 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe PostStatusService do
subject { described_class.new }
it 'creates a new status' do
account = Fabricate(:account)
text = 'test status update'
status = subject.call(account, text: 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: text, thread: 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
context 'when scheduling a status' do
let!(:account) { Fabricate(:account) }
let!(:future) { Time.now.utc + 2.hours }
let!(:previous_status) { Fabricate(:status, account: account) }
it 'schedules a status for future creation and does not create one immediately' do
media = Fabricate(:media_attachment, account: account)
status = subject.call(account, text: 'Hi future!', media_ids: [media.id.to_s], scheduled_at: future)
expect(status)
.to be_a(ScheduledStatus)
.and have_attributes(
scheduled_at: eq(future),
params: include(
'text' => eq('Hi future!'),
'media_ids' => contain_exactly(media.id.to_s)
)
)
expect(media.reload.status).to be_nil
expect(Status.where(text: 'Hi future!')).to_not exist
end
it 'does not change statuses_count of account or replies_count of thread previous status' do
expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }
.to not_change { account.statuses_count }
.and(not_change { previous_status.replies_count })
end
it 'returns existing status when used twice with idempotency key' do
account = Fabricate(:account)
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
expect(status2.id).to eq status1.id
end
context 'when scheduled_at is less than min offset' do
let(:invalid_scheduled_time) { 4.minutes.from_now }
it 'raises invalid record error' do
expect do
subject.call(account, text: 'Hi future!', scheduled_at: invalid_scheduled_time)
end.to raise_error(
ActiveRecord::RecordInvalid,
'Validation failed: Scheduled at The scheduled date must be in the future'
)
end
end
end
it 'creates response to the original status of boost' do
boosted_status = Fabricate(:status)
in_reply_to_status = Fabricate(:status, reblog: boosted_status)
account = Fabricate(:account)
text = 'test status update'
status = subject.call(account, text: text, thread: in_reply_to_status)
expect(status).to be_persisted
expect(status.text).to eq text
expect(status.thread).to eq boosted_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 sensitive status when there is a CW but no text' do
status = subject.call(Fabricate(:account), text: '', spoiler_text: 'foo')
expect(status).to be_persisted
expect(status).to be_sensitive
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 'raises on an invalid visibility' do
expect do
create_status_with_options(visibility: :xxx)
end.to raise_error(
ActiveRecord::RecordInvalid,
'Validation failed: Visibility is not included in the list'
)
end
it 'creates a status with limited visibility for silenced users' do
status = subject.call(Fabricate(:account, silenced: true), text: 'test', visibility: :public)
expect(status).to be_persisted
expect(status.visibility).to eq 'unlisted'
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 'creates a status with a language set' do
account = Fabricate(:account)
text = 'This is an English text.'
status = subject.call(account, text: text)
expect(status.language).to eq 'en'
end
it 'processes mentions' do
mention_service = instance_double(ProcessMentionsService)
allow(mention_service).to receive(:call)
allow(ProcessMentionsService).to receive(:new).and_return(mention_service)
account = Fabricate(:account)
status = subject.call(account, text: 'test status update')
expect(ProcessMentionsService).to have_received(:new)
expect(mention_service).to have_received(:call).with(status, save_records: false)
end
it 'safeguards mentions' do
account = Fabricate(:account)
mentioned_account = Fabricate(:account, username: 'alice')
unexpected_mentioned_account = Fabricate(:account, username: 'bob')
expect do
subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id])
end.to raise_error(an_instance_of(described_class::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
end
it 'processes duplicate mentions correctly' do
account = Fabricate(:account)
Fabricate(:account, username: 'alice')
expect do
subject.call(account, text: '@alice @alice @alice hey @alice')
end.to_not raise_error
end
it 'processes hashtags' do
hashtags_service = instance_double(ProcessHashtagsService)
allow(hashtags_service).to receive(:call)
allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service)
account = Fabricate(:account)
status = subject.call(account, text: 'test status update')
expect(ProcessHashtagsService).to have_received(:new)
expect(hashtags_service).to have_received(:call).with(status)
end
it 'gets distributed' do
allow(DistributionWorker).to receive(:perform_async)
allow(ActivityPub::DistributionWorker).to receive(:perform_async)
account = Fabricate(:account)
status = subject.call(account, text: 'test status update')
expect(DistributionWorker).to have_received(:perform_async).with(status.id)
expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id)
end
it 'crawls links' do
allow(LinkCrawlWorker).to receive(:perform_async)
account = Fabricate(:account)
status = subject.call(account, text: '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, account: account)
status = subject.call(
account,
text: 'test status update',
media_ids: [media.id.to_s]
)
expect(media.reload.status).to eq status
end
it 'does not attach media from another account to the created status' do
account = Fabricate(:account)
media = Fabricate(:media_attachment, account: Fabricate(:account))
expect do
subject.call(
account,
text: 'test status update',
media_ids: [media.id.to_s]
)
end.to raise_error(
Mastodon::ValidationError,
I18n.t('media_attachments.validations.not_found', ids: media.id)
)
end
it 'does not allow attaching more files than configured limit' do
stub_const('Status::MEDIA_ATTACHMENTS_LIMIT', 1)
account = Fabricate(:account)
expect do
subject.call(
account,
text: 'test status update',
media_ids: Array.new(2) { Fabricate(:media_attachment, account: account) }.map { |m| m.id.to_s }
)
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)
video = Fabricate(:media_attachment, type: :video, account: account)
image = Fabricate(:media_attachment, type: :image, account: account)
video.update(type: :video)
expect do
subject.call(
account,
text: 'test status update',
media_ids: [
video,
image,
].map { |m| m.id.to_s }
)
end.to raise_error(
Mastodon::ValidationError,
I18n.t('media_attachments.validations.images_and_video')
)
end
it 'returns existing status when used twice with idempotency key' do
account = Fabricate(:account)
status1 = subject.call(account, text: 'test', idempotency: 'meepmeep')
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep')
expect(status2.id).to eq status1.id
end
def create_status_with_options(**options)
subject.call(Fabricate(:account), options.merge(text: 'test'))
end
end