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 clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
31 clear_config([Pleroma.Uploaders.Local, :uploads], "uploads")
32 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
36 setup do: clear_config([:instance, :safe_dm_mentions])
37 setup do: clear_config([:instance, :limit])
38 setup do: clear_config([:instance, :max_pinned_statuses])
40 describe "posting polls" do
41 test "it posts a poll" do
45 CommonAPI.post(user, %{
46 status: "who is the best",
47 poll: %{expires_in: 600, options: ["reimu", "marisa"]}
50 object = Object.normalize(activity, fetch: false)
52 assert object.data["type"] == "Question"
53 assert object.data["oneOf"] |> length() == 2
57 args: %{op: "poll_end", activity_id: activity.id},
58 scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
63 describe "blocking" do
65 blocker = insert(:user)
66 blocked = insert(:user)
67 User.follow(blocker, blocked)
68 User.follow(blocked, blocker)
69 %{blocker: blocker, blocked: blocked}
72 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
73 clear_config([:instance, :federating], true)
75 with_mock Pleroma.Web.Federator,
76 publish: fn _ -> nil end do
77 assert {:ok, block} = CommonAPI.block(blocker, blocked)
80 assert User.blocks?(blocker, blocked)
81 refute User.following?(blocker, blocked)
82 refute User.following?(blocked, blocker)
84 assert called(Pleroma.Web.Federator.publish(block))
88 test "it blocks and does not federate if outgoing blocks are disabled", %{
92 clear_config([:instance, :federating], true)
93 clear_config([:activitypub, :outgoing_blocks], false)
95 with_mock Pleroma.Web.Federator,
96 publish: fn _ -> nil end do
97 assert {:ok, block} = CommonAPI.block(blocker, blocked)
100 assert User.blocks?(blocker, blocked)
101 refute User.following?(blocker, blocked)
102 refute User.following?(blocked, blocker)
104 refute called(Pleroma.Web.Federator.publish(block))
109 describe "posting chat messages" do
110 setup do: clear_config([:instance, :chat_limit])
112 test "it posts a self-chat" do
113 author = insert(:user)
117 CommonAPI.post_chat_message(
120 "remember to buy milk when milk truk arive"
123 assert activity.data["type"] == "Create"
126 test "it posts a chat message without content but with an attachment" do
127 author = insert(:user)
128 recipient = insert(:user)
131 content_type: "image/jpeg",
132 path: Path.absname("test/fixtures/image.jpg"),
133 filename: "an_image.jpg"
136 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
140 Pleroma.Web.Streamer,
152 send: fn _ -> nil end
157 CommonAPI.post_chat_message(
165 Notification.for_user_and_activity(recipient, activity)
166 |> Repo.preload(:activity)
168 assert called(Pleroma.Web.Push.send(notification))
169 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
170 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
176 test "it adds html newlines" do
177 author = insert(:user)
178 recipient = insert(:user)
180 other_user = insert(:user)
183 CommonAPI.post_chat_message(
189 assert other_user.ap_id not in activity.recipients
191 object = Object.normalize(activity, fetch: false)
193 assert object.data["content"] == "uguu<br/>uguuu"
196 test "it linkifies" do
197 author = insert(:user)
198 recipient = insert(:user)
200 other_user = insert(:user)
203 CommonAPI.post_chat_message(
206 "https://example.org is the site of @#{other_user.nickname} #2hu"
209 assert other_user.ap_id not in activity.recipients
211 object = Object.normalize(activity, fetch: false)
213 assert object.data["content"] ==
214 "<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>"
217 test "it posts a chat message" do
218 author = insert(:user)
219 recipient = insert(:user)
222 CommonAPI.post_chat_message(
225 "a test message <script>alert('uuu')</script> :firefox:"
228 assert activity.data["type"] == "Create"
229 assert activity.local
230 object = Object.normalize(activity, fetch: false)
232 assert object.data["type"] == "ChatMessage"
233 assert object.data["to"] == [recipient.ap_id]
235 assert object.data["content"] ==
236 "a test message <script>alert('uuu')</script> :firefox:"
238 assert object.data["emoji"] == %{
239 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
242 assert Chat.get(author.id, recipient.ap_id)
243 assert Chat.get(recipient.id, author.ap_id)
245 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
248 test "it reject messages over the local limit" do
249 clear_config([:instance, :chat_limit], 2)
251 author = insert(:user)
252 recipient = insert(:user)
255 CommonAPI.post_chat_message(
261 assert message == :content_too_long
264 test "it reject messages via MRF" do
265 clear_config([:mrf_keyword, :reject], ["GNO"])
266 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
268 author = insert(:user)
269 recipient = insert(:user)
271 assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
272 CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
276 describe "unblocking" do
277 test "it works even without an existing block activity" do
278 blocked = insert(:user)
279 blocker = insert(:user)
280 User.block(blocker, blocked)
282 assert User.blocks?(blocker, blocked)
283 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
284 refute User.blocks?(blocker, blocked)
288 describe "deletion" do
289 test "it works with pruned objects" do
292 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
294 clear_config([:instance, :federating], true)
296 Object.normalize(post, fetch: false)
299 with_mock Pleroma.Web.Federator,
300 publish: fn _ -> nil end do
301 assert {:ok, delete} = CommonAPI.delete(post.id, user)
303 assert called(Pleroma.Web.Federator.publish(delete))
306 refute Activity.get_by_id(post.id)
309 test "it allows users to delete their posts" do
312 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
314 clear_config([:instance, :federating], true)
316 with_mock Pleroma.Web.Federator,
317 publish: fn _ -> nil end do
318 assert {:ok, delete} = CommonAPI.delete(post.id, user)
320 assert called(Pleroma.Web.Federator.publish(delete))
323 refute Activity.get_by_id(post.id)
326 test "it does not allow a user to delete their posts" do
328 other_user = insert(:user)
330 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
332 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
333 assert Activity.get_by_id(post.id)
336 test "it allows moderators to delete other user's posts" do
338 moderator = insert(:user, is_moderator: true)
340 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
342 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
345 refute Activity.get_by_id(post.id)
348 test "it allows admins to delete other user's posts" do
350 moderator = insert(:user, is_admin: true)
352 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
354 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
357 refute Activity.get_by_id(post.id)
360 test "superusers deleting non-local posts won't federate the delete" do
361 # This is the user of the ingested activity
365 ap_id: "http://mastodon.example.org/users/admin",
366 last_refreshed_at: NaiveDateTime.utc_now()
369 moderator = insert(:user, is_admin: true)
372 File.read!("test/fixtures/mastodon-post-activity.json")
375 {:ok, post} = Transmogrifier.handle_incoming(data)
377 with_mock Pleroma.Web.Federator,
378 publish: fn _ -> nil end do
379 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
381 refute called(Pleroma.Web.Federator.publish(:_))
384 refute Activity.get_by_id(post.id)
388 test "favoriting race condition" do
390 users_serial = insert_list(10, :user)
391 users = insert_list(10, :user)
393 {:ok, activity} = CommonAPI.post(user, %{status: "."})
396 |> Enum.map(fn user ->
397 CommonAPI.favorite(user, activity.id)
400 object = Object.get_by_ap_id(activity.data["object"])
401 assert object.data["like_count"] == 10
404 |> Enum.map(fn user ->
406 CommonAPI.favorite(user, activity.id)
409 |> Enum.map(&Task.await/1)
411 object = Object.get_by_ap_id(activity.data["object"])
412 assert object.data["like_count"] == 20
415 test "repeating race condition" do
417 users_serial = insert_list(10, :user)
418 users = insert_list(10, :user)
420 {:ok, activity} = CommonAPI.post(user, %{status: "."})
423 |> Enum.map(fn user ->
424 CommonAPI.repeat(activity.id, user)
427 object = Object.get_by_ap_id(activity.data["object"])
428 assert object.data["announcement_count"] == 10
431 |> Enum.map(fn user ->
433 CommonAPI.repeat(activity.id, user)
436 |> Enum.map(&Task.await/1)
438 object = Object.get_by_ap_id(activity.data["object"])
439 assert object.data["announcement_count"] == 20
442 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
444 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
446 [participation] = Participation.for_user(user)
449 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
451 assert Visibility.is_direct?(convo_reply)
453 assert activity.data["context"] == convo_reply.data["context"]
456 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
458 jafnhar = insert(:user)
459 tridi = insert(:user)
462 CommonAPI.post(har, %{
463 status: "@#{jafnhar.nickname} hey",
467 assert har.ap_id in activity.recipients
468 assert jafnhar.ap_id in activity.recipients
470 [participation] = Participation.for_user(har)
473 CommonAPI.post(har, %{
474 status: "I don't really like @#{tridi.nickname}",
475 visibility: "direct",
476 in_reply_to_status_id: activity.id,
477 in_reply_to_conversation_id: participation.id
480 assert har.ap_id in activity.recipients
481 assert jafnhar.ap_id in activity.recipients
482 refute tridi.ap_id in activity.recipients
485 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
487 jafnhar = insert(:user)
488 tridi = insert(:user)
490 clear_config([:instance, :safe_dm_mentions], true)
493 CommonAPI.post(har, %{
494 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
498 refute tridi.ap_id in activity.recipients
499 assert jafnhar.ap_id in activity.recipients
502 test "it de-duplicates tags" do
504 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
506 object = Object.normalize(activity, fetch: false)
508 assert Object.tags(object) == ["2hu"]
511 test "it adds emoji in the object" do
513 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
515 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
518 describe "posting" do
519 test "it adds an emoji on an external site" do
521 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
523 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
524 assert url == "https://example.com/emoji.png"
526 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
528 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
529 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
532 test "it copies emoji from the subject of the parent post" do
535 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
539 activity = Activity.get_create_by_object_ap_id(object.data["id"])
542 {:ok, reply_activity} =
543 CommonAPI.post(user, %{
544 in_reply_to_id: activity.id,
545 status: ":joker_disapprove:",
546 spoiler_text: ":joker_smile:"
549 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
550 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
553 test "deactivated users can't post" do
554 user = insert(:user, is_active: false)
555 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
558 test "it supports explicit addressing" do
560 user_two = insert(:user)
561 user_three = insert(:user)
562 user_four = insert(:user)
565 CommonAPI.post(user, %{
567 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
568 to: [user_two.nickname, user_four.nickname, "nonexistent"]
571 assert user.ap_id in activity.recipients
572 assert user_two.ap_id in activity.recipients
573 assert user_four.ap_id in activity.recipients
574 refute user_three.ap_id in activity.recipients
577 test "it filters out obviously bad tags when accepting a post as HTML" do
580 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
583 CommonAPI.post(user, %{
585 content_type: "text/html"
588 object = Object.normalize(activity, fetch: false)
590 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
592 assert object.data["source"] == %{
593 "mediaType" => "text/html",
598 test "it filters out obviously bad tags when accepting a post as Markdown" do
601 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
604 CommonAPI.post(user, %{
606 content_type: "text/markdown"
609 object = Object.normalize(activity, fetch: false)
611 assert object.data["content"] == "<p><b>2hu</b></p>"
613 assert object.data["source"] == %{
614 "mediaType" => "text/markdown",
619 test "it does not allow replies to direct messages that are not direct messages themselves" do
622 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
625 CommonAPI.post(user, %{
627 visibility: "direct",
628 in_reply_to_status_id: activity.id
631 Enum.each(["public", "private", "unlisted"], fn visibility ->
632 assert {:error, "The message visibility must be direct"} =
633 CommonAPI.post(user, %{
635 visibility: visibility,
636 in_reply_to_status_id: activity.id
641 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
643 other_user = insert(:user)
644 third_user = insert(:user)
646 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
649 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
651 # The OP is implicitly added
652 assert user.ap_id in open_answer.recipients
654 {:ok, secret_answer} =
655 CommonAPI.post(other_user, %{
656 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
657 in_reply_to_status_id: post.id,
661 assert third_user.ap_id in secret_answer.recipients
663 # The OP is not added
664 refute user.ap_id in secret_answer.recipients
667 test "it allows to address a list" do
669 {:ok, list} = Pleroma.List.create("foo", user)
671 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
673 assert activity.data["bcc"] == [list.ap_id]
674 assert activity.recipients == [list.ap_id, user.ap_id]
675 assert activity.data["listMessage"] == list.ap_id
678 test "it returns error when status is empty and no attachments" do
681 assert {:error, "Cannot post an empty status without attachments"} =
682 CommonAPI.post(user, %{status: ""})
685 test "it validates character limits are correctly enforced" do
686 clear_config([:instance, :limit], 5)
690 assert {:error, "The status is over the character limit"} =
691 CommonAPI.post(user, %{status: "foobar"})
693 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
696 test "it can handle activities that expire" do
699 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
701 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
704 worker: Pleroma.Workers.PurgeExpiredActivity,
705 args: %{activity_id: activity.id},
706 scheduled_at: expires_at
711 describe "reactions" do
712 test "reacting to a status with an emoji" do
714 other_user = insert(:user)
716 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
718 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
720 assert reaction.data["actor"] == user.ap_id
721 assert reaction.data["content"] == "👍"
723 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
725 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
728 test "unreacting to a status with an emoji" do
730 other_user = insert(:user)
732 clear_config([:instance, :federating], true)
734 with_mock Pleroma.Web.Federator,
735 publish: fn _ -> nil end do
736 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
737 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
739 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
741 assert unreaction.data["type"] == "Undo"
742 assert unreaction.data["object"] == reaction.data["id"]
743 assert unreaction.local
745 # On federation, it contains the undone (and deleted) object
746 unreaction_with_object = %{
748 | data: Map.put(unreaction.data, "object", reaction.data)
751 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
755 test "repeating a status" do
757 other_user = insert(:user)
759 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
761 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
762 assert Visibility.is_public?(announce_activity)
765 test "can't repeat a repeat" do
767 other_user = insert(:user)
768 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
770 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
772 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
775 test "repeating a status privately" do
777 other_user = insert(:user)
779 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
781 {:ok, %Activity{} = announce_activity} =
782 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
784 assert Visibility.is_private?(announce_activity)
785 refute Visibility.visible_for_user?(announce_activity, nil)
788 test "author can repeat own private statuses" do
789 author = insert(:user)
790 follower = insert(:user)
791 CommonAPI.follow(follower, author)
793 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
795 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
797 assert Visibility.is_private?(announce_activity)
798 refute Visibility.visible_for_user?(announce_activity, nil)
800 assert Visibility.visible_for_user?(activity, follower)
801 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
804 test "favoriting a status" do
806 other_user = insert(:user)
808 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
810 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
811 assert data["type"] == "Like"
812 assert data["actor"] == user.ap_id
813 assert data["object"] == post_activity.data["object"]
816 test "retweeting a status twice returns the status" do
818 other_user = insert(:user)
820 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
821 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
822 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
825 test "favoriting a status twice returns ok, but without the like activity" do
827 other_user = insert(:user)
829 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
830 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
831 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
835 describe "pinned statuses" do
837 clear_config([:instance, :max_pinned_statuses], 1)
840 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
842 [user: user, activity: activity]
845 test "activity not found error", %{user: user} do
846 assert {:error, :not_found} = CommonAPI.pin("id", user)
849 test "pin status", %{user: user, activity: activity} do
850 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
852 %{data: %{"id" => object_id}} = Object.normalize(activity)
853 user = refresh_record(user)
855 assert user.pinned_objects |> Map.keys() == [object_id]
858 test "pin poll", %{user: user} do
860 CommonAPI.post(user, %{
861 status: "How is fediverse today?",
862 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
865 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
867 %{data: %{"id" => object_id}} = Object.normalize(activity)
869 user = refresh_record(user)
871 assert user.pinned_objects |> Map.keys() == [object_id]
874 test "unlisted statuses can be pinned", %{user: user} do
875 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
876 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
879 test "only self-authored can be pinned", %{activity: activity} do
882 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
885 test "max pinned statuses", %{user: user, activity: activity_one} do
886 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
888 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
890 user = refresh_record(user)
892 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
895 test "only public can be pinned", %{user: user} do
896 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
897 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
900 test "unpin status", %{user: user, activity: activity} do
901 {:ok, activity} = CommonAPI.pin(activity.id, user)
903 user = refresh_record(user)
907 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
909 user = refresh_record(user)
911 assert user.pinned_objects == %{}
914 test "should unpin when deleting a status", %{user: user, activity: activity} do
915 {:ok, activity} = CommonAPI.pin(activity.id, user)
917 user = refresh_record(user)
919 assert {:ok, _} = CommonAPI.delete(activity.id, user)
921 user = refresh_record(user)
923 assert user.pinned_objects == %{}
926 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
927 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
929 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
931 {:ok, _activity} = CommonAPI.pin(activity.id, user)
932 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
934 user = refresh_record(user)
935 {:ok, _} = CommonAPI.unpin(activity.id, user)
937 # recreates expiration job on unpin
938 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
941 test "ephemeral activity deletion job won't be deleted on pinning error", %{
945 clear_config([:instance, :max_pinned_statuses], 1)
947 {:ok, _activity} = CommonAPI.pin(activity.id, user)
949 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
951 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
953 user = refresh_record(user)
954 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
956 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
960 describe "mute tests" do
964 activity = insert(:note_activity)
966 [user: user, activity: activity]
969 test "marks notifications as read after mute" do
970 author = insert(:user)
971 activity = insert(:note_activity, user: author)
973 friend1 = insert(:user)
974 friend2 = insert(:user)
976 {:ok, reply_activity} =
980 status: "@#{author.nickname} @#{friend1.nickname} test reply",
981 in_reply_to_status_id: activity.id
985 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
986 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
988 assert Repo.aggregate(
989 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
993 unread_notifications =
994 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
996 assert Enum.any?(unread_notifications, fn n ->
997 n.type == "favourite" && n.activity_id == favorite_activity.id
1000 assert Enum.any?(unread_notifications, fn n ->
1001 n.type == "reblog" && n.activity_id == repeat_activity.id
1004 assert Enum.any?(unread_notifications, fn n ->
1005 n.type == "mention" && n.activity_id == reply_activity.id
1008 {:ok, _} = CommonAPI.add_mute(author, activity)
1009 assert CommonAPI.thread_muted?(author, activity)
1011 assert Repo.aggregate(
1012 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1016 read_notifications =
1017 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1019 assert Enum.any?(read_notifications, fn n ->
1020 n.type == "favourite" && n.activity_id == favorite_activity.id
1023 assert Enum.any?(read_notifications, fn n ->
1024 n.type == "reblog" && n.activity_id == repeat_activity.id
1027 assert Enum.any?(read_notifications, fn n ->
1028 n.type == "mention" && n.activity_id == reply_activity.id
1032 test "add mute", %{user: user, activity: activity} do
1033 {:ok, _} = CommonAPI.add_mute(user, activity)
1034 assert CommonAPI.thread_muted?(user, activity)
1037 test "add expiring mute", %{user: user, activity: activity} do
1038 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1039 assert CommonAPI.thread_muted?(user, activity)
1041 worker = Pleroma.Workers.MuteExpireWorker
1042 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1049 assert :ok = perform_job(worker, args)
1050 refute CommonAPI.thread_muted?(user, activity)
1053 test "remove mute", %{user: user, activity: activity} do
1054 CommonAPI.add_mute(user, activity)
1055 {:ok, _} = CommonAPI.remove_mute(user, activity)
1056 refute CommonAPI.thread_muted?(user, activity)
1059 test "remove mute by ids", %{user: user, activity: activity} do
1060 CommonAPI.add_mute(user, activity)
1061 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1062 refute CommonAPI.thread_muted?(user, activity)
1065 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1066 CommonAPI.add_mute(user, activity)
1067 {:error, _} = CommonAPI.add_mute(user, activity)
1071 describe "reports" do
1072 test "creates a report" do
1073 reporter = insert(:user)
1074 target_user = insert(:user)
1076 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1078 reporter_ap_id = reporter.ap_id
1079 target_ap_id = target_user.ap_id
1080 activity_ap_id = activity.data["id"]
1084 account_id: target_user.id,
1086 status_ids: [activity.id]
1091 "id" => activity_ap_id,
1092 "content" => "foobar",
1093 "published" => activity.object.data["published"],
1094 "actor" => AccountView.render("show.json", %{user: target_user})
1097 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1100 actor: ^reporter_ap_id,
1103 "content" => ^comment,
1104 "object" => [^target_ap_id, ^note_obj],
1110 test "updates report state" do
1111 [reporter, target_user] = insert_pair(:user)
1112 activity = insert(:note_activity, user: target_user)
1114 {:ok, %Activity{id: report_id}} =
1115 CommonAPI.report(reporter, %{
1116 account_id: target_user.id,
1117 comment: "I feel offended",
1118 status_ids: [activity.id]
1121 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1123 assert report.data["state"] == "resolved"
1125 [reported_user, activity_id] = report.data["object"]
1127 assert reported_user == target_user.ap_id
1128 assert activity_id == activity.data["id"]
1131 test "does not update report state when state is unsupported" do
1132 [reporter, target_user] = insert_pair(:user)
1133 activity = insert(:note_activity, user: target_user)
1135 {:ok, %Activity{id: report_id}} =
1136 CommonAPI.report(reporter, %{
1137 account_id: target_user.id,
1138 comment: "I feel offended",
1139 status_ids: [activity.id]
1142 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1145 test "updates state of multiple reports" do
1146 [reporter, target_user] = insert_pair(:user)
1147 activity = insert(:note_activity, user: target_user)
1149 {:ok, %Activity{id: first_report_id}} =
1150 CommonAPI.report(reporter, %{
1151 account_id: target_user.id,
1152 comment: "I feel offended",
1153 status_ids: [activity.id]
1156 {:ok, %Activity{id: second_report_id}} =
1157 CommonAPI.report(reporter, %{
1158 account_id: target_user.id,
1159 comment: "I feel very offended!",
1160 status_ids: [activity.id]
1164 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1166 first_report = Activity.get_by_id(first_report_id)
1167 second_report = Activity.get_by_id(second_report_id)
1169 assert report_ids -- [first_report_id, second_report_id] == []
1170 assert first_report.data["state"] == "resolved"
1171 assert second_report.data["state"] == "resolved"
1175 describe "reblog muting" do
1177 muter = insert(:user)
1179 muted = insert(:user)
1181 [muter: muter, muted: muted]
1184 test "add a reblog mute", %{muter: muter, muted: muted} do
1185 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1187 assert User.showing_reblogs?(muter, muted) == false
1190 test "remove a reblog mute", %{muter: muter, muted: muted} do
1191 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1192 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1194 assert User.showing_reblogs?(muter, muted) == true
1198 describe "follow/2" do
1199 test "directly follows a non-locked local user" do
1200 [follower, followed] = insert_pair(:user)
1201 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1203 assert User.following?(follower, followed)
1207 describe "unfollow/2" do
1208 test "also unsubscribes a user" do
1209 [follower, followed] = insert_pair(:user)
1210 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1211 {:ok, _subscription} = User.subscribe(follower, followed)
1213 assert User.subscribed_to?(follower, followed)
1215 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1217 refute User.subscribed_to?(follower, followed)
1220 test "cancels a pending follow for a local user" do
1221 follower = insert(:user)
1222 followed = insert(:user, is_locked: true)
1224 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1225 CommonAPI.follow(follower, followed)
1227 assert User.get_follow_state(follower, followed) == :follow_pending
1228 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1229 assert User.get_follow_state(follower, followed) == nil
1231 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1232 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1237 "object" => %{"type" => "Follow", "state" => "cancelled"}
1239 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1242 test "cancels a pending follow for a remote user" do
1243 follower = insert(:user)
1244 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1246 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1247 CommonAPI.follow(follower, followed)
1249 assert User.get_follow_state(follower, followed) == :follow_pending
1250 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1251 assert User.get_follow_state(follower, followed) == nil
1253 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1254 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1259 "object" => %{"type" => "Follow", "state" => "cancelled"}
1261 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1265 describe "accept_follow_request/2" do
1266 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1267 user = insert(:user, is_locked: true)
1268 follower = insert(:user)
1269 follower_two = insert(:user)
1271 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1272 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1273 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1275 assert follow_activity.data["state"] == "pending"
1276 assert follow_activity_two.data["state"] == "pending"
1277 assert follow_activity_three.data["state"] == "pending"
1279 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1281 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1282 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1283 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1286 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1287 user = insert(:user, is_locked: true)
1288 follower = insert(:user)
1289 follower_two = insert(:user)
1291 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1292 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1293 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1295 assert follow_activity.data["state"] == "pending"
1296 assert follow_activity_two.data["state"] == "pending"
1297 assert follow_activity_three.data["state"] == "pending"
1299 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1301 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1302 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1303 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1306 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1307 user = insert(:user, is_locked: true)
1308 not_follower = insert(:user)
1309 CommonAPI.accept_follow_request(not_follower, user)
1311 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1315 describe "vote/3" do
1316 test "does not allow to vote twice" do
1317 user = insert(:user)
1318 other_user = insert(:user)
1321 CommonAPI.post(user, %{
1322 status: "Am I cute?",
1323 poll: %{options: ["Yes", "No"], expires_in: 20}
1326 object = Object.normalize(activity, fetch: false)
1328 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1330 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1334 describe "listen/2" do
1335 test "returns a valid activity" do
1336 user = insert(:user)
1339 CommonAPI.listen(user, %{
1340 title: "lain radio episode 1",
1341 album: "lain radio",
1346 object = Object.normalize(activity, fetch: false)
1348 assert object.data["title"] == "lain radio episode 1"
1350 assert Visibility.get_visibility(activity) == "public"
1353 test "respects visibility=private" do
1354 user = insert(:user)
1357 CommonAPI.listen(user, %{
1358 title: "lain radio episode 1",
1359 album: "lain radio",
1362 visibility: "private"
1365 object = Object.normalize(activity, fetch: false)
1367 assert object.data["title"] == "lain radio episode 1"
1369 assert Visibility.get_visibility(activity) == "private"
1373 describe "get_user/1" do
1374 test "gets user by ap_id" do
1375 user = insert(:user)
1376 assert CommonAPI.get_user(user.ap_id) == user
1379 test "gets user by guessed nickname" do
1380 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1381 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1388 nickname: "erroruser@example.com"
1389 } = CommonAPI.get_user("")
1393 describe "with `local` visibility" do
1394 setup do: clear_config([:instance, :federating], true)
1397 user = insert(:user)
1399 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1400 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1402 assert Visibility.is_local_public?(activity)
1403 assert_not_called(Pleroma.Web.Federator.publish(activity))
1408 user = insert(:user)
1410 {:ok, %Activity{id: activity_id}} =
1411 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1413 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1414 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1415 CommonAPI.delete(activity_id, user)
1417 assert Visibility.is_local_public?(activity)
1418 assert_not_called(Pleroma.Web.Federator.publish(activity))
1423 user = insert(:user)
1424 other_user = insert(:user)
1426 {:ok, %Activity{id: activity_id}} =
1427 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1429 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1430 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1431 CommonAPI.repeat(activity_id, user)
1433 assert Visibility.is_local_public?(activity)
1434 refute called(Pleroma.Web.Federator.publish(activity))
1439 user = insert(:user)
1440 other_user = insert(:user)
1442 {:ok, %Activity{id: activity_id}} =
1443 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1445 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1447 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1448 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1449 CommonAPI.unrepeat(activity_id, user)
1451 assert Visibility.is_local_public?(activity)
1452 refute called(Pleroma.Web.Federator.publish(activity))
1457 user = insert(:user)
1458 other_user = insert(:user)
1460 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1462 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1463 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1464 CommonAPI.favorite(user, activity.id)
1466 assert Visibility.is_local_public?(activity)
1467 refute called(Pleroma.Web.Federator.publish(activity))
1471 test "unfavorite" do
1472 user = insert(:user)
1473 other_user = insert(:user)
1475 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1477 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1479 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1480 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1481 assert Visibility.is_local_public?(activity)
1482 refute called(Pleroma.Web.Federator.publish(activity))
1486 test "react_with_emoji" do
1487 user = insert(:user)
1488 other_user = insert(:user)
1489 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1491 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1492 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1493 CommonAPI.react_with_emoji(activity.id, user, "👍")
1495 assert Visibility.is_local_public?(activity)
1496 refute called(Pleroma.Web.Federator.publish(activity))
1500 test "unreact_with_emoji" do
1501 user = insert(:user)
1502 other_user = insert(:user)
1503 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1505 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1507 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1508 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1509 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1511 assert Visibility.is_local_public?(activity)
1512 refute called(Pleroma.Web.Federator.publish(activity))