Convert activitypub/*
controller specs to request specs (#33992)
This commit is contained in:
parent
445aa4ac72
commit
ef879a532f
4 changed files with 197 additions and 129 deletions
|
@ -1,130 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::CollectionsController do
|
||||
let!(:account) { Fabricate(:account) }
|
||||
let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) }
|
||||
let(:remote_account) { nil }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
|
||||
|
||||
Fabricate(:status_pin, account: account)
|
||||
Fabricate(:status_pin, account: account)
|
||||
Fabricate(:status_pin, account: account, status: private_pinned)
|
||||
Fabricate(:status, account: account, visibility: :private)
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
subject(:response) { get :show, params: { id: id, account_username: account.username } }
|
||||
|
||||
context 'when id is "featured"' do
|
||||
let(:id) { 'featured' }
|
||||
|
||||
context 'without signature' do
|
||||
let(:remote_account) { nil }
|
||||
|
||||
it 'returns http success and correct media type and correct items' do
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
expect(response.parsed_body[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 3)
|
||||
.and include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
.and not_include(private_pinned.text)
|
||||
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
|
||||
|
||||
context 'with signature' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
context 'when getting a featured resource' do
|
||||
it 'returns http success and correct media type and expected items' do
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
|
||||
expect(response.parsed_body[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 3)
|
||||
.and include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
.and not_include(private_pinned.text)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with authorized fetch mode' do
|
||||
before do
|
||||
allow(controller).to receive(:authorized_fetch_mode?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when signed request account is blocked' do
|
||||
before do
|
||||
account.block!(remote_account)
|
||||
end
|
||||
|
||||
it 'returns http success and correct media type and cache headers and empty items' do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
|
||||
expect(response.parsed_body[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed request account is domain blocked' do
|
||||
before do
|
||||
account.block_domain!(remote_account.domain)
|
||||
end
|
||||
|
||||
it 'returns http success and correct media type and cache headers and empty items' do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
|
||||
expect(response.parsed_body[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when id is not "featured"' do
|
||||
let(:id) { 'hoge' }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,75 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::FollowersSynchronizationsController do
|
||||
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_b) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/users/b') }
|
||||
let!(:follower_foo_com_user_a) { Fabricate(:account, domain: 'foo.com', uri: 'https://foo.com/users/a') }
|
||||
let!(:follower_example_com_instance_actor) { Fabricate(:account, username: 'instance-actor', domain: 'example.com', uri: 'https://example.com') }
|
||||
|
||||
before do
|
||||
follower_example_com_user_a.follow!(account)
|
||||
follower_example_com_user_b.follow!(account)
|
||||
follower_foo_com_user_a.follow!(account)
|
||||
follower_example_com_instance_actor.follow!(account)
|
||||
|
||||
allow(controller).to receive(:signed_request_actor).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
|
||||
subject(:response) { get :show, params: { account_username: account.username } }
|
||||
|
||||
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
|
||||
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])
|
||||
.to be_an(Array)
|
||||
.and contain_exactly(
|
||||
follower_example_com_instance_actor.uri,
|
||||
follower_example_com_user_a.uri,
|
||||
follower_example_com_user_b.uri
|
||||
)
|
||||
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
|
|
@ -1,197 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::OutboxesController do
|
||||
let!(:account) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate(:status, account: account, visibility: :public)
|
||||
Fabricate(:status, account: account, visibility: :unlisted)
|
||||
Fabricate(:status, account: account, visibility: :private)
|
||||
Fabricate(:status, account: account, visibility: :direct)
|
||||
Fabricate(:status, account: account, visibility: :limited)
|
||||
|
||||
allow(controller).to receive(:signed_request_actor).and_return(remote_account)
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
context 'without signature' do
|
||||
subject(:response) { get :show, params: { account_username: account.username, page: page } }
|
||||
|
||||
let(:remote_account) { nil }
|
||||
|
||||
context 'with page not requested' do
|
||||
let(:page) { nil }
|
||||
|
||||
it 'returns http success and correct media type and headers and items count' do
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Vary']).to be_nil
|
||||
expect(response.parsed_body[:totalItems]).to eq 4
|
||||
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
|
||||
|
||||
context 'with page requested' do
|
||||
let(:page) { 'true' }
|
||||
|
||||
it 'returns http success and correct media type and vary header and items' do
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Vary']).to include 'Signature'
|
||||
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
orderedItems: be_an(Array)
|
||||
.and(have_attributes(size: 2))
|
||||
.and(all(satisfy { |item| targets_public_collection?(item) }))
|
||||
)
|
||||
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
|
||||
|
||||
context 'with signature' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:page) { 'true' }
|
||||
|
||||
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
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
|
||||
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
orderedItems: be_an(Array)
|
||||
.and(have_attributes(size: 2))
|
||||
.and(all(satisfy { |item| targets_public_collection?(item) }))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed request account follows account' do
|
||||
before do
|
||||
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
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
|
||||
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
orderedItems: be_an(Array)
|
||||
.and(have_attributes(size: 3))
|
||||
.and(all(satisfy { |item| targets_public_collection?(item) || targets_followers_collection?(item, account) }))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed request account is blocked' do
|
||||
before do
|
||||
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
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
|
||||
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
orderedItems: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed request account is domain blocked' do
|
||||
before do
|
||||
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
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
expect(response.headers['Cache-Control']).to eq 'max-age=60, private'
|
||||
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
orderedItems: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ap_public_collection
|
||||
ActivityPub::TagManager::COLLECTIONS[:public]
|
||||
end
|
||||
|
||||
def targets_public_collection?(item)
|
||||
item[:to].include?(ap_public_collection) || item[:cc].include?(ap_public_collection)
|
||||
end
|
||||
|
||||
def targets_followers_collection?(item, account)
|
||||
item[:to].include?(
|
||||
account_followers_url(account, ActionMailer::Base.default_url_options)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,250 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::RepliesController do
|
||||
let(:status) { Fabricate(:status, visibility: parent_visibility) }
|
||||
let(:remote_account) { Fabricate(:account, domain: 'foobar.com') }
|
||||
let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
|
||||
let(:remote_querier) { nil }
|
||||
|
||||
shared_examples 'common behavior' do
|
||||
context 'when status is private' do
|
||||
let(:parent_visibility) { :private }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is direct' do
|
||||
let(:parent_visibility) { :direct }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'disallowed access' do
|
||||
context 'when status is public' do
|
||||
let(:parent_visibility) { :public }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'common behavior'
|
||||
end
|
||||
|
||||
shared_examples 'allowed access' do
|
||||
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
|
||||
let(:parent_visibility) { :public }
|
||||
|
||||
it 'returns http success and correct media type' do
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
.and have_cacheable_headers
|
||||
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
context 'without only_other_accounts' do
|
||||
it "returns items with thread author's replies" do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(
|
||||
include(
|
||||
items: be_an(Array)
|
||||
.and(have_attributes(size: 1))
|
||||
.and(all(satisfy { |item| targets_public_collection?(item) }))
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'when there are few self-replies' do
|
||||
it 'points next to replies from other people' do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(
|
||||
include(
|
||||
next: satisfy { |value| (parsed_uri_query_values(value) & %w(only_other_accounts=true page=true)).any? }
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are many self-replies' do
|
||||
before do
|
||||
10.times { Fabricate(:status, account: status.account, thread: status, visibility: :public) }
|
||||
end
|
||||
|
||||
it 'points next to other self-replies' do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(
|
||||
include(
|
||||
next: satisfy { |value| (parsed_uri_query_values(value) & %w(only_other_accounts=false page=true)).any? }
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with only_other_accounts' do
|
||||
let(:only_other_accounts) { 'true' }
|
||||
|
||||
it 'returns items with other public or unlisted replies' do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(
|
||||
include(items: be_an(Array).and(have_attributes(size: 3)))
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'only inlines items that are local and public or unlisted replies' do
|
||||
expect(inlined_replies)
|
||||
.to all(satisfy { |item| targets_public_collection?(item) })
|
||||
.and all(satisfy { |item| ActivityPub::TagManager.instance.local_uri?(item[:id]) })
|
||||
end
|
||||
|
||||
it 'uses ids for remote toots' do
|
||||
expect(remote_replies)
|
||||
.to all(satisfy { |item| item.is_a?(String) && !ActivityPub::TagManager.instance.local_uri?(item) })
|
||||
end
|
||||
|
||||
context 'when there are few replies' do
|
||||
it 'does not have a next page' do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(not_include(next: be_present))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are many replies' do
|
||||
before do
|
||||
10.times { Fabricate(:status, thread: status, visibility: :public) }
|
||||
end
|
||||
|
||||
it 'points next to other replies' do
|
||||
expect(response.parsed_body)
|
||||
.to include(
|
||||
first: be_a(Hash).and(
|
||||
include(
|
||||
next: satisfy { |value| (parsed_uri_query_values(value) & %w(only_other_accounts=true page=true)).any? }
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'common behavior'
|
||||
end
|
||||
|
||||
before do
|
||||
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(:status, thread: status, visibility: :public)
|
||||
Fabricate(:status, thread: status, visibility: :private)
|
||||
Fabricate(:status, account: status.account, thread: status, visibility: :public)
|
||||
Fabricate(:status, account: status.account, thread: status, visibility: :private)
|
||||
|
||||
Fabricate(:status, account: remote_account, thread: status, visibility: :public, uri: remote_reply_id)
|
||||
end
|
||||
|
||||
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 }
|
||||
|
||||
context 'with no signature' do
|
||||
it_behaves_like 'allowed access'
|
||||
end
|
||||
|
||||
context 'with signature' do
|
||||
let(:remote_querier) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
it_behaves_like 'allowed access'
|
||||
|
||||
context 'when signed request account is blocked' do
|
||||
before do
|
||||
status.account.block!(remote_querier)
|
||||
end
|
||||
|
||||
it_behaves_like 'disallowed access'
|
||||
end
|
||||
|
||||
context 'when signed request account is domain blocked' do
|
||||
before do
|
||||
status.account.block_domain!(remote_querier.domain)
|
||||
end
|
||||
|
||||
it_behaves_like 'disallowed access'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inlined_replies
|
||||
response
|
||||
.parsed_body[:first][:items]
|
||||
.select { |x| x.is_a?(Hash) }
|
||||
end
|
||||
|
||||
def remote_replies
|
||||
response
|
||||
.parsed_body[:first][:items]
|
||||
.reject { |x| x.is_a?(Hash) }
|
||||
end
|
||||
|
||||
def parsed_uri_query_values(uri)
|
||||
Addressable::URI
|
||||
.parse(uri)
|
||||
.query
|
||||
.split('&')
|
||||
end
|
||||
|
||||
def ap_public_collection
|
||||
ActivityPub::TagManager::COLLECTIONS[:public]
|
||||
end
|
||||
|
||||
def targets_public_collection?(item)
|
||||
item[:to].include?(ap_public_collection) || item[:cc].include?(ap_public_collection)
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue