1
0

status_spec.rb 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe Status do
  4. subject { Fabricate(:status, account: alice) }
  5. let(:alice) { Fabricate(:account, username: 'alice') }
  6. let(:bob) { Fabricate(:account, username: 'bob') }
  7. let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') }
  8. describe '#local?' do
  9. it 'returns true when no remote URI is set' do
  10. expect(subject.local?).to be true
  11. end
  12. it 'returns false if a remote URI is set' do
  13. alice.update(domain: 'example.com')
  14. subject.save
  15. expect(subject.local?).to be false
  16. end
  17. it 'returns true if a URI is set and `local` is true' do
  18. subject.update(uri: 'example.com', local: true)
  19. expect(subject.local?).to be true
  20. end
  21. end
  22. describe '#reblog?' do
  23. it 'returns true when the status reblogs another status' do
  24. subject.reblog = other
  25. expect(subject.reblog?).to be true
  26. end
  27. it 'returns false if the status is self-contained' do
  28. expect(subject.reblog?).to be false
  29. end
  30. end
  31. describe '#reply?' do
  32. it 'returns true if the status references another' do
  33. subject.thread = other
  34. expect(subject.reply?).to be true
  35. end
  36. it 'returns false if the status is self-contained' do
  37. expect(subject.reply?).to be false
  38. end
  39. end
  40. describe '#verb' do
  41. context 'when destroyed?' do
  42. it 'returns :delete' do
  43. subject.destroy!
  44. expect(subject.verb).to be :delete
  45. end
  46. end
  47. context 'when not destroyed?' do
  48. context 'when reblog?' do
  49. it 'returns :share' do
  50. subject.reblog = other
  51. expect(subject.verb).to be :share
  52. end
  53. end
  54. context 'when not reblog?' do
  55. it 'returns :post' do
  56. subject.reblog = nil
  57. expect(subject.verb).to be :post
  58. end
  59. end
  60. end
  61. end
  62. describe '#object_type' do
  63. it 'is note when the status is self-contained' do
  64. expect(subject.object_type).to be :note
  65. end
  66. it 'is comment when the status replies to another' do
  67. subject.thread = other
  68. expect(subject.object_type).to be :comment
  69. end
  70. end
  71. describe '#hidden?' do
  72. context 'when private_visibility?' do
  73. it 'returns true' do
  74. subject.visibility = :private
  75. expect(subject.hidden?).to be true
  76. end
  77. end
  78. context 'when direct_visibility?' do
  79. it 'returns true' do
  80. subject.visibility = :direct
  81. expect(subject.hidden?).to be true
  82. end
  83. end
  84. context 'when public_visibility?' do
  85. it 'returns false' do
  86. subject.visibility = :public
  87. expect(subject.hidden?).to be false
  88. end
  89. end
  90. context 'when unlisted_visibility?' do
  91. it 'returns false' do
  92. subject.visibility = :unlisted
  93. expect(subject.hidden?).to be false
  94. end
  95. end
  96. end
  97. describe '#content' do
  98. it 'returns the text of the status if it is not a reblog' do
  99. expect(subject.content).to eql subject.text
  100. end
  101. it 'returns the text of the reblogged status' do
  102. subject.reblog = other
  103. expect(subject.content).to eql other.text
  104. end
  105. end
  106. describe '#target' do
  107. it 'returns nil if the status is self-contained' do
  108. expect(subject.target).to be_nil
  109. end
  110. it 'returns nil if the status is a reply' do
  111. subject.thread = other
  112. expect(subject.target).to be_nil
  113. end
  114. it 'returns the reblogged status' do
  115. subject.reblog = other
  116. expect(subject.target).to eq other
  117. end
  118. end
  119. describe '#reblogs_count' do
  120. it 'is the number of reblogs' do
  121. Fabricate(:status, account: bob, reblog: subject)
  122. Fabricate(:status, account: alice, reblog: subject)
  123. expect(subject.reblogs_count).to eq 2
  124. end
  125. it 'is decremented when reblog is removed' do
  126. reblog = Fabricate(:status, account: bob, reblog: subject)
  127. expect(subject.reblogs_count).to eq 1
  128. reblog.destroy
  129. expect(subject.reblogs_count).to eq 0
  130. end
  131. it 'does not fail when original is deleted before reblog' do
  132. reblog = Fabricate(:status, account: bob, reblog: subject)
  133. expect(subject.reblogs_count).to eq 1
  134. expect { subject.destroy }.to_not raise_error
  135. expect(described_class.find_by(id: reblog.id)).to be_nil
  136. end
  137. end
  138. describe '#untrusted_reblogs_count' do
  139. before do
  140. alice.update(domain: 'example.com')
  141. subject.status_stat.tap do |status_stat|
  142. status_stat.untrusted_reblogs_count = 0
  143. status_stat.save
  144. end
  145. subject.save
  146. end
  147. it 'is incremented by the number of reblogs' do
  148. Fabricate(:status, account: bob, reblog: subject)
  149. Fabricate(:status, account: alice, reblog: subject)
  150. expect(subject.untrusted_reblogs_count).to eq 2
  151. end
  152. it 'is decremented when reblog is removed' do
  153. reblog = Fabricate(:status, account: bob, reblog: subject)
  154. expect(subject.untrusted_reblogs_count).to eq 1
  155. reblog.destroy
  156. expect(subject.untrusted_reblogs_count).to eq 0
  157. end
  158. end
  159. describe '#replies_count' do
  160. it 'is the number of replies' do
  161. Fabricate(:status, account: bob, thread: subject)
  162. expect(subject.replies_count).to eq 1
  163. end
  164. it 'is decremented when reply is removed' do
  165. reply = Fabricate(:status, account: bob, thread: subject)
  166. expect(subject.replies_count).to eq 1
  167. reply.destroy
  168. expect(subject.replies_count).to eq 0
  169. end
  170. end
  171. describe '#favourites_count' do
  172. it 'is the number of favorites' do
  173. Fabricate(:favourite, account: bob, status: subject)
  174. Fabricate(:favourite, account: alice, status: subject)
  175. expect(subject.favourites_count).to eq 2
  176. end
  177. it 'is decremented when favourite is removed' do
  178. favourite = Fabricate(:favourite, account: bob, status: subject)
  179. expect(subject.favourites_count).to eq 1
  180. favourite.destroy
  181. expect(subject.favourites_count).to eq 0
  182. end
  183. end
  184. describe '#untrusted_favourites_count' do
  185. before do
  186. alice.update(domain: 'example.com')
  187. subject.status_stat.tap do |status_stat|
  188. status_stat.untrusted_favourites_count = 0
  189. status_stat.save
  190. end
  191. subject.save
  192. end
  193. it 'is incremented by favorites' do
  194. Fabricate(:favourite, account: bob, status: subject)
  195. Fabricate(:favourite, account: alice, status: subject)
  196. expect(subject.untrusted_favourites_count).to eq 2
  197. end
  198. it 'is decremented when favourite is removed' do
  199. favourite = Fabricate(:favourite, account: bob, status: subject)
  200. expect(subject.untrusted_favourites_count).to eq 1
  201. favourite.destroy
  202. expect(subject.untrusted_favourites_count).to eq 0
  203. end
  204. end
  205. describe '#proper' do
  206. it 'is itself for original statuses' do
  207. expect(subject.proper).to eq subject
  208. end
  209. it 'is the source status for reblogs' do
  210. subject.reblog = other
  211. expect(subject.proper).to eq other
  212. end
  213. end
  214. describe '#reported?' do
  215. context 'when the status is not reported' do
  216. it 'returns false' do
  217. expect(subject.reported?).to be false
  218. end
  219. end
  220. context 'when the status is part of an open report' do
  221. before do
  222. Fabricate(:report, target_account: subject.account, status_ids: [subject.id])
  223. end
  224. it 'returns true' do
  225. expect(subject.reported?).to be true
  226. end
  227. end
  228. context 'when the status is part of a closed report with an account warning mentioning the account' do
  229. before do
  230. report = Fabricate(:report, target_account: subject.account, status_ids: [subject.id])
  231. report.resolve!(Fabricate(:account))
  232. Fabricate(:account_warning, target_account: subject.account, status_ids: [subject.id], report: report)
  233. end
  234. it 'returns true' do
  235. expect(subject.reported?).to be true
  236. end
  237. end
  238. context 'when the status is part of a closed report with an account warning not mentioning the account' do
  239. before do
  240. report = Fabricate(:report, target_account: subject.account, status_ids: [subject.id])
  241. report.resolve!(Fabricate(:account))
  242. Fabricate(:account_warning, target_account: subject.account, report: report)
  243. end
  244. it 'returns false' do
  245. expect(subject.reported?).to be false
  246. end
  247. end
  248. end
  249. describe '#ordered_media_attachments' do
  250. let(:status) { Fabricate(:status) }
  251. let(:first_attachment) { Fabricate(:media_attachment) }
  252. let(:second_attachment) { Fabricate(:media_attachment) }
  253. let(:last_attachment) { Fabricate(:media_attachment) }
  254. let(:extra_attachment) { Fabricate(:media_attachment) }
  255. before do
  256. stub_const('Status::MEDIA_ATTACHMENTS_LIMIT', 3)
  257. # Add attachments out of order
  258. status.media_attachments << second_attachment
  259. status.media_attachments << last_attachment
  260. status.media_attachments << extra_attachment
  261. status.media_attachments << first_attachment
  262. end
  263. context 'when ordered_media_attachment_ids is not set' do
  264. it 'returns up to MEDIA_ATTACHMENTS_LIMIT attachments' do
  265. expect(status.ordered_media_attachments.size).to eq Status::MEDIA_ATTACHMENTS_LIMIT
  266. end
  267. end
  268. context 'when ordered_media_attachment_ids is set' do
  269. before do
  270. status.update!(ordered_media_attachment_ids: [first_attachment.id, second_attachment.id, last_attachment.id, extra_attachment.id])
  271. end
  272. it 'returns up to MEDIA_ATTACHMENTS_LIMIT attachments in the expected order' do
  273. expect(status.ordered_media_attachments).to eq [first_attachment, second_attachment, last_attachment]
  274. end
  275. end
  276. end
  277. describe '.mutes_map' do
  278. subject { described_class.mutes_map([status.conversation.id], account) }
  279. let(:status) { Fabricate(:status) }
  280. let(:account) { Fabricate(:account) }
  281. it 'returns a hash' do
  282. expect(subject).to be_a Hash
  283. end
  284. it 'contains true value' do
  285. account.mute_conversation!(status.conversation)
  286. expect(subject[status.conversation.id]).to be true
  287. end
  288. end
  289. describe '.favourites_map' do
  290. subject { described_class.favourites_map([status], account) }
  291. let(:status) { Fabricate(:status) }
  292. let(:account) { Fabricate(:account) }
  293. it 'returns a hash' do
  294. expect(subject).to be_a Hash
  295. end
  296. it 'contains true value' do
  297. Fabricate(:favourite, status: status, account: account)
  298. expect(subject[status.id]).to be true
  299. end
  300. end
  301. describe '.reblogs_map' do
  302. subject { described_class.reblogs_map([status], account) }
  303. let(:status) { Fabricate(:status) }
  304. let(:account) { Fabricate(:account) }
  305. it 'returns a hash' do
  306. expect(subject).to be_a Hash
  307. end
  308. it 'contains true value' do
  309. Fabricate(:status, account: account, reblog: status)
  310. expect(subject[status.id]).to be true
  311. end
  312. end
  313. describe '.tagged_with' do
  314. let(:tag_cats) { Fabricate(:tag, name: 'cats') }
  315. let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
  316. let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
  317. let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
  318. let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
  319. let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
  320. let!(:status_without_tags) { Fabricate(:status, tags: []) }
  321. let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) }
  322. context 'when given one tag' do
  323. it 'returns the expected statuses' do
  324. expect(described_class.tagged_with([tag_cats.id]))
  325. .to include(status_with_tag_cats, status_with_all_tags)
  326. .and not_include(status_without_tags)
  327. expect(described_class.tagged_with([tag_dogs.id]))
  328. .to include(status_with_tag_dogs, status_with_all_tags)
  329. .and not_include(status_without_tags)
  330. expect(described_class.tagged_with([tag_zebras.id]))
  331. .to include(status_tagged_with_zebras, status_with_all_tags)
  332. .and not_include(status_without_tags)
  333. end
  334. end
  335. context 'when given multiple tags' do
  336. it 'returns the expected statuses' do
  337. expect(described_class.tagged_with([tag_cats.id, tag_dogs.id]))
  338. .to include(status_with_tag_cats, status_with_tag_dogs, status_with_all_tags)
  339. .and not_include(status_without_tags)
  340. expect(described_class.tagged_with([tag_cats.id, tag_zebras.id]))
  341. .to include(status_with_tag_cats, status_tagged_with_zebras, status_with_all_tags)
  342. .and not_include(status_without_tags)
  343. expect(described_class.tagged_with([tag_dogs.id, tag_zebras.id]))
  344. .to include(status_with_tag_dogs, status_tagged_with_zebras, status_with_all_tags)
  345. .and not_include(status_without_tags)
  346. end
  347. end
  348. end
  349. describe '.tagged_with_all' do
  350. let(:tag_cats) { Fabricate(:tag, name: 'cats') }
  351. let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
  352. let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
  353. let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
  354. let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
  355. let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
  356. let!(:status_without_tags) { Fabricate(:status, tags: []) }
  357. let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs]) }
  358. context 'when given one tag' do
  359. it 'returns the expected statuses' do
  360. expect(described_class.tagged_with_all([tag_cats.id]))
  361. .to include(status_with_tag_cats, status_with_all_tags)
  362. .and not_include(status_without_tags)
  363. expect(described_class.tagged_with_all([tag_dogs.id]))
  364. .to include(status_with_tag_dogs, status_with_all_tags)
  365. .and not_include(status_without_tags)
  366. expect(described_class.tagged_with_all([tag_zebras.id]))
  367. .to include(status_tagged_with_zebras)
  368. .and not_include(status_without_tags)
  369. end
  370. end
  371. context 'when given multiple tags' do
  372. it 'returns the expected statuses' do
  373. expect(described_class.tagged_with_all([tag_cats.id, tag_dogs.id]))
  374. .to include(status_with_all_tags)
  375. expect(described_class.tagged_with_all([tag_cats.id, tag_zebras.id]))
  376. .to eq []
  377. expect(described_class.tagged_with_all([tag_dogs.id, tag_zebras.id]))
  378. .to eq []
  379. end
  380. end
  381. end
  382. describe '.tagged_with_none' do
  383. let(:tag_cats) { Fabricate(:tag, name: 'cats') }
  384. let(:tag_dogs) { Fabricate(:tag, name: 'dogs') }
  385. let(:tag_zebras) { Fabricate(:tag, name: 'zebras') }
  386. let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) }
  387. let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) }
  388. let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) }
  389. let!(:status_without_tags) { Fabricate(:status, tags: []) }
  390. let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) }
  391. context 'when given one tag' do
  392. it 'returns the expected statuses' do
  393. expect(described_class.tagged_with_none([tag_cats.id]))
  394. .to include(status_with_tag_dogs, status_tagged_with_zebras, status_without_tags)
  395. .and not_include(status_with_all_tags)
  396. expect(described_class.tagged_with_none([tag_dogs.id]))
  397. .to include(status_with_tag_cats, status_tagged_with_zebras, status_without_tags)
  398. .and not_include(status_with_all_tags)
  399. expect(described_class.tagged_with_none([tag_zebras.id]))
  400. .to include(status_with_tag_cats, status_with_tag_dogs, status_without_tags)
  401. .and not_include(status_with_all_tags)
  402. end
  403. end
  404. context 'when given multiple tags' do
  405. it 'returns the expected statuses' do
  406. expect(described_class.tagged_with_none([tag_cats.id, tag_dogs.id]))
  407. .to include(status_tagged_with_zebras, status_without_tags)
  408. .and not_include(status_with_all_tags)
  409. expect(described_class.tagged_with_none([tag_cats.id, tag_zebras.id]))
  410. .to include(status_with_tag_dogs, status_without_tags)
  411. .and not_include(status_with_all_tags)
  412. expect(described_class.tagged_with_none([tag_dogs.id, tag_zebras.id]))
  413. .to include(status_with_tag_cats, status_without_tags)
  414. .and not_include(status_with_all_tags)
  415. end
  416. end
  417. end
  418. describe 'before_validation' do
  419. it 'sets account being replied to correctly over intermediary nodes' do
  420. first_status = Fabricate(:status, account: bob)
  421. intermediary = Fabricate(:status, thread: first_status, account: alice)
  422. final = Fabricate(:status, thread: intermediary, account: alice)
  423. expect(final.in_reply_to_account_id).to eq bob.id
  424. end
  425. it 'creates new conversation for stand-alone status' do
  426. expect(described_class.create(account: alice, text: 'First').conversation_id).to_not be_nil
  427. end
  428. it 'keeps conversation of parent node' do
  429. parent = Fabricate(:status, text: 'First')
  430. expect(described_class.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id
  431. end
  432. it 'sets `local` to true for status by local account' do
  433. expect(described_class.create(account: alice, text: 'foo').local).to be true
  434. end
  435. it 'sets `local` to false for status by remote account' do
  436. alice.update(domain: 'example.com')
  437. expect(described_class.create(account: alice, text: 'foo').local).to be false
  438. end
  439. end
  440. describe 'Validations' do
  441. context 'with a remote account' do
  442. subject { Fabricate.build :status, account: remote_account }
  443. let(:remote_account) { Fabricate :account, domain: 'example.com' }
  444. it { is_expected.to_not allow_value('').for(:uri) }
  445. end
  446. end
  447. describe 'Callbacks' do
  448. describe 'Stripping content when required' do
  449. context 'with a remote account' do
  450. subject { Fabricate.build :status, local: false, account:, text: ' text ', spoiler_text: ' spoiler ' }
  451. let(:account) { Fabricate.build :account, domain: 'host.example' }
  452. it 'preserves content' do
  453. expect { subject.valid? }
  454. .to not_change(subject, :text)
  455. .and not_change(subject, :spoiler_text)
  456. end
  457. end
  458. context 'with a local account' do
  459. let(:account) { Fabricate.build :account, domain: nil }
  460. context 'with populated fields' do
  461. subject { Fabricate.build :status, local: true, account:, text: ' text ', spoiler_text: ' spoiler ' }
  462. it 'strips content' do
  463. expect { subject.valid? }
  464. .to change(subject, :text).to('text')
  465. .and change(subject, :spoiler_text).to('spoiler')
  466. end
  467. end
  468. context 'with empty fields' do
  469. subject { Fabricate.build :status, local: true, account:, text: nil, spoiler_text: nil }
  470. it 'preserves content' do
  471. expect { subject.valid? }
  472. .to not_change(subject, :text)
  473. .and not_change(subject, :spoiler_text)
  474. end
  475. end
  476. end
  477. end
  478. end
  479. describe 'after_create' do
  480. it 'saves ActivityPub uri as uri for local status' do
  481. status = described_class.create(account: alice, text: 'foo')
  482. status.reload
  483. expect(status.uri).to start_with('https://')
  484. end
  485. end
  486. end