1
0

account_spec.rb 32 KB

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