2
0

Add digest re-check before removing followers in synchronization mechanism (#34273)

This commit is contained in:
Claire
2025-08-27 14:12:55 +02:00
committed by GitHub
parent 66f5ad42e2
commit c00ed9c913
5 changed files with 145 additions and 7 deletions

View File

@@ -35,7 +35,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
shared_examples 'synchronizes followers' do
before do
subject.call(actor, collection_uri)
subject.call(actor, collection_uri, expected_digest)
end
it 'maintains following records and sends Undo Follow to actor' do
@@ -51,6 +51,8 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
end
describe '#call' do
let(:expected_digest) { nil }
context 'when the endpoint is a Collection of actor URIs' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
@@ -197,5 +199,131 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
.to be_following(actor)
end
end
context 'when passing a matching expected_digest' do
let(:expected_digest) do
digest = "\x00" * 32
items.each do |uri|
Xorcist.xor!(digest, Digest::SHA256.digest(uri))
end
digest.unpack1('H*')
end
context 'when the endpoint is a Collection of actor URIs' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is an OrderedCollection of actor URIs' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
id: collection_uri,
orderedItems: items,
}.with_indifferent_access
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is a single-page paginated Collection of actor URIs' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
first: {
type: 'CollectionPage',
partOf: collection_uri,
items: items,
},
}.with_indifferent_access
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'
end
end
context 'when passing a non-matching expected_digest' do
let(:expected_digest) { '123456789' }
context 'when the endpoint is a Collection of actor URIs' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'does not remove followers' do
follower_ids = actor.followers.reload.pluck(:id)
subject.call(actor, collection_uri, expected_digest)
expect(follower_ids - actor.followers.reload.pluck(:id)).to be_empty
end
end
context 'when the endpoint is an OrderedCollection of actor URIs' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'OrderedCollection',
id: collection_uri,
orderedItems: items,
}.with_indifferent_access
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'does not remove followers' do
follower_ids = actor.followers.reload.pluck(:id)
subject.call(actor, collection_uri, expected_digest)
expect(follower_ids - actor.followers.reload.pluck(:id)).to be_empty
end
end
context 'when the endpoint is a single-page paginated Collection of actor URIs' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
first: {
type: 'CollectionPage',
partOf: collection_uri,
items: items,
},
}.with_indifferent_access
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'does not remove followers' do
follower_ids = actor.followers.reload.pluck(:id)
subject.call(actor, collection_uri, expected_digest)
expect(follower_ids - actor.followers.reload.pluck(:id)).to be_empty
end
end
end
end
end

View File

@@ -15,7 +15,13 @@ RSpec.describe ActivityPub::FollowersSynchronizationWorker do
it 'sends the status to the service' do
worker.perform(account.id, url)
expect(service).to have_received(:call).with(account, url)
expect(service).to have_received(:call).with(account, url, nil)
end
it 'sends the status to the service with the passed digest' do
worker.perform(account.id, url, 'digest-123')
expect(service).to have_received(:call).with(account, url, 'digest-123')
end
it 'returns nil for non-existent record' do