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')"
590 assert object.data["source"] == %{
591 "mediaType" => "text/html",
596 test "it filters out obviously bad tags when accepting a post as Markdown" do
599 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
602 CommonAPI.post(user, %{
604 content_type: "text/markdown"
607 object = Object.normalize(activity, fetch: false)
609 assert object.data["content"] == "<p><b>2hu</b></p>"
611 assert object.data["source"] == %{
612 "mediaType" => "text/markdown",
617 test "it does not allow replies to direct messages that are not direct messages themselves" do
620 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
623 CommonAPI.post(user, %{
625 visibility: "direct",
626 in_reply_to_status_id: activity.id
629 Enum.each(["public", "private", "unlisted"], fn visibility ->
630 assert {:error, "The message visibility must be direct"} =
631 CommonAPI.post(user, %{
633 visibility: visibility,
634 in_reply_to_status_id: activity.id
639 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
641 other_user = insert(:user)
642 third_user = insert(:user)
644 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
647 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
649 # The OP is implicitly added
650 assert user.ap_id in open_answer.recipients
652 {:ok, secret_answer} =
653 CommonAPI.post(other_user, %{
654 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
655 in_reply_to_status_id: post.id,
659 assert third_user.ap_id in secret_answer.recipients
661 # The OP is not added
662 refute user.ap_id in secret_answer.recipients
665 test "it allows to address a list" do
667 {:ok, list} = Pleroma.List.create("foo", user)
669 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
671 assert activity.data["bcc"] == [list.ap_id]
672 assert activity.recipients == [list.ap_id, user.ap_id]
673 assert activity.data["listMessage"] == list.ap_id
676 test "it returns error when status is empty and no attachments" do
679 assert {:error, "Cannot post an empty status without attachments"} =
680 CommonAPI.post(user, %{status: ""})
683 test "it validates character limits are correctly enforced" do
684 clear_config([:instance, :limit], 5)
688 assert {:error, "The status is over the character limit"} =
689 CommonAPI.post(user, %{status: "foobar"})
691 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
694 test "it can handle activities that expire" do
697 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
699 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
702 worker: Pleroma.Workers.PurgeExpiredActivity,
703 args: %{activity_id: activity.id},
704 scheduled_at: expires_at
709 describe "reactions" do
710 test "reacting to a status with an emoji" do
712 other_user = insert(:user)
714 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
716 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
718 assert reaction.data["actor"] == user.ap_id
719 assert reaction.data["content"] == "👍"
721 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
723 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
726 test "unreacting to a status with an emoji" do
728 other_user = insert(:user)
730 clear_config([:instance, :federating], true)
732 with_mock Pleroma.Web.Federator,
733 publish: fn _ -> nil end do
734 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
735 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
737 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
739 assert unreaction.data["type"] == "Undo"
740 assert unreaction.data["object"] == reaction.data["id"]
741 assert unreaction.local
743 # On federation, it contains the undone (and deleted) object
744 unreaction_with_object = %{
746 | data: Map.put(unreaction.data, "object", reaction.data)
749 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
753 test "repeating a status" do
755 other_user = insert(:user)
757 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
759 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
760 assert Visibility.is_public?(announce_activity)
763 test "can't repeat a repeat" do
765 other_user = insert(:user)
766 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
768 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
770 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
773 test "repeating a status privately" do
775 other_user = insert(:user)
777 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
779 {:ok, %Activity{} = announce_activity} =
780 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
782 assert Visibility.is_private?(announce_activity)
783 refute Visibility.visible_for_user?(announce_activity, nil)
786 test "author can repeat own private statuses" do
787 author = insert(:user)
788 follower = insert(:user)
789 CommonAPI.follow(follower, author)
791 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
793 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
795 assert Visibility.is_private?(announce_activity)
796 refute Visibility.visible_for_user?(announce_activity, nil)
798 assert Visibility.visible_for_user?(activity, follower)
799 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
802 test "favoriting a status" do
804 other_user = insert(:user)
806 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
808 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
809 assert data["type"] == "Like"
810 assert data["actor"] == user.ap_id
811 assert data["object"] == post_activity.data["object"]
814 test "retweeting a status twice returns the status" do
816 other_user = insert(:user)
818 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
819 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
820 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
823 test "favoriting a status twice returns ok, but without the like activity" do
825 other_user = insert(:user)
827 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
828 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
829 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
833 describe "pinned statuses" do
835 clear_config([:instance, :max_pinned_statuses], 1)
838 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
840 [user: user, activity: activity]
843 test "activity not found error", %{user: user} do
844 assert {:error, :not_found} = CommonAPI.pin("id", user)
847 test "pin status", %{user: user, activity: activity} do
848 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
850 %{data: %{"id" => object_id}} = Object.normalize(activity)
851 user = refresh_record(user)
853 assert user.pinned_objects |> Map.keys() == [object_id]
856 test "pin poll", %{user: user} do
858 CommonAPI.post(user, %{
859 status: "How is fediverse today?",
860 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
863 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
865 %{data: %{"id" => object_id}} = Object.normalize(activity)
867 user = refresh_record(user)
869 assert user.pinned_objects |> Map.keys() == [object_id]
872 test "unlisted statuses can be pinned", %{user: user} do
873 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
874 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
877 test "only self-authored can be pinned", %{activity: activity} do
880 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
883 test "max pinned statuses", %{user: user, activity: activity_one} do
884 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
886 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
888 user = refresh_record(user)
890 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
893 test "only public can be pinned", %{user: user} do
894 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
895 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
898 test "unpin status", %{user: user, activity: activity} do
899 {:ok, activity} = CommonAPI.pin(activity.id, user)
901 user = refresh_record(user)
905 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
907 user = refresh_record(user)
909 assert user.pinned_objects == %{}
912 test "should unpin when deleting a status", %{user: user, activity: activity} do
913 {:ok, activity} = CommonAPI.pin(activity.id, user)
915 user = refresh_record(user)
917 assert {:ok, _} = CommonAPI.delete(activity.id, user)
919 user = refresh_record(user)
921 assert user.pinned_objects == %{}
924 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
925 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
927 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
929 {:ok, _activity} = CommonAPI.pin(activity.id, user)
930 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
932 user = refresh_record(user)
933 {:ok, _} = CommonAPI.unpin(activity.id, user)
935 # recreates expiration job on unpin
936 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
939 test "ephemeral activity deletion job won't be deleted on pinning error", %{
943 clear_config([:instance, :max_pinned_statuses], 1)
945 {:ok, _activity} = CommonAPI.pin(activity.id, user)
947 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
949 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
951 user = refresh_record(user)
952 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
954 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
958 describe "mute tests" do
962 activity = insert(:note_activity)
964 [user: user, activity: activity]
967 test "marks notifications as read after mute" do
968 author = insert(:user)
969 activity = insert(:note_activity, user: author)
971 friend1 = insert(:user)
972 friend2 = insert(:user)
974 {:ok, reply_activity} =
978 status: "@#{author.nickname} @#{friend1.nickname} test reply",
979 in_reply_to_status_id: activity.id
983 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
984 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
986 assert Repo.aggregate(
987 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
991 unread_notifications =
992 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
994 assert Enum.any?(unread_notifications, fn n ->
995 n.type == "favourite" && n.activity_id == favorite_activity.id
998 assert Enum.any?(unread_notifications, fn n ->
999 n.type == "reblog" && n.activity_id == repeat_activity.id
1002 assert Enum.any?(unread_notifications, fn n ->
1003 n.type == "mention" && n.activity_id == reply_activity.id
1006 {:ok, _} = CommonAPI.add_mute(author, activity)
1007 assert CommonAPI.thread_muted?(author, activity)
1009 assert Repo.aggregate(
1010 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1014 read_notifications =
1015 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1017 assert Enum.any?(read_notifications, fn n ->
1018 n.type == "favourite" && n.activity_id == favorite_activity.id
1021 assert Enum.any?(read_notifications, fn n ->
1022 n.type == "reblog" && n.activity_id == repeat_activity.id
1025 assert Enum.any?(read_notifications, fn n ->
1026 n.type == "mention" && n.activity_id == reply_activity.id
1030 test "add mute", %{user: user, activity: activity} do
1031 {:ok, _} = CommonAPI.add_mute(user, activity)
1032 assert CommonAPI.thread_muted?(user, activity)
1035 test "add expiring mute", %{user: user, activity: activity} do
1036 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1037 assert CommonAPI.thread_muted?(user, activity)
1039 worker = Pleroma.Workers.MuteExpireWorker
1040 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1047 assert :ok = perform_job(worker, args)
1048 refute CommonAPI.thread_muted?(user, activity)
1051 test "remove mute", %{user: user, activity: activity} do
1052 CommonAPI.add_mute(user, activity)
1053 {:ok, _} = CommonAPI.remove_mute(user, activity)
1054 refute CommonAPI.thread_muted?(user, activity)
1057 test "remove mute by ids", %{user: user, activity: activity} do
1058 CommonAPI.add_mute(user, activity)
1059 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1060 refute CommonAPI.thread_muted?(user, activity)
1063 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1064 CommonAPI.add_mute(user, activity)
1065 {:error, _} = CommonAPI.add_mute(user, activity)
1069 describe "reports" do
1070 test "creates a report" do
1071 reporter = insert(:user)
1072 target_user = insert(:user)
1074 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1076 reporter_ap_id = reporter.ap_id
1077 target_ap_id = target_user.ap_id
1078 activity_ap_id = activity.data["id"]
1082 account_id: target_user.id,
1084 status_ids: [activity.id]
1089 "id" => activity_ap_id,
1090 "content" => "foobar",
1091 "published" => activity.object.data["published"],
1092 "actor" => AccountView.render("show.json", %{user: target_user})
1095 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1098 actor: ^reporter_ap_id,
1101 "content" => ^comment,
1102 "object" => [^target_ap_id, ^note_obj],
1108 test "updates report state" do
1109 [reporter, target_user] = insert_pair(:user)
1110 activity = insert(:note_activity, user: target_user)
1112 {:ok, %Activity{id: report_id}} =
1113 CommonAPI.report(reporter, %{
1114 account_id: target_user.id,
1115 comment: "I feel offended",
1116 status_ids: [activity.id]
1119 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1121 assert report.data["state"] == "resolved"
1123 [reported_user, activity_id] = report.data["object"]
1125 assert reported_user == target_user.ap_id
1126 assert activity_id == activity.data["id"]
1129 test "does not update report state when state is unsupported" do
1130 [reporter, target_user] = insert_pair(:user)
1131 activity = insert(:note_activity, user: target_user)
1133 {:ok, %Activity{id: report_id}} =
1134 CommonAPI.report(reporter, %{
1135 account_id: target_user.id,
1136 comment: "I feel offended",
1137 status_ids: [activity.id]
1140 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1143 test "updates state of multiple reports" do
1144 [reporter, target_user] = insert_pair(:user)
1145 activity = insert(:note_activity, user: target_user)
1147 {:ok, %Activity{id: first_report_id}} =
1148 CommonAPI.report(reporter, %{
1149 account_id: target_user.id,
1150 comment: "I feel offended",
1151 status_ids: [activity.id]
1154 {:ok, %Activity{id: second_report_id}} =
1155 CommonAPI.report(reporter, %{
1156 account_id: target_user.id,
1157 comment: "I feel very offended!",
1158 status_ids: [activity.id]
1162 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1164 first_report = Activity.get_by_id(first_report_id)
1165 second_report = Activity.get_by_id(second_report_id)
1167 assert report_ids -- [first_report_id, second_report_id] == []
1168 assert first_report.data["state"] == "resolved"
1169 assert second_report.data["state"] == "resolved"
1173 describe "reblog muting" do
1175 muter = insert(:user)
1177 muted = insert(:user)
1179 [muter: muter, muted: muted]
1182 test "add a reblog mute", %{muter: muter, muted: muted} do
1183 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1185 assert User.showing_reblogs?(muter, muted) == false
1188 test "remove a reblog mute", %{muter: muter, muted: muted} do
1189 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1190 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1192 assert User.showing_reblogs?(muter, muted) == true
1196 describe "follow/2" do
1197 test "directly follows a non-locked local user" do
1198 [follower, followed] = insert_pair(:user)
1199 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1201 assert User.following?(follower, followed)
1205 describe "unfollow/2" do
1206 test "also unsubscribes a user" do
1207 [follower, followed] = insert_pair(:user)
1208 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1209 {:ok, _subscription} = User.subscribe(follower, followed)
1211 assert User.subscribed_to?(follower, followed)
1213 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1215 refute User.subscribed_to?(follower, followed)
1218 test "cancels a pending follow for a local user" do
1219 follower = insert(:user)
1220 followed = insert(:user, is_locked: true)
1222 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1223 CommonAPI.follow(follower, followed)
1225 assert User.get_follow_state(follower, followed) == :follow_pending
1226 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1227 assert User.get_follow_state(follower, followed) == nil
1229 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1230 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1235 "object" => %{"type" => "Follow", "state" => "cancelled"}
1237 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1240 test "cancels a pending follow for a remote user" do
1241 follower = insert(:user)
1242 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1244 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1245 CommonAPI.follow(follower, followed)
1247 assert User.get_follow_state(follower, followed) == :follow_pending
1248 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1249 assert User.get_follow_state(follower, followed) == nil
1251 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1252 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1257 "object" => %{"type" => "Follow", "state" => "cancelled"}
1259 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1263 describe "accept_follow_request/2" do
1264 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1265 user = insert(:user, is_locked: true)
1266 follower = insert(:user)
1267 follower_two = insert(:user)
1269 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1270 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1271 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1273 assert follow_activity.data["state"] == "pending"
1274 assert follow_activity_two.data["state"] == "pending"
1275 assert follow_activity_three.data["state"] == "pending"
1277 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1279 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1280 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1281 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1284 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1285 user = insert(:user, is_locked: true)
1286 follower = insert(:user)
1287 follower_two = insert(:user)
1289 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1290 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1291 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1293 assert follow_activity.data["state"] == "pending"
1294 assert follow_activity_two.data["state"] == "pending"
1295 assert follow_activity_three.data["state"] == "pending"
1297 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1299 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1300 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1301 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1304 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1305 user = insert(:user, is_locked: true)
1306 not_follower = insert(:user)
1307 CommonAPI.accept_follow_request(not_follower, user)
1309 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1313 describe "vote/3" do
1314 test "does not allow to vote twice" do
1315 user = insert(:user)
1316 other_user = insert(:user)
1319 CommonAPI.post(user, %{
1320 status: "Am I cute?",
1321 poll: %{options: ["Yes", "No"], expires_in: 20}
1324 object = Object.normalize(activity, fetch: false)
1326 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1328 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1332 describe "listen/2" do
1333 test "returns a valid activity" do
1334 user = insert(:user)
1337 CommonAPI.listen(user, %{
1338 title: "lain radio episode 1",
1339 album: "lain radio",
1344 object = Object.normalize(activity, fetch: false)
1346 assert object.data["title"] == "lain radio episode 1"
1348 assert Visibility.get_visibility(activity) == "public"
1351 test "respects visibility=private" do
1352 user = insert(:user)
1355 CommonAPI.listen(user, %{
1356 title: "lain radio episode 1",
1357 album: "lain radio",
1360 visibility: "private"
1363 object = Object.normalize(activity, fetch: false)
1365 assert object.data["title"] == "lain radio episode 1"
1367 assert Visibility.get_visibility(activity) == "private"
1371 describe "get_user/1" do
1372 test "gets user by ap_id" do
1373 user = insert(:user)
1374 assert CommonAPI.get_user(user.ap_id) == user
1377 test "gets user by guessed nickname" do
1378 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1379 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1386 nickname: "erroruser@example.com"
1387 } = CommonAPI.get_user("")
1391 describe "with `local` visibility" do
1392 setup do: clear_config([:instance, :federating], true)
1395 user = insert(:user)
1397 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1398 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1400 assert Visibility.is_local_public?(activity)
1401 assert_not_called(Pleroma.Web.Federator.publish(activity))
1406 user = insert(:user)
1408 {:ok, %Activity{id: activity_id}} =
1409 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1411 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1412 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1413 CommonAPI.delete(activity_id, user)
1415 assert Visibility.is_local_public?(activity)
1416 assert_not_called(Pleroma.Web.Federator.publish(activity))
1421 user = insert(:user)
1422 other_user = insert(:user)
1424 {:ok, %Activity{id: activity_id}} =
1425 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1427 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1428 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1429 CommonAPI.repeat(activity_id, user)
1431 assert Visibility.is_local_public?(activity)
1432 refute called(Pleroma.Web.Federator.publish(activity))
1437 user = insert(:user)
1438 other_user = insert(:user)
1440 {:ok, %Activity{id: activity_id}} =
1441 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1443 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1445 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1446 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1447 CommonAPI.unrepeat(activity_id, user)
1449 assert Visibility.is_local_public?(activity)
1450 refute called(Pleroma.Web.Federator.publish(activity))
1455 user = insert(:user)
1456 other_user = insert(:user)
1458 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1460 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1461 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1462 CommonAPI.favorite(user, activity.id)
1464 assert Visibility.is_local_public?(activity)
1465 refute called(Pleroma.Web.Federator.publish(activity))
1469 test "unfavorite" do
1470 user = insert(:user)
1471 other_user = insert(:user)
1473 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1475 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1477 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1478 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1479 assert Visibility.is_local_public?(activity)
1480 refute called(Pleroma.Web.Federator.publish(activity))
1484 test "react_with_emoji" do
1485 user = insert(:user)
1486 other_user = insert(:user)
1487 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1489 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1490 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1491 CommonAPI.react_with_emoji(activity.id, user, "👍")
1493 assert Visibility.is_local_public?(activity)
1494 refute called(Pleroma.Web.Federator.publish(activity))
1498 test "unreact_with_emoji" do
1499 user = insert(:user)
1500 other_user = insert(:user)
1501 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1503 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1505 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1506 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1507 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1509 assert Visibility.is_local_public?(activity)
1510 refute called(Pleroma.Web.Federator.publish(activity))