Merge commit 'c15c8c7aa8' into kb_migration

This commit is contained in:
KMY 2023-06-01 17:30:18 +09:00
commit 093cfdbb7b
208 changed files with 2976 additions and 5888 deletions

View file

@ -40,42 +40,135 @@ RSpec.describe Admin::DomainBlocksController do
end
describe 'POST #create' do
it 'blocks the domain when succeeded to save' do
before do
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
expect(DomainBlockWorker).to have_received(:perform_async)
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
expect(response).to redirect_to(admin_instances_path(limited: '1'))
end
it 'renders new when failed to save' do
Fabricate(:domain_block, domain: 'example.com', severity: 'suspend')
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
context 'with "silence" severity and no conflict' do
before do
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
end
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
it 'records a block' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
end
expect(DomainBlockWorker).to_not have_received(:perform_async)
expect(response).to render_template :new
it 'calls DomainBlockWorker' do
expect(DomainBlockWorker).to have_received(:perform_async)
end
it 'redirects with a success message' do
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
expect(response).to redirect_to(admin_instances_path(limited: '1'))
end
end
it 'allows upgrading a block' do
Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
allow(DomainBlockWorker).to receive(:perform_async).and_return(true)
context 'when the new domain block conflicts with an existing one' do
before do
Fabricate(:domain_block, domain: 'example.com', severity: 'suspend')
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence' } }
end
post :create, params: { domain_block: { domain: 'example.com', severity: 'silence', reject_media: true, reject_reports: true } }
it 'does not record a block' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be false
end
expect(DomainBlockWorker).to have_received(:perform_async)
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
expect(response).to redirect_to(admin_instances_path(limited: '1'))
it 'does not call DomainBlockWorker' do
expect(DomainBlockWorker).to_not have_received(:perform_async)
end
it 'renders new' do
expect(response).to render_template :new
end
end
context 'with "suspend" severity and no conflict' do
context 'without a confirmation' do
before do
post :create, params: { domain_block: { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true } }
end
it 'does not record a block' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be false
end
it 'does not call DomainBlockWorker' do
expect(DomainBlockWorker).to_not have_received(:perform_async)
end
it 'renders confirm_suspension' do
expect(response).to render_template :confirm_suspension
end
end
context 'with a confirmation' do
before do
post :create, params: { :domain_block => { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true }, 'confirm' => '' }
end
it 'records a block' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
end
it 'calls DomainBlockWorker' do
expect(DomainBlockWorker).to have_received(:perform_async)
end
it 'redirects with a success message' do
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
expect(response).to redirect_to(admin_instances_path(limited: '1'))
end
end
end
context 'when upgrading an existing block' do
before do
Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
end
context 'without a confirmation' do
before do
post :create, params: { domain_block: { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true } }
end
it 'does not record a block' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be false
end
it 'does not call DomainBlockWorker' do
expect(DomainBlockWorker).to_not have_received(:perform_async)
end
it 'renders confirm_suspension' do
expect(response).to render_template :confirm_suspension
end
end
context 'with a confirmation' do
before do
post :create, params: { :domain_block => { domain: 'example.com', severity: 'suspend', reject_media: true, reject_reports: true }, 'confirm' => '' }
end
it 'updates the record' do
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
end
it 'calls DomainBlockWorker' do
expect(DomainBlockWorker).to have_received(:perform_async)
end
it 'redirects with a success message' do
expect(flash[:notice]).to eq I18n.t('admin.domain_blocks.created_msg')
expect(response).to redirect_to(admin_instances_path(limited: '1'))
end
end
end
end
describe 'PUT #update' do
let!(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:subject) do
post :update, params: { id: domain_block.id, domain_block: { domain: 'example.com', severity: new_severity } }
post :update, params: { :id => domain_block.id, :domain_block => { domain: 'example.com', severity: new_severity }, 'confirm' => '' }
end
let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) }

View file

@ -18,4 +18,37 @@ describe Admin::IpBlocksController do
expect(response).to have_http_status(:success)
end
end
describe 'GET #new' do
it 'returns http success and renders view' do
get :new
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
describe 'POST #create' do
context 'with valid data' do
it 'creates a new ip block and redirects' do
expect do
post :create, params: { ip_block: { ip: '1.1.1.1', severity: 'no_access', expires_in: 1.day.to_i.to_s } }
end.to change(IpBlock, :count).by(1)
expect(response).to redirect_to(admin_ip_blocks_path)
expect(flash.notice).to match(I18n.t('admin.ip_blocks.created_msg'))
end
end
context 'with invalid data' do
it 'does not create new a ip block and renders new' do
expect do
post :create, params: { ip_block: { ip: '1.1.1.1' } }
end.to_not change(IpBlock, :count)
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
end
end

View file

@ -18,4 +18,42 @@ describe Admin::RelaysController do
expect(response).to have_http_status(:success)
end
end
describe 'GET #new' do
it 'returns http success and renders view' do
get :new
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
describe 'POST #create' do
context 'with valid data' do
let(:inbox_url) { 'https://example.com/inbox' }
before do
stub_request(:post, inbox_url).to_return(status: 200)
end
it 'creates a new relay and redirects' do
expect do
post :create, params: { relay: { inbox_url: inbox_url } }
end.to change(Relay, :count).by(1)
expect(response).to redirect_to(admin_relays_path)
end
end
context 'with invalid data' do
it 'does not create new a relay and renders new' do
expect do
post :create, params: { relay: { inbox_url: 'invalid' } }
end.to_not change(Relay, :count)
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
end
end

View file

@ -18,4 +18,68 @@ describe Admin::RulesController do
expect(response).to have_http_status(:success)
end
end
describe 'GET #edit' do
let(:rule) { Fabricate(:rule) }
it 'returns http success and renders edit' do
get :edit, params: { id: rule.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:edit)
end
end
describe 'POST #create' do
context 'with valid data' do
it 'creates a new rule and redirects' do
expect do
post :create, params: { rule: { text: 'The rule text.' } }
end.to change(Rule, :count).by(1)
expect(response).to redirect_to(admin_rules_path)
end
end
context 'with invalid data' do
it 'does creates a new rule and renders index' do
expect do
post :create, params: { rule: { text: '' } }
end.to_not change(Rule, :count)
expect(response).to render_template(:index)
end
end
end
describe 'PUT #update' do
let(:rule) { Fabricate(:rule, text: 'Original text') }
context 'with valid data' do
it 'updates the rule and redirects' do
put :update, params: { id: rule.id, rule: { text: 'Updated text.' } }
expect(response).to redirect_to(admin_rules_path)
end
end
context 'with invalid data' do
it 'does not update the rule and renders index' do
put :update, params: { id: rule.id, rule: { text: '' } }
expect(response).to render_template(:edit)
end
end
end
describe 'DELETE #destroy' do
let!(:rule) { Fabricate(:rule) }
it 'destroys the rule and redirects' do
delete :destroy, params: { id: rule.id }
expect(rule.reload).to be_discarded
expect(response).to redirect_to(admin_rules_path)
end
end
end

View file

@ -18,4 +18,82 @@ describe Admin::WebhooksController do
expect(response).to have_http_status(:success)
end
end
describe 'GET #new' do
it 'returns http success and renders view' do
get :new
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
describe 'POST #create' do
it 'creates a new webhook record with valid data' do
expect do
post :create, params: { webhook: { url: 'https://example.com/hook', events: ['account.approved'] } }
end.to change(Webhook, :count).by(1)
expect(response).to be_redirect
end
it 'does not create a new webhook record with invalid data' do
expect do
post :create, params: { webhook: { url: 'https://example.com/hook', events: [] } }
end.to_not change(Webhook, :count)
expect(response).to have_http_status(:success)
expect(response).to render_template(:new)
end
end
context 'with an existing record' do
let!(:webhook) { Fabricate :webhook }
describe 'GET #show' do
it 'returns http success and renders view' do
get :show, params: { id: webhook.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:show)
end
end
describe 'GET #edit' do
it 'returns http success and renders view' do
get :edit, params: { id: webhook.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:edit)
end
end
describe 'PUT #update' do
it 'updates the record with valid data' do
put :update, params: { id: webhook.id, webhook: { url: 'https://example.com/new/location' } }
expect(webhook.reload.url).to match(%r{new/location})
expect(response).to redirect_to(admin_webhook_path(webhook))
end
it 'does not update the record with invalid data' do
expect do
put :update, params: { id: webhook.id, webhook: { url: '' } }
end.to_not change(webhook, :url)
expect(response).to have_http_status(:success)
expect(response).to render_template(:show)
end
end
describe 'DELETE #destroy' do
it 'destroys the record' do
expect do
delete :destroy, params: { id: webhook.id }
end.to change(Webhook, :count).by(-1)
expect(response).to redirect_to(admin_webhooks_path)
end
end
end
end

View file

@ -19,7 +19,7 @@ describe Api::V1::Statuses::TranslationsController do
before do
translation = TranslationService::Translation.new(text: 'Hello')
service = instance_double(TranslationService::DeepL, translate: translation)
service = instance_double(TranslationService::DeepL, translate: [translation])
allow(TranslationService).to receive(:configured?).and_return(true)
allow(TranslationService).to receive(:configured).and_return(service)
Rails.cache.write('translation_service/languages', { 'es' => ['en'] })

View file

@ -0,0 +1,78 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'blocking domains through the moderation interface' do
before do
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user
end
context 'when silencing a new domain' 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')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true
end
end
context 'when suspending a new domain' 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')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming creates a block
click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true
end
end
context 'when suspending a domain that is already silenced' do
it 'presents a confirmation screen before suspending the domain' do
domain_block = Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
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')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming updates the block
click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(domain_block.reload.severity).to eq 'silence'
end
end
context 'when editing a domain block' do
it 'presents a confirmation screen before suspending the domain' do
domain_block = Fabricate(:domain_block, domain: 'example.com', severity: 'silence')
visit edit_admin_domain_block_path(domain_block)
select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity'
click_on I18n.t('generic.save_changes')
# It presents a confirmation screen
expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com'))
# Confirming updates the block
click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm')
expect(domain_block.reload.severity).to eq 'silence'
end
end
end

View file

@ -208,6 +208,18 @@ describe ApplicationHelper do
end
end
context 'when S3 alias includes a path component' do
around do |example|
ClimateControl.modify S3_ALIAS_HOST: 's3.alias/path' do
example.run
end
end
it 'returns a correct URL' do
expect(helper.storage_host).to eq('https://s3.alias/path')
end
end
context 'when S3 cloudfront is present' do
around do |example|
ClimateControl.modify S3_CLOUDFRONT_HOST: 's3.cloudfront' do
@ -219,12 +231,6 @@ describe ApplicationHelper do
expect(helper.storage_host).to eq('https://s3.cloudfront')
end
end
context 'when neither env value is present' do
it 'returns false' do
expect(helper.storage_host).to eq('https:')
end
end
end
describe 'storage_host?' do

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceAccountsMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
before do
Fabricate(:account, domain: domain, created_at: 1.year.ago)
Fabricate(:account, domain: domain, created_at: 1.month.ago)
Fabricate(:account, domain: domain)
Fabricate(:account, domain: "foo.#{domain}", created_at: 1.year.ago)
Fabricate(:account, domain: "foo.#{domain}")
Fabricate(:account, domain: "bar.#{domain}")
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expect(measure.total).to eq 3
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expect(measure.total).to eq 6
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceFollowersMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
before do
local_account = Fabricate(:account)
Fabricate(:account, domain: domain).follow!(local_account)
Fabricate(:account, domain: domain).follow!(local_account)
Fabricate(:account, domain: domain)
Fabricate(:account, domain: "foo.#{domain}").follow!(local_account)
Fabricate(:account, domain: "foo.#{domain}").follow!(local_account)
Fabricate(:account, domain: "bar.#{domain}")
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expect(measure.total).to eq 2
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expect(measure.total).to eq 4
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceFollowsMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
before do
local_account = Fabricate(:account)
local_account.follow!(Fabricate(:account, domain: domain))
local_account.follow!(Fabricate(:account, domain: domain))
Fabricate(:account, domain: domain)
local_account.follow!(Fabricate(:account, domain: "foo.#{domain}"))
local_account.follow!(Fabricate(:account, domain: "foo.#{domain}"))
Fabricate(:account, domain: "bar.#{domain}")
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expect(measure.total).to eq 2
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expect(measure.total).to eq 4
end
end
end
end

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
let(:remote_account) { Fabricate(:account, domain: domain) }
let(:remote_account_on_subdomain) { Fabricate(:account, domain: "foo.#{domain}") }
before do
remote_account.media_attachments.create!(file: attachment_fixture('attachment.jpg'))
remote_account_on_subdomain.media_attachments.create!(file: attachment_fixture('attachment.jpg'))
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expected_total = remote_account.media_attachments.sum(:file_file_size) + remote_account.media_attachments.sum(:thumbnail_file_size)
expect(measure.total).to eq expected_total
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expected_total = [remote_account, remote_account_on_subdomain].sum do |account|
account.media_attachments.sum(:file_file_size) + account.media_attachments.sum(:thumbnail_file_size)
end
expect(measure.total).to eq expected_total
end
end
end
end

View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceReportsMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
before do
Fabricate(:report, target_account: Fabricate(:account, domain: domain))
Fabricate(:report, target_account: Fabricate(:account, domain: domain))
Fabricate(:report, target_account: Fabricate(:account, domain: "foo.#{domain}"))
Fabricate(:report, target_account: Fabricate(:account, domain: "foo.#{domain}"))
Fabricate(:report, target_account: Fabricate(:account, domain: "bar.#{domain}"))
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expect(measure.total).to eq 2
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expect(measure.total).to eq 5
end
end
end
end

View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::Metrics::Measure::InstanceStatusesMeasure do
subject(:measure) { described_class.new(start_at, end_at, params) }
let(:domain) { 'example.com' }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new(domain: domain) }
before do
Fabricate(:status, account: Fabricate(:account, domain: domain))
Fabricate(:status, account: Fabricate(:account, domain: domain))
Fabricate(:status, account: Fabricate(:account, domain: "foo.#{domain}"))
Fabricate(:status, account: Fabricate(:account, domain: "foo.#{domain}"))
Fabricate(:status, account: Fabricate(:account, domain: "bar.#{domain}"))
end
describe 'total' do
context 'without include_subdomains' do
it 'returns the expected number of accounts' do
expect(measure.total).to eq 2
end
end
context 'with include_subdomains' do
let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') }
it 'returns the expected number of accounts' do
expect(measure.total).to eq 5
end
end
end
end

View file

@ -1,9 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe HashObject do
it 'has methods corresponding to hash properties' do
expect(HashObject.new(key: 'value').key).to eq 'value'
end
end

View file

@ -577,4 +577,351 @@ describe Mastodon::CLI::Accounts do
end
end
end
describe '#unfollow' do
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that no account with the given username was found' do
expect { cli.invoke(:unfollow, arguments) }.to output(
a_string_including('No such account')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'when the given username is found' do
let!(:target_account) { Fabricate(:account) }
let!(:follower_chris) { Fabricate(:account, username: 'chris') }
let!(:follower_rambo) { Fabricate(:account, username: 'rambo') }
let!(:follower_ana) { Fabricate(:account, username: 'ana') }
let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
let(:scope) { target_account.followers.local }
before do
accounts = [follower_chris, follower_rambo, follower_ana]
accounts.each { |account| target_account.follow!(account) }
allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris)
.and_yield(follower_rambo)
.and_yield(follower_ana)
.and_return([3, nil])
allow(UnfollowService).to receive(:new).and_return(unfollow_service)
end
it 'makes all local accounts unfollow the target account' do
cli.unfollow(target_account.username)
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once
expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once
expect(unfollow_service).to have_received(:call).with(follower_ana, target_account).once
end
it 'displays a successful message' do
expect { cli.unfollow(target_account.username) }.to output(
a_string_including('OK, unfollowed target from 3 accounts')
).to_stdout
end
end
end
describe '#backup' do
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that there is no such account' do
expect { cli.invoke(:backup, arguments) }.to output(
a_string_including('No user with such username')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'when the given username is found' do
let(:account) { Fabricate(:account) }
let(:user) { account.user }
let(:arguments) { [account.username] }
it 'creates a new backup for the specified user' do
expect { cli.invoke(:backup, arguments) }.to change { user.backups.count }.by(1)
end
it 'creates a backup job' do
allow(BackupWorker).to receive(:perform_async)
cli.invoke(:backup, arguments)
latest_backup = user.backups.last
expect(BackupWorker).to have_received(:perform_async).with(latest_backup.id).once
end
it 'displays a successful message' do
expect { cli.invoke(:backup, arguments) }.to output(
a_string_including('OK')
).to_stdout
end
end
end
describe '#refresh' do
context 'with --all option' do
let!(:local_account) { Fabricate(:account, domain: nil) }
let!(:remote_account_example_com) { Fabricate(:account, domain: 'example.com') }
let!(:account_example_net) { Fabricate(:account, domain: 'example.net') }
let(:scope) { Account.remote }
before do
allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com)
.and_yield(account_example_net)
.and_return([2, nil])
cli.options = { all: true }
end
it 'refreshes the avatar for all remote accounts' do
allow(remote_account_example_com).to receive(:reset_avatar!)
allow(account_example_net).to receive(:reset_avatar!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(remote_account_example_com).to have_received(:reset_avatar!).once
expect(account_example_net).to have_received(:reset_avatar!).once
end
it 'does not refresh avatar for local accounts' do
allow(local_account).to receive(:reset_avatar!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(local_account).to_not have_received(:reset_avatar!)
end
it 'refreshes the header for all remote accounts' do
allow(remote_account_example_com).to receive(:reset_header!)
allow(account_example_net).to receive(:reset_header!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(remote_account_example_com).to have_received(:reset_header!).once
expect(account_example_net).to have_received(:reset_header!).once
end
it 'does not refresh the header for local accounts' do
allow(local_account).to receive(:reset_header!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(local_account).to_not have_received(:reset_header!)
end
it 'displays a successful message' do
expect { cli.refresh }.to output(
a_string_including('Refreshed 2 accounts')
).to_stdout
end
context 'with --dry-run option' do
before do
cli.options = { all: true, dry_run: true }
end
it 'does not refresh the avatar for any account' do
allow(local_account).to receive(:reset_avatar!)
allow(remote_account_example_com).to receive(:reset_avatar!)
allow(account_example_net).to receive(:reset_avatar!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(local_account).to_not have_received(:reset_avatar!)
expect(remote_account_example_com).to_not have_received(:reset_avatar!)
expect(account_example_net).to_not have_received(:reset_avatar!)
end
it 'does not refresh the header for any account' do
allow(local_account).to receive(:reset_header!)
allow(remote_account_example_com).to receive(:reset_header!)
allow(account_example_net).to receive(:reset_header!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(local_account).to_not have_received(:reset_header!)
expect(remote_account_example_com).to_not have_received(:reset_header!)
expect(account_example_net).to_not have_received(:reset_header!)
end
it 'displays a successful message with (DRY RUN)' do
expect { cli.refresh }.to output(
a_string_including('Refreshed 2 accounts (DRY RUN)')
).to_stdout
end
end
end
context 'with a list of accts' do
let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') }
let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') }
let!(:account_example_net) { Fabricate(:account, domain: 'example.net') }
let(:arguments) { [account_example_com_a.acct, account_example_com_b.acct] }
before do
allow(Account).to receive(:find_remote).with(account_example_com_a.username, account_example_com_a.domain).and_return(account_example_com_a)
allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(account_example_com_b)
allow(Account).to receive(:find_remote).with(account_example_net.username, account_example_net.domain).and_return(account_example_net)
end
it 'resets the avatar for the specified accounts' do
allow(account_example_com_a).to receive(:reset_avatar!)
allow(account_example_com_b).to receive(:reset_avatar!)
cli.refresh(*arguments)
expect(account_example_com_a).to have_received(:reset_avatar!).once
expect(account_example_com_b).to have_received(:reset_avatar!).once
end
it 'does not reset the avatar for unspecified accounts' do
allow(account_example_net).to receive(:reset_avatar!)
cli.refresh(*arguments)
expect(account_example_net).to_not have_received(:reset_avatar!)
end
it 'resets the header for the specified accounts' do
allow(account_example_com_a).to receive(:reset_header!)
allow(account_example_com_b).to receive(:reset_header!)
cli.refresh(*arguments)
expect(account_example_com_a).to have_received(:reset_header!).once
expect(account_example_com_b).to have_received(:reset_header!).once
end
it 'does not reset the header for unspecified accounts' do
allow(account_example_net).to receive(:reset_header!)
cli.refresh(*arguments)
expect(account_example_net).to_not have_received(:reset_header!)
end
context 'when an UnexpectedResponseError is raised' do
it 'displays a failure message' do
allow(account_example_com_a).to receive(:reset_avatar!).and_raise(Mastodon::UnexpectedResponseError)
expect { cli.refresh(*arguments) }
.to output(
a_string_including("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}")
).to_stdout
end
end
context 'when a specified account is not found' do
it 'exits with an error message' do
allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil)
expect { cli.refresh(*arguments) }.to output(
a_string_including('No such account')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'with --dry-run option' do
before do
cli.options = { dry_run: true }
end
it 'does not refresh the avatar for any account' do
allow(account_example_com_a).to receive(:reset_avatar!)
allow(account_example_com_b).to receive(:reset_avatar!)
cli.refresh(*arguments)
expect(account_example_com_a).to_not have_received(:reset_avatar!)
expect(account_example_com_b).to_not have_received(:reset_avatar!)
end
it 'does not refresh the header for any account' do
allow(account_example_com_a).to receive(:reset_header!)
allow(account_example_com_b).to receive(:reset_header!)
cli.refresh(*arguments)
expect(account_example_com_a).to_not have_received(:reset_header!)
expect(account_example_com_b).to_not have_received(:reset_header!)
end
end
end
context 'with --domain option' do
let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') }
let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') }
let!(:account_example_net) { Fabricate(:account, domain: 'example.net') }
let(:domain) { 'example.com' }
let(:scope) { Account.remote.where(domain: domain) }
before do
allow(cli).to receive(:parallelize_with_progress).and_yield(account_example_com_a)
.and_yield(account_example_com_b)
.and_return([2, nil])
cli.options = { domain: domain }
end
it 'refreshes the avatar for all accounts on specified domain' do
allow(account_example_com_a).to receive(:reset_avatar!)
allow(account_example_com_b).to receive(:reset_avatar!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(account_example_com_a).to have_received(:reset_avatar!).once
expect(account_example_com_b).to have_received(:reset_avatar!).once
end
it 'does not refresh the avatar for accounts outside specified domain' do
allow(account_example_net).to receive(:reset_avatar!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(account_example_net).to_not have_received(:reset_avatar!)
end
it 'refreshes the header for all accounts on specified domain' do
allow(account_example_com_a).to receive(:reset_header!)
allow(account_example_com_b).to receive(:reset_header!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope)
expect(account_example_com_a).to have_received(:reset_header!).once
expect(account_example_com_b).to have_received(:reset_header!).once
end
it 'does not refresh the header for accounts outside specified domain' do
allow(account_example_net).to receive(:reset_header!)
cli.refresh
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
expect(account_example_net).to_not have_received(:reset_header!)
end
end
context 'when neither a list of accts nor options are provided' do
it 'exits with an error message' do
expect { cli.refresh }.to output(
a_string_including('No account(s) given')
).to_stdout
.and raise_error(SystemExit)
end
end
end
end

View file

@ -22,7 +22,10 @@ RSpec.describe TranslationService::DeepL do
.with(body: 'text=Hasta+la+vista&source_lang=ES&target_lang=en&tag_handling=html')
.to_return(body: '{"translations":[{"detected_source_language":"ES","text":"See you soon"}]}')
translation = service.translate('Hasta la vista', 'es', 'en')
translations = service.translate(['Hasta la vista'], 'es', 'en')
expect(translations.size).to eq 1
translation = translations.first
expect(translation.detected_source_language).to eq 'es'
expect(translation.provider).to eq 'DeepL.com'
expect(translation.text).to eq 'See you soon'
@ -31,12 +34,27 @@ RSpec.describe TranslationService::DeepL do
it 'returns translation with auto-detected source language' do
stub_request(:post, 'https://api.deepl.com/v2/translate')
.with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html')
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good Morning"}]}')
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"}]}')
translation = service.translate('Guten Tag', nil, 'en')
translations = service.translate(['Guten Tag'], nil, 'en')
expect(translations.size).to eq 1
translation = translations.first
expect(translation.detected_source_language).to eq 'de'
expect(translation.provider).to eq 'DeepL.com'
expect(translation.text).to eq 'Good Morning'
expect(translation.text).to eq 'Good morning'
end
it 'returns translation of multiple texts' do
stub_request(:post, 'https://api.deepl.com/v2/translate')
.with(body: 'text=Guten+Morgen&text=Gute+Nacht&source_lang=DE&target_lang=en&tag_handling=html')
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"},{"detected_source_language":"DE","text":"Good night"}]}')
translations = service.translate(['Guten Morgen', 'Gute Nacht'], 'de', 'en')
expect(translations.size).to eq 2
expect(translations.first.text).to eq 'Good morning'
expect(translations.last.text).to eq 'Good night'
end
end

View file

@ -31,24 +31,42 @@ RSpec.describe TranslationService::LibreTranslate do
describe '#translate' do
it 'returns translation with specified source language' do
stub_request(:post, 'https://libretranslate.example.com/translate')
.with(body: '{"q":"Hasta la vista","source":"es","target":"en","format":"html","api_key":"my-api-key"}')
.to_return(body: '{"translatedText": "See you"}')
.with(body: '{"q":["Hasta la vista"],"source":"es","target":"en","format":"html","api_key":"my-api-key"}')
.to_return(body: '{"translatedText": ["See you"]}')
translation = service.translate('Hasta la vista', 'es', 'en')
expect(translation.detected_source_language).to eq 'es'
translations = service.translate(['Hasta la vista'], 'es', 'en')
expect(translations.size).to eq 1
translation = translations.first
expect(translation.detected_source_language).to be 'es'
expect(translation.provider).to eq 'LibreTranslate'
expect(translation.text).to eq 'See you'
end
it 'returns translation with auto-detected source language' do
stub_request(:post, 'https://libretranslate.example.com/translate')
.with(body: '{"q":"Guten Morgen","source":"auto","target":"en","format":"html","api_key":"my-api-key"}')
.to_return(body: '{"detectedLanguage":{"confidence":92,"language":"de"},"translatedText":"Good morning"}')
.with(body: '{"q":["Guten Morgen"],"source":"auto","target":"en","format":"html","api_key":"my-api-key"}')
.to_return(body: '{"detectedLanguage": [{"confidence": 92, "language": "de"}], "translatedText": ["Good morning"]}')
translation = service.translate('Guten Morgen', nil, 'en')
expect(translation.detected_source_language).to be_nil
translations = service.translate(['Guten Morgen'], nil, 'en')
expect(translations.size).to eq 1
translation = translations.first
expect(translation.detected_source_language).to eq 'de'
expect(translation.provider).to eq 'LibreTranslate'
expect(translation.text).to eq 'Good morning'
end
it 'returns translation of multiple texts' do
stub_request(:post, 'https://libretranslate.example.com/translate')
.with(body: '{"q":["Guten Morgen","Gute Nacht"],"source":"de","target":"en","format":"html","api_key":"my-api-key"}')
.to_return(body: '{"translatedText": ["Good morning", "Good night"]}')
translations = service.translate(['Guten Morgen', 'Gute Nacht'], 'de', 'en')
expect(translations.size).to eq 2
expect(translations.first.text).to eq 'Good morning'
expect(translations.last.text).to eq 'Good night'
end
end
end

View file

@ -62,6 +62,10 @@ RSpec.configure do |config|
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata|
metadata[:type] = :cli
end
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :helper
config.include Devise::Test::ControllerHelpers, type: :view
@ -73,6 +77,10 @@ RSpec.configure do |config|
config.include Redisable
config.include SignedRequestHelpers, type: :request
config.before :each, type: :cli do
stub_stdout
end
config.before :each, type: :feature do
https = ENV['LOCAL_HTTPS'] == 'true'
Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}"
@ -106,6 +114,10 @@ def attachment_fixture(name)
Rails.root.join('spec', 'fixtures', 'files', name).open
end
def stub_stdout
allow($stdout).to receive(:write)
end
def stub_jsonld_contexts!
stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt'))
stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt'))

View file

@ -0,0 +1,226 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TranslateStatusService, type: :service do
subject(:service) { described_class.new }
let(:status) { Fabricate(:status, text: text, spoiler_text: spoiler_text, language: 'en', preloadable_poll: poll, media_attachments: media_attachments) }
let(:text) { 'Hello' }
let(:spoiler_text) { '' }
let(:poll) { nil }
let(:media_attachments) { [] }
before do
Fabricate(:custom_emoji, shortcode: 'highfive')
end
describe '#call' do
before do
translation_service = TranslationService.new
allow(translation_service).to receive(:languages).and_return({ 'en' => ['es'] })
allow(translation_service).to receive(:translate) do |texts|
texts.map do |text|
TranslationService::Translation.new(
text: text.gsub('Hello', 'Hola').gsub('higfive', 'cincoaltos'),
detected_source_language: 'en',
provider: 'Dummy'
)
end
end
allow(TranslationService).to receive(:configured?).and_return(true)
allow(TranslationService).to receive(:configured).and_return(translation_service)
end
it 'returns translated status content' do
expect(service.call(status, 'es').content).to eq '<p>Hola</p>'
end
it 'returns source language' do
expect(service.call(status, 'es').detected_source_language).to eq 'en'
end
it 'returns translation provider' do
expect(service.call(status, 'es').provider).to eq 'Dummy'
end
it 'returns original status' do
expect(service.call(status, 'es').status).to eq status
end
describe 'status has content with custom emoji' do
let(:text) { 'Hello & :highfive:' }
it 'does not translate shortcode' do
expect(service.call(status, 'es').content).to eq '<p>Hola &amp; :highfive:</p>'
end
end
describe 'status has no spoiler_text' do
it 'returns an empty string' do
expect(service.call(status, 'es').spoiler_text).to eq ''
end
end
describe 'status has spoiler_text' do
let(:spoiler_text) { 'Hello & Hello!' }
it 'translates the spoiler text' do
expect(service.call(status, 'es').spoiler_text).to eq 'Hola & Hola!'
end
end
describe 'status has spoiler_text with custom emoji' do
let(:spoiler_text) { 'Hello :highfive:' }
it 'does not translate shortcode' do
expect(service.call(status, 'es').spoiler_text).to eq 'Hola :highfive:'
end
end
describe 'status has spoiler_text with unmatched custom emoji' do
let(:spoiler_text) { 'Hello :Hello:' }
it 'translates the invalid shortcode' do
expect(service.call(status, 'es').spoiler_text).to eq 'Hola :Hola:'
end
end
describe 'status has poll' do
let(:poll) { Fabricate(:poll, options: ['Hello 1', 'Hello 2']) }
it 'translates the poll option title' do
status_translation = service.call(status, 'es')
expect(status_translation.poll_options.size).to eq 2
expect(status_translation.poll_options.first.title).to eq 'Hola 1'
end
end
describe 'status has media attachment' do
let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello & :highfive:')] }
it 'translates the media attachment description' do
status_translation = service.call(status, 'es')
media_attachment = status_translation.media_attachments.first
expect(media_attachment.id).to eq media_attachments.first.id
expect(media_attachment.description).to eq 'Hola & :highfive:'
end
end
end
describe '#source_texts' do
before do
service.instance_variable_set(:@status, status)
end
describe 'status only has content' do
it 'returns formatted content' do
expect(service.send(:source_texts)).to eq({ content: '<p>Hello</p>' })
end
end
describe 'status content contains custom emoji' do
let(:status) { Fabricate(:status, text: 'Hello :highfive:') }
it 'returns formatted content' do
source_texts = service.send(:source_texts)
expect(source_texts[:content]).to eq '<p>Hello <span translate="no">:highfive:</span></p>'
end
end
describe 'status content contains tags' do
let(:status) { Fabricate(:status, text: 'Hello #hola') }
it 'returns formatted content' do
source_texts = service.send(:source_texts)
expect(source_texts[:content]).to include '<p>Hello <a'
expect(source_texts[:content]).to include '/tags/hola'
end
end
describe 'status has spoiler text' do
let(:status) { Fabricate(:status, spoiler_text: 'Hello :highfive:') }
it 'returns formatted spoiler text' do
source_texts = service.send(:source_texts)
expect(source_texts[:spoiler_text]).to eq 'Hello <span translate="no">:highfive:</span>'
end
end
describe 'status has poll' do
let(:poll) { Fabricate(:poll, options: %w(Blue Green)) }
it 'returns formatted poll options' do
source_texts = service.send(:source_texts)
expect(source_texts.size).to eq 3
expect(source_texts.values).to eq %w(<p>Hello</p> Blue Green)
expect(source_texts.keys.first).to eq :content
option1 = source_texts.keys.second
expect(option1).to be_a Poll::Option
expect(option1.id).to eq '0'
expect(option1.title).to eq 'Blue'
option2 = source_texts.keys.third
expect(option2).to be_a Poll::Option
expect(option2.id).to eq '1'
expect(option2.title).to eq 'Green'
end
end
describe 'status has poll with custom emoji' do
let(:poll) { Fabricate(:poll, options: ['Blue', 'Green :highfive:']) }
it 'returns formatted poll options' do
html = service.send(:source_texts).values.last
expect(html).to eq 'Green <span translate="no">:highfive:</span>'
end
end
describe 'status has media attachments' do
let(:text) { '' }
let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello :highfive:')] }
it 'returns media attachments without custom emoji rendering' do
source_texts = service.send(:source_texts)
expect(source_texts.size).to eq 1
key, text = source_texts.first
expect(key).to eq media_attachments.first
expect(text).to eq 'Hello :highfive:'
end
end
end
describe '#wrap_emoji_shortcodes' do
before do
service.instance_variable_set(:@status, status)
end
describe 'string contains custom emoji' do
let(:text) { ':highfive:' }
it 'renders the emoji' do
html = service.send(:wrap_emoji_shortcodes, 'Hello :highfive:'.html_safe)
expect(html).to eq 'Hello <span translate="no">:highfive:</span>'
end
end
end
describe '#unwrap_emoji_shortcodes' do
describe 'string contains custom emoji' do
it 'inserts the shortcode' do
fragment = service.send(:unwrap_emoji_shortcodes, '<p>Hello <span translate="no">:highfive:</span>!</p>')
expect(fragment.to_html).to eq '<p>Hello :highfive:!</p>'
end
it 'preserves other attributes than translate=no' do
fragment = service.send(:unwrap_emoji_shortcodes, '<p>Hello <span translate="no" class="foo">:highfive:</span>!</p>')
expect(fragment.to_html).to eq '<p>Hello <span class="foo">:highfive:</span>!</p>'
end
end
end
end