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