aboutsummaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/account_follow_controller_spec.rb48
-rw-r--r--spec/controllers/account_unfollow_controller_spec.rb48
-rw-r--r--spec/controllers/accounts_controller_spec.rb88
-rw-r--r--spec/controllers/activitypub/collections_controller_spec.rb32
-rw-r--r--spec/controllers/activitypub/followers_synchronizations_controller_spec.rb77
-rw-r--r--spec/controllers/activitypub/inboxes_controller_spec.rb77
-rw-r--r--spec/controllers/activitypub/outboxes_controller_spec.rb58
-rw-r--r--spec/controllers/activitypub/replies_controller_spec.rb39
-rw-r--r--spec/controllers/admin/instances_controller_spec.rb6
-rw-r--r--spec/controllers/api/v1/accounts_controller_spec.rb84
-rw-r--r--spec/controllers/api/v1/admin/accounts_controller_spec.rb20
-rw-r--r--spec/controllers/api/v1/statuses/favourites_controller_spec.rb25
-rw-r--r--spec/controllers/auth/registrations_controller_spec.rb12
-rw-r--r--spec/controllers/auth/sessions_controller_spec.rb14
-rw-r--r--spec/controllers/concerns/export_controller_concern_spec.rb1
-rw-r--r--spec/controllers/follower_accounts_controller_spec.rb63
-rw-r--r--spec/controllers/following_accounts_controller_spec.rb63
-rw-r--r--spec/controllers/remote_follow_controller_spec.rb43
-rw-r--r--spec/controllers/settings/deletes_controller_spec.rb28
-rw-r--r--spec/controllers/settings/exports/bookmarks_controller_specs.rb17
-rw-r--r--spec/controllers/statuses_controller_spec.rb38
-rw-r--r--spec/controllers/well_known/host_meta_controller_spec.rb2
-rw-r--r--spec/controllers/well_known/webfinger_controller_spec.rb167
-rw-r--r--spec/fabricators/account_deletion_request_fabricator.rb3
-rw-r--r--spec/fabricators/ip_block_fabricator.rb6
-rw-r--r--spec/fixtures/files/bookmark-imports.txt4
-rw-r--r--spec/helpers/statuses_helper_spec.rb18
-rw-r--r--spec/lib/fast_ip_map_spec.rb21
-rw-r--r--spec/lib/feed_manager_spec.rb182
-rw-r--r--spec/lib/spam_check_spec.rb8
-rw-r--r--spec/models/account_deletion_request_spec.rb4
-rw-r--r--spec/models/account_filter_spec.rb2
-rw-r--r--spec/models/account_spec.rb44
-rw-r--r--spec/models/admin/account_action_spec.rb8
-rw-r--r--spec/models/concerns/account_interactions_spec.rb45
-rw-r--r--spec/models/follow_request_spec.rb2
-rw-r--r--spec/models/follow_spec.rb2
-rw-r--r--spec/models/import_spec.rb10
-rw-r--r--spec/models/invite_spec.rb2
-rw-r--r--spec/models/ip_block_spec.rb5
-rw-r--r--spec/models/public_feed_spec.rb212
-rw-r--r--spec/models/status_spec.rb235
-rw-r--r--spec/models/tag_feed_spec.rb (renamed from spec/services/hashtag_query_service_spec.rb)26
-rw-r--r--spec/models/webauthn_credentials_spec.rb4
-rw-r--r--spec/policies/account_policy_spec.rb35
-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/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
-rw-r--r--spec/workers/activitypub/delivery_worker_spec.rb10
-rw-r--r--spec/workers/refollow_worker_spec.rb4
-rw-r--r--spec/workers/scheduler/feed_cleanup_scheduler_spec.rb4
62 files changed, 2000 insertions, 673 deletions
diff --git a/spec/controllers/account_follow_controller_spec.rb b/spec/controllers/account_follow_controller_spec.rb
index 9a93e1ebe..d33cd0499 100644
--- a/spec/controllers/account_follow_controller_spec.rb
+++ b/spec/controllers/account_follow_controller_spec.rb
@@ -16,17 +16,49 @@ describe AccountFollowController do
allow(service).to receive(:call)
end
- it 'does not create for user who is not signed in' do
- subject
- expect(FollowService).not_to receive(:new)
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ subject
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ subject
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when signed out' do
+ before do
+ subject
+ end
+
+ it 'does not follow' do
+ expect(FollowService).not_to receive(:new)
+ end
end
- it 'redirects to account path' do
- sign_in(user)
- subject
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ subject
+ end
- expect(service).to have_received(:call).with(user.account, alice, with_rate_limit: true)
- expect(response).to redirect_to(account_path(alice))
+ it 'redirects to account path' do
+ expect(service).to have_received(:call).with(user.account, alice, with_rate_limit: true)
+ expect(response).to redirect_to(account_path(alice))
+ end
end
end
end
diff --git a/spec/controllers/account_unfollow_controller_spec.rb b/spec/controllers/account_unfollow_controller_spec.rb
index bdebcfa94..a11f7aa68 100644
--- a/spec/controllers/account_unfollow_controller_spec.rb
+++ b/spec/controllers/account_unfollow_controller_spec.rb
@@ -16,17 +16,49 @@ describe AccountUnfollowController do
allow(service).to receive(:call)
end
- it 'does not create for user who is not signed in' do
- subject
- expect(UnfollowService).not_to receive(:new)
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ subject
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ subject
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when signed out' do
+ before do
+ subject
+ end
+
+ it 'does not unfollow' do
+ expect(UnfollowService).not_to receive(:new)
+ end
end
- it 'redirects to account path' do
- sign_in(user)
- subject
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ subject
+ end
- expect(service).to have_received(:call).with(user.account, alice)
- expect(response).to redirect_to(account_path(alice))
+ it 'redirects to account path' do
+ expect(service).to have_received(:call).with(user.account, alice)
+ expect(response).to redirect_to(account_path(alice))
+ end
end
end
end
diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 93bf2c83f..f7d0b1af5 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -48,10 +48,17 @@ RSpec.describe AccountsController, type: :controller do
expect(response).to have_http_status(404)
end
end
+ end
- context 'when account is suspended' do
+ context 'as HTML' do
+ let(:format) { 'html' }
+
+ it_behaves_like 'preliminary checks'
+
+ context 'when account is permanently suspended' do
before do
account.suspend!
+ account.deletion_request.destroy
end
it 'returns http gone' do
@@ -59,12 +66,17 @@ RSpec.describe AccountsController, type: :controller do
expect(response).to have_http_status(410)
end
end
- end
- context 'as HTML' do
- let(:format) { 'html' }
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
- it_behaves_like 'preliminary checks'
+ it 'returns http forbidden' do
+ get :show, params: { username: account.username, format: format }
+ expect(response).to have_http_status(403)
+ end
+ end
shared_examples 'common response characteristics' do
it 'returns http success' do
@@ -325,6 +337,29 @@ RSpec.describe AccountsController, type: :controller do
it_behaves_like 'preliminary checks'
+ context 'when account is suspended permanently' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ get :show, params: { username: account.username, format: format }
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is suspended temporarily' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http success' do
+ get :show, params: { username: account.username, format: format }
+ expect(response).to have_http_status(200)
+ end
+ end
+
context do
before do
get :show, params: { username: account.username, format: format }
@@ -348,24 +383,8 @@ RSpec.describe AccountsController, type: :controller do
context 'in authorized fetch mode' do
let(:authorized_fetch_mode) { true }
- it 'returns http success' do
- expect(response).to have_http_status(200)
- end
-
- it 'returns application/activity+json' do
- expect(response.content_type).to eq 'application/activity+json'
- end
-
- it_behaves_like 'cachable response'
-
- it 'returns Vary header with Signature' do
- expect(response.headers['Vary']).to include 'Signature'
- end
-
- it 'renders bare minimum account' do
- json = body_as_json
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey)
- expect(json).to_not include(:name, :summary)
+ it 'returns http unauthorized' do
+ expect(response).to have_http_status(401)
end
end
end
@@ -451,6 +470,29 @@ RSpec.describe AccountsController, type: :controller do
it_behaves_like 'preliminary checks'
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ get :show, params: { username: account.username, format: format }
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ get :show, params: { username: account.username, format: format }
+ expect(response).to have_http_status(403)
+ end
+ end
+
shared_examples 'common response characteristics' do
it 'returns http success' do
expect(response).to have_http_status(200)
diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb
index 89939d1d2..ac661e5e1 100644
--- a/spec/controllers/activitypub/collections_controller_spec.rb
+++ b/spec/controllers/activitypub/collections_controller_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
end
it 'does not set sessions' do
+ response
expect(session).to be_empty
end
@@ -34,9 +35,8 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
context 'without signature' do
let(:remote_account) { nil }
- before do
- get :show, params: { id: 'featured', account_username: account.username }
- end
+ subject(:response) { get :show, params: { id: 'featured', account_username: account.username } }
+ subject(:body) { body_as_json }
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -49,9 +49,29 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
it_behaves_like 'cachable response'
it 'returns orderedItems with pinned statuses' do
- json = body_as_json
- expect(json[:orderedItems]).to be_an Array
- expect(json[:orderedItems].size).to eq 2
+ expect(body[:orderedItems]).to be_an Array
+ expect(body[:orderedItems].size).to eq 2
+ end
+
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
end
end
diff --git a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
new file mode 100644
index 000000000..88f4554c2
--- /dev/null
+++ b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb
@@ -0,0 +1,77 @@
+require 'rails_helper'
+
+RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controller do
+ let!(:account) { Fabricate(:account) }
+ let!(:follower_1) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
+ let!(:follower_2) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
+ let!(:follower_3) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
+
+ before do
+ follower_1.follow!(account)
+ follower_2.follow!(account)
+ follower_3.follow!(account)
+ end
+
+ before do
+ allow(controller).to receive(:signed_request_account).and_return(remote_account)
+ end
+
+ describe 'GET #show' do
+ context 'without signature' do
+ let(:remote_account) { nil }
+
+ before do
+ get :show, params: { account_username: account.username }
+ end
+
+ it 'returns http not authorized' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'with signature from example.com' do
+ let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') }
+
+ subject(:response) { get :show, params: { account_username: account.username } }
+ subject(:body) { body_as_json }
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns application/activity+json' do
+ expect(response.content_type).to eq 'application/activity+json'
+ end
+
+ it 'returns orderedItems with followers from example.com' do
+ expect(body[:orderedItems]).to be_an Array
+ expect(body[:orderedItems].sort).to eq [follower_1.uri, follower_2.uri]
+ end
+
+ it 'returns private Cache-Control header' do
+ expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
+ end
+
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/activitypub/inboxes_controller_spec.rb b/spec/controllers/activitypub/inboxes_controller_spec.rb
index f3bc23953..973ad83bb 100644
--- a/spec/controllers/activitypub/inboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/inboxes_controller_spec.rb
@@ -20,6 +20,83 @@ RSpec.describe ActivityPub::InboxesController, type: :controller do
it 'returns http accepted' do
expect(response).to have_http_status(202)
end
+
+ context 'for a specific account' do
+ let(:account) { Fabricate(:account) }
+
+ subject(:response) { post :create, params: { account_username: account.username }, body: '{}' }
+
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http accepted' do
+ expect(response).to have_http_status(202)
+ end
+ end
+ end
+ end
+
+ context 'with Collection-Synchronization header' do
+ let(:remote_account) { Fabricate(:account, followers_url: 'https://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor', protocol: :activitypub) }
+ let(:synchronization_collection) { remote_account.followers_url }
+ let(:synchronization_url) { 'https://example.com/followers-for-domain' }
+ let(:synchronization_hash) { 'somehash' }
+ let(:synchronization_header) { "collectionId=\"#{synchronization_collection}\", digest=\"#{synchronization_hash}\", url=\"#{synchronization_url}\"" }
+
+ before do
+ allow(ActivityPub::FollowersSynchronizationWorker).to receive(:perform_async).and_return(nil)
+ allow_any_instance_of(Account).to receive(:local_followers_hash).and_return('somehash')
+
+ request.headers['Collection-Synchronization'] = synchronization_header
+ post :create, body: '{}'
+ end
+
+ context 'with mismatching target collection' do
+ let(:synchronization_collection) { 'https://example.com/followers2' }
+
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with mismatching domain in partial collection attribute' do
+ let(:synchronization_url) { 'https://example.org/followers' }
+
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with matching digest' do
+ it 'does not start a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).not_to have_received(:perform_async)
+ end
+ end
+
+ context 'with mismatching digest' do
+ let(:synchronization_hash) { 'wronghash' }
+
+ it 'starts a synchronization job' do
+ expect(ActivityPub::FollowersSynchronizationWorker).to have_received(:perform_async)
+ end
+ end
+
+ it 'returns http accepted' do
+ expect(response).to have_http_status(202)
+ end
end
context 'without signature' do
diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb
index 1baf5a623..84e3a8956 100644
--- a/spec/controllers/activitypub/outboxes_controller_spec.rb
+++ b/spec/controllers/activitypub/outboxes_controller_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
end
it 'does not set sessions' do
+ response
expect(session).to be_empty
end
@@ -34,9 +35,8 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
context 'without signature' do
let(:remote_account) { nil }
- before do
- get :show, params: { account_username: account.username, page: page }
- end
+ subject(:response) { get :show, params: { account_username: account.username, page: page } }
+ subject(:body) { body_as_json }
context 'with page not requested' do
let(:page) { nil }
@@ -50,11 +50,31 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
end
it 'returns totalItems' do
- json = body_as_json
- expect(json[:totalItems]).to eq 4
+ expect(body[:totalItems]).to eq 4
end
it_behaves_like 'cachable response'
+
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
context 'with page requested' do
@@ -69,13 +89,33 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
end
it 'returns orderedItems with public or unlisted statuses' do
- json = body_as_json
- expect(json[:orderedItems]).to be_an Array
- expect(json[:orderedItems].size).to eq 2
- expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
+ expect(body[:orderedItems]).to be_an Array
+ expect(body[:orderedItems].size).to eq 2
+ expect(body[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
it_behaves_like 'cachable response'
+
+ context 'when account is permanently suspended' do
+ before do
+ account.suspend!
+ account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
end
diff --git a/spec/controllers/activitypub/replies_controller_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb
index ed383864d..250259752 100644
--- a/spec/controllers/activitypub/replies_controller_spec.rb
+++ b/spec/controllers/activitypub/replies_controller_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
end
it 'does not set sessions' do
+ response
expect(session).to be_empty
end
@@ -36,8 +37,32 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
describe 'GET #index' do
context 'with no signature' do
- before do
- get :index, params: { account_username: status.account.username, status_id: status.id }
+ subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id } }
+ subject(:body) { body_as_json }
+
+ context 'when account is permanently suspended' do
+ let(:parent_visibility) { :public }
+
+ before do
+ status.account.suspend!
+ status.account.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ let(:parent_visibility) { :public }
+
+ before do
+ status.account.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
end
context 'when status is public' do
@@ -54,12 +79,10 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
it_behaves_like 'cachable response'
it 'returns items with account\'s own replies' do
- json = body_as_json
-
- expect(json[:first]).to be_a Hash
- expect(json[:first][:items]).to be_an Array
- expect(json[:first][:items].size).to eq 1
- expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
+ expect(body[:first]).to be_a Hash
+ expect(body[:first][:items]).to be_an Array
+ expect(body[:first][:items].size).to eq 1
+ expect(body[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
end
diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb
index 412b81443..8c0b309f2 100644
--- a/spec/controllers/admin/instances_controller_spec.rb
+++ b/spec/controllers/admin/instances_controller_spec.rb
@@ -9,10 +9,10 @@ RSpec.describe Admin::InstancesController, type: :controller do
describe 'GET #index' do
around do |example|
- default_per_page = Account.default_per_page
- Account.paginates_per 1
+ default_per_page = Instance.default_per_page
+ Instance.paginates_per 1
example.run
- Account.paginates_per default_per_page
+ Instance.paginates_per default_per_page
end
it 'renders instances' do
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index 024409dab..1e656503f 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -71,50 +71,80 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob', locked: locked)).account }
- before do
- post :follow, params: { id: other_account.id }
- end
+ context do
+ before do
+ post :follow, params: { id: other_account.id }
+ end
- context 'with unlocked account' do
- let(:locked) { false }
+ context 'with unlocked account' do
+ let(:locked) { false }
- it 'returns http success' do
- expect(response).to have_http_status(200)
- end
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
- it 'returns JSON with following=true and requested=false' do
- json = body_as_json
+ it 'returns JSON with following=true and requested=false' do
+ json = body_as_json
- expect(json[:following]).to be true
- expect(json[:requested]).to be false
- end
+ expect(json[:following]).to be true
+ expect(json[:requested]).to be false
+ end
+
+ it 'creates a following relation between user and target user' do
+ expect(user.account.following?(other_account)).to be true
+ end
- it 'creates a following relation between user and target user' do
- expect(user.account.following?(other_account)).to be true
+ it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
- it_behaves_like 'forbidden for wrong scope', 'read:accounts'
+ context 'with locked account' do
+ let(:locked) { true }
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns JSON with following=false and requested=true' do
+ json = body_as_json
+
+ expect(json[:following]).to be false
+ expect(json[:requested]).to be true
+ end
+
+ it 'creates a follow request relation between user and target user' do
+ expect(user.account.requested?(other_account)).to be true
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:accounts'
+ end
end
- context 'with locked account' do
- let(:locked) { true }
+ context 'modifying follow options' do
+ let(:locked) { false }
- it 'returns http success' do
- expect(response).to have_http_status(200)
+ before do
+ user.account.follow!(other_account, reblogs: false, notify: false)
end
- it 'returns JSON with following=false and requested=true' do
+ it 'changes reblogs option' do
+ post :follow, params: { id: other_account.id, reblogs: true }
+
json = body_as_json
- expect(json[:following]).to be false
- expect(json[:requested]).to be true
+ expect(json[:following]).to be true
+ expect(json[:showing_reblogs]).to be true
+ expect(json[:notifying]).to be false
end
- it 'creates a follow request relation between user and target user' do
- expect(user.account.requested?(other_account)).to be true
- end
+ it 'changes notify option' do
+ post :follow, params: { id: other_account.id, notify: true }
+
+ json = body_as_json
- it_behaves_like 'forbidden for wrong scope', 'read:accounts'
+ expect(json[:following]).to be true
+ expect(json[:showing_reblogs]).to be false
+ expect(json[:notifying]).to be true
+ end
end
end
diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
index f3f9946ba..f6be35f7f 100644
--- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb
@@ -111,7 +111,7 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
describe 'POST #unsuspend' do
before do
- account.touch(:suspended_at)
+ account.suspend!
post :unsuspend, params: { id: account.id }
end
@@ -127,6 +127,24 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
end
end
+ describe 'POST #unsensitive' do
+ before do
+ account.touch(:sensitized_at)
+ post :unsensitive, params: { id: account.id }
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+ it_behaves_like 'forbidden for wrong role', 'user'
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'unsensitives account' do
+ expect(account.reload.sensitized?).to be false
+ end
+ end
+
describe 'POST #unsilence' do
before do
account.touch(:silenced_at)
diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
index 6e947f5d2..4716ecae3 100644
--- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
@@ -82,6 +82,31 @@ describe Api::V1::Statuses::FavouritesController do
end
end
+ context 'with public status when blocked by its author' do
+ let(:status) { Fabricate(:status) }
+
+ before do
+ FavouriteService.new.call(user.account, status)
+ status.account.block!(user.account)
+ post :destroy, params: { status_id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'updates the favourite attribute' do
+ expect(user.account.favourited?(status)).to be false
+ end
+
+ it 'returns json with updated attributes' do
+ hash_body = body_as_json
+
+ expect(hash_body[:id]).to eq status.id.to_s
+ expect(hash_body[:favourited]).to be false
+ end
+ end
+
context 'with private status that was not favourited' do
let(:status) { Fabricate(:status, visibility: :private) }
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index c2e9f33a8..ccf304a93 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -82,6 +82,10 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
describe 'POST #create' do
let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s }
+ before do
+ session[:registration_form_time] = 5.seconds.ago
+ end
+
around do |example|
current_locale = I18n.locale
example.run
@@ -191,17 +195,21 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
end
end
- context 'approval-based registrations with valid invite' do
+ context 'approval-based registrations with valid invite and required invite text' do
around do |example|
registrations_mode = Setting.registrations_mode
+ require_invite_text = Setting.require_invite_text
example.run
+ Setting.require_invite_text = require_invite_text
Setting.registrations_mode = registrations_mode
end
subject do
+ inviter = Fabricate(:user, confirmed_at: 2.days.ago)
Setting.registrations_mode = 'approved'
+ Setting.require_invite_text = true
request.headers["Accept-Language"] = accept_language
- invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.from_now)
+ invite = Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now)
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code, agreement: 'true' } }
end
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 8ad9e74fc..d3a9a11eb 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -219,7 +219,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using a valid OTP' do
before do
- post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'redirects to home' do
@@ -234,7 +234,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'when the server has an decryption error' do
before do
allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError)
- post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'shows a login error' do
@@ -248,7 +248,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using a valid recovery code' do
before do
- post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'redirects to home' do
@@ -262,7 +262,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using an invalid OTP' do
before do
- post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'shows a login error' do
@@ -334,7 +334,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
before do
@controller.session[:webauthn_challenge] = challenge
- post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'instructs the browser to redirect to home' do
@@ -383,7 +383,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using a valid sign in token' do
before do
user.generate_sign_in_token && user.save
- post :create, params: { user: { sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'redirects to home' do
@@ -397,7 +397,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using an invalid sign in token' do
before do
- post :create, params: { user: { sign_in_token_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id }
+ post :create, params: { user: { sign_in_token_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
end
it 'shows a login error' do
diff --git a/spec/controllers/concerns/export_controller_concern_spec.rb b/spec/controllers/concerns/export_controller_concern_spec.rb
index e5861c801..fce129bee 100644
--- a/spec/controllers/concerns/export_controller_concern_spec.rb
+++ b/spec/controllers/concerns/export_controller_concern_spec.rb
@@ -5,6 +5,7 @@ require 'rails_helper'
describe ApplicationController, type: :controller do
controller do
include ExportControllerConcern
+
def index
send_export_file
end
diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb
index 34a0cf3f4..f6d55f693 100644
--- a/spec/controllers/follower_accounts_controller_spec.rb
+++ b/spec/controllers/follower_accounts_controller_spec.rb
@@ -14,6 +14,27 @@ describe FollowerAccountsController do
context 'when format is html' do
subject(:response) { get :index, params: { account_username: alice.username, format: :html } }
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
it 'assigns follows' do
expect(response).to have_http_status(200)
@@ -48,6 +69,27 @@ describe FollowerAccountsController do
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_present
end
+
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
context 'without page' do
@@ -58,6 +100,27 @@ describe FollowerAccountsController do
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_blank
end
+
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
end
end
diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb
index e9a1f597d..0fc0967a6 100644
--- a/spec/controllers/following_accounts_controller_spec.rb
+++ b/spec/controllers/following_accounts_controller_spec.rb
@@ -14,6 +14,27 @@ describe FollowingAccountsController do
context 'when format is html' do
subject(:response) { get :index, params: { account_username: alice.username, format: :html } }
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
it 'assigns follows' do
expect(response).to have_http_status(200)
@@ -48,6 +69,27 @@ describe FollowingAccountsController do
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_present
end
+
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
context 'without page' do
@@ -58,6 +100,27 @@ describe FollowingAccountsController do
expect(body['totalItems']).to eq 2
expect(body['partOf']).to be_blank
end
+
+ context 'when account is permanently suspended' do
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ end
+
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when account is temporarily suspended' do
+ before do
+ alice.suspend!
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
end
end
diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb
index 3ef8f14d9..01d43f48c 100644
--- a/spec/controllers/remote_follow_controller_spec.rb
+++ b/spec/controllers/remote_follow_controller_spec.rb
@@ -43,8 +43,7 @@ describe RemoteFollowController do
end
it 'renders new when template is nil' do
- link_with_nil_template = double(template: nil)
- resource_with_link = double(link: link_with_nil_template)
+ resource_with_link = double(link: nil)
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
@@ -55,8 +54,7 @@ describe RemoteFollowController do
context 'when webfinger values are good' do
before do
- link_with_template = double(template: 'http://example.com/follow_me?acct={uri}')
- resource_with_link = double(link: link_with_template)
+ resource_with_link = double(link: 'http://example.com/follow_me?acct={uri}')
allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_return(resource_with_link)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
end
@@ -78,8 +76,8 @@ describe RemoteFollowController do
expect(response).to render_template(:new)
end
- it 'renders new with error when goldfinger fails' do
- allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Goldfinger::Error)
+ it 'renders new with error when webfinger fails' do
+ allow_any_instance_of(WebfingerHelper).to receive(:webfinger!).with('acct:user@example.com').and_raise(Webfinger::Error)
post :create, params: { account_username: @account.to_param, remote_follow: { acct: 'user@example.com' } }
expect(response).to render_template(:new)
@@ -96,21 +94,42 @@ describe RemoteFollowController do
end
end
- describe 'with a suspended account' do
+ context 'with a permanently suspended account' do
before do
- @account = Fabricate(:account, suspended: true)
+ @account = Fabricate(:account)
+ @account.suspend!
+ @account.deletion_request.destroy
end
- it 'returns 410 gone on GET to #new' do
+ it 'returns http gone on GET to #new' do
get :new, params: { account_username: @account.to_param }
- expect(response).to have_http_status(:gone)
+ expect(response).to have_http_status(410)
end
- it 'returns 410 gone on POST to #create' do
+ it 'returns http gone on POST to #create' do
post :create, params: { account_username: @account.to_param }
- expect(response).to have_http_status(:gone)
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'with a temporarily suspended account' do
+ before do
+ @account = Fabricate(:account)
+ @account.suspend!
+ end
+
+ it 'returns http forbidden on GET to #new' do
+ get :new, params: { account_username: @account.to_param }
+
+ expect(response).to have_http_status(403)
+ end
+
+ it 'returns http forbidden on POST to #create' do
+ post :create, params: { account_username: @account.to_param }
+
+ expect(response).to have_http_status(403)
end
end
end
diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb
index 996872efd..8d5c4774f 100644
--- a/spec/controllers/settings/deletes_controller_spec.rb
+++ b/spec/controllers/settings/deletes_controller_spec.rb
@@ -77,26 +77,26 @@ describe Settings::DeletesController do
expect(response).to redirect_to settings_delete_path
end
end
- end
- context 'when not signed in' do
- it 'redirects' do
- delete :destroy
- expect(response).to redirect_to '/auth/sign_in'
- end
- end
+ context 'when account deletions are disabled' do
+ around do |example|
+ open_deletion = Setting.open_deletion
+ example.run
+ Setting.open_deletion = open_deletion
+ end
- context do
- around do |example|
- open_deletion = Setting.open_deletion
- example.run
- Setting.open_deletion = open_deletion
+ it 'redirects' do
+ Setting.open_deletion = false
+ delete :destroy
+ expect(response).to redirect_to root_path
+ end
end
+ end
+ context 'when not signed in' do
it 'redirects' do
- Setting.open_deletion = false
delete :destroy
- expect(response).to redirect_to root_path
+ expect(response).to redirect_to '/auth/sign_in'
end
end
end
diff --git a/spec/controllers/settings/exports/bookmarks_controller_specs.rb b/spec/controllers/settings/exports/bookmarks_controller_specs.rb
new file mode 100644
index 000000000..85761577b
--- /dev/null
+++ b/spec/controllers/settings/exports/bookmarks_controller_specs.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Settings::Exports::BookmarksController do
+ render_views
+
+ describe 'GET #index' do
+ it 'returns a csv of the bookmarked toots' do
+ user = Fabricate(:user)
+ user.account.bookmarks.create!(status: Fabricate(:status, uri: 'https://foo.bar/statuses/1312'))
+
+ sign_in user, scope: :user
+ get :index, format: :csv
+
+ expect(response.body).to eq "https://foo.bar/statuses/1312\n"
+ end
+ end
+end
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index d5da493cf..d32da1b76 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -24,10 +24,11 @@ describe StatusesController do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }
- context 'when account is suspended' do
- let(:account) { Fabricate(:account, suspended: true) }
-
+ context 'when account is permanently suspended' do
before do
+ account.suspend!
+ account.deletion_request.destroy
+
get :show, params: { account_username: account.username, id: status.id }
end
@@ -36,6 +37,18 @@ describe StatusesController do
end
end
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+
+ get :show, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
context 'when status is a reblog' do
let(:original_account) { Fabricate(:account, domain: 'example.com') }
let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') }
@@ -676,10 +689,11 @@ describe StatusesController do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }
- context 'when account is suspended' do
- let(:account) { Fabricate(:account, suspended: true) }
-
+ context 'when account is permanently suspended' do
before do
+ account.suspend!
+ account.deletion_request.destroy
+
get :activity, params: { account_username: account.username, id: status.id }
end
@@ -688,6 +702,18 @@ describe StatusesController do
end
end
+ context 'when account is temporarily suspended' do
+ before do
+ account.suspend!
+
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
context 'when status is public' do
pending
end
diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb
index b43ae19d8..643ba9cd3 100644
--- a/spec/controllers/well_known/host_meta_controller_spec.rb
+++ b/spec/controllers/well_known/host_meta_controller_spec.rb
@@ -12,7 +12,7 @@ describe WellKnown::HostMetaController, type: :controller do
expect(response.body).to eq <<XML
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
- <Link rel="lrdd" type="application/xrd+xml" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
+ <Link rel="lrdd" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
</XRD>
XML
end
diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb
index 46f63185b..cf7005b0e 100644
--- a/spec/controllers/well_known/webfinger_controller_spec.rb
+++ b/spec/controllers/well_known/webfinger_controller_spec.rb
@@ -4,95 +4,134 @@ describe WellKnown::WebfingerController, type: :controller do
render_views
describe 'GET #show' do
- let(:alice) do
- Fabricate(:account, username: 'alice')
+ let(:alternate_domains) { [] }
+ let(:alice) { Fabricate(:account, username: 'alice') }
+ let(:resource) { nil }
+
+ around(:each) do |example|
+ tmp = Rails.configuration.x.alternate_domains
+ Rails.configuration.x.alternate_domains = alternate_domains
+ example.run
+ Rails.configuration.x.alternate_domains = tmp
end
- before do
- alice.private_key = <<-PEM
------BEGIN RSA PRIVATE KEY-----
-MIICXQIBAAKBgQDHgPoPJlrfMZrVcuF39UbVssa8r4ObLP3dYl9Y17Mgp5K4mSYD
-R/Y2ag58tSi6ar2zM3Ze3QYsNfTq0NqN1g89eAu0MbSjWqpOsgntRPJiFuj3hai2
-X2Im8TBrkiM/UyfTRgn8q8WvMoKbXk8Lu6nqv420eyqhhLxfUoCpxuem1QIDAQAB
-AoGBAIKsOh2eM7spVI8mdgQKheEG/iEsnPkQ2R8ehfE9JzjmSbXbqghQJDaz9NU+
-G3Uu4R31QT0VbCudE9SSA/UPFl82GeQG4QLjrSE+PSjSkuslgSXelJHfAJ+ycGax
-ajtPyiQD0e4c2loagHNHPjqK9OhHx9mFnZWmoagjlZ+mQGEpAkEA8GtqfS65IaRQ
-uVhMzpp25rF1RWOwaaa+vBPkd7pGdJEQGFWkaR/a9UkU+2C4ZxGBkJDP9FApKVQI
-RANEwN3/hwJBANRuw5+es6BgBv4PD387IJvuruW2oUtYP+Lb2Z5k77J13hZTr0db
-Oo9j1UbbR0/4g+vAcsDl4JD9c/9LrGYEpcMCQBon9Yvs+2M3lziy7JhFoc3zXIjS
-Ea1M4M9hcqe78lJYPeIH3z04o/+vlcLLgQRlmSz7NESmO/QtGkEcAezhuh0CQHji
-pzO4LeO/gXslut3eGcpiYuiZquOjToecMBRwv+5AIKd367Che4uJdh6iPcyGURvh
-IewfZFFdyZqnx20ui90CQQC1W2rK5Y30wAunOtSLVA30TLK/tKrTppMC3corjKlB
-FTX8IvYBNTbpEttc1VCf/0ccnNpfb0CrFNSPWxRj7t7D
------END RSA PRIVATE KEY-----
-PEM
+ subject do
+ get :show, params: { resource: resource }, format: :json
+ end
- alice.public_key = <<-PEM
------BEGIN PUBLIC KEY-----
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHgPoPJlrfMZrVcuF39UbVssa8
-r4ObLP3dYl9Y17Mgp5K4mSYDR/Y2ag58tSi6ar2zM3Ze3QYsNfTq0NqN1g89eAu0
-MbSjWqpOsgntRPJiFuj3hai2X2Im8TBrkiM/UyfTRgn8q8WvMoKbXk8Lu6nqv420
-eyqhhLxfUoCpxuem1QIDAQAB
------END PUBLIC KEY-----
-PEM
+ shared_examples 'a successful response' do
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
- alice.save!
- end
+ it 'returns application/jrd+json' do
+ expect(response.content_type).to eq 'application/jrd+json'
+ end
- around(:each) do |example|
- before = Rails.configuration.x.alternate_domains
- example.run
- Rails.configuration.x.alternate_domains = before
+ it 'returns links for the account' do
+ json = body_as_json
+ expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io'
+ expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
+ end
end
- it 'returns JSON when account can be found' do
- get :show, params: { resource: alice.to_webfinger_s }, format: :json
+ context 'when an account exists' do
+ let(:resource) { alice.to_webfinger_s }
- json = body_as_json
+ before do
+ subject
+ end
- expect(response).to have_http_status(200)
- expect(response.content_type).to eq 'application/jrd+json'
- expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io'
- expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
+ it_behaves_like 'a successful response'
end
- it 'returns http not found when account cannot be found' do
- get :show, params: { resource: 'acct:not@existing.com' }, format: :json
+ context 'when an account is temporarily suspended' do
+ let(:resource) { alice.to_webfinger_s }
- expect(response).to have_http_status(:not_found)
+ before do
+ alice.suspend!
+ subject
+ end
+
+ it_behaves_like 'a successful response'
end
- it 'returns JSON when account can be found with alternate domains' do
- Rails.configuration.x.alternate_domains = ['foo.org']
- username, = alice.to_webfinger_s.split('@')
+ context 'when an account is permanently suspended or deleted' do
+ let(:resource) { alice.to_webfinger_s }
+
+ before do
+ alice.suspend!
+ alice.deletion_request.destroy
+ subject
+ end
- get :show, params: { resource: "#{username}@foo.org" }, format: :json
+ it 'returns http gone' do
+ expect(response).to have_http_status(410)
+ end
+ end
+
+ context 'when an account is not found' do
+ let(:resource) { 'acct:not@existing.com' }
- json = body_as_json
+ before do
+ subject
+ end
- expect(response).to have_http_status(200)
- expect(response.content_type).to eq 'application/jrd+json'
- expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io'
- expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice')
+ it 'returns http not found' do
+ expect(response).to have_http_status(404)
+ end
end
- it 'returns http not found when account can not be found with alternate domains' do
- Rails.configuration.x.alternate_domains = ['foo.org']
- username, = alice.to_webfinger_s.split('@')
+ context 'with an alternate domain' do
+ let(:alternate_domains) { ['foo.org'] }
+
+ before do
+ subject
+ end
+
+ context 'when an account exists' do
+ let(:resource) do
+ username, = alice.to_webfinger_s.split('@')
+ "#{username}@foo.org"
+ end
+
+ it_behaves_like 'a successful response'
+ end
- get :show, params: { resource: "#{username}@bar.org" }, format: :json
+ context 'when the domain is wrong' do
+ let(:resource) do
+ username, = alice.to_webfinger_s.split('@')
+ "#{username}@bar.org"
+ end
- expect(response).to have_http_status(:not_found)
+ it 'returns http not found' do
+ expect(response).to have_http_status(404)
+ end
+ end
end
- it 'returns http bad request when not given a resource parameter' do
- get :show, params: { }, format: :json
- expect(response).to have_http_status(:bad_request)
+ context 'with no resource parameter' do
+ let(:resource) { nil }
+
+ before do
+ subject
+ end
+
+ it 'returns http bad request' do
+ expect(response).to have_http_status(400)
+ end
end
- it 'returns http bad request when given a nonsense parameter' do
- get :show, params: { resource: 'df/:dfkj' }
- expect(response).to have_http_status(:bad_request)
+ context 'with a nonsense parameter' do
+ let(:resource) { 'df/:dfkj' }
+
+ before do
+ subject
+ end
+
+ it 'returns http bad request' do
+ expect(response).to have_http_status(400)
+ end
end
end
end
diff --git a/spec/fabricators/account_deletion_request_fabricator.rb b/spec/fabricators/account_deletion_request_fabricator.rb
new file mode 100644
index 000000000..08a82ba3c
--- /dev/null
+++ b/spec/fabricators/account_deletion_request_fabricator.rb
@@ -0,0 +1,3 @@
+Fabricator(:account_deletion_request) do
+ account
+end
diff --git a/spec/fabricators/ip_block_fabricator.rb b/spec/fabricators/ip_block_fabricator.rb
new file mode 100644
index 000000000..31dc336e6
--- /dev/null
+++ b/spec/fabricators/ip_block_fabricator.rb
@@ -0,0 +1,6 @@
+Fabricator(:ip_block) do
+ ip ""
+ severity ""
+ expires_at "2020-10-08 22:20:37"
+ comment "MyText"
+end \ No newline at end of file
diff --git a/spec/fixtures/files/bookmark-imports.txt b/spec/fixtures/files/bookmark-imports.txt
new file mode 100644
index 000000000..7cc8901a0
--- /dev/null
+++ b/spec/fixtures/files/bookmark-imports.txt
@@ -0,0 +1,4 @@
+https://example.com/statuses/1312
+https://local.com/users/foo/statuses/42
+https://unknown-remote.com/users/bar/statuses/1
+https://example.com/statuses/direct
diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb
index 940ff072e..cba659bfb 100644
--- a/spec/helpers/statuses_helper_spec.rb
+++ b/spec/helpers/statuses_helper_spec.rb
@@ -149,22 +149,4 @@ RSpec.describe StatusesHelper, type: :helper do
expect(css_class).to eq 'h-cite'
end
end
-
- describe '#rtl?' do
- it 'is false if text is empty' do
- expect(helper).not_to be_rtl ''
- end
-
- it 'is false if there are no right to left characters' do
- expect(helper).not_to be_rtl 'hello world'
- end
-
- it 'is false if right to left characters are fewer than 1/3 of total text' do
- expect(helper).not_to be_rtl 'hello ÝŸ world'
- end
-
- it 'is true if right to left characters are greater than 1/3 of total text' do
- expect(helper).to be_rtl 'aaÝŸaaÝŸ'
- end
- end
end
diff --git a/spec/lib/fast_ip_map_spec.rb b/spec/lib/fast_ip_map_spec.rb
new file mode 100644
index 000000000..c66f64828
--- /dev/null
+++ b/spec/lib/fast_ip_map_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe FastIpMap do
+ describe '#include?' do
+ subject { described_class.new([IPAddr.new('20.4.0.0/16'), IPAddr.new('145.22.30.0/24'), IPAddr.new('189.45.86.3')])}
+
+ it 'returns true for an exact match' do
+ expect(subject.include?(IPAddr.new('189.45.86.3'))).to be true
+ end
+
+ it 'returns true for a range match' do
+ expect(subject.include?(IPAddr.new('20.4.45.7'))).to be true
+ end
+
+ it 'returns false for no match' do
+ expect(subject.include?(IPAddr.new('145.22.40.64'))).to be false
+ end
+ end
+end
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 22eddd2ab..0df85e5bc 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -29,14 +29,14 @@ RSpec.describe FeedManager do
it 'returns false for followee\'s status' do
status = Fabricate(:status, text: 'Hello world', account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, status, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, status, bob)).to be false
end
it 'returns false for reblog by followee' do
status = Fabricate(:status, text: 'Hello world', account: jeff)
reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, reblog, bob)).to be false
end
it 'returns true for reblog by followee of blocked account' do
@@ -44,7 +44,7 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice)
bob.block!(jeff)
- expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true
end
it 'returns true for reblog by followee of muted account' do
@@ -52,7 +52,7 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice)
bob.mute!(jeff)
- expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true
end
it 'returns true for reblog by followee of someone who is blocking recipient' do
@@ -60,14 +60,14 @@ RSpec.describe FeedManager do
reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice)
jeff.block!(bob)
- expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true
end
it 'returns true for reblog from account with reblogs disabled' do
status = Fabricate(:status, text: 'Hello world', account: jeff)
reblog = Fabricate(:status, reblog: status, account: alice)
bob.follow!(alice, reblogs: false)
- expect(FeedManager.instance.filter?(:home, reblog, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reblog, bob)).to be true
end
it 'returns false for reply by followee to another followee' do
@@ -75,48 +75,49 @@ RSpec.describe FeedManager do
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice)
bob.follow!(jeff)
- expect(FeedManager.instance.filter?(:home, reply, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, reply, bob)).to be false
end
it 'returns false for reply by followee to recipient' do
status = Fabricate(:status, text: 'Hello world', account: bob)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, reply, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, reply, bob)).to be false
end
it 'returns false for reply by followee to self' do
status = Fabricate(:status, text: 'Hello world', account: alice)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, reply, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, reply, bob)).to be false
end
it 'returns true for reply by followee to non-followed account' do
status = Fabricate(:status, text: 'Hello world', account: jeff)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, reply, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reply, bob)).to be true
end
it 'returns true for the second reply by followee to a non-federated status' do
reply = Fabricate(:status, text: 'Reply 1', reply: true, account: alice)
second_reply = Fabricate(:status, text: 'Reply 2', thread: reply, account: alice)
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:home, second_reply, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, second_reply, bob)).to be true
end
it 'returns false for status by followee mentioning another account' do
bob.follow!(alice)
+ jeff.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff')
- expect(FeedManager.instance.filter?(:home, status, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:home, status, bob)).to be false
end
it 'returns true for status by followee mentioning blocked account' do
bob.block!(jeff)
bob.follow!(alice)
status = PostStatusService.new.call(alice, text: 'Hey @jeff')
- expect(FeedManager.instance.filter?(:home, status, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:home, status, bob)).to be true
end
it 'returns true for reblog of a personally blocked domain' do
@@ -124,7 +125,7 @@ RSpec.describe FeedManager do
alice.follow!(jeff)
status = Fabricate(:status, text: 'Hello world', account: bob)
reblog = Fabricate(:status, reblog: status, account: jeff)
- expect(FeedManager.instance.filter?(:home, reblog, alice.id)).to be true
+ expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true
end
context 'for irreversibly muted phrases' do
@@ -132,7 +133,7 @@ RSpec.describe FeedManager do
alice.custom_filters.create!(phrase: 'bob', context: %w(home), irreversible: true)
alice.follow!(jeff)
status = Fabricate(:status, text: 'bobcats', account: jeff)
- expect(FeedManager.instance.filter?(:home, status, alice.id)).to be_falsy
+ expect(FeedManager.instance.filter?(:home, status, alice)).to be_falsy
end
it 'returns true if phrase is contained' do
@@ -140,14 +141,14 @@ RSpec.describe FeedManager do
alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
alice.follow!(jeff)
status = Fabricate(:status, text: 'i sure like POP TARts', account: jeff)
- expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
+ expect(FeedManager.instance.filter?(:home, status, alice)).to be true
end
it 'matches substrings if whole_word is false' do
alice.custom_filters.create!(phrase: 'take', context: %w(home), whole_word: false, irreversible: true)
alice.follow!(jeff)
status = Fabricate(:status, text: 'shiitake', account: jeff)
- expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
+ expect(FeedManager.instance.filter?(:home, status, alice)).to be true
end
it 'returns true if phrase is contained in a poll option' do
@@ -155,7 +156,7 @@ RSpec.describe FeedManager do
alice.custom_filters.create!(phrase: 'pop tarts', context: %w(home), irreversible: true)
alice.follow!(jeff)
status = Fabricate(:status, text: 'what do you prefer', poll: Fabricate(:poll, options: %w(farts POP TARts)), account: jeff)
- expect(FeedManager.instance.filter?(:home, status, alice.id)).to be true
+ expect(FeedManager.instance.filter?(:home, status, alice)).to be true
end
end
end
@@ -164,27 +165,27 @@ RSpec.describe FeedManager do
it 'returns true for status that mentions blocked account' do
bob.block!(jeff)
status = PostStatusService.new.call(alice, text: 'Hey @jeff')
- expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:mentions, status, bob)).to be true
end
it 'returns true for status that replies to a blocked account' do
status = Fabricate(:status, text: 'Hello world', account: jeff)
reply = Fabricate(:status, text: 'Nay', thread: status, account: alice)
bob.block!(jeff)
- expect(FeedManager.instance.filter?(:mentions, reply, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:mentions, reply, bob)).to be true
end
it 'returns true for status by silenced account who recipient is not following' do
status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence!
- expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be true
+ expect(FeedManager.instance.filter?(:mentions, status, bob)).to be true
end
it 'returns false for status by followed silenced account' do
status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence!
bob.follow!(alice)
- expect(FeedManager.instance.filter?(:mentions, status, bob.id)).to be false
+ expect(FeedManager.instance.filter?(:mentions, status, bob)).to be false
end
end
end
@@ -309,62 +310,125 @@ RSpec.describe FeedManager do
end
describe '#push_to_list' do
+ let(:owner) { Fabricate(:account, username: 'owner') }
+ let(:alice) { Fabricate(:account, username: 'alice') }
+ let(:bob) { Fabricate(:account, username: 'bob') }
+ let(:eve) { Fabricate(:account, username: 'eve') }
+ let(:list) { Fabricate(:list, account: owner) }
+
+ before do
+ owner.follow!(alice)
+ owner.follow!(bob)
+ owner.follow!(eve)
+
+ list.accounts << alice
+ list.accounts << bob
+ end
+
it "does not push when the given status's reblog is already inserted" do
- list = Fabricate(:list)
reblog = Fabricate(:status)
status = Fabricate(:status, reblog: reblog)
FeedManager.instance.push_to_list(list, status)
expect(FeedManager.instance.push_to_list(list, reblog)).to eq false
end
- end
- describe '#merge_into_timeline' do
- it "does not push source account's statuses whose reblogs are already inserted" do
- account = Fabricate(:account, id: 0)
- reblog = Fabricate(:status)
- status = Fabricate(:status, reblog: reblog)
- FeedManager.instance.push_to_home(account, status)
+ context 'when replies policy is set to no replies' do
+ before do
+ list.replies_policy = :none
+ end
- FeedManager.instance.merge_into_timeline(account, reblog.account)
+ it 'pushes statuses that are not replies' do
+ status = Fabricate(:status, text: 'Hello world', account: bob)
+ expect(FeedManager.instance.push_to_list(list, status)).to eq true
+ end
- expect(Redis.current.zscore("feed:home:0", reblog.id)).to eq nil
+ it 'pushes statuses that are replies to list owner' do
+ status = Fabricate(:status, text: 'Hello world', account: owner)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
+
+ it 'does not push replies to another member of the list' do
+ status = Fabricate(:status, text: 'Hello world', account: alice)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq false
+ end
end
- end
- describe '#trim' do
- let(:receiver) { Fabricate(:account) }
+ context 'when replies policy is set to list-only replies' do
+ before do
+ list.replies_policy = :list
+ end
- it 'cleans up reblog tracking keys' do
- reblogged = Fabricate(:status)
- status = Fabricate(:status, reblog: reblogged)
- another_status = Fabricate(:status, reblog: reblogged)
- reblogs_key = FeedManager.instance.key('home', receiver.id, 'reblogs')
- reblog_set_key = FeedManager.instance.key('home', receiver.id, "reblogs:#{reblogged.id}")
+ it 'pushes statuses that are not replies' do
+ status = Fabricate(:status, text: 'Hello world', account: bob)
+ expect(FeedManager.instance.push_to_list(list, status)).to eq true
+ end
- FeedManager.instance.push_to_home(receiver, status)
- FeedManager.instance.push_to_home(receiver, another_status)
+ it 'pushes statuses that are replies to list owner' do
+ status = Fabricate(:status, text: 'Hello world', account: owner)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
- # We should have a tracking set and an entry in reblogs.
- expect(Redis.current.exists(reblog_set_key)).to be true
- expect(Redis.current.zrange(reblogs_key, 0, -1)).to eq [reblogged.id.to_s]
+ it 'pushes replies to another member of the list' do
+ status = Fabricate(:status, text: 'Hello world', account: alice)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
- # Push everything off the end of the feed.
- FeedManager::MAX_ITEMS.times do
- FeedManager.instance.push_to_home(receiver, Fabricate(:status))
+ it 'does not push replies to someone not a member of the list' do
+ status = Fabricate(:status, text: 'Hello world', account: eve)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq false
end
+ end
- # `trim` should be called automatically, but do it anyway, as
- # we're testing `trim`, not side effects of `push`.
- FeedManager.instance.trim('home', receiver.id)
+ context 'when replies policy is set to any reply' do
+ before do
+ list.replies_policy = :followed
+ end
+
+ it 'pushes statuses that are not replies' do
+ status = Fabricate(:status, text: 'Hello world', account: bob)
+ expect(FeedManager.instance.push_to_list(list, status)).to eq true
+ end
+
+ it 'pushes statuses that are replies to list owner' do
+ status = Fabricate(:status, text: 'Hello world', account: owner)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
+
+ it 'pushes replies to another member of the list' do
+ status = Fabricate(:status, text: 'Hello world', account: alice)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
- # We should not have any reblog tracking data.
- expect(Redis.current.exists(reblog_set_key)).to be false
- expect(Redis.current.zrange(reblogs_key, 0, -1)).to be_empty
+ it 'pushes replies to someone not a member of the list' do
+ status = Fabricate(:status, text: 'Hello world', account: eve)
+ reply = Fabricate(:status, text: 'Nay', thread: status, account: bob)
+ expect(FeedManager.instance.push_to_list(list, reply)).to eq true
+ end
+ end
+ end
+
+ describe '#merge_into_home' do
+ it "does not push source account's statuses whose reblogs are already inserted" do
+ account = Fabricate(:account, id: 0)
+ reblog = Fabricate(:status)
+ status = Fabricate(:status, reblog: reblog)
+ FeedManager.instance.push_to_home(account, status)
+
+ FeedManager.instance.merge_into_home(account, reblog.account)
+
+ expect(Redis.current.zscore("feed:home:0", reblog.id)).to eq nil
end
end
- describe '#unpush' do
+ describe '#unpush_from_home' do
let(:receiver) { Fabricate(:account) }
it 'leaves a reblogged status if original was on feed' do
@@ -430,7 +494,7 @@ RSpec.describe FeedManager do
end
end
- describe '#clear_from_timeline' do
+ describe '#clear_from_home' do
let(:account) { Fabricate(:account) }
let(:followed_account) { Fabricate(:account) }
let(:target_account) { Fabricate(:account) }
@@ -448,8 +512,8 @@ RSpec.describe FeedManager do
end
end
- it 'correctly cleans the timeline' do
- FeedManager.instance.clear_from_timeline(account, target_account)
+ it 'correctly cleans the home timeline' do
+ FeedManager.instance.clear_from_home(account, target_account)
expect(Redis.current.zrange("feed:home:#{account.id}", 0, -1)).to eq [status_1.id.to_s, status_7.id.to_s]
end
diff --git a/spec/lib/spam_check_spec.rb b/spec/lib/spam_check_spec.rb
index d4d66a499..159d83257 100644
--- a/spec/lib/spam_check_spec.rb
+++ b/spec/lib/spam_check_spec.rb
@@ -150,9 +150,9 @@ RSpec.describe SpamCheck do
let(:redis_key) { spam_check.send(:redis_key) }
it 'remembers' do
- expect(Redis.current.exists(redis_key)).to be true
+ expect(Redis.current.exists?(redis_key)).to be true
spam_check.remember!
- expect(Redis.current.exists(redis_key)).to be true
+ expect(Redis.current.exists?(redis_key)).to be true
end
end
@@ -166,9 +166,9 @@ RSpec.describe SpamCheck do
end
it 'resets' do
- expect(Redis.current.exists(redis_key)).to be true
+ expect(Redis.current.exists?(redis_key)).to be true
spam_check.reset!
- expect(Redis.current.exists(redis_key)).to be false
+ expect(Redis.current.exists?(redis_key)).to be false
end
end
diff --git a/spec/models/account_deletion_request_spec.rb b/spec/models/account_deletion_request_spec.rb
new file mode 100644
index 000000000..afaecbe22
--- /dev/null
+++ b/spec/models/account_deletion_request_spec.rb
@@ -0,0 +1,4 @@
+require 'rails_helper'
+
+RSpec.describe AccountDeletionRequest, type: :model do
+end
diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb
index 176a0eeac..0cdb373f6 100644
--- a/spec/models/account_filter_spec.rb
+++ b/spec/models/account_filter_spec.rb
@@ -5,7 +5,7 @@ describe AccountFilter do
it 'defaults to recent local not-suspended account list' do
filter = described_class.new({})
- expect(filter.results).to eq Account.local.recent.without_suspended
+ expect(filter.results).to eq Account.local.without_instance_actor.recent.without_suspended
end
end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 98d29e6f3..1d000ed4d 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -440,13 +440,6 @@ RSpec.describe Account, type: :model do
end
end
- describe '.domains' do
- it 'returns domains' do
- Fabricate(:account, domain: 'domain')
- expect(Account.remote.domains).to match_array(['domain'])
- end
- end
-
describe '#statuses_count' do
subject { Fabricate(:account) }
@@ -737,20 +730,6 @@ RSpec.describe Account, type: :model do
end
end
- describe 'by_domain_accounts' do
- it 'returns accounts grouped by domain sorted by accounts' do
- 2.times { Fabricate(:account, domain: 'example.com') }
- Fabricate(:account, domain: 'example2.com')
-
- results = Account.where('id > 0').by_domain_accounts
- expect(results.length).to eq 2
- expect(results.first.domain).to eq 'example.com'
- expect(results.first.accounts_count).to eq 2
- expect(results.last.domain).to eq 'example2.com'
- expect(results.last.accounts_count).to eq 1
- end
- end
-
describe 'local' do
it 'returns an array of accounts who do not have a domain' do
account_1 = Fabricate(:account, domain: nil)
@@ -817,4 +796,27 @@ RSpec.describe Account, type: :model do
include_examples 'AccountAvatar', :account
include_examples 'AccountHeader', :account
+
+ describe '#increment_count!' do
+ subject { Fabricate(:account) }
+
+ it 'increments the count in multi-threaded an environment when account_stat is not yet initialized' do
+ subject
+
+ increment_by = 15
+ wait_for_start = true
+
+ threads = Array.new(increment_by) do
+ Thread.new do
+ true while wait_for_start
+ Account.find(subject.id).increment_count!(:followers_count)
+ end
+ end
+
+ wait_for_start = false
+ threads.each(&:join)
+
+ expect(subject.reload.followers_count).to eq 15
+ end
+ end
end
diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb
index 87fc28500..2366b9ca4 100644
--- a/spec/models/admin/account_action_spec.rb
+++ b/spec/models/admin/account_action_spec.rb
@@ -115,16 +115,16 @@ RSpec.describe Admin::AccountAction, type: :model do
context 'account.local?' do
let(:account) { Fabricate(:account, domain: nil) }
- it 'returns ["none", "disable", "silence", "suspend"]' do
- expect(subject).to eq %w(none disable silence suspend)
+ it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do
+ expect(subject).to eq %w(none disable sensitive silence suspend)
end
end
context '!account.local?' do
let(:account) { Fabricate(:account, domain: 'hoge.com') }
- it 'returns ["silence", "suspend"]' do
- expect(subject).to eq %w(silence suspend)
+ it 'returns ["sensitive", "silence", "suspend"]' do
+ expect(subject).to eq %w(sensitive silence suspend)
end
end
end
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index e8ef61f66..85fbf7e79 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -14,7 +14,7 @@ describe AccountInteractions do
context 'account with Follow' do
it 'returns { target_account_id => true }' do
Fabricate(:follow, account: account, target_account: target_account)
- is_expected.to eq(target_account_id => { reblogs: true })
+ is_expected.to eq(target_account_id => { reblogs: true, notify: false })
end
end
@@ -539,6 +539,49 @@ describe AccountInteractions do
end
end
+ describe '#followers_hash' do
+ let(:me) { Fabricate(:account, username: 'Me') }
+ let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') }
+ let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') }
+ let(:remote_3) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') }
+
+ before do
+ remote_1.follow!(me)
+ remote_2.follow!(me)
+ remote_3.follow!(me)
+ me.follow!(remote_1)
+ end
+
+ context 'on a local user' do
+ it 'returns correct hash for remote domains' do
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ expect(me.remote_followers_hash('https://foo.org/')).to eq 'ccb9c18a67134cfff9d62c7f7e7eb88e6b803446c244b84265565f4eba29df0e'
+ end
+
+ it 'invalidates cache as needed when removing or adding followers' do
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ remote_1.unfollow!(me)
+ expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff'
+ remote_1.follow!(me)
+ expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec'
+ end
+ end
+
+ context 'on a remote user' do
+ it 'returns correct hash for remote domains' do
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ end
+
+ it 'invalidates cache as needed when removing or adding followers' do
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ me.unfollow!(remote_1)
+ expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000'
+ me.follow!(remote_1)
+ expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me))
+ end
+ end
+ end
+
describe 'muting an account' do
let(:me) { Fabricate(:account, username: 'Me') }
let(:you) { Fabricate(:account, username: 'You') }
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index 2cf28b263..cc484a5b9 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe FollowRequest, type: :model do
let(:target_account) { Fabricate(:account) }
it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
- expect(account).to receive(:follow!).with(target_account, reblogs: true, uri: follow_request.uri)
+ expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri)
expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id)
expect(follow_request).to receive(:destroy!)
follow_request.authorize!
diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb
index 0c84e5e7b..e723a1ef2 100644
--- a/spec/models/follow_spec.rb
+++ b/spec/models/follow_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Follow, type: :model do
let(:bob) { Fabricate(:account, username: 'bob') }
describe 'validations' do
- subject { Follow.new(account: alice, target_account: bob) }
+ subject { Follow.new(account: alice, target_account: bob, rate_limit: true) }
it 'has a valid fabricator' do
follow = Fabricate.build(:follow)
diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb
index 321761166..a5eec1722 100644
--- a/spec/models/import_spec.rb
+++ b/spec/models/import_spec.rb
@@ -20,5 +20,15 @@ RSpec.describe Import, type: :model do
import = Import.create(account: account, type: type)
expect(import).to model_have_error_on_field(:data)
end
+
+ it 'is invalid with too many rows in data' do
+ import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (ImportService::ROWS_PROCESSING_LIMIT + 10)))
+ expect(import).to model_have_error_on_field(:data)
+ end
+
+ it 'is invalid when there are more rows when following limit' do
+ import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (FollowLimitValidator.limit_for_account(account) + 10)))
+ expect(import).to model_have_error_on_field(:data)
+ end
end
end
diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb
index 30abfb86b..b0596c561 100644
--- a/spec/models/invite_spec.rb
+++ b/spec/models/invite_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Invite, type: :model do
it 'returns false when invite creator has been disabled' do
invite = Fabricate(:invite, max_uses: nil, expires_at: nil)
- SuspendAccountService.new.call(invite.user.account)
+ invite.user.account.suspend!
expect(invite.valid_for_use?).to be false
end
end
diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb
new file mode 100644
index 000000000..6603c6417
--- /dev/null
+++ b/spec/models/ip_block_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe IpBlock, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb
new file mode 100644
index 000000000..0392a582c
--- /dev/null
+++ b/spec/models/public_feed_spec.rb
@@ -0,0 +1,212 @@
+require 'rails_helper'
+
+RSpec.describe PublicFeed, type: :model do
+ let(:account) { Fabricate(:account) }
+
+ describe '#get' do
+ subject { described_class.new(nil).get(20).map(&:id) }
+
+ it 'only includes statuses with public visibility' do
+ public_status = Fabricate(:status, visibility: :public)
+ private_status = Fabricate(:status, visibility: :private)
+
+ expect(subject).to include(public_status.id)
+ expect(subject).not_to include(private_status.id)
+ end
+
+ it 'does not include replies' do
+ status = Fabricate(:status)
+ reply = Fabricate(:status, in_reply_to_id: status.id)
+
+ expect(subject).to include(status.id)
+ expect(subject).not_to include(reply.id)
+ end
+
+ it 'does not include boosts' do
+ status = Fabricate(:status)
+ boost = Fabricate(:status, reblog_of_id: status.id)
+
+ expect(subject).to include(status.id)
+ expect(subject).not_to include(boost.id)
+ end
+
+ it 'filters out silenced accounts' do
+ account = Fabricate(:account)
+ silenced_account = Fabricate(:account, silenced: true)
+ status = Fabricate(:status, account: account)
+ silenced_status = Fabricate(:status, account: silenced_account)
+
+ expect(subject).to include(status.id)
+ expect(subject).not_to include(silenced_status.id)
+ end
+
+ context 'without local_only option' do
+ let(:viewer) { nil }
+
+ let!(:local_account) { Fabricate(:account, domain: nil) }
+ let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
+ let!(:local_status) { Fabricate(:status, account: local_account) }
+ let!(:remote_status) { Fabricate(:status, account: remote_account) }
+
+ subject { described_class.new(viewer).get(20).map(&:id) }
+
+ context 'without a viewer' do
+ let(:viewer) { nil }
+
+ it 'includes remote instances statuses' do
+ expect(subject).to include(remote_status.id)
+ end
+
+ it 'includes local statuses' do
+ expect(subject).to include(local_status.id)
+ end
+ end
+
+ context 'with a viewer' do
+ let(:viewer) { Fabricate(:account, username: 'viewer') }
+
+ it 'includes remote instances statuses' do
+ expect(subject).to include(remote_status.id)
+ end
+
+ it 'includes local statuses' do
+ expect(subject).to include(local_status.id)
+ end
+ end
+ end
+
+ context 'with a local_only option set' do
+ let!(:local_account) { Fabricate(:account, domain: nil) }
+ let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
+ let!(:local_status) { Fabricate(:status, account: local_account) }
+ let!(:remote_status) { Fabricate(:status, account: remote_account) }
+
+ subject { described_class.new(viewer, local: true).get(20).map(&:id) }
+
+ context 'without a viewer' do
+ let(:viewer) { nil }
+
+ it 'does not include remote instances statuses' do
+ expect(subject).to include(local_status.id)
+ expect(subject).not_to include(remote_status.id)
+ end
+ end
+
+ context 'with a viewer' do
+ let(:viewer) { Fabricate(:account, username: 'viewer') }
+
+ it 'does not include remote instances statuses' do
+ expect(subject).to include(local_status.id)
+ expect(subject).not_to include(remote_status.id)
+ end
+
+ it 'is not affected by personal domain blocks' do
+ viewer.block_domain!('test.com')
+ expect(subject).to include(local_status.id)
+ expect(subject).not_to include(remote_status.id)
+ end
+ end
+ end
+
+ context 'with a remote_only option set' do
+ let!(:local_account) { Fabricate(:account, domain: nil) }
+ let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
+ let!(:local_status) { Fabricate(:status, account: local_account) }
+ let!(:remote_status) { Fabricate(:status, account: remote_account) }
+
+ subject { described_class.new(viewer, remote: true).get(20).map(&:id) }
+
+ context 'without a viewer' do
+ let(:viewer) { nil }
+
+ it 'does not include local instances statuses' do
+ expect(subject).not_to include(local_status.id)
+ expect(subject).to include(remote_status.id)
+ end
+ end
+
+ context 'with a viewer' do
+ let(:viewer) { Fabricate(:account, username: 'viewer') }
+
+ it 'does not include local instances statuses' do
+ expect(subject).not_to include(local_status.id)
+ expect(subject).to include(remote_status.id)
+ end
+ end
+ end
+
+ describe 'with an account passed in' do
+ before do
+ @account = Fabricate(:account)
+ end
+
+ subject { described_class.new(@account).get(20).map(&:id) }
+
+ it 'excludes statuses from accounts blocked by the account' do
+ blocked = Fabricate(:account)
+ @account.block!(blocked)
+ blocked_status = Fabricate(:status, account: blocked)
+
+ expect(subject).not_to include(blocked_status.id)
+ end
+
+ it 'excludes statuses from accounts who have blocked the account' do
+ blocker = Fabricate(:account)
+ blocker.block!(@account)
+ blocked_status = Fabricate(:status, account: blocker)
+
+ expect(subject).not_to include(blocked_status.id)
+ end
+
+ it 'excludes statuses from accounts muted by the account' do
+ muted = Fabricate(:account)
+ @account.mute!(muted)
+ muted_status = Fabricate(:status, account: muted)
+
+ expect(subject).not_to include(muted_status.id)
+ end
+
+ it 'excludes statuses from accounts from personally blocked domains' do
+ blocked = Fabricate(:account, domain: 'example.com')
+ @account.block_domain!(blocked.domain)
+ blocked_status = Fabricate(:status, account: blocked)
+
+ expect(subject).not_to include(blocked_status.id)
+ end
+
+ context 'with language preferences' do
+ it 'excludes statuses in languages not allowed by the account user' do
+ user = Fabricate(:user, chosen_languages: [:en, :es])
+ @account.update(user: user)
+ en_status = Fabricate(:status, language: 'en')
+ es_status = Fabricate(:status, language: 'es')
+ fr_status = Fabricate(:status, language: 'fr')
+
+ expect(subject).to include(en_status.id)
+ expect(subject).to include(es_status.id)
+ expect(subject).not_to include(fr_status.id)
+ end
+
+ it 'includes all languages when user does not have a setting' do
+ user = Fabricate(:user, chosen_languages: nil)
+ @account.update(user: user)
+
+ en_status = Fabricate(:status, language: 'en')
+ es_status = Fabricate(:status, language: 'es')
+
+ expect(subject).to include(en_status.id)
+ expect(subject).to include(es_status.id)
+ end
+
+ it 'includes all languages when account does not have a user' do
+ expect(@account.user).to be_nil
+ en_status = Fabricate(:status, language: 'en')
+ es_status = Fabricate(:status, language: 'es')
+
+ expect(subject).to include(en_status.id)
+ expect(subject).to include(es_status.id)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 4aee14cbd..20fb894e7 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -267,241 +267,6 @@ RSpec.describe Status, type: :model do
end
end
- describe '.as_public_timeline' do
- it 'only includes statuses with public visibility' do
- public_status = Fabricate(:status, visibility: :public)
- private_status = Fabricate(:status, visibility: :private)
-
- results = Status.as_public_timeline
- expect(results).to include(public_status)
- expect(results).not_to include(private_status)
- end
-
- it 'does not include replies' do
- status = Fabricate(:status)
- reply = Fabricate(:status, in_reply_to_id: status.id)
-
- results = Status.as_public_timeline
- expect(results).to include(status)
- expect(results).not_to include(reply)
- end
-
- it 'does not include boosts' do
- status = Fabricate(:status)
- boost = Fabricate(:status, reblog_of_id: status.id)
-
- results = Status.as_public_timeline
- expect(results).to include(status)
- expect(results).not_to include(boost)
- end
-
- it 'filters out silenced accounts' do
- account = Fabricate(:account)
- silenced_account = Fabricate(:account, silenced: true)
- status = Fabricate(:status, account: account)
- silenced_status = Fabricate(:status, account: silenced_account)
-
- results = Status.as_public_timeline
- expect(results).to include(status)
- expect(results).not_to include(silenced_status)
- end
-
- context 'without local_only option' do
- let(:viewer) { nil }
-
- let!(:local_account) { Fabricate(:account, domain: nil) }
- let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
- let!(:local_status) { Fabricate(:status, account: local_account) }
- let!(:remote_status) { Fabricate(:status, account: remote_account) }
-
- subject { Status.as_public_timeline(viewer, false) }
-
- context 'without a viewer' do
- let(:viewer) { nil }
-
- it 'includes remote instances statuses' do
- expect(subject).to include(remote_status)
- end
-
- it 'includes local statuses' do
- expect(subject).to include(local_status)
- end
- end
-
- context 'with a viewer' do
- let(:viewer) { Fabricate(:account, username: 'viewer') }
-
- it 'includes remote instances statuses' do
- expect(subject).to include(remote_status)
- end
-
- it 'includes local statuses' do
- expect(subject).to include(local_status)
- end
- end
- end
-
- context 'with a local_only option set' do
- let!(:local_account) { Fabricate(:account, domain: nil) }
- let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
- let!(:local_status) { Fabricate(:status, account: local_account) }
- let!(:remote_status) { Fabricate(:status, account: remote_account) }
-
- subject { Status.as_public_timeline(viewer, true) }
-
- context 'without a viewer' do
- let(:viewer) { nil }
-
- it 'does not include remote instances statuses' do
- expect(subject).to include(local_status)
- expect(subject).not_to include(remote_status)
- end
- end
-
- context 'with a viewer' do
- let(:viewer) { Fabricate(:account, username: 'viewer') }
-
- it 'does not include remote instances statuses' do
- expect(subject).to include(local_status)
- expect(subject).not_to include(remote_status)
- end
-
- it 'is not affected by personal domain blocks' do
- viewer.block_domain!('test.com')
- expect(subject).to include(local_status)
- expect(subject).not_to include(remote_status)
- end
- end
- end
-
- context 'with a remote_only option set' do
- let!(:local_account) { Fabricate(:account, domain: nil) }
- let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
- let!(:local_status) { Fabricate(:status, account: local_account) }
- let!(:remote_status) { Fabricate(:status, account: remote_account) }
-
- subject { Status.as_public_timeline(viewer, :remote) }
-
- context 'without a viewer' do
- let(:viewer) { nil }
-
- it 'does not include local instances statuses' do
- expect(subject).not_to include(local_status)
- expect(subject).to include(remote_status)
- end
- end
-
- context 'with a viewer' do
- let(:viewer) { Fabricate(:account, username: 'viewer') }
-
- it 'does not include local instances statuses' do
- expect(subject).not_to include(local_status)
- expect(subject).to include(remote_status)
- end
- end
- end
-
- describe 'with an account passed in' do
- before do
- @account = Fabricate(:account)
- end
-
- it 'excludes statuses from accounts blocked by the account' do
- blocked = Fabricate(:account)
- Fabricate(:block, account: @account, target_account: blocked)
- blocked_status = Fabricate(:status, account: blocked)
-
- results = Status.as_public_timeline(@account)
- expect(results).not_to include(blocked_status)
- end
-
- it 'excludes statuses from accounts who have blocked the account' do
- blocked = Fabricate(:account)
- Fabricate(:block, account: blocked, target_account: @account)
- blocked_status = Fabricate(:status, account: blocked)
-
- results = Status.as_public_timeline(@account)
- expect(results).not_to include(blocked_status)
- end
-
- it 'excludes statuses from accounts muted by the account' do
- muted = Fabricate(:account)
- Fabricate(:mute, account: @account, target_account: muted)
- muted_status = Fabricate(:status, account: muted)
-
- results = Status.as_public_timeline(@account)
- expect(results).not_to include(muted_status)
- end
-
- it 'excludes statuses from accounts from personally blocked domains' do
- blocked = Fabricate(:account, domain: 'example.com')
- @account.block_domain!(blocked.domain)
- blocked_status = Fabricate(:status, account: blocked)
-
- results = Status.as_public_timeline(@account)
- expect(results).not_to include(blocked_status)
- end
-
- context 'with language preferences' do
- it 'excludes statuses in languages not allowed by the account user' do
- user = Fabricate(:user, chosen_languages: [:en, :es])
- @account.update(user: user)
- en_status = Fabricate(:status, language: 'en')
- es_status = Fabricate(:status, language: 'es')
- fr_status = Fabricate(:status, language: 'fr')
-
- results = Status.as_public_timeline(@account)
- expect(results).to include(en_status)
- expect(results).to include(es_status)
- expect(results).not_to include(fr_status)
- end
-
- it 'includes all languages when user does not have a setting' do
- user = Fabricate(:user, chosen_languages: nil)
- @account.update(user: user)
-
- en_status = Fabricate(:status, language: 'en')
- es_status = Fabricate(:status, language: 'es')
-
- results = Status.as_public_timeline(@account)
- expect(results).to include(en_status)
- expect(results).to include(es_status)
- end
-
- it 'includes all languages when account does not have a user' do
- expect(@account.user).to be_nil
- en_status = Fabricate(:status, language: 'en')
- es_status = Fabricate(:status, language: 'es')
-
- results = Status.as_public_timeline(@account)
- expect(results).to include(en_status)
- expect(results).to include(es_status)
- end
- end
- end
- end
-
- describe '.as_tag_timeline' do
- it 'includes statuses with a tag' do
- tag = Fabricate(:tag)
- status = Fabricate(:status, tags: [tag])
- other = Fabricate(:status)
-
- results = Status.as_tag_timeline(tag)
- expect(results).to include(status)
- expect(results).not_to include(other)
- end
-
- it 'allows replies to be included' do
- original = Fabricate(:status)
- tag = Fabricate(:tag)
- status = Fabricate(:status, tags: [tag], in_reply_to_id: original.id)
-
- results = Status.as_tag_timeline(tag)
- expect(results).to include(status)
- end
- end
-
describe '.permitted_for' do
subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
diff --git a/spec/services/hashtag_query_service_spec.rb b/spec/models/tag_feed_spec.rb
index 24282d2f0..17d88eb99 100644
--- a/spec/services/hashtag_query_service_spec.rb
+++ b/spec/models/tag_feed_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
-describe HashtagQueryService, type: :service do
- describe '.call' do
+describe TagFeed, type: :service do
+ describe '#get' do
let(:account) { Fabricate(:account) }
let(:tag1) { Fabricate(:tag) }
let(:tag2) { Fabricate(:tag) }
@@ -10,35 +10,35 @@ describe HashtagQueryService, type: :service do
let!(:both) { Fabricate(:status, tags: [tag1, tag2]) }
it 'can add tags in "any" mode' do
- results = subject.call(tag1, { any: [tag2.name] })
+ results = described_class.new(tag1, nil, any: [tag2.name]).get(20)
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] })
+ results = described_class.new(tag1, nil, all: [tag2.name]).get(20)
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] })
+ results = described_class.new(tag1, nil, none: [tag2.name]).get(20)
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] })
+ results = described_class.new(tag1, nil, wark: [tag2.name]).get(20)
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'] })
+ results = described_class.new(tag1, nil, any: ['wark']).get(20)
expect(results).to include status1
expect(results).to_not include status2
expect(results).to include both
@@ -46,15 +46,23 @@ describe HashtagQueryService, type: :service do
it 'can restrict to an account' do
BlockService.new.call(account, status1.account)
- results = subject.call(tag1, { none: [tag2.name] }, account)
+ results = described_class.new(tag1, account, none: [tag2.name]).get(20)
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)
+ results = described_class.new(tag1, nil, any: [tag2.name], local: true).get(20)
expect(results).to_not include status1
end
+
+ it 'allows replies to be included' do
+ original = Fabricate(:status)
+ status = Fabricate(:status, tags: [tag1], in_reply_to_id: original.id)
+
+ results = described_class.new(tag1, nil).get(20)
+ expect(results).to include(status)
+ end
end
end
diff --git a/spec/models/webauthn_credentials_spec.rb b/spec/models/webauthn_credentials_spec.rb
index 9289c371e..a63ae6cd2 100644
--- a/spec/models/webauthn_credentials_spec.rb
+++ b/spec/models/webauthn_credentials_spec.rb
@@ -69,8 +69,8 @@ RSpec.describe WebauthnCredential, type: :model do
expect(webauthn_credential).to model_have_error_on_field(:sign_count)
end
- it 'is invalid if sign_count is greater 2**32 - 1' do
- webauthn_credential = Fabricate.build(:webauthn_credential, sign_count: 2**32)
+ it 'is invalid if sign_count is greater 2**63 - 1' do
+ webauthn_credential = Fabricate.build(:webauthn_credential, sign_count: 2**63)
webauthn_credential.valid?
diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb
index 6648b0888..1347ca4a0 100644
--- a/spec/policies/account_policy_spec.rb
+++ b/spec/policies/account_policy_spec.rb
@@ -7,8 +7,9 @@ RSpec.describe AccountPolicy do
let(:subject) { described_class }
let(:admin) { Fabricate(:user, admin: true).account }
let(:john) { Fabricate(:user).account }
+ let(:alice) { Fabricate(:user).account }
- permissions :index?, :show?, :unsuspend?, :unsilence?, :remove_avatar?, :remove_header? do
+ permissions :index? do
context 'staff' do
it 'permits' do
expect(subject).to permit(admin)
@@ -22,6 +23,38 @@ RSpec.describe AccountPolicy do
end
end
+ permissions :show?, :unsilence?, :unsensitive?, :remove_avatar?, :remove_header? do
+ context 'staff' do
+ it 'permits' do
+ expect(subject).to permit(admin, alice)
+ end
+ end
+
+ context 'not staff' do
+ it 'denies' do
+ expect(subject).to_not permit(john, alice)
+ end
+ end
+ end
+
+ permissions :unsuspend? do
+ before do
+ alice.suspend!
+ end
+
+ context 'staff' do
+ it 'permits' do
+ expect(subject).to permit(admin, alice)
+ end
+ end
+
+ context 'not staff' do
+ it 'denies' do
+ expect(subject).to_not permit(john, alice)
+ end
+ end
+ end
+
permissions :redownload?, :subscribe?, :unsubscribe? do
context 'admin' do
it 'permits' do
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/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
diff --git a/spec/workers/activitypub/delivery_worker_spec.rb b/spec/workers/activitypub/delivery_worker_spec.rb
index 351be185c..f4633731e 100644
--- a/spec/workers/activitypub/delivery_worker_spec.rb
+++ b/spec/workers/activitypub/delivery_worker_spec.rb
@@ -3,16 +3,22 @@
require 'rails_helper'
describe ActivityPub::DeliveryWorker do
+ include RoutingHelper
+
subject { described_class.new }
let(:sender) { Fabricate(:account) }
let(:payload) { 'test' }
+ before do
+ allow_any_instance_of(Account).to receive(:remote_followers_hash).with('https://example.com/').and_return('somehash')
+ end
+
describe 'perform' do
it 'performs a request' do
stub_request(:post, 'https://example.com/api').to_return(status: 200)
- subject.perform(payload, sender.id, 'https://example.com/api')
- expect(a_request(:post, 'https://example.com/api')).to have_been_made.once
+ subject.perform(payload, sender.id, 'https://example.com/api', { synchronize_followers: true })
+ expect(a_request(:post, 'https://example.com/api').with(headers: { 'Collection-Synchronization' => "collectionId=\"#{account_followers_url(sender)}\", digest=\"somehash\", url=\"#{account_followers_synchronization_url(sender)}\"" })).to have_been_made.once
end
it 'raises when request fails' do
diff --git a/spec/workers/refollow_worker_spec.rb b/spec/workers/refollow_worker_spec.rb
index 29771aa59..df6731b64 100644
--- a/spec/workers/refollow_worker_spec.rb
+++ b/spec/workers/refollow_worker_spec.rb
@@ -23,8 +23,8 @@ describe RefollowWorker do
result = subject.perform(account.id)
expect(result).to be_nil
- expect(service).to have_received(:call).with(alice, account, reblogs: true)
- expect(service).to have_received(:call).with(bob, account, reblogs: false)
+ expect(service).to have_received(:call).with(alice, account, reblogs: true, notify: false, bypass_limit: true)
+ expect(service).to have_received(:call).with(bob, account, reblogs: false, notify: false, bypass_limit: true)
end
end
end
diff --git a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb b/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
index 7fae680ba..914eed829 100644
--- a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
+++ b/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
@@ -16,8 +16,8 @@ describe Scheduler::FeedCleanupScheduler do
expect(Redis.current.zcard(feed_key_for(inactive_user))).to eq 0
expect(Redis.current.zcard(feed_key_for(active_user))).to eq 1
- expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs'))).to be false
- expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs:2'))).to be false
+ expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs'))).to be false
+ expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs:2'))).to be false
end
def feed_key_for(user, subtype = nil)