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.

780 lines
24 KiB

8 years ago
Account domain blocks (#2381) * Add <ostatus:conversation /> tag to Atom input/output Only uses ref attribute (not href) because href would be the alternate link that's always included also. Creates new conversation for every non-reply status. Carries over conversation for every reply. Keeps remote URIs verbatim, generates local URIs on the fly like the rest of them. * Conversation muting - prevents notifications that reference a conversation (including replies, favourites, reblogs) from being created. API endpoints /api/v1/statuses/:id/mute and /api/v1/statuses/:id/unmute Currently no way to tell when a status/conversation is muted, so the web UI only has a "disable notifications" button, doesn't work as a toggle * Display "Dismiss notifications" on all statuses in notifications column, not just own * Add "muted" as a boolean attribute on statuses JSON For now always false on contained reblogs, since it's only relevant for statuses returned from the notifications endpoint, which are not nested Remove "Disable notifications" from detailed status view, since it's only relevant in the notifications column * Up max class length * Remove pending test for conversation mute * Add tests, clean up * Rename to "mute conversation" and "unmute conversation" * Raise validation error when trying to mute/unmute status without conversation * Adding account domain blocks that filter notifications and public timelines * Add tests for domain blocks in notifications, public timelines Filter reblogs of blocked domains from home * Add API for listing and creating account domain blocks * API for creating/deleting domain blocks, tests for Status#ancestors and Status#descendants, filter domain blocks from them * Filter domains in streaming API * Update account_domain_block_spec.rb
7 years ago
8 years ago
  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 a status by #{reblog.account.acct}"' do
  82. reblog = subject.reblog = other
  83. expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
  84. end
  85. end
  86. context 'unless reblog?' do
  87. it 'returns "New status by #{account.acct}"' do
  88. subject.reblog = nil
  89. expect(subject.title).to eq "New status by #{account.acct}"
  90. end
  91. end
  92. end
  93. end
  94. describe '#hidden?' do
  95. context 'if private_visibility?' do
  96. it 'returns true' do
  97. subject.visibility = :private
  98. expect(subject.hidden?).to be true
  99. end
  100. end
  101. context 'if direct_visibility?' do
  102. it 'returns true' do
  103. subject.visibility = :direct
  104. expect(subject.hidden?).to be true
  105. end
  106. end
  107. context 'if public_visibility?' do
  108. it 'returns false' do
  109. subject.visibility = :public
  110. expect(subject.hidden?).to be false
  111. end
  112. end
  113. context 'if unlisted_visibility?' do
  114. it 'returns false' do
  115. subject.visibility = :unlisted
  116. expect(subject.hidden?).to be false
  117. end
  118. end
  119. end
  120. describe '#content' do
  121. it 'returns the text of the status if it is not a reblog' do
  122. expect(subject.content).to eql subject.text
  123. end
  124. it 'returns the text of the reblogged status' do
  125. subject.reblog = other
  126. expect(subject.content).to eql other.text
  127. end
  128. end
  129. describe '#target' do
  130. it 'returns nil if the status is self-contained' do
  131. expect(subject.target).to be_nil
  132. end
  133. it 'returns nil if the status is a reply' do
  134. subject.thread = other
  135. expect(subject.target).to be_nil
  136. end
  137. it 'returns the reblogged status' do
  138. subject.reblog = other
  139. expect(subject.target).to eq other
  140. end
  141. end
  142. describe '#reblogs_count' do
  143. it 'is the number of reblogs' do
  144. Fabricate(:status, account: bob, reblog: subject)
  145. Fabricate(:status, account: alice, reblog: subject)
  146. expect(subject.reblogs_count).to eq 2
  147. end
  148. it 'is decremented when reblog is removed' do
  149. reblog = Fabricate(:status, account: bob, reblog: subject)
  150. expect(subject.reblogs_count).to eq 1
  151. reblog.destroy
  152. expect(subject.reblogs_count).to eq 0
  153. end
  154. it 'does not fail when original is deleted before reblog' do
  155. reblog = Fabricate(:status, account: bob, reblog: subject)
  156. expect(subject.reblogs_count).to eq 1
  157. expect { subject.destroy }.to_not raise_error
  158. expect(Status.find_by(id: reblog.id)).to be_nil
  159. end
  160. end
  161. describe '#replies_count' do
  162. it 'is the number of replies' do
  163. reply = Fabricate(:status, account: bob, thread: subject)
  164. expect(subject.replies_count).to eq 1
  165. end
  166. it 'is decremented when reply is removed' do
  167. reply = Fabricate(:status, account: bob, thread: subject)
  168. expect(subject.replies_count).to eq 1
  169. reply.destroy
  170. expect(subject.replies_count).to eq 0
  171. end
  172. end
  173. describe '#favourites_count' do
  174. it 'is the number of favorites' do
  175. Fabricate(:favourite, account: bob, status: subject)
  176. Fabricate(:favourite, account: alice, status: subject)
  177. expect(subject.favourites_count).to eq 2
  178. end
  179. it 'is decremented when favourite is removed' do
  180. favourite = Fabricate(:favourite, account: bob, status: subject)
  181. expect(subject.favourites_count).to eq 1
  182. favourite.destroy
  183. expect(subject.favourites_count).to eq 0
  184. end
  185. end
  186. describe '#proper' do
  187. it 'is itself for original statuses' do
  188. expect(subject.proper).to eq subject
  189. end
  190. it 'is the source status for reblogs' do
  191. subject.reblog = other
  192. expect(subject.proper).to eq other
  193. end
  194. end
  195. describe 'on create' do
  196. let(:local_account) { Fabricate(:account, username: 'local', domain: nil) }
  197. let(:remote_account) { Fabricate(:account, username: 'remote', domain: 'example.com') }
  198. subject { Status.new }
  199. describe 'on a status that ends with the local-only emoji' do
  200. before do
  201. subject.text = 'A toot ' + subject.local_only_emoji
  202. end
  203. context 'if the status originates from this instance' do
  204. before do
  205. subject.account = local_account
  206. end
  207. it 'is marked local-only' do
  208. subject.save!
  209. expect(subject).to be_local_only
  210. end
  211. end
  212. context 'if the status is remote' do
  213. before do
  214. subject.account = remote_account
  215. end
  216. it 'is not marked local-only' do
  217. subject.save!
  218. expect(subject).to_not be_local_only
  219. end
  220. end
  221. end
  222. end
  223. describe '.mutes_map' do
  224. let(:status) { Fabricate(:status) }
  225. let(:account) { Fabricate(:account) }
  226. subject { Status.mutes_map([status.conversation.id], account) }
  227. it 'returns a hash' do
  228. expect(subject).to be_a Hash
  229. end
  230. it 'contains true value' do
  231. account.mute_conversation!(status.conversation)
  232. expect(subject[status.conversation.id]).to be true
  233. end
  234. end
  235. describe '.favourites_map' do
  236. let(:status) { Fabricate(:status) }
  237. let(:account) { Fabricate(:account) }
  238. subject { Status.favourites_map([status], account) }
  239. it 'returns a hash' do
  240. expect(subject).to be_a Hash
  241. end
  242. it 'contains true value' do
  243. Fabricate(:favourite, status: status, account: account)
  244. expect(subject[status.id]).to be true
  245. end
  246. end
  247. describe '.reblogs_map' do
  248. let(:status) { Fabricate(:status) }
  249. let(:account) { Fabricate(:account) }
  250. subject { Status.reblogs_map([status], account) }
  251. it 'returns a hash' do
  252. expect(subject).to be_a Hash
  253. end
  254. it 'contains true value' do
  255. Fabricate(:status, account: account, reblog: status)
  256. expect(subject[status.id]).to be true
  257. end
  258. end
  259. describe '.in_chosen_languages' do
  260. context 'for accounts with language filters' do
  261. let(:user) { Fabricate(:user, chosen_languages: ['en']) }
  262. it 'does not include statuses in not in chosen languages' do
  263. status = Fabricate(:status, language: 'de')
  264. expect(Status.in_chosen_languages(user.account)).not_to include status
  265. end
  266. it 'includes status with unknown language' do
  267. status = Fabricate(:status, language: nil)
  268. expect(Status.in_chosen_languages(user.account)).to include status
  269. end
  270. end
  271. end
  272. describe '.as_home_timeline' do
  273. let(:account) { Fabricate(:account) }
  274. let(:followed) { Fabricate(:account) }
  275. let(:not_followed) { Fabricate(:account) }
  276. before do
  277. Fabricate(:follow, account: account, target_account: followed)
  278. @self_status = Fabricate(:status, account: account, visibility: :public)
  279. @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
  280. @followed_status = Fabricate(:status, account: followed, visibility: :public)
  281. @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
  282. @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
  283. @results = Status.as_home_timeline(account)
  284. end
  285. it 'includes statuses from self' do
  286. expect(@results).to include(@self_status)
  287. end
  288. it 'does not include direct statuses from self' do
  289. expect(@results).to_not include(@self_direct_status)
  290. end
  291. it 'includes statuses from followed' do
  292. expect(@results).to include(@followed_status)
  293. end
  294. it 'does not include direct statuses mentioning recipient from followed' do
  295. Fabricate(:mention, account: account, status: @followed_direct_status)
  296. expect(@results).to_not include(@followed_direct_status)
  297. end
  298. it 'does not include direct statuses not mentioning recipient from followed' do
  299. expect(@results).not_to include(@followed_direct_status)
  300. end
  301. it 'does not include statuses from non-followed' do
  302. expect(@results).not_to include(@not_followed_status)
  303. end
  304. end
  305. describe '.as_direct_timeline' do
  306. let(:account) { Fabricate(:account) }
  307. let(:followed) { Fabricate(:account) }
  308. let(:not_followed) { Fabricate(:account) }
  309. before do
  310. Fabricate(:follow, account: account, target_account: followed)
  311. @self_public_status = Fabricate(:status, account: account, visibility: :public)
  312. @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
  313. @followed_public_status = Fabricate(:status, account: followed, visibility: :public)
  314. @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
  315. @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct)
  316. @results = Status.as_direct_timeline(account)
  317. end
  318. it 'does not include public statuses from self' do
  319. expect(@results).to_not include(@self_public_status)
  320. end
  321. it 'includes direct statuses from self' do
  322. expect(@results).to include(@self_direct_status)
  323. end
  324. it 'does not include public statuses from followed' do
  325. expect(@results).to_not include(@followed_public_status)
  326. end
  327. it 'does not include direct statuses not mentioning recipient from followed' do
  328. expect(@results).to_not include(@followed_direct_status)
  329. end
  330. it 'does not include direct statuses not mentioning recipient from non-followed' do
  331. expect(@results).to_not include(@not_followed_direct_status)
  332. end
  333. it 'includes direct statuses mentioning recipient from followed' do
  334. Fabricate(:mention, account: account, status: @followed_direct_status)
  335. results2 = Status.as_direct_timeline(account)
  336. expect(results2).to include(@followed_direct_status)
  337. end
  338. it 'includes direct statuses mentioning recipient from non-followed' do
  339. Fabricate(:mention, account: account, status: @not_followed_direct_status)
  340. results2 = Status.as_direct_timeline(account)
  341. expect(results2).to include(@not_followed_direct_status)
  342. end
  343. end
  344. describe '.as_public_timeline' do
  345. it 'only includes statuses with public visibility' do
  346. public_status = Fabricate(:status, visibility: :public)
  347. private_status = Fabricate(:status, visibility: :private)
  348. results = Status.as_public_timeline
  349. expect(results).to include(public_status)
  350. expect(results).not_to include(private_status)
  351. end
  352. it 'does not include replies' do
  353. status = Fabricate(:status)
  354. reply = Fabricate(:status, in_reply_to_id: status.id)
  355. results = Status.as_public_timeline
  356. expect(results).to include(status)
  357. expect(results).not_to include(reply)
  358. end
  359. it 'does not include boosts' do
  360. status = Fabricate(:status)
  361. boost = Fabricate(:status, reblog_of_id: status.id)
  362. results = Status.as_public_timeline
  363. expect(results).to include(status)
  364. expect(results).not_to include(boost)
  365. end
  366. it 'filters out silenced accounts' do
  367. account = Fabricate(:account)
  368. silenced_account = Fabricate(:account, silenced: true)
  369. status = Fabricate(:status, account: account)
  370. silenced_status = Fabricate(:status, account: silenced_account)
  371. results = Status.as_public_timeline
  372. expect(results).to include(status)
  373. expect(results).not_to include(silenced_status)
  374. end
  375. context 'without local_only option' do
  376. let(:viewer) { nil }
  377. let!(:local_account) { Fabricate(:account, domain: nil) }
  378. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  379. let!(:local_status) { Fabricate(:status, account: local_account) }
  380. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  381. subject { Status.as_public_timeline(viewer, false) }
  382. context 'without a viewer' do
  383. let(:viewer) { nil }
  384. it 'includes remote instances statuses' do
  385. expect(subject).to include(remote_status)
  386. end
  387. it 'includes local statuses' do
  388. expect(subject).to include(local_status)
  389. end
  390. end
  391. context 'with a viewer' do
  392. let(:viewer) { Fabricate(:account, username: 'viewer') }
  393. it 'includes remote instances statuses' do
  394. expect(subject).to include(remote_status)
  395. end
  396. it 'includes local statuses' do
  397. expect(subject).to include(local_status)
  398. end
  399. end
  400. end
  401. context 'with a local_only option set' do
  402. let!(:local_account) { Fabricate(:account, domain: nil) }
  403. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  404. let!(:local_status) { Fabricate(:status, account: local_account) }
  405. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  406. subject { Status.as_public_timeline(viewer, true) }
  407. context 'without a viewer' do
  408. let(:viewer) { nil }
  409. it 'does not include remote instances statuses' do
  410. expect(subject).to include(local_status)
  411. expect(subject).not_to include(remote_status)
  412. end
  413. end
  414. context 'with a viewer' do
  415. let(:viewer) { Fabricate(:account, username: 'viewer') }
  416. it 'does not include remote instances statuses' do
  417. expect(subject).to include(local_status)
  418. expect(subject).not_to include(remote_status)
  419. end
  420. it 'is not affected by personal domain blocks' do
  421. viewer.block_domain!('test.com')
  422. expect(subject).to include(local_status)
  423. expect(subject).not_to include(remote_status)
  424. end
  425. end
  426. end
  427. describe 'with an account passed in' do
  428. before do
  429. @account = Fabricate(:account)
  430. end
  431. it 'excludes statuses from accounts blocked by the account' do
  432. blocked = Fabricate(:account)
  433. Fabricate(:block, account: @account, target_account: blocked)
  434. blocked_status = Fabricate(:status, account: blocked)
  435. results = Status.as_public_timeline(@account)
  436. expect(results).not_to include(blocked_status)
  437. end
  438. it 'excludes statuses from accounts who have blocked the account' do
  439. blocked = Fabricate(:account)
  440. Fabricate(:block, account: blocked, target_account: @account)
  441. blocked_status = Fabricate(:status, account: blocked)
  442. results = Status.as_public_timeline(@account)
  443. expect(results).not_to include(blocked_status)
  444. end
  445. it 'excludes statuses from accounts muted by the account' do
  446. muted = Fabricate(:account)
  447. Fabricate(:mute, account: @account, target_account: muted)
  448. muted_status = Fabricate(:status, account: muted)
  449. results = Status.as_public_timeline(@account)
  450. expect(results).not_to include(muted_status)
  451. end
  452. it 'excludes statuses from accounts from personally blocked domains' do
  453. blocked = Fabricate(:account, domain: 'example.com')
  454. @account.block_domain!(blocked.domain)
  455. blocked_status = Fabricate(:status, account: blocked)
  456. results = Status.as_public_timeline(@account)
  457. expect(results).not_to include(blocked_status)
  458. end
  459. context 'with language preferences' do
  460. it 'excludes statuses in languages not allowed by the account user' do
  461. user = Fabricate(:user, chosen_languages: [:en, :es])
  462. @account.update(user: user)
  463. en_status = Fabricate(:status, language: 'en')
  464. es_status = Fabricate(:status, language: 'es')
  465. fr_status = Fabricate(:status, language: 'fr')
  466. results = Status.as_public_timeline(@account)
  467. expect(results).to include(en_status)
  468. expect(results).to include(es_status)
  469. expect(results).not_to include(fr_status)
  470. end
  471. it 'includes all languages when user does not have a setting' do
  472. user = Fabricate(:user, chosen_languages: nil)
  473. @account.update(user: user)
  474. en_status = Fabricate(:status, language: 'en')
  475. es_status = Fabricate(:status, language: 'es')
  476. results = Status.as_public_timeline(@account)
  477. expect(results).to include(en_status)
  478. expect(results).to include(es_status)
  479. end
  480. it 'includes all languages when account does not have a user' do
  481. expect(@account.user).to be_nil
  482. en_status = Fabricate(:status, language: 'en')
  483. es_status = Fabricate(:status, language: 'es')
  484. results = Status.as_public_timeline(@account)
  485. expect(results).to include(en_status)
  486. expect(results).to include(es_status)
  487. end
  488. end
  489. end
  490. context 'with local-only statuses' do
  491. let(:status) { Fabricate(:status, local_only: true) }
  492. subject { Status.as_public_timeline(viewer) }
  493. context 'without a viewer' do
  494. let(:viewer) { nil }
  495. it 'excludes local-only statuses' do
  496. expect(subject).to_not include(status)
  497. end
  498. end
  499. context 'with a viewer' do
  500. let(:viewer) { Fabricate(:account, username: 'viewer') }
  501. it 'includes local-only statuses' do
  502. expect(subject).to include(status)
  503. end
  504. end
  505. # TODO: What happens if the viewer is remote?
  506. # Can the viewer be remote?
  507. # What prevents the viewer from being remote?
  508. end
  509. end
  510. describe '.as_tag_timeline' do
  511. it 'includes statuses with a tag' do
  512. tag = Fabricate(:tag)
  513. status = Fabricate(:status, tags: [tag])
  514. other = Fabricate(:status)
  515. results = Status.as_tag_timeline(tag)
  516. expect(results).to include(status)
  517. expect(results).not_to include(other)
  518. end
  519. it 'allows replies to be included' do
  520. original = Fabricate(:status)
  521. tag = Fabricate(:tag)
  522. status = Fabricate(:status, tags: [tag], in_reply_to_id: original.id)
  523. results = Status.as_tag_timeline(tag)
  524. expect(results).to include(status)
  525. end
  526. context 'on a local-only status' do
  527. let(:tag) { Fabricate(:tag) }
  528. let(:status) { Fabricate(:status, local_only: true, tags: [tag]) }
  529. context 'without a viewer' do
  530. let(:viewer) { nil }
  531. it 'filters the local-only status out of the result set' do
  532. expect(Status.as_tag_timeline(tag, viewer)).not_to include(status)
  533. end
  534. end
  535. context 'with a viewer' do
  536. let(:viewer) { Fabricate(:account, username: 'viewer', domain: nil) }
  537. it 'keeps the local-only status in the result set' do
  538. expect(Status.as_tag_timeline(tag, viewer)).to include(status)
  539. end
  540. end
  541. end
  542. end
  543. describe '.permitted_for' do
  544. subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
  545. let(:target_account) { alice }
  546. let(:account) { bob }
  547. let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
  548. let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
  549. let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
  550. let!(:direct_status) do
  551. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  552. Fabricate(:mention, status: status, account: account)
  553. end
  554. end
  555. let!(:other_direct_status) do
  556. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  557. Fabricate(:mention, status: status)
  558. end
  559. end
  560. context 'given nil' do
  561. let(:account) { nil }
  562. let(:direct_status) { nil }
  563. it { is_expected.to eq(%w(unlisted public)) }
  564. end
  565. context 'given blocked account' do
  566. before do
  567. target_account.block!(account)
  568. end
  569. it { is_expected.to be_empty }
  570. end
  571. context 'given same account' do
  572. let(:account) { target_account }
  573. it { is_expected.to eq(%w(direct direct private unlisted public)) }
  574. end
  575. context 'given followed account' do
  576. before do
  577. account.follow!(target_account)
  578. end
  579. it { is_expected.to eq(%w(direct private unlisted public)) }
  580. end
  581. context 'given unfollowed account' do
  582. it { is_expected.to eq(%w(direct unlisted public)) }
  583. end
  584. end
  585. describe 'before_validation' do
  586. it 'sets account being replied to correctly over intermediary nodes' do
  587. first_status = Fabricate(:status, account: bob)
  588. intermediary = Fabricate(:status, thread: first_status, account: alice)
  589. final = Fabricate(:status, thread: intermediary, account: alice)
  590. expect(final.in_reply_to_account_id).to eq bob.id
  591. end
  592. it 'creates new conversation for stand-alone status' do
  593. expect(Status.create(account: alice, text: 'First').conversation_id).to_not be_nil
  594. end
  595. it 'keeps conversation of parent node' do
  596. parent = Fabricate(:status, text: 'First')
  597. expect(Status.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id
  598. end
  599. it 'sets `local` to true for status by local account' do
  600. expect(Status.create(account: alice, text: 'foo').local).to be true
  601. end
  602. it 'sets `local` to false for status by remote account' do
  603. alice.update(domain: 'example.com')
  604. expect(Status.create(account: alice, text: 'foo').local).to be false
  605. end
  606. end
  607. describe 'validation' do
  608. it 'disallow empty uri for remote status' do
  609. alice.update(domain: 'example.com')
  610. status = Fabricate.build(:status, uri: '', account: alice)
  611. expect(status).to model_have_error_on_field(:uri)
  612. end
  613. end
  614. describe 'after_create' do
  615. it 'saves ActivityPub uri as uri for local status' do
  616. status = Status.create(account: alice, text: 'foo')
  617. status.reload
  618. expect(status.uri).to start_with('https://')
  619. end
  620. end
  621. end