mirror of
https://github.com/mastodon/mastodon.git
synced 2024-12-30 04:14:36 +00:00
440eb71310
* Change unapproved and unconfirmed account to not be accessible in the REST API * Change Account#searchable? to reject unconfirmed and unapproved users * Disable search for unapproved and unconfirmed users in Account.search_for * Disable search for unapproved and unconfirmed users in Account.advanced_search_for * Remove unconfirmed and unapproved accounts from Account.searchable scope * Prevent mentions to unapproved/unconfirmed accounts * Fix some old tests for Account.advanced_search_for * Add some Account.advanced_search_for tests for existing behaviors * Add some tests for Account.search_for * Add Account.advanced_search_for tests unconfirmed and unapproved accounts * Add Account.searchable tests * Fix Account.without_unapproved scope potentially messing with previously-applied scopes * Allow lookup of unconfirmed/unapproved accounts through /api/v1/accounts/lookup This is so that the API can still be used to check whether an username is free to use.
997 lines
31 KiB
Ruby
997 lines
31 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe Account, type: :model do
|
|
context do
|
|
let(:bob) { Fabricate(:account, username: 'bob') }
|
|
subject { Fabricate(:account) }
|
|
|
|
describe '#suspend!' do
|
|
it 'marks the account as suspended' do
|
|
subject.suspend!
|
|
expect(subject.suspended?).to be true
|
|
end
|
|
|
|
it 'creates a deletion request' do
|
|
subject.suspend!
|
|
expect(AccountDeletionRequest.where(account: subject).exists?).to be true
|
|
end
|
|
|
|
context 'when the account is of a local user' do
|
|
let!(:subject) { Fabricate(:user, email: 'foo+bar@domain.org').account }
|
|
|
|
it 'creates a canonical domain block' do
|
|
subject.suspend!
|
|
expect(CanonicalEmailBlock.block?(subject.user_email)).to be true
|
|
end
|
|
|
|
context 'when a canonical domain block already exists for that email' do
|
|
before do
|
|
Fabricate(:canonical_email_block, email: subject.user_email)
|
|
end
|
|
|
|
it 'does not raise an error' do
|
|
expect { subject.suspend! }.not_to raise_error
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#follow!' do
|
|
it 'creates a follow' do
|
|
follow = subject.follow!(bob)
|
|
|
|
expect(follow).to be_instance_of Follow
|
|
expect(follow.account).to eq subject
|
|
expect(follow.target_account).to eq bob
|
|
end
|
|
end
|
|
|
|
describe '#unfollow!' do
|
|
before do
|
|
subject.follow!(bob)
|
|
end
|
|
|
|
it 'destroys a follow' do
|
|
unfollow = subject.unfollow!(bob)
|
|
|
|
expect(unfollow).to be_instance_of Follow
|
|
expect(unfollow.account).to eq subject
|
|
expect(unfollow.target_account).to eq bob
|
|
expect(unfollow.destroyed?).to be true
|
|
end
|
|
end
|
|
|
|
describe '#following?' do
|
|
it 'returns true when the target is followed' do
|
|
subject.follow!(bob)
|
|
expect(subject.following?(bob)).to be true
|
|
end
|
|
|
|
it 'returns false if the target is not followed' do
|
|
expect(subject.following?(bob)).to be false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#local?' do
|
|
it 'returns true when the account is local' do
|
|
account = Fabricate(:account, domain: nil)
|
|
expect(account.local?).to be true
|
|
end
|
|
|
|
it 'returns false when the account is on a different domain' do
|
|
account = Fabricate(:account, domain: 'foreign.tld')
|
|
expect(account.local?).to be false
|
|
end
|
|
end
|
|
|
|
describe 'Local domain user methods' do
|
|
around do |example|
|
|
before = Rails.configuration.x.local_domain
|
|
example.run
|
|
Rails.configuration.x.local_domain = before
|
|
end
|
|
|
|
subject { Fabricate(:account, domain: nil, username: 'alice') }
|
|
|
|
describe '#to_webfinger_s' do
|
|
it 'returns a webfinger string for the account' do
|
|
Rails.configuration.x.local_domain = 'example.com'
|
|
|
|
expect(subject.to_webfinger_s).to eq 'acct:alice@example.com'
|
|
end
|
|
end
|
|
|
|
describe '#local_username_and_domain' do
|
|
it 'returns the username and local domain for the account' do
|
|
Rails.configuration.x.local_domain = 'example.com'
|
|
|
|
expect(subject.local_username_and_domain).to eq 'alice@example.com'
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#acct' do
|
|
it 'returns username for local users' do
|
|
account = Fabricate(:account, domain: nil, username: 'alice')
|
|
expect(account.acct).to eql 'alice'
|
|
end
|
|
|
|
it 'returns username@domain for foreign users' do
|
|
account = Fabricate(:account, domain: 'foreign.tld', username: 'alice')
|
|
expect(account.acct).to eql 'alice@foreign.tld'
|
|
end
|
|
end
|
|
|
|
describe '#save_with_optional_media!' do
|
|
before do
|
|
stub_request(:get, 'https://remote.test/valid_avatar').to_return(request_fixture('avatar.txt'))
|
|
stub_request(:get, 'https://remote.test/invalid_avatar').to_return(request_fixture('feed.txt'))
|
|
end
|
|
|
|
let(:account) do
|
|
Fabricate(:account,
|
|
avatar_remote_url: 'https://remote.test/valid_avatar',
|
|
header_remote_url: 'https://remote.test/valid_avatar')
|
|
end
|
|
|
|
let!(:expectation) { account.dup }
|
|
|
|
context 'with valid properties' do
|
|
before do
|
|
account.save_with_optional_media!
|
|
end
|
|
|
|
it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do
|
|
expect(account.avatar_remote_url).to eq expectation.avatar_remote_url
|
|
expect(account.header_remote_url).to eq expectation.header_remote_url
|
|
expect(account.avatar_file_name).to eq expectation.avatar_file_name
|
|
expect(account.header_file_name).to eq expectation.header_file_name
|
|
end
|
|
end
|
|
|
|
context 'with invalid properties' do
|
|
before do
|
|
account.avatar_remote_url = 'https://remote.test/invalid_avatar'
|
|
account.save_with_optional_media!
|
|
end
|
|
|
|
it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
|
|
expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar'
|
|
expect(account.header_remote_url).to eq expectation.header_remote_url
|
|
expect(account.avatar_file_name).to eq nil
|
|
expect(account.header_file_name).to eq nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#possibly_stale?' do
|
|
let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) }
|
|
|
|
context 'last_webfingered_at is nil' do
|
|
let(:last_webfingered_at) { nil }
|
|
|
|
it 'returns true' do
|
|
expect(account.possibly_stale?).to be true
|
|
end
|
|
end
|
|
|
|
context 'last_webfingered_at is more than 24 hours before' do
|
|
let(:last_webfingered_at) { 25.hours.ago }
|
|
|
|
it 'returns true' do
|
|
expect(account.possibly_stale?).to be true
|
|
end
|
|
end
|
|
|
|
context 'last_webfingered_at is less than 24 hours before' do
|
|
let(:last_webfingered_at) { 23.hours.ago }
|
|
|
|
it 'returns false' do
|
|
expect(account.possibly_stale?).to be false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#refresh!' do
|
|
let(:account) { Fabricate(:account, domain: domain) }
|
|
let(:acct) { account.acct }
|
|
|
|
context 'domain is nil' do
|
|
let(:domain) { nil }
|
|
|
|
it 'returns nil' do
|
|
expect(account.refresh!).to be_nil
|
|
end
|
|
|
|
it 'calls not ResolveAccountService#call' do
|
|
expect_any_instance_of(ResolveAccountService).not_to receive(:call).with(acct)
|
|
account.refresh!
|
|
end
|
|
end
|
|
|
|
context 'domain is present' do
|
|
let(:domain) { 'example.com' }
|
|
|
|
it 'calls ResolveAccountService#call' do
|
|
expect_any_instance_of(ResolveAccountService).to receive(:call).with(acct).once
|
|
account.refresh!
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#to_param' do
|
|
it 'returns username' do
|
|
account = Fabricate(:account, username: 'alice')
|
|
expect(account.to_param).to eq 'alice'
|
|
end
|
|
end
|
|
|
|
describe '#keypair' do
|
|
it 'returns an RSA key pair' do
|
|
account = Fabricate(:account)
|
|
expect(account.keypair).to be_instance_of OpenSSL::PKey::RSA
|
|
end
|
|
end
|
|
|
|
describe '#object_type' do
|
|
it 'is always a person' do
|
|
account = Fabricate(:account)
|
|
expect(account.object_type).to be :person
|
|
end
|
|
end
|
|
|
|
describe '#favourited?' do
|
|
let(:original_status) do
|
|
author = Fabricate(:account, username: 'original')
|
|
Fabricate(:status, account: author)
|
|
end
|
|
|
|
subject { Fabricate(:account) }
|
|
|
|
context 'when the status is a reblog of another status' do
|
|
let(:original_reblog) do
|
|
author = Fabricate(:account, username: 'original_reblogger')
|
|
Fabricate(:status, reblog: original_status, account: author)
|
|
end
|
|
|
|
it 'is is true when this account has favourited it' do
|
|
Fabricate(:favourite, status: original_reblog, account: subject)
|
|
|
|
expect(subject.favourited?(original_status)).to eq true
|
|
end
|
|
|
|
it 'is false when this account has not favourited it' do
|
|
expect(subject.favourited?(original_status)).to eq false
|
|
end
|
|
end
|
|
|
|
context 'when the status is an original status' do
|
|
it 'is is true when this account has favourited it' do
|
|
Fabricate(:favourite, status: original_status, account: subject)
|
|
|
|
expect(subject.favourited?(original_status)).to eq true
|
|
end
|
|
|
|
it 'is false when this account has not favourited it' do
|
|
expect(subject.favourited?(original_status)).to eq false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#reblogged?' do
|
|
let(:original_status) do
|
|
author = Fabricate(:account, username: 'original')
|
|
Fabricate(:status, account: author)
|
|
end
|
|
|
|
subject { Fabricate(:account) }
|
|
|
|
context 'when the status is a reblog of another status' do
|
|
let(:original_reblog) do
|
|
author = Fabricate(:account, username: 'original_reblogger')
|
|
Fabricate(:status, reblog: original_status, account: author)
|
|
end
|
|
|
|
it 'is true when this account has reblogged it' do
|
|
Fabricate(:status, reblog: original_reblog, account: subject)
|
|
|
|
expect(subject.reblogged?(original_reblog)).to eq true
|
|
end
|
|
|
|
it 'is false when this account has not reblogged it' do
|
|
expect(subject.reblogged?(original_reblog)).to eq false
|
|
end
|
|
end
|
|
|
|
context 'when the status is an original status' do
|
|
it 'is true when this account has reblogged it' do
|
|
Fabricate(:status, reblog: original_status, account: subject)
|
|
|
|
expect(subject.reblogged?(original_status)).to eq true
|
|
end
|
|
|
|
it 'is false when this account has not reblogged it' do
|
|
expect(subject.reblogged?(original_status)).to eq false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#excluded_from_timeline_account_ids' do
|
|
it 'includes account ids of blockings, blocked_bys and mutes' do
|
|
account = Fabricate(:account)
|
|
block = Fabricate(:block, account: account)
|
|
mute = Fabricate(:mute, account: account)
|
|
block_by = Fabricate(:block, target_account: account)
|
|
|
|
results = account.excluded_from_timeline_account_ids
|
|
expect(results.size).to eq 3
|
|
expect(results).to include(block.target_account.id)
|
|
expect(results).to include(mute.target_account.id)
|
|
expect(results).to include(block_by.account.id)
|
|
end
|
|
end
|
|
|
|
describe '#excluded_from_timeline_domains' do
|
|
it 'returns the domains blocked by the account' do
|
|
account = Fabricate(:account)
|
|
account.block_domain!('domain')
|
|
expect(account.excluded_from_timeline_domains).to match_array ['domain']
|
|
end
|
|
end
|
|
|
|
describe '.search_for' do
|
|
before do
|
|
_missing = Fabricate(
|
|
:account,
|
|
display_name: "Missing",
|
|
username: "missing",
|
|
domain: "missing.com"
|
|
)
|
|
end
|
|
|
|
it 'does not return suspended users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username',
|
|
domain: 'example.com',
|
|
suspended: true
|
|
)
|
|
|
|
results = Account.search_for('username')
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unapproved users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(approved: false)
|
|
|
|
results = Account.search_for('username')
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unconfirmed users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(confirmed_at: nil)
|
|
|
|
results = Account.search_for('username')
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'accepts ?, \, : and space as delimiter' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'A & l & i & c & e',
|
|
username: 'username',
|
|
domain: 'example.com'
|
|
)
|
|
|
|
results = Account.search_for('A?l\i:c e')
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'finds accounts with matching display_name' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: "Display Name",
|
|
username: "username",
|
|
domain: "example.com"
|
|
)
|
|
|
|
results = Account.search_for("display")
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'finds accounts with matching username' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: "Display Name",
|
|
username: "username",
|
|
domain: "example.com"
|
|
)
|
|
|
|
results = Account.search_for("username")
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'finds accounts with matching domain' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: "Display Name",
|
|
username: "username",
|
|
domain: "example.com"
|
|
)
|
|
|
|
results = Account.search_for("example")
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'limits by 10 by default' do
|
|
11.times.each { Fabricate(:account, display_name: "Display Name") }
|
|
results = Account.search_for("display")
|
|
expect(results.size).to eq 10
|
|
end
|
|
|
|
it 'accepts arbitrary limits' do
|
|
2.times.each { Fabricate(:account, display_name: "Display Name") }
|
|
results = Account.search_for("display", 1)
|
|
expect(results.size).to eq 1
|
|
end
|
|
|
|
it 'ranks multiple matches higher' do
|
|
matches = [
|
|
{ username: "username", display_name: "username" },
|
|
{ display_name: "Display Name", username: "username", domain: "example.com" },
|
|
].map(&method(:Fabricate).curry(2).call(:account))
|
|
|
|
results = Account.search_for("username")
|
|
expect(results).to eq matches
|
|
end
|
|
end
|
|
|
|
describe '.advanced_search_for' do
|
|
let(:account) { Fabricate(:account) }
|
|
|
|
context 'when limiting search to followed accounts' do
|
|
it 'accepts ?, \, : and space as delimiter' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'A & l & i & c & e',
|
|
username: 'username',
|
|
domain: 'example.com'
|
|
)
|
|
account.follow!(match)
|
|
|
|
results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'does not return non-followed accounts' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'A & l & i & c & e',
|
|
username: 'username',
|
|
domain: 'example.com'
|
|
)
|
|
|
|
results = Account.advanced_search_for('A?l\i:c e', account, 10, true)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return suspended users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username',
|
|
domain: 'example.com',
|
|
suspended: true
|
|
)
|
|
|
|
results = Account.advanced_search_for('username', account, 10, true)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unapproved users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(approved: false)
|
|
|
|
results = Account.advanced_search_for('username', account, 10, true)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unconfirmed users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(confirmed_at: nil)
|
|
|
|
results = Account.advanced_search_for('username', account, 10, true)
|
|
expect(results).to eq []
|
|
end
|
|
end
|
|
|
|
it 'does not return suspended users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username',
|
|
domain: 'example.com',
|
|
suspended: true
|
|
)
|
|
|
|
results = Account.advanced_search_for('username', account)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unapproved users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(approved: false)
|
|
|
|
results = Account.advanced_search_for('username', account)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'does not return unconfirmed users' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'Display Name',
|
|
username: 'username'
|
|
)
|
|
|
|
match.user.update(confirmed_at: nil)
|
|
|
|
results = Account.advanced_search_for('username', account)
|
|
expect(results).to eq []
|
|
end
|
|
|
|
it 'accepts ?, \, : and space as delimiter' do
|
|
match = Fabricate(
|
|
:account,
|
|
display_name: 'A & l & i & c & e',
|
|
username: 'username',
|
|
domain: 'example.com'
|
|
)
|
|
|
|
results = Account.advanced_search_for('A?l\i:c e', account)
|
|
expect(results).to eq [match]
|
|
end
|
|
|
|
it 'limits by 10 by default' do
|
|
11.times { Fabricate(:account, display_name: "Display Name") }
|
|
results = Account.advanced_search_for("display", account)
|
|
expect(results.size).to eq 10
|
|
end
|
|
|
|
it 'accepts arbitrary limits' do
|
|
2.times { Fabricate(:account, display_name: "Display Name") }
|
|
results = Account.advanced_search_for("display", account, 1)
|
|
expect(results.size).to eq 1
|
|
end
|
|
|
|
it 'ranks followed accounts higher' do
|
|
match = Fabricate(:account, username: "Matching")
|
|
followed_match = Fabricate(:account, username: "Matcher")
|
|
Fabricate(:follow, account: account, target_account: followed_match)
|
|
|
|
results = Account.advanced_search_for("match", account)
|
|
expect(results).to eq [followed_match, match]
|
|
expect(results.first.rank).to be > results.last.rank
|
|
end
|
|
end
|
|
|
|
describe '#statuses_count' do
|
|
subject { Fabricate(:account) }
|
|
|
|
it 'counts statuses' do
|
|
Fabricate(:status, account: subject)
|
|
Fabricate(:status, account: subject)
|
|
expect(subject.statuses_count).to eq 2
|
|
end
|
|
|
|
it 'does not count direct statuses' do
|
|
Fabricate(:status, account: subject, visibility: :direct)
|
|
expect(subject.statuses_count).to eq 0
|
|
end
|
|
|
|
it 'is decremented when status is removed' do
|
|
status = Fabricate(:status, account: subject)
|
|
expect(subject.statuses_count).to eq 1
|
|
status.destroy
|
|
expect(subject.statuses_count).to eq 0
|
|
end
|
|
|
|
it 'is decremented when status is removed when account is not preloaded' do
|
|
status = Fabricate(:status, account: subject)
|
|
expect(subject.reload.statuses_count).to eq 1
|
|
clean_status = Status.find(status.id)
|
|
expect(clean_status.association(:account).loaded?).to be false
|
|
clean_status.destroy
|
|
expect(subject.reload.statuses_count).to eq 0
|
|
end
|
|
end
|
|
|
|
describe '.following_map' do
|
|
it 'returns an hash' do
|
|
expect(Account.following_map([], 1)).to be_a Hash
|
|
end
|
|
end
|
|
|
|
describe '.followed_by_map' do
|
|
it 'returns an hash' do
|
|
expect(Account.followed_by_map([], 1)).to be_a Hash
|
|
end
|
|
end
|
|
|
|
describe '.blocking_map' do
|
|
it 'returns an hash' do
|
|
expect(Account.blocking_map([], 1)).to be_a Hash
|
|
end
|
|
end
|
|
|
|
describe '.requested_map' do
|
|
it 'returns an hash' do
|
|
expect(Account.requested_map([], 1)).to be_a Hash
|
|
end
|
|
end
|
|
|
|
describe 'MENTION_RE' do
|
|
subject { Account::MENTION_RE }
|
|
|
|
it 'matches usernames in the middle of a sentence' do
|
|
expect(subject.match('Hello to @alice from me')[1]).to eq 'alice'
|
|
end
|
|
|
|
it 'matches usernames in the beginning of status' do
|
|
expect(subject.match('@alice Hey how are you?')[1]).to eq 'alice'
|
|
end
|
|
|
|
it 'matches full usernames' do
|
|
expect(subject.match('@alice@example.com')[1]).to eq 'alice@example.com'
|
|
end
|
|
|
|
it 'matches full usernames with a dot at the end' do
|
|
expect(subject.match('Hello @alice@example.com.')[1]).to eq 'alice@example.com'
|
|
end
|
|
|
|
it 'matches dot-prepended usernames' do
|
|
expect(subject.match('.@alice I want everybody to see this')[1]).to eq 'alice'
|
|
end
|
|
|
|
it 'does not match e-mails' do
|
|
expect(subject.match('Drop me an e-mail at alice@example.com')).to be_nil
|
|
end
|
|
|
|
it 'does not match URLs' do
|
|
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
|
|
end
|
|
|
|
xit 'does not match URL querystring' do
|
|
expect(subject.match('https://example.com/?x=@alice')).to be_nil
|
|
end
|
|
end
|
|
|
|
describe 'validations' do
|
|
it 'has a valid fabricator' do
|
|
account = Fabricate.build(:account)
|
|
account.valid?
|
|
expect(account).to be_valid
|
|
end
|
|
|
|
it 'is invalid without a username' do
|
|
account = Fabricate.build(:account, username: nil)
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'squishes the username before validation' do
|
|
account = Fabricate(:account, domain: nil, username: " \u3000bob \t \u00a0 \n ")
|
|
expect(account.username).to eq 'bob'
|
|
end
|
|
|
|
context 'when is local' do
|
|
it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
|
|
account_1 = Fabricate(:account, username: 'the_doctor')
|
|
account_2 = Fabricate.build(:account, username: 'the_Doctor')
|
|
account_2.valid?
|
|
expect(account_2).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the username is reserved' do
|
|
account = Fabricate.build(:account, username: 'support')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is valid when username is reserved but record has already been created' do
|
|
account = Fabricate.build(:account, username: 'support')
|
|
account.save(validate: false)
|
|
expect(account.valid?).to be true
|
|
end
|
|
|
|
it 'is valid if we are creating an instance actor account with a period' do
|
|
account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
|
|
expect(account.valid?).to be true
|
|
end
|
|
|
|
it 'is valid if we are creating a possibly-conflicting instance actor account' do
|
|
account_1 = Fabricate(:account, username: 'examplecom')
|
|
account_2 = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
|
|
expect(account_2.valid?).to be true
|
|
end
|
|
|
|
it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
|
|
account = Fabricate.build(:account, username: 'the-doctor')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the username contains a period' do
|
|
account = Fabricate.build(:account, username: 'the.doctor')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the username is longer then 30 characters' do
|
|
account = Fabricate.build(:account, username: Faker::Lorem.characters(number: 31))
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the display name is longer than 30 characters' do
|
|
account = Fabricate.build(:account, display_name: Faker::Lorem.characters(number: 31))
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:display_name)
|
|
end
|
|
|
|
it 'is invalid if the note is longer than 500 characters' do
|
|
account = Fabricate.build(:account, note: Faker::Lorem.characters(number: 501))
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:note)
|
|
end
|
|
end
|
|
|
|
context 'when is remote' do
|
|
it 'is invalid if the username is same among accounts in the same normalized domain' do
|
|
Fabricate(:account, domain: 'にゃん', username: 'username')
|
|
account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'username')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the username is not unique in case-insensitive comparison among accounts in the same normalized domain' do
|
|
Fabricate(:account, domain: 'にゃん', username: 'username')
|
|
account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'Username')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is valid even if the username contains hyphens' do
|
|
account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor')
|
|
account.valid?
|
|
expect(account).to_not model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do
|
|
account = Fabricate.build(:account, domain: 'domain', username: 'the doctor')
|
|
account.valid?
|
|
expect(account).to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is valid even if the username is longer then 30 characters' do
|
|
account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(number: 31))
|
|
account.valid?
|
|
expect(account).not_to model_have_error_on_field(:username)
|
|
end
|
|
|
|
it 'is valid even if the display name is longer than 30 characters' do
|
|
account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(number: 31))
|
|
account.valid?
|
|
expect(account).not_to model_have_error_on_field(:display_name)
|
|
end
|
|
|
|
it 'is valid even if the note is longer than 500 characters' do
|
|
account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(number: 501))
|
|
account.valid?
|
|
expect(account).not_to model_have_error_on_field(:note)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'scopes' do
|
|
describe 'alphabetic' do
|
|
it 'sorts by alphabetic order of domain and username' do
|
|
matches = [
|
|
{ username: 'a', domain: 'a' },
|
|
{ username: 'b', domain: 'a' },
|
|
{ username: 'a', domain: 'b' },
|
|
{ username: 'b', domain: 'b' },
|
|
].map(&method(:Fabricate).curry(2).call(:account))
|
|
|
|
expect(Account.where('id > 0').alphabetic).to eq matches
|
|
end
|
|
end
|
|
|
|
describe 'matches_display_name' do
|
|
it 'matches display name which starts with the given string' do
|
|
match = Fabricate(:account, display_name: 'pattern and suffix')
|
|
Fabricate(:account, display_name: 'prefix and pattern')
|
|
|
|
expect(Account.matches_display_name('pattern')).to eq [match]
|
|
end
|
|
end
|
|
|
|
describe 'matches_username' do
|
|
it 'matches display name which starts with the given string' do
|
|
match = Fabricate(:account, username: 'pattern_and_suffix')
|
|
Fabricate(:account, username: 'prefix_and_pattern')
|
|
|
|
expect(Account.matches_username('pattern')).to eq [match]
|
|
end
|
|
end
|
|
|
|
describe 'by_domain_and_subdomains' do
|
|
it 'returns exact domain matches' do
|
|
account = Fabricate(:account, domain: 'example.com')
|
|
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
|
|
end
|
|
|
|
it 'returns subdomains' do
|
|
account = Fabricate(:account, domain: 'foo.example.com')
|
|
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
|
|
end
|
|
|
|
it 'does not return partially matching domains' do
|
|
account = Fabricate(:account, domain: 'grexample.com')
|
|
expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account]
|
|
end
|
|
end
|
|
|
|
describe 'remote' do
|
|
it 'returns an array of accounts who have a domain' do
|
|
account_1 = Fabricate(:account, domain: nil)
|
|
account_2 = Fabricate(:account, domain: 'example.com')
|
|
expect(Account.remote).to match_array([account_2])
|
|
end
|
|
end
|
|
|
|
describe 'local' do
|
|
it 'returns an array of accounts who do not have a domain' do
|
|
account_1 = Fabricate(:account, domain: nil)
|
|
account_2 = Fabricate(:account, domain: 'example.com')
|
|
expect(Account.where('id > 0').local).to match_array([account_1])
|
|
end
|
|
end
|
|
|
|
describe 'partitioned' do
|
|
it 'returns a relation of accounts partitioned by domain' do
|
|
matches = ['a', 'b', 'a', 'b']
|
|
matches.size.times.to_a.shuffle.each do |index|
|
|
matches[index] = Fabricate(:account, domain: matches[index])
|
|
end
|
|
|
|
expect(Account.where('id > 0').partitioned).to match_array(matches)
|
|
end
|
|
end
|
|
|
|
describe 'recent' do
|
|
it 'returns a relation of accounts sorted by recent creation' do
|
|
matches = 2.times.map { Fabricate(:account) }
|
|
expect(Account.where('id > 0').recent).to match_array(matches)
|
|
end
|
|
end
|
|
|
|
describe 'silenced' do
|
|
it 'returns an array of accounts who are silenced' do
|
|
account_1 = Fabricate(:account, silenced: true)
|
|
account_2 = Fabricate(:account, silenced: false)
|
|
expect(Account.silenced).to match_array([account_1])
|
|
end
|
|
end
|
|
|
|
describe 'suspended' do
|
|
it 'returns an array of accounts who are suspended' do
|
|
account_1 = Fabricate(:account, suspended: true)
|
|
account_2 = Fabricate(:account, suspended: false)
|
|
expect(Account.suspended).to match_array([account_1])
|
|
end
|
|
end
|
|
|
|
describe 'searchable' do
|
|
let!(:suspended_local) { Fabricate(:account, suspended: true, username: 'suspended_local') }
|
|
let!(:suspended_remote) { Fabricate(:account, suspended: true, domain: 'example.org', username: 'suspended_remote') }
|
|
let!(:silenced_local) { Fabricate(:account, silenced: true, username: 'silenced_local') }
|
|
let!(:silenced_remote) { Fabricate(:account, silenced: true, domain: 'example.org', username: 'silenced_remote') }
|
|
let!(:unconfirmed) { Fabricate(:user, confirmed_at: nil).account }
|
|
let!(:unapproved) { Fabricate(:user, approved: false).account }
|
|
let!(:unconfirmed_unapproved) { Fabricate(:user, confirmed_at: nil, approved: false).account }
|
|
let!(:local_account) { Fabricate(:account, username: 'local_account') }
|
|
let!(:remote_account) { Fabricate(:account, domain: 'example.org', username: 'remote_account') }
|
|
|
|
before do
|
|
# Accounts get automatically-approved depending on settings, so ensure they aren't approved
|
|
unapproved.user.update(approved: false)
|
|
unconfirmed_unapproved.user.update(approved: false)
|
|
end
|
|
|
|
it 'returns every usable non-suspended account' do
|
|
expect(Account.searchable).to match_array([silenced_local, silenced_remote, local_account, remote_account])
|
|
end
|
|
|
|
it 'does not mess with previously-applied scopes' do
|
|
expect(Account.where.not(id: remote_account.id).searchable).to match_array([silenced_local, silenced_remote, local_account])
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when is local' do
|
|
# Test disabled because test environment omits autogenerating keys for performance
|
|
xit 'generates keys' do
|
|
account = Account.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_']))
|
|
expect(account.keypair.private?).to eq true
|
|
end
|
|
end
|
|
|
|
context 'when is remote' do
|
|
it 'does not generate keys' do
|
|
key = OpenSSL::PKey::RSA.new(1024).public_key
|
|
account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem)
|
|
expect(account.keypair.params).to eq key.params
|
|
end
|
|
|
|
it 'normalizes domain' do
|
|
account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(separators: ['_']))
|
|
expect(account.domain).to eq 'xn--r9j5b5b'
|
|
end
|
|
end
|
|
|
|
include_examples 'AccountAvatar', :account
|
|
include_examples 'AccountHeader', :account
|
|
|
|
describe '#increment_count!' do
|
|
subject { Fabricate(:account) }
|
|
|
|
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
|
|
Account.find(subject.id).increment_count!(:followers_count)
|
|
end
|
|
end
|
|
|
|
wait_for_start = false
|
|
threads.each(&:join)
|
|
|
|
expect(subject.reload.followers_count).to eq 15
|
|
end
|
|
end
|
|
end
|