The code powering m.abunchtell.com https://m.abunchtell.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

656 lines
20 KiB

  1. require 'rails_helper'
  2. RSpec.describe Status, type: :model do
  3. let(:alice) { Fabricate(:account, username: 'alice') }
  4. let(:bob) { Fabricate(:account, username: 'bob') }
  5. let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!')}
  6. subject { Fabricate(:status, account: alice) }
  7. describe '#local?' do
  8. it 'returns true when no remote URI is set' do
  9. expect(subject.local?).to be true
  10. end
  11. it 'returns false if a remote URI is set' do
  12. subject.uri = 'a'
  13. expect(subject.local?).to be false
  14. end
  15. end
  16. describe '#reblog?' do
  17. it 'returns true when the status reblogs another status' do
  18. subject.reblog = other
  19. expect(subject.reblog?).to be true
  20. end
  21. it 'returns false if the status is self-contained' do
  22. expect(subject.reblog?).to be false
  23. end
  24. end
  25. describe '#reply?' do
  26. it 'returns true if the status references another' do
  27. subject.thread = other
  28. expect(subject.reply?).to be true
  29. end
  30. it 'returns false if the status is self-contained' do
  31. expect(subject.reply?).to be false
  32. end
  33. end
  34. describe '#verb' do
  35. it 'is always post' do
  36. expect(subject.verb).to be :post
  37. end
  38. end
  39. describe '#object_type' do
  40. it 'is note when the status is self-contained' do
  41. expect(subject.object_type).to be :note
  42. end
  43. it 'is comment when the status replies to another' do
  44. subject.thread = other
  45. expect(subject.object_type).to be :comment
  46. end
  47. end
  48. describe '#title' do
  49. it 'is a shorter version of the content' do
  50. expect(subject.title).to be_a String
  51. end
  52. end
  53. describe '#content' do
  54. it 'returns the text of the status if it is not a reblog' do
  55. expect(subject.content).to eql subject.text
  56. end
  57. it 'returns the text of the reblogged status' do
  58. subject.reblog = other
  59. expect(subject.content).to eql other.text
  60. end
  61. end
  62. describe '#target' do
  63. it 'returns nil if the status is self-contained' do
  64. expect(subject.target).to be_nil
  65. end
  66. it 'returns nil if the status is a reply' do
  67. subject.thread = other
  68. expect(subject.target).to be_nil
  69. end
  70. it 'returns the reblogged status' do
  71. subject.reblog = other
  72. expect(subject.target).to eq other
  73. end
  74. end
  75. describe '#reblogs_count' do
  76. it 'is the number of reblogs' do
  77. Fabricate(:status, account: bob, reblog: subject)
  78. Fabricate(:status, account: alice, reblog: subject)
  79. expect(subject.reblogs_count).to eq 2
  80. end
  81. end
  82. describe '#favourites_count' do
  83. it 'is the number of favorites' do
  84. Fabricate(:favourite, account: bob, status: subject)
  85. Fabricate(:favourite, account: alice, status: subject)
  86. expect(subject.favourites_count).to eq 2
  87. end
  88. end
  89. describe '#proper' do
  90. it 'is itself for original statuses' do
  91. expect(subject.proper).to eq subject
  92. end
  93. it 'is the source status for reblogs' do
  94. subject.reblog = other
  95. expect(subject.proper).to eq other
  96. end
  97. end
  98. describe '#permitted?' do
  99. it 'returns true when direct and account is viewer' do
  100. subject.visibility = :direct
  101. expect(subject.permitted?(subject.account)).to be true
  102. end
  103. it 'returns true when direct and viewer is mentioned' do
  104. subject.visibility = :direct
  105. subject.mentions = [Fabricate(:mention, account: alice)]
  106. expect(subject.permitted?(alice)).to be true
  107. end
  108. it 'returns false when direct and viewer is not mentioned' do
  109. viewer = Fabricate(:account)
  110. subject.visibility = :direct
  111. expect(subject.permitted?(viewer)).to be false
  112. end
  113. it 'returns true when private and account is viewer' do
  114. subject.visibility = :direct
  115. expect(subject.permitted?(subject.account)).to be true
  116. end
  117. it 'returns true when private and account is following viewer' do
  118. follow = Fabricate(:follow)
  119. subject.visibility = :private
  120. subject.account = follow.target_account
  121. expect(subject.permitted?(follow.account)).to be true
  122. end
  123. it 'returns true when private and viewer is mentioned' do
  124. subject.visibility = :private
  125. subject.mentions = [Fabricate(:mention, account: alice)]
  126. expect(subject.permitted?(alice)).to be true
  127. end
  128. it 'returns false when private and viewer is not mentioned or followed' do
  129. viewer = Fabricate(:account)
  130. subject.visibility = :private
  131. expect(subject.permitted?(viewer)).to be false
  132. end
  133. it 'returns true when no viewer' do
  134. expect(subject.permitted?).to be true
  135. end
  136. it 'returns false when viewer is blocked' do
  137. block = Fabricate(:block)
  138. subject.visibility = :private
  139. subject.account = block.target_account
  140. expect(subject.permitted?(block.account)).to be false
  141. end
  142. end
  143. describe '#ancestors' do
  144. let!(:alice) { Fabricate(:account, username: 'alice') }
  145. let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') }
  146. let!(:jeff) { Fabricate(:account, username: 'jeff') }
  147. let!(:status) { Fabricate(:status, account: alice) }
  148. let!(:reply1) { Fabricate(:status, thread: status, account: jeff) }
  149. let!(:reply2) { Fabricate(:status, thread: reply1, account: bob) }
  150. let!(:reply3) { Fabricate(:status, thread: reply2, account: alice) }
  151. let!(:viewer) { Fabricate(:account, username: 'viewer') }
  152. it 'returns conversation history' do
  153. expect(reply3.ancestors).to include(status, reply1, reply2)
  154. end
  155. it 'does not return conversation history user is not allowed to see' do
  156. reply1.update(visibility: :private)
  157. status.update(visibility: :direct)
  158. expect(reply3.ancestors(viewer)).to_not include(reply1, status)
  159. end
  160. it 'does not return conversation history from blocked users' do
  161. viewer.block!(jeff)
  162. expect(reply3.ancestors(viewer)).to_not include(reply1)
  163. end
  164. it 'does not return conversation history from muted users' do
  165. viewer.mute!(jeff)
  166. expect(reply3.ancestors(viewer)).to_not include(reply1)
  167. end
  168. it 'does not return conversation history from silenced and not followed users' do
  169. jeff.update(silenced: true)
  170. expect(reply3.ancestors(viewer)).to_not include(reply1)
  171. end
  172. it 'does not return conversation history from blocked domains' do
  173. viewer.block_domain!('example.com')
  174. expect(reply3.ancestors(viewer)).to_not include(reply2)
  175. end
  176. it 'ignores deleted records' do
  177. first_status = Fabricate(:status, account: bob)
  178. second_status = Fabricate(:status, thread: first_status, account: alice)
  179. # Create cache and delete cached record
  180. second_status.ancestors
  181. first_status.destroy
  182. expect(second_status.ancestors).to eq([])
  183. end
  184. end
  185. describe '#descendants' do
  186. let!(:alice) { Fabricate(:account, username: 'alice') }
  187. let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') }
  188. let!(:jeff) { Fabricate(:account, username: 'jeff') }
  189. let!(:status) { Fabricate(:status, account: alice) }
  190. let!(:reply1) { Fabricate(:status, thread: status, account: alice) }
  191. let!(:reply2) { Fabricate(:status, thread: status, account: bob) }
  192. let!(:reply3) { Fabricate(:status, thread: reply1, account: jeff) }
  193. let!(:viewer) { Fabricate(:account, username: 'viewer') }
  194. it 'returns replies' do
  195. expect(status.descendants).to include(reply1, reply2, reply3)
  196. end
  197. it 'does not return replies user is not allowed to see' do
  198. reply1.update(visibility: :private)
  199. reply3.update(visibility: :direct)
  200. expect(status.descendants(viewer)).to_not include(reply1, reply3)
  201. end
  202. it 'does not return replies from blocked users' do
  203. viewer.block!(jeff)
  204. expect(status.descendants(viewer)).to_not include(reply3)
  205. end
  206. it 'does not return replies from muted users' do
  207. viewer.mute!(jeff)
  208. expect(status.descendants(viewer)).to_not include(reply3)
  209. end
  210. it 'does not return replies from silenced and not followed users' do
  211. jeff.update(silenced: true)
  212. expect(status.descendants(viewer)).to_not include(reply3)
  213. end
  214. it 'does not return replies from blocked domains' do
  215. viewer.block_domain!('example.com')
  216. expect(status.descendants(viewer)).to_not include(reply2)
  217. end
  218. end
  219. describe '.mutes_map' do
  220. let(:status) { Fabricate(:status) }
  221. let(:account) { Fabricate(:account) }
  222. subject { Status.mutes_map([status.conversation.id], account) }
  223. it 'returns a hash' do
  224. expect(subject).to be_a Hash
  225. end
  226. it 'contains true value' do
  227. account.mute_conversation!(status.conversation)
  228. expect(subject[status.conversation.id]).to be true
  229. end
  230. end
  231. describe '.favourites_map' do
  232. let(:status) { Fabricate(:status) }
  233. let(:account) { Fabricate(:account) }
  234. subject { Status.favourites_map([status], account) }
  235. it 'returns a hash' do
  236. expect(subject).to be_a Hash
  237. end
  238. it 'contains true value' do
  239. Fabricate(:favourite, status: status, account: account)
  240. expect(subject[status.id]).to be true
  241. end
  242. end
  243. describe '.reblogs_map' do
  244. let(:status) { Fabricate(:status) }
  245. let(:account) { Fabricate(:account) }
  246. subject { Status.reblogs_map([status], account) }
  247. it 'returns a hash' do
  248. expect(subject).to be_a Hash
  249. end
  250. it 'contains true value' do
  251. Fabricate(:status, account: account, reblog: status)
  252. expect(subject[status.id]).to be true
  253. end
  254. end
  255. describe '.local_only' do
  256. it 'returns only statuses from local accounts' do
  257. local_account = Fabricate(:account, domain: nil)
  258. remote_account = Fabricate(:account, domain: 'test.com')
  259. local_status = Fabricate(:status, account: local_account)
  260. remote_status = Fabricate(:status, account: remote_account)
  261. results = described_class.local_only
  262. expect(results).to include(local_status)
  263. expect(results).not_to include(remote_status)
  264. end
  265. end
  266. describe '.as_home_timeline' do
  267. before do
  268. account = Fabricate(:account)
  269. followed = Fabricate(:account)
  270. not_followed = Fabricate(:account)
  271. Fabricate(:follow, account: account, target_account: followed)
  272. @self_status = Fabricate(:status, account: account)
  273. @followed_status = Fabricate(:status, account: followed)
  274. @not_followed_status = Fabricate(:status, account: not_followed)
  275. @results = Status.as_home_timeline(account)
  276. end
  277. it 'includes statuses from self' do
  278. expect(@results).to include(@self_status)
  279. end
  280. it 'includes statuses from followed' do
  281. expect(@results).to include(@followed_status)
  282. end
  283. it 'does not include statuses from non-followed' do
  284. expect(@results).not_to include(@not_followed_status)
  285. end
  286. end
  287. describe '.as_public_timeline' do
  288. it 'only includes statuses with public visibility' do
  289. public_status = Fabricate(:status, visibility: :public)
  290. private_status = Fabricate(:status, visibility: :private)
  291. results = Status.as_public_timeline
  292. expect(results).to include(public_status)
  293. expect(results).not_to include(private_status)
  294. end
  295. it 'does not include replies' do
  296. status = Fabricate(:status)
  297. reply = Fabricate(:status, in_reply_to_id: status.id)
  298. results = Status.as_public_timeline
  299. expect(results).to include(status)
  300. expect(results).not_to include(reply)
  301. end
  302. it 'does not include boosts' do
  303. status = Fabricate(:status)
  304. boost = Fabricate(:status, reblog_of_id: status.id)
  305. results = Status.as_public_timeline
  306. expect(results).to include(status)
  307. expect(results).not_to include(boost)
  308. end
  309. it 'filters out silenced accounts' do
  310. account = Fabricate(:account)
  311. silenced_account = Fabricate(:account, silenced: true)
  312. status = Fabricate(:status, account: account)
  313. silenced_status = Fabricate(:status, account: silenced_account)
  314. results = Status.as_public_timeline
  315. expect(results).to include(status)
  316. expect(results).not_to include(silenced_status)
  317. end
  318. context 'without local_only option' do
  319. let(:viewer) { nil }
  320. let!(:local_account) { Fabricate(:account, domain: nil) }
  321. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  322. let!(:local_status) { Fabricate(:status, account: local_account) }
  323. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  324. subject { Status.as_public_timeline(viewer, false) }
  325. context 'without a viewer' do
  326. let(:viewer) { nil }
  327. it 'includes remote instances statuses' do
  328. expect(subject).to include(remote_status)
  329. end
  330. it 'includes local statuses' do
  331. expect(subject).to include(local_status)
  332. end
  333. end
  334. context 'with a viewer' do
  335. let(:viewer) { Fabricate(:account, username: 'viewer') }
  336. it 'includes remote instances statuses' do
  337. expect(subject).to include(remote_status)
  338. end
  339. it 'includes local statuses' do
  340. expect(subject).to include(local_status)
  341. end
  342. end
  343. end
  344. context 'with a local_only option set' do
  345. let!(:local_account) { Fabricate(:account, domain: nil) }
  346. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  347. let!(:local_status) { Fabricate(:status, account: local_account) }
  348. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  349. subject { Status.as_public_timeline(viewer, true) }
  350. context 'without a viewer' do
  351. let(:viewer) { nil }
  352. it 'does not include remote instances statuses' do
  353. expect(subject).to include(local_status)
  354. expect(subject).not_to include(remote_status)
  355. end
  356. end
  357. context 'with a viewer' do
  358. let(:viewer) { Fabricate(:account, username: 'viewer') }
  359. it 'does not include remote instances statuses' do
  360. expect(subject).to include(local_status)
  361. expect(subject).not_to include(remote_status)
  362. end
  363. it 'is not affected by personal domain blocks' do
  364. viewer.block_domain!('test.com')
  365. expect(subject).to include(local_status)
  366. expect(subject).not_to include(remote_status)
  367. end
  368. end
  369. end
  370. describe 'with an account passed in' do
  371. before do
  372. @account = Fabricate(:account)
  373. end
  374. it 'excludes statuses from accounts blocked by the account' do
  375. blocked = Fabricate(:account)
  376. Fabricate(:block, account: @account, target_account: blocked)
  377. blocked_status = Fabricate(:status, account: blocked)
  378. results = Status.as_public_timeline(@account)
  379. expect(results).not_to include(blocked_status)
  380. end
  381. it 'excludes statuses from accounts who have blocked the account' do
  382. blocked = Fabricate(:account)
  383. Fabricate(:block, account: blocked, target_account: @account)
  384. blocked_status = Fabricate(:status, account: blocked)
  385. results = Status.as_public_timeline(@account)
  386. expect(results).not_to include(blocked_status)
  387. end
  388. it 'excludes statuses from accounts muted by the account' do
  389. muted = Fabricate(:account)
  390. Fabricate(:mute, account: @account, target_account: muted)
  391. muted_status = Fabricate(:status, account: muted)
  392. results = Status.as_public_timeline(@account)
  393. expect(results).not_to include(muted_status)
  394. end
  395. it 'excludes statuses from accounts from personally blocked domains' do
  396. blocked = Fabricate(:account, domain: 'example.com')
  397. @account.block_domain!(blocked.domain)
  398. blocked_status = Fabricate(:status, account: blocked)
  399. results = Status.as_public_timeline(@account)
  400. expect(results).not_to include(blocked_status)
  401. end
  402. context 'with language preferences' do
  403. it 'excludes statuses in languages not allowed by the account user' do
  404. user = Fabricate(:user, filtered_languages: [:fr])
  405. @account.update(user: user)
  406. en_status = Fabricate(:status, language: 'en')
  407. es_status = Fabricate(:status, language: 'es')
  408. fr_status = Fabricate(:status, language: 'fr')
  409. results = Status.as_public_timeline(@account)
  410. expect(results).to include(en_status)
  411. expect(results).to include(es_status)
  412. expect(results).not_to include(fr_status)
  413. end
  414. it 'includes all languages when user does not have a setting' do
  415. user = Fabricate(:user, filtered_languages: [])
  416. @account.update(user: user)
  417. en_status = Fabricate(:status, language: 'en')
  418. es_status = Fabricate(:status, language: 'es')
  419. results = Status.as_public_timeline(@account)
  420. expect(results).to include(en_status)
  421. expect(results).to include(es_status)
  422. end
  423. it 'includes all languages when account does not have a user' do
  424. expect(@account.user).to be_nil
  425. en_status = Fabricate(:status, language: 'en')
  426. es_status = Fabricate(:status, language: 'es')
  427. results = Status.as_public_timeline(@account)
  428. expect(results).to include(en_status)
  429. expect(results).to include(es_status)
  430. end
  431. end
  432. context 'where that account is silenced' do
  433. it 'includes statuses from other accounts that are silenced' do
  434. @account.update(silenced: true)
  435. other_silenced_account = Fabricate(:account, silenced: true)
  436. other_status = Fabricate(:status, account: other_silenced_account)
  437. results = Status.as_public_timeline(@account)
  438. expect(results).to include(other_status)
  439. end
  440. end
  441. end
  442. end
  443. describe '.as_tag_timeline' do
  444. it 'includes statuses with a tag' do
  445. tag = Fabricate(:tag)
  446. status = Fabricate(:status, tags: [tag])
  447. other = Fabricate(:status)
  448. results = Status.as_tag_timeline(tag)
  449. expect(results).to include(status)
  450. expect(results).not_to include(other)
  451. end
  452. it 'allows replies to be included' do
  453. original = Fabricate(:status)
  454. tag = Fabricate(:tag)
  455. status = Fabricate(:status, tags: [tag], in_reply_to_id: original.id)
  456. results = Status.as_tag_timeline(tag)
  457. expect(results).to include(status)
  458. end
  459. end
  460. describe '.permitted_for' do
  461. subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
  462. let(:target_account) { alice }
  463. let(:account) { bob }
  464. let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
  465. let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
  466. let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
  467. let!(:direct_status) do
  468. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  469. Fabricate(:mention, status: status, account: account)
  470. end
  471. end
  472. let!(:other_direct_status) do
  473. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  474. Fabricate(:mention, status: status)
  475. end
  476. end
  477. context 'given nil' do
  478. let(:account) { nil }
  479. let(:direct_status) { nil }
  480. it { is_expected.to eq(%w(unlisted public)) }
  481. end
  482. context 'given blocked account' do
  483. before do
  484. target_account.block!(account)
  485. end
  486. it { is_expected.to be_empty }
  487. end
  488. context 'given same account' do
  489. let(:account) { target_account }
  490. it { is_expected.to eq(%w(direct direct private unlisted public)) }
  491. end
  492. context 'given followed account' do
  493. before do
  494. account.follow!(target_account)
  495. end
  496. it { is_expected.to eq(%w(direct private unlisted public)) }
  497. end
  498. context 'given unfollowed account' do
  499. it { is_expected.to eq(%w(direct unlisted public)) }
  500. end
  501. end
  502. describe 'before_create' do
  503. it 'sets account being replied to correctly over intermediary nodes' do
  504. first_status = Fabricate(:status, account: bob)
  505. intermediary = Fabricate(:status, thread: first_status, account: alice)
  506. final = Fabricate(:status, thread: intermediary, account: alice)
  507. expect(final.in_reply_to_account_id).to eq bob.id
  508. end
  509. it 'creates new conversation for stand-alone status' do
  510. expect(Status.create(account: alice, text: 'First').conversation_id).to_not be_nil
  511. end
  512. it 'keeps conversation of parent node' do
  513. parent = Fabricate(:status, text: 'First')
  514. expect(Status.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id
  515. end
  516. end
  517. end