move to 2.5.5
[anni] / test / pleroma / web / common_api_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.CommonAPITest do
6   use Oban.Testing, repo: Pleroma.Repo
7   use Pleroma.DataCase, async: false
8
9   alias Pleroma.Activity
10   alias Pleroma.Chat
11   alias Pleroma.Conversation.Participation
12   alias Pleroma.Notification
13   alias Pleroma.Object
14   alias Pleroma.Repo
15   alias Pleroma.User
16   alias Pleroma.Web.ActivityPub.ActivityPub
17   alias Pleroma.Web.ActivityPub.Transmogrifier
18   alias Pleroma.Web.ActivityPub.Visibility
19   alias Pleroma.Web.AdminAPI.AccountView
20   alias Pleroma.Web.CommonAPI
21   alias Pleroma.Workers.PollWorker
22
23   import Pleroma.Factory
24   import Mock
25   import Ecto.Query, only: [from: 2]
26
27   require Pleroma.Constants
28
29   setup_all do
30     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
31     :ok
32   end
33
34   setup do: clear_config([:instance, :safe_dm_mentions])
35   setup do: clear_config([:instance, :limit])
36   setup do: clear_config([:instance, :max_pinned_statuses])
37
38   describe "posting polls" do
39     test "it posts a poll" do
40       user = insert(:user)
41
42       {:ok, activity} =
43         CommonAPI.post(user, %{
44           status: "who is the best",
45           poll: %{expires_in: 600, options: ["reimu", "marisa"]}
46         })
47
48       object = Object.normalize(activity, fetch: false)
49
50       assert object.data["type"] == "Question"
51       assert object.data["oneOf"] |> length() == 2
52
53       assert_enqueued(
54         worker: PollWorker,
55         args: %{op: "poll_end", activity_id: activity.id},
56         scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
57       )
58     end
59   end
60
61   describe "blocking" do
62     setup do
63       blocker = insert(:user)
64       blocked = insert(:user, local: false)
65       CommonAPI.follow(blocker, blocked)
66       CommonAPI.follow(blocked, blocker)
67       CommonAPI.accept_follow_request(blocker, blocked)
68       CommonAPI.accept_follow_request(blocked, blocked)
69       %{blocker: blocker, blocked: blocked}
70     end
71
72     test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
73       clear_config([:instance, :federating], true)
74
75       with_mock Pleroma.Web.Federator,
76         publish: fn _ -> nil end do
77         assert User.get_follow_state(blocker, blocked) == :follow_accept
78         refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
79
80         assert {:ok, block} = CommonAPI.block(blocker, blocked)
81
82         assert block.local
83         assert User.blocks?(blocker, blocked)
84         refute User.following?(blocker, blocked)
85         refute User.following?(blocked, blocker)
86
87         refute User.get_follow_state(blocker, blocked)
88
89         assert %{data: %{"state" => "reject"}} =
90                  Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
91
92         assert called(Pleroma.Web.Federator.publish(block))
93       end
94     end
95
96     test "it blocks and does not federate if outgoing blocks are disabled", %{
97       blocker: blocker,
98       blocked: blocked
99     } do
100       clear_config([:instance, :federating], true)
101       clear_config([:activitypub, :outgoing_blocks], false)
102
103       with_mock Pleroma.Web.Federator,
104         publish: fn _ -> nil end do
105         assert {:ok, block} = CommonAPI.block(blocker, blocked)
106
107         assert block.local
108         assert User.blocks?(blocker, blocked)
109         refute User.following?(blocker, blocked)
110         refute User.following?(blocked, blocker)
111
112         refute called(Pleroma.Web.Federator.publish(block))
113       end
114     end
115   end
116
117   describe "posting chat messages" do
118     setup do: clear_config([:instance, :chat_limit])
119
120     test "it posts a self-chat" do
121       author = insert(:user)
122       recipient = author
123
124       {:ok, activity} =
125         CommonAPI.post_chat_message(
126           author,
127           recipient,
128           "remember to buy milk when milk truk arive"
129         )
130
131       assert activity.data["type"] == "Create"
132     end
133
134     test "it posts a chat message without content but with an attachment" do
135       author = insert(:user)
136       recipient = insert(:user)
137
138       file = %Plug.Upload{
139         content_type: "image/jpeg",
140         path: Path.absname("test/fixtures/image.jpg"),
141         filename: "an_image.jpg"
142       }
143
144       {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
145
146       with_mocks([
147         {
148           Pleroma.Web.Streamer,
149           [],
150           [
151             stream: fn _, _ ->
152               nil
153             end
154           ]
155         },
156         {
157           Pleroma.Web.Push,
158           [],
159           [
160             send: fn _ -> nil end
161           ]
162         }
163       ]) do
164         {:ok, activity} =
165           CommonAPI.post_chat_message(
166             author,
167             recipient,
168             nil,
169             media_id: upload.id
170           )
171
172         notification =
173           Notification.for_user_and_activity(recipient, activity)
174           |> Repo.preload(:activity)
175
176         assert called(Pleroma.Web.Push.send(notification))
177         assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
178         assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
179
180         assert activity
181       end
182     end
183
184     test "it adds html newlines" do
185       author = insert(:user)
186       recipient = insert(:user)
187
188       other_user = insert(:user)
189
190       {:ok, activity} =
191         CommonAPI.post_chat_message(
192           author,
193           recipient,
194           "uguu\nuguuu"
195         )
196
197       assert other_user.ap_id not in activity.recipients
198
199       object = Object.normalize(activity, fetch: false)
200
201       assert object.data["content"] == "uguu<br/>uguuu"
202     end
203
204     test "it linkifies" do
205       author = insert(:user)
206       recipient = insert(:user)
207
208       other_user = insert(:user)
209
210       {:ok, activity} =
211         CommonAPI.post_chat_message(
212           author,
213           recipient,
214           "https://example.org is the site of @#{other_user.nickname} #2hu"
215         )
216
217       assert other_user.ap_id not in activity.recipients
218
219       object = Object.normalize(activity, fetch: false)
220
221       assert object.data["content"] ==
222                "<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=\"#{other_user.id}\" 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>"
223     end
224
225     test "it posts a chat message" do
226       author = insert(:user)
227       recipient = insert(:user)
228
229       {:ok, activity} =
230         CommonAPI.post_chat_message(
231           author,
232           recipient,
233           "a test message <script>alert('uuu')</script> :firefox:"
234         )
235
236       assert activity.data["type"] == "Create"
237       assert activity.local
238       object = Object.normalize(activity, fetch: false)
239
240       assert object.data["type"] == "ChatMessage"
241       assert object.data["to"] == [recipient.ap_id]
242
243       assert object.data["content"] ==
244                "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
245
246       assert object.data["emoji"] == %{
247                "firefox" => "http://localhost:4001/emoji/Firefox.gif"
248              }
249
250       assert Chat.get(author.id, recipient.ap_id)
251       assert Chat.get(recipient.id, author.ap_id)
252
253       assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
254     end
255
256     test "it reject messages over the local limit" do
257       clear_config([:instance, :chat_limit], 2)
258
259       author = insert(:user)
260       recipient = insert(:user)
261
262       {:error, message} =
263         CommonAPI.post_chat_message(
264           author,
265           recipient,
266           "123"
267         )
268
269       assert message == :content_too_long
270     end
271
272     test "it reject messages via MRF" do
273       clear_config([:mrf_keyword, :reject], ["GNO"])
274       clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
275
276       author = insert(:user)
277       recipient = insert(:user)
278
279       assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
280                CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
281     end
282
283     test "it reject messages with attachments not belonging to user" do
284       author = insert(:user)
285       not_author = insert(:user)
286       recipient = author
287
288       attachment = insert(:attachment, %{user: not_author})
289
290       {:error, message} =
291         CommonAPI.post_chat_message(
292           author,
293           recipient,
294           "123",
295           media_id: attachment.id
296         )
297
298       assert message == :forbidden
299     end
300   end
301
302   describe "unblocking" do
303     test "it works even without an existing block activity" do
304       blocked = insert(:user)
305       blocker = insert(:user)
306       User.block(blocker, blocked)
307
308       assert User.blocks?(blocker, blocked)
309       assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
310       refute User.blocks?(blocker, blocked)
311     end
312   end
313
314   describe "deletion" do
315     test "it works with pruned objects" do
316       user = insert(:user)
317
318       {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
319
320       clear_config([:instance, :federating], true)
321
322       Object.normalize(post, fetch: false)
323       |> Object.prune()
324
325       with_mock Pleroma.Web.Federator,
326         publish: fn _ -> nil end do
327         assert {:ok, delete} = CommonAPI.delete(post.id, user)
328         assert delete.local
329         assert called(Pleroma.Web.Federator.publish(delete))
330       end
331
332       refute Activity.get_by_id(post.id)
333     end
334
335     test "it allows users to delete their posts" do
336       user = insert(:user)
337
338       {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
339
340       clear_config([:instance, :federating], true)
341
342       with_mock Pleroma.Web.Federator,
343         publish: fn _ -> nil end do
344         assert {:ok, delete} = CommonAPI.delete(post.id, user)
345         assert delete.local
346         assert called(Pleroma.Web.Federator.publish(delete))
347       end
348
349       refute Activity.get_by_id(post.id)
350     end
351
352     test "it does not allow a user to delete posts from another user" do
353       user = insert(:user)
354       other_user = insert(:user)
355
356       {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
357
358       assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
359       assert Activity.get_by_id(post.id)
360     end
361
362     test "it allows privileged users to delete other user's posts" do
363       clear_config([:instance, :moderator_privileges], [:messages_delete])
364       user = insert(:user)
365       moderator = insert(:user, is_moderator: true)
366
367       {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
368
369       assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
370       assert delete.local
371
372       refute Activity.get_by_id(post.id)
373     end
374
375     test "it doesn't allow unprivileged mods or admins to delete other user's posts" do
376       clear_config([:instance, :admin_privileges], [])
377       clear_config([:instance, :moderator_privileges], [])
378       user = insert(:user)
379       moderator = insert(:user, is_moderator: true, is_admin: true)
380
381       {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
382
383       assert {:error, "Could not delete"} = CommonAPI.delete(post.id, moderator)
384       assert Activity.get_by_id(post.id)
385     end
386
387     test "privileged users deleting non-local posts won't federate the delete" do
388       clear_config([:instance, :admin_privileges], [:messages_delete])
389       # This is the user of the ingested activity
390       _user =
391         insert(:user,
392           local: false,
393           ap_id: "http://mastodon.example.org/users/admin",
394           last_refreshed_at: NaiveDateTime.utc_now()
395         )
396
397       admin = insert(:user, is_admin: true)
398
399       data =
400         File.read!("test/fixtures/mastodon-post-activity.json")
401         |> Jason.decode!()
402
403       {:ok, post} = Transmogrifier.handle_incoming(data)
404
405       with_mock Pleroma.Web.Federator,
406         publish: fn _ -> nil end do
407         assert {:ok, delete} = CommonAPI.delete(post.id, admin)
408         assert delete.local
409         refute called(Pleroma.Web.Federator.publish(:_))
410       end
411
412       refute Activity.get_by_id(post.id)
413     end
414   end
415
416   test "favoriting race condition" do
417     user = insert(:user)
418     users_serial = insert_list(10, :user)
419     users = insert_list(10, :user)
420
421     {:ok, activity} = CommonAPI.post(user, %{status: "."})
422
423     users_serial
424     |> Enum.map(fn user ->
425       CommonAPI.favorite(user, activity.id)
426     end)
427
428     object = Object.get_by_ap_id(activity.data["object"])
429     assert object.data["like_count"] == 10
430
431     users
432     |> Enum.map(fn user ->
433       Task.async(fn ->
434         CommonAPI.favorite(user, activity.id)
435       end)
436     end)
437     |> Enum.map(&Task.await/1)
438
439     object = Object.get_by_ap_id(activity.data["object"])
440     assert object.data["like_count"] == 20
441   end
442
443   test "repeating race condition" do
444     user = insert(:user)
445     users_serial = insert_list(10, :user)
446     users = insert_list(10, :user)
447
448     {:ok, activity} = CommonAPI.post(user, %{status: "."})
449
450     users_serial
451     |> Enum.map(fn user ->
452       CommonAPI.repeat(activity.id, user)
453     end)
454
455     object = Object.get_by_ap_id(activity.data["object"])
456     assert object.data["announcement_count"] == 10
457
458     users
459     |> Enum.map(fn user ->
460       Task.async(fn ->
461         CommonAPI.repeat(activity.id, user)
462       end)
463     end)
464     |> Enum.map(&Task.await/1)
465
466     object = Object.get_by_ap_id(activity.data["object"])
467     assert object.data["announcement_count"] == 20
468   end
469
470   test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
471     user = insert(:user)
472     {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
473
474     [participation] = Participation.for_user(user)
475
476     {:ok, convo_reply} =
477       CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
478
479     assert Visibility.is_direct?(convo_reply)
480
481     assert activity.data["context"] == convo_reply.data["context"]
482   end
483
484   test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
485     har = insert(:user)
486     jafnhar = insert(:user)
487     tridi = insert(:user)
488
489     {:ok, activity} =
490       CommonAPI.post(har, %{
491         status: "@#{jafnhar.nickname} hey",
492         visibility: "direct"
493       })
494
495     assert har.ap_id in activity.recipients
496     assert jafnhar.ap_id in activity.recipients
497
498     [participation] = Participation.for_user(har)
499
500     {:ok, activity} =
501       CommonAPI.post(har, %{
502         status: "I don't really like @#{tridi.nickname}",
503         visibility: "direct",
504         in_reply_to_status_id: activity.id,
505         in_reply_to_conversation_id: participation.id
506       })
507
508     assert har.ap_id in activity.recipients
509     assert jafnhar.ap_id in activity.recipients
510     refute tridi.ap_id in activity.recipients
511   end
512
513   test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
514     har = insert(:user)
515     jafnhar = insert(:user)
516     tridi = insert(:user)
517
518     clear_config([:instance, :safe_dm_mentions], true)
519
520     {:ok, activity} =
521       CommonAPI.post(har, %{
522         status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
523         visibility: "direct"
524       })
525
526     refute tridi.ap_id in activity.recipients
527     assert jafnhar.ap_id in activity.recipients
528   end
529
530   test "it de-duplicates tags" do
531     user = insert(:user)
532     {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
533
534     object = Object.normalize(activity, fetch: false)
535
536     assert Object.tags(object) == ["2hu"]
537   end
538
539   test "zwnj is treated as word character" do
540     user = insert(:user)
541     {:ok, activity} = CommonAPI.post(user, %{status: "#ساٴين‌س"})
542
543     object = Object.normalize(activity, fetch: false)
544
545     assert Object.tags(object) == ["ساٴين‌س"]
546   end
547
548   test "double dot in link is allowed" do
549     user = insert(:user)
550     text = "https://example.to/something..mp3"
551     {:ok, activity} = CommonAPI.post(user, %{status: text})
552
553     object = Object.normalize(activity, fetch: false)
554
555     assert object.data["content"] == "<a href=\"#{text}\" rel=\"ugc\">#{text}</a>"
556   end
557
558   test "it adds emoji in the object" do
559     user = insert(:user)
560     {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
561
562     assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
563   end
564
565   describe "posting" do
566     test "it adds an emoji on an external site" do
567       user = insert(:user)
568       {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
569
570       assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
571       assert url == "https://example.com/emoji.png"
572
573       {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
574
575       assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
576       assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
577     end
578
579     test "it copies emoji from the subject of the parent post" do
580       %Object{} =
581         object =
582         Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
583           fetch: true
584         )
585
586       activity = Activity.get_create_by_object_ap_id(object.data["id"])
587       user = insert(:user)
588
589       {:ok, reply_activity} =
590         CommonAPI.post(user, %{
591           in_reply_to_id: activity.id,
592           status: ":joker_disapprove:",
593           spoiler_text: ":joker_smile:"
594         })
595
596       assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
597       refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
598     end
599
600     test "deactivated users can't post" do
601       user = insert(:user, is_active: false)
602       assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
603     end
604
605     test "it supports explicit addressing" do
606       user = insert(:user)
607       user_two = insert(:user)
608       user_three = insert(:user)
609       user_four = insert(:user)
610
611       {:ok, activity} =
612         CommonAPI.post(user, %{
613           status:
614             "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
615           to: [user_two.nickname, user_four.nickname, "nonexistent"]
616         })
617
618       assert user.ap_id in activity.recipients
619       assert user_two.ap_id in activity.recipients
620       assert user_four.ap_id in activity.recipients
621       refute user_three.ap_id in activity.recipients
622     end
623
624     test "it filters out obviously bad tags when accepting a post as HTML" do
625       user = insert(:user)
626
627       post = "<p><b>2hu</b></p><script>alert('xss')</script>"
628
629       {:ok, activity} =
630         CommonAPI.post(user, %{
631           status: post,
632           content_type: "text/html"
633         })
634
635       object = Object.normalize(activity, fetch: false)
636
637       assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
638       assert object.data["source"]["content"] == post
639     end
640
641     test "it filters out obviously bad tags when accepting a post as Markdown" do
642       user = insert(:user)
643
644       post = "<p><b>2hu</b></p><script>alert('xss')</script>"
645
646       {:ok, activity} =
647         CommonAPI.post(user, %{
648           status: post,
649           content_type: "text/markdown"
650         })
651
652       object = Object.normalize(activity, fetch: false)
653
654       assert object.data["content"] == "<p><b>2hu</b></p>"
655       assert object.data["source"]["content"] == post
656     end
657
658     test "it does not allow replies to direct messages that are not direct messages themselves" do
659       user = insert(:user)
660
661       {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
662
663       assert {:ok, _} =
664                CommonAPI.post(user, %{
665                  status: "suya..",
666                  visibility: "direct",
667                  in_reply_to_status_id: activity.id
668                })
669
670       Enum.each(["public", "private", "unlisted"], fn visibility ->
671         assert {:error, "The message visibility must be direct"} =
672                  CommonAPI.post(user, %{
673                    status: "suya..",
674                    visibility: visibility,
675                    in_reply_to_status_id: activity.id
676                  })
677       end)
678     end
679
680     test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
681       user = insert(:user)
682       other_user = insert(:user)
683       third_user = insert(:user)
684
685       {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
686
687       {:ok, open_answer} =
688         CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
689
690       # The OP is implicitly added
691       assert user.ap_id in open_answer.recipients
692
693       {:ok, secret_answer} =
694         CommonAPI.post(other_user, %{
695           status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
696           in_reply_to_status_id: post.id,
697           visibility: "direct"
698         })
699
700       assert third_user.ap_id in secret_answer.recipients
701
702       # The OP is not added
703       refute user.ap_id in secret_answer.recipients
704     end
705
706     test "it allows to address a list" do
707       user = insert(:user)
708       {:ok, list} = Pleroma.List.create("foo", user)
709
710       {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
711
712       assert activity.data["bcc"] == [list.ap_id]
713       assert activity.recipients == [list.ap_id, user.ap_id]
714       assert activity.data["listMessage"] == list.ap_id
715     end
716
717     test "it returns error when status is empty and no attachments" do
718       user = insert(:user)
719
720       assert {:error, "Cannot post an empty status without attachments"} =
721                CommonAPI.post(user, %{status: ""})
722     end
723
724     test "it validates character limits are correctly enforced" do
725       clear_config([:instance, :limit], 5)
726
727       user = insert(:user)
728
729       assert {:error, "The status is over the character limit"} =
730                CommonAPI.post(user, %{status: "foobar"})
731
732       assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
733     end
734
735     test "it validates media attachment limits are correctly enforced" do
736       clear_config([:instance, :max_media_attachments], 4)
737
738       user = insert(:user)
739
740       file = %Plug.Upload{
741         content_type: "image/jpeg",
742         path: Path.absname("test/fixtures/image.jpg"),
743         filename: "an_image.jpg"
744       }
745
746       {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
747
748       assert {:error, "Too many attachments"} =
749                CommonAPI.post(user, %{
750                  status: "",
751                  media_ids: List.duplicate(upload.id, 5)
752                })
753
754       assert {:ok, _activity} =
755                CommonAPI.post(user, %{
756                  status: "",
757                  media_ids: [upload.id]
758                })
759     end
760
761     test "it can handle activities that expire" do
762       user = insert(:user)
763
764       expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
765
766       assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
767
768       assert_enqueued(
769         worker: Pleroma.Workers.PurgeExpiredActivity,
770         args: %{activity_id: activity.id},
771         scheduled_at: expires_at
772       )
773     end
774   end
775
776   describe "reactions" do
777     test "reacting to a status with an emoji" do
778       user = insert(:user)
779       other_user = insert(:user)
780
781       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
782
783       {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
784
785       assert reaction.data["actor"] == user.ap_id
786       assert reaction.data["content"] == "👍"
787
788       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
789
790       {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
791     end
792
793     test "unreacting to a status with an emoji" do
794       user = insert(:user)
795       other_user = insert(:user)
796
797       clear_config([:instance, :federating], true)
798
799       with_mock Pleroma.Web.Federator,
800         publish: fn _ -> nil end do
801         {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
802         {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
803
804         {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
805
806         assert unreaction.data["type"] == "Undo"
807         assert unreaction.data["object"] == reaction.data["id"]
808         assert unreaction.local
809
810         # On federation, it contains the undone (and deleted) object
811         unreaction_with_object = %{
812           unreaction
813           | data: Map.put(unreaction.data, "object", reaction.data)
814         }
815
816         assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
817       end
818     end
819
820     test "repeating a status" do
821       user = insert(:user)
822       other_user = insert(:user)
823
824       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
825
826       {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
827       assert Visibility.is_public?(announce_activity)
828     end
829
830     test "can't repeat a repeat" do
831       user = insert(:user)
832       other_user = insert(:user)
833       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
834
835       {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
836
837       refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
838     end
839
840     test "repeating a status privately" do
841       user = insert(:user)
842       other_user = insert(:user)
843
844       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
845
846       {:ok, %Activity{} = announce_activity} =
847         CommonAPI.repeat(activity.id, user, %{visibility: "private"})
848
849       assert Visibility.is_private?(announce_activity)
850       refute Visibility.visible_for_user?(announce_activity, nil)
851     end
852
853     test "author can repeat own private statuses" do
854       author = insert(:user)
855       follower = insert(:user)
856       CommonAPI.follow(follower, author)
857
858       {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
859
860       {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
861
862       assert Visibility.is_private?(announce_activity)
863       refute Visibility.visible_for_user?(announce_activity, nil)
864
865       assert Visibility.visible_for_user?(activity, follower)
866       assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
867     end
868
869     test "favoriting a status" do
870       user = insert(:user)
871       other_user = insert(:user)
872
873       {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
874
875       {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
876       assert data["type"] == "Like"
877       assert data["actor"] == user.ap_id
878       assert data["object"] == post_activity.data["object"]
879     end
880
881     test "retweeting a status twice returns the status" do
882       user = insert(:user)
883       other_user = insert(:user)
884
885       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
886       {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
887       {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
888     end
889
890     test "favoriting a status twice returns ok, but without the like activity" do
891       user = insert(:user)
892       other_user = insert(:user)
893
894       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
895       {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
896       assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
897     end
898   end
899
900   describe "pinned statuses" do
901     setup do
902       clear_config([:instance, :max_pinned_statuses], 1)
903
904       user = insert(:user)
905       {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
906
907       [user: user, activity: activity]
908     end
909
910     test "activity not found error", %{user: user} do
911       assert {:error, :not_found} = CommonAPI.pin("id", user)
912     end
913
914     test "pin status", %{user: user, activity: activity} do
915       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
916
917       %{data: %{"id" => object_id}} = Object.normalize(activity)
918       user = refresh_record(user)
919
920       assert user.pinned_objects |> Map.keys() == [object_id]
921     end
922
923     test "pin poll", %{user: user} do
924       {:ok, activity} =
925         CommonAPI.post(user, %{
926           status: "How is fediverse today?",
927           poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
928         })
929
930       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
931
932       %{data: %{"id" => object_id}} = Object.normalize(activity)
933
934       user = refresh_record(user)
935
936       assert user.pinned_objects |> Map.keys() == [object_id]
937     end
938
939     test "unlisted statuses can be pinned", %{user: user} do
940       {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
941       assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
942     end
943
944     test "only self-authored can be pinned", %{activity: activity} do
945       user = insert(:user)
946
947       assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
948     end
949
950     test "max pinned statuses", %{user: user, activity: activity_one} do
951       {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
952
953       assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
954
955       user = refresh_record(user)
956
957       assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
958     end
959
960     test "only public can be pinned", %{user: user} do
961       {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
962       {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
963     end
964
965     test "unpin status", %{user: user, activity: activity} do
966       {:ok, activity} = CommonAPI.pin(activity.id, user)
967
968       user = refresh_record(user)
969
970       id = activity.id
971
972       assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
973
974       user = refresh_record(user)
975
976       assert user.pinned_objects == %{}
977     end
978
979     test "should unpin when deleting a status", %{user: user, activity: activity} do
980       {:ok, activity} = CommonAPI.pin(activity.id, user)
981
982       user = refresh_record(user)
983
984       assert {:ok, _} = CommonAPI.delete(activity.id, user)
985
986       user = refresh_record(user)
987
988       assert user.pinned_objects == %{}
989     end
990
991     test "ephemeral activity won't be deleted if was pinned", %{user: user} do
992       {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
993
994       assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
995
996       {:ok, _activity} = CommonAPI.pin(activity.id, user)
997       refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
998
999       user = refresh_record(user)
1000       {:ok, _} = CommonAPI.unpin(activity.id, user)
1001
1002       # recreates expiration job on unpin
1003       assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
1004     end
1005
1006     test "ephemeral activity deletion job won't be deleted on pinning error", %{
1007       user: user,
1008       activity: activity
1009     } do
1010       clear_config([:instance, :max_pinned_statuses], 1)
1011
1012       {:ok, _activity} = CommonAPI.pin(activity.id, user)
1013
1014       {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
1015
1016       assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
1017
1018       user = refresh_record(user)
1019       {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
1020
1021       assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
1022     end
1023   end
1024
1025   describe "mute tests" do
1026     setup do
1027       user = insert(:user)
1028
1029       activity = insert(:note_activity)
1030
1031       [user: user, activity: activity]
1032     end
1033
1034     test "marks notifications as read after mute" do
1035       author = insert(:user)
1036       activity = insert(:note_activity, user: author)
1037
1038       friend1 = insert(:user)
1039       friend2 = insert(:user)
1040
1041       {:ok, reply_activity} =
1042         CommonAPI.post(
1043           friend2,
1044           %{
1045             status: "@#{author.nickname} @#{friend1.nickname} test reply",
1046             in_reply_to_status_id: activity.id
1047           }
1048         )
1049
1050       {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
1051       {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
1052
1053       assert Repo.aggregate(
1054                from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1055                :count
1056              ) == 1
1057
1058       unread_notifications =
1059         Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
1060
1061       assert Enum.any?(unread_notifications, fn n ->
1062                n.type == "favourite" && n.activity_id == favorite_activity.id
1063              end)
1064
1065       assert Enum.any?(unread_notifications, fn n ->
1066                n.type == "reblog" && n.activity_id == repeat_activity.id
1067              end)
1068
1069       assert Enum.any?(unread_notifications, fn n ->
1070                n.type == "mention" && n.activity_id == reply_activity.id
1071              end)
1072
1073       {:ok, _} = CommonAPI.add_mute(author, activity)
1074       assert CommonAPI.thread_muted?(author, activity)
1075
1076       assert Repo.aggregate(
1077                from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1078                :count
1079              ) == 1
1080
1081       read_notifications =
1082         Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1083
1084       assert Enum.any?(read_notifications, fn n ->
1085                n.type == "favourite" && n.activity_id == favorite_activity.id
1086              end)
1087
1088       assert Enum.any?(read_notifications, fn n ->
1089                n.type == "reblog" && n.activity_id == repeat_activity.id
1090              end)
1091
1092       assert Enum.any?(read_notifications, fn n ->
1093                n.type == "mention" && n.activity_id == reply_activity.id
1094              end)
1095     end
1096
1097     test "add mute", %{user: user, activity: activity} do
1098       {:ok, _} = CommonAPI.add_mute(user, activity)
1099       assert CommonAPI.thread_muted?(user, activity)
1100     end
1101
1102     test "add expiring mute", %{user: user, activity: activity} do
1103       {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1104       assert CommonAPI.thread_muted?(user, activity)
1105
1106       worker = Pleroma.Workers.MuteExpireWorker
1107       args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1108
1109       assert_enqueued(
1110         worker: worker,
1111         args: args
1112       )
1113
1114       assert :ok = perform_job(worker, args)
1115       refute CommonAPI.thread_muted?(user, activity)
1116     end
1117
1118     test "remove mute", %{user: user, activity: activity} do
1119       CommonAPI.add_mute(user, activity)
1120       {:ok, _} = CommonAPI.remove_mute(user, activity)
1121       refute CommonAPI.thread_muted?(user, activity)
1122     end
1123
1124     test "remove mute by ids", %{user: user, activity: activity} do
1125       CommonAPI.add_mute(user, activity)
1126       {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1127       refute CommonAPI.thread_muted?(user, activity)
1128     end
1129
1130     test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1131       CommonAPI.add_mute(user, activity)
1132       {:error, _} = CommonAPI.add_mute(user, activity)
1133     end
1134   end
1135
1136   describe "reports" do
1137     test "creates a report" do
1138       reporter = insert(:user)
1139       target_user = insert(:user)
1140
1141       {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1142       activity = Activity.normalize(activity)
1143
1144       reporter_ap_id = reporter.ap_id
1145       target_ap_id = target_user.ap_id
1146       reported_object_ap_id = activity.object.data["id"]
1147       comment = "foobar"
1148
1149       report_data = %{
1150         account_id: target_user.id,
1151         comment: comment,
1152         status_ids: [activity.id]
1153       }
1154
1155       note_obj = %{
1156         "type" => "Note",
1157         "id" => reported_object_ap_id,
1158         "content" => "foobar",
1159         "published" => activity.object.data["published"],
1160         "actor" => AccountView.render("show.json", %{user: target_user})
1161       }
1162
1163       assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1164
1165       assert %Activity{
1166                actor: ^reporter_ap_id,
1167                data: %{
1168                  "type" => "Flag",
1169                  "content" => ^comment,
1170                  "object" => [^target_ap_id, ^note_obj],
1171                  "state" => "open"
1172                }
1173              } = flag_activity
1174     end
1175
1176     test "updates report state" do
1177       [reporter, target_user] = insert_pair(:user)
1178       activity = insert(:note_activity, user: target_user)
1179       object = Object.normalize(activity)
1180
1181       {:ok, %Activity{id: report_id}} =
1182         CommonAPI.report(reporter, %{
1183           account_id: target_user.id,
1184           comment: "I feel offended",
1185           status_ids: [activity.id]
1186         })
1187
1188       {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1189
1190       assert report.data["state"] == "resolved"
1191
1192       [reported_user, object_id] = report.data["object"]
1193
1194       assert reported_user == target_user.ap_id
1195       assert object_id == object.data["id"]
1196     end
1197
1198     test "updates report state, don't strip when report_strip_status is false" do
1199       clear_config([:instance, :report_strip_status], false)
1200
1201       [reporter, target_user] = insert_pair(:user)
1202       activity = insert(:note_activity, user: target_user)
1203
1204       {:ok, %Activity{id: report_id, data: report_data}} =
1205         CommonAPI.report(reporter, %{
1206           account_id: target_user.id,
1207           comment: "I feel offended",
1208           status_ids: [activity.id]
1209         })
1210
1211       {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1212
1213       assert report.data["state"] == "resolved"
1214
1215       [reported_user, reported_activity] = report.data["object"]
1216
1217       assert reported_user == target_user.ap_id
1218       assert is_map(reported_activity)
1219
1220       assert reported_activity["content"] ==
1221                report_data["object"] |> Enum.at(1) |> Map.get("content")
1222     end
1223
1224     test "does not update report state when state is unsupported" do
1225       [reporter, target_user] = insert_pair(:user)
1226       activity = insert(:note_activity, user: target_user)
1227
1228       {:ok, %Activity{id: report_id}} =
1229         CommonAPI.report(reporter, %{
1230           account_id: target_user.id,
1231           comment: "I feel offended",
1232           status_ids: [activity.id]
1233         })
1234
1235       assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1236     end
1237
1238     test "updates state of multiple reports" do
1239       [reporter, target_user] = insert_pair(:user)
1240       activity = insert(:note_activity, user: target_user)
1241
1242       {:ok, %Activity{id: first_report_id}} =
1243         CommonAPI.report(reporter, %{
1244           account_id: target_user.id,
1245           comment: "I feel offended",
1246           status_ids: [activity.id]
1247         })
1248
1249       {:ok, %Activity{id: second_report_id}} =
1250         CommonAPI.report(reporter, %{
1251           account_id: target_user.id,
1252           comment: "I feel very offended!",
1253           status_ids: [activity.id]
1254         })
1255
1256       {:ok, report_ids} =
1257         CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1258
1259       first_report = Activity.get_by_id(first_report_id)
1260       second_report = Activity.get_by_id(second_report_id)
1261
1262       assert report_ids -- [first_report_id, second_report_id] == []
1263       assert first_report.data["state"] == "resolved"
1264       assert second_report.data["state"] == "resolved"
1265     end
1266   end
1267
1268   describe "reblog muting" do
1269     setup do
1270       muter = insert(:user)
1271
1272       muted = insert(:user)
1273
1274       [muter: muter, muted: muted]
1275     end
1276
1277     test "add a reblog mute", %{muter: muter, muted: muted} do
1278       {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1279
1280       assert User.showing_reblogs?(muter, muted) == false
1281     end
1282
1283     test "remove a reblog mute", %{muter: muter, muted: muted} do
1284       {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1285       {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1286
1287       assert User.showing_reblogs?(muter, muted) == true
1288     end
1289   end
1290
1291   describe "follow/2" do
1292     test "directly follows a non-locked local user" do
1293       [follower, followed] = insert_pair(:user)
1294       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1295
1296       assert User.following?(follower, followed)
1297     end
1298   end
1299
1300   describe "unfollow/2" do
1301     test "also unsubscribes a user" do
1302       [follower, followed] = insert_pair(:user)
1303       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1304       {:ok, _subscription} = User.subscribe(follower, followed)
1305
1306       assert User.subscribed_to?(follower, followed)
1307
1308       {:ok, follower} = CommonAPI.unfollow(follower, followed)
1309
1310       refute User.subscribed_to?(follower, followed)
1311     end
1312
1313     test "also unpins a user" do
1314       [follower, followed] = insert_pair(:user)
1315       {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1316       {:ok, _endorsement} = User.endorse(follower, followed)
1317
1318       assert User.endorses?(follower, followed)
1319
1320       {:ok, follower} = CommonAPI.unfollow(follower, followed)
1321
1322       refute User.endorses?(follower, followed)
1323     end
1324
1325     test "cancels a pending follow for a local user" do
1326       follower = insert(:user)
1327       followed = insert(:user, is_locked: true)
1328
1329       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1330                CommonAPI.follow(follower, followed)
1331
1332       assert User.get_follow_state(follower, followed) == :follow_pending
1333       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1334       assert User.get_follow_state(follower, followed) == nil
1335
1336       assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1337                Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1338
1339       assert %{
1340                data: %{
1341                  "type" => "Undo",
1342                  "object" => %{"type" => "Follow", "state" => "cancelled"}
1343                }
1344              } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1345     end
1346
1347     test "cancels a pending follow for a remote user" do
1348       follower = insert(:user)
1349       followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1350
1351       assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1352                CommonAPI.follow(follower, followed)
1353
1354       assert User.get_follow_state(follower, followed) == :follow_pending
1355       assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1356       assert User.get_follow_state(follower, followed) == nil
1357
1358       assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1359                Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1360
1361       assert %{
1362                data: %{
1363                  "type" => "Undo",
1364                  "object" => %{"type" => "Follow", "state" => "cancelled"}
1365                }
1366              } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1367     end
1368   end
1369
1370   describe "accept_follow_request/2" do
1371     test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1372       user = insert(:user, is_locked: true)
1373       follower = insert(:user)
1374       follower_two = insert(:user)
1375
1376       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1377       {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1378       {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1379
1380       assert follow_activity.data["state"] == "pending"
1381       assert follow_activity_two.data["state"] == "pending"
1382       assert follow_activity_three.data["state"] == "pending"
1383
1384       {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1385
1386       assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1387       assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1388       assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1389     end
1390
1391     test "after rejection, it sets all existing pending follow request states to 'reject'" do
1392       user = insert(:user, is_locked: true)
1393       follower = insert(:user)
1394       follower_two = insert(:user)
1395
1396       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1397       {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1398       {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1399
1400       assert follow_activity.data["state"] == "pending"
1401       assert follow_activity_two.data["state"] == "pending"
1402       assert follow_activity_three.data["state"] == "pending"
1403
1404       {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1405
1406       assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1407       assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1408       assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1409     end
1410
1411     test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1412       user = insert(:user, is_locked: true)
1413       not_follower = insert(:user)
1414       CommonAPI.accept_follow_request(not_follower, user)
1415
1416       assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1417     end
1418   end
1419
1420   describe "vote/3" do
1421     test "does not allow to vote twice" do
1422       user = insert(:user)
1423       other_user = insert(:user)
1424
1425       {:ok, activity} =
1426         CommonAPI.post(user, %{
1427           status: "Am I cute?",
1428           poll: %{options: ["Yes", "No"], expires_in: 20}
1429         })
1430
1431       object = Object.normalize(activity, fetch: false)
1432
1433       {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1434
1435       assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1436     end
1437   end
1438
1439   describe "listen/2" do
1440     test "returns a valid activity" do
1441       user = insert(:user)
1442
1443       {:ok, activity} =
1444         CommonAPI.listen(user, %{
1445           title: "lain radio episode 1",
1446           album: "lain radio",
1447           artist: "lain",
1448           length: 180_000
1449         })
1450
1451       object = Object.normalize(activity, fetch: false)
1452
1453       assert object.data["title"] == "lain radio episode 1"
1454
1455       assert Visibility.get_visibility(activity) == "public"
1456     end
1457
1458     test "respects visibility=private" do
1459       user = insert(:user)
1460
1461       {:ok, activity} =
1462         CommonAPI.listen(user, %{
1463           title: "lain radio episode 1",
1464           album: "lain radio",
1465           artist: "lain",
1466           length: 180_000,
1467           visibility: "private"
1468         })
1469
1470       object = Object.normalize(activity, fetch: false)
1471
1472       assert object.data["title"] == "lain radio episode 1"
1473
1474       assert Visibility.get_visibility(activity) == "private"
1475     end
1476   end
1477
1478   describe "get_user/1" do
1479     test "gets user by ap_id" do
1480       user = insert(:user)
1481       assert CommonAPI.get_user(user.ap_id) == user
1482     end
1483
1484     test "gets user by guessed nickname" do
1485       user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1486       assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1487     end
1488
1489     test "fallback" do
1490       assert %User{
1491                name: "",
1492                ap_id: "",
1493                nickname: "erroruser@example.com"
1494              } = CommonAPI.get_user("")
1495     end
1496   end
1497
1498   describe "with `local` visibility" do
1499     setup do: clear_config([:instance, :federating], true)
1500
1501     test "post" do
1502       user = insert(:user)
1503
1504       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1505         {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1506
1507         assert Visibility.is_local_public?(activity)
1508         assert_not_called(Pleroma.Web.Federator.publish(activity))
1509       end
1510     end
1511
1512     test "delete" do
1513       user = insert(:user)
1514
1515       {:ok, %Activity{id: activity_id}} =
1516         CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1517
1518       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1519         assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1520                  CommonAPI.delete(activity_id, user)
1521
1522         assert Visibility.is_local_public?(activity)
1523         assert_not_called(Pleroma.Web.Federator.publish(activity))
1524       end
1525     end
1526
1527     test "repeat" do
1528       user = insert(:user)
1529       other_user = insert(:user)
1530
1531       {:ok, %Activity{id: activity_id}} =
1532         CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1533
1534       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1535         assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1536                  CommonAPI.repeat(activity_id, user)
1537
1538         assert Visibility.is_local_public?(activity)
1539         refute called(Pleroma.Web.Federator.publish(activity))
1540       end
1541     end
1542
1543     test "unrepeat" do
1544       user = insert(:user)
1545       other_user = insert(:user)
1546
1547       {:ok, %Activity{id: activity_id}} =
1548         CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1549
1550       assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1551
1552       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1553         assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1554                  CommonAPI.unrepeat(activity_id, user)
1555
1556         assert Visibility.is_local_public?(activity)
1557         refute called(Pleroma.Web.Federator.publish(activity))
1558       end
1559     end
1560
1561     test "favorite" do
1562       user = insert(:user)
1563       other_user = insert(:user)
1564
1565       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1566
1567       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1568         assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1569                  CommonAPI.favorite(user, activity.id)
1570
1571         assert Visibility.is_local_public?(activity)
1572         refute called(Pleroma.Web.Federator.publish(activity))
1573       end
1574     end
1575
1576     test "unfavorite" do
1577       user = insert(:user)
1578       other_user = insert(:user)
1579
1580       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1581
1582       {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1583
1584       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1585         assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1586         assert Visibility.is_local_public?(activity)
1587         refute called(Pleroma.Web.Federator.publish(activity))
1588       end
1589     end
1590
1591     test "react_with_emoji" do
1592       user = insert(:user)
1593       other_user = insert(:user)
1594       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1595
1596       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1597         assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1598                  CommonAPI.react_with_emoji(activity.id, user, "👍")
1599
1600         assert Visibility.is_local_public?(activity)
1601         refute called(Pleroma.Web.Federator.publish(activity))
1602       end
1603     end
1604
1605     test "unreact_with_emoji" do
1606       user = insert(:user)
1607       other_user = insert(:user)
1608       {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1609
1610       {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1611
1612       with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1613         assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1614                  CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1615
1616         assert Visibility.is_local_public?(activity)
1617         refute called(Pleroma.Web.Federator.publish(activity))
1618       end
1619     end
1620   end
1621
1622   describe "update/3" do
1623     test "updates a post" do
1624       user = insert(:user)
1625       {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1"})
1626
1627       {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1628
1629       updated_object = Object.normalize(updated)
1630       assert updated_object.data["content"] == "updated 2"
1631       assert Map.get(updated_object.data, "summary", "") == ""
1632       assert Map.has_key?(updated_object.data, "updated")
1633     end
1634
1635     test "does not change visibility" do
1636       user = insert(:user)
1637
1638       {:ok, activity} =
1639         CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1", visibility: "private"})
1640
1641       {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1642
1643       updated_object = Object.normalize(updated)
1644       assert updated_object.data["content"] == "updated 2"
1645       assert Map.get(updated_object.data, "summary", "") == ""
1646       assert Visibility.get_visibility(updated_object) == "private"
1647       assert Visibility.get_visibility(updated) == "private"
1648     end
1649
1650     test "updates a post with emoji" do
1651       [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1652
1653       user = insert(:user)
1654
1655       {:ok, activity} =
1656         CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1657
1658       {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1659
1660       updated_object = Object.normalize(updated)
1661       assert updated_object.data["content"] == "updated 2 :#{emoji2}:"
1662       assert %{^emoji2 => _} = updated_object.data["emoji"]
1663     end
1664
1665     test "updates a post with emoji and federate properly" do
1666       [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1667
1668       user = insert(:user)
1669
1670       {:ok, activity} =
1671         CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1672
1673       clear_config([:instance, :federating], true)
1674
1675       with_mock Pleroma.Web.Federator,
1676         publish: fn _p -> nil end do
1677         {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1678
1679         assert updated.data["object"]["content"] == "updated 2 :#{emoji2}:"
1680         assert %{^emoji2 => _} = updated.data["object"]["emoji"]
1681
1682         assert called(Pleroma.Web.Federator.publish(updated))
1683       end
1684     end
1685
1686     test "editing a post that copied a remote title with remote emoji should keep that emoji" do
1687       remote_emoji_uri = "https://remote.org/emoji.png"
1688
1689       note =
1690         insert(
1691           :note,
1692           data: %{
1693             "summary" => ":remoteemoji:",
1694             "emoji" => %{
1695               "remoteemoji" => remote_emoji_uri
1696             },
1697             "tag" => [
1698               %{
1699                 "type" => "Emoji",
1700                 "name" => "remoteemoji",
1701                 "icon" => %{"url" => remote_emoji_uri}
1702               }
1703             ]
1704           }
1705         )
1706
1707       note_activity = insert(:note_activity, note: note)
1708
1709       user = insert(:user)
1710
1711       {:ok, reply} =
1712         CommonAPI.post(user, %{
1713           status: "reply",
1714           spoiler_text: ":remoteemoji:",
1715           in_reply_to_id: note_activity.id
1716         })
1717
1718       assert reply.object.data["emoji"]["remoteemoji"] == remote_emoji_uri
1719
1720       {:ok, edit} =
1721         CommonAPI.update(user, reply, %{status: "reply mew mew", spoiler_text: ":remoteemoji:"})
1722
1723       edited_note = Pleroma.Object.normalize(edit)
1724
1725       assert edited_note.data["emoji"]["remoteemoji"] == remote_emoji_uri
1726     end
1727
1728     test "respects MRF" do
1729       user = insert(:user)
1730
1731       clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
1732       clear_config([:mrf_keyword, :replace], [{"updated", "mewmew"}])
1733
1734       {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "updated 1"})
1735       assert Object.normalize(activity).data["summary"] == "mewmew 1"
1736
1737       {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1738
1739       updated_object = Object.normalize(updated)
1740       assert updated_object.data["content"] == "mewmew 2"
1741       assert Map.get(updated_object.data, "summary", "") == ""
1742       assert Map.has_key?(updated_object.data, "updated")
1743     end
1744   end
1745 end