1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.CommonAPITest do
6 use Oban.Testing, repo: Pleroma.Repo
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Notification
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
23 import Pleroma.Factory
25 import Ecto.Query, only: [from: 2]
27 require Pleroma.Constants
30 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
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])
38 describe "posting polls" do
39 test "it posts a poll" do
43 CommonAPI.post(user, %{
44 status: "who is the best",
45 poll: %{expires_in: 600, options: ["reimu", "marisa"]}
48 object = Object.normalize(activity, fetch: false)
50 assert object.data["type"] == "Question"
51 assert object.data["oneOf"] |> length() == 2
55 args: %{op: "poll_end", activity_id: activity.id},
56 scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
61 describe "blocking" do
63 blocker = insert(:user)
64 blocked = insert(:user)
65 User.follow(blocker, blocked)
66 User.follow(blocked, blocker)
67 %{blocker: blocker, blocked: blocked}
70 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
71 clear_config([:instance, :federating], true)
73 with_mock Pleroma.Web.Federator,
74 publish: fn _ -> nil end do
75 assert {:ok, block} = CommonAPI.block(blocker, blocked)
78 assert User.blocks?(blocker, blocked)
79 refute User.following?(blocker, blocked)
80 refute User.following?(blocked, blocker)
82 assert called(Pleroma.Web.Federator.publish(block))
86 test "it blocks and does not federate if outgoing blocks are disabled", %{
90 clear_config([:instance, :federating], true)
91 clear_config([:activitypub, :outgoing_blocks], false)
93 with_mock Pleroma.Web.Federator,
94 publish: fn _ -> nil end do
95 assert {:ok, block} = CommonAPI.block(blocker, blocked)
98 assert User.blocks?(blocker, blocked)
99 refute User.following?(blocker, blocked)
100 refute User.following?(blocked, blocker)
102 refute called(Pleroma.Web.Federator.publish(block))
107 describe "posting chat messages" do
108 setup do: clear_config([:instance, :chat_limit])
110 test "it posts a self-chat" do
111 author = insert(:user)
115 CommonAPI.post_chat_message(
118 "remember to buy milk when milk truk arive"
121 assert activity.data["type"] == "Create"
124 test "it posts a chat message without content but with an attachment" do
125 author = insert(:user)
126 recipient = insert(:user)
129 content_type: "image/jpeg",
130 path: Path.absname("test/fixtures/image.jpg"),
131 filename: "an_image.jpg"
134 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
138 Pleroma.Web.Streamer,
150 send: fn _ -> nil end
155 CommonAPI.post_chat_message(
163 Notification.for_user_and_activity(recipient, activity)
164 |> Repo.preload(:activity)
166 assert called(Pleroma.Web.Push.send(notification))
167 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
168 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
174 test "it adds html newlines" do
175 author = insert(:user)
176 recipient = insert(:user)
178 other_user = insert(:user)
181 CommonAPI.post_chat_message(
187 assert other_user.ap_id not in activity.recipients
189 object = Object.normalize(activity, fetch: false)
191 assert object.data["content"] == "uguu<br/>uguuu"
194 test "it linkifies" do
195 author = insert(:user)
196 recipient = insert(:user)
198 other_user = insert(:user)
201 CommonAPI.post_chat_message(
204 "https://example.org is the site of @#{other_user.nickname} #2hu"
207 assert other_user.ap_id not in activity.recipients
209 object = Object.normalize(activity, fetch: false)
211 assert object.data["content"] ==
212 "<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>"
215 test "it posts a chat message" do
216 author = insert(:user)
217 recipient = insert(:user)
220 CommonAPI.post_chat_message(
223 "a test message <script>alert('uuu')</script> :firefox:"
226 assert activity.data["type"] == "Create"
227 assert activity.local
228 object = Object.normalize(activity, fetch: false)
230 assert object.data["type"] == "ChatMessage"
231 assert object.data["to"] == [recipient.ap_id]
233 assert object.data["content"] ==
234 "a test message <script>alert('uuu')</script> :firefox:"
236 assert object.data["emoji"] == %{
237 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
240 assert Chat.get(author.id, recipient.ap_id)
241 assert Chat.get(recipient.id, author.ap_id)
243 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
246 test "it reject messages over the local limit" do
247 clear_config([:instance, :chat_limit], 2)
249 author = insert(:user)
250 recipient = insert(:user)
253 CommonAPI.post_chat_message(
259 assert message == :content_too_long
262 test "it reject messages via MRF" do
263 clear_config([:mrf_keyword, :reject], ["GNO"])
264 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
266 author = insert(:user)
267 recipient = insert(:user)
269 assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
270 CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
274 describe "unblocking" do
275 test "it works even without an existing block activity" do
276 blocked = insert(:user)
277 blocker = insert(:user)
278 User.block(blocker, blocked)
280 assert User.blocks?(blocker, blocked)
281 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
282 refute User.blocks?(blocker, blocked)
286 describe "deletion" do
287 test "it works with pruned objects" do
290 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
292 clear_config([:instance, :federating], true)
294 Object.normalize(post, fetch: false)
297 with_mock Pleroma.Web.Federator,
298 publish: fn _ -> nil end do
299 assert {:ok, delete} = CommonAPI.delete(post.id, user)
301 assert called(Pleroma.Web.Federator.publish(delete))
304 refute Activity.get_by_id(post.id)
307 test "it allows users to delete their posts" do
310 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
312 clear_config([:instance, :federating], true)
314 with_mock Pleroma.Web.Federator,
315 publish: fn _ -> nil end do
316 assert {:ok, delete} = CommonAPI.delete(post.id, user)
318 assert called(Pleroma.Web.Federator.publish(delete))
321 refute Activity.get_by_id(post.id)
324 test "it does not allow a user to delete their posts" do
326 other_user = insert(:user)
328 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
330 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
331 assert Activity.get_by_id(post.id)
334 test "it allows moderators to delete other user's posts" do
336 moderator = insert(:user, is_moderator: true)
338 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
340 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
343 refute Activity.get_by_id(post.id)
346 test "it allows admins to delete other user's posts" do
348 moderator = insert(:user, is_admin: true)
350 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
352 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
355 refute Activity.get_by_id(post.id)
358 test "superusers deleting non-local posts won't federate the delete" do
359 # This is the user of the ingested activity
363 ap_id: "http://mastodon.example.org/users/admin",
364 last_refreshed_at: NaiveDateTime.utc_now()
367 moderator = insert(:user, is_admin: true)
370 File.read!("test/fixtures/mastodon-post-activity.json")
373 {:ok, post} = Transmogrifier.handle_incoming(data)
375 with_mock Pleroma.Web.Federator,
376 publish: fn _ -> nil end do
377 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
379 refute called(Pleroma.Web.Federator.publish(:_))
382 refute Activity.get_by_id(post.id)
386 test "favoriting race condition" do
388 users_serial = insert_list(10, :user)
389 users = insert_list(10, :user)
391 {:ok, activity} = CommonAPI.post(user, %{status: "."})
394 |> Enum.map(fn user ->
395 CommonAPI.favorite(user, activity.id)
398 object = Object.get_by_ap_id(activity.data["object"])
399 assert object.data["like_count"] == 10
402 |> Enum.map(fn user ->
404 CommonAPI.favorite(user, activity.id)
407 |> Enum.map(&Task.await/1)
409 object = Object.get_by_ap_id(activity.data["object"])
410 assert object.data["like_count"] == 20
413 test "repeating race condition" do
415 users_serial = insert_list(10, :user)
416 users = insert_list(10, :user)
418 {:ok, activity} = CommonAPI.post(user, %{status: "."})
421 |> Enum.map(fn user ->
422 CommonAPI.repeat(activity.id, user)
425 object = Object.get_by_ap_id(activity.data["object"])
426 assert object.data["announcement_count"] == 10
429 |> Enum.map(fn user ->
431 CommonAPI.repeat(activity.id, user)
434 |> Enum.map(&Task.await/1)
436 object = Object.get_by_ap_id(activity.data["object"])
437 assert object.data["announcement_count"] == 20
440 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
442 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
444 [participation] = Participation.for_user(user)
447 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
449 assert Visibility.is_direct?(convo_reply)
451 assert activity.data["context"] == convo_reply.data["context"]
454 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
456 jafnhar = insert(:user)
457 tridi = insert(:user)
460 CommonAPI.post(har, %{
461 status: "@#{jafnhar.nickname} hey",
465 assert har.ap_id in activity.recipients
466 assert jafnhar.ap_id in activity.recipients
468 [participation] = Participation.for_user(har)
471 CommonAPI.post(har, %{
472 status: "I don't really like @#{tridi.nickname}",
473 visibility: "direct",
474 in_reply_to_status_id: activity.id,
475 in_reply_to_conversation_id: participation.id
478 assert har.ap_id in activity.recipients
479 assert jafnhar.ap_id in activity.recipients
480 refute tridi.ap_id in activity.recipients
483 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
485 jafnhar = insert(:user)
486 tridi = insert(:user)
488 clear_config([:instance, :safe_dm_mentions], true)
491 CommonAPI.post(har, %{
492 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
496 refute tridi.ap_id in activity.recipients
497 assert jafnhar.ap_id in activity.recipients
500 test "it de-duplicates tags" do
502 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
504 object = Object.normalize(activity, fetch: false)
506 assert Object.tags(object) == ["2hu"]
509 test "it adds emoji in the object" do
511 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
513 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
516 describe "posting" do
517 test "it adds an emoji on an external site" do
519 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
521 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
522 assert url == "https://example.com/emoji.png"
524 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
526 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
527 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
530 test "it copies emoji from the subject of the parent post" do
533 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
537 activity = Activity.get_create_by_object_ap_id(object.data["id"])
540 {:ok, reply_activity} =
541 CommonAPI.post(user, %{
542 in_reply_to_id: activity.id,
543 status: ":joker_disapprove:",
544 spoiler_text: ":joker_smile:"
547 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
548 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
551 test "deactivated users can't post" do
552 user = insert(:user, is_active: false)
553 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
556 test "it supports explicit addressing" do
558 user_two = insert(:user)
559 user_three = insert(:user)
560 user_four = insert(:user)
563 CommonAPI.post(user, %{
565 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
566 to: [user_two.nickname, user_four.nickname, "nonexistent"]
569 assert user.ap_id in activity.recipients
570 assert user_two.ap_id in activity.recipients
571 assert user_four.ap_id in activity.recipients
572 refute user_three.ap_id in activity.recipients
575 test "it filters out obviously bad tags when accepting a post as HTML" do
578 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
581 CommonAPI.post(user, %{
583 content_type: "text/html"
586 object = Object.normalize(activity, fetch: false)
588 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
589 assert object.data["source"] == post
592 test "it filters out obviously bad tags when accepting a post as Markdown" do
595 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
598 CommonAPI.post(user, %{
600 content_type: "text/markdown"
603 object = Object.normalize(activity, fetch: false)
605 assert object.data["content"] == "<p><b>2hu</b></p>"
606 assert object.data["source"] == post
609 test "it does not allow replies to direct messages that are not direct messages themselves" do
612 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
615 CommonAPI.post(user, %{
617 visibility: "direct",
618 in_reply_to_status_id: activity.id
621 Enum.each(["public", "private", "unlisted"], fn visibility ->
622 assert {:error, "The message visibility must be direct"} =
623 CommonAPI.post(user, %{
625 visibility: visibility,
626 in_reply_to_status_id: activity.id
631 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
633 other_user = insert(:user)
634 third_user = insert(:user)
636 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
639 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
641 # The OP is implicitly added
642 assert user.ap_id in open_answer.recipients
644 {:ok, secret_answer} =
645 CommonAPI.post(other_user, %{
646 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
647 in_reply_to_status_id: post.id,
651 assert third_user.ap_id in secret_answer.recipients
653 # The OP is not added
654 refute user.ap_id in secret_answer.recipients
657 test "it allows to address a list" do
659 {:ok, list} = Pleroma.List.create("foo", user)
661 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
663 assert activity.data["bcc"] == [list.ap_id]
664 assert activity.recipients == [list.ap_id, user.ap_id]
665 assert activity.data["listMessage"] == list.ap_id
668 test "it returns error when status is empty and no attachments" do
671 assert {:error, "Cannot post an empty status without attachments"} =
672 CommonAPI.post(user, %{status: ""})
675 test "it validates character limits are correctly enforced" do
676 clear_config([:instance, :limit], 5)
680 assert {:error, "The status is over the character limit"} =
681 CommonAPI.post(user, %{status: "foobar"})
683 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
686 test "it can handle activities that expire" do
689 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
691 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
694 worker: Pleroma.Workers.PurgeExpiredActivity,
695 args: %{activity_id: activity.id},
696 scheduled_at: expires_at
701 describe "reactions" do
702 test "reacting to a status with an emoji" do
704 other_user = insert(:user)
706 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
708 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
710 assert reaction.data["actor"] == user.ap_id
711 assert reaction.data["content"] == "👍"
713 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
715 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
718 test "unreacting to a status with an emoji" do
720 other_user = insert(:user)
722 clear_config([:instance, :federating], true)
724 with_mock Pleroma.Web.Federator,
725 publish: fn _ -> nil end do
726 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
727 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
729 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
731 assert unreaction.data["type"] == "Undo"
732 assert unreaction.data["object"] == reaction.data["id"]
733 assert unreaction.local
735 # On federation, it contains the undone (and deleted) object
736 unreaction_with_object = %{
738 | data: Map.put(unreaction.data, "object", reaction.data)
741 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
745 test "repeating a status" do
747 other_user = insert(:user)
749 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
751 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
752 assert Visibility.is_public?(announce_activity)
755 test "can't repeat a repeat" do
757 other_user = insert(:user)
758 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
760 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
762 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
765 test "repeating a status privately" do
767 other_user = insert(:user)
769 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
771 {:ok, %Activity{} = announce_activity} =
772 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
774 assert Visibility.is_private?(announce_activity)
775 refute Visibility.visible_for_user?(announce_activity, nil)
778 test "author can repeat own private statuses" do
779 author = insert(:user)
780 follower = insert(:user)
781 CommonAPI.follow(follower, author)
783 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
785 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
787 assert Visibility.is_private?(announce_activity)
788 refute Visibility.visible_for_user?(announce_activity, nil)
790 assert Visibility.visible_for_user?(activity, follower)
791 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
794 test "favoriting a status" do
796 other_user = insert(:user)
798 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
800 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
801 assert data["type"] == "Like"
802 assert data["actor"] == user.ap_id
803 assert data["object"] == post_activity.data["object"]
806 test "retweeting a status twice returns the status" do
808 other_user = insert(:user)
810 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
811 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
812 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
815 test "favoriting a status twice returns ok, but without the like activity" do
817 other_user = insert(:user)
819 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
820 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
821 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
825 describe "pinned statuses" do
827 clear_config([:instance, :max_pinned_statuses], 1)
830 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
832 [user: user, activity: activity]
835 test "activity not found error", %{user: user} do
836 assert {:error, :not_found} = CommonAPI.pin("id", user)
839 test "pin status", %{user: user, activity: activity} do
840 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
842 %{data: %{"id" => object_id}} = Object.normalize(activity)
843 user = refresh_record(user)
845 assert user.pinned_objects |> Map.keys() == [object_id]
848 test "pin poll", %{user: user} do
850 CommonAPI.post(user, %{
851 status: "How is fediverse today?",
852 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
855 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
857 %{data: %{"id" => object_id}} = Object.normalize(activity)
859 user = refresh_record(user)
861 assert user.pinned_objects |> Map.keys() == [object_id]
864 test "unlisted statuses can be pinned", %{user: user} do
865 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
866 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
869 test "only self-authored can be pinned", %{activity: activity} do
872 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
875 test "max pinned statuses", %{user: user, activity: activity_one} do
876 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
878 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
880 user = refresh_record(user)
882 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
885 test "only public can be pinned", %{user: user} do
886 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
887 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
890 test "unpin status", %{user: user, activity: activity} do
891 {:ok, activity} = CommonAPI.pin(activity.id, user)
893 user = refresh_record(user)
897 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
899 user = refresh_record(user)
901 assert user.pinned_objects == %{}
904 test "should unpin when deleting a status", %{user: user, activity: activity} do
905 {:ok, activity} = CommonAPI.pin(activity.id, user)
907 user = refresh_record(user)
909 assert {:ok, _} = CommonAPI.delete(activity.id, user)
911 user = refresh_record(user)
913 assert user.pinned_objects == %{}
916 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
917 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
919 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
921 {:ok, _activity} = CommonAPI.pin(activity.id, user)
922 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
924 user = refresh_record(user)
925 {:ok, _} = CommonAPI.unpin(activity.id, user)
927 # recreates expiration job on unpin
928 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
931 test "ephemeral activity deletion job won't be deleted on pinning error", %{
935 clear_config([:instance, :max_pinned_statuses], 1)
937 {:ok, _activity} = CommonAPI.pin(activity.id, user)
939 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
941 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
943 user = refresh_record(user)
944 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
946 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
950 describe "mute tests" do
954 activity = insert(:note_activity)
956 [user: user, activity: activity]
959 test "marks notifications as read after mute" do
960 author = insert(:user)
961 activity = insert(:note_activity, user: author)
963 friend1 = insert(:user)
964 friend2 = insert(:user)
966 {:ok, reply_activity} =
970 status: "@#{author.nickname} @#{friend1.nickname} test reply",
971 in_reply_to_status_id: activity.id
975 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
976 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
978 assert Repo.aggregate(
979 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
983 unread_notifications =
984 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
986 assert Enum.any?(unread_notifications, fn n ->
987 n.type == "favourite" && n.activity_id == favorite_activity.id
990 assert Enum.any?(unread_notifications, fn n ->
991 n.type == "reblog" && n.activity_id == repeat_activity.id
994 assert Enum.any?(unread_notifications, fn n ->
995 n.type == "mention" && n.activity_id == reply_activity.id
998 {:ok, _} = CommonAPI.add_mute(author, activity)
999 assert CommonAPI.thread_muted?(author, activity)
1001 assert Repo.aggregate(
1002 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1006 read_notifications =
1007 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1009 assert Enum.any?(read_notifications, fn n ->
1010 n.type == "favourite" && n.activity_id == favorite_activity.id
1013 assert Enum.any?(read_notifications, fn n ->
1014 n.type == "reblog" && n.activity_id == repeat_activity.id
1017 assert Enum.any?(read_notifications, fn n ->
1018 n.type == "mention" && n.activity_id == reply_activity.id
1022 test "add mute", %{user: user, activity: activity} do
1023 {:ok, _} = CommonAPI.add_mute(user, activity)
1024 assert CommonAPI.thread_muted?(user, activity)
1027 test "add expiring mute", %{user: user, activity: activity} do
1028 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1029 assert CommonAPI.thread_muted?(user, activity)
1031 worker = Pleroma.Workers.MuteExpireWorker
1032 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1039 assert :ok = perform_job(worker, args)
1040 refute CommonAPI.thread_muted?(user, activity)
1043 test "remove mute", %{user: user, activity: activity} do
1044 CommonAPI.add_mute(user, activity)
1045 {:ok, _} = CommonAPI.remove_mute(user, activity)
1046 refute CommonAPI.thread_muted?(user, activity)
1049 test "remove mute by ids", %{user: user, activity: activity} do
1050 CommonAPI.add_mute(user, activity)
1051 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1052 refute CommonAPI.thread_muted?(user, activity)
1055 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1056 CommonAPI.add_mute(user, activity)
1057 {:error, _} = CommonAPI.add_mute(user, activity)
1061 describe "reports" do
1062 test "creates a report" do
1063 reporter = insert(:user)
1064 target_user = insert(:user)
1066 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1068 reporter_ap_id = reporter.ap_id
1069 target_ap_id = target_user.ap_id
1070 activity_ap_id = activity.data["id"]
1074 account_id: target_user.id,
1076 status_ids: [activity.id]
1081 "id" => activity_ap_id,
1082 "content" => "foobar",
1083 "published" => activity.object.data["published"],
1084 "actor" => AccountView.render("show.json", %{user: target_user})
1087 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1090 actor: ^reporter_ap_id,
1093 "content" => ^comment,
1094 "object" => [^target_ap_id, ^note_obj],
1100 test "updates report state" do
1101 [reporter, target_user] = insert_pair(:user)
1102 activity = insert(:note_activity, user: target_user)
1104 {:ok, %Activity{id: report_id}} =
1105 CommonAPI.report(reporter, %{
1106 account_id: target_user.id,
1107 comment: "I feel offended",
1108 status_ids: [activity.id]
1111 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1113 assert report.data["state"] == "resolved"
1115 [reported_user, activity_id] = report.data["object"]
1117 assert reported_user == target_user.ap_id
1118 assert activity_id == activity.data["id"]
1121 test "does not update report state when state is unsupported" do
1122 [reporter, target_user] = insert_pair(:user)
1123 activity = insert(:note_activity, user: target_user)
1125 {:ok, %Activity{id: report_id}} =
1126 CommonAPI.report(reporter, %{
1127 account_id: target_user.id,
1128 comment: "I feel offended",
1129 status_ids: [activity.id]
1132 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1135 test "updates state of multiple reports" do
1136 [reporter, target_user] = insert_pair(:user)
1137 activity = insert(:note_activity, user: target_user)
1139 {:ok, %Activity{id: first_report_id}} =
1140 CommonAPI.report(reporter, %{
1141 account_id: target_user.id,
1142 comment: "I feel offended",
1143 status_ids: [activity.id]
1146 {:ok, %Activity{id: second_report_id}} =
1147 CommonAPI.report(reporter, %{
1148 account_id: target_user.id,
1149 comment: "I feel very offended!",
1150 status_ids: [activity.id]
1154 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1156 first_report = Activity.get_by_id(first_report_id)
1157 second_report = Activity.get_by_id(second_report_id)
1159 assert report_ids -- [first_report_id, second_report_id] == []
1160 assert first_report.data["state"] == "resolved"
1161 assert second_report.data["state"] == "resolved"
1165 describe "reblog muting" do
1167 muter = insert(:user)
1169 muted = insert(:user)
1171 [muter: muter, muted: muted]
1174 test "add a reblog mute", %{muter: muter, muted: muted} do
1175 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1177 assert User.showing_reblogs?(muter, muted) == false
1180 test "remove a reblog mute", %{muter: muter, muted: muted} do
1181 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1182 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1184 assert User.showing_reblogs?(muter, muted) == true
1188 describe "follow/2" do
1189 test "directly follows a non-locked local user" do
1190 [follower, followed] = insert_pair(:user)
1191 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1193 assert User.following?(follower, followed)
1197 describe "unfollow/2" do
1198 test "also unsubscribes a user" do
1199 [follower, followed] = insert_pair(:user)
1200 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1201 {:ok, _subscription} = User.subscribe(follower, followed)
1203 assert User.subscribed_to?(follower, followed)
1205 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1207 refute User.subscribed_to?(follower, followed)
1210 test "cancels a pending follow for a local user" do
1211 follower = insert(:user)
1212 followed = insert(:user, is_locked: true)
1214 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1215 CommonAPI.follow(follower, followed)
1217 assert User.get_follow_state(follower, followed) == :follow_pending
1218 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1219 assert User.get_follow_state(follower, followed) == nil
1221 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1222 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1227 "object" => %{"type" => "Follow", "state" => "cancelled"}
1229 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1232 test "cancels a pending follow for a remote user" do
1233 follower = insert(:user)
1234 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1236 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1237 CommonAPI.follow(follower, followed)
1239 assert User.get_follow_state(follower, followed) == :follow_pending
1240 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1241 assert User.get_follow_state(follower, followed) == nil
1243 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1244 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1249 "object" => %{"type" => "Follow", "state" => "cancelled"}
1251 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1255 describe "accept_follow_request/2" do
1256 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1257 user = insert(:user, is_locked: true)
1258 follower = insert(:user)
1259 follower_two = insert(:user)
1261 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1262 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1263 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1265 assert follow_activity.data["state"] == "pending"
1266 assert follow_activity_two.data["state"] == "pending"
1267 assert follow_activity_three.data["state"] == "pending"
1269 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1271 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1272 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1273 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1276 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1277 user = insert(:user, is_locked: true)
1278 follower = insert(:user)
1279 follower_two = insert(:user)
1281 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1282 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1283 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1285 assert follow_activity.data["state"] == "pending"
1286 assert follow_activity_two.data["state"] == "pending"
1287 assert follow_activity_three.data["state"] == "pending"
1289 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1291 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1292 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1293 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1296 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1297 user = insert(:user, is_locked: true)
1298 not_follower = insert(:user)
1299 CommonAPI.accept_follow_request(not_follower, user)
1301 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1305 describe "vote/3" do
1306 test "does not allow to vote twice" do
1307 user = insert(:user)
1308 other_user = insert(:user)
1311 CommonAPI.post(user, %{
1312 status: "Am I cute?",
1313 poll: %{options: ["Yes", "No"], expires_in: 20}
1316 object = Object.normalize(activity, fetch: false)
1318 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1320 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1324 describe "listen/2" do
1325 test "returns a valid activity" do
1326 user = insert(:user)
1329 CommonAPI.listen(user, %{
1330 title: "lain radio episode 1",
1331 album: "lain radio",
1336 object = Object.normalize(activity, fetch: false)
1338 assert object.data["title"] == "lain radio episode 1"
1340 assert Visibility.get_visibility(activity) == "public"
1343 test "respects visibility=private" do
1344 user = insert(:user)
1347 CommonAPI.listen(user, %{
1348 title: "lain radio episode 1",
1349 album: "lain radio",
1352 visibility: "private"
1355 object = Object.normalize(activity, fetch: false)
1357 assert object.data["title"] == "lain radio episode 1"
1359 assert Visibility.get_visibility(activity) == "private"
1363 describe "get_user/1" do
1364 test "gets user by ap_id" do
1365 user = insert(:user)
1366 assert CommonAPI.get_user(user.ap_id) == user
1369 test "gets user by guessed nickname" do
1370 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1371 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1378 nickname: "erroruser@example.com"
1379 } = CommonAPI.get_user("")
1383 describe "with `local` visibility" do
1384 setup do: clear_config([:instance, :federating], true)
1387 user = insert(:user)
1389 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1390 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1392 assert Visibility.is_local_public?(activity)
1393 assert_not_called(Pleroma.Web.Federator.publish(activity))
1398 user = insert(:user)
1400 {:ok, %Activity{id: activity_id}} =
1401 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1403 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1404 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1405 CommonAPI.delete(activity_id, user)
1407 assert Visibility.is_local_public?(activity)
1408 assert_not_called(Pleroma.Web.Federator.publish(activity))
1413 user = insert(:user)
1414 other_user = insert(:user)
1416 {:ok, %Activity{id: activity_id}} =
1417 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1419 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1420 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1421 CommonAPI.repeat(activity_id, user)
1423 assert Visibility.is_local_public?(activity)
1424 refute called(Pleroma.Web.Federator.publish(activity))
1429 user = insert(:user)
1430 other_user = insert(:user)
1432 {:ok, %Activity{id: activity_id}} =
1433 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1435 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1437 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1438 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1439 CommonAPI.unrepeat(activity_id, user)
1441 assert Visibility.is_local_public?(activity)
1442 refute called(Pleroma.Web.Federator.publish(activity))
1447 user = insert(:user)
1448 other_user = insert(:user)
1450 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1452 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1453 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1454 CommonAPI.favorite(user, activity.id)
1456 assert Visibility.is_local_public?(activity)
1457 refute called(Pleroma.Web.Federator.publish(activity))
1461 test "unfavorite" do
1462 user = insert(:user)
1463 other_user = insert(:user)
1465 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1467 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1469 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1470 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1471 assert Visibility.is_local_public?(activity)
1472 refute called(Pleroma.Web.Federator.publish(activity))
1476 test "react_with_emoji" do
1477 user = insert(:user)
1478 other_user = insert(:user)
1479 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1481 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1482 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1483 CommonAPI.react_with_emoji(activity.id, user, "👍")
1485 assert Visibility.is_local_public?(activity)
1486 refute called(Pleroma.Web.Federator.publish(activity))
1490 test "unreact_with_emoji" do
1491 user = insert(:user)
1492 other_user = insert(:user)
1493 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1495 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1497 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1498 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1499 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1501 assert Visibility.is_local_public?(activity)
1502 refute called(Pleroma.Web.Federator.publish(activity))