account_spec.rb 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  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 'does not call ResolveAccountService#call' do
  168. service = instance_double(ResolveAccountService, call: nil)
  169. allow(ResolveAccountService).to receive(:new).and_return(service)
  170. account.refresh!
  171. expect(service).to_not have_received(:call).with(acct)
  172. end
  173. end
  174. context 'when domain is present' do
  175. let(:domain) { 'example.com' }
  176. it 'calls ResolveAccountService#call' do
  177. service = instance_double(ResolveAccountService, call: nil)
  178. allow(ResolveAccountService).to receive(:new).and_return(service)
  179. account.refresh!
  180. expect(service).to have_received(:call).with(acct).once
  181. end
  182. end
  183. end
  184. describe '#to_param' do
  185. it 'returns username' do
  186. account = Fabricate(:account, username: 'alice')
  187. expect(account.to_param).to eq 'alice'
  188. end
  189. end
  190. describe '#keypair' do
  191. it 'returns an RSA key pair' do
  192. account = Fabricate(:account)
  193. expect(account.keypair).to be_instance_of OpenSSL::PKey::RSA
  194. end
  195. end
  196. describe '#object_type' do
  197. it 'is always a person' do
  198. account = Fabricate(:account)
  199. expect(account.object_type).to be :person
  200. end
  201. end
  202. describe '#favourited?' do
  203. subject { Fabricate(:account) }
  204. let(:original_status) do
  205. author = Fabricate(:account, username: 'original')
  206. Fabricate(:status, account: author)
  207. end
  208. context 'when the status is a reblog of another status' do
  209. let(:original_reblog) do
  210. author = Fabricate(:account, username: 'original_reblogger')
  211. Fabricate(:status, reblog: original_status, account: author)
  212. end
  213. it 'is true when this account has favourited it' do
  214. Fabricate(:favourite, status: original_reblog, account: subject)
  215. expect(subject.favourited?(original_status)).to be true
  216. end
  217. it 'is false when this account has not favourited it' do
  218. expect(subject.favourited?(original_status)).to be false
  219. end
  220. end
  221. context 'when the status is an original status' do
  222. it 'is true when this account has favourited it' do
  223. Fabricate(:favourite, status: original_status, account: subject)
  224. expect(subject.favourited?(original_status)).to be true
  225. end
  226. it 'is false when this account has not favourited it' do
  227. expect(subject.favourited?(original_status)).to be false
  228. end
  229. end
  230. end
  231. describe '#reblogged?' do
  232. subject { Fabricate(:account) }
  233. let(:original_status) do
  234. author = Fabricate(:account, username: 'original')
  235. Fabricate(:status, account: author)
  236. end
  237. context 'when the status is a reblog of another status' do
  238. let(:original_reblog) do
  239. author = Fabricate(:account, username: 'original_reblogger')
  240. Fabricate(:status, reblog: original_status, account: author)
  241. end
  242. it 'is true when this account has reblogged it' do
  243. Fabricate(:status, reblog: original_reblog, account: subject)
  244. expect(subject.reblogged?(original_reblog)).to be true
  245. end
  246. it 'is false when this account has not reblogged it' do
  247. expect(subject.reblogged?(original_reblog)).to be false
  248. end
  249. end
  250. context 'when the status is an original status' do
  251. it 'is true when this account has reblogged it' do
  252. Fabricate(:status, reblog: original_status, account: subject)
  253. expect(subject.reblogged?(original_status)).to be true
  254. end
  255. it 'is false when this account has not reblogged it' do
  256. expect(subject.reblogged?(original_status)).to be false
  257. end
  258. end
  259. end
  260. describe '#excluded_from_timeline_account_ids' do
  261. it 'includes account ids of blockings, blocked_bys and mutes' do
  262. account = Fabricate(:account)
  263. block = Fabricate(:block, account: account)
  264. mute = Fabricate(:mute, account: account)
  265. block_by = Fabricate(:block, target_account: account)
  266. results = account.excluded_from_timeline_account_ids
  267. expect(results.size).to eq 3
  268. expect(results).to include(
  269. block.target_account.id,
  270. mute.target_account.id,
  271. block_by.account.id
  272. )
  273. end
  274. end
  275. describe '#excluded_from_timeline_domains' do
  276. it 'returns the domains blocked by the account' do
  277. account = Fabricate(:account)
  278. account.block_domain!('domain')
  279. expect(account.excluded_from_timeline_domains).to contain_exactly('domain')
  280. end
  281. end
  282. describe '.search_for' do
  283. before do
  284. _missing = Fabricate(
  285. :account,
  286. display_name: 'Missing',
  287. username: 'missing',
  288. domain: 'missing.com'
  289. )
  290. end
  291. it 'does not return suspended users' do
  292. Fabricate(
  293. :account,
  294. display_name: 'Display Name',
  295. username: 'username',
  296. domain: 'example.com',
  297. suspended: true
  298. )
  299. results = described_class.search_for('username')
  300. expect(results).to eq []
  301. end
  302. it 'does not return unapproved users' do
  303. match = Fabricate(
  304. :account,
  305. display_name: 'Display Name',
  306. username: 'username'
  307. )
  308. match.user.update(approved: false)
  309. results = described_class.search_for('username')
  310. expect(results).to eq []
  311. end
  312. it 'does not return unconfirmed users' do
  313. match = Fabricate(
  314. :account,
  315. display_name: 'Display Name',
  316. username: 'username'
  317. )
  318. match.user.update(confirmed_at: nil)
  319. results = described_class.search_for('username')
  320. expect(results).to eq []
  321. end
  322. it 'accepts ?, \, : and space as delimiter' do
  323. match = Fabricate(
  324. :account,
  325. display_name: 'A & l & i & c & e',
  326. username: 'username',
  327. domain: 'example.com'
  328. )
  329. results = described_class.search_for('A?l\i:c e')
  330. expect(results).to eq [match]
  331. end
  332. it 'finds accounts with matching display_name' do
  333. match = Fabricate(
  334. :account,
  335. display_name: 'Display Name',
  336. username: 'username',
  337. domain: 'example.com'
  338. )
  339. results = described_class.search_for('display')
  340. expect(results).to eq [match]
  341. end
  342. it 'finds accounts with matching username' do
  343. match = Fabricate(
  344. :account,
  345. display_name: 'Display Name',
  346. username: 'username',
  347. domain: 'example.com'
  348. )
  349. results = described_class.search_for('username')
  350. expect(results).to eq [match]
  351. end
  352. it 'finds accounts with matching domain' do
  353. match = Fabricate(
  354. :account,
  355. display_name: 'Display Name',
  356. username: 'username',
  357. domain: 'example.com'
  358. )
  359. results = described_class.search_for('example')
  360. expect(results).to eq [match]
  361. end
  362. it 'limits via constant by default' do
  363. stub_const('Account::Search::DEFAULT_LIMIT', 1)
  364. 2.times.each { Fabricate(:account, display_name: 'Display Name') }
  365. results = described_class.search_for('display')
  366. expect(results.size).to eq 1
  367. end
  368. it 'accepts arbitrary limits' do
  369. 2.times.each { Fabricate(:account, display_name: 'Display Name') }
  370. results = described_class.search_for('display', limit: 1)
  371. expect(results.size).to eq 1
  372. end
  373. it 'ranks multiple matches higher' do
  374. matches = [
  375. { username: 'username', display_name: 'username' },
  376. { display_name: 'Display Name', username: 'username', domain: 'example.com' },
  377. ].map(&method(:Fabricate).curry(2).call(:account))
  378. results = described_class.search_for('username')
  379. expect(results).to eq matches
  380. end
  381. end
  382. describe '.advanced_search_for' do
  383. let(:account) { Fabricate(:account) }
  384. context 'when limiting search to followed accounts' do
  385. it 'accepts ?, \, : and space as delimiter' do
  386. match = Fabricate(
  387. :account,
  388. display_name: 'A & l & i & c & e',
  389. username: 'username',
  390. domain: 'example.com'
  391. )
  392. account.follow!(match)
  393. results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
  394. expect(results).to eq [match]
  395. end
  396. it 'does not return non-followed accounts' do
  397. Fabricate(
  398. :account,
  399. display_name: 'A & l & i & c & e',
  400. username: 'username',
  401. domain: 'example.com'
  402. )
  403. results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
  404. expect(results).to eq []
  405. end
  406. it 'does not return suspended users' do
  407. Fabricate(
  408. :account,
  409. display_name: 'Display Name',
  410. username: 'username',
  411. domain: 'example.com',
  412. suspended: true
  413. )
  414. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  415. expect(results).to eq []
  416. end
  417. it 'does not return unapproved users' do
  418. match = Fabricate(
  419. :account,
  420. display_name: 'Display Name',
  421. username: 'username'
  422. )
  423. match.user.update(approved: false)
  424. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  425. expect(results).to eq []
  426. end
  427. it 'does not return unconfirmed users' do
  428. match = Fabricate(
  429. :account,
  430. display_name: 'Display Name',
  431. username: 'username'
  432. )
  433. match.user.update(confirmed_at: nil)
  434. results = described_class.advanced_search_for('username', account, limit: 10, following: true)
  435. expect(results).to eq []
  436. end
  437. end
  438. it 'does not return suspended users' do
  439. Fabricate(
  440. :account,
  441. display_name: 'Display Name',
  442. username: 'username',
  443. domain: 'example.com',
  444. suspended: true
  445. )
  446. results = described_class.advanced_search_for('username', account)
  447. expect(results).to eq []
  448. end
  449. it 'does not return unapproved users' do
  450. match = Fabricate(
  451. :account,
  452. display_name: 'Display Name',
  453. username: 'username'
  454. )
  455. match.user.update(approved: false)
  456. results = described_class.advanced_search_for('username', account)
  457. expect(results).to eq []
  458. end
  459. it 'does not return unconfirmed users' do
  460. match = Fabricate(
  461. :account,
  462. display_name: 'Display Name',
  463. username: 'username'
  464. )
  465. match.user.update(confirmed_at: nil)
  466. results = described_class.advanced_search_for('username', account)
  467. expect(results).to eq []
  468. end
  469. it 'accepts ?, \, : and space as delimiter' do
  470. match = Fabricate(
  471. :account,
  472. display_name: 'A & l & i & c & e',
  473. username: 'username',
  474. domain: 'example.com'
  475. )
  476. results = described_class.advanced_search_for('A?l\i:c e', account)
  477. expect(results).to eq [match]
  478. end
  479. it 'limits by 10 by default' do
  480. stub_const('Account::Search::DEFAULT_LIMIT', 1)
  481. 2.times { Fabricate(:account, display_name: 'Display Name') }
  482. results = described_class.advanced_search_for('display', account)
  483. expect(results.size).to eq 1
  484. end
  485. it 'accepts arbitrary limits' do
  486. 2.times { Fabricate(:account, display_name: 'Display Name') }
  487. results = described_class.advanced_search_for('display', account, limit: 1)
  488. expect(results.size).to eq 1
  489. end
  490. it 'ranks followed accounts higher' do
  491. match = Fabricate(:account, username: 'Matching')
  492. followed_match = Fabricate(:account, username: 'Matcher')
  493. Fabricate(:follow, account: account, target_account: followed_match)
  494. results = described_class.advanced_search_for('match', account)
  495. expect(results).to eq [followed_match, match]
  496. expect(results.first.rank).to be > results.last.rank
  497. end
  498. end
  499. describe '#statuses_count' do
  500. subject { Fabricate(:account) }
  501. it 'counts statuses' do
  502. Fabricate(:status, account: subject)
  503. Fabricate(:status, account: subject)
  504. expect(subject.statuses_count).to eq 2
  505. end
  506. it 'does not count direct statuses' do
  507. Fabricate(:status, account: subject, visibility: :direct)
  508. expect(subject.statuses_count).to eq 0
  509. end
  510. it 'is decremented when status is removed' do
  511. status = Fabricate(:status, account: subject)
  512. expect(subject.statuses_count).to eq 1
  513. status.destroy
  514. expect(subject.statuses_count).to eq 0
  515. end
  516. it 'is decremented when status is removed when account is not preloaded' do
  517. status = Fabricate(:status, account: subject)
  518. expect(subject.reload.statuses_count).to eq 1
  519. clean_status = Status.find(status.id)
  520. expect(clean_status.association(:account).loaded?).to be false
  521. clean_status.destroy
  522. expect(subject.reload.statuses_count).to eq 0
  523. end
  524. end
  525. describe '.following_map' do
  526. it 'returns an hash' do
  527. expect(described_class.following_map([], 1)).to be_a Hash
  528. end
  529. end
  530. describe '.followed_by_map' do
  531. it 'returns an hash' do
  532. expect(described_class.followed_by_map([], 1)).to be_a Hash
  533. end
  534. end
  535. describe '.blocking_map' do
  536. it 'returns an hash' do
  537. expect(described_class.blocking_map([], 1)).to be_a Hash
  538. end
  539. end
  540. describe '.requested_map' do
  541. it 'returns an hash' do
  542. expect(described_class.requested_map([], 1)).to be_a Hash
  543. end
  544. end
  545. describe '.requested_by_map' do
  546. it 'returns an hash' do
  547. expect(described_class.requested_by_map([], 1)).to be_a Hash
  548. end
  549. end
  550. describe 'MENTION_RE' do
  551. subject { Account::MENTION_RE }
  552. it 'matches usernames in the middle of a sentence' do
  553. expect(subject.match('Hello to @alice from me')[1]).to eq 'alice'
  554. end
  555. it 'matches usernames in the beginning of status' do
  556. expect(subject.match('@alice Hey how are you?')[1]).to eq 'alice'
  557. end
  558. it 'matches full usernames' do
  559. expect(subject.match('@alice@example.com')[1]).to eq 'alice@example.com'
  560. end
  561. it 'matches full usernames with a dot at the end' do
  562. expect(subject.match('Hello @alice@example.com.')[1]).to eq 'alice@example.com'
  563. end
  564. it 'matches dot-prepended usernames' do
  565. expect(subject.match('.@alice I want everybody to see this')[1]).to eq 'alice'
  566. end
  567. it 'does not match e-mails' do
  568. expect(subject.match('Drop me an e-mail at alice@example.com')).to be_nil
  569. end
  570. it 'does not match URLs' do
  571. expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
  572. end
  573. it 'does not match URL query string' do
  574. expect(subject.match('https://example.com/?x=@alice')).to be_nil
  575. end
  576. end
  577. describe 'validations' do
  578. it 'is invalid without a username' do
  579. account = Fabricate.build(:account, username: nil)
  580. account.valid?
  581. expect(account).to model_have_error_on_field(:username)
  582. end
  583. it 'squishes the username before validation' do
  584. account = Fabricate(:account, domain: nil, username: " \u3000bob \t \u00a0 \n ")
  585. expect(account.username).to eq 'bob'
  586. end
  587. context 'when is local' do
  588. it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
  589. _account = Fabricate(:account, username: 'the_doctor')
  590. non_unique_account = Fabricate.build(:account, username: 'the_Doctor')
  591. non_unique_account.valid?
  592. expect(non_unique_account).to model_have_error_on_field(:username)
  593. end
  594. it 'is invalid if the username is reserved' do
  595. account = Fabricate.build(:account, username: 'support')
  596. account.valid?
  597. expect(account).to model_have_error_on_field(:username)
  598. end
  599. it 'is valid when username is reserved but record has already been created' do
  600. account = Fabricate.build(:account, username: 'support')
  601. account.save(validate: false)
  602. expect(account.valid?).to be true
  603. end
  604. it 'is valid if we are creating an instance actor account with a period' do
  605. account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
  606. expect(account.valid?).to be true
  607. end
  608. it 'is valid if we are creating a possibly-conflicting instance actor account' do
  609. _account = Fabricate(:account, username: 'examplecom')
  610. instance_account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
  611. expect(instance_account.valid?).to be true
  612. end
  613. it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
  614. account = Fabricate.build(:account, username: 'the-doctor')
  615. account.valid?
  616. expect(account).to model_have_error_on_field(:username)
  617. end
  618. it 'is invalid if the username contains a period' do
  619. account = Fabricate.build(:account, username: 'the.doctor')
  620. account.valid?
  621. expect(account).to model_have_error_on_field(:username)
  622. end
  623. it 'is invalid if the username is longer than 30 characters' do
  624. account = Fabricate.build(:account, username: Faker::Lorem.characters(number: 31))
  625. account.valid?
  626. expect(account).to model_have_error_on_field(:username)
  627. end
  628. it 'is invalid if the display name is longer than 30 characters' do
  629. account = Fabricate.build(:account, display_name: Faker::Lorem.characters(number: 31))
  630. account.valid?
  631. expect(account).to model_have_error_on_field(:display_name)
  632. end
  633. it 'is invalid if the note is longer than 500 characters' do
  634. account = Fabricate.build(:account, note: Faker::Lorem.characters(number: 501))
  635. account.valid?
  636. expect(account).to model_have_error_on_field(:note)
  637. end
  638. end
  639. context 'when is remote' do
  640. it 'is invalid if the username is same among accounts in the same normalized domain' do
  641. Fabricate(:account, domain: 'にゃん', username: 'username')
  642. account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'username')
  643. account.valid?
  644. expect(account).to model_have_error_on_field(:username)
  645. end
  646. it 'is invalid if the username is not unique in case-insensitive comparison among accounts in the same normalized domain' do
  647. Fabricate(:account, domain: 'にゃん', username: 'username')
  648. account = Fabricate.build(:account, domain: 'xn--r9j5b5b', username: 'Username')
  649. account.valid?
  650. expect(account).to model_have_error_on_field(:username)
  651. end
  652. it 'is valid even if the username contains hyphens' do
  653. account = Fabricate.build(:account, domain: 'domain', username: 'the-doctor')
  654. account.valid?
  655. expect(account).to_not model_have_error_on_field(:username)
  656. end
  657. it 'is invalid if the username doesn\'t only contains letters, numbers, underscores and hyphens' do
  658. account = Fabricate.build(:account, domain: 'domain', username: 'the doctor')
  659. account.valid?
  660. expect(account).to model_have_error_on_field(:username)
  661. end
  662. it 'is valid even if the username is longer than 30 characters' do
  663. account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(number: 31))
  664. account.valid?
  665. expect(account).to_not model_have_error_on_field(:username)
  666. end
  667. it 'is valid even if the display name is longer than 30 characters' do
  668. account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(number: 31))
  669. account.valid?
  670. expect(account).to_not model_have_error_on_field(:display_name)
  671. end
  672. it 'is valid even if the note is longer than 500 characters' do
  673. account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(number: 501))
  674. account.valid?
  675. expect(account).to_not model_have_error_on_field(:note)
  676. end
  677. end
  678. end
  679. describe 'scopes' do
  680. describe 'alphabetic' do
  681. it 'sorts by alphabetic order of domain and username' do
  682. matches = [
  683. { username: 'a', domain: 'a' },
  684. { username: 'b', domain: 'a' },
  685. { username: 'a', domain: 'b' },
  686. { username: 'b', domain: 'b' },
  687. ].map(&method(:Fabricate).curry(2).call(:account))
  688. expect(described_class.where('id > 0').alphabetic).to eq matches
  689. end
  690. end
  691. describe 'matches_display_name' do
  692. it 'matches display name which starts with the given string' do
  693. match = Fabricate(:account, display_name: 'pattern and suffix')
  694. Fabricate(:account, display_name: 'prefix and pattern')
  695. expect(described_class.matches_display_name('pattern')).to eq [match]
  696. end
  697. end
  698. describe 'matches_username' do
  699. it 'matches display name which starts with the given string' do
  700. match = Fabricate(:account, username: 'pattern_and_suffix')
  701. Fabricate(:account, username: 'prefix_and_pattern')
  702. expect(described_class.matches_username('pattern')).to eq [match]
  703. end
  704. end
  705. describe 'by_domain_and_subdomains' do
  706. it 'returns exact domain matches' do
  707. account = Fabricate(:account, domain: 'example.com')
  708. expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
  709. end
  710. it 'returns subdomains' do
  711. account = Fabricate(:account, domain: 'foo.example.com')
  712. expect(described_class.by_domain_and_subdomains('example.com')).to eq [account]
  713. end
  714. it 'does not return partially matching domains' do
  715. account = Fabricate(:account, domain: 'grexample.com')
  716. expect(described_class.by_domain_and_subdomains('example.com')).to_not eq [account]
  717. end
  718. end
  719. describe 'remote' do
  720. it 'returns an array of accounts who have a domain' do
  721. _account = Fabricate(:account, domain: nil)
  722. account_with_domain = Fabricate(:account, domain: 'example.com')
  723. expect(described_class.remote).to contain_exactly(account_with_domain)
  724. end
  725. end
  726. describe 'local' do
  727. it 'returns an array of accounts who do not have a domain' do
  728. local_account = Fabricate(:account, domain: nil)
  729. _account_with_domain = Fabricate(:account, domain: 'example.com')
  730. expect(described_class.where('id > 0').local).to contain_exactly(local_account)
  731. end
  732. end
  733. describe 'partitioned' do
  734. it 'returns a relation of accounts partitioned by domain' do
  735. matches = %w(a b a b)
  736. matches.size.times.to_a.shuffle.each do |index|
  737. matches[index] = Fabricate(:account, domain: matches[index])
  738. end
  739. expect(described_class.where('id > 0').partitioned).to match_array(matches)
  740. end
  741. end
  742. describe 'recent' do
  743. it 'returns a relation of accounts sorted by recent creation' do
  744. matches = Array.new(2) { Fabricate(:account) }
  745. expect(described_class.where('id > 0').recent).to match_array(matches)
  746. end
  747. end
  748. describe 'silenced' do
  749. it 'returns an array of accounts who are silenced' do
  750. silenced_account = Fabricate(:account, silenced: true)
  751. _account = Fabricate(:account, silenced: false)
  752. expect(described_class.silenced).to contain_exactly(silenced_account)
  753. end
  754. end
  755. describe 'suspended' do
  756. it 'returns an array of accounts who are suspended' do
  757. suspended_account = Fabricate(:account, suspended: true)
  758. _account = Fabricate(:account, suspended: false)
  759. expect(described_class.suspended).to contain_exactly(suspended_account)
  760. end
  761. end
  762. describe 'searchable' do
  763. let!(:suspended_local) { Fabricate(:account, suspended: true, username: 'suspended_local') }
  764. let!(:suspended_remote) { Fabricate(:account, suspended: true, domain: 'example.org', username: 'suspended_remote') }
  765. let!(:silenced_local) { Fabricate(:account, silenced: true, username: 'silenced_local') }
  766. let!(:silenced_remote) { Fabricate(:account, silenced: true, domain: 'example.org', username: 'silenced_remote') }
  767. let!(:unconfirmed) { Fabricate(:user, confirmed_at: nil).account }
  768. let!(:unapproved) { Fabricate(:user, approved: false).account }
  769. let!(:unconfirmed_unapproved) { Fabricate(:user, confirmed_at: nil, approved: false).account }
  770. let!(:local_account) { Fabricate(:account, username: 'local_account') }
  771. let!(:remote_account) { Fabricate(:account, domain: 'example.org', username: 'remote_account') }
  772. before do
  773. # Accounts get automatically-approved depending on settings, so ensure they aren't approved
  774. unapproved.user.update(approved: false)
  775. unconfirmed_unapproved.user.update(approved: false)
  776. end
  777. it 'returns every usable non-suspended account' do
  778. expect(described_class.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account)
  779. expect(described_class.searchable).to_not include(suspended_local, suspended_remote, unconfirmed, unapproved)
  780. end
  781. it 'does not mess with previously-applied scopes' do
  782. expect(described_class.where.not(id: remote_account.id).searchable).to contain_exactly(silenced_local, silenced_remote, local_account)
  783. end
  784. end
  785. end
  786. context 'when is local' do
  787. it 'generates keys' do
  788. account = described_class.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_']))
  789. expect(account.keypair).to be_private
  790. expect(account.keypair).to be_public
  791. end
  792. end
  793. context 'when is remote' do
  794. it 'does not generate keys' do
  795. key = OpenSSL::PKey::RSA.new(1024).public_key
  796. account = described_class.create!(domain: 'remote', uri: 'https://remote/actor', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem)
  797. expect(account.keypair.params).to eq key.params
  798. end
  799. it 'normalizes domain' do
  800. account = described_class.create!(domain: 'にゃん', uri: 'https://xn--r9j5b5b/actor', username: Faker::Internet.user_name(separators: ['_']))
  801. expect(account.domain).to eq 'xn--r9j5b5b'
  802. end
  803. end
  804. include_examples 'AccountAvatar', :account
  805. include_examples 'AccountHeader', :account
  806. describe '#increment_count!' do
  807. subject { Fabricate(:account) }
  808. it 'increments the count in multi-threaded an environment when account_stat is not yet initialized' do
  809. subject
  810. increment_by = 15
  811. wait_for_start = true
  812. threads = Array.new(increment_by) do
  813. Thread.new do
  814. true while wait_for_start
  815. described_class.find(subject.id).increment_count!(:followers_count)
  816. end
  817. end
  818. wait_for_start = false
  819. threads.each(&:join)
  820. expect(subject.reload.followers_count).to eq 15
  821. end
  822. end
  823. describe '.followable_by' do
  824. context 'with follows and follow requests' do
  825. let!(:account) { Fabricate(:account) }
  826. let!(:eligible_account) { Fabricate(:account) }
  827. let!(:following_account) { Fabricate(:account) }
  828. let!(:follow_requested_account) { Fabricate(:account) }
  829. before do
  830. Fabricate :follow, account: account, target_account: following_account
  831. Fabricate :follow_request, account: account, target_account: follow_requested_account
  832. end
  833. it 'returns accounts not already following or requested to follow' do
  834. results = described_class.followable_by(account)
  835. expect(results)
  836. .to include(eligible_account)
  837. .and not_include(following_account)
  838. .and not_include(follow_requested_account)
  839. end
  840. end
  841. end
  842. end