Merge tag 'v4.2.12' into chinwag-next

This commit is contained in:
Mike Barnes 2024-09-24 20:03:33 +10:00
commit 9bcb7630b3
3138 changed files with 94619 additions and 59187 deletions

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
context 'when visited anonymously' do
around do |example|
old = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
example.run
ActionController::Base.allow_forgery_protection = old
end
describe 'account pages' do
it 'do not set cookies' do
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
_status = Fabricate(:status, account: alice, text: 'Hello World')
get '/@alice'
expect(response.cookies).to be_empty
end
end
describe 'status pages' do
it 'do not set cookies' do
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
status = Fabricate(:status, account: alice, text: 'Hello World')
get short_account_status_url(alice, status)
expect(response.cookies).to be_empty
end
end
describe 'the /about page' do
it 'does not set cookies' do
get '/about'
expect(response.cookies).to be_empty
end
end
end

View file

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'credentials API' do
let(:user) { Fabricate(:user, account_attributes: { discoverable: false, locked: true, indexable: false }) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/accounts/verify_credentials' do
subject do
get '/api/v1/accounts/verify_credentials', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'write write:accounts'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected content' do
subject
expect(body_as_json).to include({
source: hash_including({
discoverable: false,
indexable: false,
}),
locked: true,
})
end
end
describe 'POST /api/v1/accounts/update_credentials' do
subject do
patch '/api/v1/accounts/update_credentials', headers: headers, params: params
end
let(:params) { { discoverable: true, locked: false, indexable: true } }
it_behaves_like 'forbidden for wrong scope', 'read read:accounts'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns JSON with updated attributes' do
subject
expect(body_as_json).to include({
source: hash_including({
discoverable: true,
indexable: true,
}),
locked: false,
})
end
end
end

View file

@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'GET /api/v1/accounts/{account_id}' do
it 'returns account entity as 200 OK' do
account = Fabricate(:account)
get "/api/v1/accounts/#{account.id}"
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 404 if account not found' do
get '/api/v1/accounts/1'
aggregate_failures do
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when with token' do
it 'returns account entity as 200 OK if token is valid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 403 if scope of token is invalid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(403)
expect(body_as_json[:error]).to eq('This action is outside the authorized scopes')
end
end
end
end

View file

@ -0,0 +1,154 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Account actions' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:write admin:write:accounts' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) }
before do
allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer)
end
shared_examples 'a successful notification delivery' do
it 'notifies the user about the action taken' do
subject
expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once
expect(mailer).to have_received(:deliver_later!).once
end
end
shared_examples 'a successful logged action' do |action_type, target_type|
it 'logs action' do
subject
log_item = Admin::ActionLog.last
expect(log_item).to be_present
expect(log_item.action).to eq(action_type)
expect(log_item.account_id).to eq(user.account_id)
expect(log_item.target_id).to eq(target_type == :user ? target_account.user.id : target_account.id)
end
end
describe 'POST /api/v1/admin/accounts/:id/action' do
subject do
post "/api/v1/admin/accounts/#{target_account.id}/action", headers: headers, params: params
end
let(:target_account) { Fabricate(:account) }
context 'with type of disable' do
let(:params) { { type: 'disable' } }
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :disable, :user
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'disables the target account' do
expect { subject }.to change { target_account.reload.user_disabled? }.from(false).to(true)
end
end
context 'with type of sensitive' do
let(:params) { { type: 'sensitive' } }
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :sensitive, :account
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'marks the target account as sensitive' do
expect { subject }.to change { target_account.reload.sensitized? }.from(false).to(true)
end
end
context 'with type of silence' do
let(:params) { { type: 'silence' } }
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :silence, :account
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'marks the target account as silenced' do
expect { subject }.to change { target_account.reload.silenced? }.from(false).to(true)
end
end
context 'with type of suspend' do
let(:params) { { type: 'suspend' } }
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :suspend, :account
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'marks the target account as suspended' do
expect { subject }.to change { target_account.reload.suspended? }.from(false).to(true)
end
end
context 'with type of none' do
let(:params) { { type: 'none' } }
it_behaves_like 'a successful notification delivery'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
context 'with no type' do
let(:params) { {} }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'with invalid type' do
let(:params) { { type: 'invalid' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
end

View file

@ -0,0 +1,285 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Canonical Email Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/canonical_email_blocks' do
subject do
get '/api/v1/admin/canonical_email_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no canonical email block' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are canonical email blocks' do
let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) }
let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) }
it 'returns the correct canonical email hashes' do
subject
expect(body_as_json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of canonical email blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
context 'with since_id param' do
let(:params) { { since_id: canonical_email_blocks[1].id } }
it 'returns only the canonical email blocks after since_id' do
subject
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..])
end
end
context 'with max_id param' do
let(:params) { { max_id: canonical_email_blocks[3].id } }
it 'returns only the canonical email blocks before max_id' do
subject
canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2])
end
end
end
end
describe 'GET /api/v1/admin/canonical_email_blocks/:id' do
subject do
get "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers
end
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'when the requested canonical email block exists' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the requested canonical email block data correctly' do
subject
json = body_as_json
expect(json[:id]).to eq(canonical_email_block.id.to_s)
expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when the requested canonical block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/canonical_email_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/canonical_email_blocks/test' do
subject do
post '/api/v1/admin/canonical_email_blocks/test', headers: headers, params: params
end
let(:params) { { email: 'email@example.com' } }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'when the required email param is not provided' do
let(:params) { {} }
it 'returns http bad request' do
subject
expect(response).to have_http_status(400)
end
end
context 'when the required email param is provided' do
context 'when there is a matching canonical email block' do
let!(:canonical_email_block) { CanonicalEmailBlock.create(params) }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected canonical email hash' do
subject
expect(body_as_json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when there is no matching canonical email block' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
end
end
describe 'POST /api/v1/admin/canonical_email_blocks' do
subject do
post '/api/v1/admin/canonical_email_blocks', headers: headers, params: params
end
let(:params) { { email: 'example@email.com' } }
let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the canonical_email_hash correctly' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
context 'when the required email param is not provided' do
let(:params) { {} }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the canonical_email_hash param is provided instead of email' do
let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct canonical_email_hash' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(params[:canonical_email_hash])
end
end
context 'when both email and canonical_email_hash params are provided' do
let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'ignores the canonical_email_hash param' do
subject
expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
end
end
context 'when the given canonical email was already blocked' do
before do
canonical_email_block.save
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/admin/canonical_email_blocks/:id' do
subject do
delete "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers
end
let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the canonical email block' do
subject
expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil
end
context 'when the canonical email block is not found' do
it 'returns http not found' do
delete '/api/v1/admin/canonical_email_blocks/0', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,194 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Domain Allows' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/domain_allows' do
subject do
get '/api/v1/admin/domain_allows', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no allowed domains' do
it 'returns an empty body' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are allowed domains' do
let!(:domain_allows) { Fabricate.times(5, :domain_allow) }
let(:expected_response) do
domain_allows.map do |domain_allow|
{
id: domain_allow.id.to_s,
domain: domain_allow.domain,
created_at: domain_allow.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
}
end
end
it 'returns the correct allowed domains' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of allowed domains' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/domain_allows/:id' do
subject do
get "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers
end
let!(:domain_allow) { Fabricate(:domain_allow) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected allowed domain name' do
subject
expect(body_as_json[:domain]).to eq domain_allow.domain
end
context 'when the requested allowed domain does not exist' do
it 'returns http not found' do
get '/api/v1/admin/domain_allows/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/domain_allows' do
subject do
post '/api/v1/admin/domain_allows', headers: headers, params: params
end
let(:params) { { domain: 'foo.bar.com' } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'with a valid domain name' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected domain name' do
subject
expect(body_as_json[:domain]).to eq 'foo.bar.com'
end
it 'creates a domain allow' do
subject
expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present
end
end
context 'with invalid domain name' do
let(:params) { 'foo bar' }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when domain name is not specified' do
let(:params) { {} }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the domain is already allowed' do
before do
DomainAllow.create(params)
end
it 'returns the existing allowed domain name' do
subject
expect(body_as_json[:domain]).to eq(params[:domain])
end
end
end
describe 'DELETE /api/v1/admin/domain_allows/:id' do
subject do
delete "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers
end
let!(:domain_allow) { Fabricate(:domain_allow) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the allowed domain' do
subject
expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil
end
context 'when the allowed domain does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/domain_allows/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,300 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Domain Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read:domain_blocks admin:write:domain_blocks' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/domain_blocks' do
subject do
get '/api/v1/admin/domain_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there are no domain blocks' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are domain blocks' do
let!(:domain_blocks) do
[
Fabricate(:domain_block, severity: :silence, reject_media: true),
Fabricate(:domain_block, severity: :suspend, obfuscate: true),
Fabricate(:domain_block, severity: :noop, reject_reports: true),
Fabricate(:domain_block, public_comment: 'Spam'),
Fabricate(:domain_block, private_comment: 'Spam'),
]
end
let(:expected_responde) do
domain_blocks.map do |domain_block|
{
id: domain_block.id.to_s,
domain: domain_block.domain,
digest: domain_block.domain_digest,
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
severity: domain_block.severity.to_s,
reject_media: domain_block.reject_media,
reject_reports: domain_block.reject_reports,
private_comment: domain_block.private_comment,
public_comment: domain_block.public_comment,
obfuscate: domain_block.obfuscate,
}
end
end
it 'returns the expected domain blocks' do
subject
expect(body_as_json).to match_array(expected_responde)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of domain blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/domain_blocks/:id' do
subject do
get "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers
end
let!(:domain_block) { Fabricate(:domain_block) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected domain block content' do
subject
expect(body_as_json).to eq(
{
id: domain_block.id.to_s,
domain: domain_block.domain,
digest: domain_block.domain_digest,
created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
severity: domain_block.severity.to_s,
reject_media: domain_block.reject_media,
reject_reports: domain_block.reject_reports,
private_comment: domain_block.private_comment,
public_comment: domain_block.public_comment,
obfuscate: domain_block.obfuscate,
}
)
end
context 'when the requested domain block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/domain_blocks' do
subject do
post '/api/v1/admin/domain_blocks', headers: headers, params: params
end
let(:params) { { domain: 'foo.bar.com', severity: :silence } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'creates a domain block with the expected domain name and severity', :aggregate_failures do
subject
expect(response).to have_http_status(200)
body = body_as_json
expect(body).to match a_hash_including(
{
domain: 'foo.bar.com',
severity: 'silence',
}
)
end
it 'creates a domain block' do
subject
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
end
context 'when a looser domain block already exists on a higher level domain' do
let(:params) { { domain: 'foo.bar.com', severity: :suspend } }
before do
Fabricate(:domain_block, domain: 'bar.com', severity: :silence)
end
it 'creates a domain block with the expected domain name and severity', :aggregate_failures do
subject
body = body_as_json
expect(response).to have_http_status(200)
expect(body).to match a_hash_including(
{
domain: 'foo.bar.com',
severity: 'suspend',
}
)
expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present
end
end
context 'when a domain block already exists on the same domain' do
before do
Fabricate(:domain_block, domain: 'foo.bar.com', severity: :silence)
end
it 'returns existing domain block in error', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(body_as_json[:existing_domain_block][:domain]).to eq('foo.bar.com')
end
end
context 'when a stricter domain block already exists on a higher level domain' do
before do
Fabricate(:domain_block, domain: 'bar.com', severity: :suspend)
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
it 'returns existing domain block in error' do
subject
expect(body_as_json[:existing_domain_block][:domain]).to eq('bar.com')
end
end
context 'when given domain name is invalid' do
let(:params) { { domain: 'foo bar', severity: :silence } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'PUT /api/v1/admin/domain_blocks/:id' do
subject do
put "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers, params: params
end
let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: :silence) }
let(:params) { { domain: 'example.com', severity: 'suspend' } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the updated domain block' do
subject
expect(body_as_json).to match a_hash_including(
{
id: domain_block.id.to_s,
domain: domain_block.domain,
digest: domain_block.domain_digest,
severity: 'suspend',
}
)
end
it 'updates the block severity' do
expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend')
end
context 'when domain block does not exist' do
it 'returns http not found' do
put '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /api/v1/admin/domain_blocks/:id' do
subject do
delete "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers
end
let!(:domain_block) { Fabricate(:domain_block) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the domain block' do
subject
expect(DomainBlock.find_by(id: domain_block.id)).to be_nil
end
context 'when domain block does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,211 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Email Domain Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:account) { Fabricate(:account) }
let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/email_domain_blocks' do
subject do
get '/api/v1/admin/email_domain_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no email domain block' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are email domain blocks' do
let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) }
let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) }
it 'return the correct blocked email domains' do
subject
expect(body_as_json.pluck(:domain)).to match_array(blocked_email_domains)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of email domain blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
context 'with since_id param' do
let(:params) { { since_id: email_domain_blocks[1].id } }
it 'returns only the email domain blocks after since_id' do
subject
email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[2..])
end
end
context 'with max_id param' do
let(:params) { { max_id: email_domain_blocks[3].id } }
it 'returns only the email domain blocks before max_id' do
subject
email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[..2])
end
end
end
end
describe 'GET /api/v1/admin/email_domain_blocks/:id' do
subject do
get "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers
end
let!(:email_domain_block) { Fabricate(:email_domain_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
context 'when email domain block exists' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct blocked domain' do
subject
expect(body_as_json[:domain]).to eq(email_domain_block.domain)
end
end
context 'when email domain block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/email_domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/email_domain_blocks' do
subject do
post '/api/v1/admin/email_domain_blocks', headers: headers, params: params
end
let(:params) { { domain: 'example.com' } }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct blocked email domain' do
subject
expect(body_as_json[:domain]).to eq(params[:domain])
end
context 'when domain param is not provided' do
let(:params) { { domain: '' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when provided domain name has an invalid character' do
let(:params) { { domain: 'do\uD800.com' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when provided domain is already blocked' do
before do
EmailDomainBlock.create(params)
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/admin/email_domain_blocks' do
subject do
delete "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers
end
let!(:email_domain_block) { Fabricate(:email_domain_block) }
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns an empty body' do
subject
expect(body_as_json).to be_empty
end
it 'deletes email domain block' do
subject
expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil
end
context 'when email domain block does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/email_domain_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,255 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'IP Blocks' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/ip_blocks' do
subject do
get '/api/v1/admin/ip_blocks', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there is no ip block' do
it 'returns an empty body' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are ip blocks' do
let!(:ip_blocks) do
[
IpBlock.create(ip: '192.0.2.0/24', severity: :no_access),
IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'),
IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days),
]
end
let(:expected_response) do
ip_blocks.map do |ip_block|
{
id: ip_block.id.to_s,
ip: ip_block.ip,
severity: ip_block.severity.to_s,
comment: ip_block.comment,
created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
}
end
end
it 'returns the correct blocked ips' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of ip blocks' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/ip_blocks/:id' do
subject do
get "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers
end
let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) }
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
subject
json = body_as_json
expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}")
expect(json[:severity]).to eq(ip_block.severity.to_s)
end
context 'when ip block does not exist' do
it 'returns http not found' do
get '/api/v1/admin/ip_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/admin/ip_blocks' do
subject do
post '/api/v1/admin/ip_blocks', headers: headers, params: params
end
let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } }
it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
subject
json = body_as_json
expect(json[:ip]).to eq("#{params[:ip]}/32")
expect(json[:severity]).to eq(params[:severity])
expect(json[:comment]).to eq(params[:comment])
end
context 'when the required ip param is not provided' do
let(:params) { { ip: '', severity: 'no_access' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the required severity param is not provided' do
let(:params) { { ip: '173.65.23.1', severity: '' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the given ip address is already blocked' do
before do
IpBlock.create(params)
end
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the given ip address is invalid' do
let(:params) { { ip: '520.13.54.120', severity: 'no_access' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'PUT /api/v1/admin/ip_blocks/:id' do
subject do
put "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers, params: params
end
let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) }
let(:params) { { severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the correct ip block' do
subject
expect(body_as_json).to match(hash_including({
ip: "#{ip_block.ip}/#{ip_block.ip.prefix}",
severity: 'sign_up_requires_approval',
comment: 'Decreasing severity',
}))
end
it 'updates the severity correctly' do
expect { subject }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval')
end
it 'updates the comment correctly' do
expect { subject }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity')
end
context 'when ip block does not exist' do
it 'returns http not found' do
put '/api/v1/admin/ip_blocks/-1', headers: headers, params: params
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /api/v1/admin/ip_blocks/:id' do
subject do
delete "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers
end
let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns an empty body' do
subject
expect(body_as_json).to be_empty
end
it 'deletes the ip block' do
subject
expect(IpBlock.find_by(id: ip_block.id)).to be_nil
end
context 'when ip block does not exist' do
it 'returns http not found' do
delete '/api/v1/admin/ip_blocks/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,272 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Reports' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read:reports admin:write:reports' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/reports' do
subject do
get '/api/v1/admin/reports', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there are no reports' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are reports' do
let!(:reporter) { Fabricate(:account) }
let!(:spammer) { Fabricate(:account) }
let(:expected_response) do
scope.map do |report|
hash_including({
id: report.id.to_s,
action_taken: report.action_taken?,
category: report.category,
comment: report.comment,
account: hash_including(id: report.account.id.to_s),
target_account: hash_including(id: report.target_account.id.to_s),
statuses: report.statuses,
rules: report.rules,
forwarded: report.forwarded,
})
end
end
let(:scope) { Report.unresolved }
before do
Fabricate(:report)
Fabricate(:report, target_account: spammer)
Fabricate(:report, account: reporter, target_account: spammer)
Fabricate(:report, action_taken_at: 4.days.ago, account: reporter)
Fabricate(:report, action_taken_at: 20.days.ago)
end
it 'returns all unresolved reports' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with resolved param' do
let(:params) { { resolved: true } }
let(:scope) { Report.resolved }
it 'returns only the resolved reports' do
subject
expect(body_as_json).to match_array(expected_response)
end
end
context 'with account_id param' do
let(:params) { { account_id: reporter.id } }
let(:scope) { Report.unresolved.where(account: reporter) }
it 'returns all unresolved reports filed by the specified account' do
subject
expect(body_as_json).to match_array(expected_response)
end
end
context 'with target_account_id param' do
let(:params) { { target_account_id: spammer.id } }
let(:scope) { Report.unresolved.where(target_account: spammer) }
it 'returns all unresolved reports targeting the specified account' do
subject
expect(body_as_json).to match_array(expected_response)
end
end
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of reports' do
subject
expect(body_as_json.size).to eq(1)
end
end
end
end
describe 'GET /api/v1/admin/reports/:id' do
subject do
get "/api/v1/admin/reports/#{report.id}", headers: headers
end
let(:report) { Fabricate(:report) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the requested report content' do
subject
expect(body_as_json).to include(
{
id: report.id.to_s,
action_taken: report.action_taken?,
category: report.category,
comment: report.comment,
account: a_hash_including(id: report.account.id.to_s),
target_account: a_hash_including(id: report.target_account.id.to_s),
statuses: report.statuses,
rules: report.rules,
forwarded: report.forwarded,
}
)
end
end
describe 'PUT /api/v1/admin/reports/:id' do
subject do
put "/api/v1/admin/reports/#{report.id}", headers: headers, params: params
end
let!(:report) { Fabricate(:report, category: :other) }
let(:params) { { category: 'spam' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'updates the report category' do
expect { subject }.to change { report.reload.category }.from('other').to('spam')
end
it 'returns the updated report content' do
subject
report.reload
expect(body_as_json).to include(
{
id: report.id.to_s,
action_taken: report.action_taken?,
category: report.category,
comment: report.comment,
account: a_hash_including(id: report.account.id.to_s),
target_account: a_hash_including(id: report.target_account.id.to_s),
statuses: report.statuses,
rules: report.rules,
forwarded: report.forwarded,
}
)
end
end
describe 'POST #resolve' do
subject do
post "/api/v1/admin/reports/#{report.id}/resolve", headers: headers
end
let(:report) { Fabricate(:report, action_taken_at: nil) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'marks report as resolved' do
expect { subject }.to change { report.reload.unresolved? }.from(true).to(false)
end
end
describe 'POST #reopen' do
subject do
post "/api/v1/admin/reports/#{report.id}/reopen", headers: headers
end
let(:report) { Fabricate(:report, action_taken_at: 10.days.ago) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'marks report as unresolved' do
expect { subject }.to change { report.reload.unresolved? }.from(false).to(true)
end
end
describe 'POST #assign_to_self' do
subject do
post "/api/v1/admin/reports/#{report.id}/assign_to_self", headers: headers
end
let(:report) { Fabricate(:report) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'assigns report to the requesting user' do
expect { subject }.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id)
end
end
describe 'POST #unassign' do
subject do
post "/api/v1/admin/reports/#{report.id}/unassign", headers: headers
end
let(:report) { Fabricate(:report, assigned_account_id: user.account.id) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'unassigns report from assignee' do
expect { subject }.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil)
end
end
end

View file

@ -0,0 +1,141 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Tags' do
let(:role) { UserRole.find_by(name: 'Admin') }
let(:user) { Fabricate(:user, role: role) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:tag) { Fabricate(:tag) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/admin/tags' do
subject do
get '/api/v1/admin/tags', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
context 'when there are no tags' do
it 'returns an empty list' do
subject
expect(body_as_json).to be_empty
end
end
context 'when there are tagss' do
let!(:tags) do
[
Fabricate(:tag),
Fabricate(:tag),
Fabricate(:tag),
Fabricate(:tag),
]
end
it 'returns the expected tags' do
subject
tags.each do |tag|
expect(body_as_json.find { |item| item[:id] == tag.id.to_s && item[:name] == tag.name }).to_not be_nil
end
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of tags' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
end
describe 'GET /api/v1/admin/tags/:id' do
subject do
get "/api/v1/admin/tags/#{tag.id}", headers: headers
end
let!(:tag) { Fabricate(:tag) }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns expected tag content' do
subject
expect(body_as_json[:id].to_i).to eq(tag.id)
expect(body_as_json[:name]).to eq(tag.name)
end
context 'when the requested tag does not exist' do
it 'returns http not found' do
get '/api/v1/admin/tags/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'PUT /api/v1/admin/tags/:id' do
subject do
put "/api/v1/admin/tags/#{tag.id}", headers: headers, params: params
end
let!(:tag) { Fabricate(:tag) }
let(:params) { { display_name: tag.name.upcase } }
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong scope', 'admin:read'
it_behaves_like 'forbidden for wrong role', ''
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns updated tag' do
subject
expect(body_as_json[:id].to_i).to eq(tag.id)
expect(body_as_json[:name]).to eq(tag.name.upcase)
end
context 'when the updated display name is invalid' do
let(:params) { { display_name: tag.name + tag.id.to_s } }
it 'returns http unprocessable content' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the requested tag does not exist' do
it 'returns http not found' do
get '/api/v1/admin/tags/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Credentials' do
describe 'GET /api/v1/apps/verify_credentials' do
subject do
get '/api/v1/apps/verify_credentials', headers: headers
end
context 'with an oauth token' do
let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the app information correctly' do
subject
expect(body_as_json).to match(
a_hash_including(
name: token.application.name,
website: token.application.website,
vapid_key: Rails.configuration.x.vapid_public_key
)
)
end
end
context 'without an oauth token' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,115 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Apps' do
describe 'POST /api/v1/apps' do
subject do
post '/api/v1/apps', params: params
end
let(:client_name) { 'Test app' }
let(:scopes) { nil }
let(:redirect_uris) { 'urn:ietf:wg:oauth:2.0:oob' }
let(:website) { nil }
let(:params) do
{
client_name: client_name,
redirect_uris: redirect_uris,
scopes: scopes,
website: website,
}
end
context 'with valid params' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'creates an OAuth app' do
subject
expect(Doorkeeper::Application.find_by(name: client_name)).to be_present
end
it 'returns client ID and client secret' do
subject
body = body_as_json
expect(body[:client_id]).to be_present
expect(body[:client_secret]).to be_present
end
end
context 'with an unsupported scope' do
let(:scopes) { 'hoge' }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'with many duplicate scopes' do
let(:scopes) { (%w(read) * 40).join(' ') }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'only saves the scope once' do
subject
expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read'
end
end
context 'with a too-long name' do
let(:client_name) { 'hoge' * 20 }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'with a too-long website' do
let(:website) { "https://foo.bar/#{'hoge' * 2_000}" }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'with a too-long redirect_uris' do
let(:redirect_uris) { "https://foo.bar/#{'hoge' * 2_000}" }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'without required params' do
let(:client_name) { '' }
let(:redirect_uris) { '' }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
end

View file

@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Bookmarks' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:bookmarks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/bookmarks' do
subject do
get '/api/v1/bookmarks', headers: headers, params: params
end
let(:params) { {} }
let!(:bookmarks) { Fabricate.times(3, :bookmark, account: user.account) }
let(:expected_response) do
bookmarks.map do |bookmark|
a_hash_including(id: bookmark.status.id.to_s, account: a_hash_including(id: bookmark.status.account.id.to_s))
end
end
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the bookmarked statuses' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'paginates correctly', :aggregate_failures do
subject
expect(body_as_json.size).to eq(params[:limit])
expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id))
expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks[1].id))
end
end
context 'without the authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,125 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Domain blocks' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:blocks write:blocks' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/domain_blocks' do
subject do
get '/api/v1/domain_blocks', headers: headers, params: params
end
let(:blocked_domains) { ['example.com', 'example.net', 'example.org', 'example.com.br'] }
let(:params) { {} }
before do
blocked_domains.each { |domain| user.account.block_domain!(domain) }
end
it_behaves_like 'forbidden for wrong scope', 'write:blocks'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the domains blocked by the requesting user' do
subject
expect(body_as_json).to match_array(blocked_domains)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of blocked domains' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
describe 'POST /api/v1/domain_blocks' do
subject do
post '/api/v1/domain_blocks', headers: headers, params: params
end
let(:params) { { domain: 'example.com' } }
it_behaves_like 'forbidden for wrong scope', 'read read:blocks'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'creates a domain block' do
subject
expect(user.account.domain_blocking?(params[:domain])).to be(true)
end
context 'when no domain name is given' do
let(:params) { { domain: '' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the given domain name is invalid' do
let(:params) { { domain: 'example com' } }
it 'returns unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/domain_blocks' do
subject do
delete '/api/v1/domain_blocks/', headers: headers, params: params
end
let(:params) { { domain: 'example.com' } }
before do
user.account.block_domain!('example.com')
end
it_behaves_like 'forbidden for wrong scope', 'read read:blocks'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the specified domain block' do
subject
expect(user.account.domain_blocking?('example.com')).to be(false)
end
context 'when the given domain name is not blocked' do
let(:params) { { domain: 'example.org' } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
end
end

View file

@ -0,0 +1,168 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Confirmations' do
let(:confirmed_at) { nil }
let(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'POST /api/v1/emails/confirmations' do
subject do
post '/api/v1/emails/confirmations', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read read:accounts'
context 'with an oauth token' do
context 'when user was created by a different application' do
let(:user) { Fabricate(:user, confirmed_at: confirmed_at, created_by_application: Fabricate(:application)) }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
context 'when user was created by the same application' do
before do
user.update(created_by_application: token.application)
end
context 'when the account is already confirmed' do
let(:confirmed_at) { Time.now.utc }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
context 'when user changed e-mail and has not confirmed it' do
before do
user.update(email: 'foo@bar.com')
end
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
end
context 'when the account is unconfirmed' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
context 'with email param' do
let(:params) { { email: 'foo@bar.com' } }
it "updates the user's e-mail address", :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.reload.unconfirmed_email).to eq('foo@bar.com')
end
end
context 'with invalid email param' do
let(:params) { { email: 'invalid' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
end
context 'without an oauth token' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'GET /api/v1/emails/check_confirmation' do
subject do
get '/api/v1/emails/check_confirmation', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'write'
context 'with an oauth token' do
context 'when the account is not confirmed' do
it 'returns the confirmation status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to be false
end
end
context 'when the account is confirmed' do
let(:confirmed_at) { Time.now.utc }
it 'returns the confirmation status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to be true
end
end
end
context 'with an authentication cookie' do
let(:headers) { {} }
before do
sign_in user, scope: :user
end
context 'when the account is not confirmed' do
it 'returns the confirmation status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to be false
end
end
context 'when the account is confirmed' do
let(:confirmed_at) { Time.now.utc }
it 'returns the confirmation status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to be true
end
end
end
context 'without an oauth token and an authentication cookie' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,193 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'FeaturedTags' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:accounts write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/featured_tags' do
context 'with wrong scope' do
before do
get '/api/v1/featured_tags', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
get '/api/v1/featured_tags'
expect(response).to have_http_status(401)
end
end
it 'returns http success' do
get '/api/v1/featured_tags', headers: headers
expect(response).to have_http_status(200)
end
context 'when the requesting user has no featured tag' do
before { Fabricate.times(3, :featured_tag) }
it 'returns an empty body' do
get '/api/v1/featured_tags', headers: headers
body = body_as_json
expect(body).to be_empty
end
end
context 'when the requesting user has featured tags' do
let!(:user_featured_tags) { Fabricate.times(5, :featured_tag, account: user.account) }
it 'returns only the featured tags belonging to the requesting user' do
get '/api/v1/featured_tags', headers: headers
body = body_as_json
expected_ids = user_featured_tags.pluck(:id).map(&:to_s)
expect(body.pluck(:id)).to match_array(expected_ids)
end
end
end
describe 'POST /api/v1/featured_tags' do
let(:params) { { name: 'tag' } }
it 'returns http success' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(200)
end
it 'returns the correct tag name' do
post '/api/v1/featured_tags', headers: headers, params: params
body = body_as_json
expect(body[:name]).to eq(params[:name])
end
it 'creates a new featured tag for the requesting user' do
post '/api/v1/featured_tags', headers: headers, params: params
featured_tag = FeaturedTag.find_by(name: params[:name], account: user.account)
expect(featured_tag).to be_present
end
context 'with wrong scope' do
before do
post '/api/v1/featured_tags', headers: headers, params: params
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
post '/api/v1/featured_tags', params: params
expect(response).to have_http_status(401)
end
end
context 'when required param "name" is not provided' do
it 'returns http bad request' do
post '/api/v1/featured_tags', headers: headers
expect(response).to have_http_status(400)
end
end
context 'when provided tag name is invalid' do
let(:params) { { name: 'asj&*!' } }
it 'returns http unprocessable entity' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(422)
end
end
context 'when tag name is already taken' do
before do
FeaturedTag.create(name: params[:name], account: user.account)
end
it 'returns http unprocessable entity' do
post '/api/v1/featured_tags', headers: headers, params: params
expect(response).to have_http_status(422)
end
end
end
describe 'DELETE /api/v1/featured_tags' do
let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) }
let(:id) { featured_tag.id }
it 'returns http success' do
delete "/api/v1/featured_tags/#{id}", headers: headers
expect(response).to have_http_status(200)
end
it 'returns an empty body' do
delete "/api/v1/featured_tags/#{id}", headers: headers
body = body_as_json
expect(body).to be_empty
end
it 'deletes the featured tag' do
delete "/api/v1/featured_tags/#{id}", headers: headers
featured_tag = FeaturedTag.find_by(id: id)
expect(featured_tag).to be_nil
end
context 'with wrong scope' do
before do
delete "/api/v1/featured_tags/#{id}", headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
end
context 'when Authorization header is missing' do
it 'returns http unauthorized' do
delete "/api/v1/featured_tags/#{id}"
expect(response).to have_http_status(401)
end
end
context 'when featured tag with given id does not exist' do
it 'returns http not found' do
delete '/api/v1/featured_tags/0', headers: headers
expect(response).to have_http_status(404)
end
end
context 'when deleting a featured tag of another user' do
let!(:other_user_featured_tag) { Fabricate(:featured_tag) }
let(:id) { other_user_featured_tag.id }
it 'returns http not found' do
delete "/api/v1/featured_tags/#{id}", headers: headers
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,119 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Follow requests' do
let(:user) { Fabricate(:user, account_attributes: { locked: true }) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:follows write:follows' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/follow_requests' do
subject do
get '/api/v1/follow_requests', headers: headers, params: params
end
let(:accounts) { Fabricate.times(5, :account) }
let(:params) { {} }
let(:expected_response) do
accounts.map do |account|
a_hash_including(
id: account.id.to_s,
username: account.username,
acct: account.acct
)
end
end
before do
accounts.each { |account| FollowService.new.call(account, user.account) }
end
it_behaves_like 'forbidden for wrong scope', 'write write:follows'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected content from accounts requesting to follow' do
subject
expect(body_as_json).to match_array(expected_response)
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of follow requests' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
end
end
describe 'POST /api/v1/follow_requests/:account_id/authorize' do
subject do
post "/api/v1/follow_requests/#{follower.id}/authorize", headers: headers
end
let(:follower) { Fabricate(:account) }
before do
FollowService.new.call(follower, user.account)
end
it_behaves_like 'forbidden for wrong scope', 'read read:follows'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'allows the requesting follower to follow' do
expect { subject }.to change { follower.following?(user.account) }.from(false).to(true)
end
it 'returns JSON with followed_by set to true' do
subject
expect(body_as_json[:followed_by]).to be true
end
end
describe 'POST /api/v1/follow_requests/:account_id/reject' do
subject do
post "/api/v1/follow_requests/#{follower.id}/reject", headers: headers
end
let(:follower) { Fabricate(:account) }
before do
FollowService.new.call(follower, user.account)
end
it_behaves_like 'forbidden for wrong scope', 'read read:follows'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'removes the follow request' do
subject
expect(FollowRequest.where(target_account: user.account, account: follower)).to_not exist
end
it 'returns JSON with followed_by set to false' do
subject
expect(body_as_json[:followed_by]).to be false
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Languages' do
describe 'GET /api/v1/instance/languages' do
before do
get '/api/v1/instance/languages'
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns the supported languages' do
expect(body_as_json.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s)
end
end
end

View file

@ -0,0 +1,247 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Lists' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read:lists write:lists' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/lists' do
subject do
get '/api/v1/lists', headers: headers
end
let!(:lists) do
[
Fabricate(:list, account: user.account, title: 'first list', replies_policy: :followed),
Fabricate(:list, account: user.account, title: 'second list', replies_policy: :list),
Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none),
Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true),
]
end
let(:expected_response) do
lists.map do |list|
{
id: list.id.to_s,
title: list.title,
replies_policy: list.replies_policy,
exclusive: list.exclusive,
}
end
end
before do
Fabricate(:list)
end
it_behaves_like 'forbidden for wrong scope', 'write write:lists'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the expected lists' do
subject
expect(body_as_json).to match_array(expected_response)
end
end
describe 'GET /api/v1/lists/:id' do
subject do
get "/api/v1/lists/#{list.id}", headers: headers
end
let(:list) { Fabricate(:list, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:lists'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the requested list correctly' do
subject
expect(body_as_json).to eq({
id: list.id.to_s,
title: list.title,
replies_policy: list.replies_policy,
exclusive: list.exclusive,
})
end
context 'when the list belongs to a different user' do
let(:list) { Fabricate(:list) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when the list does not exist' do
it 'returns http not found' do
get '/api/v1/lists/-1', headers: headers
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/lists' do
subject do
post '/api/v1/lists', headers: headers, params: params
end
let(:params) { { title: 'my list', replies_policy: 'none', exclusive: 'true' } }
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the new list' do
subject
expect(body_as_json).to match(a_hash_including(title: 'my list', replies_policy: 'none', exclusive: true))
end
it 'creates a list' do
subject
expect(List.where(account: user.account).count).to eq(1)
end
context 'when a title is not given' do
let(:params) { { title: '' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the given replies_policy is invalid' do
let(:params) { { title: 'a list', replies_policy: 'whatever' } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'PUT /api/v1/lists/:id' do
subject do
put "/api/v1/lists/#{list.id}", headers: headers, params: params
end
let(:list) { Fabricate(:list, account: user.account, title: 'my list') }
let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true' } }
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the updated list' do
subject
list.reload
expect(body_as_json).to eq({
id: list.id.to_s,
title: list.title,
replies_policy: list.replies_policy,
exclusive: list.exclusive,
})
end
it 'updates the list title' do
expect { subject }.to change { list.reload.title }.from('my list').to('list')
end
it 'updates the list replies_policy' do
expect { subject }.to change { list.reload.replies_policy }.from('list').to('followed')
end
it 'updates the list exclusive' do
expect { subject }.to change { list.reload.exclusive }.from(false).to(true)
end
context 'when the list does not exist' do
it 'returns http not found' do
put '/api/v1/lists/-1', headers: headers, params: params
expect(response).to have_http_status(404)
end
end
context 'when the list belongs to another user' do
let(:list) { Fabricate(:list) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /api/v1/lists/:id' do
subject do
delete "/api/v1/lists/#{list.id}", headers: headers
end
let(:list) { Fabricate(:list, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'read read:lists'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'deletes the list' do
subject
expect(List.where(id: list.id)).to_not exist
end
context 'when the list does not exist' do
it 'returns http not found' do
delete '/api/v1/lists/-1', headers: headers
expect(response).to have_http_status(404)
end
end
context 'when the list belongs to another user' do
let(:list) { Fabricate(:list) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,90 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Mutes' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'read:mutes' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/mutes' do
subject do
get '/api/v1/mutes', headers: headers, params: params
end
let!(:mutes) { Fabricate.times(3, :mute, account: user.account) }
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write write:mutes'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the muted accounts' do
subject
muted_accounts = mutes.map(&:target_account)
expect(body_as_json.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s })
end
context 'with limit param' do
let(:params) { { limit: 2 } }
it 'returns only the requested number of muted accounts' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_mutes_url(limit: params[:limit], since_id: mutes[2].id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_mutes_url(limit: params[:limit], max_id: mutes[1].id.to_s))
end
end
context 'with max_id param' do
let(:params) { { max_id: mutes[1].id } }
it 'queries mutes in range according to max_id', :aggregate_failures do
subject
body = body_as_json
expect(body.size).to eq 1
expect(body[0][:id]).to eq mutes[0].target_account_id.to_s
end
end
context 'with since_id param' do
let(:params) { { since_id: mutes[0].id } }
it 'queries mutes in range according to since_id', :aggregate_failures do
subject
body = body_as_json
expect(body.size).to eq 2
expect(body[0][:id]).to eq mutes[2].target_account_id.to_s
end
end
context 'without an authentication header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Polls' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'read:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/polls/:id' do
subject do
get "/api/v1/polls/#{poll.id}", headers: headers
end
let(:poll) { Fabricate(:poll, status: Fabricate(:status, visibility: visibility)) }
let(:visibility) { 'public' }
it_behaves_like 'forbidden for wrong scope', 'write write:statuses'
context 'when parent status is public' do
it 'returns the poll data successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json).to match(
a_hash_including(
id: poll.id.to_s,
voted: false,
voters_count: poll.voters_count,
votes_count: poll.votes_count
)
)
end
end
context 'when parent status is private' do
let(:visibility) { 'private' }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Deleting profile images' do
let(:account) do
Fabricate(
:account,
avatar: fixture_file_upload('avatar.gif', 'image/gif'),
header: fixture_file_upload('attachment.jpg', 'image/jpeg')
)
end
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: account.user.id, scopes: scopes) }
let(:scopes) { 'write:accounts' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'DELETE /api/v1/profile' do
before do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
end
context 'when deleting an avatar' do
context 'with wrong scope' do
before do
delete '/api/v1/profile/avatar', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read'
end
it 'returns http success' do
delete '/api/v1/profile/avatar', headers: headers
expect(response).to have_http_status(200)
end
it 'deletes the avatar' do
delete '/api/v1/profile/avatar', headers: headers
account.reload
expect(account.avatar).to_not exist
end
it 'does not delete the header' do
delete '/api/v1/profile/avatar', headers: headers
account.reload
expect(account.header).to exist
end
it 'queues up an account update distribution' do
delete '/api/v1/profile/avatar', headers: headers
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
end
end
context 'when deleting a header' do
context 'with wrong scope' do
before do
delete '/api/v1/profile/header', headers: headers
end
it_behaves_like 'forbidden for wrong scope', 'read'
end
it 'returns http success' do
delete '/api/v1/profile/header', headers: headers
expect(response).to have_http_status(200)
end
it 'does not delete the avatar' do
delete '/api/v1/profile/header', headers: headers
account.reload
expect(account.avatar).to exist
end
it 'deletes the header' do
delete '/api/v1/profile/header', headers: headers
account.reload
expect(account.header).to_not exist
end
it 'queues up an account update distribution' do
delete '/api/v1/profile/header', headers: headers
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
end
end
end
end

View file

@ -0,0 +1,155 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Bookmarks' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:bookmarks' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'POST /api/v1/statuses/:status_id/bookmark' do
subject do
post "/api/v1/statuses/#{status.id}/bookmark", headers: headers
end
let(:status) { Fabricate(:status) }
it_behaves_like 'forbidden for wrong scope', 'read'
context 'with public status' do
it 'bookmarks the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be true
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, bookmarked: true)
)
end
end
context 'with private status of not-followed account' do
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'with private status of followed account' do
let(:status) { Fabricate(:status, visibility: :private) }
before do
user.account.follow!(status.account)
end
it 'bookmarks the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be true
end
end
context 'when the status does not exist' do
it 'returns http not found' do
post '/api/v1/statuses/-1/bookmark', headers: headers
expect(response).to have_http_status(404)
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'POST /api/v1/statuses/:status_id/unbookmark' do
subject do
post "/api/v1/statuses/#{status.id}/unbookmark", headers: headers
end
let(:status) { Fabricate(:status) }
it_behaves_like 'forbidden for wrong scope', 'read'
context 'with public status' do
context 'when the status was previously bookmarked' do
before do
Bookmark.find_or_create_by!(account: user.account, status: status)
end
it 'unbookmarks the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, bookmarked: false)
)
end
end
context 'when the requesting user was blocked by the status author' do
let(:status) { Fabricate(:status) }
before do
Bookmark.find_or_create_by!(account: user.account, status: status)
status.account.block!(user.account)
end
it 'unbookmarks the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.bookmarked?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, bookmarked: false)
)
end
end
context 'when the status is not bookmarked' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
end
context 'with private status that was not bookmarked' do
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,155 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Favourites' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:favourites' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'POST /api/v1/statuses/:status_id/favourite' do
subject do
post "/api/v1/statuses/#{status.id}/favourite", headers: headers
end
let(:status) { Fabricate(:status) }
it_behaves_like 'forbidden for wrong scope', 'read read:favourites'
context 'with public status' do
it 'favourites the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true)
)
end
end
context 'with private status of not-followed account' do
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'with private status of followed account' do
let(:status) { Fabricate(:status, visibility: :private) }
before do
user.account.follow!(status.account)
end
it 'favourites the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'POST /api/v1/statuses/:status_id/unfavourite' do
subject do
post "/api/v1/statuses/#{status.id}/unfavourite", headers: headers
end
let(:status) { Fabricate(:status) }
around do |example|
Sidekiq::Testing.fake! do
example.run
end
end
it_behaves_like 'forbidden for wrong scope', 'read read:favourites'
context 'with public status' do
before do
FavouriteService.new.call(user.account, status)
end
it 'unfavourites the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
UnfavouriteWorker.drain
expect(user.account.favourited?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false)
)
end
end
context 'when the requesting user was blocked by the status author' do
before do
FavouriteService.new.call(user.account, status)
status.account.block!(user.account)
end
it 'unfavourites the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.favourited?(status)).to be true
UnfavouriteWorker.drain
expect(user.account.favourited?(status)).to be false
end
it 'returns json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false)
)
end
end
context 'when status is not favourited' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
context 'with private status that was not favourited' do
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,131 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Pins' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:accounts' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'POST /api/v1/statuses/:status_id/pin' do
subject do
post "/api/v1/statuses/#{status.id}/pin", headers: headers
end
let(:status) { Fabricate(:status, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'read read:accounts'
context 'when the status is public' do
it 'pins the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.pinned?(status)).to be true
end
it 'return json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, pinned: true)
)
end
end
context 'when the status is private' do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) }
it 'pins the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.pinned?(status)).to be true
end
end
context 'when the status belongs to somebody else' do
let(:status) { Fabricate(:status) }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the status does not exist' do
it 'returns http not found' do
post '/api/v1/statuses/-1/pin', headers: headers
expect(response).to have_http_status(404)
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'POST /api/v1/statuses/:status_id/unpin' do
subject do
post "/api/v1/statuses/#{status.id}/unpin", headers: headers
end
let(:status) { Fabricate(:status, account: user.account) }
context 'when the status is pinned' do
before do
Fabricate(:status_pin, status: status, account: user.account)
end
it 'unpins the status successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(user.account.pinned?(status)).to be false
end
it 'return json with updated attributes' do
subject
expect(body_as_json).to match(
a_hash_including(id: status.id.to_s, pinned: false)
)
end
end
context 'when the status is not pinned' do
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
context 'when the status does not exist' do
it 'returns http not found' do
post '/api/v1/statuses/-1/unpin', headers: headers
expect(response).to have_http_status(404)
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Suggestions' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:scopes) { 'read' }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/suggestions' do
subject do
get '/api/v1/suggestions', headers: headers, params: params
end
let(:bob) { Fabricate(:account) }
let(:jeff) { Fabricate(:account) }
let(:params) { {} }
before do
PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog)
PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite)
end
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns accounts' do
subject
body = body_as_json
expect(body.size).to eq 2
expect(body.pluck(:id)).to match_array([bob, jeff].map { |i| i.id.to_s })
end
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of accounts' do
subject
expect(body_as_json.size).to eq 1
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'DELETE /api/v1/suggestions/:id' do
subject do
delete "/api/v1/suggestions/#{jeff.id}", headers: headers
end
let(:suggestions_source) { instance_double(AccountSuggestions::PastInteractionsSource, remove: nil) }
let(:bob) { Fabricate(:account) }
let(:jeff) { Fabricate(:account) }
before do
PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog)
PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite)
allow(AccountSuggestions::PastInteractionsSource).to receive(:new).and_return(suggestions_source)
end
it_behaves_like 'forbidden for wrong scope', 'write'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'removes the specified suggestion' do
subject
expect(suggestions_source).to have_received(:remove).with(user.account, jeff.id.to_s).once
expect(suggestions_source).to_not have_received(:remove).with(user.account, bob.id.to_s)
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,169 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Tags' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'write:follows' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/tags/:id' do
subject do
get "/api/v1/tags/#{name}"
end
context 'when the tag exists' do
let!(:tag) { Fabricate(:tag) }
let(:name) { tag.name }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the tag' do
subject
expect(body_as_json[:name]).to eq(name)
end
end
context 'when the tag does not exist' do
let(:name) { 'hoge' }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
end
context 'when the tag name is invalid' do
let(:name) { 'tag-name' }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
describe 'POST /api/v1/tags/:id/follow' do
subject do
post "/api/v1/tags/#{name}/follow", headers: headers
end
let!(:tag) { Fabricate(:tag) }
let(:name) { tag.name }
it_behaves_like 'forbidden for wrong scope', 'read read:follows'
context 'when the tag exists' do
it 'returns http success' do
subject
expect(response).to have_http_status(:success)
end
it 'creates follow' do
subject
expect(TagFollow.where(tag: tag, account: user.account)).to exist
end
end
context 'when the tag does not exist' do
let(:name) { 'hoge' }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'creates a new tag with the specified name' do
subject
expect(Tag.where(name: name)).to exist
end
it 'creates follow' do
subject
expect(TagFollow.where(tag: Tag.find_by(name: name), account: user.account)).to exist
end
end
context 'when the tag name is invalid' do
let(:name) { 'tag-name' }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when the Authorization header is missing' do
let(:headers) { {} }
let(:name) { 'unauthorized' }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
describe 'POST #unfollow' do
subject do
post "/api/v1/tags/#{name}/unfollow", headers: headers
end
let(:name) { tag.name }
let!(:tag) { Fabricate(:tag, name: 'foo') }
before do
Fabricate(:tag_follow, account: user.account, tag: tag)
end
it_behaves_like 'forbidden for wrong scope', 'read read:follows'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'removes the follow' do
subject
expect(TagFollow.where(tag: tag, account: user.account)).to_not exist
end
context 'when the tag name is invalid' do
let(:name) { 'tag-name' }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when the Authorization header is missing' do
let(:headers) { {} }
let(:name) { 'unauthorized' }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
end
end

View file

@ -0,0 +1,101 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Home' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'read:statuses' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/timelines/home' do
subject do
get '/api/v1/timelines/home', headers: headers, params: params
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'write write:statuses'
context 'when the timeline is available' do
let(:home_statuses) { bob.statuses + ana.statuses }
let!(:bob) { Fabricate(:account) }
let!(:tim) { Fabricate(:account) }
let!(:ana) { Fabricate(:account) }
before do
user.account.follow!(bob)
user.account.follow!(ana)
PostStatusService.new.call(bob, text: 'New toot from bob.')
PostStatusService.new.call(tim, text: 'New toot from tim.')
PostStatusService.new.call(ana, text: 'New toot from ana.')
end
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns the statuses of followed users' do
subject
expect(body_as_json.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s })
end
context 'with limit param' do
let(:params) { { limit: 1 } }
it 'returns only the requested number of statuses' do
subject
expect(body_as_json.size).to eq(params[:limit])
end
it 'sets the correct pagination headers', :aggregate_failures do
subject
headers = response.headers['Link']
expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_home_url(limit: 1, min_id: ana.statuses.first.id.to_s))
expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_home_url(limit: 1, max_id: ana.statuses.first.id.to_s))
end
end
end
context 'when the timeline is regenerating' do
let(:timeline) { instance_double(HomeFeed, regenerating?: true, get: []) }
before do
allow(HomeFeed).to receive(:new).and_return(timeline)
end
it 'returns http partial content' do
subject
expect(response).to have_http_status(206)
end
end
context 'without an authorization header' do
let(:headers) { {} }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
context 'without a user context' do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) }
it 'returns http unprocessable entity', :aggregate_failures do
subject
expect(response).to have_http_status(422)
expect(response.headers['Link']).to be_nil
end
end
end
end

View file

@ -8,16 +8,6 @@ describe 'Public' do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
subject
expect(response).to have_http_status(403)
end
end
shared_examples 'a successful request to the public timeline' do
it 'returns the expected statuses successfully', :aggregate_failures do
subject
@ -68,6 +58,13 @@ describe 'Public' do
it_behaves_like 'a successful request to the public timeline'
end
context 'with local and remote params' do
let(:params) { { local: true, remote: true } }
let(:expected_statuses) { [local_status, remote_status, media_status] }
it_behaves_like 'a successful request to the public timeline'
end
context 'with only_media param' do
let(:params) { { only_media: true } }
let(:expected_statuses) { [media_status] }

View file

@ -0,0 +1,248 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Filters' do
let(:user) { Fabricate(:user) }
let(:scopes) { 'read:filters write:filters' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
shared_examples 'unauthorized for invalid token' do
let(:headers) { { 'Authorization' => '' } }
it 'returns http unauthorized' do
subject
expect(response).to have_http_status(401)
end
end
describe 'GET /api/v2/filters' do
subject do
get '/api/v2/filters', headers: headers
end
let!(:filters) { Fabricate.times(3, :custom_filter, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
it_behaves_like 'unauthorized for invalid token'
it 'returns the existing filters successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s })
end
end
describe 'POST /api/v2/filters' do
subject do
post '/api/v2/filters', params: params, headers: headers
end
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
it_behaves_like 'unauthorized for invalid token'
context 'with valid params' do
let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'returns a filter with keywords', :aggregate_failures do
subject
json = body_as_json
expect(json[:title]).to eq 'magic'
expect(json[:filter_action]).to eq 'hide'
expect(json[:context]).to eq ['home']
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
end
it 'creates a filter', :aggregate_failures do
subject
filter = user.account.custom_filters.first
expect(filter).to be_present
expect(filter.keywords.pluck(:keyword)).to eq ['magic']
expect(filter.context).to eq %w(home)
expect(filter.irreversible?).to be true
expect(filter.expires_at).to be_nil
end
end
context 'when the required title param is missing' do
let(:params) { { context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the required context param is missing' do
let(:params) { { title: 'magic', filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
context 'when the given context value is invalid' do
let(:params) { { title: 'magic', context: %w(shaolin), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
describe 'GET /api/v2/filters/:id' do
subject do
get "/api/v2/filters/#{filter.id}", headers: headers
end
let(:filter) { Fabricate(:custom_filter, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
it_behaves_like 'unauthorized for invalid token'
it 'returns the filter successfully', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(filter.id.to_s)
end
context 'when the filter belongs to someone else' do
let(:filter) { Fabricate(:custom_filter) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
describe 'PUT /api/v2/filters/:id' do
subject do
put "/api/v2/filters/#{filter.id}", params: params, headers: headers
end
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
let(:params) { {} }
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
it_behaves_like 'unauthorized for invalid token'
context 'when updating filter parameters' do
context 'with valid params' do
let(:params) { { title: 'updated', context: %w(home public) } }
it 'updates the filter successfully', :aggregate_failures do
subject
filter.reload
expect(response).to have_http_status(200)
expect(filter.title).to eq 'updated'
expect(filter.reload.context).to eq %w(home public)
end
end
context 'with invalid params' do
let(:params) { { title: 'updated', context: %w(word) } }
it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422)
end
end
end
context 'when updating keywords in bulk' do
let(:params) { { keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] } }
before do
allow(redis).to receive_messages(publish: nil)
end
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'updates the keyword' do
subject
expect(keyword.reload.keyword).to eq 'updated'
end
it 'sends exactly one filters_changed event' do
subject
expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
end
end
context 'when the filter belongs to someone else' do
let(:filter) { Fabricate(:custom_filter) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
describe 'DELETE /api/v2/filters/:id' do
subject do
delete "/api/v2/filters/#{filter.id}", headers: headers
end
let(:filter) { Fabricate(:custom_filter, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
it_behaves_like 'unauthorized for invalid token'
it 'returns http success' do
subject
expect(response).to have_http_status(200)
end
it 'removes the filter' do
subject
expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
end
context 'when the filter belongs to someone else' do
let(:filter) { Fabricate(:custom_filter) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,161 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe '/api/web/embed' do
subject { get "/api/web/embeds/#{id}", headers: headers }
context 'when accessed anonymously' do
let(:headers) { {} }
context 'when the requested status is local' do
let(:id) { status.id }
context 'when the requested status is public' do
let(:status) { Fabricate(:status, visibility: :public) }
it 'returns JSON with an html attribute' do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:html]).to be_present
end
end
context 'when the requested status is private' do
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
context 'when the requested status is remote' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') }
let(:id) { status.id }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when the requested status does not exist' do
let(:id) { -1 }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
context 'with an API token' do
let(:user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
context 'when the requested status is local' do
let(:id) { status.id }
context 'when the requested status is public' do
let(:status) { Fabricate(:status, visibility: :public) }
it 'returns JSON with an html attribute' do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:html]).to be_present
end
context 'when the requesting user is blocked' do
before do
status.account.block!(user.account)
end
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
context 'when the requested status is private' do
let(:status) { Fabricate(:status, visibility: :private) }
before do
user.account.follow!(status.account)
end
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
context 'when the requested status is remote' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') }
let(:id) { status.id }
let(:service_instance) { instance_double(FetchOEmbedService) }
before do
allow(FetchOEmbedService).to receive(:new) { service_instance }
allow(service_instance).to receive(:call) { call_result }
end
context 'when the requesting user is blocked' do
before do
status.account.block!(user.account)
end
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
context 'when successfully fetching OEmbed' do
let(:call_result) { { html: 'ok' } }
it 'returns JSON with an html attribute' do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:html]).to be_present
end
end
context 'when failing to fetch OEmbed' do
let(:call_result) { nil }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
context 'when the requested status does not exist' do
let(:id) { -1 }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
end
end
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Backups' do
include RoutingHelper
describe 'GET backups#download' do
let(:user) { Fabricate(:user) }
let(:backup) { Fabricate(:backup, user: user) }
before do
sign_in user
end
it 'Downloads a user backup' do
get download_backup_path(backup)
expect(response).to redirect_to(backup_dump_url)
end
def backup_dump_url
full_asset_url(backup.dump.url)
end
end
end

692
spec/requests/cache_spec.rb Normal file
View file

@ -0,0 +1,692 @@
# frozen_string_literal: true
require 'rails_helper'
module TestEndpoints
# Endpoints that do not include authorization-dependent results
# and should be cacheable no matter what.
ALWAYS_CACHED = %w(
/.well-known/host-meta
/.well-known/nodeinfo
/nodeinfo/2.0
/manifest
/custom.css
/actor
/api/v1/instance/extended_description
/api/v1/instance/rules
/api/v1/instance/peers
/api/v1/instance
/api/v2/instance
).freeze
# Endpoints that should be cachable when accessed anonymously but have a Vary
# on Cookie to prevent logged-in users from getting values from logged-out cache.
COOKIE_DEPENDENT_CACHABLE = %w(
/
/explore
/public
/about
/privacy-policy
/directory
/@alice
/@alice/110224538612341312
/deck/home
).freeze
# Endpoints that should be cachable when accessed anonymously but have a Vary
# on Authorization to prevent logged-in users from getting values from logged-out cache.
AUTHORIZATION_DEPENDENT_CACHABLE = %w(
/api/v1/accounts/lookup?acct=alice
/api/v1/statuses/110224538612341312
/api/v1/statuses/110224538612341312/context
/api/v1/polls/12345
/api/v1/trends/statuses
/api/v1/directory
).freeze
# Private status that should only be returned with to a valid signature from
# a specific user.
# Should never be cached.
REQUIRE_SIGNATURE = %w(
/users/alice/statuses/110224538643211312
).freeze
# Pages only available to logged-in users.
# Should never be cached.
REQUIRE_LOGIN = %w(
/settings/preferences/appearance
/settings/profile
/settings/featured_tags
/settings/export
/relationships
/filters
/statuses_cleanup
/auth/edit
/oauth/authorized_applications
/admin/dashboard
).freeze
# API endpoints only available to logged-in users.
# Should never be cached.
REQUIRE_TOKEN = %w(
/api/v1/announcements
/api/v1/timelines/home
/api/v1/notifications
/api/v1/bookmarks
/api/v1/favourites
/api/v1/follow_requests
/api/v1/conversations
/api/v1/statuses/110224538643211312
/api/v1/statuses/110224538643211312/context
/api/v1/lists
/api/v2/filters
).freeze
# Pages that are only shown to logged-out users, and should never get cached
# because of CSRF protection.
REQUIRE_LOGGED_OUT = %w(
/invite/abcdef
/auth/sign_in
/auth/sign_up
/auth/password/new
/auth/confirmation/new
).freeze
# Non-exhaustive list of endpoints that feature language-dependent results
# and thus need to have a Vary on Accept-Language
LANGUAGE_DEPENDENT = %w(
/
/explore
/about
/api/v1/trends/statuses
).freeze
module AuthorizedFetch
# Endpoints that require a signature with AUTHORIZED_FETCH and LIMITED_FEDERATION_MODE
# and thus should not be cached in those modes.
REQUIRE_SIGNATURE = %w(
/users/alice
).freeze
end
module DisabledAnonymousAPI
# Endpoints that require a signature with DISALLOW_UNAUTHENTICATED_API_ACCESS
# and thus should not be cached in this mode.
REQUIRE_TOKEN = %w(
/api/v1/custom_emojis
).freeze
end
end
describe 'Caching behavior' do
shared_examples 'cachable response' do
it 'does not set cookies' do
expect(response.cookies).to be_empty
end
it 'sets public cache control', :aggregate_failures do
# expect(response.cache_control[:max_age]&.to_i).to be_positive
expect(response.cache_control[:public]).to be_truthy
expect(response.cache_control[:private]).to be_falsy
expect(response.cache_control[:no_store]).to be_falsy
expect(response.cache_control[:no_cache]).to be_falsy
end
end
shared_examples 'non-cacheable response' do
it 'sets private cache control' do
expect(response.cache_control[:private]).to be_truthy
expect(response.cache_control[:no_store]).to be_truthy
end
end
shared_examples 'non-cacheable error' do
it 'does not return HTTP success and does not have cache headers', :aggregate_failures do
expect(response).to_not have_http_status(200)
expect(response.cache_control[:public]).to be_falsy
end
end
shared_examples 'language-dependent' do
it 'has a Vary on Accept-Language' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept-language')
end
end
# Enable CSRF protection like it is in production, as it can cause cookies
# to be set and thus mess with cache.
around do |example|
old = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
example.run
ActionController::Base.allow_forgery_protection = old
end
let(:alice) { Fabricate(:account, username: 'alice') }
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
before do
# rubocop:disable Style/NumericLiterals
status = Fabricate(:status, account: alice, id: 110224538612341312)
Fabricate(:status, account: alice, id: 110224538643211312, visibility: :private)
Fabricate(:invite, code: 'abcdef')
Fabricate(:poll, status: status, account: alice, id: 12345)
# rubocop:enable Style/NumericLiterals
user.account.follow!(alice)
end
context 'when anonymously accessed' do
describe '/users/alice' do
it 'redirects with proper cache header', :aggregate_failures do
get '/users/alice'
expect(response).to redirect_to('/@alice')
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept')
end
end
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'cachable response'
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'cachable response'
it 'has a Vary on Cookie' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
end
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'cachable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
end
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable response'
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::REQUIRE_LOGIN + TestEndpoints::REQUIRE_TOKEN).each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable error'
end
end
describe '/api/v1/instance/domain_blocks' do
around do |example|
old_setting = Setting.show_domain_blocks
Setting.show_domain_blocks = show_domain_blocks
example.run
Setting.show_domain_blocks = old_setting
end
before { get '/api/v1/instance/domain_blocks' }
context 'when set to be publicly-available' do
let(:show_domain_blocks) { 'all' }
it_behaves_like 'cachable response'
end
context 'when allowed for local users only' do
let(:show_domain_blocks) { 'users' }
it_behaves_like 'non-cacheable error'
end
context 'when disabled' do
let(:show_domain_blocks) { 'disabled' }
it_behaves_like 'non-cacheable error'
end
end
end
context 'when logged in' do
before do
sign_in user, scope: :user
# Unfortunately, devise's `sign_in` helper causes the `session` to be
# loaded in the next request regardless of whether it's actually accessed
# by the client code.
#
# So, we make an extra query to clear issue a session cookie instead.
#
# A less resource-intensive way to deal with that would be to generate the
# session cookie manually, but this seems pretty involved.
get '/'
end
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'cachable response'
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable response'
it 'has a Vary on Cookie' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
end
end
end
TestEndpoints::REQUIRE_LOGIN.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable error'
end
end
end
context 'with an auth token' do
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'cachable response'
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
end
end
end
(TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN).each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
describe '/api/v1/instance/domain_blocks' do
around do |example|
old_setting = Setting.show_domain_blocks
Setting.show_domain_blocks = show_domain_blocks
example.run
Setting.show_domain_blocks = old_setting
end
before do
get '/api/v1/instance/domain_blocks', headers: { 'Authorization' => "Bearer #{token.token}" }
end
context 'when set to be publicly-available' do
let(:show_domain_blocks) { 'all' }
it_behaves_like 'cachable response'
end
context 'when allowed for local users only' do
let(:show_domain_blocks) { 'users' }
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
context 'when disabled' do
let(:show_domain_blocks) { 'disabled' }
it_behaves_like 'non-cacheable error'
end
end
end
context 'with a Signature header' do
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
let(:dummy_signature) { 'dummy-signature' }
before do
remote_actor.follow!(alice)
end
describe '/actor' do
before do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint|
describe endpoint do
before do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
end
context 'when enabling AUTHORIZED_FETCH mode' do
around do |example|
ClimateControl.modify AUTHORIZED_FETCH: 'true' do
example.run
end
end
context 'when not providing a Signature' do
describe '/actor' do
before do
get '/actor', headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable error'
end
end
end
context 'when providing a Signature' do
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
let(:dummy_signature) { 'dummy-signature' }
before do
remote_actor.follow!(alice)
end
describe '/actor' do
before do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
describe endpoint do
before do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
end
end
context 'when enabling LIMITED_FEDERATION_MODE mode' do
around do |example|
ClimateControl.modify LIMITED_FEDERATION_MODE: 'true' do
old_limited_federation_mode = Rails.configuration.x.limited_federation_mode
Rails.configuration.x.limited_federation_mode = true
example.run
Rails.configuration.x.limited_federation_mode = old_limited_federation_mode
end
end
context 'when not providing a Signature' do
describe '/actor' do
before do
get '/actor', headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable error'
end
end
end
context 'when providing a Signature from an allowed domain' do
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
let(:dummy_signature) { 'dummy-signature' }
before do
DomainAllow.create!(domain: remote_actor.domain)
remote_actor.follow!(alice)
end
describe '/actor' do
before do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
describe endpoint do
before do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
end
context 'when providing a Signature from a non-allowed domain' do
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
let(:dummy_signature) { 'dummy-signature' }
describe '/actor' do
before do
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'cachable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
describe endpoint do
before do
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
end
it_behaves_like 'non-cacheable error'
end
end
end
end
context 'when enabling DISALLOW_UNAUTHENTICATED_API_ACCESS' do
around do |example|
ClimateControl.modify DISALLOW_UNAUTHENTICATED_API_ACCESS: 'true' do
example.run
end
end
context 'when anonymously accessed' do
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'cachable response'
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable response'
end
end
(TestEndpoints::REQUIRE_TOKEN + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint|
describe endpoint do
before { get endpoint }
it_behaves_like 'non-cacheable error'
end
end
end
context 'with an auth token' do
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'cachable response'
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
end
end
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'has a Vary on Authorization' do
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
end
end
end
(TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint|
describe endpoint do
before do
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
end
it_behaves_like 'non-cacheable response'
it 'returns HTTP success' do
expect(response).to have_http_status(200)
end
end
end
end
end
end

View file

@ -1,21 +1,23 @@
require "rails_helper"
# frozen_string_literal: true
describe "The catch all route" do
describe "with a simple value" do
it "returns a 404 page as html" do
get "/test"
require 'rails_helper'
expect(response.status).to eq 404
expect(response.media_type).to eq "text/html"
describe 'The catch all route' do
describe 'with a simple value' do
it 'returns a 404 page as html' do
get '/test'
expect(response).to have_http_status 404
expect(response.media_type).to eq 'text/html'
end
end
describe "with an implied format" do
it "returns a 404 page as html" do
get "/test.test"
describe 'with an implied format' do
it 'returns a 404 page as html' do
get '/test.test'
expect(response.status).to eq 404
expect(response.media_type).to eq "text/html"
expect(response).to have_http_status 404
expect(response.media_type).to eq 'text/html'
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'FollowerAccountsController' do
describe 'The follower_accounts route' do
it "returns a http 'moved_permanently' code" do
get '/users/:username/followers'
expect(response).to have_http_status(301)
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'FollowingAccountsController' do
describe 'The following_accounts route' do
it "returns a http 'moved_permanently' code" do
get '/users/:username/following'
expect(response).to have_http_status(301)
end
end
end

View file

@ -1,12 +1,14 @@
require "rails_helper"
# frozen_string_literal: true
describe "The host_meta route" do
describe "requested without accepts headers" do
it "returns an xml response" do
require 'rails_helper'
describe 'The host_meta route' do
describe 'requested without accepts headers' do
it 'returns an xml response' do
get host_meta_url
expect(response).to have_http_status(200)
expect(response.media_type).to eq "application/xrd+xml"
expect(response.media_type).to eq 'application/xrd+xml'
end
end
end

View file

@ -13,7 +13,7 @@ describe 'Link headers' do
it 'contains webfinger url in link header' do
link_header = link_header_with_type('application/jrd+json')
expect(link_header.href).to match 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
expect(link_header.href).to eq 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
expect(link_header.attr_pairs.first).to eq %w(rel lrdd)
end
@ -26,7 +26,7 @@ describe 'Link headers' do
def link_header_with_type(type)
LinkHeader.parse(response.headers['Link'].to_s).links.find do |link|
link.attr_pairs.any? { |pair| pair == ['type', type] }
link.attr_pairs.any?(['type', type])
end
end
end

View file

@ -3,14 +3,16 @@
require 'rails_helper'
describe 'Localization' do
after(:all) do
I18n.locale = I18n.default_locale
around do |example|
I18n.with_locale(I18n.locale) do
example.run
end
end
it 'uses a specific region when provided' do
headers = { 'Accept-Language' => 'zh-HK' }
get "/auth/sign_in", headers: headers
get '/auth/sign_in', headers: headers
expect(response.body).to include(
I18n.t('auth.login', locale: 'zh-HK')
@ -20,7 +22,7 @@ describe 'Localization' do
it 'falls back to a locale when region missing' do
headers = { 'Accept-Language' => 'es-FAKE' }
get "/auth/sign_in", headers: headers
get '/auth/sign_in', headers: headers
expect(response.body).to include(
I18n.t('auth.login', locale: 'es')
@ -30,7 +32,7 @@ describe 'Localization' do
it 'falls back to english when locale is missing' do
headers = { 'Accept-Language' => '12-FAKE' }
get "/auth/sign_in", headers: headers
get '/auth/sign_in', headers: headers
expect(response.body).to include(
I18n.t('auth.login', locale: 'en')

View file

@ -0,0 +1,103 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'MailSubscriptionsController' do
let(:user) { Fabricate(:user) }
let(:token) { user.to_sgid(for: 'unsubscribe').to_s }
let(:type) { 'follow' }
shared_examples 'not found with invalid token' do
context 'with invalid token' do
let(:token) { 'invalid-token' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
shared_examples 'not found with invalid type' do
context 'with invalid type' do
let(:type) { 'invalid_type' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
describe 'on the unsubscribe confirmation page' do
before do
get unsubscribe_url(token: token, type: type)
end
it_behaves_like 'not found with invalid token'
it_behaves_like 'not found with invalid type'
it 'shows unsubscribe form' do
expect(response).to have_http_status(200)
expect(response.body).to include(
I18n.t('mail_subscriptions.unsubscribe.action')
)
expect(response.body).to include(user.email)
end
end
describe 'submitting the unsubscribe confirmation page' do
before do
user.settings.update('notification_emails.follow': true)
user.save!
post unsubscribe_url, params: { token: token, type: type }
end
it_behaves_like 'not found with invalid token'
it_behaves_like 'not found with invalid type'
it 'shows confirmation page' do
expect(response).to have_http_status(200)
expect(response.body).to include(
I18n.t('mail_subscriptions.unsubscribe.complete')
)
expect(response.body).to include(user.email)
end
it 'updates notification settings' do
user.reload
expect(user.settings['notification_emails.follow']).to be false
end
end
describe 'unsubscribing with List-Unsubscribe-Post' do
around do |example|
old = ActionController::Base.allow_forgery_protection
ActionController::Base.allow_forgery_protection = true
example.run
ActionController::Base.allow_forgery_protection = old
end
before do
user.settings.update('notification_emails.follow': true)
user.save!
post unsubscribe_url(token: token, type: type), params: { 'List-Unsubscribe' => 'One-Click' }
end
it_behaves_like 'not found with invalid token'
it_behaves_like 'not found with invalid type'
it 'return http success' do
expect(response).to have_http_status(200)
end
it 'updates notification settings' do
user.reload
expect(user.settings['notification_emails.follow']).to be false
end
end
end

View file

@ -4,7 +4,7 @@ require 'rails_helper'
describe 'OmniAuth callbacks' do
shared_examples 'omniauth provider callbacks' do |provider|
subject { post send :"user_#{provider}_omniauth_callback_path" }
subject { post send "user_#{provider}_omniauth_callback_path" }
context 'with full information in response' do
before do

View file

@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'The webfinger route' do