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
29 setup do: clear_config([:instance, :safe_dm_mentions])
30 setup do: clear_config([:instance, :limit])
31 setup do: clear_config([:instance, :max_pinned_statuses])
33 describe "posting polls" do
34 test "it posts a poll" do
38 CommonAPI.post(user, %{
39 status: "who is the best",
40 poll: %{expires_in: 600, options: ["reimu", "marisa"]}
43 object = Object.normalize(activity, fetch: false)
45 assert object.data["type"] == "Question"
46 assert object.data["oneOf"] |> length() == 2
50 args: %{op: "poll_end", activity_id: activity.id},
51 scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
56 describe "blocking" do
58 blocker = insert(:user)
59 blocked = insert(:user)
60 User.follow(blocker, blocked)
61 User.follow(blocked, blocker)
62 %{blocker: blocker, blocked: blocked}
65 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
66 clear_config([:instance, :federating], true)
68 with_mock Pleroma.Web.Federator,
69 publish: fn _ -> nil end do
70 assert {:ok, block} = CommonAPI.block(blocker, blocked)
73 assert User.blocks?(blocker, blocked)
74 refute User.following?(blocker, blocked)
75 refute User.following?(blocked, blocker)
77 assert called(Pleroma.Web.Federator.publish(block))
81 test "it blocks and does not federate if outgoing blocks are disabled", %{
85 clear_config([:instance, :federating], true)
86 clear_config([:activitypub, :outgoing_blocks], false)
88 with_mock Pleroma.Web.Federator,
89 publish: fn _ -> nil end do
90 assert {:ok, block} = CommonAPI.block(blocker, blocked)
93 assert User.blocks?(blocker, blocked)
94 refute User.following?(blocker, blocked)
95 refute User.following?(blocked, blocker)
97 refute called(Pleroma.Web.Federator.publish(block))
102 describe "posting chat messages" do
103 setup do: clear_config([:instance, :chat_limit])
105 test "it posts a self-chat" do
106 author = insert(:user)
110 CommonAPI.post_chat_message(
113 "remember to buy milk when milk truk arive"
116 assert activity.data["type"] == "Create"
119 test "it posts a chat message without content but with an attachment" do
120 author = insert(:user)
121 recipient = insert(:user)
124 content_type: "image/jpeg",
125 path: Path.absname("test/fixtures/image.jpg"),
126 filename: "an_image.jpg"
129 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
133 Pleroma.Web.Streamer,
145 send: fn _ -> nil end
150 CommonAPI.post_chat_message(
158 Notification.for_user_and_activity(recipient, activity)
159 |> Repo.preload(:activity)
161 assert called(Pleroma.Web.Push.send(notification))
162 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
163 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
169 test "it adds html newlines" do
170 author = insert(:user)
171 recipient = insert(:user)
173 other_user = insert(:user)
176 CommonAPI.post_chat_message(
182 assert other_user.ap_id not in activity.recipients
184 object = Object.normalize(activity, fetch: false)
186 assert object.data["content"] == "uguu<br/>uguuu"
189 test "it linkifies" do
190 author = insert(:user)
191 recipient = insert(:user)
193 other_user = insert(:user)
196 CommonAPI.post_chat_message(
199 "https://example.org is the site of @#{other_user.nickname} #2hu"
202 assert other_user.ap_id not in activity.recipients
204 object = Object.normalize(activity, fetch: false)
206 assert object.data["content"] ==
207 "<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=\"#{
209 }\" 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>"
212 test "it posts a chat message" do
213 author = insert(:user)
214 recipient = insert(:user)
217 CommonAPI.post_chat_message(
220 "a test message <script>alert('uuu')</script> :firefox:"
223 assert activity.data["type"] == "Create"
224 assert activity.local
225 object = Object.normalize(activity, fetch: false)
227 assert object.data["type"] == "ChatMessage"
228 assert object.data["to"] == [recipient.ap_id]
230 assert object.data["content"] ==
231 "a test message <script>alert('uuu')</script> :firefox:"
233 assert object.data["emoji"] == %{
234 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
237 assert Chat.get(author.id, recipient.ap_id)
238 assert Chat.get(recipient.id, author.ap_id)
240 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
243 test "it reject messages over the local limit" do
244 clear_config([:instance, :chat_limit], 2)
246 author = insert(:user)
247 recipient = insert(:user)
250 CommonAPI.post_chat_message(
256 assert message == :content_too_long
259 test "it reject messages via MRF" do
260 clear_config([:mrf_keyword, :reject], ["GNO"])
261 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
263 author = insert(:user)
264 recipient = insert(:user)
266 assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
267 CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
271 describe "unblocking" do
272 test "it works even without an existing block activity" do
273 blocked = insert(:user)
274 blocker = insert(:user)
275 User.block(blocker, blocked)
277 assert User.blocks?(blocker, blocked)
278 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
279 refute User.blocks?(blocker, blocked)
283 describe "deletion" do
284 test "it works with pruned objects" do
287 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
289 clear_config([:instance, :federating], true)
291 Object.normalize(post, fetch: false)
294 with_mock Pleroma.Web.Federator,
295 publish: fn _ -> nil end do
296 assert {:ok, delete} = CommonAPI.delete(post.id, user)
298 assert called(Pleroma.Web.Federator.publish(delete))
301 refute Activity.get_by_id(post.id)
304 test "it allows users to delete their posts" do
307 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
309 clear_config([:instance, :federating], true)
311 with_mock Pleroma.Web.Federator,
312 publish: fn _ -> nil end do
313 assert {:ok, delete} = CommonAPI.delete(post.id, user)
315 assert called(Pleroma.Web.Federator.publish(delete))
318 refute Activity.get_by_id(post.id)
321 test "it does not allow a user to delete their posts" do
323 other_user = insert(:user)
325 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
327 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
328 assert Activity.get_by_id(post.id)
331 test "it allows moderators to delete other user's posts" do
333 moderator = insert(:user, is_moderator: true)
335 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
337 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
340 refute Activity.get_by_id(post.id)
343 test "it allows admins to delete other user's posts" do
345 moderator = insert(:user, is_admin: true)
347 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
349 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
352 refute Activity.get_by_id(post.id)
355 test "superusers deleting non-local posts won't federate the delete" do
356 # This is the user of the ingested activity
360 ap_id: "http://mastodon.example.org/users/admin",
361 last_refreshed_at: NaiveDateTime.utc_now()
364 moderator = insert(:user, is_admin: true)
367 File.read!("test/fixtures/mastodon-post-activity.json")
370 {:ok, post} = Transmogrifier.handle_incoming(data)
372 with_mock Pleroma.Web.Federator,
373 publish: fn _ -> nil end do
374 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
376 refute called(Pleroma.Web.Federator.publish(:_))
379 refute Activity.get_by_id(post.id)
383 test "favoriting race condition" do
385 users_serial = insert_list(10, :user)
386 users = insert_list(10, :user)
388 {:ok, activity} = CommonAPI.post(user, %{status: "."})
391 |> Enum.map(fn user ->
392 CommonAPI.favorite(user, activity.id)
395 object = Object.get_by_ap_id(activity.data["object"])
396 assert object.data["like_count"] == 10
399 |> Enum.map(fn user ->
401 CommonAPI.favorite(user, activity.id)
404 |> Enum.map(&Task.await/1)
406 object = Object.get_by_ap_id(activity.data["object"])
407 assert object.data["like_count"] == 20
410 test "repeating race condition" do
412 users_serial = insert_list(10, :user)
413 users = insert_list(10, :user)
415 {:ok, activity} = CommonAPI.post(user, %{status: "."})
418 |> Enum.map(fn user ->
419 CommonAPI.repeat(activity.id, user)
422 object = Object.get_by_ap_id(activity.data["object"])
423 assert object.data["announcement_count"] == 10
426 |> Enum.map(fn user ->
428 CommonAPI.repeat(activity.id, user)
431 |> Enum.map(&Task.await/1)
433 object = Object.get_by_ap_id(activity.data["object"])
434 assert object.data["announcement_count"] == 20
437 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
439 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
441 [participation] = Participation.for_user(user)
444 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
446 assert Visibility.is_direct?(convo_reply)
448 assert activity.data["context"] == convo_reply.data["context"]
451 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
453 jafnhar = insert(:user)
454 tridi = insert(:user)
457 CommonAPI.post(har, %{
458 status: "@#{jafnhar.nickname} hey",
462 assert har.ap_id in activity.recipients
463 assert jafnhar.ap_id in activity.recipients
465 [participation] = Participation.for_user(har)
468 CommonAPI.post(har, %{
469 status: "I don't really like @#{tridi.nickname}",
470 visibility: "direct",
471 in_reply_to_status_id: activity.id,
472 in_reply_to_conversation_id: participation.id
475 assert har.ap_id in activity.recipients
476 assert jafnhar.ap_id in activity.recipients
477 refute tridi.ap_id in activity.recipients
480 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
482 jafnhar = insert(:user)
483 tridi = insert(:user)
485 clear_config([:instance, :safe_dm_mentions], true)
488 CommonAPI.post(har, %{
489 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
493 refute tridi.ap_id in activity.recipients
494 assert jafnhar.ap_id in activity.recipients
497 test "it de-duplicates tags" do
499 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
501 object = Object.normalize(activity, fetch: false)
503 assert object.data["tag"] == ["2hu"]
506 test "it adds emoji in the object" do
508 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
510 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
513 describe "posting" do
514 test "it adds an emoji on an external site" do
516 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
518 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
519 assert url == "https://example.com/emoji.png"
521 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
523 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
524 assert url == "#{Pleroma.Web.base_url()}/emoji/blank.png"
527 test "deactivated users can't post" do
528 user = insert(:user, is_active: false)
529 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
532 test "it supports explicit addressing" do
534 user_two = insert(:user)
535 user_three = insert(:user)
536 user_four = insert(:user)
539 CommonAPI.post(user, %{
541 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
542 to: [user_two.nickname, user_four.nickname, "nonexistent"]
545 assert user.ap_id in activity.recipients
546 assert user_two.ap_id in activity.recipients
547 assert user_four.ap_id in activity.recipients
548 refute user_three.ap_id in activity.recipients
551 test "it filters out obviously bad tags when accepting a post as HTML" do
554 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
557 CommonAPI.post(user, %{
559 content_type: "text/html"
562 object = Object.normalize(activity, fetch: false)
564 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
565 assert object.data["source"] == post
568 test "it filters out obviously bad tags when accepting a post as Markdown" do
571 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
574 CommonAPI.post(user, %{
576 content_type: "text/markdown"
579 object = Object.normalize(activity, fetch: false)
581 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
582 assert object.data["source"] == post
585 test "it does not allow replies to direct messages that are not direct messages themselves" do
588 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
591 CommonAPI.post(user, %{
593 visibility: "direct",
594 in_reply_to_status_id: activity.id
597 Enum.each(["public", "private", "unlisted"], fn visibility ->
598 assert {:error, "The message visibility must be direct"} =
599 CommonAPI.post(user, %{
601 visibility: visibility,
602 in_reply_to_status_id: activity.id
607 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
609 other_user = insert(:user)
610 third_user = insert(:user)
612 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
615 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
617 # The OP is implicitly added
618 assert user.ap_id in open_answer.recipients
620 {:ok, secret_answer} =
621 CommonAPI.post(other_user, %{
622 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
623 in_reply_to_status_id: post.id,
627 assert third_user.ap_id in secret_answer.recipients
629 # The OP is not added
630 refute user.ap_id in secret_answer.recipients
633 test "it allows to address a list" do
635 {:ok, list} = Pleroma.List.create("foo", user)
637 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
639 assert activity.data["bcc"] == [list.ap_id]
640 assert activity.recipients == [list.ap_id, user.ap_id]
641 assert activity.data["listMessage"] == list.ap_id
644 test "it returns error when status is empty and no attachments" do
647 assert {:error, "Cannot post an empty status without attachments"} =
648 CommonAPI.post(user, %{status: ""})
651 test "it validates character limits are correctly enforced" do
652 clear_config([:instance, :limit], 5)
656 assert {:error, "The status is over the character limit"} =
657 CommonAPI.post(user, %{status: "foobar"})
659 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
662 test "it can handle activities that expire" do
665 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
667 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
670 worker: Pleroma.Workers.PurgeExpiredActivity,
671 args: %{activity_id: activity.id},
672 scheduled_at: expires_at
677 describe "reactions" do
678 test "reacting to a status with an emoji" do
680 other_user = insert(:user)
682 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
684 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
686 assert reaction.data["actor"] == user.ap_id
687 assert reaction.data["content"] == "👍"
689 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
691 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
694 test "unreacting to a status with an emoji" do
696 other_user = insert(:user)
698 clear_config([:instance, :federating], true)
700 with_mock Pleroma.Web.Federator,
701 publish: fn _ -> nil end do
702 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
703 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
705 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
707 assert unreaction.data["type"] == "Undo"
708 assert unreaction.data["object"] == reaction.data["id"]
709 assert unreaction.local
711 # On federation, it contains the undone (and deleted) object
712 unreaction_with_object = %{
714 | data: Map.put(unreaction.data, "object", reaction.data)
717 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
721 test "repeating a status" do
723 other_user = insert(:user)
725 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
727 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
728 assert Visibility.is_public?(announce_activity)
731 test "can't repeat a repeat" do
733 other_user = insert(:user)
734 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
736 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
738 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
741 test "repeating a status privately" do
743 other_user = insert(:user)
745 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
747 {:ok, %Activity{} = announce_activity} =
748 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
750 assert Visibility.is_private?(announce_activity)
751 refute Visibility.visible_for_user?(announce_activity, nil)
754 test "author can repeat own private statuses" do
755 author = insert(:user)
756 follower = insert(:user)
757 CommonAPI.follow(follower, author)
759 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
761 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
763 assert Visibility.is_private?(announce_activity)
764 refute Visibility.visible_for_user?(announce_activity, nil)
766 assert Visibility.visible_for_user?(activity, follower)
767 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
770 test "favoriting a status" do
772 other_user = insert(:user)
774 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
776 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
777 assert data["type"] == "Like"
778 assert data["actor"] == user.ap_id
779 assert data["object"] == post_activity.data["object"]
782 test "retweeting a status twice returns the status" do
784 other_user = insert(:user)
786 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
787 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
788 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
791 test "favoriting a status twice returns ok, but without the like activity" do
793 other_user = insert(:user)
795 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
796 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
797 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
801 describe "pinned statuses" do
803 clear_config([:instance, :max_pinned_statuses], 1)
806 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
808 [user: user, activity: activity]
811 test "pin status", %{user: user, activity: activity} do
812 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
815 user = refresh_record(user)
817 assert %User{pinned_activities: [^id]} = user
820 test "pin poll", %{user: user} do
822 CommonAPI.post(user, %{
823 status: "How is fediverse today?",
824 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
827 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
830 user = refresh_record(user)
832 assert %User{pinned_activities: [^id]} = user
835 test "unlisted statuses can be pinned", %{user: user} do
836 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
837 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
840 test "only self-authored can be pinned", %{activity: activity} do
843 assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
846 test "max pinned statuses", %{user: user, activity: activity_one} do
847 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
849 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
851 user = refresh_record(user)
853 assert {:error, "You have already pinned the maximum number of statuses"} =
854 CommonAPI.pin(activity_two.id, user)
857 test "unpin status", %{user: user, activity: activity} do
858 {:ok, activity} = CommonAPI.pin(activity.id, user)
860 user = refresh_record(user)
864 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
866 user = refresh_record(user)
868 assert %User{pinned_activities: []} = user
871 test "should unpin when deleting a status", %{user: user, activity: activity} do
872 {:ok, activity} = CommonAPI.pin(activity.id, user)
874 user = refresh_record(user)
876 assert {:ok, _} = CommonAPI.delete(activity.id, user)
878 user = refresh_record(user)
880 assert %User{pinned_activities: []} = user
884 describe "mute tests" do
888 activity = insert(:note_activity)
890 [user: user, activity: activity]
893 test "marks notifications as read after mute" do
894 author = insert(:user)
895 activity = insert(:note_activity, user: author)
897 friend1 = insert(:user)
898 friend2 = insert(:user)
900 {:ok, reply_activity} =
904 status: "@#{author.nickname} @#{friend1.nickname} test reply",
905 in_reply_to_status_id: activity.id
909 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
910 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
912 assert Repo.aggregate(
913 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
917 unread_notifications =
918 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
920 assert Enum.any?(unread_notifications, fn n ->
921 n.type == "favourite" && n.activity_id == favorite_activity.id
924 assert Enum.any?(unread_notifications, fn n ->
925 n.type == "reblog" && n.activity_id == repeat_activity.id
928 assert Enum.any?(unread_notifications, fn n ->
929 n.type == "mention" && n.activity_id == reply_activity.id
932 {:ok, _} = CommonAPI.add_mute(author, activity)
933 assert CommonAPI.thread_muted?(author, activity)
935 assert Repo.aggregate(
936 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
941 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
943 assert Enum.any?(read_notifications, fn n ->
944 n.type == "favourite" && n.activity_id == favorite_activity.id
947 assert Enum.any?(read_notifications, fn n ->
948 n.type == "reblog" && n.activity_id == repeat_activity.id
951 assert Enum.any?(read_notifications, fn n ->
952 n.type == "mention" && n.activity_id == reply_activity.id
956 test "add mute", %{user: user, activity: activity} do
957 {:ok, _} = CommonAPI.add_mute(user, activity)
958 assert CommonAPI.thread_muted?(user, activity)
961 test "add expiring mute", %{user: user, activity: activity} do
962 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
963 assert CommonAPI.thread_muted?(user, activity)
965 worker = Pleroma.Workers.MuteExpireWorker
966 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
973 assert :ok = perform_job(worker, args)
974 refute CommonAPI.thread_muted?(user, activity)
977 test "remove mute", %{user: user, activity: activity} do
978 CommonAPI.add_mute(user, activity)
979 {:ok, _} = CommonAPI.remove_mute(user, activity)
980 refute CommonAPI.thread_muted?(user, activity)
983 test "remove mute by ids", %{user: user, activity: activity} do
984 CommonAPI.add_mute(user, activity)
985 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
986 refute CommonAPI.thread_muted?(user, activity)
989 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
990 CommonAPI.add_mute(user, activity)
991 {:error, _} = CommonAPI.add_mute(user, activity)
995 describe "reports" do
996 test "creates a report" do
997 reporter = insert(:user)
998 target_user = insert(:user)
1000 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1002 reporter_ap_id = reporter.ap_id
1003 target_ap_id = target_user.ap_id
1004 activity_ap_id = activity.data["id"]
1008 account_id: target_user.id,
1010 status_ids: [activity.id]
1015 "id" => activity_ap_id,
1016 "content" => "foobar",
1017 "published" => activity.object.data["published"],
1018 "actor" => AccountView.render("show.json", %{user: target_user})
1021 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1024 actor: ^reporter_ap_id,
1027 "content" => ^comment,
1028 "object" => [^target_ap_id, ^note_obj],
1034 test "updates report state" do
1035 [reporter, target_user] = insert_pair(:user)
1036 activity = insert(:note_activity, user: target_user)
1038 {:ok, %Activity{id: report_id}} =
1039 CommonAPI.report(reporter, %{
1040 account_id: target_user.id,
1041 comment: "I feel offended",
1042 status_ids: [activity.id]
1045 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1047 assert report.data["state"] == "resolved"
1049 [reported_user, activity_id] = report.data["object"]
1051 assert reported_user == target_user.ap_id
1052 assert activity_id == activity.data["id"]
1055 test "does not update report state when state is unsupported" do
1056 [reporter, target_user] = insert_pair(:user)
1057 activity = insert(:note_activity, user: target_user)
1059 {:ok, %Activity{id: report_id}} =
1060 CommonAPI.report(reporter, %{
1061 account_id: target_user.id,
1062 comment: "I feel offended",
1063 status_ids: [activity.id]
1066 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1069 test "updates state of multiple reports" do
1070 [reporter, target_user] = insert_pair(:user)
1071 activity = insert(:note_activity, user: target_user)
1073 {:ok, %Activity{id: first_report_id}} =
1074 CommonAPI.report(reporter, %{
1075 account_id: target_user.id,
1076 comment: "I feel offended",
1077 status_ids: [activity.id]
1080 {:ok, %Activity{id: second_report_id}} =
1081 CommonAPI.report(reporter, %{
1082 account_id: target_user.id,
1083 comment: "I feel very offended!",
1084 status_ids: [activity.id]
1088 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1090 first_report = Activity.get_by_id(first_report_id)
1091 second_report = Activity.get_by_id(second_report_id)
1093 assert report_ids -- [first_report_id, second_report_id] == []
1094 assert first_report.data["state"] == "resolved"
1095 assert second_report.data["state"] == "resolved"
1099 describe "reblog muting" do
1101 muter = insert(:user)
1103 muted = insert(:user)
1105 [muter: muter, muted: muted]
1108 test "add a reblog mute", %{muter: muter, muted: muted} do
1109 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1111 assert User.showing_reblogs?(muter, muted) == false
1114 test "remove a reblog mute", %{muter: muter, muted: muted} do
1115 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1116 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1118 assert User.showing_reblogs?(muter, muted) == true
1122 describe "follow/2" do
1123 test "directly follows a non-locked local user" do
1124 [follower, followed] = insert_pair(:user)
1125 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1127 assert User.following?(follower, followed)
1131 describe "unfollow/2" do
1132 test "also unsubscribes a user" do
1133 [follower, followed] = insert_pair(:user)
1134 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1135 {:ok, _subscription} = User.subscribe(follower, followed)
1137 assert User.subscribed_to?(follower, followed)
1139 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1141 refute User.subscribed_to?(follower, followed)
1144 test "cancels a pending follow for a local user" do
1145 follower = insert(:user)
1146 followed = insert(:user, is_locked: true)
1148 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1149 CommonAPI.follow(follower, followed)
1151 assert User.get_follow_state(follower, followed) == :follow_pending
1152 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1153 assert User.get_follow_state(follower, followed) == nil
1155 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1156 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1161 "object" => %{"type" => "Follow", "state" => "cancelled"}
1163 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1166 test "cancels a pending follow for a remote user" do
1167 follower = insert(:user)
1168 followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
1170 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1171 CommonAPI.follow(follower, followed)
1173 assert User.get_follow_state(follower, followed) == :follow_pending
1174 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1175 assert User.get_follow_state(follower, followed) == nil
1177 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1178 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1183 "object" => %{"type" => "Follow", "state" => "cancelled"}
1185 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1189 describe "accept_follow_request/2" do
1190 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1191 user = insert(:user, is_locked: true)
1192 follower = insert(:user)
1193 follower_two = insert(:user)
1195 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1196 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1197 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1199 assert follow_activity.data["state"] == "pending"
1200 assert follow_activity_two.data["state"] == "pending"
1201 assert follow_activity_three.data["state"] == "pending"
1203 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1205 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1206 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1207 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1210 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1211 user = insert(:user, is_locked: true)
1212 follower = insert(:user)
1213 follower_two = insert(:user)
1215 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1216 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1217 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1219 assert follow_activity.data["state"] == "pending"
1220 assert follow_activity_two.data["state"] == "pending"
1221 assert follow_activity_three.data["state"] == "pending"
1223 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1225 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1226 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1227 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1230 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1231 user = insert(:user, is_locked: true)
1232 not_follower = insert(:user)
1233 CommonAPI.accept_follow_request(not_follower, user)
1235 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1239 describe "vote/3" do
1240 test "does not allow to vote twice" do
1241 user = insert(:user)
1242 other_user = insert(:user)
1245 CommonAPI.post(user, %{
1246 status: "Am I cute?",
1247 poll: %{options: ["Yes", "No"], expires_in: 20}
1250 object = Object.normalize(activity, fetch: false)
1252 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1254 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1258 describe "listen/2" do
1259 test "returns a valid activity" do
1260 user = insert(:user)
1263 CommonAPI.listen(user, %{
1264 title: "lain radio episode 1",
1265 album: "lain radio",
1270 object = Object.normalize(activity, fetch: false)
1272 assert object.data["title"] == "lain radio episode 1"
1274 assert Visibility.get_visibility(activity) == "public"
1277 test "respects visibility=private" do
1278 user = insert(:user)
1281 CommonAPI.listen(user, %{
1282 title: "lain radio episode 1",
1283 album: "lain radio",
1286 visibility: "private"
1289 object = Object.normalize(activity, fetch: false)
1291 assert object.data["title"] == "lain radio episode 1"
1293 assert Visibility.get_visibility(activity) == "private"
1297 describe "get_user/1" do
1298 test "gets user by ap_id" do
1299 user = insert(:user)
1300 assert CommonAPI.get_user(user.ap_id) == user
1303 test "gets user by guessed nickname" do
1304 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1305 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1312 nickname: "erroruser@example.com"
1313 } = CommonAPI.get_user("")
1317 describe "with `local` visibility" do
1318 setup do: clear_config([:instance, :federating], true)
1321 user = insert(:user)
1323 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1324 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1326 assert Visibility.is_local_public?(activity)
1327 assert_not_called(Pleroma.Web.Federator.publish(activity))
1332 user = insert(:user)
1334 {:ok, %Activity{id: activity_id}} =
1335 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1337 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1338 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1339 CommonAPI.delete(activity_id, user)
1341 assert Visibility.is_local_public?(activity)
1342 assert_not_called(Pleroma.Web.Federator.publish(activity))
1347 user = insert(:user)
1348 other_user = insert(:user)
1350 {:ok, %Activity{id: activity_id}} =
1351 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1353 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1354 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1355 CommonAPI.repeat(activity_id, user)
1357 assert Visibility.is_local_public?(activity)
1358 refute called(Pleroma.Web.Federator.publish(activity))
1363 user = insert(:user)
1364 other_user = insert(:user)
1366 {:ok, %Activity{id: activity_id}} =
1367 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1369 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1371 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1372 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1373 CommonAPI.unrepeat(activity_id, user)
1375 assert Visibility.is_local_public?(activity)
1376 refute called(Pleroma.Web.Federator.publish(activity))
1381 user = insert(:user)
1382 other_user = insert(:user)
1384 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1386 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1387 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1388 CommonAPI.favorite(user, activity.id)
1390 assert Visibility.is_local_public?(activity)
1391 refute called(Pleroma.Web.Federator.publish(activity))
1395 test "unfavorite" do
1396 user = insert(:user)
1397 other_user = insert(:user)
1399 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1401 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1403 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1404 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1405 assert Visibility.is_local_public?(activity)
1406 refute called(Pleroma.Web.Federator.publish(activity))
1410 test "react_with_emoji" do
1411 user = insert(:user)
1412 other_user = insert(:user)
1413 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1415 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1416 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1417 CommonAPI.react_with_emoji(activity.id, user, "👍")
1419 assert Visibility.is_local_public?(activity)
1420 refute called(Pleroma.Web.Federator.publish(activity))
1424 test "unreact_with_emoji" do
1425 user = insert(:user)
1426 other_user = insert(:user)
1427 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1429 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1431 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1432 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1433 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1435 assert Visibility.is_local_public?(activity)
1436 refute called(Pleroma.Web.Federator.publish(activity))