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
10 alias Pleroma.Conversation.Participation
11 alias Pleroma.Notification
15 alias Pleroma.Web.ActivityPub.Transmogrifier
16 alias Pleroma.Web.ActivityPub.Visibility
17 alias Pleroma.Web.AdminAPI.AccountView
18 alias Pleroma.Web.CommonAPI
19 alias Pleroma.Workers.PollWorker
21 import Pleroma.Factory
23 import Ecto.Query, only: [from: 2]
25 require Pleroma.Constants
28 clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
29 clear_config([Pleroma.Uploaders.Local, :uploads], "uploads")
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 "unblocking" do
108 test "it works even without an existing block activity" do
109 blocked = insert(:user)
110 blocker = insert(:user)
111 User.block(blocker, blocked)
113 assert User.blocks?(blocker, blocked)
114 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
115 refute User.blocks?(blocker, blocked)
119 describe "deletion" do
120 test "it works with pruned objects" do
123 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
125 clear_config([:instance, :federating], true)
127 Object.normalize(post, fetch: false)
130 with_mock Pleroma.Web.Federator,
131 publish: fn _ -> nil end do
132 assert {:ok, delete} = CommonAPI.delete(post.id, user)
134 assert called(Pleroma.Web.Federator.publish(delete))
137 refute Activity.get_by_id(post.id)
140 test "it allows users to delete their posts" do
143 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
145 clear_config([:instance, :federating], true)
147 with_mock Pleroma.Web.Federator,
148 publish: fn _ -> nil end do
149 assert {:ok, delete} = CommonAPI.delete(post.id, user)
151 assert called(Pleroma.Web.Federator.publish(delete))
154 refute Activity.get_by_id(post.id)
157 test "it does not allow a user to delete their posts" do
159 other_user = insert(:user)
161 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
163 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
164 assert Activity.get_by_id(post.id)
167 test "it allows moderators to delete other user's posts" do
169 moderator = insert(:user, is_moderator: true)
171 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
173 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
176 refute Activity.get_by_id(post.id)
179 test "it allows admins to delete other user's posts" do
181 moderator = insert(:user, is_admin: true)
183 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
185 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
188 refute Activity.get_by_id(post.id)
191 test "superusers deleting non-local posts won't federate the delete" do
192 # This is the user of the ingested activity
196 ap_id: "http://mastodon.example.org/users/admin",
197 last_refreshed_at: NaiveDateTime.utc_now()
200 moderator = insert(:user, is_admin: true)
203 File.read!("test/fixtures/mastodon-post-activity.json")
206 {:ok, post} = Transmogrifier.handle_incoming(data)
208 with_mock Pleroma.Web.Federator,
209 publish: fn _ -> nil end do
210 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
212 refute called(Pleroma.Web.Federator.publish(:_))
215 refute Activity.get_by_id(post.id)
219 test "favoriting race condition" do
221 users_serial = insert_list(10, :user)
222 users = insert_list(10, :user)
224 {:ok, activity} = CommonAPI.post(user, %{status: "."})
227 |> Enum.map(fn user ->
228 CommonAPI.favorite(user, activity.id)
231 object = Object.get_by_ap_id(activity.data["object"])
232 assert object.data["like_count"] == 10
235 |> Enum.map(fn user ->
237 CommonAPI.favorite(user, activity.id)
240 |> Enum.map(&Task.await/1)
242 object = Object.get_by_ap_id(activity.data["object"])
243 assert object.data["like_count"] == 20
246 test "repeating race condition" do
248 users_serial = insert_list(10, :user)
249 users = insert_list(10, :user)
251 {:ok, activity} = CommonAPI.post(user, %{status: "."})
254 |> Enum.map(fn user ->
255 CommonAPI.repeat(activity.id, user)
258 object = Object.get_by_ap_id(activity.data["object"])
259 assert object.data["announcement_count"] == 10
262 |> Enum.map(fn user ->
264 CommonAPI.repeat(activity.id, user)
267 |> Enum.map(&Task.await/1)
269 object = Object.get_by_ap_id(activity.data["object"])
270 assert object.data["announcement_count"] == 20
273 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
275 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
277 [participation] = Participation.for_user(user)
280 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
282 assert Visibility.is_direct?(convo_reply)
284 assert activity.data["context"] == convo_reply.data["context"]
287 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
289 jafnhar = insert(:user)
290 tridi = insert(:user)
293 CommonAPI.post(har, %{
294 status: "@#{jafnhar.nickname} hey",
298 assert har.ap_id in activity.recipients
299 assert jafnhar.ap_id in activity.recipients
301 [participation] = Participation.for_user(har)
304 CommonAPI.post(har, %{
305 status: "I don't really like @#{tridi.nickname}",
306 visibility: "direct",
307 in_reply_to_status_id: activity.id,
308 in_reply_to_conversation_id: participation.id
311 assert har.ap_id in activity.recipients
312 assert jafnhar.ap_id in activity.recipients
313 refute tridi.ap_id in activity.recipients
316 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
318 jafnhar = insert(:user)
319 tridi = insert(:user)
321 clear_config([:instance, :safe_dm_mentions], true)
324 CommonAPI.post(har, %{
325 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
329 refute tridi.ap_id in activity.recipients
330 assert jafnhar.ap_id in activity.recipients
333 test "it de-duplicates tags" do
335 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
337 object = Object.normalize(activity, fetch: false)
339 assert Object.tags(object) == ["2hu"]
342 test "it adds emoji in the object" do
344 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
346 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
349 describe "posting" do
350 test "it adds an emoji on an external site" do
352 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
354 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
355 assert url == "https://example.com/emoji.png"
357 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
359 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
360 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
363 test "it copies emoji from the subject of the parent post" do
366 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
370 activity = Activity.get_create_by_object_ap_id(object.data["id"])
373 {:ok, reply_activity} =
374 CommonAPI.post(user, %{
375 in_reply_to_id: activity.id,
376 status: ":joker_disapprove:",
377 spoiler_text: ":joker_smile:"
380 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
381 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
384 test "deactivated users can't post" do
385 user = insert(:user, is_active: false)
386 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
389 test "it supports explicit addressing" do
391 user_two = insert(:user)
392 user_three = insert(:user)
393 user_four = insert(:user)
396 CommonAPI.post(user, %{
398 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
399 to: [user_two.nickname, user_four.nickname, "nonexistent"]
402 assert user.ap_id in activity.recipients
403 assert user_two.ap_id in activity.recipients
404 assert user_four.ap_id in activity.recipients
405 refute user_three.ap_id in activity.recipients
408 test "it filters out obviously bad tags when accepting a post as HTML" do
411 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
414 CommonAPI.post(user, %{
416 content_type: "text/html"
419 object = Object.normalize(activity, fetch: false)
421 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
423 assert object.data["source"] == %{
424 "mediaType" => "text/html",
429 test "it filters out obviously bad tags when accepting a post as Markdown" do
432 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
435 CommonAPI.post(user, %{
437 content_type: "text/markdown"
440 object = Object.normalize(activity, fetch: false)
442 assert object.data["content"] == "<p><b>2hu</b></p>"
444 assert object.data["source"] == %{
445 "mediaType" => "text/markdown",
450 test "it does not allow replies to direct messages that are not direct messages themselves" do
453 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
456 CommonAPI.post(user, %{
458 visibility: "direct",
459 in_reply_to_status_id: activity.id
462 Enum.each(["public", "private", "unlisted"], fn visibility ->
463 assert {:error, "The message visibility must be direct"} =
464 CommonAPI.post(user, %{
466 visibility: visibility,
467 in_reply_to_status_id: activity.id
472 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
474 other_user = insert(:user)
475 third_user = insert(:user)
477 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
480 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
482 # The OP is implicitly added
483 assert user.ap_id in open_answer.recipients
485 {:ok, secret_answer} =
486 CommonAPI.post(other_user, %{
487 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
488 in_reply_to_status_id: post.id,
492 assert third_user.ap_id in secret_answer.recipients
494 # The OP is not added
495 refute user.ap_id in secret_answer.recipients
498 test "it allows to address a list" do
500 {:ok, list} = Pleroma.List.create("foo", user)
502 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
504 assert activity.data["bcc"] == [list.ap_id]
505 assert activity.recipients == [list.ap_id, user.ap_id]
506 assert activity.data["listMessage"] == list.ap_id
509 test "it returns error when status is empty and no attachments" do
512 assert {:error, "Cannot post an empty status without attachments"} =
513 CommonAPI.post(user, %{status: ""})
516 test "it validates character limits are correctly enforced" do
517 clear_config([:instance, :limit], 5)
521 assert {:error, "The status is over the character limit"} =
522 CommonAPI.post(user, %{status: "foobar"})
524 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
527 test "it can handle activities that expire" do
530 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
532 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
535 worker: Pleroma.Workers.PurgeExpiredActivity,
536 args: %{activity_id: activity.id},
537 scheduled_at: expires_at
542 describe "reactions" do
543 test "reacting to a status with an emoji" do
545 other_user = insert(:user)
547 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
549 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
551 assert reaction.data["actor"] == user.ap_id
552 assert reaction.data["content"] == "👍"
554 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
556 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
559 test "unreacting to a status with an emoji" do
561 other_user = insert(:user)
563 clear_config([:instance, :federating], true)
565 with_mock Pleroma.Web.Federator,
566 publish: fn _ -> nil end do
567 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
568 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
570 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
572 assert unreaction.data["type"] == "Undo"
573 assert unreaction.data["object"] == reaction.data["id"]
574 assert unreaction.local
576 # On federation, it contains the undone (and deleted) object
577 unreaction_with_object = %{
579 | data: Map.put(unreaction.data, "object", reaction.data)
582 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
586 test "repeating a status" do
588 other_user = insert(:user)
590 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
592 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
593 assert Visibility.is_public?(announce_activity)
596 test "can't repeat a repeat" do
598 other_user = insert(:user)
599 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
601 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
603 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
606 test "repeating a status privately" do
608 other_user = insert(:user)
610 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
612 {:ok, %Activity{} = announce_activity} =
613 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
615 assert Visibility.is_private?(announce_activity)
616 refute Visibility.visible_for_user?(announce_activity, nil)
619 test "author can repeat own private statuses" do
620 author = insert(:user)
621 follower = insert(:user)
622 CommonAPI.follow(follower, author)
624 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
626 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
628 assert Visibility.is_private?(announce_activity)
629 refute Visibility.visible_for_user?(announce_activity, nil)
631 assert Visibility.visible_for_user?(activity, follower)
632 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
635 test "favoriting a status" do
637 other_user = insert(:user)
639 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
641 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
642 assert data["type"] == "Like"
643 assert data["actor"] == user.ap_id
644 assert data["object"] == post_activity.data["object"]
647 test "retweeting a status twice returns the status" do
649 other_user = insert(:user)
651 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
652 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
653 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
656 test "favoriting a status twice returns ok, but without the like activity" do
658 other_user = insert(:user)
660 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
661 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
662 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
666 describe "pinned statuses" do
668 clear_config([:instance, :max_pinned_statuses], 1)
671 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
673 [user: user, activity: activity]
676 test "activity not found error", %{user: user} do
677 assert {:error, :not_found} = CommonAPI.pin("id", user)
680 test "pin status", %{user: user, activity: activity} do
681 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
683 %{data: %{"id" => object_id}} = Object.normalize(activity)
684 user = refresh_record(user)
686 assert user.pinned_objects |> Map.keys() == [object_id]
689 test "pin poll", %{user: user} do
691 CommonAPI.post(user, %{
692 status: "How is fediverse today?",
693 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
696 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
698 %{data: %{"id" => object_id}} = Object.normalize(activity)
700 user = refresh_record(user)
702 assert user.pinned_objects |> Map.keys() == [object_id]
705 test "unlisted statuses can be pinned", %{user: user} do
706 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
707 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
710 test "only self-authored can be pinned", %{activity: activity} do
713 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
716 test "max pinned statuses", %{user: user, activity: activity_one} do
717 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
719 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
721 user = refresh_record(user)
723 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
726 test "only public can be pinned", %{user: user} do
727 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
728 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
731 test "unpin status", %{user: user, activity: activity} do
732 {:ok, activity} = CommonAPI.pin(activity.id, user)
734 user = refresh_record(user)
738 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
740 user = refresh_record(user)
742 assert user.pinned_objects == %{}
745 test "should unpin when deleting a status", %{user: user, activity: activity} do
746 {:ok, activity} = CommonAPI.pin(activity.id, user)
748 user = refresh_record(user)
750 assert {:ok, _} = CommonAPI.delete(activity.id, user)
752 user = refresh_record(user)
754 assert user.pinned_objects == %{}
757 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
758 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
760 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
762 {:ok, _activity} = CommonAPI.pin(activity.id, user)
763 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
765 user = refresh_record(user)
766 {:ok, _} = CommonAPI.unpin(activity.id, user)
768 # recreates expiration job on unpin
769 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
772 test "ephemeral activity deletion job won't be deleted on pinning error", %{
776 clear_config([:instance, :max_pinned_statuses], 1)
778 {:ok, _activity} = CommonAPI.pin(activity.id, user)
780 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
782 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
784 user = refresh_record(user)
785 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
787 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
791 describe "mute tests" do
795 activity = insert(:note_activity)
797 [user: user, activity: activity]
800 test "marks notifications as read after mute" do
801 author = insert(:user)
802 activity = insert(:note_activity, user: author)
804 friend1 = insert(:user)
805 friend2 = insert(:user)
807 {:ok, reply_activity} =
811 status: "@#{author.nickname} @#{friend1.nickname} test reply",
812 in_reply_to_status_id: activity.id
816 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
817 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
819 assert Repo.aggregate(
820 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
824 unread_notifications =
825 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
827 assert Enum.any?(unread_notifications, fn n ->
828 n.type == "favourite" && n.activity_id == favorite_activity.id
831 assert Enum.any?(unread_notifications, fn n ->
832 n.type == "reblog" && n.activity_id == repeat_activity.id
835 assert Enum.any?(unread_notifications, fn n ->
836 n.type == "mention" && n.activity_id == reply_activity.id
839 {:ok, _} = CommonAPI.add_mute(author, activity)
840 assert CommonAPI.thread_muted?(author, activity)
842 assert Repo.aggregate(
843 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
848 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
850 assert Enum.any?(read_notifications, fn n ->
851 n.type == "favourite" && n.activity_id == favorite_activity.id
854 assert Enum.any?(read_notifications, fn n ->
855 n.type == "reblog" && n.activity_id == repeat_activity.id
858 assert Enum.any?(read_notifications, fn n ->
859 n.type == "mention" && n.activity_id == reply_activity.id
863 test "add mute", %{user: user, activity: activity} do
864 {:ok, _} = CommonAPI.add_mute(user, activity)
865 assert CommonAPI.thread_muted?(user, activity)
868 test "add expiring mute", %{user: user, activity: activity} do
869 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
870 assert CommonAPI.thread_muted?(user, activity)
872 worker = Pleroma.Workers.MuteExpireWorker
873 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
880 assert :ok = perform_job(worker, args)
881 refute CommonAPI.thread_muted?(user, activity)
884 test "remove mute", %{user: user, activity: activity} do
885 CommonAPI.add_mute(user, activity)
886 {:ok, _} = CommonAPI.remove_mute(user, activity)
887 refute CommonAPI.thread_muted?(user, activity)
890 test "remove mute by ids", %{user: user, activity: activity} do
891 CommonAPI.add_mute(user, activity)
892 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
893 refute CommonAPI.thread_muted?(user, activity)
896 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
897 CommonAPI.add_mute(user, activity)
898 {:error, _} = CommonAPI.add_mute(user, activity)
902 describe "reports" do
903 test "creates a report" do
904 reporter = insert(:user)
905 target_user = insert(:user)
907 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
909 reporter_ap_id = reporter.ap_id
910 target_ap_id = target_user.ap_id
911 activity_ap_id = activity.data["id"]
915 account_id: target_user.id,
917 status_ids: [activity.id]
922 "id" => activity_ap_id,
923 "content" => "foobar",
924 "published" => activity.object.data["published"],
925 "actor" => AccountView.render("show.json", %{user: target_user})
928 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
931 actor: ^reporter_ap_id,
934 "content" => ^comment,
935 "object" => [^target_ap_id, ^note_obj],
941 test "updates report state" do
942 [reporter, target_user] = insert_pair(:user)
943 activity = insert(:note_activity, user: target_user)
945 {:ok, %Activity{id: report_id}} =
946 CommonAPI.report(reporter, %{
947 account_id: target_user.id,
948 comment: "I feel offended",
949 status_ids: [activity.id]
952 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
954 assert report.data["state"] == "resolved"
956 [reported_user, activity_id] = report.data["object"]
958 assert reported_user == target_user.ap_id
959 assert activity_id == activity.data["id"]
962 test "does not update report state when state is unsupported" do
963 [reporter, target_user] = insert_pair(:user)
964 activity = insert(:note_activity, user: target_user)
966 {:ok, %Activity{id: report_id}} =
967 CommonAPI.report(reporter, %{
968 account_id: target_user.id,
969 comment: "I feel offended",
970 status_ids: [activity.id]
973 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
976 test "updates state of multiple reports" do
977 [reporter, target_user] = insert_pair(:user)
978 activity = insert(:note_activity, user: target_user)
980 {:ok, %Activity{id: first_report_id}} =
981 CommonAPI.report(reporter, %{
982 account_id: target_user.id,
983 comment: "I feel offended",
984 status_ids: [activity.id]
987 {:ok, %Activity{id: second_report_id}} =
988 CommonAPI.report(reporter, %{
989 account_id: target_user.id,
990 comment: "I feel very offended!",
991 status_ids: [activity.id]
995 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
997 first_report = Activity.get_by_id(first_report_id)
998 second_report = Activity.get_by_id(second_report_id)
1000 assert report_ids -- [first_report_id, second_report_id] == []
1001 assert first_report.data["state"] == "resolved"
1002 assert second_report.data["state"] == "resolved"
1006 describe "reblog muting" do
1008 muter = insert(:user)
1010 muted = insert(:user)
1012 [muter: muter, muted: muted]
1015 test "add a reblog mute", %{muter: muter, muted: muted} do
1016 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1018 assert User.showing_reblogs?(muter, muted) == false
1021 test "remove a reblog mute", %{muter: muter, muted: muted} do
1022 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1023 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1025 assert User.showing_reblogs?(muter, muted) == true
1029 describe "follow/2" do
1030 test "directly follows a non-locked local user" do
1031 [follower, followed] = insert_pair(:user)
1032 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1034 assert User.following?(follower, followed)
1038 describe "unfollow/2" do
1039 test "also unsubscribes a user" do
1040 [follower, followed] = insert_pair(:user)
1041 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1042 {:ok, _subscription} = User.subscribe(follower, followed)
1044 assert User.subscribed_to?(follower, followed)
1046 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1048 refute User.subscribed_to?(follower, followed)
1051 test "cancels a pending follow for a local user" do
1052 follower = insert(:user)
1053 followed = insert(:user, is_locked: true)
1055 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1056 CommonAPI.follow(follower, followed)
1058 assert User.get_follow_state(follower, followed) == :follow_pending
1059 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1060 assert User.get_follow_state(follower, followed) == nil
1062 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1063 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1068 "object" => %{"type" => "Follow", "state" => "cancelled"}
1070 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1073 test "cancels a pending follow for a remote user" do
1074 follower = insert(:user)
1075 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1077 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1078 CommonAPI.follow(follower, followed)
1080 assert User.get_follow_state(follower, followed) == :follow_pending
1081 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1082 assert User.get_follow_state(follower, followed) == nil
1084 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1085 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1090 "object" => %{"type" => "Follow", "state" => "cancelled"}
1092 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1096 describe "accept_follow_request/2" do
1097 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1098 user = insert(:user, is_locked: true)
1099 follower = insert(:user)
1100 follower_two = insert(:user)
1102 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1103 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1104 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1106 assert follow_activity.data["state"] == "pending"
1107 assert follow_activity_two.data["state"] == "pending"
1108 assert follow_activity_three.data["state"] == "pending"
1110 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1112 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1113 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1114 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1117 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1118 user = insert(:user, is_locked: true)
1119 follower = insert(:user)
1120 follower_two = insert(:user)
1122 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1123 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1124 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1126 assert follow_activity.data["state"] == "pending"
1127 assert follow_activity_two.data["state"] == "pending"
1128 assert follow_activity_three.data["state"] == "pending"
1130 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1132 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1133 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1134 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1137 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1138 user = insert(:user, is_locked: true)
1139 not_follower = insert(:user)
1140 CommonAPI.accept_follow_request(not_follower, user)
1142 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1146 describe "vote/3" do
1147 test "does not allow to vote twice" do
1148 user = insert(:user)
1149 other_user = insert(:user)
1152 CommonAPI.post(user, %{
1153 status: "Am I cute?",
1154 poll: %{options: ["Yes", "No"], expires_in: 20}
1157 object = Object.normalize(activity, fetch: false)
1159 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1161 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1165 describe "get_user/1" do
1166 test "gets user by ap_id" do
1167 user = insert(:user)
1168 assert CommonAPI.get_user(user.ap_id) == user
1171 test "gets user by guessed nickname" do
1172 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1173 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1180 nickname: "erroruser@example.com"
1181 } = CommonAPI.get_user("")
1185 describe "with `local` visibility" do
1186 setup do: clear_config([:instance, :federating], true)
1189 user = insert(:user)
1191 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1192 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1194 assert Visibility.is_local_public?(activity)
1195 assert_not_called(Pleroma.Web.Federator.publish(activity))
1200 user = insert(:user)
1202 {:ok, %Activity{id: activity_id}} =
1203 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1205 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1206 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1207 CommonAPI.delete(activity_id, user)
1209 assert Visibility.is_local_public?(activity)
1210 assert_not_called(Pleroma.Web.Federator.publish(activity))
1215 user = insert(:user)
1216 other_user = insert(:user)
1218 {:ok, %Activity{id: activity_id}} =
1219 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1221 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1222 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1223 CommonAPI.repeat(activity_id, user)
1225 assert Visibility.is_local_public?(activity)
1226 refute called(Pleroma.Web.Federator.publish(activity))
1231 user = insert(:user)
1232 other_user = insert(:user)
1234 {:ok, %Activity{id: activity_id}} =
1235 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1237 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1239 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1240 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1241 CommonAPI.unrepeat(activity_id, user)
1243 assert Visibility.is_local_public?(activity)
1244 refute called(Pleroma.Web.Federator.publish(activity))
1249 user = insert(:user)
1250 other_user = insert(:user)
1252 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1254 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1255 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1256 CommonAPI.favorite(user, activity.id)
1258 assert Visibility.is_local_public?(activity)
1259 refute called(Pleroma.Web.Federator.publish(activity))
1263 test "unfavorite" do
1264 user = insert(:user)
1265 other_user = insert(:user)
1267 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1269 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1271 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1272 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1273 assert Visibility.is_local_public?(activity)
1274 refute called(Pleroma.Web.Federator.publish(activity))
1278 test "react_with_emoji" do
1279 user = insert(:user)
1280 other_user = insert(:user)
1281 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1283 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1284 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1285 CommonAPI.react_with_emoji(activity.id, user, "👍")
1287 assert Visibility.is_local_public?(activity)
1288 refute called(Pleroma.Web.Federator.publish(activity))
1292 test "unreact_with_emoji" do
1293 user = insert(:user)
1294 other_user = insert(:user)
1295 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1297 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1299 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1300 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1301 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1303 assert Visibility.is_local_public?(activity)
1304 refute called(Pleroma.Web.Federator.publish(activity))