Merge branch 'master' into feature-circles
This commit is contained in:
commit
824d1b8893
906 changed files with 37100 additions and 11600 deletions
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
||||
|
|
105
spec/services/activitypub/synchronize_followers_service_spec.rb
Normal file
105
spec/services/activitypub/synchronize_followers_service_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
96
spec/services/delete_account_service_spec.rb
Normal file
96
spec/services/delete_account_service_spec.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue