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

This commit is contained in:
KMY 2024-08-13 07:01:38 +09:00
commit e7ccc0539f
358 changed files with 4653 additions and 4261 deletions

View file

@ -227,7 +227,7 @@ describe ApplicationHelper do
it 'returns an unlock icon for a unlisted visible status' do
result = helper.visibility_icon Status.new(visibility: 'unlisted')
expect(result).to match(/unlock/)
expect(result).to match(/lock_open/)
end
it 'returns a lock icon for a private visible status' do
@ -237,7 +237,7 @@ describe ApplicationHelper do
it 'returns an at icon for a direct visible status' do
result = helper.visibility_icon Status.new(visibility: 'direct')
expect(result).to match(/at/)
expect(result).to match(/alternate_email/)
end
end

View file

@ -36,7 +36,7 @@ describe StatusesHelper do
it 'returns the correct fa icon' do
result = helper.fa_visibility_icon(status)
expect(result).to match('fa-globe')
expect(result).to match('material-globe')
end
end
@ -46,7 +46,7 @@ describe StatusesHelper do
it 'returns the correct fa icon' do
result = helper.fa_visibility_icon(status)
expect(result).to match('fa-unlock')
expect(result).to match('material-lock_open')
end
end
@ -56,7 +56,7 @@ describe StatusesHelper do
it 'returns the correct fa icon' do
result = helper.fa_visibility_icon(status)
expect(result).to match('fa-lock')
expect(result).to match('material-lock')
end
end
@ -66,7 +66,7 @@ describe StatusesHelper do
it 'returns the correct fa icon' do
result = helper.fa_visibility_icon(status)
expect(result).to match('fa-at')
expect(result).to match('material-alternate_email')
end
end
end

View file

@ -0,0 +1,93 @@
# frozen_string_literal: true
require 'rails_helper'
describe Paperclip::ResponseWithLimitAdapter do
subject { described_class.new(response_with_limit) }
before { stub_request(:get, url).to_return(headers: headers, body: body) }
let(:response_with_limit) { ResponseWithLimit.new(response, 50.kilobytes) }
let(:response) { Request.new(:get, url).perform(&:itself) }
let(:url) { 'https://example.com/dir/foo.png' }
let(:headers) { nil }
let(:body) { attachment_fixture('600x400.jpeg').binmode.read }
it 'writes temporary file' do
expect(subject.tempfile.read).to eq body
expect(subject.size).to eq body.bytesize
end
context 'with Content-Disposition header' do
let(:headers) { { 'Content-Disposition' => 'attachment; filename="bar.png"' } }
it 'uses filename from header' do
expect(subject.original_filename).to eq 'bar.png'
end
it 'detects MIME type from content' do
expect(subject.content_type).to eq 'image/jpeg'
end
end
context 'without Content-Disposition header' do
it 'uses filename from path' do
expect(subject.original_filename).to eq 'foo.png'
end
it 'detects MIME type from content' do
expect(subject.content_type).to eq 'image/jpeg'
end
end
context 'without filename in path' do
let(:url) { 'https://example.com/' }
it 'falls back to "data"' do
expect(subject.original_filename).to eq 'data'
end
it 'detects MIME type from content' do
expect(subject.content_type).to eq 'image/jpeg'
end
end
context 'with very long filename' do
let(:url) { 'https://example.com/abcdefghijklmnopqrstuvwxyz.0123456789' }
it 'truncates the filename' do
expect(subject.original_filename).to eq 'abcdefghijklmnopqrst.0123'
end
end
context 'when response size exceeds limit' do
context 'with Content-Length header' do
let(:headers) { { 'Content-Length' => 5.megabytes } }
it 'raises without reading the body' do
allow(response).to receive(:body).and_call_original
expect { subject }.to raise_error(Mastodon::LengthValidationError, 'Content-Length 5242880 exceeds limit of 51200')
expect(response).to_not have_received(:body)
end
end
context 'without Content-Length header' do
let(:body) { SecureRandom.random_bytes(1.megabyte) }
it 'raises while reading the body' do
expect { subject }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 51200')
expect(response.content_length).to be_nil
end
end
end
context 'when response times out' do
it 'raises' do
allow(response.body.connection).to receive(:readpartial).and_raise(HTTP::TimeoutError)
expect { subject }.to raise_error(HTTP::TimeoutError)
end
end
end

View file

@ -100,7 +100,7 @@ describe Request do
describe "response's body_with_limit method" do
it 'rejects body more than 1 megabyte by default' do
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes))
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1048576')
end
it 'accepts body less than 1 megabyte by default' do
@ -110,17 +110,17 @@ describe Request do
it 'rejects body by given size' do
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes))
expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error Mastodon::LengthValidationError
expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1024')
end
it 'rejects too large chunked body' do
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' })
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Body size exceeds limit of 1048576')
end
it 'rejects too large monolithic body' do
stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes })
expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError
expect { subject.perform(&:body_with_limit) }.to raise_error(Mastodon::LengthValidationError, 'Content-Length 2097152 exceeds limit of 1048576')
end
it 'truncates large monolithic body' do

View file

@ -292,6 +292,25 @@ RSpec.describe MediaAttachment, :attachment_processing do
end
end
describe 'cache deletion hooks' do
let(:media) { Fabricate(:media_attachment) }
before do
allow(Rails.configuration.x).to receive(:cache_buster_enabled).and_return(true)
end
it 'queues CacheBusterWorker jobs' do
original_path = media.file.path(:original)
small_path = media.file.path(:small)
thumbnail_path = media.thumbnail.path(:original)
expect { media.destroy }
.to enqueue_sidekiq_job(CacheBusterWorker).with(original_path)
.and enqueue_sidekiq_job(CacheBusterWorker).with(small_path)
.and enqueue_sidekiq_job(CacheBusterWorker).with(thumbnail_path)
end
end
private
def media_metadata

View file

@ -9,7 +9,7 @@ RSpec.describe NotificationPolicy do
let(:sender) { Fabricate(:account) }
before do
Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender), filtered: true)
Fabricate.times(2, :notification, account: subject.account, activity: Fabricate(:status, account: sender), filtered: true, type: :mention)
Fabricate(:notification_request, account: subject.account, from_account: sender)
subject.summarize!
end

View file

@ -8,7 +8,7 @@ RSpec.describe NotificationRequest do
context 'when there are remaining notifications' do
before do
Fabricate(:notification, account: subject.account, activity: Fabricate(:status, account: subject.from_account), filtered: true)
Fabricate(:notification, account: subject.account, activity: Fabricate(:status, account: subject.from_account), filtered: true, type: :mention)
subject.reconsider_existence!
end

View file

@ -51,7 +51,7 @@ RSpec.describe 'Policies' do
it 'changes notification policy and returns an updated json object', :aggregate_failures do
expect { subject }
.to change { NotificationPolicy.find_or_initialize_by(account: user.account).filter_not_following }.from(false).to(true)
.to change { NotificationPolicy.find_or_initialize_by(account: user.account).for_not_following.to_sym }.from(:accept).to(:filter)
expect(response).to have_http_status(200)
expect(body_as_json).to include(

View file

@ -0,0 +1,72 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Policies' do
let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:notifications write:notifications' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v2/notifications/policy', :inline_jobs do
subject do
get '/api/v2/notifications/policy', headers: headers, params: params
end
let(:params) { {} }
before do
Fabricate(:notification_request, account: user.account)
end
it_behaves_like 'forbidden for wrong scope', 'write write:notifications'
context 'with no options' do
it 'returns json with expected attributes', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to include(
for_not_following: 'accept',
for_not_followers: 'accept',
for_new_accounts: 'accept',
for_private_mentions: 'filter',
for_limited_accounts: 'filter',
summary: a_hash_including(
pending_requests_count: 1,
pending_notifications_count: 0
)
)
end
end
end
describe 'PUT /api/v2/notifications/policy' do
subject do
put '/api/v2/notifications/policy', headers: headers, params: params
end
let(:params) { { for_not_following: 'filter', for_limited_accounts: 'drop' } }
it_behaves_like 'forbidden for wrong scope', 'read read:notifications'
it 'changes notification policy and returns an updated json object', :aggregate_failures do
expect { subject }
.to change { NotificationPolicy.find_or_initialize_by(account: user.account).for_not_following.to_sym }.from(:accept).to(:filter)
.and change { NotificationPolicy.find_or_initialize_by(account: user.account).for_limited_accounts.to_sym }.from(:filter).to(:drop)
expect(response).to have_http_status(200)
expect(body_as_json).to include(
for_not_following: 'filter',
for_not_followers: 'accept',
for_new_accounts: 'accept',
for_private_mentions: 'filter',
for_limited_accounts: 'drop',
summary: a_hash_including(
pending_requests_count: 0,
pending_notifications_count: 0
)
)
end
end
end

View file

@ -160,6 +160,36 @@ RSpec.describe 'Notifications' do
end
end
context 'when requesting stripped-down accounts' do
let(:params) { { expand_accounts: 'partial_avatars' } }
let(:recent_account) { Fabricate(:account) }
before do
FavouriteService.new.call(recent_account, user.account.statuses.first)
end
it 'returns an account in "partial_accounts", with the expected keys', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:partial_accounts].size).to be > 0
expect(body_as_json[:partial_accounts][0].keys).to contain_exactly(:acct, :avatar, :avatar_static, :bot, :id, :locked, :url)
expect(body_as_json[:partial_accounts].pluck(:id)).to_not include(recent_account.id.to_s)
expect(body_as_json[:accounts].pluck(:id)).to include(recent_account.id.to_s)
end
end
context 'when passing an invalid value for "expand_accounts"' do
let(:params) { { expand_accounts: 'unknown_foobar' } }
it 'returns http bad request' do
subject
expect(response).to have_http_status(400)
end
end
def body_json_types
body_as_json[:notification_groups].pluck(:type)
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe DismissNotificationRequestService do
describe '#call' do
let(:sender) { Fabricate(:account) }
let(:receiver) { Fabricate(:account) }
let(:request) { Fabricate(:notification_request, account: receiver, from_account: sender) }
it 'destroys the request and queues a worker', :aggregate_failures do
expect { described_class.new.call(request) }
.to change(request, :destroyed?).to(true)
expect(FilteredNotificationCleanupWorker)
.to have_enqueued_sidekiq_job(receiver.id, sender.id)
end
end
end

View file

@ -129,6 +129,40 @@ RSpec.describe NotifyService do
end
end
context 'when the blocked sender has a role' do
let(:sender) { Fabricate(:user, role: sender_role).account }
let(:activity) { Fabricate(:mention, status: Fabricate(:status, account: sender)) }
let(:type) { :mention }
before do
recipient.block!(sender)
end
context 'when the role is a visible moderator' do
let(:sender_role) { Fabricate(:user_role, highlighted: true, permissions: UserRole::FLAGS[:manage_users]) }
it 'does notify' do
expect { subject }.to change(Notification, :count)
end
end
context 'when the role is a non-visible moderator' do
let(:sender_role) { Fabricate(:user_role, highlighted: false, permissions: UserRole::FLAGS[:manage_users]) }
it 'does not notify' do
expect { subject }.to_not change(Notification, :count)
end
end
context 'when the role is a visible non-moderator' do
let(:sender_role) { Fabricate(:user_role, highlighted: true) }
it 'does not notify' do
expect { subject }.to_not change(Notification, :count)
end
end
end
context 'with filtered notifications' do
let(:unknown) { Fabricate(:account, username: 'unknown') }
let(:status) { Fabricate(:status, account: unknown) }
@ -162,20 +196,58 @@ RSpec.describe NotifyService do
end
end
describe NotifyService::DismissCondition do
describe NotifyService::DropCondition do
subject { described_class.new(notification) }
let(:activity) { Fabricate(:mention, status: Fabricate(:status)) }
let(:notification) { Fabricate(:notification, type: :mention, activity: activity, from_account: activity.status.account, account: activity.account) }
describe '#dismiss?' do
context 'when sender is silenced' do
describe '#drop' do
context 'when sender is silenced and recipient has a default policy' do
before do
notification.from_account.silence!
end
it 'returns false' do
expect(subject.dismiss?).to be false
expect(subject.drop?).to be false
end
end
context 'when sender is silenced and recipient has a policy to ignore silenced accounts' do
before do
notification.from_account.silence!
notification.account.create_notification_policy!(for_limited_accounts: :drop)
end
it 'returns true' do
expect(subject.drop?).to be true
end
end
context 'when sender is new and recipient has a default policy' do
it 'returns false' do
expect(subject.drop?).to be false
end
end
context 'when sender is new and recipient has a policy to ignore silenced accounts' do
before do
notification.account.create_notification_policy!(for_new_accounts: :drop)
end
it 'returns true' do
expect(subject.drop?).to be true
end
end
context 'when sender is new and followed and recipient has a policy to ignore silenced accounts' do
before do
notification.account.create_notification_policy!(for_new_accounts: :drop)
notification.account.follow!(notification.from_account)
end
it 'returns false' do
expect(subject.drop?).to be false
end
end
@ -185,7 +257,7 @@ RSpec.describe NotifyService do
end
it 'returns true' do
expect(subject.dismiss?).to be true
expect(subject.drop?).to be true
end
end
end
@ -216,6 +288,16 @@ RSpec.describe NotifyService do
expect(subject.filter?).to be false
end
end
context 'when recipient is allowing limited accounts' do
before do
notification.account.create_notification_policy!(for_limited_accounts: :accept)
end
it 'returns false' do
expect(subject.filter?).to be false
end
end
end
context 'when recipient is filtering not-followed senders' do

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
describe FilteredNotificationCleanupWorker do
describe '#perform' do
let(:sender) { Fabricate(:account) }
let(:recipient) { Fabricate(:account) }
let(:bystander) { Fabricate(:account) }
before do
Fabricate(:notification, account: recipient, activity: Fabricate(:favourite, account: sender), filtered: true)
Fabricate(:notification, account: recipient, activity: Fabricate(:favourite, account: bystander), filtered: true)
Fabricate(:notification, account: recipient, activity: Fabricate(:follow, account: sender), filtered: true)
Fabricate(:notification, account: recipient, activity: Fabricate(:favourite, account: bystander), filtered: true)
end
it 'deletes all filtered notifications to the account' do
expect { described_class.new.perform(recipient.id, sender.id) }
.to change { recipient.notifications.where(from_account: sender).count }.from(2).to(0)
.and(not_change { recipient.notifications.where(from_account: bystander).count })
end
end
end