1
0

account_spec.rb 33 KB

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