account_spec.rb 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe Account do
  4. context 'with an account record' do
  5. subject { Fabricate(:account) }
  6. let(:bob) { Fabricate(:account, username: 'bob') }
  7. describe '#suspend!' do
  8. it 'marks the account as suspended and creates a deletion request' do
  9. expect { subject.suspend! }
  10. .to change(subject, :suspended?).from(false).to(true)
  11. .and(change { AccountDeletionRequest.exists?(account: subject) }.from(false).to(true))
  12. end
  13. context 'when the account is of a local user' do
  14. subject { local_user_account }
  15. let!(:local_user_account) { Fabricate(:user, email: 'foo+bar@domain.org').account }
  16. it 'creates a canonical domain block' do
  17. subject.suspend!
  18. expect(CanonicalEmailBlock.block?(subject.user_email)).to be true
  19. end
  20. context 'when a canonical domain block already exists for that email' do
  21. before do
  22. Fabricate(:canonical_email_block, email: subject.user_email)
  23. end
  24. it 'does not raise an error' do
  25. expect { subject.suspend! }.to_not raise_error
  26. end
  27. end
  28. end
  29. end
  30. describe '#follow!' do
  31. it 'creates a follow' do
  32. follow = subject.follow!(bob)
  33. expect(follow).to be_instance_of Follow
  34. expect(follow.account).to eq subject
  35. expect(follow.target_account).to eq bob
  36. end
  37. end
  38. describe '#unfollow!' do
  39. before do
  40. subject.follow!(bob)
  41. end
  42. it 'destroys a follow' do
  43. unfollow = subject.unfollow!(bob)
  44. expect(unfollow).to be_instance_of Follow
  45. expect(unfollow.account).to eq subject
  46. expect(unfollow.target_account).to eq bob
  47. expect(unfollow.destroyed?).to be true
  48. end
  49. end
  50. describe '#following?' do
  51. it 'returns true when the target is followed' do
  52. subject.follow!(bob)
  53. expect(subject.following?(bob)).to be true
  54. end
  55. it 'returns false if the target is not followed' do
  56. expect(subject.following?(bob)).to be false
  57. end
  58. end
  59. end
  60. describe '#local?' do
  61. it 'returns true when the account is local' do
  62. account = Fabricate(:account, domain: nil)
  63. expect(account.local?).to be true
  64. end
  65. it 'returns false when the account is on a different domain' do
  66. account = Fabricate(:account, domain: 'foreign.tld')
  67. expect(account.local?).to be false
  68. end
  69. end
  70. describe 'Local domain user methods' do
  71. subject { Fabricate(:account, domain: nil, username: 'alice') }
  72. around do |example|
  73. before = Rails.configuration.x.local_domain
  74. example.run
  75. Rails.configuration.x.local_domain = before
  76. end
  77. describe '#to_webfinger_s' do
  78. it 'returns a webfinger string for the account' do
  79. Rails.configuration.x.local_domain = 'example.com'
  80. expect(subject.to_webfinger_s).to eq 'acct:alice@example.com'
  81. end
  82. end
  83. describe '#local_username_and_domain' do
  84. it 'returns the username and local domain for the account' do
  85. Rails.configuration.x.local_domain = 'example.com'
  86. expect(subject.local_username_and_domain).to eq 'alice@example.com'
  87. end
  88. end
  89. end
  90. describe '#acct' do
  91. it 'returns username for local users' do
  92. account = Fabricate(:account, domain: nil, username: 'alice')
  93. expect(account.acct).to eql 'alice'
  94. end
  95. it 'returns username@domain for foreign users' do
  96. account = Fabricate(:account, domain: 'foreign.tld', username: 'alice')
  97. expect(account.acct).to eql 'alice@foreign.tld'
  98. end
  99. end
  100. describe '#save_with_optional_media!' do
  101. before do
  102. stub_request(:get, 'https://remote.test/valid_avatar').to_return(request_fixture('avatar.txt'))
  103. stub_request(:get, 'https://remote.test/invalid_avatar').to_return(request_fixture('feed.txt'))
  104. end
  105. let(:account) do
  106. Fabricate(:account,
  107. avatar_remote_url: 'https://remote.test/valid_avatar',
  108. header_remote_url: 'https://remote.test/valid_avatar')
  109. end
  110. let!(:expectation) { account.dup }
  111. context 'with valid properties' do
  112. before do
  113. account.save_with_optional_media!
  114. end
  115. it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do
  116. expect(account.avatar_remote_url).to eq expectation.avatar_remote_url
  117. expect(account.header_remote_url).to eq expectation.header_remote_url
  118. expect(account.avatar_file_name).to eq expectation.avatar_file_name
  119. expect(account.header_file_name).to eq expectation.header_file_name
  120. end
  121. end
  122. context 'with invalid properties' do
  123. before do
  124. account.avatar_remote_url = 'https://remote.test/invalid_avatar'
  125. account.save_with_optional_media!
  126. end
  127. it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
  128. expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar'
  129. expect(account.header_remote_url).to eq expectation.header_remote_url
  130. expect(account.avatar_file_name).to be_nil
  131. expect(account.header_file_name).to eq expectation.header_file_name
  132. end
  133. end
  134. end
  135. describe '#possibly_stale?' do
  136. let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) }
  137. context 'when last_webfingered_at is nil' do
  138. let(:last_webfingered_at) { nil }
  139. it 'returns true' do
  140. expect(account.possibly_stale?).to be true
  141. end
  142. end
  143. context 'when last_webfingered_at is more than 24 hours before' do
  144. let(:last_webfingered_at) { 25.hours.ago }
  145. it 'returns true' do
  146. expect(account.possibly_stale?).to be true
  147. end
  148. end
  149. context 'when last_webfingered_at is less than 24 hours before' do
  150. let(:last_webfingered_at) { 23.hours.ago }
  151. it 'returns false' do
  152. expect(account.possibly_stale?).to be false
  153. end
  154. end
  155. end
  156. describe '#refresh!' do
  157. let(:account) { Fabricate(:account, domain: domain) }
  158. let(:acct) { account.acct }
  159. context 'when domain is nil' do
  160. let(:domain) { nil }
  161. it 'returns nil' do
  162. expect(account.refresh!).to be_nil
  163. end
  164. it 'does not call ResolveAccountService#call' do
  165. service = instance_double(ResolveAccountService, call: nil)
  166. allow(ResolveAccountService).to receive(:new).and_return(service)
  167. account.refresh!
  168. expect(service).to_not have_received(:call).with(acct)
  169. end
  170. end
  171. context 'when domain is present' do
  172. let(:domain) { 'example.com' }
  173. it 'calls ResolveAccountService#call' do
  174. service = instance_double(ResolveAccountService, call: nil)
  175. allow(ResolveAccountService).to receive(:new).and_return(service)
  176. account.refresh!
  177. expect(service).to have_received(:call).with(acct).once
  178. end
  179. end
  180. end
  181. describe '#to_param' do
  182. it 'returns username' do
  183. account = Fabricate(:account, username: 'alice')
  184. expect(account.to_param).to eq 'alice'
  185. end
  186. end
  187. describe '#keypair' do
  188. it 'returns an RSA key pair' do
  189. account = Fabricate(:account)
  190. expect(account.keypair).to be_instance_of OpenSSL::PKey::RSA
  191. end
  192. end
  193. describe '#object_type' do
  194. it 'is always a person' do
  195. account = Fabricate(:account)
  196. expect(account.object_type).to be :person
  197. end
  198. end
  199. describe '#favourited?' do
  200. subject { Fabricate(:account) }
  201. let(:original_status) do
  202. author = Fabricate(:account, username: 'original')
  203. Fabricate(:status, account: author)
  204. end
  205. context 'when the status is a reblog of another status' do
  206. let(:original_reblog) do
  207. author = Fabricate(:account, username: 'original_reblogger')
  208. Fabricate(:status, reblog: original_status, account: author)
  209. end
  210. it 'is true when this account has favourited it' do
  211. Fabricate(:favourite, status: original_reblog, account: subject)
  212. expect(subject.favourited?(original_status)).to be true
  213. end
  214. it 'is false when this account has not favourited it' do
  215. expect(subject.favourited?(original_status)).to be false
  216. end
  217. end
  218. context 'when the status is an original status' do
  219. it 'is true when this account has favourited it' do
  220. Fabricate(:favourite, status: original_status, account: subject)
  221. expect(subject.favourited?(original_status)).to be true
  222. end
  223. it 'is false when this account has not favourited it' do
  224. expect(subject.favourited?(original_status)).to be false
  225. end
  226. end
  227. end
  228. describe '#reblogged?' do
  229. subject { Fabricate(:account) }
  230. let(:original_status) do
  231. author = Fabricate(:account, username: 'original')
  232. Fabricate(:status, account: author)
  233. end
  234. context 'when the status is a reblog of another status' do
  235. let(:original_reblog) do
  236. author = Fabricate(:account, username: 'original_reblogger')
  237. Fabricate(:status, reblog: original_status, account: author)
  238. end
  239. it 'is true when this account has reblogged it' do
  240. Fabricate(:status, reblog: original_reblog, account: subject)
  241. expect(subject.reblogged?(original_reblog)).to be true
  242. end
  243. it 'is false when this account has not reblogged it' do
  244. expect(subject.reblogged?(original_reblog)).to be false
  245. end
  246. end
  247. context 'when the status is an original status' do
  248. it 'is true when this account has reblogged it' do
  249. Fabricate(:status, reblog: original_status, account: subject)
  250. expect(subject.reblogged?(original_status)).to be true
  251. end
  252. it 'is false when this account has not reblogged it' do
  253. expect(subject.reblogged?(original_status)).to be false
  254. end
  255. end
  256. end
  257. describe '#excluded_from_timeline_account_ids' do
  258. it 'includes account ids of blockings, blocked_bys and mutes' do
  259. account = Fabricate(:account)
  260. block = Fabricate(:block, account: account)
  261. mute = Fabricate(:mute, account: account)
  262. block_by = Fabricate(:block, target_account: account)
  263. results = account.excluded_from_timeline_account_ids
  264. expect(results.size).to eq 3
  265. expect(results).to include(
  266. block.target_account.id,
  267. mute.target_account.id,
  268. block_by.account.id
  269. )
  270. end
  271. end
  272. describe '#excluded_from_timeline_domains' do
  273. it 'returns the domains blocked by the account' do
  274. account = Fabricate(:account)
  275. account.block_domain!('domain')
  276. expect(account.excluded_from_timeline_domains).to contain_exactly('domain')
  277. end
  278. end
  279. describe '.search_for' do
  280. before do
  281. _missing = Fabricate(
  282. :account,
  283. display_name: 'Missing',
  284. username: 'missing',
  285. domain: 'missing.com'
  286. )
  287. end
  288. it 'does not return suspended users' do
  289. Fabricate(
  290. :account,
  291. display_name: 'Display Name',
  292. username: 'username',
  293. domain: 'example.com',
  294. suspended: true
  295. )
  296. results = described_class.search_for('username')
  297. expect(results).to eq []
  298. end
  299. it 'does not return unapproved users' do
  300. match = Fabricate(
  301. :account,
  302. display_name: 'Display Name',
  303. username: 'username'
  304. )
  305. match.user.update(approved: false)
  306. results = described_class.search_for('username')
  307. expect(results).to eq []
  308. end
  309. it 'does not return unconfirmed users' do
  310. match = Fabricate(
  311. :account,
  312. display_name: 'Display Name',
  313. username: 'username'
  314. )
  315. match.user.update(confirmed_at: nil)
  316. results = described_class.search_for('username')
  317. expect(results).to eq []
  318. end
  319. it 'accepts ?, \, : and space as delimiter' do
  320. match = Fabricate(
  321. :account,
  322. display_name: 'A & l & i & c & e',
  323. username: 'username',
  324. domain: 'example.com'
  325. )
  326. results = described_class.search_for('A?l\i:c e')
  327. expect(results).to eq [match]
  328. end
  329. it 'finds accounts with matching display_name' do
  330. match = Fabricate(
  331. :account,
  332. display_name: 'Display Name',
  333. username: 'username',
  334. domain: 'example.com'
  335. )
  336. results = described_class.search_for('display')
  337. expect(results).to eq [match]
  338. end
  339. it 'finds accounts with matching username' do
  340. match = Fabricate(
  341. :account,
  342. display_name: 'Display Name',
  343. username: 'username',
  344. domain: 'example.com'
  345. )
  346. results = described_class.search_for('username')
  347. expect(results).to eq [match]
  348. end
  349. it 'finds accounts with matching domain' do
  350. match = Fabricate(
  351. :account,
  352. display_name: 'Display Name',
  353. username: 'username',
  354. domain: 'example.com'
  355. )
  356. results = described_class.search_for('example')
  357. expect(results).to eq [match]
  358. end
  359. it 'limits via constant by default' do
  360. stub_const('Account::Search::DEFAULT_LIMIT', 1)
  361. 2.times.each { Fabricate(:account, display_name: 'Display Name') }
  362. results = described_class.search_for('display')
  363. expect(results.size).to eq 1
  364. end
  365. it 'accepts arbitrary limits' do
  366. 2.times.each { Fabricate(:account, display_name: 'Display Name') }
  367. results = described_class.search_for('display', limit: 1)
  368. expect(results.size).to eq 1
  369. end
  370. it 'ranks multiple matches higher' do
  371. matches = [
  372. { username: 'username', display_name: 'username' },
  373. { display_name: 'Display Name', username: 'username', domain: 'example.com' },
  374. ].map(&method(:Fabricate).curry(2).call(:account))
  375. results = described_class.search_for('username')
  376. expect(results).to eq matches
  377. end
  378. end
  379. describe '.advanced_search_for' do
  380. let(:account) { Fabricate(:account) }
  381. context 'when limiting search to followed accounts' do
  382. it 'accepts ?, \, : and space as delimiter' do
  383. match = Fabricate(
  384. :account,
  385. display_name: 'A & l & i & c & e',
  386. username: 'username',
  387. domain: 'example.com'
  388. )
  389. account.follow!(match)
  390. results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
  391. expect(results).to eq [match]
  392. end
  393. it 'does not return non-followed accounts' do
  394. Fabricate(
  395. :account,
  396. display_name: 'A & l & i & c & e',
  397. username: 'username',
  398. domain: 'example.com'
  399. )
  400. results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
  401. expect(results).to eq []
  402. end
  403. it 'does not return suspended users' do
  404. Fabricate(
  405. :account,
  406. display_name: 'Display Name',
  407. username: 'username',
  408. domain: 'example.com',
  409. suspended: true
  410. )
  411. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  412. expect(results).to eq []
  413. end
  414. it 'does not return unapproved users' do
  415. match = Fabricate(
  416. :account,
  417. display_name: 'Display Name',
  418. username: 'username'
  419. )
  420. match.user.update(approved: false)
  421. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  422. expect(results).to eq []
  423. end
  424. it 'does not return unconfirmed users' do
  425. match = Fabricate(
  426. :account,
  427. display_name: 'Display Name',
  428. username: 'username'
  429. )
  430. match.user.update(confirmed_at: nil)
  431. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  432. expect(results).to eq []
  433. end
  434. end
  435. it 'does not return suspended users' do
  436. Fabricate(
  437. :account,
  438. display_name: 'Display Name',
  439. username: 'username',
  440. domain: 'example.com',
  441. suspended: true
  442. )
  443. results = described_class.advanced_search_for('username', account)
  444. expect(results).to eq []
  445. end
  446. it 'does not return unapproved users' do
  447. match = Fabricate(
  448. :account,
  449. display_name: 'Display Name',
  450. username: 'username'
  451. )
  452. match.user.update(approved: false)
  453. results = described_class.advanced_search_for('username', account)
  454. expect(results).to eq []
  455. end
  456. it 'does not return unconfirmed users' do
  457. match = Fabricate(
  458. :account,
  459. display_name: 'Display Name',
  460. username: 'username'
  461. )
  462. match.user.update(confirmed_at: nil)
  463. results = described_class.advanced_search_for('username', account)
  464. expect(results).to eq []
  465. end
  466. it 'accepts ?, \, : and space as delimiter' do
  467. match = Fabricate(
  468. :account,
  469. display_name: 'A & l & i & c & e',
  470. username: 'username',
  471. domain: 'example.com'
  472. )
  473. results = described_class.advanced_search_for('A?l\i:c e', account)
  474. expect(results).to eq [match]
  475. end
  476. it 'limits by 10 by default' do
  477. stub_const('Account::Search::DEFAULT_LIMIT', 1)
  478. 2.times { Fabricate(:account, display_name: 'Display Name') }
  479. results = described_class.advanced_search_for('display', account)
  480. expect(results.size).to eq 1
  481. end
  482. it 'accepts arbitrary limits' do
  483. 2.times { Fabricate(:account, display_name: 'Display Name') }
  484. results = described_class.advanced_search_for('display', account, limit: 1)
  485. expect(results.size).to eq 1
  486. end
  487. it 'ranks followed accounts higher' do
  488. match = Fabricate(:account, username: 'Matching')
  489. followed_match = Fabricate(:account, username: 'Matcher')
  490. Fabricate(:follow, account: account, target_account: followed_match)
  491. results = described_class.advanced_search_for('match', account)
  492. expect(results).to eq [followed_match, match]
  493. expect(results.first.rank).to be > results.last.rank
  494. end
  495. end
  496. describe '#statuses_count' do
  497. subject { Fabricate(:account) }
  498. it 'counts statuses' do
  499. Fabricate(:status, account: subject)
  500. Fabricate(:status, account: subject)
  501. expect(subject.statuses_count).to eq 2
  502. end
  503. it 'does not count direct statuses' do
  504. Fabricate(:status, account: subject, visibility: :direct)
  505. expect(subject.statuses_count).to eq 0
  506. end
  507. it 'is decremented when status is removed' do
  508. status = Fabricate(:status, account: subject)
  509. expect(subject.statuses_count).to eq 1
  510. status.destroy
  511. expect(subject.statuses_count).to eq 0
  512. end
  513. it 'is decremented when status is removed when account is not preloaded' do
  514. status = Fabricate(:status, account: subject)
  515. expect(subject.reload.statuses_count).to eq 1
  516. clean_status = Status.find(status.id)
  517. expect(clean_status.association(:account).loaded?).to be false
  518. clean_status.destroy
  519. expect(subject.reload.statuses_count).to eq 0
  520. end
  521. end
  522. describe '.following_map' do
  523. it 'returns an hash' do
  524. expect(described_class.following_map([], 1)).to be_a Hash
  525. end
  526. end
  527. describe '.followed_by_map' do
  528. it 'returns an hash' do
  529. expect(described_class.followed_by_map([], 1)).to be_a Hash
  530. end
  531. end
  532. describe '.blocking_map' do
  533. it 'returns an hash' do
  534. expect(described_class.blocking_map([], 1)).to be_a Hash
  535. end
  536. end
  537. describe '.requested_map' do
  538. it 'returns an hash' do
  539. expect(described_class.requested_map([], 1)).to be_a Hash
  540. end
  541. end
  542. describe '.requested_by_map' do
  543. it 'returns an hash' do
  544. expect(described_class.requested_by_map([], 1)).to be_a Hash
  545. end
  546. end
  547. describe 'MENTION_RE' do
  548. subject { described_class::MENTION_RE }
  549. it 'matches usernames in the middle of a sentence' do
  550. expect(subject.match('Hello to @alice from me')[1]).to eq 'alice'
  551. end
  552. it 'matches usernames in the beginning of status' do
  553. expect(subject.match('@alice Hey how are you?')[1]).to eq 'alice'
  554. end
  555. it 'matches full usernames' do
  556. expect(subject.match('@alice@example.com')[1]).to eq 'alice@example.com'
  557. end
  558. it 'matches full usernames with a dot at the end' do
  559. expect(subject.match('Hello @alice@example.com.')[1]).to eq 'alice@example.com'
  560. end
  561. it 'matches dot-prepended usernames' do
  562. expect(subject.match('.@alice I want everybody to see this')[1]).to eq 'alice'
  563. end
  564. it 'does not match e-mails' do
  565. expect(subject.match('Drop me an e-mail at alice@example.com')).to be_nil
  566. end
  567. it 'does not match URLs' do
  568. expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
  569. end
  570. it 'does not match URL query string' do
  571. expect(subject.match('https://example.com/?x=@alice')).to be_nil
  572. end
  573. end
  574. describe 'validations' do
  575. it 'is invalid without a username' do
  576. account = Fabricate.build(:account, username: nil)
  577. account.valid?
  578. expect(account).to model_have_error_on_field(:username)
  579. end
  580. it 'squishes the username before validation' do
  581. account = Fabricate(:account, domain: nil, username: " \u3000bob \t \u00a0 \n ")
  582. expect(account.username).to eq 'bob'
  583. end
  584. context 'when is local' do
  585. it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
  586. _account = Fabricate(:account, username: 'the_doctor')
  587. non_unique_account = Fabricate.build(:account, username: 'the_Doctor')
  588. non_unique_account.valid?
  589. expect(non_unique_account).to model_have_error_on_field(:username)
  590. end
  591. it 'is invalid if the username is reserved' do
  592. account = Fabricate.build(:account, username: 'support')
  593. account.valid?
  594. expect(account).to model_have_error_on_field(:username)
  595. end
  596. it 'is valid when username is reserved but record has already been created' do
  597. account = Fabricate.build(:account, username: 'support')
  598. account.save(validate: false)
  599. expect(account.valid?).to be true
  600. end
  601. it 'is valid if we are creating an instance actor account with a period' do
  602. account = Fabricate.build(:account, id: described_class::INSTANCE_ACTOR_ID, actor_type: 'Application', locked: true, username: 'example.com')
  603. expect(account.valid?).to be true
  604. end
  605. it 'is valid if we are creating a possibly-conflicting instance actor account' do
  606. _account = Fabricate(:account, username: 'examplecom')
  607. instance_account = Fabricate.build(:account, id: described_class::INSTANCE_ACTOR_ID, actor_type: 'Application', locked: true, username: 'example.com')
  608. expect(instance_account.valid?).to be true
  609. end
  610. it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
  611. account = Fabricate.build(:account, username: 'the-doctor')
  612. account.valid?
  613. expect(account).to model_have_error_on_field(:username)
  614. end
  615. it 'is invalid if the username contains a period' do
  616. account = Fabricate.build(:account, username: 'the.doctor')
  617. account.valid?
  618. expect(account).to model_have_error_on_field(:username)
  619. end
  620. it 'is invalid if the username is longer than the character limit' do
  621. account = Fabricate.build(:account, username: username_over_limit)
  622. account.valid?
  623. expect(account).to model_have_error_on_field(:username)
  624. end
  625. it 'is invalid if the display name is longer than the character limit' do
  626. account = Fabricate.build(:account, display_name: username_over_limit)
  627. account.valid?
  628. expect(account).to model_have_error_on_field(:display_name)
  629. end
  630. it 'is invalid if the note is longer than the character limit' do
  631. account = Fabricate.build(:account, note: account_note_over_limit)
  632. account.valid?
  633. expect(account).to model_have_error_on_field(:note)
  634. end
  635. end
  636. context 'when is remote' do
  637. it 'is invalid if the username is same among accounts in the same normalized domain' do
  638. Fabricate(:account, domain: 'にゃん', username: 'username')
  639. account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'username')
  640. account.valid?
  641. expect(account).to model_have_error_on_field(:username)
  642. end
  643. it 'is invalid if the username is not unique in case-insensitive comparison among accounts in the same normalized domain' do
  644. Fabricate(:account, domain: 'にゃん', username: 'username')
  645. account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'Username')
  646. account.valid?
  647. expect(account).to model_have_error_on_field(:username)
  648. end
  649. it 'is valid even if the username contains hyphens' do
  650. account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor')
  651. account.valid?
  652. expect(account).to_not model_have_error_on_field(:username)
  653. end
  654. it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do
  655. account = Fabricate.build(:account, domain: 'domain', username: 'the doctor')
  656. account.valid?
  657. expect(account).to model_have_error_on_field(:username)
  658. end
  659. it 'is valid even if the username is longer than the character limit' do
  660. account = Fabricate.build(:account, domain: 'domain', username: username_over_limit)
  661. account.valid?
  662. expect(account).to_not model_have_error_on_field(:username)
  663. end
  664. it 'is valid even if the display name is longer than the character limit' do
  665. account = Fabricate.build(:account, domain: 'domain', display_name: username_over_limit)
  666. account.valid?
  667. expect(account).to_not model_have_error_on_field(:display_name)
  668. end
  669. it 'is valid even if the note is longer than the character limit' do
  670. account = Fabricate.build(:account, domain: 'domain', note: account_note_over_limit)
  671. account.valid?
  672. expect(account).to_not model_have_error_on_field(:note)
  673. end
  674. end
  675. def username_over_limit
  676. 'a' * described_class::USERNAME_LENGTH_LIMIT * 2
  677. end
  678. def account_note_over_limit
  679. 'a' * described_class::NOTE_LENGTH_LIMIT * 2
  680. end
  681. end
  682. describe 'scopes' do
  683. describe 'matches_uri_prefix' do
  684. let!(:alice) { Fabricate :account, domain: 'host.example', uri: 'https://host.example/user/a' }
  685. let!(:bob) { Fabricate :account, domain: 'top-level.example', uri: 'https://top-level.example' }
  686. it 'returns accounts which start with the value' do
  687. results = described_class.matches_uri_prefix('https://host.example')
  688. expect(results.size)
  689. .to eq(1)
  690. expect(results)
  691. .to include(alice)
  692. .and not_include(bob)
  693. end
  694. it 'returns accounts which equal the value' do
  695. results = described_class.matches_uri_prefix('https://top-level.example')
  696. expect(results.size)
  697. .to eq(1)
  698. expect(results)
  699. .to include(bob)
  700. .and not_include(alice)
  701. end
  702. end
  703. describe 'auditable' do
  704. let!(:alice) { Fabricate :account }
  705. let!(:bob) { Fabricate :account }
  706. before do
  707. 2.times { Fabricate :action_log, account: alice }
  708. end
  709. it 'returns distinct accounts with action log records' do
  710. results = described_class.auditable
  711. expect(results.size)
  712. .to eq(1)
  713. expect(results)
  714. .to include(alice)
  715. .and not_include(bob)
  716. end
  717. end
  718. describe 'alphabetic' do
  719. it 'sorts by alphabetic order of domain and username' do
  720. matches = [
  721. { username: 'a', domain: 'a' },
  722. { username: 'b', domain: 'a' },
  723. { username: 'a', domain: 'b' },
  724. { username: 'b', domain: 'b' },
  725. ].map(&method(:Fabricate).curry(2).call(:account))
  726. expect(described_class.without_internal.alphabetic).to eq matches
  727. end
  728. end
  729. describe 'matches_display_name' do
  730. it 'matches display name which starts with the given string' do
  731. match = Fabricate(:account, display_name: 'pattern and suffix')
  732. Fabricate(:account, display_name: 'prefix and pattern')
  733. expect(described_class.matches_display_name('pattern')).to eq [match]
  734. end
  735. end
  736. describe 'matches_username' do
  737. it 'matches display name which starts with the given string' do
  738. match = Fabricate(:account, username: 'pattern_and_suffix')
  739. Fabricate(:account, username: 'prefix_and_pattern')
  740. expect(described_class.matches_username('pattern')).to eq [match]
  741. end
  742. end
  743. describe 'by_domain_and_subdomains' do
  744. it 'returns exact domain matches' do
  745. account = Fabricate(:account, domain: 'example.com')
  746. expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
  747. end
  748. it 'returns subdomains' do
  749. account = Fabricate(:account, domain: 'foo.example.com')
  750. expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
  751. end
  752. it 'does not return partially matching domains' do
  753. account = Fabricate(:account, domain: 'grexample.com')
  754. expect(described_class.by_domain_and_subdomains('example.com')).to_not eq [account]
  755. end
  756. end
  757. describe 'remote' do
  758. it 'returns an array of accounts who have a domain' do
  759. _account = Fabricate(:account, domain: nil)
  760. account_with_domain = Fabricate(:account, domain: 'example.com')
  761. expect(described_class.remote).to contain_exactly(account_with_domain)
  762. end
  763. end
  764. describe 'local' do
  765. it 'returns an array of accounts who do not have a domain' do
  766. local_account = Fabricate(:account, domain: nil)
  767. _account_with_domain = Fabricate(:account, domain: 'example.com')
  768. expect(described_class.without_internal.local).to contain_exactly(local_account)
  769. end
  770. end
  771. describe 'partitioned' do
  772. it 'returns a relation of accounts partitioned by domain' do
  773. matches = %w(a b a b)
  774. matches.size.times.to_a.shuffle.each do |index|
  775. matches[index] = Fabricate(:account, domain: matches[index])
  776. end
  777. expect(described_class.without_internal.partitioned).to match_array(matches)
  778. end
  779. end
  780. describe 'recent' do
  781. it 'returns a relation of accounts sorted by recent creation' do
  782. matches = Array.new(2) { Fabricate(:account) }
  783. expect(described_class.without_internal.recent).to match_array(matches)
  784. end
  785. end
  786. describe 'silenced' do
  787. it 'returns an array of accounts who are silenced' do
  788. silenced_account = Fabricate(:account, silenced: true)
  789. _account = Fabricate(:account, silenced: false)
  790. expect(described_class.silenced).to contain_exactly(silenced_account)
  791. end
  792. end
  793. describe 'suspended' do
  794. it 'returns an array of accounts who are suspended' do
  795. suspended_account = Fabricate(:account, suspended: true)
  796. _account = Fabricate(:account, suspended: false)
  797. expect(described_class.suspended).to contain_exactly(suspended_account)
  798. end
  799. end
  800. describe 'searchable' do
  801. let!(:suspended_local) { Fabricate(:account, suspended: true, username: 'suspended_local') }
  802. let!(:suspended_remote) { Fabricate(:account, suspended: true, domain: 'example.org', username: 'suspended_remote') }
  803. let!(:silenced_local) { Fabricate(:account, silenced: true, username: 'silenced_local') }
  804. let!(:silenced_remote) { Fabricate(:account, silenced: true, domain: 'example.org', username: 'silenced_remote') }
  805. let!(:unconfirmed) { Fabricate(:user, confirmed_at: nil).account }
  806. let!(:unapproved) { Fabricate(:user, approved: false).account }
  807. let!(:unconfirmed_unapproved) { Fabricate(:user, confirmed_at: nil, approved: false).account }
  808. let!(:local_account) { Fabricate(:account, username: 'local_account') }
  809. let!(:remote_account) { Fabricate(:account, domain: 'example.org', username: 'remote_account') }
  810. before do
  811. # Accounts get automatically-approved depending on settings, so ensure they aren't approved
  812. unapproved.user.update(approved: false)
  813. unconfirmed_unapproved.user.update(approved: false)
  814. end
  815. it 'returns every usable non-suspended account' do
  816. expect(described_class.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account)
  817. expect(described_class.searchable).to_not include(suspended_local, suspended_remote, unconfirmed, unapproved)
  818. end
  819. it 'does not mess with previously-applied scopes' do
  820. expect(described_class.where.not(id: remote_account.id).searchable).to contain_exactly(silenced_local, silenced_remote, local_account)
  821. end
  822. end
  823. end
  824. context 'when is local' do
  825. it 'generates keys' do
  826. account = described_class.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_']))
  827. expect(account.keypair).to be_private
  828. expect(account.keypair).to be_public
  829. end
  830. end
  831. context 'when is remote' do
  832. it 'does not generate keys' do
  833. key = OpenSSL::PKey::RSA.new(1024).public_key
  834. account = described_class.create!(domain: 'remote', uri: 'https://remote/actor', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem)
  835. expect(account.keypair.params).to eq key.params
  836. end
  837. it 'normalizes domain' do
  838. account = described_class.create!(domain: 'にゃん', uri: 'https://xn--r9j5b5b/actor', username: Faker::Internet.user_name(separators: ['_']))
  839. expect(account.domain).to eq 'xn--r9j5b5b'
  840. end
  841. end
  842. include_examples 'AccountAvatar', :account
  843. include_examples 'AccountHeader', :account
  844. describe '#increment_count!' do
  845. subject { Fabricate(:account) }
  846. it 'increments the count in multi-threaded an environment when account_stat is not yet initialized' do
  847. subject
  848. multi_threaded_execution(15) do
  849. described_class.find(subject.id).increment_count!(:followers_count)
  850. end
  851. expect(subject.reload.followers_count).to eq 15
  852. end
  853. end
  854. end