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

This commit is contained in:
KMY 2024-03-19 09:15:20 +09:00
commit 76598bd542
496 changed files with 5795 additions and 3709 deletions

View file

@ -3,10 +3,6 @@
require 'rails_helper'
describe Api::BaseController do
before do
stub_const('FakeService', Class.new)
end
controller do
def success
head 200
@ -72,36 +68,4 @@ describe Api::BaseController do
expect(response).to have_http_status(403)
end
end
describe 'error handling' do
before do
routes.draw { get 'failure' => 'api/base#failure' }
end
{
ActiveRecord::RecordInvalid => 422,
ActiveRecord::RecordNotFound => 404,
ActiveRecord::RecordNotUnique => 422,
Date::Error => 422,
HTTP::Error => 503,
Mastodon::InvalidParameterError => 400,
Mastodon::NotPermittedError => 403,
Mastodon::RaceConditionError => 503,
Mastodon::RateLimitExceededError => 429,
Mastodon::UnexpectedResponseError => 503,
Mastodon::ValidationError => 422,
OpenSSL::SSL::SSLError => 503,
Seahorse::Client::NetworkingError => 503,
Stoplight::Error::RedLight => 503,
}.each do |error, code|
it "Handles error class of #{error}" do
allow(FakeService).to receive(:new).and_raise(error)
get :failure
expect(response).to have_http_status(code)
expect(FakeService).to have_received(:new)
end
end
end
end

View file

@ -1,105 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::CredentialsController do
render_views
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
context 'with an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #show' do
let(:scopes) { 'read:accounts' }
it 'returns http success' do
get :show
expect(response).to have_http_status(200)
end
end
describe 'PATCH #update' do
let(:scopes) { 'write:accounts' }
describe 'with valid data' do
before do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
patch :update, params: {
display_name: "Alice Isn't Dead",
note: "Hi!\n\nToot toot!",
avatar: fixture_file_upload('avatar.gif', 'image/gif'),
header: fixture_file_upload('attachment.jpg', 'image/jpeg'),
source: {
privacy: 'unlisted',
sensitive: true,
},
}
end
it 'updates account info', :aggregate_failures do
expect(response).to have_http_status(200)
user.reload
user.account.reload
expect(user.account.display_name).to eq("Alice Isn't Dead")
expect(user.account.note).to eq("Hi!\n\nToot toot!")
expect(user.account.avatar).to exist
expect(user.account.header).to exist
expect(user.setting_default_privacy).to eq('unlisted')
expect(user.setting_default_sensitive).to be(true)
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id)
end
end
describe 'with empty source list' do
before do
patch :update, params: {
display_name: "I'm a cat",
source: {},
}, as: :json
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'with invalid data' do
before do
patch :update, params: { note: 'This is too long. ' * 30 }
end
it 'returns http unprocessable entity' do
expect(response).to have_http_status(422)
end
end
end
end
context 'without an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token).and_return(nil)
end
describe 'GET #show' do
it 'returns http unauthorized' do
get :show
expect(response).to have_http_status(401)
end
end
describe 'PATCH #update' do
it 'returns http unauthorized' do
patch :update, params: { note: 'Foo' }
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'rails_helper'
describe Api::ErrorHandling do
before do
stub_const('FakeService', Class.new)
end
controller(Api::BaseController) do
def failure
FakeService.new
end
end
describe 'error handling' do
before do
routes.draw { get 'failure' => 'api/base#failure' }
end
{
ActiveRecord::RecordInvalid => 422,
ActiveRecord::RecordNotFound => 404,
ActiveRecord::RecordNotUnique => 422,
Date::Error => 422,
HTTP::Error => 503,
Mastodon::InvalidParameterError => 400,
Mastodon::NotPermittedError => 403,
Mastodon::RaceConditionError => 503,
Mastodon::RateLimitExceededError => 429,
Mastodon::UnexpectedResponseError => 503,
Mastodon::ValidationError => 422,
OpenSSL::SSL::SSLError => 503,
Seahorse::Client::NetworkingError => 503,
Stoplight::Error::RedLight => 503,
}.each do |error, code|
it "Handles error class of #{error}" do
allow(FakeService)
.to receive(:new)
.and_raise(error)
get :failure
expect(response)
.to have_http_status(code)
expect(FakeService)
.to have_received(:new)
end
end
end
end

View file

@ -41,6 +41,14 @@ RSpec.describe InstanceActorsController do
it_behaves_like 'shared behavior'
end
context 'with a suspended instance actor' do
let(:authorized_fetch_mode) { false }
before { Account.representative.update(suspended_at: 10.days.ago) }
it_behaves_like 'shared behavior'
end
end
end
end

View file

@ -61,15 +61,11 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('auth.login'))
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
click_on I18n.t('auth.login')
fill_in_auth_details(email, 'wrong password')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
@ -88,15 +84,11 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('auth.login'))
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
click_on I18n.t('auth.login')
fill_in_auth_details(email, 'wrong password')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to an authorization page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
@ -118,25 +110,19 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('auth.login'))
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
click_on I18n.t('auth.login')
fill_in_auth_details(email, 'wrong password')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong'
click_on I18n.t('auth.login')
fill_in_otp_details('wrong')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp
click_on I18n.t('auth.login')
fill_in_otp_details(user.current_otp)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon authorizing, it redirects to the apps' callback URL
@ -155,25 +141,19 @@ describe 'Using OAuth from an external app' do
expect(page).to have_content(I18n.t('auth.login'))
# Failing to log-in presents the form again
fill_in 'user_email', with: email
fill_in 'user_password', with: 'wrong password'
click_on I18n.t('auth.login')
fill_in_auth_details(email, 'wrong password')
expect(page).to have_content(I18n.t('auth.login'))
# Logging in redirects to a two-factor authentication page
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
fill_in_auth_details(email, password)
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in an incorrect two-factor authentication code presents the form again
fill_in 'user_otp_attempt', with: 'wrong'
click_on I18n.t('auth.login')
fill_in_otp_details('wrong')
expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp'))
# Filling in the correct TOTP code redirects to an app authorization page
fill_in 'user_otp_attempt', with: user.current_otp
click_on I18n.t('auth.login')
fill_in_otp_details(user.current_otp)
expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize'))
# Upon denying, it redirects to the apps' callback URL
@ -185,6 +165,19 @@ describe 'Using OAuth from an external app' do
end
end
private
def fill_in_auth_details(email, password)
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
end
def fill_in_otp_details(value)
fill_in 'user_otp_attempt', with: value
click_on I18n.t('auth.login')
end
# TODO: external auth
end
end

View file

@ -3,30 +3,6 @@
require 'rails_helper'
describe ApplicationHelper do
describe 'active_nav_class' do
it 'returns active when on the current page' do
allow(helper).to receive(:current_page?).and_return(true)
result = helper.active_nav_class('/test')
expect(result).to eq 'active'
end
it 'returns active when on a current page' do
allow(helper).to receive(:current_page?).with('/foo').and_return(false)
allow(helper).to receive(:current_page?).with('/test').and_return(true)
result = helper.active_nav_class('/foo', '/test')
expect(result).to eq 'active'
end
it 'returns empty string when not on current page' do
allow(helper).to receive(:current_page?).and_return(false)
result = helper.active_nav_class('/test')
expect(result).to eq ''
end
end
describe 'body_classes' do
context 'with a body class string from a controller' do
before { helper.extend controller_helpers }
@ -103,36 +79,6 @@ describe ApplicationHelper do
end
end
describe 'show_landing_strip?', :without_verify_partial_doubles do
describe 'when signed in' do
before do
allow(helper).to receive(:user_signed_in?).and_return(true)
end
it 'does not show landing strip' do
expect(helper.show_landing_strip?).to be false
end
end
describe 'when signed out' do
before do
allow(helper).to receive(:user_signed_in?).and_return(false)
end
it 'does not show landing strip on single user instance' do
allow(helper).to receive(:single_user_mode?).and_return(true)
expect(helper.show_landing_strip?).to be false
end
it 'shows landing strip on multi user instance' do
allow(helper).to receive(:single_user_mode?).and_return(false)
expect(helper.show_landing_strip?).to be true
end
end
end
describe 'available_sign_up_path' do
context 'when registrations are closed' do
before do

View file

@ -29,26 +29,6 @@ describe StatusesHelper do
I18n.t('statuses.content_warning', warning: status.spoiler_text)
end
describe 'link_to_newer' do
it 'returns a link to newer content' do
url = 'https://example.com'
result = helper.link_to_newer(url)
expect(result).to match('load-more')
expect(result).to match(I18n.t('statuses.show_newer'))
end
end
describe 'link_to_older' do
it 'returns a link to older content' do
url = 'https://example.com'
result = helper.link_to_older(url)
expect(result).to match('load-more')
expect(result).to match(I18n.t('statuses.show_older'))
end
end
describe 'fa_visibility_icon' do
context 'with a status that is public' do
let(:status) { Status.new(visibility: 'public') }

View file

@ -50,7 +50,7 @@ RSpec.describe ActivityPub::Activity::Add do
end
it 'fetches the status and pins it' do
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, **|
expect(uri).to eq 'https://example.com/unknown'
expect(id).to be true
expect(on_behalf_of&.following?(sender)).to be true
@ -64,7 +64,7 @@ RSpec.describe ActivityPub::Activity::Add do
context 'when there is no local follower' do
it 'tries to fetch the status' do
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument
allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, **|
expect(uri).to eq 'https://example.com/unknown'
expect(id).to be true
expect(on_behalf_of).to be_nil

View file

@ -11,7 +11,7 @@ RSpec.describe FeedManager do
end
it 'tracks at least as many statuses as reblogs', :skip_stub do
expect(FeedManager::REBLOG_FALLOFF).to be <= FeedManager::MAX_ITEMS
expect(described_class::REBLOG_FALLOFF).to be <= described_class::MAX_ITEMS
end
describe '#key' do
@ -262,12 +262,12 @@ RSpec.describe FeedManager do
it 'trims timelines if they will have more than FeedManager::MAX_ITEMS' do
account = Fabricate(:account)
status = Fabricate(:status)
members = Array.new(FeedManager::MAX_ITEMS) { |count| [count, count] }
members = Array.new(described_class::MAX_ITEMS) { |count| [count, count] }
redis.zadd("feed:home:#{account.id}", members)
described_class.instance.push_to_home(account, status)
expect(redis.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS
expect(redis.zcard("feed:home:#{account.id}")).to eq described_class::MAX_ITEMS
end
context 'with reblogs' do
@ -297,7 +297,7 @@ RSpec.describe FeedManager do
described_class.instance.push_to_home(account, reblogged)
# Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do
described_class::REBLOG_FALLOFF.times do
described_class.instance.push_to_home(account, Fabricate(:status))
end
@ -358,7 +358,7 @@ RSpec.describe FeedManager do
described_class.instance.push_to_home(account, reblogs.first)
# Fill the feed with intervening statuses
FeedManager::REBLOG_FALLOFF.times do
described_class::REBLOG_FALLOFF.times do
described_class.instance.push_to_home(account, Fabricate(:status))
end
@ -504,7 +504,7 @@ RSpec.describe FeedManager do
status = Fabricate(:status, reblog: reblogged)
described_class.instance.push_to_home(receiver, reblogged)
FeedManager::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
described_class::REBLOG_FALLOFF.times { described_class.instance.push_to_home(receiver, Fabricate(:status)) }
described_class.instance.push_to_home(receiver, status)
# The reblogging status should show up under normal conditions.

View file

@ -15,6 +15,23 @@ describe Mastodon::CLI::Domains do
describe '#purge' do
let(:action) { :purge }
context 'with invalid limited federation mode argument' do
let(:arguments) { ['example.host'] }
let(:options) { { limited_federation_mode: true } }
it 'warns about usage and exits' do
expect { subject }
.to raise_error(Thor::Error, /DOMAIN parameter not supported/)
end
end
context 'without a domains argument' do
it 'warns about usage and exits' do
expect { subject }
.to raise_error(Thor::Error, 'No domain(s) given')
end
end
context 'with accounts from the domain' do
let(:domain) { 'host.example' }
let!(:account) { Fabricate(:account, domain: domain) }

View file

@ -33,6 +33,17 @@ describe Mastodon::CLI::Search do
end
end
context 'when server communication raises an error' do
let(:options) { { reset_chewy: true } }
before { allow(Chewy::Stash::Specification).to receive(:reset!).and_raise(Elasticsearch::Transport::Transport::Errors::InternalServerError) }
it 'Exits with error message' do
expect { subject }
.to raise_error(Thor::Error, /issue connecting to the search/)
end
end
context 'without options' do
before { stub_search_indexes }

View file

@ -4,7 +4,7 @@ require 'rails_helper'
describe Sanitize::Config do
describe '::MASTODON_STRICT' do
subject { Sanitize::Config::MASTODON_STRICT }
subject { described_class::MASTODON_STRICT }
it 'converts h1 to p strong' do
expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<p><strong>Foo</strong></p>'

View file

@ -27,7 +27,7 @@ RSpec.describe SignatureParser do
let(:header) { 'hello this is malformed!' }
it 'raises an error' do
expect { subject }.to raise_error(SignatureParser::ParsingError)
expect { subject }.to raise_error(described_class::ParsingError)
end
end
end

View file

@ -13,7 +13,26 @@ RSpec.describe Vacuum::ImportsVacuum do
describe '#perform' do
it 'cleans up the expected imports' do
expect { subject.perform }.to change { BulkImport.pluck(:id) }.from([old_unconfirmed, new_unconfirmed, recent_ongoing, recent_finished, old_finished].map(&:id)).to([new_unconfirmed, recent_ongoing, recent_finished].map(&:id))
expect { subject.perform }
.to change { ordered_bulk_imports.pluck(:id) }
.from(original_import_ids)
.to(remaining_import_ids)
end
def ordered_bulk_imports
BulkImport.order(id: :asc)
end
def original_import_ids
[old_unconfirmed, new_unconfirmed, recent_ongoing, recent_finished, old_finished].map(&:id)
end
def vacuumed_import_ids
[old_unconfirmed, old_finished].map(&:id)
end
def remaining_import_ids
original_import_ids - vacuumed_import_ids
end
end
end

View file

@ -46,7 +46,7 @@ describe WebfingerResource do
expect do
described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest)
end.to raise_error(described_class::InvalidRequest)
end
it 'finds the username in a valid https route' do
@ -137,7 +137,7 @@ describe WebfingerResource do
expect do
described_class.new(resource).username
end.to raise_error(WebfingerResource::InvalidRequest)
end.to raise_error(described_class::InvalidRequest)
end
end
end

View file

@ -901,7 +901,7 @@ RSpec.describe Account do
end
describe 'MENTION_RE' do
subject { Account::MENTION_RE }
subject { described_class::MENTION_RE }
it 'matches usernames in the middle of a sentence' do
expect(subject.match('Hello to @alice from me')[1]).to eq 'alice'
@ -1111,7 +1111,7 @@ RSpec.describe Account do
{ username: 'b', domain: 'b' },
].map(&method(:Fabricate).curry(2).call(:account))
expect(described_class.where('id > 0').alphabetic).to eq matches
expect(described_class.without_internal.alphabetic).to eq matches
end
end
@ -1162,7 +1162,7 @@ RSpec.describe Account do
it 'returns an array of accounts who do not have a domain' do
local_account = Fabricate(:account, domain: nil)
_account_with_domain = Fabricate(:account, domain: 'example.com')
expect(described_class.where('id > 0').local).to contain_exactly(local_account)
expect(described_class.without_internal.local).to contain_exactly(local_account)
end
end
@ -1173,14 +1173,14 @@ RSpec.describe Account do
matches[index] = Fabricate(:account, domain: matches[index])
end
expect(described_class.where('id > 0').partitioned).to match_array(matches)
expect(described_class.without_internal.partitioned).to match_array(matches)
end
end
describe 'recent' do
it 'returns a relation of accounts sorted by recent creation' do
matches = Array.new(2) { Fabricate(:account) }
expect(described_class.where('id > 0').recent).to match_array(matches)
expect(described_class.without_internal.recent).to match_array(matches)
end
end

View file

@ -1,6 +1,100 @@
# frozen_string_literal: true
# Reverted this commit.temporarily because load issues.
# Whenever a manual merge occurs, be sure to check the following commits.
# Hash: ee8d0b94473df357677cd1f82581251ce0423c01
# Message: Fix follow suggestions potentially including silenced or blocked accounts (#29306)
require 'rails_helper'
RSpec.describe AccountSuggestions::FriendsOfFriendsSource do
describe '#get' do
subject { described_class.new }
let!(:bob) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:alice) { Fabricate(:account, discoverable: true, hide_collections: true) }
let!(:eve) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:mallory) { Fabricate(:account, discoverable: false, hide_collections: false) }
let!(:eugen) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:neil) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:john) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:jerk) { Fabricate(:account, discoverable: true, hide_collections: false) }
let!(:larry) { Fabricate(:account, discoverable: true, hide_collections: false) }
context 'with follows and blocks' do
before do
bob.block!(jerk)
FollowRecommendationMute.create!(account: bob, target_account: neil)
# bob follows eugen, alice and larry
[eugen, alice, larry].each { |account| bob.follow!(account) }
# alice follows eve and mallory
[john, mallory].each { |account| alice.follow!(account) }
# eugen follows eve, john, jerk, larry and neil
[eve, mallory, jerk, larry, neil].each { |account| eugen.follow!(account) }
end
it 'returns eligible accounts', :aggregate_failures do
results = subject.get(bob)
# eve is returned through eugen
expect(results).to include([eve.id, :friends_of_friends])
# john is not reachable because alice hides who she follows
expect(results).to_not include([john.id, :friends_of_friends])
# mallory is not discoverable
expect(results).to_not include([mallory.id, :friends_of_friends])
# larry is not included because he's followed already
expect(results).to_not include([larry.id, :friends_of_friends])
# jerk is blocked
expect(results).to_not include([jerk.id, :friends_of_friends])
# the suggestion for neil has already been rejected
expect(results).to_not include([neil.id, :friends_of_friends])
end
end
context 'with deterministic order' do
before do
# bob follows eve and mallory
[eve, mallory].each { |account| bob.follow!(account) }
# eve follows eugen, john, and jerk
[jerk, eugen, john].each { |account| eve.follow!(account) }
# mallory follows eugen, john, and neil
[neil, eugen, john].each { |account| mallory.follow!(account) }
john.follow!(eugen)
john.follow!(neil)
end
it 'returns eligible accounts in the expected order' do
expect(subject.get(bob)).to eq expected_results
end
it 'contains correct underlying source data' do
expect(source_query_values)
.to contain_exactly(
[john.id, 2, 2], # Followed by 2 friends of bob (eve, mallory), 2 followers total (breaks tie)
[eugen.id, 2, 3], # Followed by 2 friends of bob (eve, mallory), 3 followers total
[jerk.id, 1, 1], # Followed by 1 friends of bob (eve), 1 followers total (breaks tie)
[neil.id, 1, 2] # Followed by 1 friends of bob (mallory), 2 followers total
)
end
def expected_results
[
[john.id, :friends_of_friends],
[eugen.id, :friends_of_friends],
[jerk.id, :friends_of_friends],
[neil.id, :friends_of_friends],
]
end
def source_query_values
subject.source_query(bob).to_a
end
end
end
end

View file

@ -32,4 +32,12 @@ RSpec.describe CustomFilter do
expect(record).to model_have_error_on_field(:context)
end
end
describe 'Normalizations' do
it 'cleans up context values' do
record = described_class.new(context: ['home', 'notifications', 'public ', ''])
expect(record.context).to eq(%w(home notifications public))
end
end
end

View file

@ -30,7 +30,7 @@ RSpec.describe Form::Import do
it 'has errors' do
subject.validate
expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: Form::Import::ROWS_PROCESSING_LIMIT))
expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: described_class::ROWS_PROCESSING_LIMIT))
end
end

View file

@ -8,7 +8,7 @@ describe PrivacyPolicy do
it 'has the privacy text' do
policy = described_class.current
expect(policy.text).to eq(PrivacyPolicy::DEFAULT_PRIVACY_POLICY)
expect(policy.text).to eq(described_class::DEFAULT_PRIVACY_POLICY)
end
end

View file

@ -22,7 +22,7 @@ RSpec.describe Tag do
end
describe 'HASHTAG_RE' do
subject { Tag::HASHTAG_RE }
subject { described_class::HASHTAG_RE }
it 'does not match URLs with anchors with non-hashtag characters' do
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil

View file

@ -8,7 +8,7 @@ RSpec.describe UserRole do
describe '#can?' do
context 'with a single flag' do
it 'returns true if any of them are present' do
subject.permissions = UserRole::FLAGS[:manage_reports]
subject.permissions = described_class::FLAGS[:manage_reports]
expect(subject.can?(:manage_reports)).to be true
end
@ -19,7 +19,7 @@ RSpec.describe UserRole do
context 'with multiple flags' do
it 'returns true if any of them are present' do
subject.permissions = UserRole::FLAGS[:manage_users]
subject.permissions = described_class::FLAGS[:manage_users]
expect(subject.can?(:manage_reports, :manage_users)).to be true
end
@ -51,7 +51,7 @@ RSpec.describe UserRole do
describe '#permissions_as_keys' do
before do
subject.permissions = UserRole::FLAGS[:invite_users] | UserRole::FLAGS[:view_dashboard] | UserRole::FLAGS[:manage_reports]
subject.permissions = described_class::FLAGS[:invite_users] | described_class::FLAGS[:view_dashboard] | described_class::FLAGS[:manage_reports]
end
it 'returns an array' do
@ -70,7 +70,7 @@ RSpec.describe UserRole do
let(:input) { %w(manage_users) }
it 'sets permission flags' do
expect(subject.permissions).to eq UserRole::FLAGS[:manage_users]
expect(subject.permissions).to eq described_class::FLAGS[:manage_users]
end
end
@ -78,7 +78,7 @@ RSpec.describe UserRole do
let(:input) { %w(manage_users manage_reports) }
it 'sets permission flags' do
expect(subject.permissions).to eq UserRole::FLAGS[:manage_users] | UserRole::FLAGS[:manage_reports]
expect(subject.permissions).to eq described_class::FLAGS[:manage_users] | described_class::FLAGS[:manage_reports]
end
end
@ -86,7 +86,7 @@ RSpec.describe UserRole do
let(:input) { %w(foo) }
it 'does not set permission flags' do
expect(subject.permissions).to eq UserRole::Flags::NONE
expect(subject.permissions).to eq described_class::Flags::NONE
end
end
end
@ -96,7 +96,7 @@ RSpec.describe UserRole do
subject { described_class.nobody }
it 'returns none' do
expect(subject.computed_permissions).to eq UserRole::Flags::NONE
expect(subject.computed_permissions).to eq described_class::Flags::NONE
end
end
@ -110,11 +110,11 @@ RSpec.describe UserRole do
context 'when role has the administrator flag' do
before do
subject.permissions = UserRole::FLAGS[:administrator]
subject.permissions = described_class::FLAGS[:administrator]
end
it 'returns all permissions' do
expect(subject.computed_permissions).to eq UserRole::Flags::ALL
expect(subject.computed_permissions).to eq described_class::Flags::ALL
end
end
@ -159,7 +159,7 @@ RSpec.describe UserRole do
end
it 'has no permissions' do
expect(subject.permissions).to eq UserRole::Flags::NONE
expect(subject.permissions).to eq described_class::Flags::NONE
end
it 'has negative position' do

View file

@ -24,7 +24,7 @@ RSpec.describe UserSettings do
context 'when setting was not defined' do
it 'raises error' do
expect { subject[:foo] }.to raise_error UserSettings::KeyError
expect { subject[:foo] }.to raise_error described_class::KeyError
end
end
end
@ -93,7 +93,7 @@ RSpec.describe UserSettings do
describe '.definition_for' do
context 'when key is defined' do
it 'returns a setting' do
expect(described_class.definition_for(:always_send_emails)).to be_a UserSettings::Setting
expect(described_class.definition_for(:always_send_emails)).to be_a described_class::Setting
end
end

View file

@ -21,6 +21,7 @@ require 'paperclip/matchers'
require 'capybara/rspec'
require 'chewy/rspec'
require 'email_spec/rspec'
require 'test_prof/recipes/rspec/before_all'
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

View file

@ -15,15 +15,11 @@ RSpec.describe 'credentials API' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected content' do
it 'returns http success with expected content' do
subject
expect(response)
.to have_http_status(200)
expect(body_as_json).to include({
source: hash_including({
discoverable: false,
@ -34,24 +30,55 @@ RSpec.describe 'credentials API' do
end
end
describe 'POST /api/v1/accounts/update_credentials' do
describe 'PATCH /api/v1/accounts/update_credentials' do
subject do
patch '/api/v1/accounts/update_credentials', headers: headers, params: params
end
let(:params) { { discoverable: true, locked: false, indexable: true } }
before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
let(:params) do
{
avatar: fixture_file_upload('avatar.gif', 'image/gif'),
discoverable: true,
display_name: "Alice Isn't Dead",
header: fixture_file_upload('attachment.jpg', 'image/jpeg'),
indexable: true,
locked: false,
note: 'Hello!',
source: {
privacy: 'unlisted',
sensitive: true,
},
}
end
it_behaves_like 'forbidden for wrong scope', 'read read:accounts'
it 'returns http success' do
subject
describe 'with empty source list' do
let(:params) { { display_name: "I'm a cat", source: {} } }
expect(response).to have_http_status(200)
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
it 'returns JSON with updated attributes' do
describe 'with invalid data' do
let(:params) { { note: 'This is too long. ' * 30 } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
it 'returns http success with updated JSON attributes' do
subject
expect(response)
.to have_http_status(200)
expect(body_as_json).to include({
source: hash_including({
discoverable: true,
@ -59,6 +86,27 @@ RSpec.describe 'credentials API' do
}),
locked: false,
})
expect(ActivityPub::UpdateDistributionWorker)
.to have_received(:perform_async).with(user.account_id)
end
def expect_account_updates
expect(user.account.reload)
.to have_attributes(
display_name: eq("Alice Isn't Dead"),
note: 'Hello!',
avatar: exist,
header: exist
)
end
def expect_user_updates
expect(user.reload)
.to have_attributes(
setting_default_privacy: eq('unlisted'),
setting_default_sensitive: be(true)
)
end
end
end

View file

@ -38,16 +38,14 @@ RSpec.describe 'Blocks' do
expect(body_as_json.size).to eq(params[:limit])
end
it 'sets the correct pagination header for the prev path' do
it 'sets correct link header pagination' do
subject
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id))
end
it 'sets the correct pagination header for the next path' do
subject
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_blocks_url(limit: params[:limit], max_id: blocks[1].id))
expect(response)
.to include_pagination_headers(
prev: api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id),
next: api_v1_blocks_url(limit: params[:limit], max_id: blocks.second.id)
)
end
end

View file

@ -42,9 +42,14 @@ RSpec.describe 'Bookmarks' do
it 'paginates correctly', :aggregate_failures do
subject
expect(body_as_json.size).to eq(params[:limit])
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id))
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks[1].id))
expect(body_as_json.size)
.to eq(params[:limit])
expect(response)
.to include_pagination_headers(
prev: api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id),
next: api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks.second.id)
)
end
end

View file

@ -45,16 +45,14 @@ RSpec.describe 'Favourites' do
expect(body_as_json.size).to eq(params[:limit])
end
it 'sets the correct pagination header for the prev path' do
it 'sets the correct pagination headers' do
subject
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id))
end
it 'sets the correct pagination header for the next path' do
subject
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_favourites_url(limit: params[:limit], max_id: favourites[1].id))
expect(response)
.to include_pagination_headers(
prev: api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id),
next: api_v1_favourites_url(limit: params[:limit], max_id: favourites.second.id)
)
end
end

View file

@ -49,16 +49,14 @@ RSpec.describe 'Followed tags' do
expect(body_as_json.size).to eq(params[:limit])
end
it 'sets the correct pagination header for the prev path' do
it 'sets the correct pagination headers' do
subject
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], since_id: tag_follows.last.id))
end
it 'sets the correct pagination header for the next path' do
subject
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], max_id: tag_follows.last.id))
expect(response)
.to include_pagination_headers(
prev: api_v1_followed_tags_url(limit: params[:limit], since_id: tag_follows.last.id),
next: api_v1_followed_tags_url(limit: params[:limit], max_id: tag_follows.last.id)
)
end
end
end

View file

@ -44,10 +44,11 @@ RSpec.describe 'Mutes' do
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_mutes_url(limit: params[:limit], max_id: mutes.last.id.to_s))
expect(response)
.to include_pagination_headers(
prev: api_v1_mutes_url(limit: params[:limit], since_id: mutes.last.id),
next: api_v1_mutes_url(limit: params[:limit], max_id: mutes.last.id)
)
end
end

View file

@ -98,9 +98,14 @@ RSpec.describe 'Notifications' do
notifications = user.account.notifications
expect(body_as_json.size).to eq(params[:limit])
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id.to_s))
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id.to_s))
expect(body_as_json.size)
.to eq(params[:limit])
expect(response)
.to include_pagination_headers(
prev: api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id),
next: api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id)
)
end
end

View file

@ -55,10 +55,11 @@ describe 'Home', :sidekiq_inline do
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_home_url(limit: 1, min_id: ana.statuses.first.id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_home_url(limit: 1, max_id: ana.statuses.first.id.to_s))
expect(response)
.to include_pagination_headers(
prev: api_v1_timelines_home_url(limit: params[:limit], min_id: ana.statuses.first.id),
next: api_v1_timelines_home_url(limit: params[:limit], max_id: ana.statuses.first.id)
)
end
end
end

View file

@ -106,10 +106,11 @@ describe 'Public' do
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_public_url(limit: 1, min_id: media_status.id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_public_url(limit: 1, max_id: media_status.id.to_s))
expect(response)
.to include_pagination_headers(
prev: api_v1_timelines_public_url(limit: params[:limit], min_id: media_status.id),
next: api_v1_timelines_public_url(limit: params[:limit], max_id: media_status.id)
)
end
end
end

View file

@ -71,10 +71,11 @@ RSpec.describe 'Tag' do
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_tag_url(limit: 1, min_id: love_status.id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_tag_url(limit: 1, max_id: love_status.id.to_s))
expect(response)
.to include_pagination_headers(
prev: api_v1_timelines_tag_url(limit: params[:limit], min_id: love_status.id),
next: api_v1_timelines_tag_url(limit: params[:limit], max_id: love_status.id)
)
end
end

View file

@ -2,25 +2,20 @@
require 'rails_helper'
RSpec.describe Api::V2::Filters::KeywordsController do
render_views
RSpec.describe 'API V2 Filters Keywords' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:filter) { Fabricate(:custom_filter, account: user.account) }
let(:other_user) { Fabricate(:user) }
let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET /api/v2/filters/:filter_id/keywords' do
let(:scopes) { 'read:filters' }
let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
it 'returns http success' do
get :index, params: { filter_id: filter.id }
get "/api/v2/filters/#{filter.id}/keywords", headers: headers
expect(response).to have_http_status(200)
expect(body_as_json)
.to contain_exactly(
@ -30,18 +25,18 @@ RSpec.describe Api::V2::Filters::KeywordsController do
context "when trying to access another's user filters" do
it 'returns http not found' do
get :index, params: { filter_id: other_filter.id }
get "/api/v2/filters/#{other_filter.id}/keywords", headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST #create' do
describe 'POST /api/v2/filters/:filter_id/keywords' do
let(:scopes) { 'write:filters' }
let(:filter_id) { filter.id }
before do
post :create, params: { filter_id: filter_id, keyword: 'magic', whole_word: false }
post "/api/v2/filters/#{filter_id}/keywords", headers: headers, params: { keyword: 'magic', whole_word: false }
end
it 'creates a filter', :aggregate_failures do
@ -65,12 +60,12 @@ RSpec.describe Api::V2::Filters::KeywordsController do
end
end
describe 'GET #show' do
describe 'GET /api/v2/filters/keywords/:id' do
let(:scopes) { 'read:filters' }
let(:keyword) { Fabricate(:custom_filter_keyword, keyword: 'foo', whole_word: false, custom_filter: filter) }
before do
get :show, params: { id: keyword.id }
get "/api/v2/filters/keywords/#{keyword.id}", headers: headers
end
it 'responds with the keyword', :aggregate_failures do
@ -90,12 +85,12 @@ RSpec.describe Api::V2::Filters::KeywordsController do
end
end
describe 'PUT #update' do
describe 'PUT /api/v2/filters/keywords/:id' do
let(:scopes) { 'write:filters' }
let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
before do
get :update, params: { id: keyword.id, keyword: 'updated' }
put "/api/v2/filters/keywords/#{keyword.id}", headers: headers, params: { keyword: 'updated' }
end
it 'updates the keyword', :aggregate_failures do
@ -113,12 +108,12 @@ RSpec.describe Api::V2::Filters::KeywordsController do
end
end
describe 'DELETE #destroy' do
describe 'DELETE /api/v2/filters/keywords/:id' do
let(:scopes) { 'write:filters' }
let(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
before do
delete :destroy, params: { id: keyword.id }
delete "/api/v2/filters/keywords/#{keyword.id}", headers: headers
end
it 'destroys the keyword', :aggregate_failures do

View file

@ -2,25 +2,20 @@
require 'rails_helper'
RSpec.describe Api::V2::Filters::StatusesController do
render_views
RSpec.describe 'API V2 Filters Statuses' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:filter) { Fabricate(:custom_filter, account: user.account) }
let(:other_user) { Fabricate(:user) }
let(:other_filter) { Fabricate(:custom_filter, account: other_user.account) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
describe 'GET /api/v2/filters/:filter_id/statuses' do
let(:scopes) { 'read:filters' }
let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) }
it 'returns http success' do
get :index, params: { filter_id: filter.id }
get "/api/v2/filters/#{filter.id}/statuses", headers: headers
expect(response).to have_http_status(200)
expect(body_as_json)
.to contain_exactly(
@ -30,7 +25,7 @@ RSpec.describe Api::V2::Filters::StatusesController do
context "when trying to access another's user filters" do
it 'returns http not found' do
get :index, params: { filter_id: other_filter.id }
get "/api/v2/filters/#{other_filter.id}/statuses", headers: headers
expect(response).to have_http_status(404)
end
end
@ -42,7 +37,7 @@ RSpec.describe Api::V2::Filters::StatusesController do
let!(:status) { Fabricate(:status) }
before do
post :create, params: { filter_id: filter_id, status_id: status.id }
post "/api/v2/filters/#{filter_id}/statuses", headers: headers, params: { status_id: status.id }
end
it 'creates a filter', :aggregate_failures do
@ -65,12 +60,12 @@ RSpec.describe Api::V2::Filters::StatusesController do
end
end
describe 'GET #show' do
describe 'GET /api/v2/filters/statuses/:id' do
let(:scopes) { 'read:filters' }
let!(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) }
before do
get :show, params: { id: status_filter.id }
get "/api/v2/filters/statuses/#{status_filter.id}", headers: headers
end
it 'responds with the filter', :aggregate_failures do
@ -89,12 +84,12 @@ RSpec.describe Api::V2::Filters::StatusesController do
end
end
describe 'DELETE #destroy' do
describe 'DELETE /api/v2/filters/statuses/:id' do
let(:scopes) { 'write:filters' }
let(:status_filter) { Fabricate(:custom_filter_status, custom_filter: filter) }
before do
delete :destroy, params: { id: status_filter.id }
delete "/api/v2/filters/statuses/#{status_filter.id}", headers: headers
end
it 'destroys the filter', :aggregate_failures do

View file

@ -39,7 +39,7 @@ module TestEndpoints
/api/v1/accounts/lookup?acct=alice
/api/v1/statuses/110224538612341312
/api/v1/statuses/110224538612341312/context
/api/v1/polls/12345
/api/v1/polls/123456789
/api/v1/trends/statuses
/api/v1/directory
).freeze
@ -166,14 +166,18 @@ describe 'Caching behavior' do
ActionController::Base.allow_forgery_protection = old
end
let(:alice) { Fabricate(:account, username: 'alice') }
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
let(:alice) { Account.find_by(username: 'alice') }
let(:user) { User.find_by(email: 'user@host.example') }
let(:token) { Doorkeeper::AccessToken.find_by(resource_owner_id: user.id) }
before do
status = Fabricate(:status, account: alice, id: '110224538612341312')
Fabricate(:status, account: alice, id: '110224538643211312', visibility: :private)
before_all do
alice = Fabricate(:account, username: 'alice')
user = Fabricate(:user, email: 'user@host.example', role: UserRole.find_by(name: 'Moderator'))
status = Fabricate(:status, account: alice, id: 110_224_538_612_341_312)
Fabricate(:status, account: alice, id: 110_224_538_643_211_312, visibility: :private)
Fabricate(:invite, code: 'abcdef')
Fabricate(:poll, status: status, account: alice, id: '12345')
Fabricate(:poll, status: status, account: alice, id: 123_456_789)
Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read')
user.account.follow!(alice)
end
@ -321,8 +325,6 @@ describe 'Caching behavior' do
end
context 'with an auth token' do
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before do
@ -585,8 +587,6 @@ describe 'Caching behavior' do
end
context 'with an auth token' do
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before do

View file

@ -3,25 +3,38 @@
require 'rails_helper'
describe 'Content-Security-Policy' do
it 'sets the expected CSP headers' do
allow(SecureRandom).to receive(:base64).with(16).and_return('ZbA+JmE7+bK8F5qvADZHuQ==')
before { allow(SecureRandom).to receive(:base64).with(16).and_return('ZbA+JmE7+bK8F5qvADZHuQ==') }
it 'sets the expected CSP headers' do
get '/'
expect(response.headers['Content-Security-Policy'].split(';').map(&:strip)).to contain_exactly(
"base-uri 'none'",
"default-src 'none'",
"frame-ancestors 'none'",
"font-src 'self' https://cb6e6126.ngrok.io",
"img-src 'self' data: blob: https://cb6e6126.ngrok.io",
"style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='",
"media-src 'self' data: https://cb6e6126.ngrok.io",
"frame-src 'self' https:",
"manifest-src 'self' https://cb6e6126.ngrok.io",
"form-action 'self'",
"child-src 'self' blob: https://cb6e6126.ngrok.io",
"worker-src 'self' blob: https://cb6e6126.ngrok.io",
"connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://cb6e6126.ngrok.io:4000",
"script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'"
)
expect(response_csp_headers)
.to match_array(expected_csp_headers)
end
def response_csp_headers
response
.headers['Content-Security-Policy']
.split(';')
.map(&:strip)
end
def expected_csp_headers
<<~CSP.split("\n").map(&:strip)
base-uri 'none'
child-src 'self' blob: https://cb6e6126.ngrok.io
connect-src 'self' data: blob: https://cb6e6126.ngrok.io ws://cb6e6126.ngrok.io:4000
default-src 'none'
font-src 'self' https://cb6e6126.ngrok.io
form-action 'self'
frame-ancestors 'none'
frame-src 'self' https:
img-src 'self' data: blob: https://cb6e6126.ngrok.io
manifest-src 'self' https://cb6e6126.ngrok.io
media-src 'self' data: https://cb6e6126.ngrok.io
script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'
style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='
worker-src 'self' blob: https://cb6e6126.ngrok.io
CSP
end
end

View file

@ -21,7 +21,7 @@ describe Account::StatusesSearch, :sidekiq_inline do
account.indexable = true
account.save!
expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.where(visibility: :public).count)
expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.public_visibility.count)
expect(StatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.count)
end
end
@ -32,7 +32,7 @@ describe Account::StatusesSearch, :sidekiq_inline do
context 'when picking an indexable account' do
it 'has statuses in the PublicStatusesIndex' do
expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.where(visibility: :public).count)
expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.public_visibility.count)
end
it 'has statuses in the StatusesIndex' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe AccountSearchService, type: :service do
describe AccountSearchService do
describe '#call' do
context 'with a query to ignore' do
it 'returns empty array for missing query' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe AccountStatusesCleanupService, type: :service do
describe AccountStatusesCleanupService do
let(:account) { Fabricate(:account, username: 'alice', domain: nil) }
let(:account_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) }
let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
RSpec.describe ActivityPub::FetchFeaturedCollectionService do
subject { described_class.new }
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/account', featured_collection_url: 'https://example.com/account/pinned') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service do
RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService do
subject { described_class.new }
let(:collection_url) { 'https://example.com/account/tags' }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
RSpec.describe ActivityPub::FetchRemoteAccountService do
subject { described_class.new }
let!(:actor) do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
RSpec.describe ActivityPub::FetchRemoteActorService do
subject { described_class.new }
let!(:actor) do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
RSpec.describe ActivityPub::FetchRemoteKeyService do
subject { described_class.new }
let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
RSpec.describe ActivityPub::FetchRemoteStatusService do
include ActionView::Helpers::TextHelper
subject { described_class.new }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::FetchRepliesService, type: :service do
RSpec.describe ActivityPub::FetchRepliesService do
subject { described_class.new }
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::ProcessAccountService, type: :service do
RSpec.describe ActivityPub::ProcessAccountService do
subject { described_class.new }
before do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
RSpec.describe ActivityPub::ProcessCollectionService do
subject { described_class.new }
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }

View file

@ -6,7 +6,7 @@ def poll_option_json(name, votes)
{ type: 'Note', name: name, replies: { type: 'Collection', totalItems: votes } }
end
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
RSpec.describe ActivityPub::ProcessStatusUpdateService do
subject { described_class.new }
let(:thread) { nil }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
RSpec.describe ActivityPub::SynchronizeFollowersService do
subject { described_class.new }
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe AfterBlockDomainFromAccountService, type: :service do
RSpec.describe AfterBlockDomainFromAccountService do
subject { described_class.new }
let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org', inbox_url: 'https://evil.org/inbox', protocol: :activitypub) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe AfterBlockService, type: :service do
RSpec.describe AfterBlockService do
subject { described_class.new.call(account, target_account) }
let(:account) { Fabricate(:account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe AppSignUpService, type: :service do
RSpec.describe AppSignUpService do
subject { described_class.new }
let(:app) { Fabricate(:application, scopes: 'read write') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe AuthorizeFollowService, type: :service do
RSpec.describe AuthorizeFollowService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BackupService, type: :service do
RSpec.describe BackupService do
subject(:service_call) { described_class.new.call(backup) }
let!(:user) { Fabricate(:user) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BatchedRemoveStatusService, :sidekiq_inline, type: :service do
RSpec.describe BatchedRemoveStatusService, :sidekiq_inline do
subject { described_class.new }
let!(:alice) { Fabricate(:account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BlockDomainService, type: :service do
RSpec.describe BlockDomainService do
subject { described_class.new }
let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BlockService, type: :service do
RSpec.describe BlockService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe BootstrapTimelineService, type: :service do
RSpec.describe BootstrapTimelineService do
subject { described_class.new }
context 'when the new user has registered from an invite' do

View file

@ -110,7 +110,7 @@ RSpec.describe BulkImportRowService do
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
expect { subject.call(import_row) }.to add_target_account_to_list
end
end
@ -124,7 +124,7 @@ RSpec.describe BulkImportRowService do
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
expect { subject.call(import_row) }.to add_target_account_to_list
end
end
@ -134,7 +134,7 @@ RSpec.describe BulkImportRowService do
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
expect { subject.call(import_row) }.to add_target_account_to_list
end
end
@ -146,9 +146,24 @@ RSpec.describe BulkImportRowService do
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
expect { subject.call(import_row) }.to add_target_account_to_list
end
end
def add_target_account_to_list
change { target_account_on_list? }
.from(false)
.to(true)
end
def target_account_on_list?
ListAccount
.joins(:list)
.exists?(
account_id: target_account.id,
list: { title: 'my list' }
)
end
end
context 'when the list does not exist yet' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ClearDomainMediaService, type: :service do
RSpec.describe ClearDomainMediaService do
subject { described_class.new }
let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe DeleteAccountService, type: :service do
RSpec.describe DeleteAccountService do
shared_examples 'common behavior' do
subject { described_class.new.call(account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FanOutOnWriteService, type: :service do
RSpec.describe FanOutOnWriteService do
subject { described_class.new }
let(:ltl_enabled) { true }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FavouriteService, type: :service do
RSpec.describe FavouriteService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FetchLinkCardService, type: :service do
RSpec.describe FetchLinkCardService do
subject { described_class.new }
let(:html) { '<!doctype html><title>Hello world</title>' }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe FetchOEmbedService, type: :service do
describe FetchOEmbedService do
subject { described_class.new }
before do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FetchRemoteStatusService, type: :service do
RSpec.describe FetchRemoteStatusService do
let(:account) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/foo') }
let(:prefetched_body) { nil }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FetchResourceService, type: :service do
RSpec.describe FetchResourceService do
describe '#call' do
subject { described_class.new.call(url) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe FollowService, type: :service do
RSpec.describe FollowService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ImportService, :sidekiq_inline, type: :service do
RSpec.describe ImportService, :sidekiq_inline do
include RoutingHelper
let!(:account) { Fabricate(:account, locked: false) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe MuteService, type: :service do
RSpec.describe MuteService do
subject { described_class.new.call(account, target_account) }
let(:account) { Fabricate(:account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe NotifyService, type: :service do
RSpec.describe NotifyService do
subject { described_class.new.call(recipient, type, activity) }
let(:user) { Fabricate(:user) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe PostStatusService, type: :service do
RSpec.describe PostStatusService do
subject { described_class.new }
it 'creates a new status' do
@ -481,7 +481,7 @@ RSpec.describe PostStatusService, type: :service do
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(PostStatusService::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
end.to raise_error(an_instance_of(described_class::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account])))
end
it 'processes duplicate mentions correctly' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe PrecomputeFeedService, type: :service do
RSpec.describe PrecomputeFeedService do
subject { described_class.new }
describe 'call' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ProcessMentionsService, type: :service do
RSpec.describe ProcessMentionsService do
subject { described_class.new }
let(:account) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe PurgeDomainService, type: :service do
RSpec.describe PurgeDomainService do
subject { described_class.new }
let(:domain) { 'obsolete.org' }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ReblogService, type: :service do
RSpec.describe ReblogService do
let(:alice) { Fabricate(:account, username: 'alice') }
context 'when creates a reblog with appropriate visibility' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe RejectFollowService, type: :service do
RSpec.describe RejectFollowService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe RemoveFromFollowersService, type: :service do
RSpec.describe RemoveFromFollowersService do
subject { described_class.new }
let(:bob) { Fabricate(:account, username: 'bob') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe RemoveStatusService, :sidekiq_inline, type: :service do
RSpec.describe RemoveStatusService, :sidekiq_inline do
subject { described_class.new }
let!(:alice) { Fabricate(:account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ReportService, type: :service do
RSpec.describe ReportService do
subject { described_class.new }
let(:source_account) { Fabricate(:account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe ResolveAccountService, type: :service do
RSpec.describe ResolveAccountService do
subject { described_class.new }
before do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe ResolveURLService, type: :service do
describe ResolveURLService do
subject { described_class.new }
describe '#call' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe SearchService, type: :service do
describe SearchService do
subject { described_class.new }
describe '#call' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe SoftwareUpdateCheckService, type: :service do
RSpec.describe SoftwareUpdateCheckService do
subject { described_class.new }
shared_examples 'when the feature is enabled' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe SuspendAccountService, :sidekiq_inline, type: :service do
RSpec.describe SuspendAccountService, :sidekiq_inline do
shared_examples 'common behavior' do
subject { described_class.new.call(account) }

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TagSearchService do
describe '#call' do
let!(:one) { Fabricate(:tag, name: 'one') }
before { Fabricate(:tag, name: 'two') }
it 'runs a search for tags' do
results = subject.call('#one', limit: 5)
expect(results)
.to have_attributes(
size: 1,
first: eq(one)
)
end
end
end

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe TranslateStatusService, type: :service do
RSpec.describe TranslateStatusService do
subject(:service) { described_class.new }
let(:status) { Fabricate(:status, text: text, spoiler_text: spoiler_text, language: 'en', preloadable_poll: poll, media_attachments: media_attachments) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UnallowDomainService, type: :service do
RSpec.describe UnallowDomainService do
subject { described_class.new }
let(:bad_domain) { 'evil.org' }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
describe UnblockDomainService, type: :service do
describe UnblockDomainService do
subject { described_class.new }
describe 'call' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UnblockService, type: :service do
RSpec.describe UnblockService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UnfollowService, type: :service do
RSpec.describe UnfollowService do
subject { described_class.new }
let(:sender) { Fabricate(:account, username: 'alice') }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UnsuspendAccountService, type: :service do
RSpec.describe UnsuspendAccountService do
shared_context 'with common context' do
subject { described_class.new.call(account) }

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UpdateAccountService, type: :service do
RSpec.describe UpdateAccountService do
subject { described_class.new }
describe 'switching form locked to unlocked accounts', :sidekiq_inline do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe UpdateStatusService, type: :service do
RSpec.describe UpdateStatusService do
subject { described_class.new }
context 'when nothing changes' do

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe VerifyLinkService, type: :service do
RSpec.describe VerifyLinkService do
subject { described_class.new }
context 'when given a local account' do

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
RSpec::Matchers.define :include_pagination_headers do |links|
match do |response|
links.map do |key, value|
response.headers['Link'].find_link(['rel', key.to_s]).href == value
end.all?
end
failure_message do |header|
"expected that #{header} would have the same values as #{links}."
end
end

View file

@ -56,7 +56,7 @@ RSpec.describe FollowLimitValidator do
follow.valid?
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: FollowLimitValidator::LIMIT))
expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: described_class::LIMIT))
end
end

Some files were not shown because too many files have changed in this diff Show more