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