Merge tag 'v4.2.12' into chinwag-next
This commit is contained in:
commit
9bcb7630b3
3138 changed files with 94619 additions and 59187 deletions
44
spec/requests/anonymous_cookies_spec.rb
Normal file
44
spec/requests/anonymous_cookies_spec.rb
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
context 'when visited anonymously' do
|
||||
around do |example|
|
||||
old = ActionController::Base.allow_forgery_protection
|
||||
ActionController::Base.allow_forgery_protection = true
|
||||
|
||||
example.run
|
||||
|
||||
ActionController::Base.allow_forgery_protection = old
|
||||
end
|
||||
|
||||
describe 'account pages' do
|
||||
it 'do not set cookies' do
|
||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||
_status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||
|
||||
get '/@alice'
|
||||
|
||||
expect(response.cookies).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'status pages' do
|
||||
it 'do not set cookies' do
|
||||
alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
|
||||
status = Fabricate(:status, account: alice, text: 'Hello World')
|
||||
|
||||
get short_account_status_url(alice, status)
|
||||
|
||||
expect(response.cookies).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'the /about page' do
|
||||
it 'does not set cookies' do
|
||||
get '/about'
|
||||
|
||||
expect(response.cookies).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
64
spec/requests/api/v1/accounts/credentials_spec.rb
Normal file
64
spec/requests/api/v1/accounts/credentials_spec.rb
Normal 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
|
||||
53
spec/requests/api/v1/accounts_show_spec.rb
Normal file
53
spec/requests/api/v1/accounts_show_spec.rb
Normal 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
|
||||
154
spec/requests/api/v1/admin/account_actions_spec.rb
Normal file
154
spec/requests/api/v1/admin/account_actions_spec.rb
Normal 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
|
||||
285
spec/requests/api/v1/admin/canonical_email_blocks_spec.rb
Normal file
285
spec/requests/api/v1/admin/canonical_email_blocks_spec.rb
Normal 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
|
||||
194
spec/requests/api/v1/admin/domain_allows_spec.rb
Normal file
194
spec/requests/api/v1/admin/domain_allows_spec.rb
Normal 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
|
||||
300
spec/requests/api/v1/admin/domain_blocks_spec.rb
Normal file
300
spec/requests/api/v1/admin/domain_blocks_spec.rb
Normal 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
|
||||
211
spec/requests/api/v1/admin/email_domain_blocks_spec.rb
Normal file
211
spec/requests/api/v1/admin/email_domain_blocks_spec.rb
Normal 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
|
||||
255
spec/requests/api/v1/admin/ip_blocks_spec.rb
Normal file
255
spec/requests/api/v1/admin/ip_blocks_spec.rb
Normal 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
|
||||
272
spec/requests/api/v1/admin/reports_spec.rb
Normal file
272
spec/requests/api/v1/admin/reports_spec.rb
Normal 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
|
||||
141
spec/requests/api/v1/admin/tags_spec.rb
Normal file
141
spec/requests/api/v1/admin/tags_spec.rb
Normal 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
|
||||
44
spec/requests/api/v1/apps/credentials_spec.rb
Normal file
44
spec/requests/api/v1/apps/credentials_spec.rb
Normal 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
|
||||
115
spec/requests/api/v1/apps_spec.rb
Normal file
115
spec/requests/api/v1/apps_spec.rb
Normal 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
|
||||
61
spec/requests/api/v1/bookmarks_spec.rb
Normal file
61
spec/requests/api/v1/bookmarks_spec.rb
Normal 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
|
||||
125
spec/requests/api/v1/domain_blocks_spec.rb
Normal file
125
spec/requests/api/v1/domain_blocks_spec.rb
Normal 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
|
||||
168
spec/requests/api/v1/emails/confirmations_spec.rb
Normal file
168
spec/requests/api/v1/emails/confirmations_spec.rb
Normal 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
|
||||
193
spec/requests/api/v1/featured_tags_spec.rb
Normal file
193
spec/requests/api/v1/featured_tags_spec.rb
Normal 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
|
||||
119
spec/requests/api/v1/follow_requests_spec.rb
Normal file
119
spec/requests/api/v1/follow_requests_spec.rb
Normal 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
|
||||
19
spec/requests/api/v1/instances/languages_spec.rb
Normal file
19
spec/requests/api/v1/instances/languages_spec.rb
Normal 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
|
||||
247
spec/requests/api/v1/lists_spec.rb
Normal file
247
spec/requests/api/v1/lists_spec.rb
Normal 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
|
||||
90
spec/requests/api/v1/mutes_spec.rb
Normal file
90
spec/requests/api/v1/mutes_spec.rb
Normal 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
|
||||
47
spec/requests/api/v1/polls_spec.rb
Normal file
47
spec/requests/api/v1/polls_spec.rb
Normal 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
|
||||
98
spec/requests/api/v1/profiles_spec.rb
Normal file
98
spec/requests/api/v1/profiles_spec.rb
Normal 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
|
||||
155
spec/requests/api/v1/statuses/bookmarks_spec.rb
Normal file
155
spec/requests/api/v1/statuses/bookmarks_spec.rb
Normal 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
|
||||
155
spec/requests/api/v1/statuses/favourites_spec.rb
Normal file
155
spec/requests/api/v1/statuses/favourites_spec.rb
Normal 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
|
||||
131
spec/requests/api/v1/statuses/pins_spec.rb
Normal file
131
spec/requests/api/v1/statuses/pins_spec.rb
Normal 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
|
||||
103
spec/requests/api/v1/suggestions_spec.rb
Normal file
103
spec/requests/api/v1/suggestions_spec.rb
Normal 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
|
||||
169
spec/requests/api/v1/tags_spec.rb
Normal file
169
spec/requests/api/v1/tags_spec.rb
Normal 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
|
||||
101
spec/requests/api/v1/timelines/home_spec.rb
Normal file
101
spec/requests/api/v1/timelines/home_spec.rb
Normal 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
|
||||
|
|
@ -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] }
|
||||
|
|
|
|||
248
spec/requests/api/v2/filters/filters_spec.rb
Normal file
248
spec/requests/api/v2/filters/filters_spec.rb
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Filters' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:scopes) { 'read:filters write:filters' }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
shared_examples 'unauthorized for invalid token' do
|
||||
let(:headers) { { 'Authorization' => '' } }
|
||||
|
||||
it 'returns http unauthorized' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/filters' do
|
||||
subject do
|
||||
get '/api/v2/filters', headers: headers
|
||||
end
|
||||
|
||||
let!(:filters) { Fabricate.times(3, :custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns the existing filters successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/filters' do
|
||||
subject do
|
||||
post '/api/v2/filters', params: params, headers: headers
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
context 'with valid params' do
|
||||
let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns a filter with keywords', :aggregate_failures do
|
||||
subject
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json[:title]).to eq 'magic'
|
||||
expect(json[:filter_action]).to eq 'hide'
|
||||
expect(json[:context]).to eq ['home']
|
||||
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
|
||||
end
|
||||
|
||||
it 'creates a filter', :aggregate_failures do
|
||||
subject
|
||||
|
||||
filter = user.account.custom_filters.first
|
||||
|
||||
expect(filter).to be_present
|
||||
expect(filter.keywords.pluck(:keyword)).to eq ['magic']
|
||||
expect(filter.context).to eq %w(home)
|
||||
expect(filter.irreversible?).to be true
|
||||
expect(filter.expires_at).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the required title param is missing' do
|
||||
let(:params) { { context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the required context param is missing' do
|
||||
let(:params) { { title: 'magic', filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given context value is invalid' do
|
||||
let(:params) { { title: 'magic', context: %w(shaolin), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/filters/:id' do
|
||||
subject do
|
||||
get "/api/v2/filters/#{filter.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns the filter successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json[:id]).to eq(filter.id.to_s)
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v2/filters/:id' do
|
||||
subject do
|
||||
put "/api/v2/filters/#{filter.id}", params: params, headers: headers
|
||||
end
|
||||
|
||||
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
context 'when updating filter parameters' do
|
||||
context 'with valid params' do
|
||||
let(:params) { { title: 'updated', context: %w(home public) } }
|
||||
|
||||
it 'updates the filter successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
filter.reload
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(filter.title).to eq 'updated'
|
||||
expect(filter.reload.context).to eq %w(home public)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:params) { { title: 'updated', context: %w(word) } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating keywords in bulk' do
|
||||
let(:params) { { keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] } }
|
||||
|
||||
before do
|
||||
allow(redis).to receive_messages(publish: nil)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'updates the keyword' do
|
||||
subject
|
||||
|
||||
expect(keyword.reload.keyword).to eq 'updated'
|
||||
end
|
||||
|
||||
it 'sends exactly one filters_changed event' do
|
||||
subject
|
||||
|
||||
expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v2/filters/:id' do
|
||||
subject do
|
||||
delete "/api/v2/filters/#{filter.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'removes the filter' do
|
||||
subject
|
||||
|
||||
expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
161
spec/requests/api/web/embeds_spec.rb
Normal file
161
spec/requests/api/web/embeds_spec.rb
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe '/api/web/embed' do
|
||||
subject { get "/api/web/embeds/#{id}", headers: headers }
|
||||
|
||||
context 'when accessed anonymously' do
|
||||
let(:headers) { {} }
|
||||
|
||||
context 'when the requested status is local' do
|
||||
let(:id) { status.id }
|
||||
|
||||
context 'when the requested status is public' do
|
||||
let(:status) { Fabricate(:status, visibility: :public) }
|
||||
|
||||
it 'returns JSON with an html attribute' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json[:html]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status is private' do
|
||||
let(:status) { Fabricate(:status, visibility: :private) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status is remote' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') }
|
||||
let(:id) { status.id }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status does not exist' do
|
||||
let(:id) { -1 }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an API token' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
context 'when the requested status is local' do
|
||||
let(:id) { status.id }
|
||||
|
||||
context 'when the requested status is public' do
|
||||
let(:status) { Fabricate(:status, visibility: :public) }
|
||||
|
||||
it 'returns JSON with an html attribute' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json[:html]).to be_present
|
||||
end
|
||||
|
||||
context 'when the requesting user is blocked' do
|
||||
before do
|
||||
status.account.block!(user.account)
|
||||
end
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status is private' do
|
||||
let(:status) { Fabricate(:status, visibility: :private) }
|
||||
|
||||
before do
|
||||
user.account.follow!(status.account)
|
||||
end
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status is remote' do
|
||||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') }
|
||||
let(:id) { status.id }
|
||||
|
||||
let(:service_instance) { instance_double(FetchOEmbedService) }
|
||||
|
||||
before do
|
||||
allow(FetchOEmbedService).to receive(:new) { service_instance }
|
||||
allow(service_instance).to receive(:call) { call_result }
|
||||
end
|
||||
|
||||
context 'when the requesting user is blocked' do
|
||||
before do
|
||||
status.account.block!(user.account)
|
||||
end
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when successfully fetching OEmbed' do
|
||||
let(:call_result) { { html: 'ok' } }
|
||||
|
||||
it 'returns JSON with an html attribute' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json[:html]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failing to fetch OEmbed' do
|
||||
let(:call_result) { nil }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requested status does not exist' do
|
||||
let(:id) { -1 }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
spec/requests/backups_spec.rb
Normal file
26
spec/requests/backups_spec.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'Backups' do
|
||||
include RoutingHelper
|
||||
|
||||
describe 'GET backups#download' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:backup) { Fabricate(:backup, user: user) }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it 'Downloads a user backup' do
|
||||
get download_backup_path(backup)
|
||||
|
||||
expect(response).to redirect_to(backup_dump_url)
|
||||
end
|
||||
|
||||
def backup_dump_url
|
||||
full_asset_url(backup.dump.url)
|
||||
end
|
||||
end
|
||||
end
|
||||
692
spec/requests/cache_spec.rb
Normal file
692
spec/requests/cache_spec.rb
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
module TestEndpoints
|
||||
# Endpoints that do not include authorization-dependent results
|
||||
# and should be cacheable no matter what.
|
||||
ALWAYS_CACHED = %w(
|
||||
/.well-known/host-meta
|
||||
/.well-known/nodeinfo
|
||||
/nodeinfo/2.0
|
||||
/manifest
|
||||
/custom.css
|
||||
/actor
|
||||
/api/v1/instance/extended_description
|
||||
/api/v1/instance/rules
|
||||
/api/v1/instance/peers
|
||||
/api/v1/instance
|
||||
/api/v2/instance
|
||||
).freeze
|
||||
|
||||
# Endpoints that should be cachable when accessed anonymously but have a Vary
|
||||
# on Cookie to prevent logged-in users from getting values from logged-out cache.
|
||||
COOKIE_DEPENDENT_CACHABLE = %w(
|
||||
/
|
||||
/explore
|
||||
/public
|
||||
/about
|
||||
/privacy-policy
|
||||
/directory
|
||||
/@alice
|
||||
/@alice/110224538612341312
|
||||
/deck/home
|
||||
).freeze
|
||||
|
||||
# Endpoints that should be cachable when accessed anonymously but have a Vary
|
||||
# on Authorization to prevent logged-in users from getting values from logged-out cache.
|
||||
AUTHORIZATION_DEPENDENT_CACHABLE = %w(
|
||||
/api/v1/accounts/lookup?acct=alice
|
||||
/api/v1/statuses/110224538612341312
|
||||
/api/v1/statuses/110224538612341312/context
|
||||
/api/v1/polls/12345
|
||||
/api/v1/trends/statuses
|
||||
/api/v1/directory
|
||||
).freeze
|
||||
|
||||
# Private status that should only be returned with to a valid signature from
|
||||
# a specific user.
|
||||
# Should never be cached.
|
||||
REQUIRE_SIGNATURE = %w(
|
||||
/users/alice/statuses/110224538643211312
|
||||
).freeze
|
||||
|
||||
# Pages only available to logged-in users.
|
||||
# Should never be cached.
|
||||
REQUIRE_LOGIN = %w(
|
||||
/settings/preferences/appearance
|
||||
/settings/profile
|
||||
/settings/featured_tags
|
||||
/settings/export
|
||||
/relationships
|
||||
/filters
|
||||
/statuses_cleanup
|
||||
/auth/edit
|
||||
/oauth/authorized_applications
|
||||
/admin/dashboard
|
||||
).freeze
|
||||
|
||||
# API endpoints only available to logged-in users.
|
||||
# Should never be cached.
|
||||
REQUIRE_TOKEN = %w(
|
||||
/api/v1/announcements
|
||||
/api/v1/timelines/home
|
||||
/api/v1/notifications
|
||||
/api/v1/bookmarks
|
||||
/api/v1/favourites
|
||||
/api/v1/follow_requests
|
||||
/api/v1/conversations
|
||||
/api/v1/statuses/110224538643211312
|
||||
/api/v1/statuses/110224538643211312/context
|
||||
/api/v1/lists
|
||||
/api/v2/filters
|
||||
).freeze
|
||||
|
||||
# Pages that are only shown to logged-out users, and should never get cached
|
||||
# because of CSRF protection.
|
||||
REQUIRE_LOGGED_OUT = %w(
|
||||
/invite/abcdef
|
||||
/auth/sign_in
|
||||
/auth/sign_up
|
||||
/auth/password/new
|
||||
/auth/confirmation/new
|
||||
).freeze
|
||||
|
||||
# Non-exhaustive list of endpoints that feature language-dependent results
|
||||
# and thus need to have a Vary on Accept-Language
|
||||
LANGUAGE_DEPENDENT = %w(
|
||||
/
|
||||
/explore
|
||||
/about
|
||||
/api/v1/trends/statuses
|
||||
).freeze
|
||||
|
||||
module AuthorizedFetch
|
||||
# Endpoints that require a signature with AUTHORIZED_FETCH and LIMITED_FEDERATION_MODE
|
||||
# and thus should not be cached in those modes.
|
||||
REQUIRE_SIGNATURE = %w(
|
||||
/users/alice
|
||||
).freeze
|
||||
end
|
||||
|
||||
module DisabledAnonymousAPI
|
||||
# Endpoints that require a signature with DISALLOW_UNAUTHENTICATED_API_ACCESS
|
||||
# and thus should not be cached in this mode.
|
||||
REQUIRE_TOKEN = %w(
|
||||
/api/v1/custom_emojis
|
||||
).freeze
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Caching behavior' do
|
||||
shared_examples 'cachable response' do
|
||||
it 'does not set cookies' do
|
||||
expect(response.cookies).to be_empty
|
||||
end
|
||||
|
||||
it 'sets public cache control', :aggregate_failures do
|
||||
# expect(response.cache_control[:max_age]&.to_i).to be_positive
|
||||
expect(response.cache_control[:public]).to be_truthy
|
||||
expect(response.cache_control[:private]).to be_falsy
|
||||
expect(response.cache_control[:no_store]).to be_falsy
|
||||
expect(response.cache_control[:no_cache]).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'non-cacheable response' do
|
||||
it 'sets private cache control' do
|
||||
expect(response.cache_control[:private]).to be_truthy
|
||||
expect(response.cache_control[:no_store]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'non-cacheable error' do
|
||||
it 'does not return HTTP success and does not have cache headers', :aggregate_failures do
|
||||
expect(response).to_not have_http_status(200)
|
||||
expect(response.cache_control[:public]).to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'language-dependent' do
|
||||
it 'has a Vary on Accept-Language' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept-language')
|
||||
end
|
||||
end
|
||||
|
||||
# Enable CSRF protection like it is in production, as it can cause cookies
|
||||
# to be set and thus mess with cache.
|
||||
around do |example|
|
||||
old = ActionController::Base.allow_forgery_protection
|
||||
ActionController::Base.allow_forgery_protection = true
|
||||
|
||||
example.run
|
||||
|
||||
ActionController::Base.allow_forgery_protection = old
|
||||
end
|
||||
|
||||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
|
||||
|
||||
before do
|
||||
# rubocop:disable Style/NumericLiterals
|
||||
status = Fabricate(:status, account: alice, id: 110224538612341312)
|
||||
Fabricate(:status, account: alice, id: 110224538643211312, visibility: :private)
|
||||
Fabricate(:invite, code: 'abcdef')
|
||||
Fabricate(:poll, status: status, account: alice, id: 12345)
|
||||
# rubocop:enable Style/NumericLiterals
|
||||
|
||||
user.account.follow!(alice)
|
||||
end
|
||||
|
||||
context 'when anonymously accessed' do
|
||||
describe '/users/alice' do
|
||||
it 'redirects with proper cache header', :aggregate_failures do
|
||||
get '/users/alice'
|
||||
|
||||
expect(response).to redirect_to('/@alice')
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept')
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'has a Vary on Cookie' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
|
||||
end
|
||||
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'has a Vary on Authorization' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
||||
end
|
||||
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::REQUIRE_LOGIN + TestEndpoints::REQUIRE_TOKEN).each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
|
||||
describe '/api/v1/instance/domain_blocks' do
|
||||
around do |example|
|
||||
old_setting = Setting.show_domain_blocks
|
||||
Setting.show_domain_blocks = show_domain_blocks
|
||||
|
||||
example.run
|
||||
|
||||
Setting.show_domain_blocks = old_setting
|
||||
end
|
||||
|
||||
before { get '/api/v1/instance/domain_blocks' }
|
||||
|
||||
context 'when set to be publicly-available' do
|
||||
let(:show_domain_blocks) { 'all' }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
end
|
||||
|
||||
context 'when allowed for local users only' do
|
||||
let(:show_domain_blocks) { 'users' }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
|
||||
context 'when disabled' do
|
||||
let(:show_domain_blocks) { 'disabled' }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
before do
|
||||
sign_in user, scope: :user
|
||||
|
||||
# Unfortunately, devise's `sign_in` helper causes the `session` to be
|
||||
# loaded in the next request regardless of whether it's actually accessed
|
||||
# by the client code.
|
||||
#
|
||||
# So, we make an extra query to clear issue a session cookie instead.
|
||||
#
|
||||
# A less resource-intensive way to deal with that would be to generate the
|
||||
# session cookie manually, but this seems pretty involved.
|
||||
get '/'
|
||||
end
|
||||
|
||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'has a Vary on Cookie' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::REQUIRE_LOGIN.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an auth token' do
|
||||
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||
|
||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'has a Vary on Authorization' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '/api/v1/instance/domain_blocks' do
|
||||
around do |example|
|
||||
old_setting = Setting.show_domain_blocks
|
||||
Setting.show_domain_blocks = show_domain_blocks
|
||||
|
||||
example.run
|
||||
|
||||
Setting.show_domain_blocks = old_setting
|
||||
end
|
||||
|
||||
before do
|
||||
get '/api/v1/instance/domain_blocks', headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
context 'when set to be publicly-available' do
|
||||
let(:show_domain_blocks) { 'all' }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
end
|
||||
|
||||
context 'when allowed for local users only' do
|
||||
let(:show_domain_blocks) { 'users' }
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when disabled' do
|
||||
let(:show_domain_blocks) { 'disabled' }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a Signature header' do
|
||||
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
|
||||
let(:dummy_signature) { 'dummy-signature' }
|
||||
|
||||
before do
|
||||
remote_actor.follow!(alice)
|
||||
end
|
||||
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when enabling AUTHORIZED_FETCH mode' do
|
||||
around do |example|
|
||||
ClimateControl.modify AUTHORIZED_FETCH: 'true' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not providing a Signature' do
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when providing a Signature' do
|
||||
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
|
||||
let(:dummy_signature) { 'dummy-signature' }
|
||||
|
||||
before do
|
||||
remote_actor.follow!(alice)
|
||||
end
|
||||
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when enabling LIMITED_FEDERATION_MODE mode' do
|
||||
around do |example|
|
||||
ClimateControl.modify LIMITED_FEDERATION_MODE: 'true' do
|
||||
old_limited_federation_mode = Rails.configuration.x.limited_federation_mode
|
||||
Rails.configuration.x.limited_federation_mode = true
|
||||
|
||||
example.run
|
||||
|
||||
Rails.configuration.x.limited_federation_mode = old_limited_federation_mode
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not providing a Signature' do
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when providing a Signature from an allowed domain' do
|
||||
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
|
||||
let(:dummy_signature) { 'dummy-signature' }
|
||||
|
||||
before do
|
||||
DomainAllow.create!(domain: remote_actor.domain)
|
||||
remote_actor.follow!(alice)
|
||||
end
|
||||
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when providing a Signature from a non-allowed domain' do
|
||||
let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) }
|
||||
let(:dummy_signature) { 'dummy-signature' }
|
||||
|
||||
describe '/actor' do
|
||||
before do
|
||||
get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when enabling DISALLOW_UNAUTHENTICATED_API_ACCESS' do
|
||||
around do |example|
|
||||
ClimateControl.modify DISALLOW_UNAUTHENTICATED_API_ACCESS: 'true' do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
context 'when anonymously accessed' do
|
||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_TOKEN + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint|
|
||||
describe endpoint do
|
||||
before { get endpoint }
|
||||
|
||||
it_behaves_like 'non-cacheable error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an auth token' do
|
||||
let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
|
||||
|
||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'cachable response'
|
||||
it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint)
|
||||
end
|
||||
end
|
||||
|
||||
TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'has a Vary on Authorization' do
|
||||
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
(TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint|
|
||||
describe endpoint do
|
||||
before do
|
||||
get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" }
|
||||
end
|
||||
|
||||
it_behaves_like 'non-cacheable response'
|
||||
|
||||
it 'returns HTTP success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,21 +1,23 @@
|
|||
require "rails_helper"
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe "The catch all route" do
|
||||
describe "with a simple value" do
|
||||
it "returns a 404 page as html" do
|
||||
get "/test"
|
||||
require 'rails_helper'
|
||||
|
||||
expect(response.status).to eq 404
|
||||
expect(response.media_type).to eq "text/html"
|
||||
describe 'The catch all route' do
|
||||
describe 'with a simple value' do
|
||||
it 'returns a 404 page as html' do
|
||||
get '/test'
|
||||
|
||||
expect(response).to have_http_status 404
|
||||
expect(response.media_type).to eq 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
describe "with an implied format" do
|
||||
it "returns a 404 page as html" do
|
||||
get "/test.test"
|
||||
describe 'with an implied format' do
|
||||
it 'returns a 404 page as html' do
|
||||
get '/test.test'
|
||||
|
||||
expect(response.status).to eq 404
|
||||
expect(response.media_type).to eq "text/html"
|
||||
expect(response).to have_http_status 404
|
||||
expect(response.media_type).to eq 'text/html'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
13
spec/requests/follower_accounts_spec.rb
Normal file
13
spec/requests/follower_accounts_spec.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'FollowerAccountsController' do
|
||||
describe 'The follower_accounts route' do
|
||||
it "returns a http 'moved_permanently' code" do
|
||||
get '/users/:username/followers'
|
||||
|
||||
expect(response).to have_http_status(301)
|
||||
end
|
||||
end
|
||||
end
|
||||
13
spec/requests/following_accounts_spec.rb
Normal file
13
spec/requests/following_accounts_spec.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'FollowingAccountsController' do
|
||||
describe 'The following_accounts route' do
|
||||
it "returns a http 'moved_permanently' code" do
|
||||
get '/users/:username/following'
|
||||
|
||||
expect(response).to have_http_status(301)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
require "rails_helper"
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe "The host_meta route" do
|
||||
describe "requested without accepts headers" do
|
||||
it "returns an xml response" do
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'The host_meta route' do
|
||||
describe 'requested without accepts headers' do
|
||||
it 'returns an xml response' do
|
||||
get host_meta_url
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.media_type).to eq "application/xrd+xml"
|
||||
expect(response.media_type).to eq 'application/xrd+xml'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe 'Link headers' do
|
|||
it 'contains webfinger url in link header' do
|
||||
link_header = link_header_with_type('application/jrd+json')
|
||||
|
||||
expect(link_header.href).to match 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
|
||||
expect(link_header.href).to eq 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io'
|
||||
expect(link_header.attr_pairs.first).to eq %w(rel lrdd)
|
||||
end
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ describe 'Link headers' do
|
|||
|
||||
def link_header_with_type(type)
|
||||
LinkHeader.parse(response.headers['Link'].to_s).links.find do |link|
|
||||
link.attr_pairs.any? { |pair| pair == ['type', type] }
|
||||
link.attr_pairs.any?(['type', type])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,14 +3,16 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'Localization' do
|
||||
after(:all) do
|
||||
I18n.locale = I18n.default_locale
|
||||
around do |example|
|
||||
I18n.with_locale(I18n.locale) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses a specific region when provided' do
|
||||
headers = { 'Accept-Language' => 'zh-HK' }
|
||||
|
||||
get "/auth/sign_in", headers: headers
|
||||
get '/auth/sign_in', headers: headers
|
||||
|
||||
expect(response.body).to include(
|
||||
I18n.t('auth.login', locale: 'zh-HK')
|
||||
|
|
@ -20,7 +22,7 @@ describe 'Localization' do
|
|||
it 'falls back to a locale when region missing' do
|
||||
headers = { 'Accept-Language' => 'es-FAKE' }
|
||||
|
||||
get "/auth/sign_in", headers: headers
|
||||
get '/auth/sign_in', headers: headers
|
||||
|
||||
expect(response.body).to include(
|
||||
I18n.t('auth.login', locale: 'es')
|
||||
|
|
@ -30,7 +32,7 @@ describe 'Localization' do
|
|||
it 'falls back to english when locale is missing' do
|
||||
headers = { 'Accept-Language' => '12-FAKE' }
|
||||
|
||||
get "/auth/sign_in", headers: headers
|
||||
get '/auth/sign_in', headers: headers
|
||||
|
||||
expect(response.body).to include(
|
||||
I18n.t('auth.login', locale: 'en')
|
||||
|
|
|
|||
103
spec/requests/mail_subscriptions_spec.rb
Normal file
103
spec/requests/mail_subscriptions_spec.rb
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'MailSubscriptionsController' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:token) { user.to_sgid(for: 'unsubscribe').to_s }
|
||||
let(:type) { 'follow' }
|
||||
|
||||
shared_examples 'not found with invalid token' do
|
||||
context 'with invalid token' do
|
||||
let(:token) { 'invalid-token' }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'not found with invalid type' do
|
||||
context 'with invalid type' do
|
||||
let(:type) { 'invalid_type' }
|
||||
|
||||
it 'returns http not found' do
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on the unsubscribe confirmation page' do
|
||||
before do
|
||||
get unsubscribe_url(token: token, type: type)
|
||||
end
|
||||
|
||||
it_behaves_like 'not found with invalid token'
|
||||
it_behaves_like 'not found with invalid type'
|
||||
|
||||
it 'shows unsubscribe form' do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.body).to include(
|
||||
I18n.t('mail_subscriptions.unsubscribe.action')
|
||||
)
|
||||
expect(response.body).to include(user.email)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'submitting the unsubscribe confirmation page' do
|
||||
before do
|
||||
user.settings.update('notification_emails.follow': true)
|
||||
user.save!
|
||||
|
||||
post unsubscribe_url, params: { token: token, type: type }
|
||||
end
|
||||
|
||||
it_behaves_like 'not found with invalid token'
|
||||
it_behaves_like 'not found with invalid type'
|
||||
|
||||
it 'shows confirmation page' do
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(response.body).to include(
|
||||
I18n.t('mail_subscriptions.unsubscribe.complete')
|
||||
)
|
||||
expect(response.body).to include(user.email)
|
||||
end
|
||||
|
||||
it 'updates notification settings' do
|
||||
user.reload
|
||||
expect(user.settings['notification_emails.follow']).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'unsubscribing with List-Unsubscribe-Post' do
|
||||
around do |example|
|
||||
old = ActionController::Base.allow_forgery_protection
|
||||
ActionController::Base.allow_forgery_protection = true
|
||||
|
||||
example.run
|
||||
|
||||
ActionController::Base.allow_forgery_protection = old
|
||||
end
|
||||
|
||||
before do
|
||||
user.settings.update('notification_emails.follow': true)
|
||||
user.save!
|
||||
|
||||
post unsubscribe_url(token: token, type: type), params: { 'List-Unsubscribe' => 'One-Click' }
|
||||
end
|
||||
|
||||
it_behaves_like 'not found with invalid token'
|
||||
it_behaves_like 'not found with invalid type'
|
||||
|
||||
it 'return http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'updates notification settings' do
|
||||
user.reload
|
||||
expect(user.settings['notification_emails.follow']).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@ require 'rails_helper'
|
|||
|
||||
describe 'OmniAuth callbacks' do
|
||||
shared_examples 'omniauth provider callbacks' do |provider|
|
||||
subject { post send :"user_#{provider}_omniauth_callback_path" }
|
||||
subject { post send "user_#{provider}_omniauth_callback_path" }
|
||||
|
||||
context 'with full information in response' do
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'The webfinger route' do
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue