Merge commit '74fc4dbacf5a9168074e91fc2d75309dc42ff1ed' into glitch-soc/merge-upstream

This commit is contained in:
Claire committed 2025-07-15 18:19:32 +02:00
commit eeffe580d8
69 files changed
+523 -463

No files matched your search

@@ -100,11 +100,14 @@ RSpec.describe Auth::SessionsController do
let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') }
context 'when using a valid password' do
before do
subject do
post :create, params: { user: { email: user.email, password: user.password } }
end
it 'redirects to home and logs the user in' do
expect { subject }
.to change(user.login_activities.where(success: true), :count).by(1)
expect(response).to redirect_to(root_path)
expect(controller.current_user).to eq user
@@ -265,10 +268,9 @@ RSpec.describe Auth::SessionsController do
it 'does not log the user in, sets a flash message, and sends a suspicious sign in email', :inline_jobs do
emails = capture_emails do
Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR.times do
post :create, params: { user: { otp_attempt: '1234' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
expect(controller.current_user).to be_nil
end
expect { process_maximum_two_factor_attempts }
.to change(user.login_activities.where(success: false), :count).by(1)
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
@@ -286,6 +288,13 @@ RSpec.describe Auth::SessionsController do
subject: eq(I18n.t('user_mailer.failed_2fa.subject'))
)
end
def process_maximum_two_factor_attempts
Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR.times do
post :create, params: { user: { otp_attempt: '1234' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
expect(controller.current_user).to be_nil
end
end
end
context 'when using a valid OTP' do
+33 -52
View File
@@ -6,66 +6,47 @@ RSpec.describe ActivityPub::Activity::Accept do
let(:sender) { Fabricate(:account) }
let(:recipient) { Fabricate(:account) }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Accept',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: {
id: 'bar',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
},
}.with_indifferent_access
end
describe '#perform' do
subject { described_class.new(json, sender) }
before do
allow(RemoteAccountRefreshWorker).to receive(:perform_async)
Fabricate(:follow_request, account: recipient, target_account: sender)
subject.perform
end
context 'with a Follow request' do
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Accept',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: {
id: 'https://abc-123/456',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
},
}.deep_stringify_keys
end
it 'creates a follow relationship' do
expect(recipient.following?(sender)).to be true
end
context 'with a regular Follow' do
before do
Fabricate(:follow_request, account: recipient, target_account: sender)
end
it 'removes the follow request' do
expect(recipient.requested?(sender)).to be false
end
it 'creates a follow relationship, removes the follow request, and queues a refresh' do
expect { subject.perform }
.to change { recipient.following?(sender) }.from(false).to(true)
.and change { recipient.requested?(sender) }.from(true).to(false)
it 'queues a refresh' do
expect(RemoteAccountRefreshWorker).to have_received(:perform_async).with(sender.id)
end
end
expect(RemoteAccountRefreshWorker).to have_enqueued_sidekiq_job(sender.id)
end
end
context 'when given a relay' do
subject { described_class.new(json, sender) }
context 'when given a relay' do
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Accept',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: {
id: 'https://abc-123/456',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
},
}.with_indifferent_access
end
it 'marks the relay as accepted' do
subject.perform
expect(relay.reload.accepted?).to be true
it 'marks the relay as accepted' do
expect { subject.perform }
.to change { relay.reload.accepted? }.from(false).to(true)
end
end
end
end
end
+92 -113
View File
@@ -5,14 +5,6 @@ require 'rails_helper'
RSpec.describe ActivityPub::Activity::Reject do
let(:sender) { Fabricate(:account) }
let(:recipient) { Fabricate(:account) }
let(:object_json) do
{
id: 'bar',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
}
end
let(:json) do
{
@@ -21,130 +13,117 @@ RSpec.describe ActivityPub::Activity::Reject do
type: 'Reject',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: object_json,
}.with_indifferent_access
}.deep_stringify_keys
end
describe '#perform' do
subject { described_class.new(json, sender) }
context 'when rejecting a pending follow request by target' do
before do
Fabricate(:follow_request, account: recipient, target_account: sender)
subject.perform
context 'when rejecting a Follow' do
let(:object_json) do
{
id: 'bar',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
}
end
it 'does not create a follow relationship' do
expect(recipient.following?(sender)).to be false
context 'when rejecting a pending follow request by target' do
before do
Fabricate(:follow_request, account: recipient, target_account: sender)
end
it 'removes the follow request without creating a follow relationship' do
expect { subject.perform }
.to change { recipient.requested?(sender) }.from(true).to(false)
.and not_change { recipient.following?(sender) }.from(false)
end
end
it 'removes the follow request' do
expect(recipient.requested?(sender)).to be false
context 'when rejecting a pending follow request by uri' do
before do
Fabricate(:follow_request, account: recipient, target_account: sender, uri: 'bar')
end
it 'removes the follow request without creating a follow relationship' do
expect { subject.perform }
.to change { recipient.requested?(sender) }.from(true).to(false)
.and not_change { recipient.following?(sender) }.from(false)
end
end
context 'when rejecting a pending follow request by uri only' do
let(:object_json) { 'bar' }
before do
Fabricate(:follow_request, account: recipient, target_account: sender, uri: 'bar')
end
it 'removes the follow request without creating a follow relationship' do
expect { subject.perform }
.to change { recipient.requested?(sender) }.from(true).to(false)
.and not_change { recipient.following?(sender) }.from(false)
end
end
context 'when rejecting an existing follow relationship by target' do
before do
Fabricate(:follow, account: recipient, target_account: sender)
end
it 'removes the follow relationship without creating a request' do
expect { subject.perform }
.to change { recipient.following?(sender) }.from(true).to(false)
.and not_change { recipient.requested?(sender) }.from(false)
end
end
context 'when rejecting an existing follow relationship by uri' do
before do
Fabricate(:follow, account: recipient, target_account: sender, uri: 'bar')
end
it 'removes the follow relationship without creating a request' do
expect { subject.perform }
.to change { recipient.following?(sender) }.from(true).to(false)
.and not_change { recipient.requested?(sender) }.from(false)
end
end
context 'when rejecting an existing follow relationship by uri only' do
let(:object_json) { 'bar' }
before do
Fabricate(:follow, account: recipient, target_account: sender, uri: 'bar')
end
it 'removes the follow relationship without creating a request' do
expect { subject.perform }
.to change { recipient.following?(sender) }.from(true).to(false)
.and not_change { recipient.requested?(sender) }.from(false)
end
end
end
context 'when rejecting a pending follow request by uri' do
before do
Fabricate(:follow_request, account: recipient, target_account: sender, uri: 'bar')
subject.perform
end
context 'when given a relay' do
subject { described_class.new(json, sender) }
it 'does not create a follow relationship' do
expect(recipient.following?(sender)).to be false
end
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
it 'removes the follow request' do
expect(recipient.requested?(sender)).to be false
end
end
context 'when rejecting a pending follow request by uri only' do
let(:object_json) { 'bar' }
before do
Fabricate(:follow_request, account: recipient, target_account: sender, uri: 'bar')
subject.perform
end
it 'does not create a follow relationship' do
expect(recipient.following?(sender)).to be false
end
it 'removes the follow request' do
expect(recipient.requested?(sender)).to be false
end
end
context 'when rejecting an existing follow relationship by target' do
before do
Fabricate(:follow, account: recipient, target_account: sender)
subject.perform
end
it 'removes the follow relationship' do
expect(recipient.following?(sender)).to be false
end
it 'does not create a follow request' do
expect(recipient.requested?(sender)).to be false
end
end
context 'when rejecting an existing follow relationship by uri' do
before do
Fabricate(:follow, account: recipient, target_account: sender, uri: 'bar')
subject.perform
end
it 'removes the follow relationship' do
expect(recipient.following?(sender)).to be false
end
it 'does not create a follow request' do
expect(recipient.requested?(sender)).to be false
end
end
context 'when rejecting an existing follow relationship by uri only' do
let(:object_json) { 'bar' }
before do
Fabricate(:follow, account: recipient, target_account: sender, uri: 'bar')
subject.perform
end
it 'removes the follow relationship' do
expect(recipient.following?(sender)).to be false
end
it 'does not create a follow request' do
expect(recipient.requested?(sender)).to be false
end
end
end
context 'when given a relay' do
subject { described_class.new(json, sender) }
let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') }
let(:json) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Reject',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: {
let(:object_json) do
{
id: 'https://abc-123/456',
type: 'Follow',
actor: ActivityPub::TagManager.instance.uri_for(recipient),
object: ActivityPub::TagManager.instance.uri_for(sender),
},
}.with_indifferent_access
end
}.with_indifferent_access
end
it 'marks the relay as rejected' do
subject.perform
expect(relay.reload.rejected?).to be true
it 'marks the relay as rejected' do
subject.perform
expect(relay.reload.rejected?).to be true
end
end
end
end
+4 -2
View File
@@ -506,6 +506,8 @@ RSpec.describe Account do
context 'when account is local' do
subject { Fabricate.build :account, domain: nil }
let(:domains_limit) { described_class::ATTRIBUTION_DOMAINS_LIMIT }
context 'with an existing differently-cased username account' do
before { Fabricate :account, username: 'the_doctor' }
@@ -547,8 +549,8 @@ RSpec.describe Account do
it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) }
it { is_expected.to validate_absence_of(:uri).on(:create) }
it { is_expected.to allow_values([], ['example.com'], (1..100).to_a).for(:attribution_domains) }
it { is_expected.to_not allow_values(['example com'], ['@'], (1..101).to_a).for(:attribution_domains) }
it { is_expected.to allow_values([], ['example.com'], (1..domains_limit).to_a).for(:attribution_domains) }
it { is_expected.to_not allow_values(['example com'], ['@'], (1..(domains_limit + 1)).to_a).for(:attribution_domains) }
end
context 'when account is remote' do
+33
View File
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe CustomFilterStatus do
describe 'Associations' do
it { is_expected.to belong_to(:custom_filter) }
it { is_expected.to belong_to(:status) }
end
describe 'Validations' do
subject { Fabricate.build :custom_filter_status }
it { is_expected.to validate_uniqueness_of(:status_id).scoped_to(:custom_filter_id) }
describe 'Status access' do
subject { Fabricate.build :custom_filter_status, custom_filter:, status: }
let(:custom_filter) { Fabricate :custom_filter }
let(:status) { Fabricate :status }
context 'when policy allows filter account to access status' do
it { is_expected.to allow_value(status.id).for(:status_id) }
end
context 'when policy does not allow filter account to access status' do
before { status.account.touch(:suspended_at) }
it { is_expected.to_not allow_value(status.id).for(:status_id) }
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe FollowRecommendationMute do
describe 'Associations' do
it { is_expected.to belong_to(:account) }
it { is_expected.to belong_to(:target_account).class_name('Account') }
end
describe 'Validations' do
subject { Fabricate.build :follow_recommendation_mute }
it { is_expected.to validate_uniqueness_of(:target_account_id).scoped_to(:account_id) }
end
describe 'Callbacks' do
describe 'Maintaining the recommendation cache' do
let(:account) { Fabricate :account }
let(:cache_key) { "follow_recommendations/#{account.id}" }
before { Rails.cache.write(cache_key, 123) }
it 'purges on save' do
expect { Fabricate :follow_recommendation_mute, account: account }
.to(change { Rails.cache.exist?(cache_key) }.from(true).to(false))
end
end
end
end
+1
View File
@@ -24,6 +24,7 @@ RSpec.describe User do
describe 'Associations' do
it { is_expected.to belong_to(:account).required }
it { is_expected.to have_many(:login_activities) }
end
describe 'Validations' do
@@ -435,7 +435,71 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status has an existing unverified quote and adds an approval link' do
context 'when the status has an existing unverified quote and adds an approval link through an implicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: nil) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
let(:payload) do
{
'@context': [
'https://www.w3.org/ns/activitystreams',
{
'@id': 'https://w3id.org/fep/044f#quote',
'@type': '@id',
},
{
'@id': 'https://w3id.org/fep/044f#quoteAuthorization',
'@type': '@id',
},
],
id: 'foo',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
quote: ActivityPub::TagManager.instance.uri_for(quoted_status),
quoteAuthorization: approval_uri,
}
end
before do
stub_request(:get, approval_uri).to_return(headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': [
'https://www.w3.org/ns/activitystreams',
{
QuoteAuthorization: 'https://w3id.org/fep/044f#QuoteAuthorization',
gts: 'https://gotosocial.org/ns#',
interactionPolicy: {
'@id': 'gts:interactionPolicy',
'@type': '@id',
},
interactingObject: {
'@id': 'gts:interactingObject',
'@type': '@id',
},
interactionTarget: {
'@id': 'gts:interactionTarget',
'@type': '@id',
},
},
],
type: 'QuoteAuthorization',
id: approval_uri,
attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account),
interactingObject: ActivityPub::TagManager.instance.uri_for(status),
interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status),
}))
end
it 'updates the approval URI and verifies the quote' do
expect { subject.call(status, json, json) }
.to change(quote, :approval_uri).to(approval_uri)
.and change(quote, :state).to('accepted')
end
end
context 'when the status has an existing unverified quote and adds an approval link through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: nil) }
@@ -500,7 +564,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status has an existing verified quote and removes an approval link' do
context 'when the status has an existing verified quote and removes an approval link through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) }
@@ -535,7 +599,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status adds a verifiable quote' do
context 'when the status adds a verifiable quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
@@ -600,7 +664,39 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status adds a unverifiable quote' do
context 'when the status adds a unverifiable quote through an implicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
let(:payload) do
{
'@context': [
'https://www.w3.org/ns/activitystreams',
{
'@id': 'https://w3id.org/fep/044f#quote',
'@type': '@id',
},
{
'@id': 'https://w3id.org/fep/044f#quoteAuthorization',
'@type': '@id',
},
],
id: 'foo',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
quote: ActivityPub::TagManager.instance.uri_for(quoted_status),
}
end
it 'does not add the quote' do
expect { subject.call(status, json, json) }
.to not_change(status, :quote).from(nil)
end
end
context 'when the status adds a unverifiable quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
@@ -635,7 +731,29 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status removes a verified quote' do
context 'when the status removes a verified quote through an implicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
}
end
it 'does not remove the quote' do
expect { subject.call(status, json, json) }
.to not_change { status.reload.quote }.from(quote)
end
end
context 'when the status removes a verified quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) }
@@ -660,7 +778,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status removes an unverified quote' do
context 'when the status removes an unverified quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: nil, state: :pending) }
@@ -684,7 +802,44 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status swaps a verified quote with an unverifiable quote' do
context 'when the status swaps a verified quote with an unverifiable quote through an implicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let(:second_quoted_status) { Fabricate(:status, account: quoted_account) }
let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, approval_uri: approval_uri, state: :accepted) }
let(:approval_uri) { 'https://quoted.example.com/approvals/1' }
let(:payload) do
{
'@context': [
'https://www.w3.org/ns/activitystreams',
{
'@id': 'https://w3id.org/fep/044f#quote',
'@type': '@id',
},
{
'@id': 'https://w3id.org/fep/044f#quoteAuthorization',
'@type': '@id',
},
],
id: 'foo',
type: 'Note',
summary: 'Show more',
content: 'Hello universe',
quote: ActivityPub::TagManager.instance.uri_for(second_quoted_status),
quoteAuthorization: approval_uri,
}
end
it 'does not update the URI or the quote verification status' do
expect { subject.call(status, json, json) }
.to not_change { status.reload.quote }.from(quote)
.and not_change { status.quote.quoted_status }.from(quoted_status)
.and not_change { status.quote.state }.from('accepted')
end
end
context 'when the status swaps a verified quote with an unverifiable quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }
let(:second_quoted_status) { Fabricate(:status, account: quoted_account) }
@@ -752,7 +907,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
end
context 'when the status swaps a verified quote with another verifiable quote' do
context 'when the status swaps a verified quote with another verifiable quote through an explicit update' do
let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') }
let(:second_quoted_account) { Fabricate(:account, domain: 'second-quoted.example.com') }
let(:quoted_status) { Fabricate(:status, account: quoted_account) }