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.

539 lines
17 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. it 'is always post' do
  41. expect(subject.verb).to be :post
  42. end
  43. end
  44. describe '#object_type' do
  45. it 'is note when the status is self-contained' do
  46. expect(subject.object_type).to be :note
  47. end
  48. it 'is comment when the status replies to another' do
  49. subject.thread = other
  50. expect(subject.object_type).to be :comment
  51. end
  52. end
  53. describe '#title' do
  54. it 'is a shorter version of the content' do
  55. expect(subject.title).to be_a String
  56. end
  57. end
  58. describe '#content' do
  59. it 'returns the text of the status if it is not a reblog' do
  60. expect(subject.content).to eql subject.text
  61. end
  62. it 'returns the text of the reblogged status' do
  63. subject.reblog = other
  64. expect(subject.content).to eql other.text
  65. end
  66. end
  67. describe '#target' do
  68. it 'returns nil if the status is self-contained' do
  69. expect(subject.target).to be_nil
  70. end
  71. it 'returns nil if the status is a reply' do
  72. subject.thread = other
  73. expect(subject.target).to be_nil
  74. end
  75. it 'returns the reblogged status' do
  76. subject.reblog = other
  77. expect(subject.target).to eq other
  78. end
  79. end
  80. describe '#reblogs_count' do
  81. it 'is the number of reblogs' do
  82. Fabricate(:status, account: bob, reblog: subject)
  83. Fabricate(:status, account: alice, reblog: subject)
  84. expect(subject.reblogs_count).to eq 2
  85. end
  86. end
  87. describe '#favourites_count' do
  88. it 'is the number of favorites' do
  89. Fabricate(:favourite, account: bob, status: subject)
  90. Fabricate(:favourite, account: alice, status: subject)
  91. expect(subject.favourites_count).to eq 2
  92. end
  93. end
  94. describe '#proper' do
  95. it 'is itself for original statuses' do
  96. expect(subject.proper).to eq subject
  97. end
  98. it 'is the source status for reblogs' do
  99. subject.reblog = other
  100. expect(subject.proper).to eq other
  101. end
  102. end
  103. describe '.mutes_map' do
  104. let(:status) { Fabricate(:status) }
  105. let(:account) { Fabricate(:account) }
  106. subject { Status.mutes_map([status.conversation.id], account) }
  107. it 'returns a hash' do
  108. expect(subject).to be_a Hash
  109. end
  110. it 'contains true value' do
  111. account.mute_conversation!(status.conversation)
  112. expect(subject[status.conversation.id]).to be true
  113. end
  114. end
  115. describe '.favourites_map' do
  116. let(:status) { Fabricate(:status) }
  117. let(:account) { Fabricate(:account) }
  118. subject { Status.favourites_map([status], account) }
  119. it 'returns a hash' do
  120. expect(subject).to be_a Hash
  121. end
  122. it 'contains true value' do
  123. Fabricate(:favourite, status: status, account: account)
  124. expect(subject[status.id]).to be true
  125. end
  126. end
  127. describe '.reblogs_map' do
  128. let(:status) { Fabricate(:status) }
  129. let(:account) { Fabricate(:account) }
  130. subject { Status.reblogs_map([status], account) }
  131. it 'returns a hash' do
  132. expect(subject).to be_a Hash
  133. end
  134. it 'contains true value' do
  135. Fabricate(:status, account: account, reblog: status)
  136. expect(subject[status.id]).to be true
  137. end
  138. end
  139. describe '.local_only' do
  140. it 'returns only statuses from local accounts' do
  141. local_account = Fabricate(:account, domain: nil)
  142. remote_account = Fabricate(:account, domain: 'test.com')
  143. local_status = Fabricate(:status, account: local_account)
  144. remote_status = Fabricate(:status, account: remote_account)
  145. results = described_class.local_only
  146. expect(results).to include(local_status)
  147. expect(results).not_to include(remote_status)
  148. end
  149. end
  150. describe '.as_home_timeline' do
  151. let(:account) { Fabricate(:account) }
  152. let(:followed) { Fabricate(:account) }
  153. let(:not_followed) { Fabricate(:account) }
  154. before do
  155. Fabricate(:follow, account: account, target_account: followed)
  156. @self_status = Fabricate(:status, account: account, visibility: :public)
  157. @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
  158. @followed_status = Fabricate(:status, account: followed, visibility: :public)
  159. @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
  160. @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
  161. @results = Status.as_home_timeline(account)
  162. end
  163. it 'includes statuses from self' do
  164. expect(@results).to include(@self_status)
  165. end
  166. it 'does not include direct statuses from self' do
  167. expect(@results).to_not include(@self_direct_status)
  168. end
  169. it 'includes statuses from followed' do
  170. expect(@results).to include(@followed_status)
  171. end
  172. it 'does not include direct statuses mentioning recipient from followed' do
  173. Fabricate(:mention, account: account, status: @followed_direct_status)
  174. expect(@results).to_not include(@followed_direct_status)
  175. end
  176. it 'does not include direct statuses not mentioning recipient from followed' do
  177. expect(@results).not_to include(@followed_direct_status)
  178. end
  179. it 'does not include statuses from non-followed' do
  180. expect(@results).not_to include(@not_followed_status)
  181. end
  182. end
  183. describe '.as_public_timeline' do
  184. it 'only includes statuses with public visibility' do
  185. public_status = Fabricate(:status, visibility: :public)
  186. private_status = Fabricate(:status, visibility: :private)
  187. results = Status.as_public_timeline
  188. expect(results).to include(public_status)
  189. expect(results).not_to include(private_status)
  190. end
  191. it 'does not include replies' do
  192. status = Fabricate(:status)
  193. reply = Fabricate(:status, in_reply_to_id: status.id)
  194. results = Status.as_public_timeline
  195. expect(results).to include(status)
  196. expect(results).not_to include(reply)
  197. end
  198. it 'does not include boosts' do
  199. status = Fabricate(:status)
  200. boost = Fabricate(:status, reblog_of_id: status.id)
  201. results = Status.as_public_timeline
  202. expect(results).to include(status)
  203. expect(results).not_to include(boost)
  204. end
  205. it 'filters out silenced accounts' do
  206. account = Fabricate(:account)
  207. silenced_account = Fabricate(:account, silenced: true)
  208. status = Fabricate(:status, account: account)
  209. silenced_status = Fabricate(:status, account: silenced_account)
  210. results = Status.as_public_timeline
  211. expect(results).to include(status)
  212. expect(results).not_to include(silenced_status)
  213. end
  214. context 'without local_only option' do
  215. let(:viewer) { nil }
  216. let!(:local_account) { Fabricate(:account, domain: nil) }
  217. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  218. let!(:local_status) { Fabricate(:status, account: local_account) }
  219. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  220. subject { Status.as_public_timeline(viewer, false) }
  221. context 'without a viewer' do
  222. let(:viewer) { nil }
  223. it 'includes remote instances statuses' do
  224. expect(subject).to include(remote_status)
  225. end
  226. it 'includes local statuses' do
  227. expect(subject).to include(local_status)
  228. end
  229. end
  230. context 'with a viewer' do
  231. let(:viewer) { Fabricate(:account, username: 'viewer') }
  232. it 'includes remote instances statuses' do
  233. expect(subject).to include(remote_status)
  234. end
  235. it 'includes local statuses' do
  236. expect(subject).to include(local_status)
  237. end
  238. end
  239. end
  240. context 'with a local_only option set' do
  241. let!(:local_account) { Fabricate(:account, domain: nil) }
  242. let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
  243. let!(:local_status) { Fabricate(:status, account: local_account) }
  244. let!(:remote_status) { Fabricate(:status, account: remote_account) }
  245. subject { Status.as_public_timeline(viewer, true) }
  246. context 'without a viewer' do
  247. let(:viewer) { nil }
  248. it 'does not include remote instances statuses' do
  249. expect(subject).to include(local_status)
  250. expect(subject).not_to include(remote_status)
  251. end
  252. end
  253. context 'with a viewer' do
  254. let(:viewer) { Fabricate(:account, username: 'viewer') }
  255. it 'does not include remote instances statuses' do
  256. expect(subject).to include(local_status)
  257. expect(subject).not_to include(remote_status)
  258. end
  259. it 'is not affected by personal domain blocks' do
  260. viewer.block_domain!('test.com')
  261. expect(subject).to include(local_status)
  262. expect(subject).not_to include(remote_status)
  263. end
  264. end
  265. end
  266. describe 'with an account passed in' do
  267. before do
  268. @account = Fabricate(:account)
  269. end
  270. it 'excludes statuses from accounts blocked by the account' do
  271. blocked = Fabricate(:account)
  272. Fabricate(:block, account: @account, target_account: blocked)
  273. blocked_status = Fabricate(:status, account: blocked)
  274. results = Status.as_public_timeline(@account)
  275. expect(results).not_to include(blocked_status)
  276. end
  277. it 'excludes statuses from accounts who have blocked the account' do
  278. blocked = Fabricate(:account)
  279. Fabricate(:block, account: blocked, target_account: @account)
  280. blocked_status = Fabricate(:status, account: blocked)
  281. results = Status.as_public_timeline(@account)
  282. expect(results).not_to include(blocked_status)
  283. end
  284. it 'excludes statuses from accounts muted by the account' do
  285. muted = Fabricate(:account)
  286. Fabricate(:mute, account: @account, target_account: muted)
  287. muted_status = Fabricate(:status, account: muted)
  288. results = Status.as_public_timeline(@account)
  289. expect(results).not_to include(muted_status)
  290. end
  291. it 'excludes statuses from accounts from personally blocked domains' do
  292. blocked = Fabricate(:account, domain: 'example.com')
  293. @account.block_domain!(blocked.domain)
  294. blocked_status = Fabricate(:status, account: blocked)
  295. results = Status.as_public_timeline(@account)
  296. expect(results).not_to include(blocked_status)
  297. end
  298. context 'with language preferences' do
  299. it 'excludes statuses in languages not allowed by the account user' do
  300. user = Fabricate(:user, filtered_languages: [:fr])
  301. @account.update(user: user)
  302. en_status = Fabricate(:status, language: 'en')
  303. es_status = Fabricate(:status, language: 'es')
  304. fr_status = Fabricate(:status, language: 'fr')
  305. results = Status.as_public_timeline(@account)
  306. expect(results).to include(en_status)
  307. expect(results).to include(es_status)
  308. expect(results).not_to include(fr_status)
  309. end
  310. it 'includes all languages when user does not have a setting' do
  311. user = Fabricate(:user, filtered_languages: [])
  312. @account.update(user: user)
  313. en_status = Fabricate(:status, language: 'en')
  314. es_status = Fabricate(:status, language: 'es')
  315. results = Status.as_public_timeline(@account)
  316. expect(results).to include(en_status)
  317. expect(results).to include(es_status)
  318. end
  319. it 'includes all languages when account does not have a user' do
  320. expect(@account.user).to be_nil
  321. en_status = Fabricate(:status, language: 'en')
  322. es_status = Fabricate(:status, language: 'es')
  323. results = Status.as_public_timeline(@account)
  324. expect(results).to include(en_status)
  325. expect(results).to include(es_status)
  326. end
  327. end
  328. context 'where that account is silenced' do
  329. it 'includes statuses from other accounts that are silenced' do
  330. @account.update(silenced: true)
  331. other_silenced_account = Fabricate(:account, silenced: true)
  332. other_status = Fabricate(:status, account: other_silenced_account)
  333. results = Status.as_public_timeline(@account)
  334. expect(results).to include(other_status)
  335. end
  336. end
  337. end
  338. end
  339. describe '.as_tag_timeline' do
  340. it 'includes statuses with a tag' do
  341. tag = Fabricate(:tag)
  342. status = Fabricate(:status, tags: [tag])
  343. other = Fabricate(:status)
  344. results = Status.as_tag_timeline(tag)
  345. expect(results).to include(status)
  346. expect(results).not_to include(other)
  347. end
  348. it 'allows replies to be included' do
  349. original = Fabricate(:status)
  350. tag = Fabricate(:tag)
  351. status = Fabricate(:status, tags: [tag], in_reply_to_id: original.id)
  352. results = Status.as_tag_timeline(tag)
  353. expect(results).to include(status)
  354. end
  355. end
  356. describe '.permitted_for' do
  357. subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
  358. let(:target_account) { alice }
  359. let(:account) { bob }
  360. let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
  361. let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
  362. let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
  363. let!(:direct_status) do
  364. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  365. Fabricate(:mention, status: status, account: account)
  366. end
  367. end
  368. let!(:other_direct_status) do
  369. Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
  370. Fabricate(:mention, status: status)
  371. end
  372. end
  373. context 'given nil' do
  374. let(:account) { nil }
  375. let(:direct_status) { nil }
  376. it { is_expected.to eq(%w(unlisted public)) }
  377. end
  378. context 'given blocked account' do
  379. before do
  380. target_account.block!(account)
  381. end
  382. it { is_expected.to be_empty }
  383. end
  384. context 'given same account' do
  385. let(:account) { target_account }
  386. it { is_expected.to eq(%w(direct direct private unlisted public)) }
  387. end
  388. context 'given followed account' do
  389. before do
  390. account.follow!(target_account)
  391. end
  392. it { is_expected.to eq(%w(direct private unlisted public)) }
  393. end
  394. context 'given unfollowed account' do
  395. it { is_expected.to eq(%w(direct unlisted public)) }
  396. end
  397. end
  398. describe 'before_validation' do
  399. it 'sets account being replied to correctly over intermediary nodes' do
  400. first_status = Fabricate(:status, account: bob)
  401. intermediary = Fabricate(:status, thread: first_status, account: alice)
  402. final = Fabricate(:status, thread: intermediary, account: alice)
  403. expect(final.in_reply_to_account_id).to eq bob.id
  404. end
  405. it 'creates new conversation for stand-alone status' do
  406. expect(Status.create(account: alice, text: 'First').conversation_id).to_not be_nil
  407. end
  408. it 'keeps conversation of parent node' do
  409. parent = Fabricate(:status, text: 'First')
  410. expect(Status.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id
  411. end
  412. it 'sets `local` to true for status by local account' do
  413. expect(Status.create(account: alice, text: 'foo').local).to be true
  414. end
  415. it 'sets `local` to false for status by remote account' do
  416. alice.update(domain: 'example.com')
  417. expect(Status.create(account: alice, text: 'foo').local).to be false
  418. end
  419. end
  420. describe 'after_create' do
  421. it 'saves ActivityPub uri as uri for local status' do
  422. status = Status.create(account: alice, text: 'foo')
  423. status.reload
  424. expect(status.uri).to start_with('https://')
  425. end
  426. end
  427. end