Convert activitypub/* controller specs to request specs (#33992)

This commit is contained in:
Matt Jankowski 2025-03-27 10:55:13 -04:00 committed by GitHub
parent 445aa4ac72
commit ef879a532f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 197 additions and 129 deletions

View File

@ -2,22 +2,19 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::CollectionsController do RSpec.describe 'ActivityPub Collections' do
let!(:account) { Fabricate(:account) } let!(:account) { Fabricate(:account) }
let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) } let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
let(:remote_account) { nil } let(:remote_account) { nil }
before do before do
allow(controller).to receive(:signed_request_actor).and_return(remote_account) Fabricate.times(2, :status_pin, account: account)
Fabricate(:status_pin, account: account)
Fabricate(:status_pin, account: account)
Fabricate(:status_pin, account: account, status: private_pinned) Fabricate(:status_pin, account: account, status: private_pinned)
Fabricate(:status, account: account, visibility: :private) Fabricate(:status, account: account, visibility: :private)
end end
describe 'GET #show' do describe 'GET #show' do
subject(:response) { get :show, params: { id: id, account_username: account.username } } subject { get account_collection_path(id: id, account_username: account.username), headers: nil, sign_with: remote_account }
context 'when id is "featured"' do context 'when id is "featured"' do
let(:id) { 'featured' } let(:id) { 'featured' }
@ -26,10 +23,13 @@ RSpec.describe ActivityPub::CollectionsController do
let(:remote_account) { nil } let(:remote_account) { nil }
it 'returns http success and correct media type and correct items' do it 'returns http success and correct media type and correct items' do
subject
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)
.and have_cacheable_headers .and have_cacheable_headers
expect(response.media_type).to eq 'application/activity+json' expect(response.media_type)
.to eq 'application/activity+json'
expect(response.parsed_body[:orderedItems]) expect(response.parsed_body[:orderedItems])
.to be_an(Array) .to be_an(Array)
@ -45,17 +45,21 @@ RSpec.describe ActivityPub::CollectionsController do
end end
it 'returns http gone' do it 'returns http gone' do
expect(response).to have_http_status(410) subject
expect(response)
.to have_http_status(410)
end end
end end
context 'when account is temporarily suspended' do context 'when account is temporarily suspended' do
before do before { account.suspend! }
account.suspend!
end
it 'returns http forbidden' do it 'returns http forbidden' do
expect(response).to have_http_status(403) subject
expect(response)
.to have_http_status(403)
end end
end end
end end
@ -65,11 +69,14 @@ RSpec.describe ActivityPub::CollectionsController do
context 'when getting a featured resource' do context 'when getting a featured resource' do
it 'returns http success and correct media type and expected items' do it 'returns http success and correct media type and expected items' do
subject
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)
.and have_cacheable_headers .and have_cacheable_headers
expect(response.media_type).to eq 'application/activity+json' expect(response.media_type)
.to eq 'application/activity+json'
expect(response.parsed_body[:orderedItems]) expect(response.parsed_body[:orderedItems])
.to be_an(Array) .to be_an(Array)
@ -80,39 +87,45 @@ RSpec.describe ActivityPub::CollectionsController do
end end
context 'with authorized fetch mode' do context 'with authorized fetch mode' do
before do before { Setting.authorized_fetch = true }
allow(controller).to receive(:authorized_fetch_mode?).and_return(true)
end
context 'when signed request account is blocked' do context 'when signed request account is blocked' do
before do before { account.block!(remote_account) }
account.block!(remote_account)
end
it 'returns http success and correct media type and cache headers and empty items' do it 'returns http success and correct media type and cache headers and empty items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to include 'private'
expect(response.parsed_body[:orderedItems]) expect(response)
.to be_an(Array) .to have_http_status(200)
.and be_empty expect(response.media_type)
.to eq('application/activity+json')
expect(response.headers['Cache-Control'])
.to include('private')
expect(response.parsed_body)
.to include(
orderedItems: be_an(Array).and(be_empty)
)
end end
end end
context 'when signed request account is domain blocked' do context 'when signed request account is domain blocked' do
before do before { account.block_domain!(remote_account.domain) }
account.block_domain!(remote_account.domain)
end
it 'returns http success and correct media type and cache headers and empty items' do it 'returns http success and correct media type and cache headers and empty items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to include 'private'
expect(response.parsed_body[:orderedItems]) expect(response)
.to be_an(Array) .to have_http_status(200)
.and be_empty expect(response.media_type)
.to eq('application/activity+json')
expect(response.headers['Cache-Control'])
.to include('private')
expect(response.parsed_body)
.to include(
orderedItems: be_an(Array).and(be_empty)
)
end end
end end
end end
@ -123,7 +136,10 @@ RSpec.describe ActivityPub::CollectionsController do
let(:id) { 'hoge' } let(:id) { 'hoge' }
it 'returns http not found' do it 'returns http not found' do
expect(response).to have_http_status(404) subject
expect(response)
.to have_http_status(404)
end end
end end
end end

View File

@ -2,7 +2,7 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::FollowersSynchronizationsController do RSpec.describe 'ActivityPub Follower Synchronizations' do
let!(:account) { Fabricate(:account) } let!(:account) { Fabricate(:account) }
let!(:follower_example_com_user_a) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') } let!(:follower_example_com_user_a) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/a') }
let!(:follower_example_com_user_b) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') } let!(:follower_example_com_user_b) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
@ -14,32 +14,34 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController do
follower_example_com_user_b.follow!(account) follower_example_com_user_b.follow!(account)
follower_foo_com_user_a.follow!(account) follower_foo_com_user_a.follow!(account)
follower_example_com_instance_actor.follow!(account) follower_example_com_instance_actor.follow!(account)
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
end end
describe 'GET #show' do describe 'GET #show' do
context 'without signature' do context 'without signature' do
let(:remote_account) { nil } subject { get account_followers_synchronization_path(account_username: account.username) }
before do
get :show, params: { account_username: account.username }
end
it 'returns http not authorized' do it 'returns http not authorized' do
expect(response).to have_http_status(401) subject
expect(response)
.to have_http_status(401)
end end
end end
context 'with signature from example.com' do context 'with signature from example.com' do
subject(:response) { get :show, params: { account_username: account.username } } subject { get account_followers_synchronization_path(account_username: account.username), headers: nil, sign_with: remote_account }
let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') } let(:remote_account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/instance') }
it 'returns http success and cache control and activity json types and correct items' do it 'returns http success and cache control and activity json types and correct items' do
expect(response).to have_http_status(200) subject
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
expect(response.media_type).to eq 'application/activity+json' expect(response)
.to have_http_status(200)
expect(response.headers['Cache-Control'])
.to eq 'max-age=0, private'
expect(response.media_type)
.to eq 'application/activity+json'
expect(response.parsed_body[:orderedItems]) expect(response.parsed_body[:orderedItems])
.to be_an(Array) .to be_an(Array)
@ -57,17 +59,21 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController do
end end
it 'returns http gone' do it 'returns http gone' do
expect(response).to have_http_status(410) subject
expect(response)
.to have_http_status(410)
end end
end end
context 'when account is temporarily suspended' do context 'when account is temporarily suspended' do
before do before { account.suspend! }
account.suspend!
end
it 'returns http forbidden' do it 'returns http forbidden' do
expect(response).to have_http_status(403) subject
expect(response)
.to have_http_status(403)
end end
end end
end end

View File

@ -2,7 +2,7 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::OutboxesController do RSpec.describe 'ActivityPub Outboxes' do
let!(:account) { Fabricate(:account) } let!(:account) { Fabricate(:account) }
before do before do
@ -11,13 +11,11 @@ RSpec.describe ActivityPub::OutboxesController do
Fabricate(:status, account: account, visibility: :private) Fabricate(:status, account: account, visibility: :private)
Fabricate(:status, account: account, visibility: :direct) Fabricate(:status, account: account, visibility: :direct)
Fabricate(:status, account: account, visibility: :limited) Fabricate(:status, account: account, visibility: :limited)
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
end end
describe 'GET #show' do describe 'GET #show' do
context 'without signature' do context 'without signature' do
subject(:response) { get :show, params: { account_username: account.username, page: page } } subject { get account_outbox_path(account_username: account.username, page: page) }
let(:remote_account) { nil } let(:remote_account) { nil }
@ -25,13 +23,18 @@ RSpec.describe ActivityPub::OutboxesController do
let(:page) { nil } let(:page) { nil }
it 'returns http success and correct media type and headers and items count' do it 'returns http success and correct media type and headers and items count' do
subject
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)
.and have_cacheable_headers .and have_cacheable_headers
expect(response.media_type).to eq 'application/activity+json' expect(response.media_type)
expect(response.headers['Vary']).to be_nil .to eq 'application/activity+json'
expect(response.parsed_body[:totalItems]).to eq 4 expect(response.headers['Vary'])
.to be_nil
expect(response.parsed_body[:totalItems])
.to eq 4
end end
context 'when account is permanently suspended' do context 'when account is permanently suspended' do
@ -41,17 +44,21 @@ RSpec.describe ActivityPub::OutboxesController do
end end
it 'returns http gone' do it 'returns http gone' do
expect(response).to have_http_status(410) subject
expect(response)
.to have_http_status(410)
end end
end end
context 'when account is temporarily suspended' do context 'when account is temporarily suspended' do
before do before { account.suspend! }
account.suspend!
end
it 'returns http forbidden' do it 'returns http forbidden' do
expect(response).to have_http_status(403) subject
expect(response)
.to have_http_status(403)
end end
end end
end end
@ -60,12 +67,16 @@ RSpec.describe ActivityPub::OutboxesController do
let(:page) { 'true' } let(:page) { 'true' }
it 'returns http success and correct media type and vary header and items' do it 'returns http success and correct media type and vary header and items' do
subject
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)
.and have_cacheable_headers .and have_cacheable_headers
expect(response.media_type).to eq 'application/activity+json' expect(response.media_type)
expect(response.headers['Vary']).to include 'Signature' .to eq 'application/activity+json'
expect(response.headers['Vary'])
.to include 'Signature'
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
@ -82,35 +93,42 @@ RSpec.describe ActivityPub::OutboxesController do
end end
it 'returns http gone' do it 'returns http gone' do
expect(response).to have_http_status(410) subject
expect(response)
.to have_http_status(410)
end end
end end
context 'when account is temporarily suspended' do context 'when account is temporarily suspended' do
before do before { account.suspend! }
account.suspend!
end
it 'returns http forbidden' do it 'returns http forbidden' do
expect(response).to have_http_status(403) subject
expect(response)
.to have_http_status(403)
end end
end end
end end
end end
context 'with signature' do context 'with signature' do
subject { get account_outbox_path(account_username: account.username, page: page), headers: nil, sign_with: remote_account }
let(:remote_account) { Fabricate(:account, domain: 'example.com') } let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:page) { 'true' } let(:page) { 'true' }
context 'when signed request account does not follow account' do context 'when signed request account does not follow account' do
before do
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success and correct media type and headers and items' do it 'returns http success and correct media type and headers and items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to eq 'max-age=60, private' expect(response)
.to have_http_status(200)
expect(response.media_type)
.to eq 'application/activity+json'
expect(response.headers['Cache-Control'])
.to eq 'private, no-store'
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
@ -122,15 +140,17 @@ RSpec.describe ActivityPub::OutboxesController do
end end
context 'when signed request account follows account' do context 'when signed request account follows account' do
before do before { remote_account.follow!(account) }
remote_account.follow!(account)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success and correct media type and headers and items' do it 'returns http success and correct media type and headers and items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to eq 'max-age=60, private' expect(response)
.to have_http_status(200)
expect(response.media_type)
.to eq 'application/activity+json'
expect(response.headers['Cache-Control'])
.to eq 'private, no-store'
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
@ -142,15 +162,17 @@ RSpec.describe ActivityPub::OutboxesController do
end end
context 'when signed request account is blocked' do context 'when signed request account is blocked' do
before do before { account.block!(remote_account) }
account.block!(remote_account)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success and correct media type and headers and items' do it 'returns http success and correct media type and headers and items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to eq 'max-age=60, private' expect(response)
.to have_http_status(200)
expect(response.media_type)
.to eq 'application/activity+json'
expect(response.headers['Cache-Control'])
.to eq 'private, no-store'
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
@ -160,15 +182,17 @@ RSpec.describe ActivityPub::OutboxesController do
end end
context 'when signed request account is domain blocked' do context 'when signed request account is domain blocked' do
before do before { account.block_domain!(remote_account.domain) }
account.block_domain!(remote_account.domain)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success and correct media type and headers and items' do it 'returns http success and correct media type and headers and items' do
expect(response).to have_http_status(200) subject
expect(response.media_type).to eq 'application/activity+json'
expect(response.headers['Cache-Control']).to eq 'max-age=60, private' expect(response)
.to have_http_status(200)
expect(response.media_type)
.to eq 'application/activity+json'
expect(response.headers['Cache-Control'])
.to eq 'private, no-store'
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(

View File

@ -2,9 +2,9 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::RepliesController do RSpec.describe 'ActivityPub Replies' do
let(:status) { Fabricate(:status, visibility: parent_visibility) } let(:status) { Fabricate(:status, visibility: parent_visibility) }
let(:remote_account) { Fabricate(:account, domain: 'foobar.com') } let(:remote_account) { Fabricate(:account, domain: 'foobar.com') }
let(:remote_reply_id) { 'https://foobar.com/statuses/1234' } let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
let(:remote_querier) { nil } let(:remote_querier) { nil }
@ -13,7 +13,10 @@ RSpec.describe ActivityPub::RepliesController do
let(:parent_visibility) { :private } let(:parent_visibility) { :private }
it 'returns http not found' do it 'returns http not found' do
expect(response).to have_http_status(404) subject
expect(response)
.to have_http_status(404)
end end
end end
@ -21,7 +24,10 @@ RSpec.describe ActivityPub::RepliesController do
let(:parent_visibility) { :direct } let(:parent_visibility) { :direct }
it 'returns http not found' do it 'returns http not found' do
expect(response).to have_http_status(404) subject
expect(response)
.to have_http_status(404)
end end
end end
end end
@ -31,7 +37,10 @@ RSpec.describe ActivityPub::RepliesController do
let(:parent_visibility) { :public } let(:parent_visibility) { :public }
it 'returns http not found' do it 'returns http not found' do
expect(response).to have_http_status(404) subject
expect(response)
.to have_http_status(404)
end end
end end
@ -48,19 +57,23 @@ RSpec.describe ActivityPub::RepliesController do
end end
it 'returns http gone' do it 'returns http gone' do
expect(response).to have_http_status(410) subject
expect(response)
.to have_http_status(410)
end end
end end
context 'when account is temporarily suspended' do context 'when account is temporarily suspended' do
let(:parent_visibility) { :public } let(:parent_visibility) { :public }
before do before { status.account.suspend! }
status.account.suspend!
end
it 'returns http forbidden' do it 'returns http forbidden' do
expect(response).to have_http_status(403) subject
expect(response)
.to have_http_status(403)
end end
end end
@ -68,15 +81,20 @@ RSpec.describe ActivityPub::RepliesController do
let(:parent_visibility) { :public } let(:parent_visibility) { :public }
it 'returns http success and correct media type' do it 'returns http success and correct media type' do
subject
expect(response) expect(response)
.to have_http_status(200) .to have_http_status(200)
.and have_cacheable_headers .and have_cacheable_headers
expect(response.media_type).to eq 'application/activity+json' expect(response.media_type)
.to eq 'application/activity+json'
end end
context 'without only_other_accounts' do context 'without `only_other_accounts` param' do
it "returns items with thread author's replies" do it "returns items with thread author's replies" do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and( first: be_a(Hash).and(
@ -91,6 +109,8 @@ RSpec.describe ActivityPub::RepliesController do
context 'when there are few self-replies' do context 'when there are few self-replies' do
it 'points next to replies from other people' do it 'points next to replies from other people' do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and( first: be_a(Hash).and(
@ -108,6 +128,8 @@ RSpec.describe ActivityPub::RepliesController do
end end
it 'points next to other self-replies' do it 'points next to other self-replies' do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and( first: be_a(Hash).and(
@ -120,31 +142,33 @@ RSpec.describe ActivityPub::RepliesController do
end end
end end
context 'with only_other_accounts' do context 'with `only_other_accounts` param' do
let(:only_other_accounts) { 'true' } let(:only_other_accounts) { 'true' }
it 'returns items with other public or unlisted replies' do it 'returns items with other public or unlisted replies and correctly inlines replies and uses IDs' do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and( first: be_a(Hash).and(
include(items: be_an(Array).and(have_attributes(size: 3))) include(items: be_an(Array).and(have_attributes(size: 3)))
) )
) )
end
it 'only inlines items that are local and public or unlisted replies' do # Only inline replies that are local and public, or unlisted
expect(inlined_replies) expect(inlined_replies)
.to all(satisfy { |item| targets_public_collection?(item) }) .to all(satisfy { |item| targets_public_collection?(item) })
.and all(satisfy { |item| ActivityPub::TagManager.instance.local_uri?(item[:id]) }) .and all(satisfy { |item| ActivityPub::TagManager.instance.local_uri?(item[:id]) })
end
it 'uses ids for remote toots' do # Use ids for remote replies
expect(remote_replies) expect(remote_replies)
.to all(satisfy { |item| item.is_a?(String) && !ActivityPub::TagManager.instance.local_uri?(item) }) .to all(satisfy { |item| item.is_a?(String) && !ActivityPub::TagManager.instance.local_uri?(item) })
end end
context 'when there are few replies' do context 'when there are few replies' do
it 'does not have a next page' do it 'does not have a next page' do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and(not_include(next: be_present)) first: be_a(Hash).and(not_include(next: be_present))
@ -158,6 +182,8 @@ RSpec.describe ActivityPub::RepliesController do
end end
it 'points next to other replies' do it 'points next to other replies' do
subject
expect(response.parsed_body) expect(response.parsed_body)
.to include( .to include(
first: be_a(Hash).and( first: be_a(Hash).and(
@ -176,10 +202,8 @@ RSpec.describe ActivityPub::RepliesController do
before do before do
stub_const 'ActivityPub::RepliesController::DESCENDANTS_LIMIT', 5 stub_const 'ActivityPub::RepliesController::DESCENDANTS_LIMIT', 5
allow(controller).to receive(:signed_request_actor).and_return(remote_querier)
Fabricate(:status, thread: status, visibility: :public) Fabricate.times(2, :status, thread: status, visibility: :public)
Fabricate(:status, thread: status, visibility: :public)
Fabricate(:status, thread: status, visibility: :private) Fabricate(:status, thread: status, visibility: :private)
Fabricate(:status, account: status.account, thread: status, visibility: :public) Fabricate(:status, account: status.account, thread: status, visibility: :public)
Fabricate(:status, account: status.account, thread: status, visibility: :private) Fabricate(:status, account: status.account, thread: status, visibility: :private)
@ -188,31 +212,29 @@ RSpec.describe ActivityPub::RepliesController do
end end
describe 'GET #index' do describe 'GET #index' do
subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts } }
let(:only_other_accounts) { nil } let(:only_other_accounts) { nil }
context 'with no signature' do context 'with no signature' do
subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts) }
it_behaves_like 'allowed access' it_behaves_like 'allowed access'
end end
context 'with signature' do context 'with signature' do
subject { get account_status_replies_path(account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts), headers: nil, sign_with: remote_querier }
let(:remote_querier) { Fabricate(:account, domain: 'example.com') } let(:remote_querier) { Fabricate(:account, domain: 'example.com') }
it_behaves_like 'allowed access' it_behaves_like 'allowed access'
context 'when signed request account is blocked' do context 'when signed request account is blocked' do
before do before { status.account.block!(remote_querier) }
status.account.block!(remote_querier)
end
it_behaves_like 'disallowed access' it_behaves_like 'disallowed access'
end end
context 'when signed request account is domain blocked' do context 'when signed request account is domain blocked' do
before do before { status.account.block_domain!(remote_querier.domain) }
status.account.block_domain!(remote_querier.domain)
end
it_behaves_like 'disallowed access' it_behaves_like 'disallowed access'
end end