Merge remote-tracking branch 'parent/main' into upstream-20231120

This commit is contained in:
KMY 2023-11-20 12:50:02 +09:00
commit abec232dd7
85 changed files with 1314 additions and 458 deletions

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:account_deletion_request) do
account
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:import) do
account
type :following
data { attachment_fixture('imports.txt') }
end

View file

@ -13,14 +13,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("New report for cb6e6126.ngrok.io (##{report.id})")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to eq("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n")
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("New report for cb6e6126.ngrok.io (##{report.id})"))
.and(have_body_text("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n"))
end
end
@ -33,14 +32,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match "#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io"))
.and(have_body_text("#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"))
end
end
@ -53,14 +51,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq("New account up for review on cb6e6126.ngrok.io (#{user.account.username})")
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'The details of the new account are below. You can approve or reject this application.'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject("New account up for review on cb6e6126.ngrok.io (#{user.account.username})"))
.and(have_body_text('The details of the new account are below. You can approve or reject this application.'))
end
end
@ -95,14 +92,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq('New trends up for review on cb6e6126.ngrok.io')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('New trends up for review on cb6e6126.ngrok.io'))
.and(have_body_text('The following items need a review before they can be displayed publicly'))
end
end
@ -114,14 +110,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('New Mastodon versions are available for cb6e6126.ngrok.io!'))
.and(have_body_text('New Mastodon versions have been released, you may want to update!'))
end
end
@ -133,18 +128,16 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en)
end
it 'renders the headers', :aggregate_failures do
expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
expect(mail['Importance'].value).to eq 'high'
expect(mail['Priority'].value).to eq 'urgent'
expect(mail['X-Priority'].value).to eq '1'
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
it 'renders the email' do
expect(mail)
.to be_present
.and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
.and(have_subject('Critical Mastodon updates are available for cb6e6126.ngrok.io!'))
.and(have_body_text('New critical versions of Mastodon have been released, you may want to update as soon as possible!'))
.and(have_header('Importance', 'high'))
.and(have_header('Priority', 'urgent'))
.and(have_header('X-Priority', '1'))
end
end
end

View file

@ -8,24 +8,27 @@ RSpec.describe NotificationMailer do
let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
shared_examples 'headers' do |type, thread|
it 'renders the to and from headers' do
expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>"
expect(mail.from).to eq ['notifications@localhost']
shared_examples 'standard headers' do |type|
it 'renders the email' do
expect(mail)
.to be_present
.and(have_header('To', "#{receiver.account.username} <#{receiver.email}>"))
.and(have_header('List-ID', "<#{type}.alice.cb6e6126.ngrok.io>"))
.and(have_header('List-Unsubscribe', %r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>}))
.and(have_header('List-Unsubscribe', /&type=#{type}/))
.and(have_header('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click'))
.and(deliver_to("#{receiver.account.username} <#{receiver.email}>"))
.and(deliver_from('notifications@localhost'))
end
end
it 'renders the list headers' do
expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>"
expect(mail['List-Unsubscribe'].value).to match(%r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>})
expect(mail['List-Unsubscribe'].value).to match("&type=#{type}")
expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click'
end
if thread
it 'renders the thread headers' do
expect(mail['In-Reply-To'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
expect(mail['References'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
end
shared_examples 'thread headers' do
it 'renders the email with conversation thread headers' do
conversation_header_regex = /<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/
expect(mail)
.to be_present
.and(have_header('In-Reply-To', conversation_header_regex))
.and(have_header('References', conversation_header_regex))
end
end
@ -35,15 +38,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).mention }
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
include_examples 'headers', 'mention', true
include_examples 'standard headers', 'mention'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('You were mentioned by bob')
end
it 'renders the body' do
expect(mail.body.encoded).to match('You were mentioned by bob')
expect(mail.body.encoded).to include 'The body of the foreign status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('You were mentioned by bob'))
.and(have_body_text('You were mentioned by bob'))
.and(have_body_text('The body of the foreign status'))
end
end
@ -53,14 +56,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow }
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
include_examples 'headers', 'follow', false
include_examples 'standard headers', 'follow'
it 'renders the subject' do
expect(mail.subject).to eq('bob is now following you')
end
it 'renders the body' do
expect(mail.body.encoded).to match('bob is now following you')
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob is now following you'))
.and(have_body_text('bob is now following you'))
end
end
@ -70,15 +72,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).favourite }
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
include_examples 'headers', 'favourite', true
include_examples 'standard headers', 'favourite'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('bob favorited your post')
end
it 'renders the body' do
expect(mail.body.encoded).to match('Your post was favorited by bob')
expect(mail.body.encoded).to include 'The body of the own status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob favorited your post'))
.and(have_body_text('Your post was favorited by bob'))
.and(have_body_text('The body of the own status'))
end
end
@ -88,15 +90,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).reblog }
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
include_examples 'headers', 'reblog', true
include_examples 'standard headers', 'reblog'
include_examples 'thread headers'
it 'renders the subject' do
expect(mail.subject).to eq('bob boosted your post')
end
it 'renders the body' do
expect(mail.body.encoded).to match('Your post was boosted by bob')
expect(mail.body.encoded).to include 'The body of the own status'
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('bob boosted your post'))
.and(have_body_text('Your post was boosted by bob'))
.and(have_body_text('The body of the own status'))
end
end
@ -106,14 +108,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow_request }
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
include_examples 'headers', 'follow_request', false
include_examples 'standard headers', 'follow_request'
it 'renders the subject' do
expect(mail.subject).to eq('Pending follower: bob')
end
it 'renders the body' do
expect(mail.body.encoded).to match('bob has requested to follow you')
it 'renders the email' do
expect(mail)
.to be_present
.and(have_subject('Pending follower: bob'))
.and(have_body_text('bob has requested to follow you'))
end
end

View file

@ -10,9 +10,12 @@ describe UserMailer do
it 'renders confirmation instructions' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail.body.encoded).to include Rails.configuration.x.local_domain
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.confirmation_instructions.title')))
.and(have_body_text('spec'))
.and(have_body_text(Rails.configuration.x.local_domain))
end
include_examples 'localized subject',
@ -25,13 +28,17 @@ describe UserMailer do
it 'renders reconfirmation instructions' do
receiver.update!(email: 'new-email@example.com', locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail.body.encoded).to include Rails.configuration.x.local_domain
expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject',
instance: Rails.configuration.x.local_domain,
locale: I18n.default_locale)
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.reconfirmation_instructions.title')))
.and(have_body_text('spec'))
.and(have_body_text(Rails.configuration.x.local_domain))
end
include_examples 'localized subject',
'devise.mailer.confirmation_instructions.subject',
instance: Rails.configuration.x.local_domain
end
describe '#reset_password_instructions' do
@ -39,8 +46,11 @@ describe UserMailer do
it 'renders reset password instructions' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title')
expect(mail.body.encoded).to include 'spec'
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.reset_password_instructions.title')))
.and(have_body_text('spec'))
end
include_examples 'localized subject',
@ -52,7 +62,10 @@ describe UserMailer do
it 'renders password change notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.password_change.title')))
end
include_examples 'localized subject',
@ -64,7 +77,10 @@ describe UserMailer do
it 'renders email change notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.email_changed.title')))
end
include_examples 'localized subject',
@ -77,8 +93,11 @@ describe UserMailer do
it 'renders warning notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)
expect(mail.body.encoded).to include strike.text
expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)))
.and(have_body_text(strike.text))
end
end
@ -88,7 +107,10 @@ describe UserMailer do
it 'renders webauthn credential deleted notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.deleted.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.deleted.title')))
end
include_examples 'localized subject',
@ -103,7 +125,10 @@ describe UserMailer do
it 'renders suspicious sign in notification' do
receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.suspicious_sign_in.explanation')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.suspicious_sign_in.explanation')))
end
include_examples 'localized subject',
@ -115,8 +140,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_approved(receiver, appeal) }
it 'renders appeal_approved notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_approved.title')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_approved.title')))
end
end
@ -125,8 +152,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_rejected(receiver, appeal) }
it 'renders appeal_rejected notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_rejected.title')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_rejected.title')))
end
end
@ -134,8 +163,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_enabled(receiver) }
it 'renders two_factor_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_enabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_enabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_enabled.explanation')))
end
end
@ -143,8 +174,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_disabled(receiver) }
it 'renders two_factor_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_disabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_disabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_disabled.explanation')))
end
end
@ -152,8 +185,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_enabled(receiver) }
it 'renders webauthn_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_enabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_enabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_enabled.explanation')))
end
end
@ -161,8 +196,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_disabled(receiver) }
it 'renders webauthn_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_disabled.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_disabled.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_disabled.explanation')))
end
end
@ -170,8 +207,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) }
it 'renders two_factor_recovery_codes_changed mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')))
end
end
@ -180,8 +219,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_credential_added(receiver, credential) }
it 'renders webauthn_credential_added mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_credential.added.subject')
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_credential.added.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.added.explanation')))
end
end
@ -189,8 +230,10 @@ describe UserMailer do
let(:mail) { described_class.welcome(receiver) }
it 'renders welcome mail' do
expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.welcome.subject')))
.and(have_body_text(I18n.t('user_mailer.welcome.explanation')))
end
end
@ -199,8 +242,10 @@ describe UserMailer do
let(:mail) { described_class.backup_ready(receiver, backup) }
it 'renders backup_ready mail' do
expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject')
expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation')
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.backup_ready.subject')))
.and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
end
end
end

View file

@ -23,12 +23,14 @@ RSpec.describe AccountRelationshipsPresenter do
let(:options) { {} }
it 'sets default maps' do
expect(presenter.following).to eq default_map
expect(presenter.followed_by).to eq default_map
expect(presenter.blocking).to eq default_map
expect(presenter.muting).to eq default_map
expect(presenter.requested).to eq default_map
expect(presenter.domain_blocking).to eq default_map
expect(presenter).to have_attributes(
following: default_map,
followed_by: default_map,
blocking: default_map,
muting: default_map,
requested: default_map,
domain_blocking: default_map
)
end
end

View file

@ -22,9 +22,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'returns followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to contain_exactly(familiar_follower)
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: contain_exactly(familiar_follower)
)
end
context 'when requested account hides followers' do
@ -35,9 +38,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'does not return followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to be_empty
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end
end
@ -49,9 +55,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'does not return followers you follow' do
result = subject.accounts.first
expect(result).to_not be_nil
expect(result.id).to eq requested_accounts.first.id
expect(result.accounts).to be_empty
expect(result)
.to be_present
.and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end
end
end

View file

@ -22,11 +22,13 @@ RSpec.describe StatusRelationshipsPresenter do
let(:options) { {} }
it 'sets default maps' do
expect(presenter.reblogs_map).to eq default_map
expect(presenter.favourites_map).to eq default_map
expect(presenter.bookmarks_map).to eq default_map
expect(presenter.mutes_map).to eq default_map
expect(presenter.pins_map).to eq default_map
expect(presenter).to have_attributes(
reblogs_map: eq(default_map),
favourites_map: eq(default_map),
bookmarks_map: eq(default_map),
mutes_map: eq(default_map),
pins_map: eq(default_map)
)
end
end
@ -80,18 +82,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].keyword_matches).to eq ['banned']
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('banned')
)
)
end
it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].keyword_matches).to eq ['irrelevant']
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('irrelevant')
)
)
end
end
@ -107,18 +121,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].status_matches).to eq [statuses[0].id]
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.first.id)
)
)
end
it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1'
expect(matched_filters[0].status_matches).to eq [statuses[1].reblog_of_id]
expect(matched_filters)
.to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.second.reblog_of_id)
)
)
end
end
end

View file

@ -4,7 +4,6 @@ ENV['RAILS_ENV'] ||= 'test'
# This needs to be defined before Rails is initialized
RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false)
RUN_SEARCH_SPECS = ENV.fetch('RUN_SEARCH_SPECS', false)
if RUN_SYSTEM_SPECS
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
@ -21,6 +20,7 @@ require 'webmock/rspec'
require 'paperclip/matchers'
require 'capybara/rspec'
require 'chewy/rspec'
require 'email_spec/rspec'
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
@ -54,20 +54,28 @@ RSpec.configure do |config|
case type
when :system
!RUN_SYSTEM_SPECS
when :search
!RUN_SEARCH_SPECS
end
}
# By default, skip the elastic search integration specs
config.filter_run_excluding search: true
config.fixture_path = Rails.root.join('spec', 'fixtures')
config.use_transactional_fixtures = true
config.order = 'random'
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
# Set type to `cli` for all CLI specs
config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata|
metadata[:type] = :cli
end
# Set `search` metadata true for all specs in spec/search/
config.define_derived_metadata(file_path: Regexp.new('spec/search/*')) do |metadata|
metadata[:search] = true
end
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :helper
config.include Devise::Test::ControllerHelpers, type: :view

View file

@ -1,53 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'GET /api/v1/accounts/{account_id}' do
it 'returns account entity as 200 OK' do
account = Fabricate(:account)
get "/api/v1/accounts/#{account.id}"
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 404 if account not found' do
get '/api/v1/accounts/1'
aggregate_failures do
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when with token' do
it 'returns account entity as 200 OK if token is valid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 403 if scope of token is invalid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(403)
expect(body_as_json[:error]).to eq('This action is outside the authorized scopes')
end
end
end
end

View file

@ -2,65 +2,108 @@
require 'rails_helper'
RSpec.describe Api::V1::AccountsController do
render_views
describe '/api/v1/accounts' do
let(:user) { Fabricate(:user) }
let(:scopes) { '' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:user) { Fabricate(:user) }
let(:scopes) { '' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
describe 'GET /api/v1/accounts/:id' do
context 'when logged out' do
let(:account) { Fabricate(:account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
it 'returns account entity as 200 OK', :aggregate_failures do
get "/api/v1/accounts/#{account.id}"
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
context 'when the account does not exist' do
it 'returns http not found' do
get '/api/v1/accounts/1'
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when logged in' do
subject do
get "/api/v1/accounts/#{account.id}", headers: headers
end
let(:account) { Fabricate(:account) }
let(:scopes) { 'read:accounts' }
it 'returns account entity as 200 OK', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
end
end
describe 'POST #create' do
let(:app) { Fabricate(:application) }
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
let(:agreement) { nil }
before do
post :create, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
describe 'POST /api/v1/accounts' do
subject do
post '/api/v1/accounts', headers: headers, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
end
let(:client_app) { Fabricate(:application) }
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: client_app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
let(:agreement) { nil }
context 'when given truthy agreement' do
let(:agreement) { 'true' }
it 'creates a user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:access_token]).to_not be_blank
user = User.find_by(email: 'hello@world.tld')
expect(user).to_not be_nil
expect(user.created_by_application_id).to eq app.id
expect(user.created_by_application_id).to eq client_app.id
end
end
context 'when given no agreement' do
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'POST #follow' do
describe 'POST /api/v1/accounts/:id/follow' do
let(:scopes) { 'write:follows' }
let(:my_actor_type) { 'Person' }
let(:lock_follow_from_bot) { false }
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
context 'when posting to an other account' do
subject do
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers
end
before do
other_account.user.settings['lock_follow_from_bot'] = lock_follow_from_bot
other_account.user.save!
user.account.update!(actor_type: my_actor_type)
post :follow, params: { id: other_account.id }
end
context 'with unlocked account' do
let(:locked) { false }
it 'creates a following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
json = body_as_json
@ -78,6 +121,8 @@ RSpec.describe Api::V1::AccountsController do
let(:locked) { true }
it 'creates a follow request relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
json = body_as_json
@ -97,10 +142,14 @@ RSpec.describe Api::V1::AccountsController do
let(:my_actor_type) { 'Service' }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns JSON with following=false and requested=true' do
subject
json = body_as_json
expect(json[:following]).to be false
@ -108,6 +157,8 @@ RSpec.describe Api::V1::AccountsController do
end
it 'creates a follow request relation between user and target user' do
subject
expect(user.account.requested?(other_account)).to be true
end
@ -123,48 +174,53 @@ RSpec.describe Api::V1::AccountsController do
end
it 'changes reblogs option' do
post :follow, params: { id: other_account.id, reblogs: true }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { reblogs: true }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be true
expect(json[:notifying]).to be false
expect(body_as_json).to include({
following: true,
showing_reblogs: true,
notifying: false,
})
end
it 'changes notify option' do
post :follow, params: { id: other_account.id, notify: true }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { notify: true }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be false
expect(json[:notifying]).to be true
expect(body_as_json).to include({
following: true,
showing_reblogs: false,
notifying: true,
})
end
it 'changes languages option' do
post :follow, params: { id: other_account.id, languages: %w(en es) }
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { languages: %w(en es) }
json = body_as_json
expect(json[:following]).to be true
expect(json[:showing_reblogs]).to be false
expect(json[:notifying]).to be false
expect(json[:languages]).to match_array %w(en es)
expect(body_as_json).to include({
following: true,
showing_reblogs: false,
notifying: false,
languages: match_array(%w(en es)),
})
end
end
end
describe 'POST #unfollow' do
describe 'POST /api/v1/accounts/:id/unfollow' do
subject do
post "/api/v1/accounts/#{other_account.id}/unfollow", headers: headers
end
let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :unfollow, params: { id: other_account.id }
end
it 'removes the following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false
end
@ -172,16 +228,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #remove_from_followers' do
describe 'POST /api/v1/accounts/:id/remove_from_followers' do
subject do
post "/api/v1/accounts/#{other_account.id}/remove_from_followers", headers: headers
end
let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
other_account.follow!(user.account)
post :remove_from_followers, params: { id: other_account.id }
end
it 'removes the followed relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.followed_by?(other_account)).to be false
end
@ -189,16 +250,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #block' do
describe 'POST /api/v1/accounts/:id/block' do
subject do
post "/api/v1/accounts/#{other_account.id}/block", headers: headers
end
let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :block, params: { id: other_account.id }
end
it 'creates a blocking relation', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false
expect(user.account.blocking?(other_account)).to be true
@ -207,16 +273,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #unblock' do
describe 'POST /api/v1/accounts/:id/unblock' do
subject do
post "/api/v1/accounts/#{other_account.id}/unblock", headers: headers
end
let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.block!(other_account)
post :unblock, params: { id: other_account.id }
end
it 'removes the blocking relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.blocking?(other_account)).to be false
end
@ -224,16 +295,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute' do
describe 'POST /api/v1/accounts/:id/mute' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id }
end
it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -243,16 +319,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute with notifications set to false' do
describe 'POST /api/v1/accounts/:id/mute with notifications set to false' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { notifications: false }
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id, notifications: false }
end
it 'does not mute notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -262,16 +343,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #mute with nonzero duration set' do
describe 'POST /api/v1/accounts/:id/mute with nonzero duration set' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { duration: 300 }
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.follow!(other_account)
post :mute, params: { id: other_account.id, duration: 300 }
end
it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true
@ -281,16 +367,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
describe 'POST #unmute' do
describe 'POST /api/v1/accounts/:id/unmute' do
subject do
post "/api/v1/accounts/#{other_account.id}/unmute", headers: headers
end
let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') }
before do
user.account.mute!(other_account)
post :unmute, params: { id: other_account.id }
end
it 'removes the muting relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.muting?(other_account)).to be false
end

View file

@ -8,18 +8,12 @@ RSpec.describe 'Account actions' do
let(:scopes) { 'admin:write admin:write:accounts' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) }
before do
allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer)
end
shared_examples 'a successful notification delivery' do
it 'notifies the user about the action taken' do
subject
expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once
expect(mailer).to have_received(:deliver_later!).once
expect { subject }
.to have_enqueued_job(ActionMailer::MailDeliveryJob)
.with('UserMailer', 'warning', 'deliver_now!', args: [User, AccountWarning])
end
end

View file

@ -2,24 +2,26 @@
require 'rails_helper'
RSpec.describe Api::V1::StatusesController do
render_views
let(:user) { Fabricate(:user) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: scopes) }
describe '/api/v1/statuses' do
context 'with an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
let(:user) { Fabricate(:user) }
let(:client_app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: client_app, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/statuses/:id' do
subject do
get "/api/v1/statuses/#{status.id}", headers: headers
end
describe 'GET #show' do
let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:statuses'
it 'returns http success' do
get :show, params: { id: status.id }
subject
expect(response).to have_http_status(200)
end
@ -31,11 +33,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({
expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -55,11 +56,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({
expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -78,11 +78,10 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id }
json = body_as_json
subject
expect(response).to have_http_status(200)
expect(json[:reblog][:filtered][0]).to include({
expect(body_as_json[:reblog][:filtered][0]).to include({
filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s,
title: 'filter1',
@ -94,7 +93,7 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
let!(:thread) { Fabricate(:status, account: user.account, thread: status) }
@ -142,7 +141,8 @@ RSpec.describe Api::V1::StatusesController do
end
it 'returns http success' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context", headers: headers
expect(response).to have_http_status(200)
end
@ -190,15 +190,20 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'POST #create' do
describe 'POST /api/v1/statuses' do
subject do
post '/api/v1/statuses', headers: headers, params: params
end
let(:scopes) { 'write:statuses' }
let(:params) { { status: 'Hello world' } }
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
context 'with a basic status body' do
before do
post :create, params: { status: 'Hello world' }
end
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s
@ -209,22 +214,22 @@ RSpec.describe Api::V1::StatusesController do
let!(:alice) { Fabricate(:account, username: 'alice') }
let!(:bob) { Fabricate(:account, username: 'bob') }
before do
post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] }
end
let(:params) { { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] } }
it 'returns serialized extra accounts in body', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }]
end
end
context 'with missing parameters' do
before do
post :create, params: {}
end
let(:params) { {} }
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
end
@ -234,10 +239,11 @@ RSpec.describe Api::V1::StatusesController do
before do
rate_limiter = RateLimiter.new(user.account, family: :statuses)
300.times { rate_limiter.record! }
post :create, params: { status: 'Hello world' }
end
it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(429)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq '0'
@ -245,29 +251,37 @@ RSpec.describe Api::V1::StatusesController do
end
end
describe 'DELETE #destroy' do
describe 'DELETE /api/v1/statuses/:id' do
subject do
delete "/api/v1/statuses/#{status.id}", headers: headers
end
let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
before do
post :destroy, params: { id: status.id }
end
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
it 'removes the status', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(Status.find_by(id: status.id)).to be_nil
end
end
describe 'PUT #update' do
describe 'PUT /api/v1/statuses/:id' do
subject do
put "/api/v1/statuses/#{status.id}", headers: headers, params: { status: 'I am updated' }
end
let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) }
before do
put :update, params: { id: status.id, status: 'I am updated' }
end
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
it 'updates the status', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(status.reload.text).to eq 'I am updated'
end
@ -275,49 +289,49 @@ RSpec.describe Api::V1::StatusesController do
end
context 'without an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token).and_return(nil)
end
context 'with a private status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
let(:status) { Fabricate(:status, visibility: :private) }
describe 'GET #show' do
describe 'GET /api/v1/statuses/:id' do
it 'returns http unauthorized' do
get :show, params: { id: status.id }
get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(404)
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
before do
Fabricate(:status, account: user.account, thread: status)
Fabricate(:status, thread: status)
end
it 'returns http unauthorized' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(404)
end
end
end
context 'with a public status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :public) }
let(:status) { Fabricate(:status, visibility: :public) }
describe 'GET #show' do
describe 'GET /api/v1/statuses/:id' do
it 'returns http success' do
get :show, params: { id: status.id }
get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(200)
end
end
describe 'GET #context' do
describe 'GET /api/v1/statuses/:id/context' do
before do
Fabricate(:status, account: user.account, thread: status)
Fabricate(:status, thread: status)
end
it 'returns http success' do
get :context, params: { id: status.id }
get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(200)
end
end

View file

@ -60,7 +60,7 @@ RSpec.configure do |config|
end
end
config.around :each, type: :search do |example|
config.around :each, :search do |example|
search_data_manager.populate_indexes
example.run
search_data_manager.remove_indexes
@ -73,6 +73,6 @@ RSpec.configure do |config|
end
def search_examples_present?
RUN_SEARCH_SPECS
RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[:search] == true }
end
end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'rails_helper'
describe AccountRefreshWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ResolveAccountService, call: true) }
describe '#perform' do
before do
allow(ResolveAccountService).to receive(:new).and_return(service)
end
context 'when account does not exist' do
it 'returns immediately without processing' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
context 'when account exists' do
context 'when account does not need refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: recent_webfinger_at) }
it 'returns immediately without processing' do
worker.perform(account.id)
expect(service).to_not have_received(:call)
end
end
context 'when account needs refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: outdated_webfinger_at) }
it 'schedules an account update' do
worker.perform(account.id)
expect(service).to have_received(:call)
end
end
def recent_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL - 3.days).ago
end
def outdated_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL + 3.days).ago
end
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::PostUpgradeWorker do
let(:worker) { described_class.new }
describe '#perform' do
let(:domain) { 'host.example' }
it 'updates relevant values' do
account = Fabricate(:account, domain: domain, last_webfingered_at: 1.day.ago, protocol: :ostatus)
worker.perform(domain)
expect(account.reload.last_webfingered_at).to be_nil
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::SynchronizeFeaturedTagsCollectionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ActivityPub::FetchFeaturedTagsCollectionService, call: true) }
describe '#perform' do
before do
allow(ActivityPub::FetchFeaturedTagsCollectionService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:url) { 'https://host.example' }
it 'sends the account and url to the service' do
worker.perform(account.id, url)
expect(service).to have_received(:call).with(account, url)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, url)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::SuspensionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(SuspendAccountService, call: true) }
describe '#perform' do
before do
allow(SuspendAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
it 'sends the account to the service' do
worker.perform(account.id)
expect(service).to have_received(:call).with(account)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe AfterAccountDomainBlockWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(AfterBlockDomainFromAccountService, call: true) }
describe '#perform' do
before do
allow(AfterBlockDomainFromAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:domain) { 'host.example' }
it 'sends the account and domain to the service' do
worker.perform(account.id, domain)
expect(service).to have_received(:call).with(account, domain)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, domain)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'rails_helper'
describe BackupWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(BackupService, call: true) }
describe '#perform' do
before do
allow(BackupService).to receive(:new).and_return(service)
end
let(:backup) { Fabricate(:backup) }
let!(:other_backup) { Fabricate(:backup, user: backup.user) }
it 'sends the backup to the service and removes other backups' do
expect do
worker.perform(backup.id)
end.to change(UserMailer.deliveries, :size).by(1)
expect(service).to have_received(:call).with(backup)
expect { other_backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
context 'when sidekiq retries are exhausted' do
it 'destroys the backup' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [backup.id] }) do
worker.perform(backup.id)
end
expect { backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
describe DeleteMuteWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(UnmuteService, call: true) }
describe '#perform' do
before do
allow(UnmuteService).to receive(:new).and_return(service)
end
context 'with an expired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.ago) }
it 'sends the mute to the service' do
worker.perform(mute.id)
expect(service).to have_received(:call).with(mute.account, mute.target_account)
end
end
context 'with an unexpired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.from_now) }
it 'does not send the mute to the service' do
worker.perform(mute.id)
expect(service).to_not have_received(:call)
end
end
context 'with a non-existent mute' do
it 'does not send the mute to the service' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
end
end

View file

@ -12,6 +12,7 @@ describe FeedInsertWorker do
describe 'perform' do
let(:follower) { Fabricate(:account) }
let(:status) { Fabricate(:status) }
let(:list) { Fabricate(:list) }
context 'when there are no records' do
it 'skips push with missing status' do
@ -46,11 +47,29 @@ describe FeedInsertWorker do
it 'pushes the status onto the home timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id)
result = subject.perform(status.id, follower.id, :home)
expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end
it 'pushes the status onto the tags timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id, :tags)
expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end
it 'pushes the status onto the list timeline without filter' do
instance = instance_double(FeedManager, push_to_list: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, list.id, :list)
expect(result).to be_nil
expect(instance).to have_received(:push_to_list).with(list, status, update: nil)
end
end
context 'with notification' do

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'rails_helper'
describe ImportWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ImportService, call: true) }
describe '#perform' do
before do
allow(ImportService).to receive(:new).and_return(service)
end
let(:import) { Fabricate(:import) }
it 'sends the import to the service' do
worker.perform(import.id)
expect(service).to have_received(:call).with(import)
expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end

View file

@ -2,12 +2,38 @@
require 'rails_helper'
describe PostProcessMediaWorker do
describe PostProcessMediaWorker, :paperclip_processing do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
let(:media_attachment) { Fabricate(:media_attachment) }
it 'reprocesses and updates the media attachment' do
worker.perform(media_attachment.id)
expect(media_attachment.processing).to eq('complete')
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
context 'when sidekiq retries are exhausted' do
it 'sets state to failed' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [media_attachment.id] }) do
worker.perform(media_attachment.id)
end
expect(media_attachment.reload.processing).to eq('failed')
end
it 'returns true for non-existent record' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [123_123_123] }) do
expect(worker.perform(123_123_123)).to be(true)
end
end
end
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
describe PublishAnnouncementReactionWorker do
let(:worker) { described_class.new }
describe '#perform' do
before { Fabricate(:account, user: Fabricate(:user, current_sign_in_at: 1.hour.ago)) }
let(:announcement) { Fabricate(:announcement) }
let(:name) { 'name value' }
it 'sends the announcement and name to the service when subscribed' do
allow(redis).to receive(:exists?).and_return(true)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to have_received(:publish)
end
it 'does not send the announcement and name to the service when not subscribed' do
allow(redis).to receive(:exists?).and_return(false)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to_not have_received(:publish)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, name)
expect(result).to be(true)
end
end
end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadAvatarWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an avatar remote url' do
account = Fabricate(:account, avatar_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when avatar file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file', avatar_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote avatar' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file')
account.update_column(:avatar_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.avatar_file_name).to_not be_nil
end
end
end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadHeaderWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error for missing record' do
expect { worker.perform(nil) }.to_not raise_error
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an header remote url' do
account = Fabricate(:account, header_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when header file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file', header_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote header' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file')
account.update_column(:header_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.header_file_name).to_not be_nil
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'rails_helper'
describe RedownloadMediaWorker do
let(:worker) { described_class.new }
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil without a remote_url' do
media_attachment = Fabricate(:media_attachment, remote_url: '')
result = worker.perform(media_attachment.id)
expect(result).to be_nil
end
context 'with a valid remote url' do
let(:url) { 'https://example.host/file.txt' }
before { stub_request(:get, url).to_return(status: 200) }
it 'processes downloads for valid record' do
media_attachment = Fabricate(:media_attachment, remote_url: url)
worker.perform(media_attachment.id)
expect(a_request(:get, url)).to have_been_made
end
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe RemovalWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(RemoveStatusService, call: true) }
describe '#perform' do
before do
allow(RemoveStatusService).to receive(:new).and_return(service)
end
let(:status) { Fabricate(:status) }
it 'sends the status to the service' do
worker.perform(status.id)
expect(service).to have_received(:call).with(status)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'rails_helper'
describe Scheduler::SelfDestructScheduler do
let(:worker) { described_class.new }
describe '#perform' do
let!(:account) { Fabricate(:account, domain: nil, suspended_at: nil) }
context 'when not in self destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(false)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when in self-destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(true)
end
context 'when sidekiq is overwhelmed' do
before do
stats = instance_double(Sidekiq::Stats, enqueued: described_class::MAX_ENQUEUED**2)
allow(Sidekiq::Stats).to receive(:new).and_return(stats)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when sidekiq is operational' do
it 'suspends local non-suspended accounts' do
worker.perform
expect(account.reload.suspended_at).to_not be_nil
end
it 'suspends local suspended accounts marked for deletion' do
account.update(suspended_at: 10.days.ago)
deletion_request = Fabricate(:account_deletion_request, account: account)
worker.perform
expect(account.reload.suspended_at).to be > 1.day.ago
expect { deletion_request.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
end

View file

@ -5,9 +5,21 @@ require 'rails_helper'
describe Webhooks::DeliveryWorker do
let(:worker) { described_class.new }
describe 'perform' do
it 'runs without error' do
expect { worker.perform(nil, nil) }.to_not raise_error
describe '#perform' do
let(:webhook) { Fabricate(:webhook) }
it 'reprocesses and updates the webhook' do
stub_request(:post, webhook.url).to_return(status: 200, body: '')
worker.perform(webhook.id, 'body')
expect(a_request(:post, webhook.url)).to have_been_made.at_least_once
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, '')
expect(result).to be(true)
end
end
end