account_spec.rb 32 KB

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