Merge commit '651e51a82eba542c4d8c3fec5edd16420ea1ff3e' into glitch-soc/merge-upstream

Conflicts:
- `yarn.lock`:
  Upstream updated a dependency textually adjacent to a glitch-soc-specific one.
  Updated the dependency as upstream did.
This commit is contained in:
Claire committed 2025-08-17 20:52:34 +02:00
commit 4d9150735a
86 files changed
+2659 -1164

No files matched your search

+35 -49
View File
@@ -12,27 +12,22 @@ RSpec.describe ActivityPub::Activity::Delete do
id: 'foo',
type: 'Delete',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: ActivityPub::TagManager.instance.uri_for(status),
object: object_json,
signature: 'foo',
}.with_indifferent_access
}.deep_stringify_keys
end
let(:object_json) { ActivityPub::TagManager.instance.uri_for(status) }
describe '#perform' do
subject { described_class.new(json, sender) }
before do
subject.perform
end
it 'deletes sender\'s status' do
subject.perform
expect(Status.find_by(id: status.id)).to be_nil
end
end
context 'when the status has been reblogged' do
describe '#perform' do
subject { described_class.new(json, sender) }
context 'when the status has been reblogged' do
let!(:reblogger) { Fabricate(:account) }
let!(:follower) { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
let!(:reblog) { Fabricate(:status, account: reblogger, reblog: status) }
@@ -55,12 +50,8 @@ RSpec.describe ActivityPub::Activity::Delete do
expect { reblog.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
context 'when the status has been reported' do
describe '#perform' do
subject { described_class.new(json, sender) }
context 'when the status has been reported' do
let!(:reporter) { Fabricate(:account) }
before do
@@ -76,23 +67,9 @@ RSpec.describe ActivityPub::Activity::Delete do
expect(Status.with_discarded.find_by(id: status.id)).to_not be_nil
end
end
end
context 'when the deleted object is an account' do
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Delete',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: ActivityPub::TagManager.instance.uri_for(sender),
signature: 'foo',
}.with_indifferent_access
end
describe '#perform' do
subject { described_class.new(json, sender) }
context 'when the deleted object is an account' do
let(:object_json) { ActivityPub::TagManager.instance.uri_for(sender) }
let(:service) { instance_double(DeleteAccountService, call: true) }
before do
@@ -106,27 +83,36 @@ RSpec.describe ActivityPub::Activity::Delete do
.to have_received(:call).with(sender, { reserve_username: false, skip_activitypub: true })
end
end
end
context 'when the deleted object is a quote authorization' do
let(:quoter) { Fabricate(:account, domain: 'b.example.com') }
let(:status) { Fabricate(:status, account: quoter) }
let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') }
let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) }
context 'when the deleted object is a quote authorization' do
let(:quoter) { Fabricate(:account, domain: 'b.example.com') }
let(:status) { Fabricate(:status, account: quoter) }
let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') }
let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Delete',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: quote.approval_uri,
signature: 'foo',
}.with_indifferent_access
let(:object_json) { quote.approval_uri }
it 'revokes the authorization' do
expect { subject.perform }
.to change { quote.reload.state }.to('revoked')
end
end
describe '#perform' do
subject { described_class.new(json, sender) }
context 'when the deleted object is an inlined quote authorization' do
let(:quoter) { Fabricate(:account, domain: 'b.example.com') }
let(:status) { Fabricate(:status, account: quoter) }
let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') }
let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) }
let(:object_json) do
{
type: 'QuoteAuthorization',
id: quote.approval_uri,
attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account),
interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status),
interactingObject: ActivityPub::TagManager.instance.uri_for(status),
}.deep_stringify_keys
end
it 'revokes the authorization' do
expect { subject.perform }
+47
View File
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Antispam do
describe '#local_preflight_check!' do
subject { described_class.new.local_preflight_check!(status) }
let(:status) { Fabricate :status }
context 'when there is no spammy text registered' do
it { is_expected.to be_nil }
end
context 'with spammy text' do
before { redis.sadd 'antispam:spammy_texts', 'https://banned.example' }
context 'when status matches' do
let(:status) { Fabricate :status, text: 'I use https://banned.example urls in my text' }
it 'raises error and reports' do
expect { subject }
.to raise_error(described_class::SilentlyDrop)
.and change(spam_reports, :count).by(1)
end
context 'when report already exists' do
before { Fabricate :report, account: Account.representative, target_account: status.account }
it 'raises error and does not report' do
expect { subject }
.to raise_error(described_class::SilentlyDrop)
.and not_change(spam_reports, :count)
end
end
def spam_reports
Account.representative.reports.where(target_account: status.account).spam
end
end
context 'when status does not match' do
it { is_expected.to be_nil }
end
end
end
end
+9 -2
View File
@@ -32,6 +32,7 @@ RSpec.describe Mastodon::CLI::Accounts do
describe '#create' do
let(:action) { :create }
let(:username) { 'tootctl_username' }
shared_examples 'a new user with given email address and username' do
it 'creates user and accounts from options and displays success message' do
@@ -48,18 +49,24 @@ RSpec.describe Mastodon::CLI::Accounts do
end
def account_from_options
Account.find_local('tootctl_username')
Account.find_local(username)
end
end
context 'when required USERNAME and --email are provided' do
let(:arguments) { ['tootctl_username'] }
let(:arguments) { [username] }
context 'with USERNAME and --email only' do
let(:options) { { email: 'tootctl@example.com' } }
it_behaves_like 'a new user with given email address and username'
context 'with a reserved username' do
let(:username) { 'security' }
it_behaves_like 'a new user with given email address and username'
end
context 'with invalid --email value' do
let(:options) { { email: 'invalid' } }
+1
View File
@@ -80,6 +80,7 @@ RSpec.describe CustomEmoji, :attachment_processing do
describe 'Normalizations' do
describe 'domain' do
it { is_expected.to normalize(:domain).from(' www.mastodon.host ').to('www.mastodon.host') }
it { is_expected.to normalize(:domain).from('wWw.MaStOdOn.CoM').to('www.mastodon.com') }
it { is_expected.to normalize(:domain).from(nil).to(nil) }
end
@@ -0,0 +1,120 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Interaction policies', feature: :outgoing_quotes do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:status) { Fabricate(:status, account: user.account) }
let(:params) { { quote_approval_policy: 'followers' } }
describe 'PUT /api/v1/statuses/:status_id/interaction_policy' do
subject do
put "/api/v1/statuses/#{status.id}/interaction_policy", headers: headers, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
expect { subject }
.to_not(change { status.reload.quote_approval_policy })
expect(response).to have_http_status(401)
expect(response.content_type)
.to start_with('application/json')
end
end
context 'with a status from a different user' do
let(:status) { Fabricate(:status) }
it 'returns http unauthorized' do
expect { subject }
.to_not(change { status.reload.quote_approval_policy })
expect(response).to have_http_status(403)
expect(response.content_type)
.to start_with('application/json')
end
end
context 'when changing the interaction policy' do
it 'changes the interaction policy, returns the updated status, and schedules distribution jobs' do
expect { subject }
.to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16)
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to include(
'quote_approval' => match(
'automatic' => ['followers'],
'manual' => [],
'current_user' => 'automatic'
)
)
expect(DistributionWorker)
.to have_enqueued_sidekiq_job(status.id, { 'update' => true })
expect(ActivityPub::StatusUpdateDistributionWorker)
.to have_enqueued_sidekiq_job(status.id)
end
end
context 'when not changing the interaction policy' do
let(:params) { { quote_approval_policy: 'nobody' } }
it 'keeps the interaction policy, returns the status, and does not schedule distribution jobs' do
expect { subject }
.to_not(change { status.reload.quote_approval_policy }.from(0))
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to include(
'quote_approval' => match(
'automatic' => [],
'manual' => [],
'current_user' => 'automatic'
)
)
expect(DistributionWorker)
.to_not have_enqueued_sidekiq_job
expect(ActivityPub::StatusUpdateDistributionWorker)
.to_not have_enqueued_sidekiq_job
end
end
context 'when trying to change the interaction policy of a private post' do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
let(:params) { { quote_approval_policy: 'public' } }
it 'keeps the interaction policy, returns the status, and does not schedule distribution jobs' do
expect { subject }
.to_not(change { status.reload.quote_approval_policy }.from(0))
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to include(
'quote_approval' => match(
'automatic' => [],
'manual' => [],
'current_user' => 'automatic'
)
)
expect(DistributionWorker)
.to_not have_enqueued_sidekiq_job
expect(ActivityPub::StatusUpdateDistributionWorker)
.to_not have_enqueued_sidekiq_job
end
end
end
end
@@ -13,7 +13,10 @@ RSpec.describe ActivityPub::DeleteQuoteAuthorizationSerializer do
expect(subject.deep_symbolize_keys)
.to include(
actor: eq(ActivityPub::TagManager.instance.uri_for(status.account)),
object: ActivityPub::TagManager.instance.approval_uri_for(quote, check_approval: false),
object: a_hash_including(
type: 'QuoteAuthorization',
id: ActivityPub::TagManager.instance.approval_uri_for(quote, check_approval: false)
),
type: 'Delete'
)
end
@@ -316,6 +316,23 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
expect(existing_status.edits).to_not be_empty
end
end
context 'with an implicit update to quoting policy' do
let(:object) do
note.merge({
'content' => existing_status.text,
'interactionPolicy' => {
'canQuote' => {
'automaticApproval' => ['https://www.w3.org/ns/activitystreams#Public'],
},
},
})
end
it 'updates status' do
expect(existing_status.reload.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16)
end
end
end
end
+1 -1
View File
@@ -23,7 +23,7 @@ RSpec.describe 'Share page', :js, :streaming do
fill_in_form
expect(page)
.to have_css('.notification-bar-message', text: frontend_translations('compose.published.body'))
.to have_current_path(%r{/@bob/[0-9]+})
end
def fill_in_form