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,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] }