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, local: false)
65 CommonAPI.follow(blocker, blocked)
66 CommonAPI.follow(blocked, blocker)
67 CommonAPI.accept_follow_request(blocker, blocked)
68 CommonAPI.accept_follow_request(blocked, blocked)
69 %{blocker: blocker, blocked: blocked}
72 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
73 clear_config([:instance, :federating], true)
74 clear_config([:activitypub, :outgoing_blocks], true)
76 with_mock Pleroma.Web.Federator,
77 publish: fn _ -> nil end do
78 assert User.get_follow_state(blocker, blocked) == :follow_accept
79 refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
81 assert {:ok, block} = CommonAPI.block(blocker, blocked)
84 assert User.blocks?(blocker, blocked)
85 refute User.following?(blocker, blocked)
86 refute User.following?(blocked, blocker)
88 refute User.get_follow_state(blocker, blocked)
90 assert %{data: %{"state" => "reject"}} =
91 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
93 assert called(Pleroma.Web.Federator.publish(block))
97 test "it blocks and does not federate if outgoing blocks are disabled", %{
101 clear_config([:instance, :federating], true)
102 clear_config([:activitypub, :outgoing_blocks], false)
104 with_mock Pleroma.Web.Federator,
105 publish: fn _ -> nil end do
106 assert {:ok, block} = CommonAPI.block(blocker, blocked)
109 assert User.blocks?(blocker, blocked)
110 refute User.following?(blocker, blocked)
111 refute User.following?(blocked, blocker)
113 refute called(Pleroma.Web.Federator.publish(block))
118 describe "unblocking" do
119 test "it works even without an existing block activity" do
120 blocked = insert(:user)
121 blocker = insert(:user)
122 User.block(blocker, blocked)
124 assert User.blocks?(blocker, blocked)
125 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
126 refute User.blocks?(blocker, blocked)
130 describe "deletion" do
131 test "it works with pruned objects" do
134 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
136 clear_config([:instance, :federating], true)
138 Object.normalize(post, fetch: false)
141 with_mock Pleroma.Web.Federator,
142 publish: fn _ -> nil end do
143 assert {:ok, delete} = CommonAPI.delete(post.id, user)
145 assert called(Pleroma.Web.Federator.publish(delete))
148 refute Activity.get_by_id(post.id)
151 test "it allows users to delete their posts" do
154 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
156 clear_config([:instance, :federating], true)
158 with_mock Pleroma.Web.Federator,
159 publish: fn _ -> nil end do
160 assert {:ok, delete} = CommonAPI.delete(post.id, user)
162 assert called(Pleroma.Web.Federator.publish(delete))
165 refute Activity.get_by_id(post.id)
168 test "it does not allow a user to delete their posts" do
170 other_user = insert(:user)
172 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
174 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
175 assert Activity.get_by_id(post.id)
178 test "it allows moderators to delete other user's posts" do
180 moderator = insert(:user, is_moderator: true)
182 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
184 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
187 refute Activity.get_by_id(post.id)
190 test "it allows admins to delete other user's posts" do
192 moderator = insert(:user, is_admin: true)
194 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
196 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
199 refute Activity.get_by_id(post.id)
202 test "superusers deleting non-local posts won't federate the delete" do
203 # This is the user of the ingested activity
207 ap_id: "http://mastodon.example.org/users/admin",
208 last_refreshed_at: NaiveDateTime.utc_now()
211 moderator = insert(:user, is_admin: true)
214 File.read!("test/fixtures/mastodon-post-activity.json")
217 {:ok, post} = Transmogrifier.handle_incoming(data)
219 with_mock Pleroma.Web.Federator,
220 publish: fn _ -> nil end do
221 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
223 refute called(Pleroma.Web.Federator.publish(:_))
226 refute Activity.get_by_id(post.id)
230 test "favoriting race condition" do
232 users_serial = insert_list(10, :user)
233 users = insert_list(10, :user)
235 {:ok, activity} = CommonAPI.post(user, %{status: "."})
238 |> Enum.map(fn user ->
239 CommonAPI.favorite(user, activity.id)
242 object = Object.get_by_ap_id(activity.data["object"])
243 assert object.data["like_count"] == 10
246 |> Enum.map(fn user ->
248 CommonAPI.favorite(user, activity.id)
251 |> Enum.map(&Task.await/1)
253 object = Object.get_by_ap_id(activity.data["object"])
254 assert object.data["like_count"] == 20
257 test "repeating race condition" do
259 users_serial = insert_list(10, :user)
260 users = insert_list(10, :user)
262 {:ok, activity} = CommonAPI.post(user, %{status: "."})
265 |> Enum.map(fn user ->
266 CommonAPI.repeat(activity.id, user)
269 object = Object.get_by_ap_id(activity.data["object"])
270 assert object.data["announcement_count"] == 10
273 |> Enum.map(fn user ->
275 CommonAPI.repeat(activity.id, user)
278 |> Enum.map(&Task.await/1)
280 object = Object.get_by_ap_id(activity.data["object"])
281 assert object.data["announcement_count"] == 20
284 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
286 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
288 [participation] = Participation.for_user(user)
291 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
293 assert Visibility.is_direct?(convo_reply)
295 assert activity.data["context"] == convo_reply.data["context"]
298 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
300 jafnhar = insert(:user)
301 tridi = insert(:user)
304 CommonAPI.post(har, %{
305 status: "@#{jafnhar.nickname} hey",
309 assert har.ap_id in activity.recipients
310 assert jafnhar.ap_id in activity.recipients
312 [participation] = Participation.for_user(har)
315 CommonAPI.post(har, %{
316 status: "I don't really like @#{tridi.nickname}",
317 visibility: "direct",
318 in_reply_to_status_id: activity.id,
319 in_reply_to_conversation_id: participation.id
322 assert har.ap_id in activity.recipients
323 assert jafnhar.ap_id in activity.recipients
324 refute tridi.ap_id in activity.recipients
327 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
329 jafnhar = insert(:user)
330 tridi = insert(:user)
332 clear_config([:instance, :safe_dm_mentions], true)
335 CommonAPI.post(har, %{
336 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
340 refute tridi.ap_id in activity.recipients
341 assert jafnhar.ap_id in activity.recipients
344 test "it de-duplicates tags" do
346 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
348 object = Object.normalize(activity, fetch: false)
350 assert Object.tags(object) == ["2hu"]
353 test "it adds emoji in the object" do
355 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
357 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
360 describe "posting" do
361 test "it adds an emoji on an external site" do
363 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
365 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
366 assert url == "https://example.com/emoji.png"
368 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
370 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
371 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
374 test "it copies emoji from the subject of the parent post" do
377 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
381 activity = Activity.get_create_by_object_ap_id(object.data["id"])
384 {:ok, reply_activity} =
385 CommonAPI.post(user, %{
386 in_reply_to_id: activity.id,
387 status: ":joker_disapprove:",
388 spoiler_text: ":joker_smile:"
391 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
392 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
395 test "deactivated users can't post" do
396 user = insert(:user, is_active: false)
397 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
400 test "it supports explicit addressing" do
402 user_two = insert(:user)
403 user_three = insert(:user)
404 user_four = insert(:user)
407 CommonAPI.post(user, %{
409 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
410 to: [user_two.nickname, user_four.nickname, "nonexistent"]
413 assert user.ap_id in activity.recipients
414 assert user_two.ap_id in activity.recipients
415 assert user_four.ap_id in activity.recipients
416 refute user_three.ap_id in activity.recipients
419 test "it filters out obviously bad tags when accepting a post as HTML" do
422 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
425 CommonAPI.post(user, %{
427 content_type: "text/html"
430 object = Object.normalize(activity, fetch: false)
432 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
434 assert object.data["source"] == %{
435 "mediaType" => "text/html",
440 test "it filters out obviously bad tags when accepting a post as Markdown" do
443 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
446 CommonAPI.post(user, %{
448 content_type: "text/markdown"
451 object = Object.normalize(activity, fetch: false)
453 assert object.data["content"] == "<p><b>2hu</b></p>"
455 assert object.data["source"] == %{
456 "mediaType" => "text/markdown",
461 test "it does not allow replies to direct messages that are not direct messages themselves" do
464 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
467 CommonAPI.post(user, %{
469 visibility: "direct",
470 in_reply_to_status_id: activity.id
473 Enum.each(["public", "private", "unlisted"], fn visibility ->
474 assert {:error, "The message visibility must be direct"} =
475 CommonAPI.post(user, %{
477 visibility: visibility,
478 in_reply_to_status_id: activity.id
483 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
485 other_user = insert(:user)
486 third_user = insert(:user)
488 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
491 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
493 # The OP is implicitly added
494 assert user.ap_id in open_answer.recipients
496 {:ok, secret_answer} =
497 CommonAPI.post(other_user, %{
498 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
499 in_reply_to_status_id: post.id,
503 assert third_user.ap_id in secret_answer.recipients
505 # The OP is not added
506 refute user.ap_id in secret_answer.recipients
509 test "it allows to address a list" do
511 {:ok, list} = Pleroma.List.create("foo", user)
513 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
515 assert activity.data["bcc"] == [list.ap_id]
516 assert activity.recipients == [list.ap_id, user.ap_id]
517 assert activity.data["listMessage"] == list.ap_id
520 test "it returns error when status is empty and no attachments" do
523 assert {:error, "Cannot post an empty status without attachments"} =
524 CommonAPI.post(user, %{status: ""})
527 test "it validates character limits are correctly enforced" do
528 clear_config([:instance, :limit], 5)
532 assert {:error, "The status is over the character limit"} =
533 CommonAPI.post(user, %{status: "foobar"})
535 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
538 test "it can handle activities that expire" do
541 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
543 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
546 worker: Pleroma.Workers.PurgeExpiredActivity,
547 args: %{activity_id: activity.id},
548 scheduled_at: expires_at
553 describe "reactions" do
554 test "reacting to a status with an emoji" do
556 other_user = insert(:user)
558 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
560 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
562 assert reaction.data["actor"] == user.ap_id
563 assert reaction.data["content"] == "👍"
565 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
567 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
570 test "unreacting to a status with an emoji" do
572 other_user = insert(:user)
574 clear_config([:instance, :federating], true)
576 with_mock Pleroma.Web.Federator,
577 publish: fn _ -> nil end do
578 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
579 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
581 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
583 assert unreaction.data["type"] == "Undo"
584 assert unreaction.data["object"] == reaction.data["id"]
585 assert unreaction.local
587 # On federation, it contains the undone (and deleted) object
588 unreaction_with_object = %{
590 | data: Map.put(unreaction.data, "object", reaction.data)
593 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
597 test "repeating a status" do
599 other_user = insert(:user)
601 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
603 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
604 assert Visibility.is_public?(announce_activity)
607 test "can't repeat a repeat" do
609 other_user = insert(:user)
610 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
612 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
614 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
617 test "repeating a status privately" do
619 other_user = insert(:user)
621 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
623 {:ok, %Activity{} = announce_activity} =
624 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
626 assert Visibility.is_private?(announce_activity)
627 refute Visibility.visible_for_user?(announce_activity, nil)
630 test "author can repeat own private statuses" do
631 author = insert(:user)
632 follower = insert(:user)
633 CommonAPI.follow(follower, author)
635 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
637 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
639 assert Visibility.is_private?(announce_activity)
640 refute Visibility.visible_for_user?(announce_activity, nil)
642 assert Visibility.visible_for_user?(activity, follower)
643 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
646 test "favoriting a status" do
648 other_user = insert(:user)
650 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
652 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
653 assert data["type"] == "Like"
654 assert data["actor"] == user.ap_id
655 assert data["object"] == post_activity.data["object"]
658 test "retweeting a status twice returns the status" do
660 other_user = insert(:user)
662 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
663 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
664 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
667 test "favoriting a status twice returns ok, but without the like activity" do
669 other_user = insert(:user)
671 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
672 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
673 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
677 describe "pinned statuses" do
679 clear_config([:instance, :max_pinned_statuses], 1)
682 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
684 [user: user, activity: activity]
687 test "activity not found error", %{user: user} do
688 assert {:error, :not_found} = CommonAPI.pin("id", user)
691 test "pin status", %{user: user, activity: activity} do
692 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
694 %{data: %{"id" => object_id}} = Object.normalize(activity)
695 user = refresh_record(user)
697 assert user.pinned_objects |> Map.keys() == [object_id]
700 test "pin poll", %{user: user} do
702 CommonAPI.post(user, %{
703 status: "How is fediverse today?",
704 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
707 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
709 %{data: %{"id" => object_id}} = Object.normalize(activity)
711 user = refresh_record(user)
713 assert user.pinned_objects |> Map.keys() == [object_id]
716 test "unlisted statuses can be pinned", %{user: user} do
717 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
718 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
721 test "only self-authored can be pinned", %{activity: activity} do
724 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
727 test "max pinned statuses", %{user: user, activity: activity_one} do
728 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
730 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
732 user = refresh_record(user)
734 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
737 test "only public can be pinned", %{user: user} do
738 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
739 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
742 test "unpin status", %{user: user, activity: activity} do
743 {:ok, activity} = CommonAPI.pin(activity.id, user)
745 user = refresh_record(user)
749 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
751 user = refresh_record(user)
753 assert user.pinned_objects == %{}
756 test "should unpin when deleting a status", %{user: user, activity: activity} do
757 {:ok, activity} = CommonAPI.pin(activity.id, user)
759 user = refresh_record(user)
761 assert {:ok, _} = CommonAPI.delete(activity.id, user)
763 user = refresh_record(user)
765 assert user.pinned_objects == %{}
768 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
769 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
771 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
773 {:ok, _activity} = CommonAPI.pin(activity.id, user)
774 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
776 user = refresh_record(user)
777 {:ok, _} = CommonAPI.unpin(activity.id, user)
779 # recreates expiration job on unpin
780 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
783 test "ephemeral activity deletion job won't be deleted on pinning error", %{
787 clear_config([:instance, :max_pinned_statuses], 1)
789 {:ok, _activity} = CommonAPI.pin(activity.id, user)
791 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
793 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
795 user = refresh_record(user)
796 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
798 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
802 describe "mute tests" do
806 activity = insert(:note_activity)
808 [user: user, activity: activity]
811 test "marks notifications as read after mute" do
812 author = insert(:user)
813 activity = insert(:note_activity, user: author)
815 friend1 = insert(:user)
816 friend2 = insert(:user)
818 {:ok, reply_activity} =
822 status: "@#{author.nickname} @#{friend1.nickname} test reply",
823 in_reply_to_status_id: activity.id
827 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
828 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
830 assert Repo.aggregate(
831 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
835 unread_notifications =
836 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
838 assert Enum.any?(unread_notifications, fn n ->
839 n.type == "favourite" && n.activity_id == favorite_activity.id
842 assert Enum.any?(unread_notifications, fn n ->
843 n.type == "reblog" && n.activity_id == repeat_activity.id
846 assert Enum.any?(unread_notifications, fn n ->
847 n.type == "mention" && n.activity_id == reply_activity.id
850 {:ok, _} = CommonAPI.add_mute(author, activity)
851 assert CommonAPI.thread_muted?(author, activity)
853 assert Repo.aggregate(
854 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
859 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
861 assert Enum.any?(read_notifications, fn n ->
862 n.type == "favourite" && n.activity_id == favorite_activity.id
865 assert Enum.any?(read_notifications, fn n ->
866 n.type == "reblog" && n.activity_id == repeat_activity.id
869 assert Enum.any?(read_notifications, fn n ->
870 n.type == "mention" && n.activity_id == reply_activity.id
874 test "add mute", %{user: user, activity: activity} do
875 {:ok, _} = CommonAPI.add_mute(user, activity)
876 assert CommonAPI.thread_muted?(user, activity)
879 test "add expiring mute", %{user: user, activity: activity} do
880 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
881 assert CommonAPI.thread_muted?(user, activity)
883 worker = Pleroma.Workers.MuteExpireWorker
884 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
891 assert :ok = perform_job(worker, args)
892 refute CommonAPI.thread_muted?(user, activity)
895 test "remove mute", %{user: user, activity: activity} do
896 CommonAPI.add_mute(user, activity)
897 {:ok, _} = CommonAPI.remove_mute(user, activity)
898 refute CommonAPI.thread_muted?(user, activity)
901 test "remove mute by ids", %{user: user, activity: activity} do
902 CommonAPI.add_mute(user, activity)
903 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
904 refute CommonAPI.thread_muted?(user, activity)
907 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
908 CommonAPI.add_mute(user, activity)
909 {:error, _} = CommonAPI.add_mute(user, activity)
913 describe "reports" do
914 test "creates a report" do
915 reporter = insert(:user)
916 target_user = insert(:user)
918 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
920 reporter_ap_id = reporter.ap_id
921 target_ap_id = target_user.ap_id
922 activity_ap_id = activity.data["id"]
926 account_id: target_user.id,
928 status_ids: [activity.id]
933 "id" => activity_ap_id,
934 "content" => "foobar",
935 "published" => activity.object.data["published"],
936 "actor" => AccountView.render("show.json", %{user: target_user})
939 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
942 actor: ^reporter_ap_id,
945 "content" => ^comment,
946 "object" => [^target_ap_id, ^note_obj],
952 test "updates report state" do
953 [reporter, target_user] = insert_pair(:user)
954 activity = insert(:note_activity, user: target_user)
956 {:ok, %Activity{id: report_id}} =
957 CommonAPI.report(reporter, %{
958 account_id: target_user.id,
959 comment: "I feel offended",
960 status_ids: [activity.id]
963 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
965 assert report.data["state"] == "resolved"
967 [reported_user, activity_id] = report.data["object"]
969 assert reported_user == target_user.ap_id
970 assert activity_id == activity.data["id"]
973 test "does not update report state when state is unsupported" do
974 [reporter, target_user] = insert_pair(:user)
975 activity = insert(:note_activity, user: target_user)
977 {:ok, %Activity{id: report_id}} =
978 CommonAPI.report(reporter, %{
979 account_id: target_user.id,
980 comment: "I feel offended",
981 status_ids: [activity.id]
984 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
987 test "updates state of multiple reports" do
988 [reporter, target_user] = insert_pair(:user)
989 activity = insert(:note_activity, user: target_user)
991 {:ok, %Activity{id: first_report_id}} =
992 CommonAPI.report(reporter, %{
993 account_id: target_user.id,
994 comment: "I feel offended",
995 status_ids: [activity.id]
998 {:ok, %Activity{id: second_report_id}} =
999 CommonAPI.report(reporter, %{
1000 account_id: target_user.id,
1001 comment: "I feel very offended!",
1002 status_ids: [activity.id]
1006 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1008 first_report = Activity.get_by_id(first_report_id)
1009 second_report = Activity.get_by_id(second_report_id)
1011 assert report_ids -- [first_report_id, second_report_id] == []
1012 assert first_report.data["state"] == "resolved"
1013 assert second_report.data["state"] == "resolved"
1017 describe "reblog muting" do
1019 muter = insert(:user)
1021 muted = insert(:user)
1023 [muter: muter, muted: muted]
1026 test "add a reblog mute", %{muter: muter, muted: muted} do
1027 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1029 assert User.showing_reblogs?(muter, muted) == false
1032 test "remove a reblog mute", %{muter: muter, muted: muted} do
1033 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1034 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1036 assert User.showing_reblogs?(muter, muted) == true
1040 describe "follow/2" do
1041 test "directly follows a non-locked local user" do
1042 [follower, followed] = insert_pair(:user)
1043 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1045 assert User.following?(follower, followed)
1049 describe "unfollow/2" do
1050 test "also unsubscribes a user" do
1051 [follower, followed] = insert_pair(:user)
1052 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1053 {:ok, _subscription} = User.subscribe(follower, followed)
1055 assert User.subscribed_to?(follower, followed)
1057 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1059 refute User.subscribed_to?(follower, followed)
1062 test "removes a pending follow for a local user" do
1063 follower = insert(:user)
1064 followed = insert(:user, is_locked: true)
1066 assert {:ok, follower, followed, %{id: _activity_id, data: %{"state" => "pending"}}} =
1067 CommonAPI.follow(follower, followed)
1069 assert User.get_follow_state(follower, followed) == :follow_pending
1070 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1071 assert User.get_follow_state(follower, followed) == nil
1073 assert is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed))
1078 "object" => %{"type" => "Follow"}
1080 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1083 test "cancels a pending follow for a remote user" do
1084 follower = insert(:user)
1085 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1087 assert {:ok, follower, followed, %{id: _activity_id, data: %{"state" => "pending"}}} =
1088 CommonAPI.follow(follower, followed)
1090 assert User.get_follow_state(follower, followed) == :follow_pending
1091 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1092 assert User.get_follow_state(follower, followed) == nil
1094 assert is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed))
1099 "object" => %{"type" => "Follow"}
1101 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1105 describe "accept_follow_request/2" do
1106 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1107 user = insert(:user, is_locked: true)
1108 follower = insert(:user)
1109 follower_two = insert(:user)
1111 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1112 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1113 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1115 assert follow_activity.data["state"] == "pending"
1116 assert follow_activity_two.data["state"] == "pending"
1117 assert follow_activity_three.data["state"] == "pending"
1119 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1121 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1122 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1123 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1126 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1127 user = insert(:user, is_locked: true)
1128 follower = insert(:user)
1129 follower_two = insert(:user)
1131 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1132 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1133 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1135 assert follow_activity.data["state"] == "pending"
1136 assert follow_activity_two.data["state"] == "pending"
1137 assert follow_activity_three.data["state"] == "pending"
1139 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1141 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1142 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1143 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1146 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1147 user = insert(:user, is_locked: true)
1148 not_follower = insert(:user)
1149 CommonAPI.accept_follow_request(not_follower, user)
1151 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1155 describe "vote/3" do
1156 test "does not allow to vote twice" do
1157 user = insert(:user)
1158 other_user = insert(:user)
1161 CommonAPI.post(user, %{
1162 status: "Am I cute?",
1163 poll: %{options: ["Yes", "No"], expires_in: 20}
1166 object = Object.normalize(activity, fetch: false)
1168 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1170 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1174 describe "get_user/1" do
1175 test "gets user by ap_id" do
1176 user = insert(:user)
1177 assert CommonAPI.get_user(user.ap_id) == user
1180 test "gets user by guessed nickname" do
1181 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1182 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1189 nickname: "erroruser@example.com"
1190 } = CommonAPI.get_user("")
1194 describe "with `local` visibility" do
1195 setup do: clear_config([:instance, :federating], true)
1198 user = insert(:user)
1200 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1201 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1203 assert Visibility.is_local_public?(activity)
1204 assert_not_called(Pleroma.Web.Federator.publish(activity))
1209 user = insert(:user)
1211 {:ok, %Activity{id: activity_id}} =
1212 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1214 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1215 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1216 CommonAPI.delete(activity_id, user)
1218 assert Visibility.is_local_public?(activity)
1219 assert_not_called(Pleroma.Web.Federator.publish(activity))
1224 user = insert(:user)
1225 other_user = insert(:user)
1227 {:ok, %Activity{id: activity_id}} =
1228 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1230 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1231 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1232 CommonAPI.repeat(activity_id, user)
1234 assert Visibility.is_local_public?(activity)
1235 refute called(Pleroma.Web.Federator.publish(activity))
1240 user = insert(:user)
1241 other_user = insert(:user)
1243 {:ok, %Activity{id: activity_id}} =
1244 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1246 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1248 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1249 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1250 CommonAPI.unrepeat(activity_id, user)
1252 assert Visibility.is_local_public?(activity)
1253 refute called(Pleroma.Web.Federator.publish(activity))
1258 user = insert(:user)
1259 other_user = insert(:user)
1261 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1263 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1264 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1265 CommonAPI.favorite(user, activity.id)
1267 assert Visibility.is_local_public?(activity)
1268 refute called(Pleroma.Web.Federator.publish(activity))
1272 test "unfavorite" do
1273 user = insert(:user)
1274 other_user = insert(:user)
1276 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1278 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1280 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1281 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1282 assert Visibility.is_local_public?(activity)
1283 refute called(Pleroma.Web.Federator.publish(activity))
1287 test "react_with_emoji" do
1288 user = insert(:user)
1289 other_user = insert(:user)
1290 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1292 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1293 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1294 CommonAPI.react_with_emoji(activity.id, user, "👍")
1296 assert Visibility.is_local_public?(activity)
1297 refute called(Pleroma.Web.Federator.publish(activity))
1301 test "unreact_with_emoji" do
1302 user = insert(:user)
1303 other_user = insert(:user)
1304 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1306 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1308 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1309 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1310 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1312 assert Visibility.is_local_public?(activity)
1313 refute called(Pleroma.Web.Federator.publish(activity))
1318 describe "update/3" do
1319 test "updates a post" do
1320 user = insert(:user)
1321 {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1"})
1323 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1325 updated_object = Object.normalize(updated)
1326 assert updated_object.data["content"] == "updated 2"
1327 assert Map.get(updated_object.data, "summary", "") == ""
1328 assert Map.has_key?(updated_object.data, "updated")
1331 test "does not change visibility" do
1332 user = insert(:user)
1335 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1", visibility: "private"})
1337 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1339 updated_object = Object.normalize(updated)
1340 assert updated_object.data["content"] == "updated 2"
1341 assert Map.get(updated_object.data, "summary", "") == ""
1342 assert Visibility.get_visibility(updated_object) == "private"
1343 assert Visibility.get_visibility(updated) == "private"
1346 test "updates a post with emoji" do
1347 [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1349 user = insert(:user)
1352 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1354 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1356 updated_object = Object.normalize(updated)
1357 assert updated_object.data["content"] == "updated 2 :#{emoji2}:"
1358 assert %{^emoji2 => _} = updated_object.data["emoji"]
1361 test "updates a post with emoji and federate properly" do
1362 [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1364 user = insert(:user)
1367 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1369 clear_config([:instance, :federating], true)
1371 with_mock Pleroma.Web.Federator,
1372 publish: fn _p -> nil end do
1373 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1375 assert updated.data["object"]["content"] == "updated 2 :#{emoji2}:"
1376 assert %{^emoji2 => _} = updated.data["object"]["emoji"]
1378 assert called(Pleroma.Web.Federator.publish(updated))
1382 test "editing a post that copied a remote title with remote emoji should keep that emoji" do
1383 remote_emoji_uri = "https://remote.org/emoji.png"
1389 "summary" => ":remoteemoji:",
1391 "remoteemoji" => remote_emoji_uri
1396 "name" => "remoteemoji",
1397 "icon" => %{"url" => remote_emoji_uri}
1403 note_activity = insert(:note_activity, note: note)
1405 user = insert(:user)
1408 CommonAPI.post(user, %{
1410 spoiler_text: ":remoteemoji:",
1411 in_reply_to_id: note_activity.id
1414 assert reply.object.data["emoji"]["remoteemoji"] == remote_emoji_uri
1417 CommonAPI.update(user, reply, %{status: "reply mew mew", spoiler_text: ":remoteemoji:"})
1419 edited_note = Pleroma.Object.normalize(edit)
1421 assert edited_note.data["emoji"]["remoteemoji"] == remote_emoji_uri
1424 test "respects MRF" do
1425 user = insert(:user)
1427 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
1428 clear_config([:mrf_keyword, :replace], [{"updated", "mewmew"}])
1430 {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "updated 1"})
1431 assert Object.normalize(activity).data["summary"] == "mewmew 1"
1433 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1435 updated_object = Object.normalize(updated)
1436 assert updated_object.data["content"] == "mewmew 2"
1437 assert Map.get(updated_object.data, "summary", "") == ""
1438 assert Map.has_key?(updated_object.data, "updated")