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=\"#{
214 }\" 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')"
591 assert object.data["source"] == post
594 test "it filters out obviously bad tags when accepting a post as Markdown" do
597 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
600 CommonAPI.post(user, %{
602 content_type: "text/markdown"
605 object = Object.normalize(activity, fetch: false)
607 assert object.data["content"] == "<p><b>2hu</b></p>"
608 assert object.data["source"] == post
611 test "it does not allow replies to direct messages that are not direct messages themselves" do
614 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
617 CommonAPI.post(user, %{
619 visibility: "direct",
620 in_reply_to_status_id: activity.id
623 Enum.each(["public", "private", "unlisted"], fn visibility ->
624 assert {:error, "The message visibility must be direct"} =
625 CommonAPI.post(user, %{
627 visibility: visibility,
628 in_reply_to_status_id: activity.id
633 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
635 other_user = insert(:user)
636 third_user = insert(:user)
638 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
641 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
643 # The OP is implicitly added
644 assert user.ap_id in open_answer.recipients
646 {:ok, secret_answer} =
647 CommonAPI.post(other_user, %{
648 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
649 in_reply_to_status_id: post.id,
653 assert third_user.ap_id in secret_answer.recipients
655 # The OP is not added
656 refute user.ap_id in secret_answer.recipients
659 test "it allows to address a list" do
661 {:ok, list} = Pleroma.List.create("foo", user)
663 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
665 assert activity.data["bcc"] == [list.ap_id]
666 assert activity.recipients == [list.ap_id, user.ap_id]
667 assert activity.data["listMessage"] == list.ap_id
670 test "it returns error when status is empty and no attachments" do
673 assert {:error, "Cannot post an empty status without attachments"} =
674 CommonAPI.post(user, %{status: ""})
677 test "it validates character limits are correctly enforced" do
678 clear_config([:instance, :limit], 5)
682 assert {:error, "The status is over the character limit"} =
683 CommonAPI.post(user, %{status: "foobar"})
685 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
688 test "it can handle activities that expire" do
691 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
693 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
696 worker: Pleroma.Workers.PurgeExpiredActivity,
697 args: %{activity_id: activity.id},
698 scheduled_at: expires_at
703 describe "reactions" do
704 test "reacting to a status with an emoji" do
706 other_user = insert(:user)
708 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
710 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
712 assert reaction.data["actor"] == user.ap_id
713 assert reaction.data["content"] == "👍"
715 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
717 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
720 test "unreacting to a status with an emoji" do
722 other_user = insert(:user)
724 clear_config([:instance, :federating], true)
726 with_mock Pleroma.Web.Federator,
727 publish: fn _ -> nil end do
728 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
729 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
731 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
733 assert unreaction.data["type"] == "Undo"
734 assert unreaction.data["object"] == reaction.data["id"]
735 assert unreaction.local
737 # On federation, it contains the undone (and deleted) object
738 unreaction_with_object = %{
740 | data: Map.put(unreaction.data, "object", reaction.data)
743 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
747 test "repeating a status" do
749 other_user = insert(:user)
751 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
753 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
754 assert Visibility.is_public?(announce_activity)
757 test "can't repeat a repeat" do
759 other_user = insert(:user)
760 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
762 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
764 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
767 test "repeating a status privately" do
769 other_user = insert(:user)
771 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
773 {:ok, %Activity{} = announce_activity} =
774 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
776 assert Visibility.is_private?(announce_activity)
777 refute Visibility.visible_for_user?(announce_activity, nil)
780 test "author can repeat own private statuses" do
781 author = insert(:user)
782 follower = insert(:user)
783 CommonAPI.follow(follower, author)
785 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
787 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
789 assert Visibility.is_private?(announce_activity)
790 refute Visibility.visible_for_user?(announce_activity, nil)
792 assert Visibility.visible_for_user?(activity, follower)
793 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
796 test "favoriting a status" do
798 other_user = insert(:user)
800 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
802 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
803 assert data["type"] == "Like"
804 assert data["actor"] == user.ap_id
805 assert data["object"] == post_activity.data["object"]
808 test "retweeting a status twice returns the status" do
810 other_user = insert(:user)
812 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
813 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
814 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
817 test "favoriting a status twice returns ok, but without the like activity" do
819 other_user = insert(:user)
821 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
822 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
823 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
827 describe "pinned statuses" do
829 clear_config([:instance, :max_pinned_statuses], 1)
832 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
834 [user: user, activity: activity]
837 test "activity not found error", %{user: user} do
838 assert {:error, :not_found} = CommonAPI.pin("id", user)
841 test "pin status", %{user: user, activity: activity} do
842 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
844 %{data: %{"id" => object_id}} = Object.normalize(activity)
845 user = refresh_record(user)
847 assert user.pinned_objects |> Map.keys() == [object_id]
850 test "pin poll", %{user: user} do
852 CommonAPI.post(user, %{
853 status: "How is fediverse today?",
854 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
857 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
859 %{data: %{"id" => object_id}} = Object.normalize(activity)
861 user = refresh_record(user)
863 assert user.pinned_objects |> Map.keys() == [object_id]
866 test "unlisted statuses can be pinned", %{user: user} do
867 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
868 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
871 test "only self-authored can be pinned", %{activity: activity} do
874 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
877 test "max pinned statuses", %{user: user, activity: activity_one} do
878 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
880 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
882 user = refresh_record(user)
884 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
887 test "only public can be pinned", %{user: user} do
888 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
889 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
892 test "unpin status", %{user: user, activity: activity} do
893 {:ok, activity} = CommonAPI.pin(activity.id, user)
895 user = refresh_record(user)
899 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
901 user = refresh_record(user)
903 assert user.pinned_objects == %{}
906 test "should unpin when deleting a status", %{user: user, activity: activity} do
907 {:ok, activity} = CommonAPI.pin(activity.id, user)
909 user = refresh_record(user)
911 assert {:ok, _} = CommonAPI.delete(activity.id, user)
913 user = refresh_record(user)
915 assert user.pinned_objects == %{}
918 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
919 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
921 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
923 {:ok, _activity} = CommonAPI.pin(activity.id, user)
924 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
926 user = refresh_record(user)
927 {:ok, _} = CommonAPI.unpin(activity.id, user)
929 # recreates expiration job on unpin
930 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
933 test "ephemeral activity deletion job won't be deleted on pinning error", %{
937 clear_config([:instance, :max_pinned_statuses], 1)
939 {:ok, _activity} = CommonAPI.pin(activity.id, user)
941 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
943 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
945 user = refresh_record(user)
946 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
948 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
952 describe "mute tests" do
956 activity = insert(:note_activity)
958 [user: user, activity: activity]
961 test "marks notifications as read after mute" do
962 author = insert(:user)
963 activity = insert(:note_activity, user: author)
965 friend1 = insert(:user)
966 friend2 = insert(:user)
968 {:ok, reply_activity} =
972 status: "@#{author.nickname} @#{friend1.nickname} test reply",
973 in_reply_to_status_id: activity.id
977 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
978 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
980 assert Repo.aggregate(
981 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
985 unread_notifications =
986 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
988 assert Enum.any?(unread_notifications, fn n ->
989 n.type == "favourite" && n.activity_id == favorite_activity.id
992 assert Enum.any?(unread_notifications, fn n ->
993 n.type == "reblog" && n.activity_id == repeat_activity.id
996 assert Enum.any?(unread_notifications, fn n ->
997 n.type == "mention" && n.activity_id == reply_activity.id
1000 {:ok, _} = CommonAPI.add_mute(author, activity)
1001 assert CommonAPI.thread_muted?(author, activity)
1003 assert Repo.aggregate(
1004 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1008 read_notifications =
1009 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1011 assert Enum.any?(read_notifications, fn n ->
1012 n.type == "favourite" && n.activity_id == favorite_activity.id
1015 assert Enum.any?(read_notifications, fn n ->
1016 n.type == "reblog" && n.activity_id == repeat_activity.id
1019 assert Enum.any?(read_notifications, fn n ->
1020 n.type == "mention" && n.activity_id == reply_activity.id
1024 test "add mute", %{user: user, activity: activity} do
1025 {:ok, _} = CommonAPI.add_mute(user, activity)
1026 assert CommonAPI.thread_muted?(user, activity)
1029 test "add expiring mute", %{user: user, activity: activity} do
1030 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1031 assert CommonAPI.thread_muted?(user, activity)
1033 worker = Pleroma.Workers.MuteExpireWorker
1034 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1041 assert :ok = perform_job(worker, args)
1042 refute CommonAPI.thread_muted?(user, activity)
1045 test "remove mute", %{user: user, activity: activity} do
1046 CommonAPI.add_mute(user, activity)
1047 {:ok, _} = CommonAPI.remove_mute(user, activity)
1048 refute CommonAPI.thread_muted?(user, activity)
1051 test "remove mute by ids", %{user: user, activity: activity} do
1052 CommonAPI.add_mute(user, activity)
1053 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1054 refute CommonAPI.thread_muted?(user, activity)
1057 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1058 CommonAPI.add_mute(user, activity)
1059 {:error, _} = CommonAPI.add_mute(user, activity)
1063 describe "reports" do
1064 test "creates a report" do
1065 reporter = insert(:user)
1066 target_user = insert(:user)
1068 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1070 reporter_ap_id = reporter.ap_id
1071 target_ap_id = target_user.ap_id
1072 activity_ap_id = activity.data["id"]
1076 account_id: target_user.id,
1078 status_ids: [activity.id]
1083 "id" => activity_ap_id,
1084 "content" => "foobar",
1085 "published" => activity.object.data["published"],
1086 "actor" => AccountView.render("show.json", %{user: target_user})
1089 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1092 actor: ^reporter_ap_id,
1095 "content" => ^comment,
1096 "object" => [^target_ap_id, ^note_obj],
1102 test "updates report state" do
1103 [reporter, target_user] = insert_pair(:user)
1104 activity = insert(:note_activity, user: target_user)
1106 {:ok, %Activity{id: report_id}} =
1107 CommonAPI.report(reporter, %{
1108 account_id: target_user.id,
1109 comment: "I feel offended",
1110 status_ids: [activity.id]
1113 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1115 assert report.data["state"] == "resolved"
1117 [reported_user, activity_id] = report.data["object"]
1119 assert reported_user == target_user.ap_id
1120 assert activity_id == activity.data["id"]
1123 test "does not update report state when state is unsupported" do
1124 [reporter, target_user] = insert_pair(:user)
1125 activity = insert(:note_activity, user: target_user)
1127 {:ok, %Activity{id: report_id}} =
1128 CommonAPI.report(reporter, %{
1129 account_id: target_user.id,
1130 comment: "I feel offended",
1131 status_ids: [activity.id]
1134 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1137 test "updates state of multiple reports" do
1138 [reporter, target_user] = insert_pair(:user)
1139 activity = insert(:note_activity, user: target_user)
1141 {:ok, %Activity{id: first_report_id}} =
1142 CommonAPI.report(reporter, %{
1143 account_id: target_user.id,
1144 comment: "I feel offended",
1145 status_ids: [activity.id]
1148 {:ok, %Activity{id: second_report_id}} =
1149 CommonAPI.report(reporter, %{
1150 account_id: target_user.id,
1151 comment: "I feel very offended!",
1152 status_ids: [activity.id]
1156 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1158 first_report = Activity.get_by_id(first_report_id)
1159 second_report = Activity.get_by_id(second_report_id)
1161 assert report_ids -- [first_report_id, second_report_id] == []
1162 assert first_report.data["state"] == "resolved"
1163 assert second_report.data["state"] == "resolved"
1167 describe "reblog muting" do
1169 muter = insert(:user)
1171 muted = insert(:user)
1173 [muter: muter, muted: muted]
1176 test "add a reblog mute", %{muter: muter, muted: muted} do
1177 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1179 assert User.showing_reblogs?(muter, muted) == false
1182 test "remove a reblog mute", %{muter: muter, muted: muted} do
1183 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1184 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1186 assert User.showing_reblogs?(muter, muted) == true
1190 describe "follow/2" do
1191 test "directly follows a non-locked local user" do
1192 [follower, followed] = insert_pair(:user)
1193 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1195 assert User.following?(follower, followed)
1199 describe "unfollow/2" do
1200 test "also unsubscribes a user" do
1201 [follower, followed] = insert_pair(:user)
1202 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1203 {:ok, _subscription} = User.subscribe(follower, followed)
1205 assert User.subscribed_to?(follower, followed)
1207 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1209 refute User.subscribed_to?(follower, followed)
1212 test "cancels a pending follow for a local user" do
1213 follower = insert(:user)
1214 followed = insert(:user, is_locked: true)
1216 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1217 CommonAPI.follow(follower, followed)
1219 assert User.get_follow_state(follower, followed) == :follow_pending
1220 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1221 assert User.get_follow_state(follower, followed) == nil
1223 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1224 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1229 "object" => %{"type" => "Follow", "state" => "cancelled"}
1231 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1234 test "cancels a pending follow for a remote user" do
1235 follower = insert(:user)
1236 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1238 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1239 CommonAPI.follow(follower, followed)
1241 assert User.get_follow_state(follower, followed) == :follow_pending
1242 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1243 assert User.get_follow_state(follower, followed) == nil
1245 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1246 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1251 "object" => %{"type" => "Follow", "state" => "cancelled"}
1253 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1257 describe "accept_follow_request/2" do
1258 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1259 user = insert(:user, is_locked: true)
1260 follower = insert(:user)
1261 follower_two = insert(:user)
1263 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1264 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1265 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1267 assert follow_activity.data["state"] == "pending"
1268 assert follow_activity_two.data["state"] == "pending"
1269 assert follow_activity_three.data["state"] == "pending"
1271 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1273 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1274 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1275 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1278 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1279 user = insert(:user, is_locked: true)
1280 follower = insert(:user)
1281 follower_two = insert(:user)
1283 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1284 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1285 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1287 assert follow_activity.data["state"] == "pending"
1288 assert follow_activity_two.data["state"] == "pending"
1289 assert follow_activity_three.data["state"] == "pending"
1291 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1293 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1294 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1295 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1298 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1299 user = insert(:user, is_locked: true)
1300 not_follower = insert(:user)
1301 CommonAPI.accept_follow_request(not_follower, user)
1303 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1307 describe "vote/3" do
1308 test "does not allow to vote twice" do
1309 user = insert(:user)
1310 other_user = insert(:user)
1313 CommonAPI.post(user, %{
1314 status: "Am I cute?",
1315 poll: %{options: ["Yes", "No"], expires_in: 20}
1318 object = Object.normalize(activity, fetch: false)
1320 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1322 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1326 describe "listen/2" do
1327 test "returns a valid activity" do
1328 user = insert(:user)
1331 CommonAPI.listen(user, %{
1332 title: "lain radio episode 1",
1333 album: "lain radio",
1338 object = Object.normalize(activity, fetch: false)
1340 assert object.data["title"] == "lain radio episode 1"
1342 assert Visibility.get_visibility(activity) == "public"
1345 test "respects visibility=private" do
1346 user = insert(:user)
1349 CommonAPI.listen(user, %{
1350 title: "lain radio episode 1",
1351 album: "lain radio",
1354 visibility: "private"
1357 object = Object.normalize(activity, fetch: false)
1359 assert object.data["title"] == "lain radio episode 1"
1361 assert Visibility.get_visibility(activity) == "private"
1365 describe "get_user/1" do
1366 test "gets user by ap_id" do
1367 user = insert(:user)
1368 assert CommonAPI.get_user(user.ap_id) == user
1371 test "gets user by guessed nickname" do
1372 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1373 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1380 nickname: "erroruser@example.com"
1381 } = CommonAPI.get_user("")
1385 describe "with `local` visibility" do
1386 setup do: clear_config([:instance, :federating], true)
1389 user = insert(:user)
1391 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1392 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1394 assert Visibility.is_local_public?(activity)
1395 assert_not_called(Pleroma.Web.Federator.publish(activity))
1400 user = insert(:user)
1402 {:ok, %Activity{id: activity_id}} =
1403 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1405 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1406 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1407 CommonAPI.delete(activity_id, user)
1409 assert Visibility.is_local_public?(activity)
1410 assert_not_called(Pleroma.Web.Federator.publish(activity))
1415 user = insert(:user)
1416 other_user = insert(:user)
1418 {:ok, %Activity{id: activity_id}} =
1419 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1421 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1422 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1423 CommonAPI.repeat(activity_id, user)
1425 assert Visibility.is_local_public?(activity)
1426 refute called(Pleroma.Web.Federator.publish(activity))
1431 user = insert(:user)
1432 other_user = insert(:user)
1434 {:ok, %Activity{id: activity_id}} =
1435 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1437 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1439 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1440 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1441 CommonAPI.unrepeat(activity_id, user)
1443 assert Visibility.is_local_public?(activity)
1444 refute called(Pleroma.Web.Federator.publish(activity))
1449 user = insert(:user)
1450 other_user = insert(:user)
1452 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1454 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1455 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1456 CommonAPI.favorite(user, activity.id)
1458 assert Visibility.is_local_public?(activity)
1459 refute called(Pleroma.Web.Federator.publish(activity))
1463 test "unfavorite" do
1464 user = insert(:user)
1465 other_user = insert(:user)
1467 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1469 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1471 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1472 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1473 assert Visibility.is_local_public?(activity)
1474 refute called(Pleroma.Web.Federator.publish(activity))
1478 test "react_with_emoji" do
1479 user = insert(:user)
1480 other_user = insert(:user)
1481 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1483 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1484 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1485 CommonAPI.react_with_emoji(activity.id, user, "👍")
1487 assert Visibility.is_local_public?(activity)
1488 refute called(Pleroma.Web.Federator.publish(activity))
1492 test "unreact_with_emoji" do
1493 user = insert(:user)
1494 other_user = insert(:user)
1495 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1497 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1499 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1500 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1501 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1503 assert Visibility.is_local_public?(activity)
1504 refute called(Pleroma.Web.Federator.publish(activity))