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)
75 with_mock Pleroma.Web.Federator,
76 publish: fn _ -> nil end do
77 assert User.get_follow_state(blocker, blocked) == :follow_accept
78 refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
80 assert {:ok, block} = CommonAPI.block(blocker, blocked)
83 assert User.blocks?(blocker, blocked)
84 refute User.following?(blocker, blocked)
85 refute User.following?(blocked, blocker)
87 refute User.get_follow_state(blocker, blocked)
89 assert %{data: %{"state" => "reject"}} =
90 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
92 assert called(Pleroma.Web.Federator.publish(block))
96 test "it blocks and does not federate if outgoing blocks are disabled", %{
100 clear_config([:instance, :federating], true)
101 clear_config([:activitypub, :outgoing_blocks], false)
103 with_mock Pleroma.Web.Federator,
104 publish: fn _ -> nil end do
105 assert {:ok, block} = CommonAPI.block(blocker, blocked)
108 assert User.blocks?(blocker, blocked)
109 refute User.following?(blocker, blocked)
110 refute User.following?(blocked, blocker)
112 refute called(Pleroma.Web.Federator.publish(block))
117 describe "unblocking" do
118 test "it works even without an existing block activity" do
119 blocked = insert(:user)
120 blocker = insert(:user)
121 User.block(blocker, blocked)
123 assert User.blocks?(blocker, blocked)
124 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
125 refute User.blocks?(blocker, blocked)
129 describe "deletion" do
130 test "it works with pruned objects" do
133 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
135 clear_config([:instance, :federating], true)
137 Object.normalize(post, fetch: false)
140 with_mock Pleroma.Web.Federator,
141 publish: fn _ -> nil end do
142 assert {:ok, delete} = CommonAPI.delete(post.id, user)
144 assert called(Pleroma.Web.Federator.publish(delete))
147 refute Activity.get_by_id(post.id)
150 test "it allows users to delete their posts" do
153 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
155 clear_config([:instance, :federating], true)
157 with_mock Pleroma.Web.Federator,
158 publish: fn _ -> nil end do
159 assert {:ok, delete} = CommonAPI.delete(post.id, user)
161 assert called(Pleroma.Web.Federator.publish(delete))
164 refute Activity.get_by_id(post.id)
167 test "it does not allow a user to delete their posts" do
169 other_user = insert(:user)
171 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
173 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
174 assert Activity.get_by_id(post.id)
177 test "it allows moderators to delete other user's posts" do
179 moderator = insert(:user, is_moderator: true)
181 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
183 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
186 refute Activity.get_by_id(post.id)
189 test "it allows admins to delete other user's posts" do
191 moderator = insert(:user, is_admin: true)
193 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
195 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
198 refute Activity.get_by_id(post.id)
201 test "superusers deleting non-local posts won't federate the delete" do
202 # This is the user of the ingested activity
206 ap_id: "http://mastodon.example.org/users/admin",
207 last_refreshed_at: NaiveDateTime.utc_now()
210 moderator = insert(:user, is_admin: true)
213 File.read!("test/fixtures/mastodon-post-activity.json")
216 {:ok, post} = Transmogrifier.handle_incoming(data)
218 with_mock Pleroma.Web.Federator,
219 publish: fn _ -> nil end do
220 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
222 refute called(Pleroma.Web.Federator.publish(:_))
225 refute Activity.get_by_id(post.id)
229 test "favoriting race condition" do
231 users_serial = insert_list(10, :user)
232 users = insert_list(10, :user)
234 {:ok, activity} = CommonAPI.post(user, %{status: "."})
237 |> Enum.map(fn user ->
238 CommonAPI.favorite(user, activity.id)
241 object = Object.get_by_ap_id(activity.data["object"])
242 assert object.data["like_count"] == 10
245 |> Enum.map(fn user ->
247 CommonAPI.favorite(user, activity.id)
250 |> Enum.map(&Task.await/1)
252 object = Object.get_by_ap_id(activity.data["object"])
253 assert object.data["like_count"] == 20
256 test "repeating race condition" do
258 users_serial = insert_list(10, :user)
259 users = insert_list(10, :user)
261 {:ok, activity} = CommonAPI.post(user, %{status: "."})
264 |> Enum.map(fn user ->
265 CommonAPI.repeat(activity.id, user)
268 object = Object.get_by_ap_id(activity.data["object"])
269 assert object.data["announcement_count"] == 10
272 |> Enum.map(fn user ->
274 CommonAPI.repeat(activity.id, user)
277 |> Enum.map(&Task.await/1)
279 object = Object.get_by_ap_id(activity.data["object"])
280 assert object.data["announcement_count"] == 20
283 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
285 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
287 [participation] = Participation.for_user(user)
290 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
292 assert Visibility.is_direct?(convo_reply)
294 assert activity.data["context"] == convo_reply.data["context"]
297 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
299 jafnhar = insert(:user)
300 tridi = insert(:user)
303 CommonAPI.post(har, %{
304 status: "@#{jafnhar.nickname} hey",
308 assert har.ap_id in activity.recipients
309 assert jafnhar.ap_id in activity.recipients
311 [participation] = Participation.for_user(har)
314 CommonAPI.post(har, %{
315 status: "I don't really like @#{tridi.nickname}",
316 visibility: "direct",
317 in_reply_to_status_id: activity.id,
318 in_reply_to_conversation_id: participation.id
321 assert har.ap_id in activity.recipients
322 assert jafnhar.ap_id in activity.recipients
323 refute tridi.ap_id in activity.recipients
326 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
328 jafnhar = insert(:user)
329 tridi = insert(:user)
331 clear_config([:instance, :safe_dm_mentions], true)
334 CommonAPI.post(har, %{
335 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
339 refute tridi.ap_id in activity.recipients
340 assert jafnhar.ap_id in activity.recipients
343 test "it de-duplicates tags" do
345 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
347 object = Object.normalize(activity, fetch: false)
349 assert Object.tags(object) == ["2hu"]
352 test "it adds emoji in the object" do
354 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
356 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
359 describe "posting" do
360 test "it adds an emoji on an external site" do
362 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
364 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
365 assert url == "https://example.com/emoji.png"
367 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
369 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
370 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
373 test "it copies emoji from the subject of the parent post" do
376 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
380 activity = Activity.get_create_by_object_ap_id(object.data["id"])
383 {:ok, reply_activity} =
384 CommonAPI.post(user, %{
385 in_reply_to_id: activity.id,
386 status: ":joker_disapprove:",
387 spoiler_text: ":joker_smile:"
390 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
391 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
394 test "deactivated users can't post" do
395 user = insert(:user, is_active: false)
396 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
399 test "it supports explicit addressing" do
401 user_two = insert(:user)
402 user_three = insert(:user)
403 user_four = insert(:user)
406 CommonAPI.post(user, %{
408 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
409 to: [user_two.nickname, user_four.nickname, "nonexistent"]
412 assert user.ap_id in activity.recipients
413 assert user_two.ap_id in activity.recipients
414 assert user_four.ap_id in activity.recipients
415 refute user_three.ap_id in activity.recipients
418 test "it filters out obviously bad tags when accepting a post as HTML" do
421 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
424 CommonAPI.post(user, %{
426 content_type: "text/html"
429 object = Object.normalize(activity, fetch: false)
431 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
433 assert object.data["source"] == %{
434 "mediaType" => "text/html",
439 test "it filters out obviously bad tags when accepting a post as Markdown" do
442 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
445 CommonAPI.post(user, %{
447 content_type: "text/markdown"
450 object = Object.normalize(activity, fetch: false)
452 assert object.data["content"] == "<p><b>2hu</b></p>"
454 assert object.data["source"] == %{
455 "mediaType" => "text/markdown",
460 test "it does not allow replies to direct messages that are not direct messages themselves" do
463 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
466 CommonAPI.post(user, %{
468 visibility: "direct",
469 in_reply_to_status_id: activity.id
472 Enum.each(["public", "private", "unlisted"], fn visibility ->
473 assert {:error, "The message visibility must be direct"} =
474 CommonAPI.post(user, %{
476 visibility: visibility,
477 in_reply_to_status_id: activity.id
482 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
484 other_user = insert(:user)
485 third_user = insert(:user)
487 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
490 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
492 # The OP is implicitly added
493 assert user.ap_id in open_answer.recipients
495 {:ok, secret_answer} =
496 CommonAPI.post(other_user, %{
497 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
498 in_reply_to_status_id: post.id,
502 assert third_user.ap_id in secret_answer.recipients
504 # The OP is not added
505 refute user.ap_id in secret_answer.recipients
508 test "it allows to address a list" do
510 {:ok, list} = Pleroma.List.create("foo", user)
512 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
514 assert activity.data["bcc"] == [list.ap_id]
515 assert activity.recipients == [list.ap_id, user.ap_id]
516 assert activity.data["listMessage"] == list.ap_id
519 test "it returns error when status is empty and no attachments" do
522 assert {:error, "Cannot post an empty status without attachments"} =
523 CommonAPI.post(user, %{status: ""})
526 test "it validates character limits are correctly enforced" do
527 clear_config([:instance, :limit], 5)
531 assert {:error, "The status is over the character limit"} =
532 CommonAPI.post(user, %{status: "foobar"})
534 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
537 test "it can handle activities that expire" do
540 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
542 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
545 worker: Pleroma.Workers.PurgeExpiredActivity,
546 args: %{activity_id: activity.id},
547 scheduled_at: expires_at
552 describe "reactions" do
553 test "reacting to a status with an emoji" do
555 other_user = insert(:user)
557 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
559 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
561 assert reaction.data["actor"] == user.ap_id
562 assert reaction.data["content"] == "👍"
564 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
566 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
569 test "unreacting to a status with an emoji" do
571 other_user = insert(:user)
573 clear_config([:instance, :federating], true)
575 with_mock Pleroma.Web.Federator,
576 publish: fn _ -> nil end do
577 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
578 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
580 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
582 assert unreaction.data["type"] == "Undo"
583 assert unreaction.data["object"] == reaction.data["id"]
584 assert unreaction.local
586 # On federation, it contains the undone (and deleted) object
587 unreaction_with_object = %{
589 | data: Map.put(unreaction.data, "object", reaction.data)
592 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
596 test "repeating a status" do
598 other_user = insert(:user)
600 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
602 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
603 assert Visibility.is_public?(announce_activity)
606 test "can't repeat a repeat" do
608 other_user = insert(:user)
609 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
611 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
613 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
616 test "repeating a status privately" do
618 other_user = insert(:user)
620 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
622 {:ok, %Activity{} = announce_activity} =
623 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
625 assert Visibility.is_private?(announce_activity)
626 refute Visibility.visible_for_user?(announce_activity, nil)
629 test "author can repeat own private statuses" do
630 author = insert(:user)
631 follower = insert(:user)
632 CommonAPI.follow(follower, author)
634 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
636 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
638 assert Visibility.is_private?(announce_activity)
639 refute Visibility.visible_for_user?(announce_activity, nil)
641 assert Visibility.visible_for_user?(activity, follower)
642 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
645 test "favoriting a status" do
647 other_user = insert(:user)
649 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
651 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
652 assert data["type"] == "Like"
653 assert data["actor"] == user.ap_id
654 assert data["object"] == post_activity.data["object"]
657 test "retweeting a status twice returns the status" do
659 other_user = insert(:user)
661 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
662 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
663 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
666 test "favoriting a status twice returns ok, but without the like activity" do
668 other_user = insert(:user)
670 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
671 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
672 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
676 describe "pinned statuses" do
678 clear_config([:instance, :max_pinned_statuses], 1)
681 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
683 [user: user, activity: activity]
686 test "activity not found error", %{user: user} do
687 assert {:error, :not_found} = CommonAPI.pin("id", user)
690 test "pin status", %{user: user, activity: activity} do
691 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
693 %{data: %{"id" => object_id}} = Object.normalize(activity)
694 user = refresh_record(user)
696 assert user.pinned_objects |> Map.keys() == [object_id]
699 test "pin poll", %{user: user} do
701 CommonAPI.post(user, %{
702 status: "How is fediverse today?",
703 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
706 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
708 %{data: %{"id" => object_id}} = Object.normalize(activity)
710 user = refresh_record(user)
712 assert user.pinned_objects |> Map.keys() == [object_id]
715 test "unlisted statuses can be pinned", %{user: user} do
716 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
717 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
720 test "only self-authored can be pinned", %{activity: activity} do
723 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
726 test "max pinned statuses", %{user: user, activity: activity_one} do
727 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
729 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
731 user = refresh_record(user)
733 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
736 test "only public can be pinned", %{user: user} do
737 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
738 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
741 test "unpin status", %{user: user, activity: activity} do
742 {:ok, activity} = CommonAPI.pin(activity.id, user)
744 user = refresh_record(user)
748 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
750 user = refresh_record(user)
752 assert user.pinned_objects == %{}
755 test "should unpin when deleting a status", %{user: user, activity: activity} do
756 {:ok, activity} = CommonAPI.pin(activity.id, user)
758 user = refresh_record(user)
760 assert {:ok, _} = CommonAPI.delete(activity.id, user)
762 user = refresh_record(user)
764 assert user.pinned_objects == %{}
767 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
768 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
770 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
772 {:ok, _activity} = CommonAPI.pin(activity.id, user)
773 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
775 user = refresh_record(user)
776 {:ok, _} = CommonAPI.unpin(activity.id, user)
778 # recreates expiration job on unpin
779 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
782 test "ephemeral activity deletion job won't be deleted on pinning error", %{
786 clear_config([:instance, :max_pinned_statuses], 1)
788 {:ok, _activity} = CommonAPI.pin(activity.id, user)
790 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
792 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
794 user = refresh_record(user)
795 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
797 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
801 describe "mute tests" do
805 activity = insert(:note_activity)
807 [user: user, activity: activity]
810 test "marks notifications as read after mute" do
811 author = insert(:user)
812 activity = insert(:note_activity, user: author)
814 friend1 = insert(:user)
815 friend2 = insert(:user)
817 {:ok, reply_activity} =
821 status: "@#{author.nickname} @#{friend1.nickname} test reply",
822 in_reply_to_status_id: activity.id
826 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
827 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
829 assert Repo.aggregate(
830 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
834 unread_notifications =
835 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
837 assert Enum.any?(unread_notifications, fn n ->
838 n.type == "favourite" && n.activity_id == favorite_activity.id
841 assert Enum.any?(unread_notifications, fn n ->
842 n.type == "reblog" && n.activity_id == repeat_activity.id
845 assert Enum.any?(unread_notifications, fn n ->
846 n.type == "mention" && n.activity_id == reply_activity.id
849 {:ok, _} = CommonAPI.add_mute(author, activity)
850 assert CommonAPI.thread_muted?(author, activity)
852 assert Repo.aggregate(
853 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
858 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
860 assert Enum.any?(read_notifications, fn n ->
861 n.type == "favourite" && n.activity_id == favorite_activity.id
864 assert Enum.any?(read_notifications, fn n ->
865 n.type == "reblog" && n.activity_id == repeat_activity.id
868 assert Enum.any?(read_notifications, fn n ->
869 n.type == "mention" && n.activity_id == reply_activity.id
873 test "add mute", %{user: user, activity: activity} do
874 {:ok, _} = CommonAPI.add_mute(user, activity)
875 assert CommonAPI.thread_muted?(user, activity)
878 test "add expiring mute", %{user: user, activity: activity} do
879 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
880 assert CommonAPI.thread_muted?(user, activity)
882 worker = Pleroma.Workers.MuteExpireWorker
883 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
890 assert :ok = perform_job(worker, args)
891 refute CommonAPI.thread_muted?(user, activity)
894 test "remove mute", %{user: user, activity: activity} do
895 CommonAPI.add_mute(user, activity)
896 {:ok, _} = CommonAPI.remove_mute(user, activity)
897 refute CommonAPI.thread_muted?(user, activity)
900 test "remove mute by ids", %{user: user, activity: activity} do
901 CommonAPI.add_mute(user, activity)
902 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
903 refute CommonAPI.thread_muted?(user, activity)
906 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
907 CommonAPI.add_mute(user, activity)
908 {:error, _} = CommonAPI.add_mute(user, activity)
912 describe "reports" do
913 test "creates a report" do
914 reporter = insert(:user)
915 target_user = insert(:user)
917 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
919 reporter_ap_id = reporter.ap_id
920 target_ap_id = target_user.ap_id
921 activity_ap_id = activity.data["id"]
925 account_id: target_user.id,
927 status_ids: [activity.id]
932 "id" => activity_ap_id,
933 "content" => "foobar",
934 "published" => activity.object.data["published"],
935 "actor" => AccountView.render("show.json", %{user: target_user})
938 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
941 actor: ^reporter_ap_id,
944 "content" => ^comment,
945 "object" => [^target_ap_id, ^note_obj],
951 test "updates report state" do
952 [reporter, target_user] = insert_pair(:user)
953 activity = insert(:note_activity, user: target_user)
955 {:ok, %Activity{id: report_id}} =
956 CommonAPI.report(reporter, %{
957 account_id: target_user.id,
958 comment: "I feel offended",
959 status_ids: [activity.id]
962 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
964 assert report.data["state"] == "resolved"
966 [reported_user, activity_id] = report.data["object"]
968 assert reported_user == target_user.ap_id
969 assert activity_id == activity.data["id"]
972 test "does not update report state when state is unsupported" do
973 [reporter, target_user] = insert_pair(:user)
974 activity = insert(:note_activity, user: target_user)
976 {:ok, %Activity{id: report_id}} =
977 CommonAPI.report(reporter, %{
978 account_id: target_user.id,
979 comment: "I feel offended",
980 status_ids: [activity.id]
983 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
986 test "updates state of multiple reports" do
987 [reporter, target_user] = insert_pair(:user)
988 activity = insert(:note_activity, user: target_user)
990 {:ok, %Activity{id: first_report_id}} =
991 CommonAPI.report(reporter, %{
992 account_id: target_user.id,
993 comment: "I feel offended",
994 status_ids: [activity.id]
997 {:ok, %Activity{id: second_report_id}} =
998 CommonAPI.report(reporter, %{
999 account_id: target_user.id,
1000 comment: "I feel very offended!",
1001 status_ids: [activity.id]
1005 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1007 first_report = Activity.get_by_id(first_report_id)
1008 second_report = Activity.get_by_id(second_report_id)
1010 assert report_ids -- [first_report_id, second_report_id] == []
1011 assert first_report.data["state"] == "resolved"
1012 assert second_report.data["state"] == "resolved"
1016 describe "reblog muting" do
1018 muter = insert(:user)
1020 muted = insert(:user)
1022 [muter: muter, muted: muted]
1025 test "add a reblog mute", %{muter: muter, muted: muted} do
1026 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1028 assert User.showing_reblogs?(muter, muted) == false
1031 test "remove a reblog mute", %{muter: muter, muted: muted} do
1032 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1033 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1035 assert User.showing_reblogs?(muter, muted) == true
1039 describe "follow/2" do
1040 test "directly follows a non-locked local user" do
1041 [follower, followed] = insert_pair(:user)
1042 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1044 assert User.following?(follower, followed)
1048 describe "unfollow/2" do
1049 test "also unsubscribes a user" do
1050 [follower, followed] = insert_pair(:user)
1051 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1052 {:ok, _subscription} = User.subscribe(follower, followed)
1054 assert User.subscribed_to?(follower, followed)
1056 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1058 refute User.subscribed_to?(follower, followed)
1061 test "cancels a pending follow for a local user" do
1062 follower = insert(:user)
1063 followed = insert(:user, is_locked: true)
1065 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1066 CommonAPI.follow(follower, followed)
1068 assert User.get_follow_state(follower, followed) == :follow_pending
1069 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1070 assert User.get_follow_state(follower, followed) == nil
1072 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1073 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1078 "object" => %{"type" => "Follow", "state" => "cancelled"}
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 %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1095 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1100 "object" => %{"type" => "Follow", "state" => "cancelled"}
1102 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1106 describe "accept_follow_request/2" do
1107 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1108 user = insert(:user, is_locked: true)
1109 follower = insert(:user)
1110 follower_two = insert(:user)
1112 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1113 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1114 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1116 assert follow_activity.data["state"] == "pending"
1117 assert follow_activity_two.data["state"] == "pending"
1118 assert follow_activity_three.data["state"] == "pending"
1120 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1122 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1123 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1124 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1127 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1128 user = insert(:user, is_locked: true)
1129 follower = insert(:user)
1130 follower_two = insert(:user)
1132 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1133 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1134 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1136 assert follow_activity.data["state"] == "pending"
1137 assert follow_activity_two.data["state"] == "pending"
1138 assert follow_activity_three.data["state"] == "pending"
1140 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1142 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1143 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1144 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1147 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1148 user = insert(:user, is_locked: true)
1149 not_follower = insert(:user)
1150 CommonAPI.accept_follow_request(not_follower, user)
1152 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1156 describe "vote/3" do
1157 test "does not allow to vote twice" do
1158 user = insert(:user)
1159 other_user = insert(:user)
1162 CommonAPI.post(user, %{
1163 status: "Am I cute?",
1164 poll: %{options: ["Yes", "No"], expires_in: 20}
1167 object = Object.normalize(activity, fetch: false)
1169 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1171 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1175 describe "get_user/1" do
1176 test "gets user by ap_id" do
1177 user = insert(:user)
1178 assert CommonAPI.get_user(user.ap_id) == user
1181 test "gets user by guessed nickname" do
1182 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1183 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1190 nickname: "erroruser@example.com"
1191 } = CommonAPI.get_user("")
1195 describe "with `local` visibility" do
1196 setup do: clear_config([:instance, :federating], true)
1199 user = insert(:user)
1201 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1202 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1204 assert Visibility.is_local_public?(activity)
1205 assert_not_called(Pleroma.Web.Federator.publish(activity))
1210 user = insert(:user)
1212 {:ok, %Activity{id: activity_id}} =
1213 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1215 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1216 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1217 CommonAPI.delete(activity_id, user)
1219 assert Visibility.is_local_public?(activity)
1220 assert_not_called(Pleroma.Web.Federator.publish(activity))
1225 user = insert(:user)
1226 other_user = insert(:user)
1228 {:ok, %Activity{id: activity_id}} =
1229 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1231 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1232 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1233 CommonAPI.repeat(activity_id, user)
1235 assert Visibility.is_local_public?(activity)
1236 refute called(Pleroma.Web.Federator.publish(activity))
1241 user = insert(:user)
1242 other_user = insert(:user)
1244 {:ok, %Activity{id: activity_id}} =
1245 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1247 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1249 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1250 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1251 CommonAPI.unrepeat(activity_id, user)
1253 assert Visibility.is_local_public?(activity)
1254 refute called(Pleroma.Web.Federator.publish(activity))
1259 user = insert(:user)
1260 other_user = insert(:user)
1262 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1264 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1265 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1266 CommonAPI.favorite(user, activity.id)
1268 assert Visibility.is_local_public?(activity)
1269 refute called(Pleroma.Web.Federator.publish(activity))
1273 test "unfavorite" do
1274 user = insert(:user)
1275 other_user = insert(:user)
1277 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1279 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1281 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1282 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1283 assert Visibility.is_local_public?(activity)
1284 refute called(Pleroma.Web.Federator.publish(activity))
1288 test "react_with_emoji" do
1289 user = insert(:user)
1290 other_user = insert(:user)
1291 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1293 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1294 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1295 CommonAPI.react_with_emoji(activity.id, user, "👍")
1297 assert Visibility.is_local_public?(activity)
1298 refute called(Pleroma.Web.Federator.publish(activity))
1302 test "unreact_with_emoji" do
1303 user = insert(:user)
1304 other_user = insert(:user)
1305 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1307 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1309 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1310 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1311 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1313 assert Visibility.is_local_public?(activity)
1314 refute called(Pleroma.Web.Federator.publish(activity))