New mix tasks for controlling user confirmation status and sending confirmation mails
[akkoma] / test / web / common_api / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPITest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Chat
9 alias Pleroma.Conversation.Participation
10 alias Pleroma.Notification
11 alias Pleroma.Object
12 alias Pleroma.Repo
13 alias Pleroma.User
14 alias Pleroma.Web.ActivityPub.ActivityPub
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.ActivityPub.Visibility
17 alias Pleroma.Web.AdminAPI.AccountView
18 alias Pleroma.Web.CommonAPI
19
20 import Pleroma.Factory
21 import Mock
22 import Ecto.Query, only: [from: 2]
23
24 require Pleroma.Constants
25
26 setup do: clear_config([:instance, :safe_dm_mentions])
27 setup do: clear_config([:instance, :limit])
28 setup do: clear_config([:instance, :max_pinned_statuses])
29
30 describe "blocking" do
31 setup do
32 blocker = insert(:user)
33 blocked = insert(:user)
34 User.follow(blocker, blocked)
35 User.follow(blocked, blocker)
36 %{blocker: blocker, blocked: blocked}
37 end
38
39 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
40 clear_config([:instance, :federating], true)
41
42 with_mock Pleroma.Web.Federator,
43 publish: fn _ -> nil end do
44 assert {:ok, block} = CommonAPI.block(blocker, blocked)
45
46 assert block.local
47 assert User.blocks?(blocker, blocked)
48 refute User.following?(blocker, blocked)
49 refute User.following?(blocked, blocker)
50
51 assert called(Pleroma.Web.Federator.publish(block))
52 end
53 end
54
55 test "it blocks and does not federate if outgoing blocks are disabled", %{
56 blocker: blocker,
57 blocked: blocked
58 } do
59 clear_config([:instance, :federating], true)
60 clear_config([:activitypub, :outgoing_blocks], false)
61
62 with_mock Pleroma.Web.Federator,
63 publish: fn _ -> nil end do
64 assert {:ok, block} = CommonAPI.block(blocker, blocked)
65
66 assert block.local
67 assert User.blocks?(blocker, blocked)
68 refute User.following?(blocker, blocked)
69 refute User.following?(blocked, blocker)
70
71 refute called(Pleroma.Web.Federator.publish(block))
72 end
73 end
74 end
75
76 describe "posting chat messages" do
77 setup do: clear_config([:instance, :chat_limit])
78
79 test "it posts a chat message without content but with an attachment" do
80 author = insert(:user)
81 recipient = insert(:user)
82
83 file = %Plug.Upload{
84 content_type: "image/jpg",
85 path: Path.absname("test/fixtures/image.jpg"),
86 filename: "an_image.jpg"
87 }
88
89 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
90
91 with_mocks([
92 {
93 Pleroma.Web.Streamer,
94 [],
95 [
96 stream: fn _, _ ->
97 nil
98 end
99 ]
100 },
101 {
102 Pleroma.Web.Push,
103 [],
104 [
105 send: fn _ -> nil end
106 ]
107 }
108 ]) do
109 {:ok, activity} =
110 CommonAPI.post_chat_message(
111 author,
112 recipient,
113 nil,
114 media_id: upload.id
115 )
116
117 notification =
118 Notification.for_user_and_activity(recipient, activity)
119 |> Repo.preload(:activity)
120
121 assert called(Pleroma.Web.Push.send(notification))
122 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
123 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
124
125 assert activity
126 end
127 end
128
129 test "it adds html newlines" do
130 author = insert(:user)
131 recipient = insert(:user)
132
133 other_user = insert(:user)
134
135 {:ok, activity} =
136 CommonAPI.post_chat_message(
137 author,
138 recipient,
139 "uguu\nuguuu"
140 )
141
142 assert other_user.ap_id not in activity.recipients
143
144 object = Object.normalize(activity, false)
145
146 assert object.data["content"] == "uguu<br/>uguuu"
147 end
148
149 test "it linkifies" do
150 author = insert(:user)
151 recipient = insert(:user)
152
153 other_user = insert(:user)
154
155 {:ok, activity} =
156 CommonAPI.post_chat_message(
157 author,
158 recipient,
159 "https://example.org is the site of @#{other_user.nickname} #2hu"
160 )
161
162 assert other_user.ap_id not in activity.recipients
163
164 object = Object.normalize(activity, false)
165
166 assert object.data["content"] ==
167 "<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{
168 other_user.id
169 }\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
170 end
171
172 test "it posts a chat message" do
173 author = insert(:user)
174 recipient = insert(:user)
175
176 {:ok, activity} =
177 CommonAPI.post_chat_message(
178 author,
179 recipient,
180 "a test message <script>alert('uuu')</script> :firefox:"
181 )
182
183 assert activity.data["type"] == "Create"
184 assert activity.local
185 object = Object.normalize(activity)
186
187 assert object.data["type"] == "ChatMessage"
188 assert object.data["to"] == [recipient.ap_id]
189
190 assert object.data["content"] ==
191 "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
192
193 assert object.data["emoji"] == %{
194 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
195 }
196
197 assert Chat.get(author.id, recipient.ap_id)
198 assert Chat.get(recipient.id, author.ap_id)
199
200 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
201 end
202
203 test "it reject messages over the local limit" do
204 Pleroma.Config.put([:instance, :chat_limit], 2)
205
206 author = insert(:user)
207 recipient = insert(:user)
208
209 {:error, message} =
210 CommonAPI.post_chat_message(
211 author,
212 recipient,
213 "123"
214 )
215
216 assert message == :content_too_long
217 end
218 end
219
220 describe "unblocking" do
221 test "it works even without an existing block activity" do
222 blocked = insert(:user)
223 blocker = insert(:user)
224 User.block(blocker, blocked)
225
226 assert User.blocks?(blocker, blocked)
227 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
228 refute User.blocks?(blocker, blocked)
229 end
230 end
231
232 describe "deletion" do
233 test "it works with pruned objects" do
234 user = insert(:user)
235
236 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
237
238 clear_config([:instance, :federating], true)
239
240 Object.normalize(post, false)
241 |> Object.prune()
242
243 with_mock Pleroma.Web.Federator,
244 publish: fn _ -> nil end do
245 assert {:ok, delete} = CommonAPI.delete(post.id, user)
246 assert delete.local
247 assert called(Pleroma.Web.Federator.publish(delete))
248 end
249
250 refute Activity.get_by_id(post.id)
251 end
252
253 test "it allows users to delete their posts" do
254 user = insert(:user)
255
256 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
257
258 clear_config([:instance, :federating], true)
259
260 with_mock Pleroma.Web.Federator,
261 publish: fn _ -> nil end do
262 assert {:ok, delete} = CommonAPI.delete(post.id, user)
263 assert delete.local
264 assert called(Pleroma.Web.Federator.publish(delete))
265 end
266
267 refute Activity.get_by_id(post.id)
268 end
269
270 test "it does not allow a user to delete their posts" do
271 user = insert(:user)
272 other_user = insert(:user)
273
274 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
275
276 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
277 assert Activity.get_by_id(post.id)
278 end
279
280 test "it allows moderators to delete other user's posts" do
281 user = insert(:user)
282 moderator = insert(:user, is_moderator: true)
283
284 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
285
286 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
287 assert delete.local
288
289 refute Activity.get_by_id(post.id)
290 end
291
292 test "it allows admins to delete other user's posts" do
293 user = insert(:user)
294 moderator = insert(:user, is_admin: true)
295
296 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
297
298 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
299 assert delete.local
300
301 refute Activity.get_by_id(post.id)
302 end
303
304 test "superusers deleting non-local posts won't federate the delete" do
305 # This is the user of the ingested activity
306 _user =
307 insert(:user,
308 local: false,
309 ap_id: "http://mastodon.example.org/users/admin",
310 last_refreshed_at: NaiveDateTime.utc_now()
311 )
312
313 moderator = insert(:user, is_admin: true)
314
315 data =
316 File.read!("test/fixtures/mastodon-post-activity.json")
317 |> Jason.decode!()
318
319 {:ok, post} = Transmogrifier.handle_incoming(data)
320
321 with_mock Pleroma.Web.Federator,
322 publish: fn _ -> nil end do
323 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
324 assert delete.local
325 refute called(Pleroma.Web.Federator.publish(:_))
326 end
327
328 refute Activity.get_by_id(post.id)
329 end
330 end
331
332 test "favoriting race condition" do
333 user = insert(:user)
334 users_serial = insert_list(10, :user)
335 users = insert_list(10, :user)
336
337 {:ok, activity} = CommonAPI.post(user, %{status: "."})
338
339 users_serial
340 |> Enum.map(fn user ->
341 CommonAPI.favorite(user, activity.id)
342 end)
343
344 object = Object.get_by_ap_id(activity.data["object"])
345 assert object.data["like_count"] == 10
346
347 users
348 |> Enum.map(fn user ->
349 Task.async(fn ->
350 CommonAPI.favorite(user, activity.id)
351 end)
352 end)
353 |> Enum.map(&Task.await/1)
354
355 object = Object.get_by_ap_id(activity.data["object"])
356 assert object.data["like_count"] == 20
357 end
358
359 test "repeating race condition" do
360 user = insert(:user)
361 users_serial = insert_list(10, :user)
362 users = insert_list(10, :user)
363
364 {:ok, activity} = CommonAPI.post(user, %{status: "."})
365
366 users_serial
367 |> Enum.map(fn user ->
368 CommonAPI.repeat(activity.id, user)
369 end)
370
371 object = Object.get_by_ap_id(activity.data["object"])
372 assert object.data["announcement_count"] == 10
373
374 users
375 |> Enum.map(fn user ->
376 Task.async(fn ->
377 CommonAPI.repeat(activity.id, user)
378 end)
379 end)
380 |> Enum.map(&Task.await/1)
381
382 object = Object.get_by_ap_id(activity.data["object"])
383 assert object.data["announcement_count"] == 20
384 end
385
386 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
387 user = insert(:user)
388 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
389
390 [participation] = Participation.for_user(user)
391
392 {:ok, convo_reply} =
393 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
394
395 assert Visibility.is_direct?(convo_reply)
396
397 assert activity.data["context"] == convo_reply.data["context"]
398 end
399
400 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
401 har = insert(:user)
402 jafnhar = insert(:user)
403 tridi = insert(:user)
404
405 {:ok, activity} =
406 CommonAPI.post(har, %{
407 status: "@#{jafnhar.nickname} hey",
408 visibility: "direct"
409 })
410
411 assert har.ap_id in activity.recipients
412 assert jafnhar.ap_id in activity.recipients
413
414 [participation] = Participation.for_user(har)
415
416 {:ok, activity} =
417 CommonAPI.post(har, %{
418 status: "I don't really like @#{tridi.nickname}",
419 visibility: "direct",
420 in_reply_to_status_id: activity.id,
421 in_reply_to_conversation_id: participation.id
422 })
423
424 assert har.ap_id in activity.recipients
425 assert jafnhar.ap_id in activity.recipients
426 refute tridi.ap_id in activity.recipients
427 end
428
429 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
430 har = insert(:user)
431 jafnhar = insert(:user)
432 tridi = insert(:user)
433
434 Pleroma.Config.put([:instance, :safe_dm_mentions], true)
435
436 {:ok, activity} =
437 CommonAPI.post(har, %{
438 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
439 visibility: "direct"
440 })
441
442 refute tridi.ap_id in activity.recipients
443 assert jafnhar.ap_id in activity.recipients
444 end
445
446 test "it de-duplicates tags" do
447 user = insert(:user)
448 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
449
450 object = Object.normalize(activity)
451
452 assert object.data["tag"] == ["2hu"]
453 end
454
455 test "it adds emoji in the object" do
456 user = insert(:user)
457 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
458
459 assert Object.normalize(activity).data["emoji"]["firefox"]
460 end
461
462 describe "posting" do
463 test "deactivated users can't post" do
464 user = insert(:user, deactivated: true)
465 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
466 end
467
468 test "it supports explicit addressing" do
469 user = insert(:user)
470 user_two = insert(:user)
471 user_three = insert(:user)
472 user_four = insert(:user)
473
474 {:ok, activity} =
475 CommonAPI.post(user, %{
476 status:
477 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
478 to: [user_two.nickname, user_four.nickname, "nonexistent"]
479 })
480
481 assert user.ap_id in activity.recipients
482 assert user_two.ap_id in activity.recipients
483 assert user_four.ap_id in activity.recipients
484 refute user_three.ap_id in activity.recipients
485 end
486
487 test "it filters out obviously bad tags when accepting a post as HTML" do
488 user = insert(:user)
489
490 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
491
492 {:ok, activity} =
493 CommonAPI.post(user, %{
494 status: post,
495 content_type: "text/html"
496 })
497
498 object = Object.normalize(activity)
499
500 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
501 assert object.data["source"] == post
502 end
503
504 test "it filters out obviously bad tags when accepting a post as Markdown" do
505 user = insert(:user)
506
507 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
508
509 {:ok, activity} =
510 CommonAPI.post(user, %{
511 status: post,
512 content_type: "text/markdown"
513 })
514
515 object = Object.normalize(activity)
516
517 assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
518 assert object.data["source"] == post
519 end
520
521 test "it does not allow replies to direct messages that are not direct messages themselves" do
522 user = insert(:user)
523
524 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
525
526 assert {:ok, _} =
527 CommonAPI.post(user, %{
528 status: "suya..",
529 visibility: "direct",
530 in_reply_to_status_id: activity.id
531 })
532
533 Enum.each(["public", "private", "unlisted"], fn visibility ->
534 assert {:error, "The message visibility must be direct"} =
535 CommonAPI.post(user, %{
536 status: "suya..",
537 visibility: visibility,
538 in_reply_to_status_id: activity.id
539 })
540 end)
541 end
542
543 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
544 user = insert(:user)
545 other_user = insert(:user)
546 third_user = insert(:user)
547
548 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
549
550 {:ok, open_answer} =
551 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
552
553 # The OP is implicitly added
554 assert user.ap_id in open_answer.recipients
555
556 {:ok, secret_answer} =
557 CommonAPI.post(other_user, %{
558 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
559 in_reply_to_status_id: post.id,
560 visibility: "direct"
561 })
562
563 assert third_user.ap_id in secret_answer.recipients
564
565 # The OP is not added
566 refute user.ap_id in secret_answer.recipients
567 end
568
569 test "it allows to address a list" do
570 user = insert(:user)
571 {:ok, list} = Pleroma.List.create("foo", user)
572
573 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
574
575 assert activity.data["bcc"] == [list.ap_id]
576 assert activity.recipients == [list.ap_id, user.ap_id]
577 assert activity.data["listMessage"] == list.ap_id
578 end
579
580 test "it returns error when status is empty and no attachments" do
581 user = insert(:user)
582
583 assert {:error, "Cannot post an empty status without attachments"} =
584 CommonAPI.post(user, %{status: ""})
585 end
586
587 test "it validates character limits are correctly enforced" do
588 Pleroma.Config.put([:instance, :limit], 5)
589
590 user = insert(:user)
591
592 assert {:error, "The status is over the character limit"} =
593 CommonAPI.post(user, %{status: "foobar"})
594
595 assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"})
596 end
597
598 test "it can handle activities that expire" do
599 user = insert(:user)
600
601 expires_at =
602 NaiveDateTime.utc_now()
603 |> NaiveDateTime.truncate(:second)
604 |> NaiveDateTime.add(1_000_000, :second)
605
606 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
607
608 assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
609 assert expiration.scheduled_at == expires_at
610 end
611 end
612
613 describe "reactions" do
614 test "reacting to a status with an emoji" do
615 user = insert(:user)
616 other_user = insert(:user)
617
618 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
619
620 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
621
622 assert reaction.data["actor"] == user.ap_id
623 assert reaction.data["content"] == "👍"
624
625 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
626
627 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
628 end
629
630 test "unreacting to a status with an emoji" do
631 user = insert(:user)
632 other_user = insert(:user)
633
634 clear_config([:instance, :federating], true)
635
636 with_mock Pleroma.Web.Federator,
637 publish: fn _ -> nil end do
638 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
639 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
640
641 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
642
643 assert unreaction.data["type"] == "Undo"
644 assert unreaction.data["object"] == reaction.data["id"]
645 assert unreaction.local
646
647 # On federation, it contains the undone (and deleted) object
648 unreaction_with_object = %{
649 unreaction
650 | data: Map.put(unreaction.data, "object", reaction.data)
651 }
652
653 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
654 end
655 end
656
657 test "repeating a status" do
658 user = insert(:user)
659 other_user = insert(:user)
660
661 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
662
663 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
664 assert Visibility.is_public?(announce_activity)
665 end
666
667 test "can't repeat a repeat" do
668 user = insert(:user)
669 other_user = insert(:user)
670 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
671
672 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
673
674 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
675 end
676
677 test "repeating a status privately" do
678 user = insert(:user)
679 other_user = insert(:user)
680
681 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
682
683 {:ok, %Activity{} = announce_activity} =
684 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
685
686 assert Visibility.is_private?(announce_activity)
687 refute Visibility.visible_for_user?(announce_activity, nil)
688 end
689
690 test "favoriting a status" do
691 user = insert(:user)
692 other_user = insert(:user)
693
694 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
695
696 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
697 assert data["type"] == "Like"
698 assert data["actor"] == user.ap_id
699 assert data["object"] == post_activity.data["object"]
700 end
701
702 test "retweeting a status twice returns the status" do
703 user = insert(:user)
704 other_user = insert(:user)
705
706 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
707 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
708 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
709 end
710
711 test "favoriting a status twice returns ok, but without the like activity" do
712 user = insert(:user)
713 other_user = insert(:user)
714
715 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
716 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
717 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
718 end
719 end
720
721 describe "pinned statuses" do
722 setup do
723 Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
724
725 user = insert(:user)
726 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
727
728 [user: user, activity: activity]
729 end
730
731 test "pin status", %{user: user, activity: activity} do
732 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
733
734 id = activity.id
735 user = refresh_record(user)
736
737 assert %User{pinned_activities: [^id]} = user
738 end
739
740 test "pin poll", %{user: user} do
741 {:ok, activity} =
742 CommonAPI.post(user, %{
743 status: "How is fediverse today?",
744 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
745 })
746
747 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
748
749 id = activity.id
750 user = refresh_record(user)
751
752 assert %User{pinned_activities: [^id]} = user
753 end
754
755 test "unlisted statuses can be pinned", %{user: user} do
756 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
757 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
758 end
759
760 test "only self-authored can be pinned", %{activity: activity} do
761 user = insert(:user)
762
763 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
764 end
765
766 test "max pinned statuses", %{user: user, activity: activity_one} do
767 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
768
769 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
770
771 user = refresh_record(user)
772
773 assert {:error, "You have already pinned the maximum number of statuses"} =
774 CommonAPI.pin(activity_two.id, user)
775 end
776
777 test "unpin status", %{user: user, activity: activity} do
778 {:ok, activity} = CommonAPI.pin(activity.id, user)
779
780 user = refresh_record(user)
781
782 id = activity.id
783
784 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
785
786 user = refresh_record(user)
787
788 assert %User{pinned_activities: []} = user
789 end
790
791 test "should unpin when deleting a status", %{user: user, activity: activity} do
792 {:ok, activity} = CommonAPI.pin(activity.id, user)
793
794 user = refresh_record(user)
795
796 assert {:ok, _} = CommonAPI.delete(activity.id, user)
797
798 user = refresh_record(user)
799
800 assert %User{pinned_activities: []} = user
801 end
802 end
803
804 describe "mute tests" do
805 setup do
806 user = insert(:user)
807
808 activity = insert(:note_activity)
809
810 [user: user, activity: activity]
811 end
812
813 test "marks notifications as read after mute" do
814 author = insert(:user)
815 activity = insert(:note_activity, user: author)
816
817 friend1 = insert(:user)
818 friend2 = insert(:user)
819
820 {:ok, reply_activity} =
821 CommonAPI.post(
822 friend2,
823 %{
824 status: "@#{author.nickname} @#{friend1.nickname} test reply",
825 in_reply_to_status_id: activity.id
826 }
827 )
828
829 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
830 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
831
832 assert Repo.aggregate(
833 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
834 :count
835 ) == 1
836
837 unread_notifications =
838 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
839
840 assert Enum.any?(unread_notifications, fn n ->
841 n.type == "favourite" && n.activity_id == favorite_activity.id
842 end)
843
844 assert Enum.any?(unread_notifications, fn n ->
845 n.type == "reblog" && n.activity_id == repeat_activity.id
846 end)
847
848 assert Enum.any?(unread_notifications, fn n ->
849 n.type == "mention" && n.activity_id == reply_activity.id
850 end)
851
852 {:ok, _} = CommonAPI.add_mute(author, activity)
853 assert CommonAPI.thread_muted?(author, activity)
854
855 assert Repo.aggregate(
856 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
857 :count
858 ) == 1
859
860 read_notifications =
861 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
862
863 assert Enum.any?(read_notifications, fn n ->
864 n.type == "favourite" && n.activity_id == favorite_activity.id
865 end)
866
867 assert Enum.any?(read_notifications, fn n ->
868 n.type == "reblog" && n.activity_id == repeat_activity.id
869 end)
870
871 assert Enum.any?(read_notifications, fn n ->
872 n.type == "mention" && n.activity_id == reply_activity.id
873 end)
874 end
875
876 test "add mute", %{user: user, activity: activity} do
877 {:ok, _} = CommonAPI.add_mute(user, activity)
878 assert CommonAPI.thread_muted?(user, activity)
879 end
880
881 test "remove mute", %{user: user, activity: activity} do
882 CommonAPI.add_mute(user, activity)
883 {:ok, _} = CommonAPI.remove_mute(user, activity)
884 refute CommonAPI.thread_muted?(user, activity)
885 end
886
887 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
888 CommonAPI.add_mute(user, activity)
889 {:error, _} = CommonAPI.add_mute(user, activity)
890 end
891 end
892
893 describe "reports" do
894 test "creates a report" do
895 reporter = insert(:user)
896 target_user = insert(:user)
897
898 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
899
900 reporter_ap_id = reporter.ap_id
901 target_ap_id = target_user.ap_id
902 activity_ap_id = activity.data["id"]
903 comment = "foobar"
904
905 report_data = %{
906 account_id: target_user.id,
907 comment: comment,
908 status_ids: [activity.id]
909 }
910
911 note_obj = %{
912 "type" => "Note",
913 "id" => activity_ap_id,
914 "content" => "foobar",
915 "published" => activity.object.data["published"],
916 "actor" => AccountView.render("show.json", %{user: target_user})
917 }
918
919 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
920
921 assert %Activity{
922 actor: ^reporter_ap_id,
923 data: %{
924 "type" => "Flag",
925 "content" => ^comment,
926 "object" => [^target_ap_id, ^note_obj],
927 "state" => "open"
928 }
929 } = flag_activity
930 end
931
932 test "updates report state" do
933 [reporter, target_user] = insert_pair(:user)
934 activity = insert(:note_activity, user: target_user)
935
936 {:ok, %Activity{id: report_id}} =
937 CommonAPI.report(reporter, %{
938 account_id: target_user.id,
939 comment: "I feel offended",
940 status_ids: [activity.id]
941 })
942
943 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
944
945 assert report.data["state"] == "resolved"
946
947 [reported_user, activity_id] = report.data["object"]
948
949 assert reported_user == target_user.ap_id
950 assert activity_id == activity.data["id"]
951 end
952
953 test "does not update report state when state is unsupported" do
954 [reporter, target_user] = insert_pair(:user)
955 activity = insert(:note_activity, user: target_user)
956
957 {:ok, %Activity{id: report_id}} =
958 CommonAPI.report(reporter, %{
959 account_id: target_user.id,
960 comment: "I feel offended",
961 status_ids: [activity.id]
962 })
963
964 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
965 end
966
967 test "updates state of multiple reports" do
968 [reporter, target_user] = insert_pair(:user)
969 activity = insert(:note_activity, user: target_user)
970
971 {:ok, %Activity{id: first_report_id}} =
972 CommonAPI.report(reporter, %{
973 account_id: target_user.id,
974 comment: "I feel offended",
975 status_ids: [activity.id]
976 })
977
978 {:ok, %Activity{id: second_report_id}} =
979 CommonAPI.report(reporter, %{
980 account_id: target_user.id,
981 comment: "I feel very offended!",
982 status_ids: [activity.id]
983 })
984
985 {:ok, report_ids} =
986 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
987
988 first_report = Activity.get_by_id(first_report_id)
989 second_report = Activity.get_by_id(second_report_id)
990
991 assert report_ids -- [first_report_id, second_report_id] == []
992 assert first_report.data["state"] == "resolved"
993 assert second_report.data["state"] == "resolved"
994 end
995 end
996
997 describe "reblog muting" do
998 setup do
999 muter = insert(:user)
1000
1001 muted = insert(:user)
1002
1003 [muter: muter, muted: muted]
1004 end
1005
1006 test "add a reblog mute", %{muter: muter, muted: muted} do
1007 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1008
1009 assert User.showing_reblogs?(muter, muted) == false
1010 end
1011
1012 test "remove a reblog mute", %{muter: muter, muted: muted} do
1013 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1014 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1015
1016 assert User.showing_reblogs?(muter, muted) == true
1017 end
1018 end
1019
1020 describe "follow/2" do
1021 test "directly follows a non-locked local user" do
1022 [follower, followed] = insert_pair(:user)
1023 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1024
1025 assert User.following?(follower, followed)
1026 end
1027 end
1028
1029 describe "unfollow/2" do
1030 test "also unsubscribes a user" do
1031 [follower, followed] = insert_pair(:user)
1032 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1033 {:ok, _subscription} = User.subscribe(follower, followed)
1034
1035 assert User.subscribed_to?(follower, followed)
1036
1037 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1038
1039 refute User.subscribed_to?(follower, followed)
1040 end
1041
1042 test "cancels a pending follow for a local user" do
1043 follower = insert(:user)
1044 followed = insert(:user, locked: true)
1045
1046 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1047 CommonAPI.follow(follower, followed)
1048
1049 assert User.get_follow_state(follower, followed) == :follow_pending
1050 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1051 assert User.get_follow_state(follower, followed) == nil
1052
1053 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1054 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1055
1056 assert %{
1057 data: %{
1058 "type" => "Undo",
1059 "object" => %{"type" => "Follow", "state" => "cancelled"}
1060 }
1061 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1062 end
1063
1064 test "cancels a pending follow for a remote user" do
1065 follower = insert(:user)
1066 followed = insert(:user, locked: true, local: false, ap_enabled: true)
1067
1068 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1069 CommonAPI.follow(follower, followed)
1070
1071 assert User.get_follow_state(follower, followed) == :follow_pending
1072 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1073 assert User.get_follow_state(follower, followed) == nil
1074
1075 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1076 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1077
1078 assert %{
1079 data: %{
1080 "type" => "Undo",
1081 "object" => %{"type" => "Follow", "state" => "cancelled"}
1082 }
1083 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1084 end
1085 end
1086
1087 describe "accept_follow_request/2" do
1088 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1089 user = insert(:user, locked: true)
1090 follower = insert(:user)
1091 follower_two = insert(:user)
1092
1093 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1094 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1095 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1096
1097 assert follow_activity.data["state"] == "pending"
1098 assert follow_activity_two.data["state"] == "pending"
1099 assert follow_activity_three.data["state"] == "pending"
1100
1101 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1102
1103 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1104 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1105 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1106 end
1107
1108 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1109 user = insert(:user, locked: true)
1110 follower = insert(:user)
1111 follower_two = insert(:user)
1112
1113 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1114 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1115 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1116
1117 assert follow_activity.data["state"] == "pending"
1118 assert follow_activity_two.data["state"] == "pending"
1119 assert follow_activity_three.data["state"] == "pending"
1120
1121 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1122
1123 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1124 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1125 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1126 end
1127
1128 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1129 user = insert(:user, locked: true)
1130 not_follower = insert(:user)
1131 CommonAPI.accept_follow_request(not_follower, user)
1132
1133 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1134 end
1135 end
1136
1137 describe "vote/3" do
1138 test "does not allow to vote twice" do
1139 user = insert(:user)
1140 other_user = insert(:user)
1141
1142 {:ok, activity} =
1143 CommonAPI.post(user, %{
1144 status: "Am I cute?",
1145 poll: %{options: ["Yes", "No"], expires_in: 20}
1146 })
1147
1148 object = Object.normalize(activity)
1149
1150 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1151
1152 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1153 end
1154 end
1155
1156 describe "listen/2" do
1157 test "returns a valid activity" do
1158 user = insert(:user)
1159
1160 {:ok, activity} =
1161 CommonAPI.listen(user, %{
1162 title: "lain radio episode 1",
1163 album: "lain radio",
1164 artist: "lain",
1165 length: 180_000
1166 })
1167
1168 object = Object.normalize(activity)
1169
1170 assert object.data["title"] == "lain radio episode 1"
1171
1172 assert Visibility.get_visibility(activity) == "public"
1173 end
1174
1175 test "respects visibility=private" do
1176 user = insert(:user)
1177
1178 {:ok, activity} =
1179 CommonAPI.listen(user, %{
1180 title: "lain radio episode 1",
1181 album: "lain radio",
1182 artist: "lain",
1183 length: 180_000,
1184 visibility: "private"
1185 })
1186
1187 object = Object.normalize(activity)
1188
1189 assert object.data["title"] == "lain radio episode 1"
1190
1191 assert Visibility.get_visibility(activity) == "private"
1192 end
1193 end
1194 end