Merge remote-tracking branch 'parent/main' into upstream-20230209
This commit is contained in:
commit
05e52a09a8
188 changed files with 2810 additions and 1295 deletions
|
@ -17,34 +17,27 @@ RSpec.describe ActivityPub::CollectionsController do
|
|||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
context 'when id is "featured"' do
|
||||
context 'without signature' do
|
||||
subject(:response) { get :show, params: { id: 'featured', account_username: account.username } }
|
||||
subject(:response) { get :show, params: { id: id, account_username: account.username } }
|
||||
|
||||
let(:body) { body_as_json }
|
||||
context 'when id is "featured"' do
|
||||
let(:id) { 'featured' }
|
||||
|
||||
context 'without signature' do
|
||||
let(:remote_account) { nil }
|
||||
|
||||
it 'returns http success' do
|
||||
it 'returns http success and correct media type' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
|
||||
it 'returns orderedItems with pinned statuses' do
|
||||
expect(body[:orderedItems]).to be_an Array
|
||||
expect(body[:orderedItems].size).to eq 3
|
||||
end
|
||||
|
||||
it 'includes URI of private pinned status' do
|
||||
expect(body[:orderedItems]).to include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
end
|
||||
|
||||
it 'does not include contents of private pinned status' do
|
||||
expect(response.body).to_not include(private_pinned.text)
|
||||
it 'returns orderedItems with correct items' do
|
||||
expect(body_as_json[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 3)
|
||||
.and include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
.and not_include(private_pinned.text)
|
||||
end
|
||||
|
||||
context 'when account is permanently suspended' do
|
||||
|
@ -73,33 +66,19 @@ RSpec.describe ActivityPub::CollectionsController do
|
|||
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
|
||||
|
||||
context 'when getting a featured resource' do
|
||||
before do
|
||||
get :show, params: { id: 'featured', account_username: account.username }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
it 'returns http success and correct media type' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response'
|
||||
|
||||
it 'returns orderedItems with pinned statuses' do
|
||||
json = body_as_json
|
||||
expect(json[:orderedItems]).to be_an Array
|
||||
expect(json[:orderedItems].size).to eq 3
|
||||
end
|
||||
|
||||
it 'includes URI of private pinned status' do
|
||||
json = body_as_json
|
||||
expect(json[:orderedItems]).to include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
end
|
||||
|
||||
it 'does not include contents of private pinned status' do
|
||||
expect(response.body).to_not include(private_pinned.text)
|
||||
it 'returns orderedItems with expected items' do
|
||||
expect(body_as_json[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 3)
|
||||
.and include(ActivityPub::TagManager.instance.uri_for(private_pinned))
|
||||
.and not_include(private_pinned.text)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -111,50 +90,36 @@ RSpec.describe ActivityPub::CollectionsController do
|
|||
context 'when signed request account is blocked' do
|
||||
before do
|
||||
account.block!(remote_account)
|
||||
get :show, params: { id: 'featured', account_username: account.username }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
it 'returns http success and correct media type and cache headers' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it 'returns private Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
end
|
||||
|
||||
it 'returns empty orderedItems' do
|
||||
json = body_as_json
|
||||
expect(json[:orderedItems]).to be_an Array
|
||||
expect(json[:orderedItems].size).to eq 0
|
||||
expect(body_as_json[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signed request account is domain blocked' do
|
||||
before do
|
||||
account.block_domain!(remote_account.domain)
|
||||
get :show, params: { id: 'featured', account_username: account.username }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
it 'returns http success and correct media type and cache headers' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns application/activity+json' do
|
||||
expect(response.media_type).to eq 'application/activity+json'
|
||||
end
|
||||
|
||||
it 'returns private Cache-Control header' do
|
||||
expect(response.headers['Cache-Control']).to include 'private'
|
||||
end
|
||||
|
||||
it 'returns empty orderedItems' do
|
||||
json = body_as_json
|
||||
expect(json[:orderedItems]).to be_an Array
|
||||
expect(json[:orderedItems].size).to eq 0
|
||||
expect(body_as_json[:orderedItems])
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -162,8 +127,9 @@ RSpec.describe ActivityPub::CollectionsController do
|
|||
end
|
||||
|
||||
context 'when id is not "featured"' do
|
||||
let(:id) { 'hoge' }
|
||||
|
||||
it 'returns http not found' do
|
||||
get :show, params: { id: 'hoge', account_username: account.username }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,18 +9,8 @@ RSpec.describe Admin::AccountsController do
|
|||
|
||||
describe 'GET #index' do
|
||||
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
|
||||
|
||||
around do |example|
|
||||
default_per_page = Account.default_per_page
|
||||
Account.paginates_per 1
|
||||
example.run
|
||||
Account.paginates_per default_per_page
|
||||
end
|
||||
|
||||
it 'filters with parameters' do
|
||||
account_filter = instance_double(AccountFilter, results: Account.all)
|
||||
allow(AccountFilter).to receive(:new).and_return(account_filter)
|
||||
params = {
|
||||
let(:params) do
|
||||
{
|
||||
origin: 'local',
|
||||
by_domain: 'domain',
|
||||
status: 'active',
|
||||
|
@ -29,25 +19,35 @@ RSpec.describe Admin::AccountsController do
|
|||
email: 'local-part@domain',
|
||||
ip: '0.0.0.42',
|
||||
}
|
||||
|
||||
get :index, params: params
|
||||
|
||||
expect(AccountFilter).to have_received(:new).with(hash_including(params))
|
||||
end
|
||||
|
||||
it 'paginates accounts' do
|
||||
around do |example|
|
||||
default_per_page = Account.default_per_page
|
||||
Account.paginates_per 1
|
||||
example.run
|
||||
Account.paginates_per default_per_page
|
||||
end
|
||||
|
||||
before do
|
||||
Fabricate(:account)
|
||||
|
||||
get :index, params: { page: 2 }
|
||||
|
||||
accounts = assigns(:accounts)
|
||||
expect(accounts.count).to eq 1
|
||||
expect(accounts.klass).to be Account
|
||||
account_filter = instance_double(AccountFilter, results: Account.all)
|
||||
allow(AccountFilter).to receive(:new).and_return(account_filter)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
it 'returns success and paginates and filters with parameters' do
|
||||
get :index, params: params.merge(page: 2)
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(200)
|
||||
expect(assigns(:accounts))
|
||||
.to have_attributes(
|
||||
count: eq(1),
|
||||
klass: be(Account)
|
||||
)
|
||||
expect(AccountFilter)
|
||||
.to have_received(:new)
|
||||
.with(hash_including(params))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ describe Admin::ReportsController do
|
|||
report.reload
|
||||
expect(report.action_taken_by_account).to eq user.account
|
||||
expect(report.action_taken?).to be true
|
||||
expect(last_action_log.target).to eq(report)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,6 +71,7 @@ describe Admin::ReportsController do
|
|||
report.reload
|
||||
expect(report.action_taken_by_account).to be_nil
|
||||
expect(report.action_taken?).to be false
|
||||
expect(last_action_log.target).to eq(report)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,6 +83,7 @@ describe Admin::ReportsController do
|
|||
expect(response).to redirect_to(admin_report_path(report))
|
||||
report.reload
|
||||
expect(report.assigned_account).to eq user.account
|
||||
expect(last_action_log.target).to eq(report)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -92,6 +95,13 @@ describe Admin::ReportsController do
|
|||
expect(response).to redirect_to(admin_report_path(report))
|
||||
report.reload
|
||||
expect(report.assigned_account).to be_nil
|
||||
expect(last_action_log.target).to eq(report)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def last_action_log
|
||||
Admin::ActionLog.last
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,9 +12,7 @@ describe 'blocking domains through the moderation interface' do
|
|||
it 'adds a new domain block' do
|
||||
visit new_admin_domain_block_path
|
||||
|
||||
fill_in 'domain_block_domain', with: 'example.com'
|
||||
select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity'
|
||||
click_on I18n.t('admin.domain_blocks.new.create')
|
||||
submit_domain_block('example.com', 'silence')
|
||||
|
||||
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
|
||||
expect(DomainBlockWorker).to have_received(:perform_async)
|
||||
|
@ -25,9 +23,7 @@ describe 'blocking domains through the moderation interface' do
|
|||
it 'presents a confirmation screen before suspending the domain' do
|
||||
visit new_admin_domain_block_path
|
||||
|
||||
fill_in 'domain_block_domain', with: 'example.com'
|
||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
||||
click_on I18n.t('admin.domain_blocks.new.create')
|
||||
submit_domain_block('example.com', 'suspend')
|
||||
|
||||
# It doesn't immediately block but presents a confirmation screen
|
||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
||||
|
@ -47,9 +43,7 @@ describe 'blocking domains through the moderation interface' do
|
|||
|
||||
visit new_admin_domain_block_path
|
||||
|
||||
fill_in 'domain_block_domain', with: 'example.com'
|
||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
||||
click_on I18n.t('admin.domain_blocks.new.create')
|
||||
submit_domain_block('example.com', 'suspend')
|
||||
|
||||
# It doesn't immediately block but presents a confirmation screen
|
||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
|
||||
|
@ -69,9 +63,7 @@ describe 'blocking domains through the moderation interface' do
|
|||
|
||||
visit new_admin_domain_block_path
|
||||
|
||||
fill_in 'domain_block_domain', with: 'subdomain.example.com'
|
||||
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
|
||||
click_on I18n.t('admin.domain_blocks.new.create')
|
||||
submit_domain_block('subdomain.example.com', 'suspend')
|
||||
|
||||
# It doesn't immediately block but presents a confirmation screen
|
||||
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com'))
|
||||
|
@ -84,8 +76,11 @@ describe 'blocking domains through the moderation interface' do
|
|||
expect(DomainBlockWorker).to have_received(:perform_async)
|
||||
|
||||
# And leaves the previous block alone
|
||||
expect(domain_block.reload.severity).to eq 'silence'
|
||||
expect(domain_block.reload.domain).to eq 'example.com'
|
||||
expect(domain_block.reload)
|
||||
.to have_attributes(
|
||||
severity: eq('silence'),
|
||||
domain: eq('example.com')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -109,4 +104,12 @@ describe 'blocking domains through the moderation interface' do
|
|||
expect(domain_block.reload.severity).to eq 'suspend'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def submit_domain_block(domain, severity)
|
||||
fill_in 'domain_block_domain', with: domain
|
||||
select I18n.t("admin.domain_blocks.new.severity.#{severity}"), from: 'domain_block_severity'
|
||||
click_on I18n.t('admin.domain_blocks.new.create')
|
||||
end
|
||||
end
|
||||
|
|
BIN
spec/fixtures/files/text.png
vendored
Normal file
BIN
spec/fixtures/files/text.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -57,20 +57,27 @@ RSpec.describe ActivityPub::TagManager do
|
|||
expect(subject.to(status)).to include(subject.followers_uri_for(mentioned))
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :direct, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.to(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.to(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.to(status)).to_not include(subject.uri_for(alice))
|
||||
context 'with followers and requested followers' do
|
||||
let!(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let!(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let!(:foo) { Fabricate(:account) }
|
||||
let!(:author) { Fabricate(:account, username: 'author', silenced: true) }
|
||||
let!(:status) { Fabricate(:status, visibility: :direct, account: author) }
|
||||
|
||||
before do
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for direct silenced author's status only if they are followers or requesting to be" do
|
||||
expect(subject.to(status))
|
||||
.to include(subject.uri_for(bob))
|
||||
.and include(subject.uri_for(foo))
|
||||
.and not_include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,20 +131,27 @@ RSpec.describe ActivityPub::TagManager do
|
|||
expect(subject.cc(status)).to include(subject.uri_for(mentioned))
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for silenced author's non-direct status only if they are followers or requesting to be" do
|
||||
bob = Fabricate(:account, username: 'bob')
|
||||
alice = Fabricate(:account, username: 'alice')
|
||||
foo = Fabricate(:account)
|
||||
author = Fabricate(:account, username: 'author', silenced: true)
|
||||
status = Fabricate(:status, visibility: :public, account: author)
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
expect(subject.cc(status)).to include(subject.uri_for(bob))
|
||||
expect(subject.cc(status)).to include(subject.uri_for(foo))
|
||||
expect(subject.cc(status)).to_not include(subject.uri_for(alice))
|
||||
context 'with followers and requested followers' do
|
||||
let!(:bob) { Fabricate(:account, username: 'bob') }
|
||||
let!(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let!(:foo) { Fabricate(:account) }
|
||||
let!(:author) { Fabricate(:account, username: 'author', silenced: true) }
|
||||
let!(:status) { Fabricate(:status, visibility: :public, account: author) }
|
||||
|
||||
before do
|
||||
bob.follow!(author)
|
||||
FollowRequest.create!(account: foo, target_account: author)
|
||||
status.mentions.create(account: alice)
|
||||
status.mentions.create(account: bob)
|
||||
status.mentions.create(account: foo)
|
||||
end
|
||||
|
||||
it "returns URIs of mentions for silenced author's non-direct status only if they are followers or requesting to be" do
|
||||
expect(subject.cc(status))
|
||||
.to include(subject.uri_for(bob))
|
||||
.and include(subject.uri_for(foo))
|
||||
.and not_include(subject.uri_for(alice))
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns poster of reblogged post, if reblog' do
|
||||
|
|
|
@ -33,18 +33,14 @@ describe RequestPool do
|
|||
|
||||
subject
|
||||
|
||||
threads = Array.new(5) do
|
||||
Thread.new do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
# Nudge scheduler to yield and exercise the full pool
|
||||
sleep(0.01)
|
||||
end
|
||||
multi_threaded_execution(5) do
|
||||
subject.with('http://example.com') do |http_client|
|
||||
http_client.get('/').flush
|
||||
# Nudge scheduler to yield and exercise the full pool
|
||||
sleep(0.01)
|
||||
end
|
||||
end
|
||||
|
||||
threads.map(&:join)
|
||||
|
||||
expect(subject.size).to be > 1
|
||||
end
|
||||
|
||||
|
|
34
spec/lib/signature_parser_spec.rb
Normal file
34
spec/lib/signature_parser_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SignatureParser do
|
||||
describe '.parse' do
|
||||
subject { described_class.parse(header) }
|
||||
|
||||
context 'with Signature headers conforming to draft-cavage-http-signatures-12' do
|
||||
let(:header) do
|
||||
# This example signature string deliberately mixes uneven spacing
|
||||
# and quoting styles to ensure everything is covered
|
||||
'keyId = "https://remote.domain/users/bob#main-key,",algorithm= rsa-sha256 , headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength
|
||||
end
|
||||
|
||||
it 'correctly parses the header' do
|
||||
expect(subject).to eq({
|
||||
'keyId' => 'https://remote.domain/users/bob#main-key,',
|
||||
'algorithm' => 'rsa-sha256',
|
||||
'headers' => 'host date digest (request-target)',
|
||||
'signature' => 'gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ==', # rubocop:disable Layout/LineLength
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a malformed Signature header' do
|
||||
let(:header) { 'hello this is malformed!' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(SignatureParser::ParsingError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1246,19 +1246,10 @@ RSpec.describe Account do
|
|||
it 'increments the count in multi-threaded an environment when account_stat is not yet initialized' do
|
||||
subject
|
||||
|
||||
increment_by = 15
|
||||
wait_for_start = true
|
||||
|
||||
threads = Array.new(increment_by) do
|
||||
Thread.new do
|
||||
true while wait_for_start
|
||||
described_class.find(subject.id).increment_count!(:followers_count)
|
||||
end
|
||||
multi_threaded_execution(15) do
|
||||
described_class.find(subject.id).increment_count!(:followers_count)
|
||||
end
|
||||
|
||||
wait_for_start = false
|
||||
threads.each(&:join)
|
||||
|
||||
expect(subject.reload.followers_count).to eq 15
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ describe Account::Counters do
|
|||
let!(:account) { Fabricate(:account) }
|
||||
|
||||
describe '#increment_count!' do
|
||||
let(:increment_by) { 15 }
|
||||
|
||||
it 'increments the count' do
|
||||
expect(account.followers_count).to eq 0
|
||||
account.increment_count!(:followers_count)
|
||||
|
@ -13,24 +15,17 @@ describe Account::Counters do
|
|||
end
|
||||
|
||||
it 'increments the count in multi-threaded an environment' do
|
||||
increment_by = 15
|
||||
wait_for_start = true
|
||||
|
||||
threads = Array.new(increment_by) do
|
||||
Thread.new do
|
||||
true while wait_for_start
|
||||
account.increment_count!(:statuses_count)
|
||||
end
|
||||
multi_threaded_execution(increment_by) do
|
||||
account.increment_count!(:statuses_count)
|
||||
end
|
||||
|
||||
wait_for_start = false
|
||||
threads.each(&:join)
|
||||
|
||||
expect(account.statuses_count).to eq increment_by
|
||||
end
|
||||
end
|
||||
|
||||
describe '#decrement_count!' do
|
||||
let(:decrement_by) { 10 }
|
||||
|
||||
it 'decrements the count' do
|
||||
account.followers_count = 15
|
||||
account.save!
|
||||
|
@ -40,22 +35,13 @@ describe Account::Counters do
|
|||
end
|
||||
|
||||
it 'decrements the count in multi-threaded an environment' do
|
||||
decrement_by = 10
|
||||
wait_for_start = true
|
||||
|
||||
account.statuses_count = 15
|
||||
account.save!
|
||||
|
||||
threads = Array.new(decrement_by) do
|
||||
Thread.new do
|
||||
true while wait_for_start
|
||||
account.decrement_count!(:statuses_count)
|
||||
end
|
||||
multi_threaded_execution(decrement_by) do
|
||||
account.decrement_count!(:statuses_count)
|
||||
end
|
||||
|
||||
wait_for_start = false
|
||||
threads.each(&:join)
|
||||
|
||||
expect(account.statuses_count).to eq 5
|
||||
end
|
||||
end
|
||||
|
|
|
@ -133,5 +133,18 @@ describe Report do
|
|||
report = Fabricate.build(:report, account: remote_account, comment: Faker::Lorem.characters(number: 1001))
|
||||
expect(report.valid?).to be true
|
||||
end
|
||||
|
||||
it 'is invalid if it references invalid rules' do
|
||||
report = Fabricate.build(:report, category: :violation, rule_ids: [-1])
|
||||
expect(report.valid?).to be false
|
||||
expect(report).to model_have_error_on_field(:rule_ids)
|
||||
end
|
||||
|
||||
it 'is invalid if it references rules but category is not "violation"' do
|
||||
rule = Fabricate(:rule)
|
||||
report = Fabricate.build(:report, category: :spam, rule_ids: rule.id)
|
||||
expect(report.valid?).to be false
|
||||
expect(report).to model_have_error_on_field(:rule_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,6 +86,7 @@ RSpec.configure do |config|
|
|||
config.include ActiveSupport::Testing::TimeHelpers
|
||||
config.include Chewy::Rspec::Helpers
|
||||
config.include Redisable
|
||||
config.include ThreadingHelpers
|
||||
config.include SignedRequestHelpers, type: :request
|
||||
config.include CommandLineHelpers, type: :cli
|
||||
|
||||
|
@ -95,8 +96,13 @@ RSpec.configure do |config|
|
|||
self.use_transactional_tests = true
|
||||
end
|
||||
|
||||
config.around(:each, :sidekiq_inline) do |example|
|
||||
Sidekiq::Testing.inline!(&example)
|
||||
config.around do |example|
|
||||
if example.metadata[:sidekiq_inline] == true
|
||||
Sidekiq::Testing.inline!
|
||||
else
|
||||
Sidekiq::Testing.fake!
|
||||
end
|
||||
example.run
|
||||
end
|
||||
|
||||
config.before :each, type: :cli do
|
||||
|
|
|
@ -49,6 +49,7 @@ RSpec.describe 'Domain Blocks' do
|
|||
{
|
||||
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,
|
||||
|
@ -123,7 +124,7 @@ RSpec.describe 'Domain Blocks' do
|
|||
it_behaves_like 'forbidden for wrong role', ''
|
||||
it_behaves_like 'forbidden for wrong role', 'Moderator'
|
||||
|
||||
it 'returns the expected domain block content', :aggregate_failures do # rubocop:disable RSpec/ExampleLength
|
||||
it 'returns the expected domain block content', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
@ -131,6 +132,7 @@ RSpec.describe 'Domain Blocks' do
|
|||
{
|
||||
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,
|
||||
|
@ -232,6 +234,7 @@ RSpec.describe 'Domain Blocks' do
|
|||
{
|
||||
id: domain_block.id.to_s,
|
||||
domain: domain_block.domain,
|
||||
digest: domain_block.domain_digest,
|
||||
severity: 'suspend',
|
||||
}
|
||||
)
|
||||
|
|
|
@ -151,7 +151,9 @@ RSpec.describe 'Reports' do
|
|||
let(:params) { { category: 'spam' } }
|
||||
|
||||
it 'updates the report category', :aggregate_failures do
|
||||
expect { subject }.to change { report.reload.category }.from('other').to('spam')
|
||||
expect { subject }
|
||||
.to change { report.reload.category }.from('other').to('spam')
|
||||
.and create_an_action_log
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
|
@ -184,7 +186,9 @@ RSpec.describe 'Reports' do
|
|||
it_behaves_like 'forbidden for wrong role', ''
|
||||
|
||||
it 'marks report as resolved', :aggregate_failures do
|
||||
expect { subject }.to change { report.reload.unresolved? }.from(true).to(false)
|
||||
expect { subject }
|
||||
.to change { report.reload.unresolved? }.from(true).to(false)
|
||||
.and create_an_action_log
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
@ -200,7 +204,9 @@ RSpec.describe 'Reports' do
|
|||
it_behaves_like 'forbidden for wrong role', ''
|
||||
|
||||
it 'marks report as unresolved', :aggregate_failures do
|
||||
expect { subject }.to change { report.reload.unresolved? }.from(false).to(true)
|
||||
expect { subject }
|
||||
.to change { report.reload.unresolved? }.from(false).to(true)
|
||||
.and create_an_action_log
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
@ -216,7 +222,9 @@ RSpec.describe 'Reports' do
|
|||
it_behaves_like 'forbidden for wrong role', ''
|
||||
|
||||
it 'assigns report to the requesting user', :aggregate_failures do
|
||||
expect { subject }.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id)
|
||||
expect { subject }
|
||||
.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id)
|
||||
.and create_an_action_log
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
@ -232,8 +240,16 @@ RSpec.describe 'Reports' do
|
|||
it_behaves_like 'forbidden for wrong role', ''
|
||||
|
||||
it 'unassigns report from assignee', :aggregate_failures do
|
||||
expect { subject }.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil)
|
||||
expect { subject }
|
||||
.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil)
|
||||
.and create_an_action_log
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_an_action_log
|
||||
change(Admin::ActionLog, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,5 +52,19 @@ RSpec.describe 'API Markers' do
|
|||
expect(user.markers.first.last_read_id).to eq 70_120
|
||||
end
|
||||
end
|
||||
|
||||
context 'when database object becomes stale' do
|
||||
before do
|
||||
allow(Marker).to receive(:transaction).and_raise(ActiveRecord::StaleObjectError)
|
||||
post '/api/v1/markers', headers: headers, params: { home: { last_read_id: '69420' } }
|
||||
end
|
||||
|
||||
it 'returns error json' do
|
||||
expect(response)
|
||||
.to have_http_status(409)
|
||||
expect(body_as_json)
|
||||
.to include(error: /Conflict during update/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,10 +9,28 @@ describe 'Suggestions API' do
|
|||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
describe 'GET /api/v2/suggestions' do
|
||||
it 'returns http success' do
|
||||
let(:bob) { Fabricate(:account) }
|
||||
let(:jeff) { Fabricate(:account) }
|
||||
let(:params) { {} }
|
||||
|
||||
before do
|
||||
Setting.bootstrap_timeline_accounts = [bob, jeff].map(&:acct).join(',')
|
||||
end
|
||||
|
||||
it 'returns the expected suggestions' do
|
||||
get '/api/v2/suggestions', headers: headers
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
expect(body_as_json).to match_array(
|
||||
[bob, jeff].map do |account|
|
||||
hash_including({
|
||||
source: 'staff',
|
||||
sources: ['featured'],
|
||||
account: hash_including({ id: account.id.to_s }),
|
||||
})
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ describe REST::SuggestionSerializer do
|
|||
let(:record) do
|
||||
AccountSuggestions::Suggestion.new(
|
||||
account: account,
|
||||
source: 'SuggestionSource'
|
||||
sources: ['SuggestionSource']
|
||||
)
|
||||
end
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
|
|
@ -245,14 +245,22 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
|
|||
|
||||
it 'parses out of attachment' do
|
||||
account = subject.call('alice', 'example.com', payload)
|
||||
expect(account.fields).to be_a Array
|
||||
expect(account.fields.size).to eq 2
|
||||
expect(account.fields[0]).to be_a Account::Field
|
||||
expect(account.fields[0].name).to eq 'Pronouns'
|
||||
expect(account.fields[0].value).to eq 'They/them'
|
||||
expect(account.fields[1]).to be_a Account::Field
|
||||
expect(account.fields[1].name).to eq 'Occupation'
|
||||
expect(account.fields[1].value).to eq 'Unit test'
|
||||
|
||||
expect(account.fields)
|
||||
.to be_an(Array)
|
||||
.and have_attributes(size: 2)
|
||||
expect(account.fields.first)
|
||||
.to be_an(Account::Field)
|
||||
.and have_attributes(
|
||||
name: eq('Pronouns'),
|
||||
value: eq('They/them')
|
||||
)
|
||||
expect(account.fields.last)
|
||||
.to be_an(Account::Field)
|
||||
.and have_attributes(
|
||||
name: eq('Occupation'),
|
||||
value: eq('Unit test')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -572,13 +572,7 @@ RSpec.describe PostStatusService, type: :service do
|
|||
subject.call(
|
||||
account,
|
||||
text: 'test status update',
|
||||
media_ids: [
|
||||
Fabricate(:media_attachment, account: account),
|
||||
Fabricate(:media_attachment, account: account),
|
||||
Fabricate(:media_attachment, account: account),
|
||||
Fabricate(:media_attachment, account: account),
|
||||
Fabricate(:media_attachment, account: account),
|
||||
].map(&:id)
|
||||
media_ids: Array.new(5) { Fabricate(:media_attachment, account: account) }.map(&:id)
|
||||
)
|
||||
end.to raise_error(
|
||||
Mastodon::ValidationError,
|
||||
|
|
|
@ -223,27 +223,19 @@ RSpec.describe ResolveAccountService, type: :service do
|
|||
end
|
||||
|
||||
it 'processes one remote account at a time using locks' do
|
||||
wait_for_start = true
|
||||
fail_occurred = false
|
||||
return_values = Concurrent::Array.new
|
||||
|
||||
threads = Array.new(5) do
|
||||
Thread.new do
|
||||
true while wait_for_start
|
||||
|
||||
begin
|
||||
return_values << described_class.new.call('foo@ap.example.com')
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
fail_occurred = true
|
||||
ensure
|
||||
RedisConfiguration.pool.checkin if Thread.current[:redis]
|
||||
end
|
||||
multi_threaded_execution(5) do
|
||||
begin
|
||||
return_values << described_class.new.call('foo@ap.example.com')
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
fail_occurred = true
|
||||
ensure
|
||||
RedisConfiguration.pool.checkin if Thread.current[:redis]
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_start = false
|
||||
threads.each(&:join)
|
||||
|
||||
expect(fail_occurred).to be false
|
||||
expect(return_values).to_not include(nil)
|
||||
end
|
||||
|
|
17
spec/support/threading_helpers.rb
Normal file
17
spec/support/threading_helpers.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ThreadingHelpers
|
||||
def multi_threaded_execution(thread_count)
|
||||
wait_for_start = true
|
||||
|
||||
threads = Array.new(thread_count) do
|
||||
Thread.new do
|
||||
true while wait_for_start
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_start = false
|
||||
threads.each(&:join)
|
||||
end
|
||||
end
|
33
spec/system/ocr_spec.rb
Normal file
33
spec/system/ocr_spec.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'OCR', :paperclip_processing, :sidekiq_inline do
|
||||
include ProfileStories
|
||||
|
||||
let(:email) { 'test@example.com' }
|
||||
let(:password) { 'password' }
|
||||
let(:confirmed_at) { Time.zone.now }
|
||||
let(:finished_onboarding) { true }
|
||||
|
||||
before do
|
||||
as_a_logged_in_user
|
||||
visit root_path
|
||||
end
|
||||
|
||||
it 'can recognize text in a media attachment' do
|
||||
expect(page).to have_css('div.app-holder')
|
||||
|
||||
within('.compose-form') do
|
||||
attach_file('file-upload-input', file_fixture('text.png'), make_visible: true)
|
||||
|
||||
within('.compose-form__upload') do
|
||||
click_on('Edit')
|
||||
end
|
||||
end
|
||||
|
||||
click_on('Detect text from picture')
|
||||
|
||||
expect(page).to have_css('#upload-modal__description', text: 'Hello Mastodon')
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ require 'rails_helper'
|
|||
describe EmailMxValidator do
|
||||
describe '#validate' do
|
||||
let(:user) { instance_double(User, email: 'foo@example.com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil)) }
|
||||
let(:resolv_dns_double) { instance_double(Resolv::DNS) }
|
||||
|
||||
context 'with an e-mail domain that is explicitly allowed' do
|
||||
around do |block|
|
||||
|
@ -15,13 +16,7 @@ describe EmailMxValidator do
|
|||
end
|
||||
|
||||
it 'does not add errors if there are no DNS records' do
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver('example.com')
|
||||
|
||||
subject.validate(user)
|
||||
expect(user.errors).to_not have_received(:add)
|
||||
|
@ -29,13 +24,7 @@ describe EmailMxValidator do
|
|||
end
|
||||
|
||||
it 'adds no error if there are DNS records for the e-mail domain' do
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver('example.com', a: resolv_double_a('192.0.2.42'))
|
||||
|
||||
subject.validate(user)
|
||||
expect(user.errors).to_not have_received(:add)
|
||||
|
@ -58,13 +47,7 @@ describe EmailMxValidator do
|
|||
end
|
||||
|
||||
it 'adds an error if the email domain name contains empty labels' do
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::MX).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')])
|
||||
allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver('example..com', a: resolv_double_a('192.0.2.42'))
|
||||
|
||||
user = instance_double(User, email: 'foo@example..com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil))
|
||||
subject.validate(user)
|
||||
|
@ -72,30 +55,15 @@ describe EmailMxValidator do
|
|||
end
|
||||
|
||||
it 'adds an error if there are no DNS records for the e-mail domain' do
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver('example.com')
|
||||
|
||||
subject.validate(user)
|
||||
expect(user.errors).to have_received(:add)
|
||||
end
|
||||
|
||||
it 'adds an error if a MX record does not lead to an IP' do
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources)
|
||||
.with('example.com', Resolv::DNS::Resource::IN::MX)
|
||||
.and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver('example.com', mx: resolv_double_mx('mail.example.com'))
|
||||
configure_resolver('mail.example.com')
|
||||
|
||||
subject.validate(user)
|
||||
expect(user.errors).to have_received(:add)
|
||||
|
@ -103,20 +71,48 @@ describe EmailMxValidator do
|
|||
|
||||
it 'adds an error if the MX record is blacklisted' do
|
||||
EmailDomainBlock.create!(domain: 'mail.example.com')
|
||||
resolver = instance_double(Resolv::DNS)
|
||||
|
||||
allow(resolver).to receive(:getresources)
|
||||
.with('example.com', Resolv::DNS::Resource::IN::MX)
|
||||
.and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([])
|
||||
allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5')])
|
||||
allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([instance_double(Resolv::DNS::Resource::IN::AAAA, address: 'fd00::2')])
|
||||
allow(resolver).to receive(:timeouts=).and_return(nil)
|
||||
allow(Resolv::DNS).to receive(:open).and_yield(resolver)
|
||||
configure_resolver(
|
||||
'example.com',
|
||||
mx: resolv_double_mx('mail.example.com')
|
||||
)
|
||||
configure_resolver(
|
||||
'mail.example.com',
|
||||
a: instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5'),
|
||||
aaaa: instance_double(Resolv::DNS::Resource::IN::AAAA, address: 'fd00::2')
|
||||
)
|
||||
|
||||
subject.validate(user)
|
||||
expect(user.errors).to have_received(:add)
|
||||
end
|
||||
end
|
||||
|
||||
def configure_resolver(domain, options = {})
|
||||
allow(resolv_dns_double)
|
||||
.to receive(:getresources)
|
||||
.with(domain, Resolv::DNS::Resource::IN::MX)
|
||||
.and_return(Array(options[:mx]))
|
||||
allow(resolv_dns_double)
|
||||
.to receive(:getresources)
|
||||
.with(domain, Resolv::DNS::Resource::IN::A)
|
||||
.and_return(Array(options[:a]))
|
||||
allow(resolv_dns_double)
|
||||
.to receive(:getresources)
|
||||
.with(domain, Resolv::DNS::Resource::IN::AAAA)
|
||||
.and_return(Array(options[:aaaa]))
|
||||
allow(resolv_dns_double)
|
||||
.to receive(:timeouts=)
|
||||
.and_return(nil)
|
||||
allow(Resolv::DNS)
|
||||
.to receive(:open)
|
||||
.and_yield(resolv_dns_double)
|
||||
end
|
||||
|
||||
def resolv_double_mx(domain)
|
||||
instance_double(Resolv::DNS::Resource::MX, exchange: domain)
|
||||
end
|
||||
|
||||
def resolv_double_a(domain)
|
||||
Resolv::DNS::Resource::IN::A.new(domain)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue