1
0
Fork 0
forked from gitea/nas

Merge remote-tracking branch 'parent/main' into upstream-20230209

This commit is contained in:
KMY 2024-02-09 12:53:59 +09:00
commit 05e52a09a8
188 changed files with 2810 additions and 1295 deletions

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

@ -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',
}
)

View file

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

View file

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

View file

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

View file

@ -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) }

View file

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

View file

@ -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,

View file

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

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

View file

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