]> cat aescling's git repositories - mastodon.git/blob - spec/models/status_spec.rb
Update AUTHORS.md (#9141)
[mastodon.git] / spec / models / status_spec.rb
1 require 'rails_helper'
2
3 RSpec.describe Status, type: :model do
4 let(:alice) { Fabricate(:account, username: 'alice') }
5 let(:bob) { Fabricate(:account, username: 'bob') }
6 let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') }
7
8 subject { Fabricate(:status, account: alice) }
9
10 describe '#local?' do
11 it 'returns true when no remote URI is set' do
12 expect(subject.local?).to be true
13 end
14
15 it 'returns false if a remote URI is set' do
16 alice.update(domain: 'example.com')
17 subject.save
18 expect(subject.local?).to be false
19 end
20
21 it 'returns true if a URI is set and `local` is true' do
22 subject.update(uri: 'example.com', local: true)
23 expect(subject.local?).to be true
24 end
25 end
26
27 describe '#reblog?' do
28 it 'returns true when the status reblogs another status' do
29 subject.reblog = other
30 expect(subject.reblog?).to be true
31 end
32
33 it 'returns false if the status is self-contained' do
34 expect(subject.reblog?).to be false
35 end
36 end
37
38 describe '#reply?' do
39 it 'returns true if the status references another' do
40 subject.thread = other
41 expect(subject.reply?).to be true
42 end
43
44 it 'returns false if the status is self-contained' do
45 expect(subject.reply?).to be false
46 end
47 end
48
49 describe '#verb' do
50 context 'if destroyed?' do
51 it 'returns :delete' do
52 subject.destroy!
53 expect(subject.verb).to be :delete
54 end
55 end
56
57 context 'unless destroyed?' do
58 context 'if reblog?' do
59 it 'returns :share' do
60 subject.reblog = other
61 expect(subject.verb).to be :share
62 end
63 end
64
65 context 'unless reblog?' do
66 it 'returns :post' do
67 subject.reblog = nil
68 expect(subject.verb).to be :post
69 end
70 end
71 end
72 end
73
74 describe '#object_type' do
75 it 'is note when the status is self-contained' do
76 expect(subject.object_type).to be :note
77 end
78
79 it 'is comment when the status replies to another' do
80 subject.thread = other
81 expect(subject.object_type).to be :comment
82 end
83 end
84
85 describe '#title' do
86 # rubocop:disable Style/InterpolationCheck
87
88 let(:account) { subject.account }
89
90 context 'if destroyed?' do
91 it 'returns "#{account.acct} deleted status"' do
92 subject.destroy!
93 expect(subject.title).to eq "#{account.acct} deleted status"
94 end
95 end
96
97 context 'unless destroyed?' do
98 context 'if reblog?' do
99 it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do
100 reblog = subject.reblog = other
101 expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
102 end
103 end
104
105 context 'unless reblog?' do
106 it 'returns "New status by #{account.acct}"' do
107 subject.reblog = nil
108 expect(subject.title).to eq "New status by #{account.acct}"
109 end
110 end
111 end
112 end
113
114 describe '#hidden?' do
115 context 'if private_visibility?' do
116 it 'returns true' do
117 subject.visibility = :private
118 expect(subject.hidden?).to be true
119 end
120 end
121
122 context 'if direct_visibility?' do
123 it 'returns true' do
124 subject.visibility = :direct
125 expect(subject.hidden?).to be true
126 end
127 end
128
129 context 'if public_visibility?' do
130 it 'returns false' do
131 subject.visibility = :public
132 expect(subject.hidden?).to be false
133 end
134 end
135
136 context 'if unlisted_visibility?' do
137 it 'returns false' do
138 subject.visibility = :unlisted
139 expect(subject.hidden?).to be false
140 end
141 end
142 end
143
144 describe '#content' do
145 it 'returns the text of the status if it is not a reblog' do
146 expect(subject.content).to eql subject.text
147 end
148
149 it 'returns the text of the reblogged status' do
150 subject.reblog = other
151 expect(subject.content).to eql other.text
152 end
153 end
154
155 describe '#target' do
156 it 'returns nil if the status is self-contained' do
157 expect(subject.target).to be_nil
158 end
159
160 it 'returns nil if the status is a reply' do
161 subject.thread = other
162 expect(subject.target).to be_nil
163 end
164
165 it 'returns the reblogged status' do
166 subject.reblog = other
167 expect(subject.target).to eq other
168 end
169 end
170
171 describe '#reblogs_count' do
172 it 'is the number of reblogs' do
173 Fabricate(:status, account: bob, reblog: subject)
174 Fabricate(:status, account: alice, reblog: subject)
175
176 expect(subject.reblogs_count).to eq 2
177 end
178
179 it 'is decremented when reblog is removed' do
180 reblog = Fabricate(:status, account: bob, reblog: subject)
181 expect(subject.reblogs_count).to eq 1
182 reblog.destroy
183 expect(subject.reblogs_count).to eq 0
184 end
185
186 it 'does not fail when original is deleted before reblog' do
187 reblog = Fabricate(:status, account: bob, reblog: subject)
188 expect(subject.reblogs_count).to eq 1
189 expect { subject.destroy }.to_not raise_error
190 expect(Status.find_by(id: reblog.id)).to be_nil
191 end
192 end
193
194 describe '#replies_count' do
195 it 'is the number of replies' do
196 reply = Fabricate(:status, account: bob, thread: subject)
197 expect(subject.replies_count).to eq 1
198 end
199
200 it 'is decremented when reply is removed' do
201 reply = Fabricate(:status, account: bob, thread: subject)
202 expect(subject.replies_count).to eq 1
203 reply.destroy
204 expect(subject.replies_count).to eq 0
205 end
206 end
207
208 describe '#favourites_count' do
209 it 'is the number of favorites' do
210 Fabricate(:favourite, account: bob, status: subject)
211 Fabricate(:favourite, account: alice, status: subject)
212
213 expect(subject.favourites_count).to eq 2
214 end
215
216 it 'is decremented when favourite is removed' do
217 favourite = Fabricate(:favourite, account: bob, status: subject)
218 expect(subject.favourites_count).to eq 1
219 favourite.destroy
220 expect(subject.favourites_count).to eq 0
221 end
222 end
223
224 describe '#proper' do
225 it 'is itself for original statuses' do
226 expect(subject.proper).to eq subject
227 end
228
229 it 'is the source status for reblogs' do
230 subject.reblog = other
231 expect(subject.proper).to eq other
232 end
233 end
234
235 describe '.mutes_map' do
236 let(:status) { Fabricate(:status) }
237 let(:account) { Fabricate(:account) }
238
239 subject { Status.mutes_map([status.conversation.id], account) }
240
241 it 'returns a hash' do
242 expect(subject).to be_a Hash
243 end
244
245 it 'contains true value' do
246 account.mute_conversation!(status.conversation)
247 expect(subject[status.conversation.id]).to be true
248 end
249 end
250
251 describe '.favourites_map' do
252 let(:status) { Fabricate(:status) }
253 let(:account) { Fabricate(:account) }
254
255 subject { Status.favourites_map([status], account) }
256
257 it 'returns a hash' do
258 expect(subject).to be_a Hash
259 end
260
261 it 'contains true value' do
262 Fabricate(:favourite, status: status, account: account)
263 expect(subject[status.id]).to be true
264 end
265 end
266
267 describe '.reblogs_map' do
268 let(:status) { Fabricate(:status) }
269 let(:account) { Fabricate(:account) }
270
271 subject { Status.reblogs_map([status], account) }
272
273 it 'returns a hash' do
274 expect(subject).to be_a Hash
275 end
276
277 it 'contains true value' do
278 Fabricate(:status, account: account, reblog: status)
279 expect(subject[status.id]).to be true
280 end
281 end
282
283 describe '.in_chosen_languages' do
284 context 'for accounts with language filters' do
285 let(:user) { Fabricate(:user, chosen_languages: ['en']) }
286
287 it 'does not include statuses in not in chosen languages' do
288 status = Fabricate(:status, language: 'de')
289 expect(Status.in_chosen_languages(user.account)).not_to include status
290 end
291
292 it 'includes status with unknown language' do
293 status = Fabricate(:status, language: nil)
294 expect(Status.in_chosen_languages(user.account)).to include status
295 end
296 end
297 end
298
299 describe '.as_home_timeline' do
300 let(:account) { Fabricate(:account) }
301 let(:followed) { Fabricate(:account) }
302 let(:not_followed) { Fabricate(:account) }
303
304 before do
305 Fabricate(:follow, account: account, target_account: followed)
306
307 @self_status = Fabricate(:status, account: account, visibility: :public)
308 @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
309 @followed_status = Fabricate(:status, account: followed, visibility: :public)
310 @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
311 @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
312
313 @results = Status.as_home_timeline(account)
314 end
315
316 it 'includes statuses from self' do
317 expect(@results).to include(@self_status)
318 end
319
320 it 'does not include direct statuses from self' do
321 expect(@results).to_not include(@self_direct_status)
322 end
323
324 it 'includes statuses from followed' do
325 expect(@results).to include(@followed_status)
326 end
327
328 it 'does not include direct statuses mentioning recipient from followed' do
329 Fabricate(:mention, account: account, status: @followed_direct_status)
330 expect(@results).to_not include(@followed_direct_status)
331 end
332
333 it 'does not include direct statuses not mentioning recipient from followed' do
334 expect(@results).not_to include(@followed_direct_status)
335 end
336
337 it 'does not include statuses from non-followed' do
338 expect(@results).not_to include(@not_followed_status)
339 end
340 end
341
342 describe '.as_direct_timeline' do
343 let(:account) { Fabricate(:account) }
344 let(:followed) { Fabricate(:account) }
345 let(:not_followed) { Fabricate(:account) }
346
347 before do
348 Fabricate(:follow, account: account, target_account: followed)
349
350 @self_public_status = Fabricate(:status, account: account, visibility: :public)
351 @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
352 @followed_public_status = Fabricate(:status, account: followed, visibility: :public)
353 @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
354 @not_followed_direct_status = Fabricate(:status, account: not_followed, visibility: :direct)
355
356 @results = Status.as_direct_timeline(account)
357 end
358
359 it 'does not include public statuses from self' do
360 expect(@results).to_not include(@self_public_status)
361 end
362
363 it 'includes direct statuses from self' do
364 expect(@results).to include(@self_direct_status)
365 end
366
367 it 'does not include public statuses from followed' do
368 expect(@results).to_not include(@followed_public_status)
369 end
370
371 it 'does not include direct statuses not mentioning recipient from followed' do
372 expect(@results).to_not include(@followed_direct_status)
373 end
374
375 it 'does not include direct statuses not mentioning recipient from non-followed' do
376 expect(@results).to_not include(@not_followed_direct_status)
377 end
378
379 it 'includes direct statuses mentioning recipient from followed' do
380 Fabricate(:mention, account: account, status: @followed_direct_status)
381 results2 = Status.as_direct_timeline(account)
382 expect(results2).to include(@followed_direct_status)
383 end
384
385 it 'includes direct statuses mentioning recipient from non-followed' do
386 Fabricate(:mention, account: account, status: @not_followed_direct_status)
387 results2 = Status.as_direct_timeline(account)
388 expect(results2).to include(@not_followed_direct_status)
389 end
390 end
391
392 describe '.as_public_timeline' do
393 it 'only includes statuses with public visibility' do
394 public_status = Fabricate(:status, visibility: :public)
395 private_status = Fabricate(:status, visibility: :private)
396
397 results = Status.as_public_timeline
398 expect(results).to include(public_status)
399 expect(results).not_to include(private_status)
400 end
401
402 it 'does not include replies' do
403 status = Fabricate(:status)
404 reply = Fabricate(:status, in_reply_to_id: status.id)
405
406 results = Status.as_public_timeline
407 expect(results).to include(status)
408 expect(results).not_to include(reply)
409 end
410
411 it 'does not include boosts' do
412 status = Fabricate(:status)
413 boost = Fabricate(:status, reblog_of_id: status.id)
414
415 results = Status.as_public_timeline
416 expect(results).to include(status)
417 expect(results).not_to include(boost)
418 end
419
420 it 'filters out silenced accounts' do
421 account = Fabricate(:account)
422 silenced_account = Fabricate(:account, silenced: true)
423 status = Fabricate(:status, account: account)
424 silenced_status = Fabricate(:status, account: silenced_account)
425
426 results = Status.as_public_timeline
427 expect(results).to include(status)
428 expect(results).not_to include(silenced_status)
429 end
430
431 context 'without local_only option' do
432 let(:viewer) { nil }
433
434 let!(:local_account) { Fabricate(:account, domain: nil) }
435 let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
436 let!(:local_status) { Fabricate(:status, account: local_account) }
437 let!(:remote_status) { Fabricate(:status, account: remote_account) }
438
439 subject { Status.as_public_timeline(viewer, false) }
440
441 context 'without a viewer' do
442 let(:viewer) { nil }
443
444 it 'includes remote instances statuses' do
445 expect(subject).to include(remote_status)
446 end
447
448 it 'includes local statuses' do
449 expect(subject).to include(local_status)
450 end
451 end
452
453 context 'with a viewer' do
454 let(:viewer) { Fabricate(:account, username: 'viewer') }
455
456 it 'includes remote instances statuses' do
457 expect(subject).to include(remote_status)
458 end
459
460 it 'includes local statuses' do
461 expect(subject).to include(local_status)
462 end
463 end
464 end
465
466 context 'with a local_only option set' do
467 let!(:local_account) { Fabricate(:account, domain: nil) }
468 let!(:remote_account) { Fabricate(:account, domain: 'test.com') }
469 let!(:local_status) { Fabricate(:status, account: local_account) }
470 let!(:remote_status) { Fabricate(:status, account: remote_account) }
471
472 subject { Status.as_public_timeline(viewer, true) }
473
474 context 'without a viewer' do
475 let(:viewer) { nil }
476
477 it 'does not include remote instances statuses' do
478 expect(subject).to include(local_status)
479 expect(subject).not_to include(remote_status)
480 end
481 end
482
483 context 'with a viewer' do
484 let(:viewer) { Fabricate(:account, username: 'viewer') }
485
486 it 'does not include remote instances statuses' do
487 expect(subject).to include(local_status)
488 expect(subject).not_to include(remote_status)
489 end
490
491 it 'is not affected by personal domain blocks' do
492 viewer.block_domain!('test.com')
493 expect(subject).to include(local_status)
494 expect(subject).not_to include(remote_status)
495 end
496 end
497 end
498
499 describe 'with an account passed in' do
500 before do
501 @account = Fabricate(:account)
502 end
503
504 it 'excludes statuses from accounts blocked by the account' do
505 blocked = Fabricate(:account)
506 Fabricate(:block, account: @account, target_account: blocked)
507 blocked_status = Fabricate(:status, account: blocked)
508
509 results = Status.as_public_timeline(@account)
510 expect(results).not_to include(blocked_status)
511 end
512
513 it 'excludes statuses from accounts who have blocked the account' do
514 blocked = Fabricate(:account)
515 Fabricate(:block, account: blocked, target_account: @account)
516 blocked_status = Fabricate(:status, account: blocked)
517
518 results = Status.as_public_timeline(@account)
519 expect(results).not_to include(blocked_status)
520 end
521
522 it 'excludes statuses from accounts muted by the account' do
523 muted = Fabricate(:account)
524 Fabricate(:mute, account: @account, target_account: muted)
525 muted_status = Fabricate(:status, account: muted)
526
527 results = Status.as_public_timeline(@account)
528 expect(results).not_to include(muted_status)
529 end
530
531 it 'excludes statuses from accounts from personally blocked domains' do
532 blocked = Fabricate(:account, domain: 'example.com')
533 @account.block_domain!(blocked.domain)
534 blocked_status = Fabricate(:status, account: blocked)
535
536 results = Status.as_public_timeline(@account)
537 expect(results).not_to include(blocked_status)
538 end
539
540 context 'with language preferences' do
541 it 'excludes statuses in languages not allowed by the account user' do
542 user = Fabricate(:user, chosen_languages: [:en, :es])
543 @account.update(user: user)
544 en_status = Fabricate(:status, language: 'en')
545 es_status = Fabricate(:status, language: 'es')
546 fr_status = Fabricate(:status, language: 'fr')
547
548 results = Status.as_public_timeline(@account)
549 expect(results).to include(en_status)
550 expect(results).to include(es_status)
551 expect(results).not_to include(fr_status)
552 end
553
554 it 'includes all languages when user does not have a setting' do
555 user = Fabricate(:user, chosen_languages: nil)
556 @account.update(user: user)
557
558 en_status = Fabricate(:status, language: 'en')
559 es_status = Fabricate(:status, language: 'es')
560
561 results = Status.as_public_timeline(@account)
562 expect(results).to include(en_status)
563 expect(results).to include(es_status)
564 end
565
566 it 'includes all languages when account does not have a user' do
567 expect(@account.user).to be_nil
568 en_status = Fabricate(:status, language: 'en')
569 es_status = Fabricate(:status, language: 'es')
570
571 results = Status.as_public_timeline(@account)
572 expect(results).to include(en_status)
573 expect(results).to include(es_status)
574 end
575 end
576 end
577 end
578
579 describe '.as_tag_timeline' do
580 it 'includes statuses with a tag' do
581 tag = Fabricate(:tag)
582 status = Fabricate(:status, tags: [tag])
583 other = Fabricate(:status)
584
585 results = Status.as_tag_timeline(tag)
586 expect(results).to include(status)
587 expect(results).not_to include(other)
588 end
589
590 it 'allows replies to be included' do
591 original = Fabricate(:status)
592 tag = Fabricate(:tag)
593 status = Fabricate(:status, tags: [tag], in_reply_to_id: original.id)
594
595 results = Status.as_tag_timeline(tag)
596 expect(results).to include(status)
597 end
598 end
599
600 describe '.permitted_for' do
601 subject { described_class.permitted_for(target_account, account).pluck(:visibility) }
602
603 let(:target_account) { alice }
604 let(:account) { bob }
605 let!(:public_status) { Fabricate(:status, account: target_account, visibility: 'public') }
606 let!(:unlisted_status) { Fabricate(:status, account: target_account, visibility: 'unlisted') }
607 let!(:private_status) { Fabricate(:status, account: target_account, visibility: 'private') }
608
609 let!(:direct_status) do
610 Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
611 Fabricate(:mention, status: status, account: account)
612 end
613 end
614
615 let!(:other_direct_status) do
616 Fabricate(:status, account: target_account, visibility: 'direct').tap do |status|
617 Fabricate(:mention, status: status)
618 end
619 end
620
621 context 'given nil' do
622 let(:account) { nil }
623 let(:direct_status) { nil }
624 it { is_expected.to eq(%w(unlisted public)) }
625 end
626
627 context 'given blocked account' do
628 before do
629 target_account.block!(account)
630 end
631
632 it { is_expected.to be_empty }
633 end
634
635 context 'given same account' do
636 let(:account) { target_account }
637 it { is_expected.to eq(%w(direct direct private unlisted public)) }
638 end
639
640 context 'given followed account' do
641 before do
642 account.follow!(target_account)
643 end
644
645 it { is_expected.to eq(%w(direct private unlisted public)) }
646 end
647
648 context 'given unfollowed account' do
649 it { is_expected.to eq(%w(direct unlisted public)) }
650 end
651 end
652
653 describe 'before_validation' do
654 it 'sets account being replied to correctly over intermediary nodes' do
655 first_status = Fabricate(:status, account: bob)
656 intermediary = Fabricate(:status, thread: first_status, account: alice)
657 final = Fabricate(:status, thread: intermediary, account: alice)
658
659 expect(final.in_reply_to_account_id).to eq bob.id
660 end
661
662 it 'creates new conversation for stand-alone status' do
663 expect(Status.create(account: alice, text: 'First').conversation_id).to_not be_nil
664 end
665
666 it 'keeps conversation of parent node' do
667 parent = Fabricate(:status, text: 'First')
668 expect(Status.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id
669 end
670
671 it 'sets `local` to true for status by local account' do
672 expect(Status.create(account: alice, text: 'foo').local).to be true
673 end
674
675 it 'sets `local` to false for status by remote account' do
676 alice.update(domain: 'example.com')
677 expect(Status.create(account: alice, text: 'foo').local).to be false
678 end
679 end
680
681 describe 'validation' do
682 it 'disallow empty uri for remote status' do
683 alice.update(domain: 'example.com')
684 status = Fabricate.build(:status, uri: '', account: alice)
685 expect(status).to model_have_error_on_field(:uri)
686 end
687 end
688
689 describe 'after_create' do
690 it 'saves ActivityPub uri as uri for local status' do
691 status = Status.create(account: alice, text: 'foo')
692 status.reload
693 expect(status.uri).to start_with('https://')
694 end
695 end
696 end
This page took 0.330463 seconds and 4 git commands to generate.