Merge commit '5fae2de454806730742b7be7435ae1c4fb97cf3c' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2023-06-10 15:17:08 +02:00
28 changed files with 792 additions and 229 deletions

3
spec/fixtures/files/lists.csv vendored Normal file
View File

@ -0,0 +1,3 @@
Mastodon project,gargron@example.com
Mastodon project,mastodon@example.com
test,foo@example.com
1 Mastodon project gargron@example.com
2 Mastodon project mastodon@example.com
3 test foo@example.com

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

@ -662,4 +662,340 @@ describe Mastodon::CLI::Accounts do
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
describe '#rotate' do
context 'when neither username nor --all option are given' do
it 'exits with an error message' do
expect { cli.rotate }.to output(
a_string_including('No account(s) given')
).to_stdout
.and raise_error(SystemExit)
end
end
context 'when a username is given' do
let(:account) { Fabricate(:account) }
it 'correctly rotates keys for the specified account' do
old_private_key = account.private_key
old_public_key = account.public_key
cli.rotate(account.username)
account.reload
expect(account.private_key).to_not eq(old_private_key)
expect(account.public_key).to_not eq(old_public_key)
end
it 'broadcasts the new keys for the specified account' do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
cli.rotate(account.username)
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
end
context 'when the given username is not found' do
it 'exits with an error message when the specified username is not found' do
expect { cli.rotate('non_existent_username') }.to output(
a_string_including('No such account')
).to_stdout
.and raise_error(SystemExit)
end
end
end
context 'when --all option is provided' do
let(:accounts) { Fabricate.times(3, :account) }
let(:options) { { all: true } }
before do
allow(Account).to receive(:local).and_return(Account.where(id: accounts.map(&:id)))
cli.options = { all: true }
end
it 'correctly rotates keys for all local accounts' do
old_private_keys = accounts.map(&:private_key)
old_public_keys = accounts.map(&:public_key)
cli.rotate
accounts.each(&:reload)
expect(accounts.map(&:private_key)).to_not eq(old_private_keys)
expect(accounts.map(&:public_key)).to_not eq(old_public_keys)
end
it 'broadcasts the new keys for each account' do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
cli.rotate
accounts.each do |account|
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
end
end
end
end
end

View File

@ -86,6 +86,7 @@ RSpec.describe Form::Import do
it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1
it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2
it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3
it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2
# Importing list of addresses with no headers into various types
it_behaves_like 'valid import', 'following', 'imports.txt'
@ -98,6 +99,9 @@ RSpec.describe Form::Import do
# Importing bookmarks list with no headers into expected type
it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt'
# Importing lists with no headers into expected type
it_behaves_like 'valid import', 'lists', 'lists.csv'
# Importing followed accounts with headers into various compatible types
it_behaves_like 'valid import', 'following', 'following_accounts.csv'
it_behaves_like 'valid import', 'blocking', 'following_accounts.csv'
@ -273,6 +277,12 @@ RSpec.describe Form::Import do
{ 'acct' => 'user@test.com', 'hide_notifications' => false },
]
it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [
{ 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' },
{ 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' },
{ 'acct' => 'foo@example.com', 'list_name' => 'test' },
]
# Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
#
# https://github.com/mastodon/mastodon/issues/20571

View File

@ -91,5 +91,77 @@ RSpec.describe BulkImportRowService do
end
end
end
context 'when importing a list row' do
let(:import_type) { 'lists' }
let(:target_account) { Fabricate(:account) }
let(:data) do
{ 'acct' => target_account.acct, 'list_name' => 'my list' }
end
shared_examples 'common behavior' do
context 'when the target account is already followed' do
before do
account.follow!(target_account)
end
it 'returns true' do
expect(subject.call(import_row)).to be true
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
end
end
context 'when the user already requested to follow the target account' do
before do
account.request_follow!(target_account)
end
it 'returns true' do
expect(subject.call(import_row)).to be true
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
end
end
context 'when the target account is neither followed nor requested' do
it 'returns true' do
expect(subject.call(import_row)).to be true
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
end
end
context 'when the target account is the user themself' do
let(:target_account) { account }
it 'returns true' do
expect(subject.call(import_row)).to be true
end
it 'adds the target account to the list' do
expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true)
end
end
end
context 'when the list does not exist yet' do
include_examples 'common behavior'
end
context 'when the list exists' do
before do
Fabricate(:list, account: account, title: 'my list')
end
include_examples 'common behavior'
end
end
end
end

View File

@ -12,6 +12,7 @@ RSpec.describe FetchLinkCardService, type: :service do
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404)
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200)
stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
@ -87,6 +88,15 @@ RSpec.describe FetchLinkCardService, type: :service do
expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made
end
end
context do
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
it 'does fetch URLs with a caret in search params' do
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
end
end
end
context 'with a remote status' do