aboutsummaryrefslogtreecommitdiff
path: root/spec/services
diff options
context:
space:
mode:
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/activitypub/process_account_service_spec.rb80
-rw-r--r--spec/services/activitypub/process_collection_service_spec.rb43
-rw-r--r--spec/services/activitypub/synchronize_followers_service_spec.rb105
-rw-r--r--spec/services/app_sign_up_service_spec.rb13
-rw-r--r--spec/services/batched_remove_status_service_spec.rb9
-rw-r--r--spec/services/delete_account_service_spec.rb96
-rw-r--r--spec/services/fan_out_on_write_service_spec.rb4
-rw-r--r--spec/services/hashtag_query_service_spec.rb60
-rw-r--r--spec/services/import_service_spec.rb43
-rw-r--r--spec/services/notify_service_spec.rb6
-rw-r--r--spec/services/remove_status_service_spec.rb14
-rw-r--r--spec/services/resolve_account_service_spec.rb127
-rw-r--r--spec/services/resolve_url_service_spec.rb97
-rw-r--r--spec/services/suspend_account_service_spec.rb84
-rw-r--r--spec/services/unallow_domain_service_spec.rb6
15 files changed, 617 insertions, 170 deletions
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index 5141e3f16..56e7f8321 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -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
diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb
index b3baf6b6b..00d71a86a 100644
--- a/spec/services/activitypub/process_collection_service_spec.rb
+++ b/spec/services/activitypub/process_collection_service_spec.rb
@@ -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') }
diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb
new file mode 100644
index 000000000..75dcf204b
--- /dev/null
+++ b/spec/services/activitypub/synchronize_followers_service_spec.rb
@@ -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
diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb
index e7c7f3ba1..e0c83b704 100644
--- a/spec/services/app_sign_up_service_spec.rb
+++ b/spec/services/app_sign_up_service_spec.rb
@@ -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
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index f84256f18..c1f54a6fd 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -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
diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb
new file mode 100644
index 000000000..cd7d32d59
--- /dev/null
+++ b/spec/services/delete_account_service_spec.rb
@@ -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
diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb
index b7fc7f7ed..538dc2592 100644
--- a/spec/services/fan_out_on_write_service_spec.rb
+++ b/spec/services/fan_out_on_write_service_spec.rb
@@ -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
diff --git a/spec/services/hashtag_query_service_spec.rb b/spec/services/hashtag_query_service_spec.rb
deleted file mode 100644
index 24282d2f0..000000000
--- a/spec/services/hashtag_query_service_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb
index 7618e9076..764225aa7 100644
--- a/spec/services/import_service_spec.rb
+++ b/spec/services/import_service_spec.rb
@@ -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
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 440018ac9..f2cb22c5e 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -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)
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 06676ec45..7ce75b2c7 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -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
diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index cea942e39..a604e90b5 100644
--- a/spec/services/resolve_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -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
+
+ 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
- it 'raises error if the domain does not have webfinger' do
- expect(subject.call('catsrgr8@example.com')).to be_nil
+ 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
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index aa4204637..a38b23590 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -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
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
deleted file mode 100644
index 32726d763..000000000
--- a/spec/services/suspend_account_service_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/services/unallow_domain_service_spec.rb b/spec/services/unallow_domain_service_spec.rb
index 559e152fb..b93945b9a 100644
--- a/spec/services/unallow_domain_service_spec.rb
+++ b/spec/services/unallow_domain_service_spec.rb
@@ -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