Browse Source

Add more tests for ActivityPub controllers (#13585)

master^2
Eugen Rochko GitHub 11 months ago
parent
commit
988b0493fe
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1315 additions and 142 deletions
  1. +7
    -7
      app/controllers/accounts_controller.rb
  2. +10
    -7
      app/controllers/activitypub/collections_controller.rb
  3. +3
    -3
      app/controllers/activitypub/outboxes_controller.rb
  4. +15
    -6
      app/controllers/activitypub/replies_controller.rb
  5. +1
    -1
      app/controllers/api/v1/polls/votes_controller.rb
  6. +1
    -1
      app/controllers/api/v1/polls_controller.rb
  7. +6
    -5
      app/controllers/api/v1/push/subscriptions_controller.rb
  8. +1
    -2
      app/controllers/api/v1/statuses/mutes_controller.rb
  9. +1
    -1
      app/controllers/api/v1/statuses_controller.rb
  10. +1
    -1
      app/controllers/media_controller.rb
  11. +1
    -1
      app/controllers/remote_interaction_controller.rb
  12. +1
    -1
      app/controllers/statuses_controller.rb
  13. +1
    -1
      app/models/status.rb
  14. +122
    -10
      spec/controllers/activitypub/collections_controller_spec.rb
  15. +17
    -11
      spec/controllers/activitypub/inboxes_controller_spec.rb
  16. +162
    -8
      spec/controllers/activitypub/outboxes_controller_spec.rb
  17. +196
    -0
      spec/controllers/activitypub/replies_controller_spec.rb
  18. +768
    -75
      spec/controllers/statuses_controller_spec.rb
  19. +1
    -1
      spec/fabricators/status_pin_fabricator.rb

+ 7
- 7
app/controllers/accounts_controller.rb View File

@@ -27,7 +27,7 @@ class AccountsController < ApplicationController
end

@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page(params)
@statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status)
@rss_url = rss_url

@@ -140,12 +140,12 @@ class AccountsController < ApplicationController
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end

def filtered_status_page(params)
if params[:min_id].present?
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
def filtered_status_page
filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end

def restrict_fields_to


+ 10
- 7
app/controllers/activitypub/collections_controller.rb View File

@@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size
case params[:id]
when 'featured'
@account.pinned_statuses.count
@size = @account.pinned_statuses.count
else
raise ActiveRecord::RecordNotFound
not_found
end
end

def scope_for_collection
case params[:id]
when 'featured'
return Status.none if @account.blocking?(signed_request_account)

@account.pinned_statuses
else
raise ActiveRecord::RecordNotFound
# Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
# would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
Status.none
else
@account.pinned_statuses
end
end
end



+ 3
- 3
app/controllers/activitypub/outboxes_controller.rb View File

@@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers

def show
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?)
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end

@@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
return unless page_requested?

@statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id])
@statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
@statuses = cache_collection(@statuses, Status)
end

def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end

def page_params


+ 15
- 6
app/controllers/activitypub/replies_controller.rb View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true

class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureAuthentication
include SignatureVerification
include Authorization
include AccountOwnedConcern

@@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController

private

def pundit_user
signed_request_account
end

def set_status
@status = @account.statuses.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_replies
@replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end
@@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
type: :unordered,
part_of: account_status_replies_url(@account, @status),
next: next_page,
items: @replies.map { |status| status.local ? status : status.uri }
items: @replies.map { |status| status.local? ? status : status.uri }
)

return page if page_requested?
@@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end

def page_requested?
params[:page] == 'true'
truthy_param?(:page)
end

def only_other_accounts?
truthy_param?(:only_other_accounts)
end

def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)

account_status_replies_url(
@account,
@status,
page: true,
min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id,
min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
only_other_accounts: only_other_accounts
)
end


+ 1
- 1
app/controllers/api/v1/polls/votes_controller.rb View File

@@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
@poll = Poll.attached.find(params[:poll_id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def vote_params


+ 1
- 1
app/controllers/api/v1/polls_controller.rb View File

@@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
@poll = Poll.attached.find(params[:id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def refresh_poll


+ 6
- 5
app/controllers/api/v1/push/subscriptions_controller.rb View File

@@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push }
before_action :require_user!
before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]

def create
@web_subscription&.destroy!
@@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end

def show
raise ActiveRecord::RecordNotFound if @web_subscription.nil?

render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end

def update
raise ActiveRecord::RecordNotFound if @web_subscription.nil?

@web_subscription.update!(data: data_params)

render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end

@@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end

def check_web_push_subscription
not_found if @web_subscription.nil?
end

def subscription_params
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end

def data_params
return {} if params[:data].blank?

params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end
end

+ 1
- 2
app/controllers/api/v1/statuses/mutes_controller.rb View File

@@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound
not_found
end

def set_conversation


+ 1
- 1
app/controllers/api/v1/statuses_controller.rb View File

@@ -67,7 +67,7 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_thread


+ 1
- 1
app/controllers/media_controller.rb View File

@@ -33,7 +33,7 @@ class MediaController < ApplicationController
def verify_permitted_status!
authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def check_playable


+ 1
- 1
app/controllers/remote_interaction_controller.rb View File

@@ -41,7 +41,7 @@ class RemoteInteractionController < ApplicationController
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end

def set_body_classes


+ 1
- 1
app/controllers/statuses_controller.rb View File

@@ -46,7 +46,7 @@ class StatusesController < ApplicationController
end

def embed
return not_found if @status.hidden?
return not_found if @status.hidden? || @status.reblog?

expires_in 180, public: true
response.headers['X-Frame-Options'] = 'ALLOWALL'


+ 1
- 1
app/models/status.rb View File

@@ -354,7 +354,7 @@ class Status < ApplicationRecord

if account.nil?
where(visibility: visibility)
elsif target_account.blocking?(account) # get rid of blocked peeps
elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
none
elsif account.id == target_account.id # author can see own stuff
all


+ 122
- 10
spec/controllers/activitypub/collections_controller_spec.rb View File

@@ -3,21 +3,133 @@
require 'rails_helper'

RSpec.describe ActivityPub::CollectionsController, type: :controller do
describe 'POST #show' do
let(:account) { Fabricate(:account) }
let!(:account) { Fabricate(:account) }
let(:remote_account) { nil }

context 'id is "featured"' do
it 'returns 200 with "application/activity+json"' do
post :show, params: { id: 'featured', account_username: account.username }
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)

expect(response).to have_http_status(200)
expect(response.content_type).to eq 'application/activity+json'
Fabricate(:status_pin, account: account)
Fabricate(:status_pin, account: account)
Fabricate(:status, account: account, visibility: :private)
end

describe 'GET #show' do
context 'when id is "featured"' do
context 'without signature' do
let(:remote_account) { nil }

before do
get :show, params: { id: 'featured', account_username: account.username }
end

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 public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

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
end
end

context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }

context do
before do
get :show, params: { id: 'featured', account_username: account.username }
end

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 public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

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
end
end

context 'in 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)
get :show, params: { id: 'featured', account_username: account.username }
end

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 private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
end

context 'when signed request account is domain blocked' do
before do
account.block_domain!(remote_account.domain)
get :show, params: { id: 'featured', account_username: account.username }
end

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 private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
end
end
end
end

context 'id is not "featured"' do
it 'returns 404' do
post :show, params: { id: 'hoge', account_username: account.username }
context 'when id is not "featured"' do
it 'returns http not found' do
get :show, params: { id: 'hoge', account_username: account.username }
expect(response).to have_http_status(404)
end
end


+ 17
- 11
spec/controllers/activitypub/inboxes_controller_spec.rb View File

@@ -3,25 +3,31 @@
require 'rails_helper'

RSpec.describe ActivityPub::InboxesController, type: :controller do
let(:remote_account) { nil }

before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end

describe 'POST #create' do
context 'with signed_request_account' do
it 'returns 202' do
allow(controller).to receive(:signed_request_account) do
Fabricate(:account)
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub) }

before do
post :create, body: '{}'
end

it 'returns http accepted' do
expect(response).to have_http_status(202)
end
end

context 'without signed_request_account' do
it 'returns 401' do
allow(controller).to receive(:signed_request_account) do
false
end

context 'without signature' do
before do
post :create, body: '{}'
end

it 'returns http not authorized' do
expect(response).to have_http_status(401)
end
end


+ 162
- 8
spec/controllers/activitypub/outboxes_controller_spec.rb View File

@@ -4,20 +4,174 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
let!(:account) { Fabricate(:account) }

before do
Fabricate(:status, account: account)
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)
end

before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end

describe 'GET #show' do
before do
get :show, params: { account_username: account.username }
end
context 'without signature' do
let(:remote_account) { nil }

before do
get :show, params: { account_username: account.username, page: page }
end

context 'with page not requested' do
let(:page) { nil }

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 totalItems' do
json = body_as_json
expect(json[:totalItems]).to eq 4
end

it 'returns http success' do
expect(response).to have_http_status(200)
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
end

context 'with page requested' do
let(:page) { '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 '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
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
end
end

it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
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' 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 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
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
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' 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 private statuses' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 3
expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:to].include?(account_followers_url(account, ActionMailer::Base.default_url_options)) }).to be true
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
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' 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 empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
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' 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 empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
end
end
end

+ 196
- 0
spec/controllers/activitypub/replies_controller_spec.rb View File

@@ -0,0 +1,196 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe ActivityPub::RepliesController, type: :controller do
let(:status) { Fabricate(:status, visibility: parent_visibility) }
let(:remote_account) { nil }

before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)

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)
end

describe 'GET #index' do
context 'with no signature' do
before do
get :index, params: { account_username: status.account.username, status_id: status.id }
end

context 'when status is public' do
let(:parent_visibility) { :public }

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 public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

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
end
end

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

context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:only_other_accounts) { nil }

context do
before do
get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts }
end

context 'when status is public' do
let(:parent_visibility) { :public }

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 public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

context 'without only_other_accounts' do
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
end
end

context 'with only_other_accounts' do
let(:only_other_accounts) { 'true' }

it 'returns items with other public or unlisted 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 2
expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
end
end

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

context 'when signed request account is blocked' do
before do
status.account.block!(remote_account)
get :index, params: { account_username: status.account.username, status_id: status.id }
end

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

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

context 'when signed request account is domain blocked' do
before do
status.account.block_domain!(remote_account.domain)
get :index, params: { account_username: status.account.username, status_id: status.id }
end

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

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
end
end
end

+ 768
- 75
spec/controllers/statuses_controller_spec.rb View File

@@ -5,128 +5,821 @@ require 'rails_helper'
describe StatusesController do
render_views

describe '#show' do
context 'account is suspended' do
it 'returns gone' do
account = Fabricate(:account, suspended: true)
status = Fabricate(:status, account: account)
describe 'GET #show' do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }

context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }

before do
get :show, params: { account_username: account.username, id: status.id }
end

it 'returns http gone' do
expect(response).to have_http_status(410)
end
end

context 'status is not permitted' do
it 'raises ActiveRecord::RecordNotFound' do
user = Fabricate(:user)
status = Fabricate(:status)
status.account.block!(user.account)
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') }
let(:status) { Fabricate(:status, account: account, reblog: original_status) }

sign_in(user)
before do
get :show, params: { account_username: status.account.username, id: status.id }
end

expect(response).to have_http_status(404)
it 'redirects to the original status' do
expect(response).to redirect_to(original_status.url)
end
end

context 'status is a reblog' do
it 'redirects to the original status' do
original_account = Fabricate(:account, domain: 'example.com')
original_status = Fabricate(:status, account: original_account, uri: 'tag:example.com,2017:foo', url: 'https://example.com/123')
status = Fabricate(:status, reblog: original_status)
context 'when status is public' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

get :show, params: { account_username: status.account.username, id: status.id }
context 'as HTML' do
let(:format) { 'html' }

expect(response).to redirect_to(original_status.url)
it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'account is not suspended and status is permitted' do
it 'assigns @account' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:account)).to eq status.account
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

it 'assigns @status' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:status)).to eq status
context 'as JSON' do
let(:format) { 'json' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

it 'assigns @ancestors for ancestors of the status if it is a reply' do
ancestor = Fabricate(:status)
status = Fabricate(:status, in_reply_to_id: ancestor.id)
context 'as HTML' do
let(:format) { 'html' }

get :show, params: { account_username: status.account.username, id: status.id }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end

context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }

expect(assigns(:ancestors)).to eq [ancestor]
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

it 'assigns @ancestors for [] if it is not a reply' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:ancestors)).to eq []
context 'as JSON' do
let(:format) { 'json' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

it 'assigns @descendant_threads for a thread with several statuses' do
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
grandchild = Fabricate(:status, in_reply_to_id: child.id)
context 'as HTML' do
let(:format) { 'html' }

get :show, params: { account_username: status.account.username, id: status.id }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end

context 'when signed-in' do
let(:user) { Fabricate(:user) }

expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id]
before do
sign_in(user)
end

it 'assigns @descendant_threads for several threads sharing the same descendant' do
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) }
context 'when account blocks user' do
before do
account.block!(user.account)
get :show, params: { account_username: status.account.username, id: status.id }
end

get :show, params: { account_username: status.account.username, id: status.id }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'when status is public' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id]
expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id]
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do
stub_const 'StatusControllerConcern::DESCENDANTS_LIMIT', 1
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }

get :show, params: { account_username: status.account.username, id: status.id }
context 'when user is authorized to see it' do
before do
user.account.follow!(account)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

expect(assigns(:descendant_threads)).to eq []
expect(assigns(:max_descendant_thread_id)).to eq child.id
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end

it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do
stub_const 'StatusControllerConcern::DESCENDANTS_DEPTH_LIMIT', 2
status = Fabricate(:status)
child0 = Fabricate(:status, in_reply_to_id: status.id)
child1 = Fabricate(:status, in_reply_to_id: child0.id)
child2 = Fabricate(:status, in_reply_to_id: child0.id)
context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }

get :show, params: { account_username: status.account.username, id: status.id }
context 'when user is authorized to see it' do
before do
Fabricate(:mention, account: user.account, status: status)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as JSON' do
let(:format) { 'json' }

expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child1.id
expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).not_to include child2.id
expect(assigns(:descendant_threads)[0][:next_status].id).to eq child1.id
expect(assigns(:descendant_threads)[1][:next_status].id).to eq child2.id
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
end

it 'returns a success' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }

before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end

context 'when account blocks account' do
before do
account.block!(remote_account)
get :show, params: { account_username: status.account.username, id: status.id }
end

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'when account domain blocks account' do
before do
account.block_domain!(remote_account.domain)
get :show, params: { account_username: status.account.username, id: status.id }
end

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'when status is public' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }

context 'when user is authorized to see it' do
before do
remote_account.follow!(account)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end

context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }

context 'when user is authorized to see it' do
before do
Fabricate(:mention, account: remote_account, status: status)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end

it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end

it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end

it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end

context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end

context 'as JSON' do
let(:format) { 'json' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'as HTML' do
let(:format) { 'html' }

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
end
end

describe 'GET #activity' do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }

context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }

before do
get :activity, params: { account_username: account.username, id: status.id }
end

it 'returns http gone' do
expect(response).to have_http_status(410)
end
end

context 'when status is public' do
pending
end

context 'when status is private' do
pending
end

context 'when status is direct' do
pending
end

context 'when signed-in' do
context 'when status is public' do
pending
end

context 'when status is private' do
context 'when user is authorized to see it' do
pending
end

context 'when user is not authorized to see it' do
pending
end
end

context 'when status is direct' do
context 'when user is authorized to see it' do
pending
end

context 'when user is not authorized to see it' do
pending
end
end
end

context 'with signature' do
context 'when status is public' do
pending
end

context 'when status is private' do
context 'when user is authorized to see it' do
pending
end

context 'when user is not authorized to see it' do
pending
end
end

context 'when status is direct' do
context 'when user is authorized to see it' do
pending
end

context 'when user is not authorized to see it' do
pending
end
end
end
end

describe 'GET #embed' do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }

context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }

before do
get :embed, params: { account_username: account.username, id: status.id }
end

it 'returns http gone' do
expect(response).to have_http_status(410)
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') }
let(:status) { Fabricate(:status, account: account, reblog: original_status) }

before do
get :embed, params: { account_username: status.account.username, id: status.id }
end

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'when status is public' do
before do
get :embed, params: { account_username: status.account.username, id: status.id }
end

it 'returns http success' do
expect(response).to have_http_status(200)
end

it 'renders statuses/show' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(response).to render_template 'statuses/show'
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end

it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end

it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end

it 'renders status' do
expect(response).to render_template(:embed)
expect(response.body).to include status.text
end
end

context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }

before do
get :embed, params: { account_username: status.account.username, id: status.id }
end

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end

context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }

before do
get :embed, params: { account_username: status.account.username, id: status.id }
end

it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end


+ 1
- 1
spec/fabricators/status_pin_fabricator.rb View File

@@ -1,4 +1,4 @@
Fabricator(:status_pin) do
account
status
status { |attrs| Fabricate(:status, account: attrs[:account], visibility: :public) }
end

Loading…
Cancel
Save