Merge commit 'c43508b3e0b05c5e739d726bad53f1eef89e9376' into glitch-soc/merge-upstream

Conflicts:
- `app/lib/content_security_policy.rb`:
  Upstream added support for `EXTRA_MEDIA_HOSTS` which is very similar to
  glitch-soc's `EXTRA_DATA_HOST`.
  Deprecate `EXTRA_DATA_HOST` FOR `EXTRA_MEDIA_HOST`.
This commit is contained in:
Claire 2025-03-26 18:52:15 +01:00
commit 449dc30739
13 changed files with 196 additions and 40 deletions

View file

@ -309,6 +309,9 @@ MAX_POLL_OPTION_CHARS=100
# HCAPTCHA_SECRET_KEY=
# HCAPTCHA_SITE_KEY=
# Optional list of hosts that are allowed to serve media for your instance
# EXTRA_MEDIA_HOSTS=https://data.example1.com,https://data.example2.com
# IP and session retention
# -----------------------
# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml

View file

@ -81,9 +81,14 @@
"alert.rate_limited.title": "Feur bevennet",
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
"alert.unexpected.title": "Hopala !",
"alt_text_modal.cancel": "Nullañ",
"alt_text_modal.change_thumbnail": "Kemmañ ar velvenn",
"alt_text_modal.done": "Graet",
"announcement.announcement": "Kemennad",
"annual_report.summary.followers.followers": "heulier",
"annual_report.summary.highlighted_post.possessive": "{name}",
"annual_report.summary.most_used_hashtag.none": "Hini ebet",
"annual_report.summary.new_posts.new_posts": "toudoù nevez",
"attachments_list.unprocessed": "(ket meret)",
"audio.hide": "Kuzhat ar c'hleved",
"block_modal.show_less": "Diskouez nebeutoc'h",
@ -109,9 +114,11 @@
"column.blocks": "Implijer·ezed·ien berzet",
"column.bookmarks": "Sinedoù",
"column.community": "Red-amzer lec'hel",
"column.create_list": "Krouiñ ul listenn",
"column.direct": "Menegoù prevez",
"column.directory": "Mont a-dreuz ar profiloù",
"column.domain_blocks": "Domani berzet",
"column.edit_list": "Kemmañ al listenn",
"column.favourites": "Muiañ-karet",
"column.firehose": "Redoù war-eeun",
"column.follow_requests": "Rekedoù heuliañ",
@ -162,9 +169,12 @@
"confirmations.delete.message": "Ha sur oc'h e fell deoc'h dilemel an toud-mañ ?",
"confirmations.delete_list.confirm": "Dilemel",
"confirmations.delete_list.message": "Ha sur eo hoc'h eus c'hoant da zilemel ar roll-mañ da vat ?",
"confirmations.delete_list.title": "Dilemel al listenn?",
"confirmations.discard_edit_media.confirm": "Nac'hañ",
"confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
"confirmations.edit.confirm": "Kemmañ",
"confirmations.edit.message": "Kemmañ bremañ a zilamo ar gemennadenn emaoc'h o skrivañ. Sur e oc'h e fell deoc'h kenderc'hel ganti?",
"confirmations.follow_to_list.title": "Heuliañ an implijer·ez?",
"confirmations.logout.confirm": "Digevreañ",
"confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
"confirmations.mute.confirm": "Kuzhat",
@ -266,8 +276,10 @@
"footer.privacy_policy": "Reolennoù prevezded",
"footer.source_code": "Gwelet ar c'hod mammenn",
"footer.status": "Statud",
"footer.terms_of_service": "Divizoù implijout hollek",
"generic.saved": "Enrollet",
"getting_started.heading": "Loc'hañ",
"hashtag.admin_moderation": "Digeriñ an etrefas evezhiañ evit #{name}",
"hashtag.column_header.tag_mode.all": "ha(g) {additional}",
"hashtag.column_header.tag_mode.any": "pe {additional}",
"hashtag.column_header.tag_mode.none": "hep {additional}",
@ -337,8 +349,14 @@
"limited_account_hint.action": "Diskouez an aelad memes tra",
"limited_account_hint.title": "Kuzhet eo bet ar profil-mañ gant an evezhierien eus {domain}.",
"link_preview.author": "Gant {name}",
"lists.add_member": "Ouzhpennañ",
"lists.add_to_list": "Ouzhpennañ d'al listenn",
"lists.create": "Krouiñ",
"lists.create_list": "Krouiñ ul listenn",
"lists.delete": "Dilemel al listenn",
"lists.done": "Graet",
"lists.edit": "Kemmañ al listenn",
"lists.list_name": "Anv al listenn",
"lists.replies_policy.followed": "Pep implijer.ez heuliet",
"lists.replies_policy.list": "Izili ar roll",
"lists.replies_policy.none": "Den ebet",
@ -373,11 +391,17 @@
"notification.follow": "heuliañ a ra {name} ac'hanoc'h",
"notification.follow.name_and_others": "{name} <a>{count, plural, one {hag # den all} two {ha # zen all} few {ha # den all} many {ha # den all} other {ha # den all}}</a> zo o heuliañ ac'hanoc'h",
"notification.follow_request": "Gant {name} eo bet goulennet ho heuliañ",
"notification.label.reply": "Respont",
"notification.moderation-warning.learn_more": "Gouzout hiroc'h",
"notification.own_poll": "Echu eo ho sontadeg",
"notification.reblog": "Gant {name} eo bet skignet ho toud",
"notification.relationships_severance_event.learn_more": "Gouzout hiroc'h",
"notification.status": "Emañ {name} o paouez toudañ",
"notification.update": "Gant {name} ez eus bet kemmet un toud",
"notification_requests.accept": "Asantiñ",
"notification_requests.dismiss": "Diverkañ",
"notification_requests.edit_selection": "Kemmañ",
"notification_requests.exit_selection": "Graet",
"notifications.clear": "Skarzhañ ar c'hemennoù",
"notifications.clear_confirmation": "Ha sur oc'h e fell deoc'h skarzhañ ho holl kemennoù ?",
"notifications.column_settings.admin.report": "Disklêriadurioù nevez :",
@ -410,6 +434,10 @@
"notifications.permission_denied": "Kemennoù war ar burev n'int ket hegerz rak pedadenn aotren ar merdeer a zo bet nullet araok",
"notifications.permission_denied_alert": "Kemennoù wa ar burev na c'hellont ket bezañ lezelet, rak aotre ar merdeer a zo bet nac'het a-raok",
"notifications.permission_required": "Kemennoù war ar burev n'int ket hegerz abalamour d'an aotre rekis n'eo ket bet roet.",
"notifications.policy.accept": "Asantiñ",
"notifications.policy.accept_hint": "Diskouez er chemennoù",
"notifications.policy.drop": "Tremen e-bioù",
"notifications.policy.filter": "Silañ",
"notifications.policy.filter_new_accounts_title": "Kontoù nevez",
"notifications_permission_banner.enable": "Lezel kemennoù war ar burev",
"notifications_permission_banner.how_to_control": "Evit reseviñ kemennoù pa ne vez ket digoret Mastodon, lezelit kemennoù war ar burev. Gallout a rit kontrollañ peseurt eskemmoù a c'henel kemennoù war ar burev gant ar {icon} nozelenn a-us kentre ma'z int lezelet.",
@ -515,6 +543,7 @@
"search_results.accounts": "Profiloù",
"search_results.all": "Pep tra",
"search_results.hashtags": "Hashtagoù",
"search_results.no_results": "Disoc'h ebet.",
"search_results.see_all": "Gwelet pep tra",
"search_results.statuses": "Toudoù",
"server_banner.active_users": "implijerien·ezed oberiant",
@ -579,6 +608,7 @@
"subscribed_languages.target": "Cheñch ar yezhoù koumanantet evit {target}",
"tabs_bar.home": "Degemer",
"tabs_bar.notifications": "Kemennoù",
"terms_of_service.title": "Divizoù implijout",
"time_remaining.days": "{number, plural,one {# devezh} other {# a zevezh}} a chom",
"time_remaining.hours": "{number, plural, one {# eurvezh} other{# eurvezh}} a chom",
"time_remaining.minutes": "{number, plural, one {# munut} other{# a vunut}} a chom",

View file

@ -562,6 +562,7 @@
"notification.favourite": "{name} märkis su postituse lemmikuks",
"notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# teist}}</a> märkis su postituse lemmikuks",
"notification.favourite_pm": "{name} märkis sinu privaatse mainimise lemmikuks",
"notification.favourite_pm.name_and_others_with_link": "{name} ja <a>{count, plural, one {# veel} other {# veel}}</a> märkisid su privaatse mainimise lemmikuks",
"notification.follow": "{name} alustas su jälgimist",
"notification.follow.name_and_others": "{name} ja veel {count, plural, one {# kasutaja} other {# kasutajat}} hakkas sind jälgima",
"notification.follow_request": "{name} soovib sind jälgida",
@ -696,6 +697,7 @@
"poll_button.remove_poll": "Eemalda küsitlus",
"privacy.change": "Muuda postituse nähtavust",
"privacy.direct.long": "Kõik postituses mainitud",
"privacy.direct.short": "Privaatne mainimine",
"privacy.private.long": "Ainult jälgijad",
"privacy.private.short": "Jälgijad",
"privacy.public.long": "Nii kasutajad kui mittekasutajad",

View file

@ -10,7 +10,7 @@ class ContentSecurityPolicy
end
def media_hosts
[assets_host, cdn_host_value, paperclip_root_url].concat(extra_data_hosts).compact
[assets_host, cdn_host_value, paperclip_root_url].concat(extra_media_hosts).compact
end
def sso_host
@ -31,8 +31,17 @@ class ContentSecurityPolicy
private
# TODO: remove after 4.4.0
def extra_data_hosts
ENV.fetch('EXTRA_DATA_HOSTS', '').split('|')
return [] unless ENV['EXTRA_DATA_HOSTS']
ENV.fetch('EXTRA_DATA_HOSTS', '').split('|').tap do |hosts|
Rails.logger.warn "EXTRA_DATA_HOSTS is deprecated, use EXTRA_MEDIA_HOSTS=#{hosts.join(',')}"
end
end
def extra_media_hosts
ENV.fetch('EXTRA_MEDIA_HOSTS', '').split(/(?:\s*,\s*|\s+)/).presence || extra_data_hosts
end
def url_from_configured_asset_host

View file

@ -153,7 +153,7 @@ class Account < ApplicationRecord
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
scope :dormant, -> { joins(:account_stat).merge(AccountStat.without_recent_activity) }
scope :with_username, ->(value) { where arel_table[:username].lower.eq(value.to_s.downcase) }
scope :with_username, ->(value) { value.is_a?(Array) ? where(arel_table[:username].lower.in(value.map { |x| x.to_s.downcase })) : where(arel_table[:username].lower.eq(value.to_s.downcase)) }
scope :with_domain, ->(value) { where arel_table[:domain].lower.eq(value&.to_s&.downcase) }
scope :without_memorial, -> { where(memorial: false) }
scope :duplicate_uris, -> { select(:uri, Arel.star.count).group(:uri).having(Arel.star.count.gt(1)) }

View file

@ -107,6 +107,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
{
enabled: registrations_enabled?,
approval_required: Setting.registrations_mode == 'approved',
reason_required: Setting.registrations_mode == 'approved' && Setting.require_invite_text,
message: registrations_enabled? ? nil : registrations_message,
min_age: Setting.min_age.presence,
url: ENV.fetch('SSO_ACCOUNT_SIGN_UP', nil),

View file

@ -4,32 +4,46 @@ class ActivityPub::SynchronizeFollowersService < BaseService
include JsonLdHelper
include Payloadable
MAX_COLLECTION_PAGES = 10
def call(account, partial_collection_url)
@account = account
@expected_followers_ids = []
items = collection_items(partial_collection_url)
return if items.nil?
# There could be unresolved accounts (hence the call to .compact) but this
# should never happen in practice, since in almost all cases we keep an
# Account record, and should we not do that, we should have sent a Delete.
# In any case there is not much we can do if that occurs.
@expected_followers = items.filter_map { |uri| ActivityPub::TagManager.instance.uri_to_resource(uri, Account) }
return unless process_collection!(partial_collection_url)
remove_unexpected_local_followers!
handle_unexpected_outgoing_follows!
end
private
def process_page!(items)
page_expected_followers = extract_local_followers(items)
@expected_followers_ids.concat(page_expected_followers.pluck(:id))
handle_unexpected_outgoing_follows!(page_expected_followers)
end
def extract_local_followers(items)
# There could be unresolved accounts (hence the call to .filter_map) but this
# should never happen in practice, since in almost all cases we keep an
# Account record, and should we not do that, we should have sent a Delete.
# In any case there is not much we can do if that occurs.
# TODO: this will need changes when switching to numeric IDs
usernames = items.filter_map { |uri| ActivityPub::TagManager.instance.uri_to_local_id(uri, :username)&.downcase }
Account.local.with_username(usernames)
end
def remove_unexpected_local_followers!
@account.followers.local.where.not(id: @expected_followers.map(&:id)).reorder(nil).find_each do |unexpected_follower|
@account.followers.local.where.not(id: @expected_followers_ids).reorder(nil).find_each do |unexpected_follower|
UnfollowService.new.call(unexpected_follower, @account)
end
end
def handle_unexpected_outgoing_follows!
@expected_followers.each do |expected_follower|
def handle_unexpected_outgoing_follows!(expected_followers)
expected_followers.each do |expected_follower|
next if expected_follower.following?(@account)
if expected_follower.requested?(@account)
@ -50,21 +64,33 @@ class ActivityPub::SynchronizeFollowersService < BaseService
Oj.dump(serialize_payload(follow, ActivityPub::UndoFollowSerializer))
end
def collection_items(collection_or_uri)
collection = fetch_collection(collection_or_uri)
return unless collection.is_a?(Hash)
# Only returns true if the whole collection has been processed
def process_collection!(collection_uri, max_pages: MAX_COLLECTION_PAGES)
collection = fetch_collection(collection_uri)
return false unless collection.is_a?(Hash)
collection = fetch_collection(collection['first']) if collection['first'].present?
return unless collection.is_a?(Hash)
# Abort if we'd have to paginate through more than one page of followers
return if collection['next'].present?
while collection.is_a?(Hash)
process_page!(as_array(collection_page_items(collection)))
max_pages -= 1
return true if collection['next'].blank? # We reached the end of the collection
return false if max_pages <= 0 # We reached our pages limit
collection = fetch_collection(collection['next'])
end
false
end
def collection_page_items(collection)
case collection['type']
when 'Collection', 'CollectionPage'
as_array(collection['items'])
collection['items']
when 'OrderedCollection', 'OrderedCollectionPage'
as_array(collection['orderedItems'])
collection['orderedItems']
end
end

View file

@ -55,6 +55,8 @@ ko:
too_soon: 너무 이릅니다. %{date} 이후로 지정해야 합니다
user:
attributes:
date_of_birth:
below_limit: 나이 제한보다 아래입니다
email:
blocked: 허용되지 않은 이메일 제공자입니다
unreachable: 존재하지 않는 것 같습니다

View file

@ -47,6 +47,7 @@ br:
demote: Argilañ
disable: Skornañ
disabled: Skornet
display_name: Anv diskouezet
domain: Domani
edit: Kemmañ
email: Postel
@ -66,6 +67,7 @@ br:
moderation:
active: Oberiant
all: Pep tra
disabled: Diweredekaet
pending: War ober
silenced: Bevennet
suspended: Astalet
@ -98,6 +100,7 @@ br:
action_logs:
action_types:
destroy_status: Dilemel ar c'hannad
reset_password_user: Adderaouekaat ar ger-tremen
update_status: Hizivaat ar c'hannad
actions:
destroy_status_html: Dilamet eo bet toud %{target} gant %{name}

View file

@ -85,25 +85,25 @@ lv:
title: Drošības atslēgas iespējotas
omniauth_callbacks:
failure: Nevarēja autentificēt tevi no %{kind}, jo "%{reason}".
success: Veiksmīgi autentificēts no %{kind} konta.
success: Sekmīgi autentificēts no %{kind} konta.
passwords:
no_token: Tu nevari piekļūt šai lapai, ja neesi saņēmis paroles atiestatīšanas e-pasta ziņojumu. Ja ienāci no paroles atiestatīšanas e-pasta, lūdzu, pārliecinies, vai izmanto visu norādīto URL.
send_instructions: Ja Tava e-pasta adrese ir mūsu datubāzē, pēc dažām minūtēm savā e-pasta adresē saņemsi paroles atkopes saiti. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
send_paranoid_instructions: Ja Tava e-pasta adrese ir mūsu datubāzē, pēc dažām minūtēm savā e-pasta adresē saņemsi paroles atkopes saiti. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
updated: Tava parole tika veiksmīgi nomainīta. Tagad esi pieteicies.
updated_not_active: Tava parole ir veiksmīgi nomainīta.
updated: Tava parole tika sekmīgi nomainīta. Tagad esi pieteicies.
updated_not_active: Tava parole tika sekmīgi nomainīta.
registrations:
destroyed: Visu labu! Tavs konts ir veiksmīgi atcelts. Mēs ceram tevi drīz atkal redzēt.
update_needs_confirmation: Tu veiksmīgi atjaunināji savu kontu, taču mums ir jāapliecina Tava jaunā e-pasta adrese. Lūgums pārbaudīt savu e-pastu un sekot apstiprinājuma saitei, lai apstiprinātu savu jauno e-pasta adresi. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
updated: Tavs konts ir veiksmīgi atjaunināts.
destroyed: Visu labu! Tavs konts ir sekmīgi atcelts. Mēs ceram Tevi drīz atkal redzēt.
update_needs_confirmation: Tu sekmīgi atjaunināji savu kontu, taču mums ir jāapliecina Tava jaunā e-pasta adrese. Lūgums pārbaudīt savu e-pastu un sekot apstiprinājuma saitei, lai apstiprinātu savu jauno e-pasta adresi. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
updated: Tavs konts tika sekmīgi atjaunināts.
sessions:
already_signed_out: Veiksmīgi izrakstījies.
signed_in: Veiksmīgi pieteicies.
signed_out: Veiksmīgi izrakstījies.
already_signed_out: Sekmīgi izrakstījies.
signed_in: Sekmīgi pierakstījies.
signed_out: Sekmīgi izrakstījies.
unlocks:
send_instructions: Pēc dažām minūtēm Tu saņemsi e-pasta ziņojumu ar norādēm, kā atslēgt savu kontu. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
send_paranoid_instructions: Ja Tavs konts pastāv, dažu minūšu laikā saņemsi e-pasta ziņojumu ar norādēm, kā to atslēgt. Lūgums pārbaudīt mēstuļu mapi, ja nesaņēmi šo e-pasta ziņojumu.
unlocked: Konts tika veiksmīgi atbloķēts. Lūgums pieteikties, lai turpinātu.
unlocked: Konts tika sekmīgi atslēgts. Lūgums pieteikties, lai turpinātu.
errors:
messages:
already_confirmed: jau tika apstiprināts, lūgums mēģināt pieteikties

View file

@ -153,7 +153,7 @@ ko:
position: 특정 상황에서 충돌이 발생할 경우 더 높은 역할이 충돌을 해결합니다. 특정 작업은 우선순위가 낮은 역할에 대해서만 수행될 수 있습니다
webhook:
events: 전송할 이벤트를 선택하세요
template: 원하는 JSON 페이로드를 변수와 함께 작성하거나, 그냥 냅둬서 기본 JSON을 사용할 수 있습니다.
template: 원하는 JSON 페이로드를 변수와 함께 작성하거나, 그대로 두어 기본 JSON을 사용할 수 있습니다.
url: 이벤트가 어디로 전송될 지
labels:
account:
@ -269,6 +269,7 @@ ko:
favicon: 파비콘
mascot: 사용자 정의 마스코트 (legacy)
media_cache_retention_period: 미디어 캐시 유지 기한
min_age: 최소 연령 제한
peers_api_enabled: API에 발견 된 서버들의 목록 발행
profile_directory: 프로필 책자 활성화
registrations_mode: 누가 가입할 수 있는지
@ -347,6 +348,9 @@ ko:
jurisdiction: 법적 관할권
min_age: 최소 연령
user:
date_of_birth_1i:
date_of_birth_2i:
date_of_birth_3i:
role: 역할
time_zone: 시간대
user_role:

View file

@ -169,7 +169,7 @@ RSpec.describe 'Notifications' do
end
context 'with min_id param' do
let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } }
let(:params) { { min_id: user.account.notifications.order(id: :asc).first.id - 1 } }
it 'returns a notification group covering all notifications' do
subject

View file

@ -10,7 +10,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
let(:bob) { Fabricate(:account, username: 'bob') }
let(:eve) { Fabricate(:account, username: 'eve') }
let(:mallory) { Fabricate(:account, username: 'mallory') }
let(:collection_uri) { 'http://example.com/partial-followers' }
let(:collection_uri) { 'https://example.com/partial-followers' }
let(:items) do
[alice, eve, mallory].map do |account|
@ -97,7 +97,76 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is a paginated Collection of actor URIs with a next page' do
context 'when the endpoint is a paginated Collection of actor URIs split across multiple pages' do
before do
stub_request(:get, 'https://example.com/partial-followers')
.to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: 'https://example.com/partial-followers',
first: 'https://example.com/partial-followers/1',
}))
stub_request(:get, 'https://example.com/partial-followers/1')
.to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'CollectionPage',
id: 'https://example.com/partial-followers/1',
partOf: 'https://example.com/partial-followers',
next: 'https://example.com/partial-followers/2',
items: [alice, eve].map { |account| ActivityPub::TagManager.instance.uri_for(account) },
}))
stub_request(:get, 'https://example.com/partial-followers/2')
.to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'CollectionPage',
id: 'https://example.com/partial-followers/2',
partOf: 'https://example.com/partial-followers',
items: ActivityPub::TagManager.instance.uri_for(mallory),
}))
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is a paginated Collection of actor URIs split across, but one page errors out' do
before do
stub_request(:get, 'https://example.com/partial-followers')
.to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: 'https://example.com/partial-followers',
first: 'https://example.com/partial-followers/1',
}))
stub_request(:get, 'https://example.com/partial-followers/1')
.to_return(status: 200, headers: { 'Content-Type': 'application/activity+json' }, body: Oj.dump({
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'CollectionPage',
id: 'https://example.com/partial-followers/1',
partOf: 'https://example.com/partial-followers',
next: 'https://example.com/partial-followers/2',
items: [mallory].map { |account| ActivityPub::TagManager.instance.uri_for(account) },
}))
stub_request(:get, 'https://example.com/partial-followers/2')
.to_return(status: 404)
end
it 'confirms pending follow request but does not remove extra followers' do
previous_follower_ids = actor.followers.pluck(:id)
subject.call(actor, collection_uri)
expect(previous_follower_ids - actor.followers.reload.pluck(:id))
.to be_empty
expect(mallory)
.to be_following(actor)
end
end
context 'when the endpoint is a paginated Collection of actor URIs with more pages than we allow' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
@ -113,12 +182,19 @@ RSpec.describe ActivityPub::SynchronizeFollowersService do
end
before do
stub_const('ActivityPub::SynchronizeFollowersService::MAX_COLLECTION_PAGES', 1)
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'does not change followers' do
expect { subject.call(actor, collection_uri) }
.to_not(change { actor.followers.reload.reorder(id: :asc).pluck(:id) })
it 'confirms pending follow request but does not remove extra followers' do
previous_follower_ids = actor.followers.pluck(:id)
subject.call(actor, collection_uri)
expect(previous_follower_ids - actor.followers.reload.pluck(:id))
.to be_empty
expect(mallory)
.to be_following(actor)
end
end
end