A fork of Glitch Social, a fork of Mastodon.
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.

608 lines
18KB

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