Merge branch 'master' into feature-circles

This commit is contained in:
Takeshi Umeda 2021-01-05 23:46:40 +09:00 committed by GitHub
commit 824d1b8893
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
906 changed files with 37100 additions and 11600 deletions

View file

@ -73,4 +73,84 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
expect(ProofProvider::Keybase::Worker).to have_received(:perform_async)
end
end
context 'when account is not suspended' do
let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') }
let(:payload) do
{
id: 'https://foo.test',
type: 'Actor',
inbox: 'https://foo.test/inbox',
suspended: true,
}.with_indifferent_access
end
before do
allow(Admin::SuspensionWorker).to receive(:perform_async)
end
subject { described_class.new.call('alice', 'example.com', payload) }
it 'suspends account remotely' do
expect(subject.suspended?).to be true
expect(subject.suspension_origin_remote?).to be true
end
it 'queues suspension worker' do
subject
expect(Admin::SuspensionWorker).to have_received(:perform_async)
end
end
context 'when account is suspended' do
let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com', display_name: '') }
let(:payload) do
{
id: 'https://foo.test',
type: 'Actor',
inbox: 'https://foo.test/inbox',
suspended: false,
name: 'Hoge',
}.with_indifferent_access
end
before do
allow(Admin::UnsuspensionWorker).to receive(:perform_async)
account.suspend!(origin: suspension_origin)
end
subject { described_class.new.call('alice', 'example.com', payload) }
context 'locally' do
let(:suspension_origin) { :local }
it 'does not unsuspend it' do
expect(subject.suspended?).to be true
end
it 'does not update any attributes' do
expect(subject.display_name).to_not eq 'Hoge'
end
end
context 'remotely' do
let(:suspension_origin) { :remote }
it 'unsuspends it' do
expect(subject.suspended?).to be false
end
it 'queues unsuspension worker' do
subject
expect(Admin::UnsuspensionWorker).to have_received(:perform_async)
end
it 'updates attributes' do
expect(subject.display_name).to eq 'Hoge'
end
end
end
end

View file

@ -22,7 +22,48 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
subject { described_class.new }
describe '#call' do
context 'when actor is the sender'
context 'when actor is suspended' do
before do
actor.suspend!(origin: :remote)
end
%w(Accept Add Announce Block Create Flag Follow Like Move Remove).each do |activity_type|
context "with #{activity_type} activity" do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: activity_type,
actor: ActivityPub::TagManager.instance.uri_for(actor),
}
end
it 'does not process payload' do
expect(ActivityPub::Activity).not_to receive(:factory)
subject.call(json, actor)
end
end
end
%w(Delete Reject Undo Update).each do |activity_type|
context "with #{activity_type} activity" do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: activity_type,
actor: ActivityPub::TagManager.instance.uri_for(actor),
}
end
it 'processes the payload' do
expect(ActivityPub::Activity).to receive(:factory)
subject.call(json, actor)
end
end
end
end
context 'when actor differs from sender' do
let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') }

View file

@ -0,0 +1,105 @@
require 'rails_helper'
RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') }
let(:alice) { Fabricate(:account, username: 'alice') }
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(:items) do
[
ActivityPub::TagManager.instance.uri_for(alice),
ActivityPub::TagManager.instance.uri_for(eve),
ActivityPub::TagManager.instance.uri_for(mallory),
]
end
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Collection',
id: collection_uri,
items: items,
}.with_indifferent_access
end
subject { described_class.new }
shared_examples 'synchronizes followers' do
before do
alice.follow!(actor)
bob.follow!(actor)
mallory.request_follow!(actor)
allow(ActivityPub::DeliveryWorker).to receive(:perform_async)
subject.call(actor, collection_uri)
end
it 'keeps expected followers' do
expect(alice.following?(actor)).to be true
end
it 'removes local followers not in the remote list' do
expect(bob.following?(actor)).to be false
end
it 'converts follow requests to follow relationships when they have been accepted' do
expect(mallory.following?(actor)).to be true
end
it 'sends an Undo Follow to the actor' do
expect(ActivityPub::DeliveryWorker).to have_received(:perform_async).with(anything, eve.id, actor.inbox_url)
end
end
describe '#call' do
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))
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))
end
it_behaves_like 'synchronizes followers'
end
context 'when the endpoint is a 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))
end
it_behaves_like 'synchronizes followers'
end
end
end

View file

@ -3,6 +3,7 @@ require 'rails_helper'
RSpec.describe AppSignUpService, type: :service do
let(:app) { Fabricate(:application, scopes: 'read write') }
let(:good_params) { { username: 'alice', password: '12345678', email: 'good@email.com', agreement: true } }
let(:remote_ip) { IPAddr.new('198.0.2.1') }
subject { described_class.new }
@ -10,16 +11,16 @@ RSpec.describe AppSignUpService, type: :service do
it 'returns nil when registrations are closed' do
tmp = Setting.registrations_mode
Setting.registrations_mode = 'none'
expect(subject.call(app, good_params)).to be_nil
expect(subject.call(app, remote_ip, good_params)).to be_nil
Setting.registrations_mode = tmp
end
it 'raises an error when params are missing' do
expect { subject.call(app, {}) }.to raise_error ActiveRecord::RecordInvalid
expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid
end
it 'creates an unconfirmed user with access token' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@ -27,13 +28,13 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates access token with the app\'s scopes' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
end
it 'creates an account' do
access_token = subject.call(app, good_params)
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
@ -42,7 +43,7 @@ RSpec.describe AppSignUpService, type: :service do
end
it 'creates an account with invite request text' do
access_token = subject.call(app, good_params.merge(reason: 'Foo bar'))
access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar'))
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil

View file

@ -26,6 +26,11 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
subject.call([status1, status2])
end
it 'removes statuses' do
expect { Status.find(status1.id) }.to raise_error ActiveRecord::RecordNotFound
expect { Status.find(status2.id) }.to raise_error ActiveRecord::RecordNotFound
end
it 'removes statuses from author\'s home feed' do
expect(HomeFeed.new(alice).get(10)).to_not include([status1.id, status2.id])
end
@ -38,10 +43,6 @@ RSpec.describe BatchedRemoveStatusService, type: :service do
expect(Redis.current).to have_received(:publish).with("timeline:#{jeff.id}", any_args).at_least(:once)
end
it 'notifies streaming API of author' do
expect(Redis.current).to have_received(:publish).with("timeline:#{alice.id}", any_args).at_least(:once)
end
it 'notifies streaming API of public timeline' do
expect(Redis.current).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
end

View file

@ -0,0 +1,96 @@
require 'rails_helper'
RSpec.describe DeleteAccountService, type: :service do
shared_examples 'common behavior' do
let!(:status) { Fabricate(:status, account: account) }
let!(:mention) { Fabricate(:mention, account: local_follower) }
let!(:status_with_mention) { Fabricate(:status, account: account, mentions: [mention]) }
let!(:media_attachment) { Fabricate(:media_attachment, account: account) }
let!(:notification) { Fabricate(:notification, account: account) }
let!(:favourite) { Fabricate(:favourite, account: account, status: Fabricate(:status, account: local_follower)) }
let!(:poll) { Fabricate(:poll, account: account) }
let!(:poll_vote) { Fabricate(:poll_vote, account: local_follower, poll: poll) }
let!(:active_relationship) { Fabricate(:follow, account: account, target_account: local_follower) }
let!(:passive_relationship) { Fabricate(:follow, account: local_follower, target_account: account) }
let!(:endorsement) { Fabricate(:account_pin, account: local_follower, target_account: account) }
let!(:mention_notification) { Fabricate(:notification, account: local_follower, activity: mention, type: :mention) }
let!(:status_notification) { Fabricate(:notification, account: local_follower, activity: status, type: :status) }
let!(:poll_notification) { Fabricate(:notification, account: local_follower, activity: poll, type: :poll) }
let!(:favourite_notification) { Fabricate(:notification, account: local_follower, activity: favourite, type: :favourite) }
let!(:follow_notification) { Fabricate(:notification, account: local_follower, activity: active_relationship, type: :follow) }
subject do
-> { described_class.new.call(account) }
end
it 'deletes associated owned records' do
is_expected.to change {
[
account.statuses,
account.media_attachments,
account.notifications,
account.favourites,
account.active_relationships,
account.passive_relationships,
account.polls,
].map(&:count)
}.from([2, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
end
it 'deletes associated target records' do
is_expected.to change {
[
AccountPin.where(target_account: account),
].map(&:count)
}.from([1]).to([0])
end
it 'deletes associated target notifications' do
is_expected.to change {
[
'poll', 'favourite', 'status', 'mention', 'follow'
].map { |type| Notification.where(type: type).count }
}.from([1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0])
end
end
describe '#call on local account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
include_examples 'common behavior' do
let!(:account) { Fabricate(:account) }
let!(:local_follower) { Fabricate(:account) }
it 'sends a delete actor activity to all known inboxes' do
subject.call
expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once
expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once
end
end
end
describe '#call on remote account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
include_examples 'common behavior' do
let!(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
let!(:local_follower) { Fabricate(:account) }
it 'sends a reject follow to follwer inboxes' do
subject.call
expect(a_request(:post, account.inbox_url)).to have_been_made.once
end
end
end
end

View file

@ -28,10 +28,10 @@ RSpec.describe FanOutOnWriteService, type: :service do
end
it 'delivers status to hashtag' do
expect(Tag.find_by!(name: 'test').statuses.pluck(:id)).to include status.id
expect(TagFeed.new(Tag.find_by(name: 'test'), alice).get(20).map(&:id)).to include status.id
end
it 'delivers status to public timeline' do
expect(Status.as_public_timeline(alice).map(&:id)).to include status.id
expect(PublicFeed.new(alice).get(20).map(&:id)).to include status.id
end
end

View file

@ -1,60 +0,0 @@
require 'rails_helper'
describe HashtagQueryService, type: :service do
describe '.call' do
let(:account) { Fabricate(:account) }
let(:tag1) { Fabricate(:tag) }
let(:tag2) { Fabricate(:tag) }
let!(:status1) { Fabricate(:status, tags: [tag1]) }
let!(:status2) { Fabricate(:status, tags: [tag2]) }
let!(:both) { Fabricate(:status, tags: [tag1, tag2]) }
it 'can add tags in "any" mode' do
results = subject.call(tag1, { any: [tag2.name] })
expect(results).to include status1
expect(results).to include status2
expect(results).to include both
end
it 'can remove tags in "all" mode' do
results = subject.call(tag1, { all: [tag2.name] })
expect(results).to_not include status1
expect(results).to_not include status2
expect(results).to include both
end
it 'can remove tags in "none" mode' do
results = subject.call(tag1, { none: [tag2.name] })
expect(results).to include status1
expect(results).to_not include status2
expect(results).to_not include both
end
it 'ignores an invalid mode' do
results = subject.call(tag1, { wark: [tag2.name] })
expect(results).to include status1
expect(results).to_not include status2
expect(results).to include both
end
it 'handles being passed non existant tag names' do
results = subject.call(tag1, { any: ['wark'] })
expect(results).to include status1
expect(results).to_not include status2
expect(results).to include both
end
it 'can restrict to an account' do
BlockService.new.call(account, status1.account)
results = subject.call(tag1, { none: [tag2.name] }, account)
expect(results).to_not include status1
end
it 'can restrict to local' do
status1.account.update(domain: 'example.com')
status1.update(local: false, uri: 'example.com/toot')
results = subject.call(tag1, { any: [tag2.name] }, nil, true)
expect(results).to_not include status1
end
end
end

View file

@ -1,6 +1,8 @@
require 'rails_helper'
RSpec.describe ImportService, type: :service do
include RoutingHelper
let!(:account) { Fabricate(:account, locked: false) }
let!(:bob) { Fabricate(:account, username: 'bob', locked: false) }
let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') }
@ -95,6 +97,7 @@ RSpec.describe ImportService, type: :service do
let(:import) { Import.create(account: account, type: 'following', data: csv) }
it 'follows the listed accounts, including boosts' do
subject.call(import)
expect(account.following.count).to eq 1
expect(account.follow_requests.count).to eq 1
expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true
@ -168,4 +171,44 @@ RSpec.describe ImportService, type: :service do
end
end
end
context 'import bookmarks' do
subject { ImportService.new }
let(:csv) { attachment_fixture('bookmark-imports.txt') }
around(:each) do |example|
local_before = Rails.configuration.x.local_domain
web_before = Rails.configuration.x.web_domain
Rails.configuration.x.local_domain = 'local.com'
Rails.configuration.x.web_domain = 'local.com'
example.run
Rails.configuration.x.web_domain = web_before
Rails.configuration.x.local_domain = local_before
end
let(:local_account) { Fabricate(:account, username: 'foo', domain: '') }
let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') }
let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) }
before do
service = double
allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service)
allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do
Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1')
end
end
describe 'when no bookmarks are set' do
let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) }
it 'adds the toots the user has access to to bookmarks' do
local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true)
subject.call(import)
expect(account.bookmarks.map(&:status).map(&:id)).to include(local_status.id)
expect(account.bookmarks.map(&:status).map(&:id)).to include(remote_status.id)
expect(account.bookmarks.map(&:status).map(&:id)).not_to include(direct_status.id)
expect(account.bookmarks.count).to eq 3
end
end
end
end

View file

@ -2,13 +2,14 @@ require 'rails_helper'
RSpec.describe NotifyService, type: :service do
subject do
-> { described_class.new.call(recipient, activity) }
-> { described_class.new.call(recipient, type, activity) }
end
let(:user) { Fabricate(:user) }
let(:recipient) { user.account }
let(:sender) { Fabricate(:account, domain: 'example.com') }
let(:activity) { Fabricate(:follow, account: sender, target_account: recipient) }
let(:type) { :follow }
it { is_expected.to change(Notification, :count).by(1) }
@ -50,6 +51,7 @@ RSpec.describe NotifyService, type: :service do
context 'for direct messages' do
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) }
let(:type) { :mention }
before do
user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled)
@ -93,6 +95,7 @@ RSpec.describe NotifyService, type: :service do
describe 'reblogs' do
let(:status) { Fabricate(:status, account: Fabricate(:account)) }
let(:activity) { Fabricate(:status, account: sender, reblog: status) }
let(:type) { :reblog }
it 'shows reblogs by default' do
recipient.follow!(sender)
@ -114,6 +117,7 @@ RSpec.describe NotifyService, type: :service do
let(:asshole) { Fabricate(:account, username: 'asshole') }
let(:reply_to) { Fabricate(:status, account: asshole) }
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, thread: reply_to)) }
let(:type) { :mention }
it 'does not notify when conversation is muted' do
recipient.mute_conversation!(activity.status.conversation)

View file

@ -3,7 +3,7 @@ require 'rails_helper'
RSpec.describe RemoveStatusService, type: :service do
subject { RemoveStatusService.new }
let!(:alice) { Fabricate(:account) }
let!(:alice) { Fabricate(:account, user: Fabricate(:user)) }
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', salmon_url: 'http://example.com/salmon') }
let!(:jeff) { Fabricate(:account) }
let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
@ -17,23 +17,33 @@ RSpec.describe RemoveStatusService, type: :service do
hank.follow!(alice)
@status = PostStatusService.new.call(alice, text: 'Hello @bob@example.com')
FavouriteService.new.call(jeff, @status)
Fabricate(:status, account: bill, reblog: @status, uri: 'hoge')
subject.call(@status)
end
it 'removes status from author\'s home feed' do
subject.call(@status)
expect(HomeFeed.new(alice).get(10)).to_not include(@status.id)
end
it 'removes status from local follower\'s home feed' do
subject.call(@status)
expect(HomeFeed.new(jeff).get(10)).to_not include(@status.id)
end
it 'sends delete activity to followers' do
subject.call(@status)
expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
end
it 'sends delete activity to rebloggers' do
subject.call(@status)
expect(a_request(:post, 'http://example2.com/inbox')).to have_been_made
end
it 'remove status from notifications' do
expect { subject.call(@status) }.to change {
Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count
}.from(1).to(0)
end
end

View file

@ -4,23 +4,101 @@ RSpec.describe ResolveAccountService, type: :service do
subject { described_class.new }
before do
stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com").to_return(status: 404)
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt'))
stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
stub_request(:get, "https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com").to_return(request_fixture('activitypub-webfinger.txt'))
stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor.txt'))
stub_request(:get, "https://ap.example.com/users/foo.atom").to_return(request_fixture('activitypub-feed.txt'))
stub_request(:get, %r{https://ap.example.com/users/foo/\w+}).to_return(status: 404)
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410)
end
it 'raises error if no such user can be resolved via webfinger' do
expect(subject.call('catsrgr8@quitter.no')).to be_nil
context 'when there is an LRDD endpoint but no resolvable account' do
before do
stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt'))
stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404)
end
it 'returns nil' do
expect(subject.call('catsrgr8@quitter.no')).to be_nil
end
end
it 'raises error if the domain does not have webfinger' do
expect(subject.call('catsrgr8@example.com')).to be_nil
context 'when there is no LRDD endpoint nor resolvable account' do
before do
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com").to_return(status: 404)
end
it 'returns nil' do
expect(subject.call('catsrgr8@example.com')).to be_nil
end
end
context 'when webfinger returns http gone' do
context 'for a previously known account' do
before do
Fabricate(:account, username: 'hoge', domain: 'example.com', last_webfingered_at: nil)
allow(AccountDeletionWorker).to receive(:perform_async)
end
it 'returns nil' do
expect(subject.call('hoge@example.com')).to be_nil
end
it 'queues account deletion worker' do
subject.call('hoge@example.com')
expect(AccountDeletionWorker).to have_received(:perform_async)
end
end
context 'for a previously unknown account' do
it 'returns nil' do
expect(subject.call('hoge@example.com')).to be_nil
end
end
end
context 'with a legitimate webfinger redirection' do
before do
webfinger = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'returns new remote account' do
account = subject.call('Foo@redirected.example.com')
expect(account.activitypub?).to eq true
expect(account.acct).to eq 'foo@ap.example.com'
expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
end
end
context 'with a misconfigured redirection' do
before do
webfinger = { subject: 'acct:Foo@redirected.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'returns new remote account' do
account = subject.call('Foo@redirected.example.com')
expect(account.activitypub?).to eq true
expect(account.acct).to eq 'foo@ap.example.com'
expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
end
end
context 'with too many webfinger redirections' do
before do
webfinger = { subject: 'acct:foo@evil.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://redirected.example.com/.well-known/webfinger?resource=acct:Foo@redirected.example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
webfinger2 = { subject: 'acct:foo@ap.example.com', links: [{ rel: 'self', href: 'https://ap.example.com/users/foo', type: 'application/activity+json' }] }
stub_request(:get, 'https://evil.example.com/.well-known/webfinger?resource=acct:foo@evil.example.com').to_return(body: Oj.dump(webfinger2), headers: { 'Content-Type': 'application/jrd+json' })
end
it 'returns new remote account' do
expect { subject.call('Foo@redirected.example.com') }.to raise_error Webfinger::RedirectError
end
end
context 'with an ActivityPub account' do
@ -48,6 +126,41 @@ RSpec.describe ResolveAccountService, type: :service do
end
end
context 'with an already-known actor changing acct: URI' do
let!(:duplicate) { Fabricate(:account, username: 'foo', domain: 'old.example.com', uri: 'https://ap.example.com/users/foo') }
let!(:status) { Fabricate(:status, account: duplicate, text: 'foo') }
it 'returns new remote account' do
account = subject.call('foo@ap.example.com')
expect(account.activitypub?).to eq true
expect(account.domain).to eq 'ap.example.com'
expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
expect(account.uri).to eq 'https://ap.example.com/users/foo'
end
it 'merges accounts' do
account = subject.call('foo@ap.example.com')
expect(status.reload.account_id).to eq account.id
expect(Account.where(uri: account.uri).count).to eq 1
end
end
context 'with an already-known acct: URI changing ActivityPub id' do
let!(:old_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', uri: 'https://old.example.com/users/foo', last_webfingered_at: nil) }
let!(:status) { Fabricate(:status, account: old_account, text: 'foo') }
it 'returns new remote account' do
account = subject.call('foo@ap.example.com')
expect(account.activitypub?).to eq true
expect(account.domain).to eq 'ap.example.com'
expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
expect(account.uri).to eq 'https://ap.example.com/users/foo'
end
end
it 'processes one remote account at a time using locks' do
wait_for_start = true
fail_occurred = false

View file

@ -15,5 +15,102 @@ describe ResolveURLService, type: :service do
expect(subject.call(url)).to be_nil
end
context 'searching for a remote private status' do
let(:account) { Fabricate(:account) }
let(:poster) { Fabricate(:account, domain: 'example.com') }
let(:url) { 'https://example.com/@foo/42' }
let(:uri) { 'https://example.com/users/foo/statuses/42' }
let!(:status) { Fabricate(:status, url: url, uri: uri, account: poster, visibility: :private) }
before do
stub_request(:get, url).to_return(status: 404) if url.present?
stub_request(:get, uri).to_return(status: 404)
end
context 'when the account follows the poster' do
before do
account.follow!(poster)
end
context 'when the status uses Mastodon-style URLs' do
let(:url) { 'https://example.com/@foo/42' }
let(:uri) { 'https://example.com/users/foo/statuses/42' }
it 'returns status by url' do
expect(subject.call(url, on_behalf_of: account)).to eq(status)
end
it 'returns status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to eq(status)
end
end
context 'when the status uses pleroma-style URLs' do
let(:url) { nil }
let(:uri) { 'https://example.com/objects/0123-456-789-abc-def' }
it 'returns status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to eq(status)
end
end
end
context 'when the account does not follow the poster' do
context 'when the status uses Mastodon-style URLs' do
let(:url) { 'https://example.com/@foo/42' }
let(:uri) { 'https://example.com/users/foo/statuses/42' }
it 'does not return the status by url' do
expect(subject.call(url, on_behalf_of: account)).to be_nil
end
it 'does not return the status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to be_nil
end
end
context 'when the status uses pleroma-style URLs' do
let(:url) { nil }
let(:uri) { 'https://example.com/objects/0123-456-789-abc-def' }
it 'returns status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to be_nil
end
end
end
end
context 'searching for a local private status' do
let(:account) { Fabricate(:account) }
let(:poster) { Fabricate(:account) }
let!(:status) { Fabricate(:status, account: poster, visibility: :private) }
let(:url) { ActivityPub::TagManager.instance.url_for(status) }
let(:uri) { ActivityPub::TagManager.instance.uri_for(status) }
context 'when the account follows the poster' do
before do
account.follow!(poster)
end
it 'returns status by url' do
expect(subject.call(url, on_behalf_of: account)).to eq(status)
end
it 'returns status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to eq(status)
end
end
context 'when the account does not follow the poster' do
it 'does not return the status by url' do
expect(subject.call(url, on_behalf_of: account)).to be_nil
end
it 'does not return the status by uri' do
expect(subject.call(uri, on_behalf_of: account)).to be_nil
end
end
end
end
end

View file

@ -1,84 +0,0 @@
require 'rails_helper'
RSpec.describe SuspendAccountService, type: :service do
describe '#call on local account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
subject do
-> { described_class.new.call(account) }
end
let!(:account) { Fabricate(:account) }
let!(:status) { Fabricate(:status, account: account) }
let!(:media_attachment) { Fabricate(:media_attachment, account: account) }
let!(:notification) { Fabricate(:notification, account: account) }
let!(:favourite) { Fabricate(:favourite, account: account) }
let!(:active_relationship) { Fabricate(:follow, account: account) }
let!(:passive_relationship) { Fabricate(:follow, target_account: account) }
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
let!(:endorsment) { Fabricate(:account_pin, account: passive_relationship.account, target_account: account) }
it 'deletes associated records' do
is_expected.to change {
[
account.statuses,
account.media_attachments,
account.notifications,
account.favourites,
account.active_relationships,
account.passive_relationships,
AccountPin.where(target_account: account),
].map(&:count)
}.from([1, 1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0, 0])
end
it 'sends a delete actor activity to all known inboxes' do
subject.call
expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once
expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once
end
end
describe '#call on remote account' do
before do
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
stub_request(:post, "https://bob.com/inbox").to_return(status: 201)
end
subject do
-> { described_class.new.call(remote_bob) }
end
let!(:account) { Fabricate(:account) }
let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) }
let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) }
let!(:status) { Fabricate(:status, account: remote_bob) }
let!(:media_attachment) { Fabricate(:media_attachment, account: remote_bob) }
let!(:notification) { Fabricate(:notification, account: remote_bob) }
let!(:favourite) { Fabricate(:favourite, account: remote_bob) }
let!(:active_relationship) { Fabricate(:follow, account: remote_bob, target_account: account) }
let!(:passive_relationship) { Fabricate(:follow, target_account: remote_bob) }
it 'deletes associated records' do
is_expected.to change {
[
remote_bob.statuses,
remote_bob.media_attachments,
remote_bob.notifications,
remote_bob.favourites,
remote_bob.active_relationships,
remote_bob.passive_relationships,
].map(&:count)
}.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
end
it 'sends a reject follow to follwer inboxes' do
subject.call
expect(a_request(:post, remote_bob.inbox_url)).to have_been_made.once
end
end
end

View file

@ -55,9 +55,9 @@ RSpec.describe UnallowDomainService, type: :service do
end
it 'removes the remote accounts\'s statuses and media attachments' do
expect { bad_status1.reload }.to_not raise_exception ActiveRecord::RecordNotFound
expect { bad_status2.reload }.to_not raise_exception ActiveRecord::RecordNotFound
expect { bad_attachment.reload }.to_not raise_exception ActiveRecord::RecordNotFound
expect { bad_status1.reload }.to_not raise_error
expect { bad_status2.reload }.to_not raise_error
expect { bad_attachment.reload }.to_not raise_error
end
end
end