1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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
7 use Pleroma.DataCase, async: false
11 alias Pleroma.Conversation.Participation
12 alias Pleroma.Notification
15 alias Pleroma.UnstubbedConfigMock, as: ConfigMock
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Transmogrifier
19 alias Pleroma.Web.ActivityPub.Visibility
20 alias Pleroma.Web.AdminAPI.AccountView
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Workers.PollWorker
24 import Ecto.Query, only: [from: 2]
27 import Pleroma.Factory
29 require Pleroma.Activity.Queries
30 require Pleroma.Constants
32 defp get_announces_of_object(%{data: %{"id" => id}} = _object) do
33 Pleroma.Activity.Queries.by_type("Announce")
34 |> Pleroma.Activity.Queries.by_object_id(id)
39 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
45 |> stub_with(Pleroma.Test.StaticConfig)
50 setup do: clear_config([:instance, :safe_dm_mentions])
51 setup do: clear_config([:instance, :limit])
52 setup do: clear_config([:instance, :max_pinned_statuses])
54 describe "posting polls" do
55 test "it posts a poll" do
59 CommonAPI.post(user, %{
60 status: "who is the best",
61 poll: %{expires_in: 600, options: ["reimu", "marisa"]}
64 object = Object.normalize(activity, fetch: false)
66 assert object.data["type"] == "Question"
67 assert object.data["oneOf"] |> length() == 2
71 args: %{op: "poll_end", activity_id: activity.id},
72 scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
77 describe "blocking" do
79 blocker = insert(:user)
80 blocked = insert(:user, local: false)
81 CommonAPI.follow(blocker, blocked)
82 CommonAPI.follow(blocked, blocker)
83 CommonAPI.accept_follow_request(blocker, blocked)
84 CommonAPI.accept_follow_request(blocked, blocked)
85 %{blocker: blocker, blocked: blocked}
88 test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
89 clear_config([:instance, :federating], true)
91 with_mock Pleroma.Web.Federator,
92 publish: fn _ -> nil end do
93 assert User.get_follow_state(blocker, blocked) == :follow_accept
94 refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
96 assert {:ok, block} = CommonAPI.block(blocker, blocked)
99 assert User.blocks?(blocker, blocked)
100 refute User.following?(blocker, blocked)
101 refute User.following?(blocked, blocker)
103 refute User.get_follow_state(blocker, blocked)
105 assert %{data: %{"state" => "reject"}} =
106 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
108 assert called(Pleroma.Web.Federator.publish(block))
112 test "it blocks and does not federate if outgoing blocks are disabled", %{
116 clear_config([:instance, :federating], true)
117 clear_config([:activitypub, :outgoing_blocks], false)
119 with_mock Pleroma.Web.Federator,
120 publish: fn _ -> nil end do
121 assert {:ok, block} = CommonAPI.block(blocker, blocked)
124 assert User.blocks?(blocker, blocked)
125 refute User.following?(blocker, blocked)
126 refute User.following?(blocked, blocker)
128 refute called(Pleroma.Web.Federator.publish(block))
133 describe "posting chat messages" do
134 setup do: clear_config([:instance, :chat_limit])
136 test "it posts a self-chat" do
137 author = insert(:user)
141 CommonAPI.post_chat_message(
144 "remember to buy milk when milk truk arive"
147 assert activity.data["type"] == "Create"
150 test "it posts a chat message without content but with an attachment" do
151 author = insert(:user)
152 recipient = insert(:user)
155 content_type: "image/jpeg",
156 path: Path.absname("test/fixtures/image.jpg"),
157 filename: "an_image.jpg"
160 {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
164 Pleroma.Web.Streamer,
176 send: fn _ -> nil end
181 CommonAPI.post_chat_message(
189 Notification.for_user_and_activity(recipient, activity)
190 |> Repo.preload(:activity)
192 assert called(Pleroma.Web.Push.send(notification))
193 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
194 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
200 test "it adds html newlines" do
201 author = insert(:user)
202 recipient = insert(:user)
204 other_user = insert(:user)
207 CommonAPI.post_chat_message(
213 assert other_user.ap_id not in activity.recipients
215 object = Object.normalize(activity, fetch: false)
217 assert object.data["content"] == "uguu<br/>uguuu"
220 test "it linkifies" do
221 author = insert(:user)
222 recipient = insert(:user)
224 other_user = insert(:user)
227 CommonAPI.post_chat_message(
230 "https://example.org is the site of @#{other_user.nickname} #2hu"
233 assert other_user.ap_id not in activity.recipients
235 object = Object.normalize(activity, fetch: false)
237 assert object.data["content"] ==
238 "<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=\"#{other_user.id}\" 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>"
241 test "it posts a chat message" do
242 author = insert(:user)
243 recipient = insert(:user)
246 CommonAPI.post_chat_message(
249 "a test message <script>alert('uuu')</script> :firefox:"
252 assert activity.data["type"] == "Create"
253 assert activity.local
254 object = Object.normalize(activity, fetch: false)
256 assert object.data["type"] == "ChatMessage"
257 assert object.data["to"] == [recipient.ap_id]
259 assert object.data["content"] ==
260 "a test message <script>alert('uuu')</script> :firefox:"
262 assert object.data["emoji"] == %{
263 "firefox" => "http://localhost:4001/emoji/Firefox.gif"
266 assert Chat.get(author.id, recipient.ap_id)
267 assert Chat.get(recipient.id, author.ap_id)
269 assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
272 test "it reject messages over the local limit" do
273 clear_config([:instance, :chat_limit], 2)
275 author = insert(:user)
276 recipient = insert(:user)
279 CommonAPI.post_chat_message(
285 assert message == :content_too_long
288 test "it reject messages via MRF" do
289 clear_config([:mrf_keyword, :reject], ["GNO"])
290 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
292 author = insert(:user)
293 recipient = insert(:user)
295 assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
296 CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
299 test "it reject messages with attachments not belonging to user" do
300 author = insert(:user)
301 not_author = insert(:user)
304 attachment = insert(:attachment, %{user: not_author})
307 CommonAPI.post_chat_message(
311 media_id: attachment.id
314 assert message == :forbidden
318 describe "unblocking" do
319 test "it works even without an existing block activity" do
320 blocked = insert(:user)
321 blocker = insert(:user)
322 User.block(blocker, blocked)
324 assert User.blocks?(blocker, blocked)
325 assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
326 refute User.blocks?(blocker, blocked)
330 describe "deletion" do
331 test "it works with pruned objects" do
334 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
336 clear_config([:instance, :federating], true)
338 Object.normalize(post, fetch: false)
341 with_mock Pleroma.Web.Federator,
342 publish: fn _ -> nil end do
343 assert {:ok, delete} = CommonAPI.delete(post.id, user)
345 assert called(Pleroma.Web.Federator.publish(delete))
348 refute Activity.get_by_id(post.id)
351 test "it allows users to delete their posts" do
354 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
356 clear_config([:instance, :federating], true)
358 with_mock Pleroma.Web.Federator,
359 publish: fn _ -> nil end do
360 assert {:ok, delete} = CommonAPI.delete(post.id, user)
362 assert called(Pleroma.Web.Federator.publish(delete))
365 refute Activity.get_by_id(post.id)
368 test "it does not allow a user to delete posts from another user" do
370 other_user = insert(:user)
372 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
374 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
375 assert Activity.get_by_id(post.id)
378 test "it allows privileged users to delete other user's posts" do
379 clear_config([:instance, :moderator_privileges], [:messages_delete])
381 moderator = insert(:user, is_moderator: true)
383 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
385 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
388 refute Activity.get_by_id(post.id)
391 test "it doesn't allow unprivileged mods or admins to delete other user's posts" do
392 clear_config([:instance, :admin_privileges], [])
393 clear_config([:instance, :moderator_privileges], [])
395 moderator = insert(:user, is_moderator: true, is_admin: true)
397 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
399 assert {:error, "Could not delete"} = CommonAPI.delete(post.id, moderator)
400 assert Activity.get_by_id(post.id)
403 test "privileged users deleting non-local posts won't federate the delete" do
404 clear_config([:instance, :admin_privileges], [:messages_delete])
405 # This is the user of the ingested activity
409 ap_id: "http://mastodon.example.org/users/admin",
410 last_refreshed_at: NaiveDateTime.utc_now()
413 admin = insert(:user, is_admin: true)
416 File.read!("test/fixtures/mastodon-post-activity.json")
419 {:ok, post} = Transmogrifier.handle_incoming(data)
421 with_mock Pleroma.Web.Federator,
422 publish: fn _ -> nil end do
423 assert {:ok, delete} = CommonAPI.delete(post.id, admin)
425 refute called(Pleroma.Web.Federator.publish(:_))
428 refute Activity.get_by_id(post.id)
431 test "it allows privileged users to delete banned user's posts" do
432 clear_config([:instance, :moderator_privileges], [:messages_delete])
434 moderator = insert(:user, is_moderator: true)
436 {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
437 User.set_activation(user, false)
439 assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
442 refute Activity.get_by_id(post.id)
446 test "favoriting race condition" do
448 users_serial = insert_list(10, :user)
449 users = insert_list(10, :user)
451 {:ok, activity} = CommonAPI.post(user, %{status: "."})
454 |> Enum.map(fn user ->
455 CommonAPI.favorite(user, activity.id)
458 object = Object.get_by_ap_id(activity.data["object"])
459 assert object.data["like_count"] == 10
462 |> Enum.map(fn user ->
464 CommonAPI.favorite(user, activity.id)
467 |> Enum.map(&Task.await/1)
469 object = Object.get_by_ap_id(activity.data["object"])
470 assert object.data["like_count"] == 20
473 test "repeating race condition" do
475 users_serial = insert_list(10, :user)
476 users = insert_list(10, :user)
478 {:ok, activity} = CommonAPI.post(user, %{status: "."})
481 |> Enum.map(fn user ->
482 CommonAPI.repeat(activity.id, user)
485 object = Object.get_by_ap_id(activity.data["object"])
486 assert object.data["announcement_count"] == 10
489 |> Enum.map(fn user ->
491 CommonAPI.repeat(activity.id, user)
494 |> Enum.map(&Task.await/1)
496 object = Object.get_by_ap_id(activity.data["object"])
497 assert object.data["announcement_count"] == 20
500 test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
502 {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
504 [participation] = Participation.for_user(user)
507 CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
509 assert Visibility.direct?(convo_reply)
511 assert activity.data["context"] == convo_reply.data["context"]
514 test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
516 jafnhar = insert(:user)
517 tridi = insert(:user)
520 CommonAPI.post(har, %{
521 status: "@#{jafnhar.nickname} hey",
525 assert har.ap_id in activity.recipients
526 assert jafnhar.ap_id in activity.recipients
528 [participation] = Participation.for_user(har)
531 CommonAPI.post(har, %{
532 status: "I don't really like @#{tridi.nickname}",
533 visibility: "direct",
534 in_reply_to_status_id: activity.id,
535 in_reply_to_conversation_id: participation.id
538 assert har.ap_id in activity.recipients
539 assert jafnhar.ap_id in activity.recipients
540 refute tridi.ap_id in activity.recipients
543 test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
545 jafnhar = insert(:user)
546 tridi = insert(:user)
548 clear_config([:instance, :safe_dm_mentions], true)
551 CommonAPI.post(har, %{
552 status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
556 refute tridi.ap_id in activity.recipients
557 assert jafnhar.ap_id in activity.recipients
560 test "it de-duplicates tags" do
562 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
564 object = Object.normalize(activity, fetch: false)
566 assert Object.tags(object) == ["2hu"]
569 test "zwnj is treated as word character" do
571 {:ok, activity} = CommonAPI.post(user, %{status: "#ساٴينس"})
573 object = Object.normalize(activity, fetch: false)
575 assert Object.tags(object) == ["ساٴينس"]
578 test "allows lang attribute" do
580 text = ~s{<span lang="en">something</span><p lang="diaetuitech_rpyhpgc">random</p>}
582 {:ok, activity} = CommonAPI.post(user, %{status: text, content_type: "text/html"})
584 object = Object.normalize(activity, fetch: false)
586 assert object.data["content"] == text
589 test "double dot in link is allowed" do
591 text = "https://example.to/something..mp3"
592 {:ok, activity} = CommonAPI.post(user, %{status: text})
594 object = Object.normalize(activity, fetch: false)
596 assert object.data["content"] == "<a href=\"#{text}\" rel=\"ugc\">#{text}</a>"
599 test "it adds emoji in the object" do
601 {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
603 assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
606 describe "posting" do
607 test "it adds an emoji on an external site" do
609 {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
611 assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
612 assert url == "https://example.com/emoji.png"
614 {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
616 assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
617 assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
620 test "it copies emoji from the subject of the parent post" do
623 Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
627 activity = Activity.get_create_by_object_ap_id(object.data["id"])
630 {:ok, reply_activity} =
631 CommonAPI.post(user, %{
632 in_reply_to_id: activity.id,
633 status: ":joker_disapprove:",
634 spoiler_text: ":joker_smile:"
637 assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
638 refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
641 test "deactivated users can't post" do
642 user = insert(:user, is_active: false)
643 assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
646 test "it supports explicit addressing" do
648 user_two = insert(:user)
649 user_three = insert(:user)
650 user_four = insert(:user)
653 CommonAPI.post(user, %{
655 "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
656 to: [user_two.nickname, user_four.nickname, "nonexistent"]
659 assert user.ap_id in activity.recipients
660 assert user_two.ap_id in activity.recipients
661 assert user_four.ap_id in activity.recipients
662 refute user_three.ap_id in activity.recipients
665 test "it filters out obviously bad tags when accepting a post as HTML" do
668 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
671 CommonAPI.post(user, %{
673 content_type: "text/html"
676 object = Object.normalize(activity, fetch: false)
678 assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
679 assert object.data["source"]["content"] == post
682 test "it filters out obviously bad tags when accepting a post as Markdown" do
685 post = "<p><b>2hu</b></p><script>alert('xss')</script>"
688 CommonAPI.post(user, %{
690 content_type: "text/markdown"
693 object = Object.normalize(activity, fetch: false)
695 assert object.data["content"] == "<p><b>2hu</b></p>"
696 assert object.data["source"]["content"] == post
699 test "it does not allow replies to direct messages that are not direct messages themselves" do
702 {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
705 CommonAPI.post(user, %{
707 visibility: "direct",
708 in_reply_to_status_id: activity.id
711 Enum.each(["public", "private", "unlisted"], fn visibility ->
712 assert {:error, "The message visibility must be direct"} =
713 CommonAPI.post(user, %{
715 visibility: visibility,
716 in_reply_to_status_id: activity.id
721 test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
723 other_user = insert(:user)
724 third_user = insert(:user)
726 {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
729 CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
731 # The OP is implicitly added
732 assert user.ap_id in open_answer.recipients
734 {:ok, secret_answer} =
735 CommonAPI.post(other_user, %{
736 status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
737 in_reply_to_status_id: post.id,
741 assert third_user.ap_id in secret_answer.recipients
743 # The OP is not added
744 refute user.ap_id in secret_answer.recipients
747 test "it allows to address a list" do
749 {:ok, list} = Pleroma.List.create("foo", user)
751 {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
753 assert activity.data["bcc"] == [list.ap_id]
754 assert activity.recipients == [list.ap_id, user.ap_id]
755 assert activity.data["listMessage"] == list.ap_id
758 test "it returns error when status is empty and no attachments" do
761 assert {:error, "Cannot post an empty status without attachments"} =
762 CommonAPI.post(user, %{status: ""})
765 test "it validates character limits are correctly enforced" do
766 clear_config([:instance, :limit], 5)
770 assert {:error, "The status is over the character limit"} =
771 CommonAPI.post(user, %{status: "foobar"})
773 assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
776 test "it validates media attachment limits are correctly enforced" do
777 clear_config([:instance, :max_media_attachments], 4)
782 content_type: "image/jpeg",
783 path: Path.absname("test/fixtures/image.jpg"),
784 filename: "an_image.jpg"
787 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
789 assert {:error, "Too many attachments"} =
790 CommonAPI.post(user, %{
792 media_ids: List.duplicate(upload.id, 5)
795 assert {:ok, _activity} =
796 CommonAPI.post(user, %{
798 media_ids: [upload.id]
802 test "it can handle activities that expire" do
805 expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
807 assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
810 worker: Pleroma.Workers.PurgeExpiredActivity,
811 args: %{activity_id: activity.id},
812 scheduled_at: expires_at
816 test "it allows quote posting" do
819 {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
820 {:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id})
822 quoted = Object.normalize(quoted)
823 quote_post = Object.normalize(quote_post)
825 assert quote_post.data["quoteUrl"] == quoted.data["id"]
827 # The OP is not mentioned
828 refute quoted.data["actor"] in quote_post.data["to"]
831 test "quote posting with explicit addressing doesn't mention the OP" do
834 {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
837 CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []})
839 assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()]
842 test "quote posting visibility" do
844 another_user = insert(:user)
846 {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
847 {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"})
848 {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
849 {:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"})
850 {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"})
852 {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: direct.id})
853 {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id})
854 {:error, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: private.id})
855 {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: unlisted.id})
856 {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: unlisted.id})
857 {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: local.id})
858 {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: local.id})
859 {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id})
860 {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: public.id})
863 test "it properly mentions punycode domain" do
867 insert(:user, ap_id: "https://xn--i2raa.com/users/yyy", nickname: "yyy@xn--i2raa.com")
870 CommonAPI.post(user, %{status: "hey @yyy@xn--i2raa.com", content_type: "text/markdown"})
872 assert "https://xn--i2raa.com/users/yyy" in Object.normalize(activity).data["to"]
876 describe "reactions" do
877 test "reacting to a status with an emoji" do
879 other_user = insert(:user)
881 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
883 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
885 assert reaction.data["actor"] == user.ap_id
886 assert reaction.data["content"] == "👍"
888 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
890 {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
893 test "unreacting to a status with an emoji" do
895 other_user = insert(:user)
897 clear_config([:instance, :federating], true)
899 with_mock Pleroma.Web.Federator,
900 publish: fn _ -> nil end do
901 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
902 {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
904 {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
906 assert unreaction.data["type"] == "Undo"
907 assert unreaction.data["object"] == reaction.data["id"]
908 assert unreaction.local
910 # On federation, it contains the undone (and deleted) object
911 unreaction_with_object = %{
913 | data: Map.put(unreaction.data, "object", reaction.data)
916 assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
920 test "repeating a status" do
922 other_user = insert(:user)
924 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
926 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
927 assert Visibility.public?(announce_activity)
930 test "can't repeat a repeat" do
932 other_user = insert(:user)
933 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
935 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
937 refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
940 test "repeating a status privately" do
942 other_user = insert(:user)
944 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
946 {:ok, %Activity{} = announce_activity} =
947 CommonAPI.repeat(activity.id, user, %{visibility: "private"})
949 assert Visibility.private?(announce_activity)
950 refute Visibility.visible_for_user?(announce_activity, nil)
953 test "author can repeat own private statuses" do
954 author = insert(:user)
955 follower = insert(:user)
956 CommonAPI.follow(follower, author)
958 {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
960 {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
962 assert Visibility.private?(announce_activity)
963 refute Visibility.visible_for_user?(announce_activity, nil)
965 assert Visibility.visible_for_user?(activity, follower)
966 assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
969 test "favoriting a status" do
971 other_user = insert(:user)
973 {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
975 {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
976 assert data["type"] == "Like"
977 assert data["actor"] == user.ap_id
978 assert data["object"] == post_activity.data["object"]
981 test "retweeting a status twice returns the status" do
983 other_user = insert(:user)
985 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
986 {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
987 {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
990 test "favoriting a status twice returns ok, but without the like activity" do
992 other_user = insert(:user)
994 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
995 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
996 assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
1000 describe "pinned statuses" do
1002 clear_config([:instance, :max_pinned_statuses], 1)
1004 user = insert(:user)
1005 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
1007 [user: user, activity: activity]
1010 test "activity not found error", %{user: user} do
1011 assert {:error, :not_found} = CommonAPI.pin("id", user)
1014 test "pin status", %{user: user, activity: activity} do
1015 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
1017 %{data: %{"id" => object_id}} = Object.normalize(activity)
1018 user = refresh_record(user)
1020 assert user.pinned_objects |> Map.keys() == [object_id]
1023 test "pin poll", %{user: user} do
1025 CommonAPI.post(user, %{
1026 status: "How is fediverse today?",
1027 poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
1030 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
1032 %{data: %{"id" => object_id}} = Object.normalize(activity)
1034 user = refresh_record(user)
1036 assert user.pinned_objects |> Map.keys() == [object_id]
1039 test "unlisted statuses can be pinned", %{user: user} do
1040 {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
1041 assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
1044 test "only self-authored can be pinned", %{activity: activity} do
1045 user = insert(:user)
1047 assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
1050 test "max pinned statuses", %{user: user, activity: activity_one} do
1051 {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
1053 assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
1055 user = refresh_record(user)
1057 assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
1060 test "only public can be pinned", %{user: user} do
1061 {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
1062 {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
1065 test "unpin status", %{user: user, activity: activity} do
1066 {:ok, activity} = CommonAPI.pin(activity.id, user)
1068 user = refresh_record(user)
1072 assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
1074 user = refresh_record(user)
1076 assert user.pinned_objects == %{}
1079 test "should unpin when deleting a status", %{user: user, activity: activity} do
1080 {:ok, activity} = CommonAPI.pin(activity.id, user)
1082 user = refresh_record(user)
1084 assert {:ok, _} = CommonAPI.delete(activity.id, user)
1086 user = refresh_record(user)
1088 assert user.pinned_objects == %{}
1091 test "ephemeral activity won't be deleted if was pinned", %{user: user} do
1092 {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
1094 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
1096 {:ok, _activity} = CommonAPI.pin(activity.id, user)
1097 refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
1099 user = refresh_record(user)
1100 {:ok, _} = CommonAPI.unpin(activity.id, user)
1102 # recreates expiration job on unpin
1103 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
1106 test "ephemeral activity deletion job won't be deleted on pinning error", %{
1110 clear_config([:instance, :max_pinned_statuses], 1)
1112 {:ok, _activity} = CommonAPI.pin(activity.id, user)
1114 {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
1116 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
1118 user = refresh_record(user)
1119 {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
1121 assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
1125 describe "mute tests" do
1127 user = insert(:user)
1129 activity = insert(:note_activity)
1131 [user: user, activity: activity]
1134 test "marks notifications as read after mute" do
1135 author = insert(:user)
1136 activity = insert(:note_activity, user: author)
1138 friend1 = insert(:user)
1139 friend2 = insert(:user)
1141 {:ok, reply_activity} =
1145 status: "@#{author.nickname} @#{friend1.nickname} test reply",
1146 in_reply_to_status_id: activity.id
1150 {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
1151 {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
1153 assert Repo.aggregate(
1154 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1158 unread_notifications =
1159 Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
1161 assert Enum.any?(unread_notifications, fn n ->
1162 n.type == "favourite" && n.activity_id == favorite_activity.id
1165 assert Enum.any?(unread_notifications, fn n ->
1166 n.type == "reblog" && n.activity_id == repeat_activity.id
1169 assert Enum.any?(unread_notifications, fn n ->
1170 n.type == "mention" && n.activity_id == reply_activity.id
1173 {:ok, _} = CommonAPI.add_mute(author, activity)
1174 assert CommonAPI.thread_muted?(author, activity)
1176 assert Repo.aggregate(
1177 from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
1181 read_notifications =
1182 Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
1184 assert Enum.any?(read_notifications, fn n ->
1185 n.type == "favourite" && n.activity_id == favorite_activity.id
1188 assert Enum.any?(read_notifications, fn n ->
1189 n.type == "reblog" && n.activity_id == repeat_activity.id
1192 assert Enum.any?(read_notifications, fn n ->
1193 n.type == "mention" && n.activity_id == reply_activity.id
1197 test "add mute", %{user: user, activity: activity} do
1198 {:ok, _} = CommonAPI.add_mute(user, activity)
1199 assert CommonAPI.thread_muted?(user, activity)
1202 test "add expiring mute", %{user: user, activity: activity} do
1203 {:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
1204 assert CommonAPI.thread_muted?(user, activity)
1206 worker = Pleroma.Workers.MuteExpireWorker
1207 args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
1214 assert :ok = perform_job(worker, args)
1215 refute CommonAPI.thread_muted?(user, activity)
1218 test "remove mute", %{user: user, activity: activity} do
1219 CommonAPI.add_mute(user, activity)
1220 {:ok, _} = CommonAPI.remove_mute(user, activity)
1221 refute CommonAPI.thread_muted?(user, activity)
1224 test "remove mute by ids", %{user: user, activity: activity} do
1225 CommonAPI.add_mute(user, activity)
1226 {:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
1227 refute CommonAPI.thread_muted?(user, activity)
1230 test "check that mutes can't be duplicate", %{user: user, activity: activity} do
1231 CommonAPI.add_mute(user, activity)
1232 {:error, _} = CommonAPI.add_mute(user, activity)
1236 describe "reports" do
1237 test "creates a report" do
1238 reporter = insert(:user)
1239 target_user = insert(:user)
1241 {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
1242 activity = Activity.normalize(activity)
1244 reporter_ap_id = reporter.ap_id
1245 target_ap_id = target_user.ap_id
1246 reported_object_ap_id = activity.object.data["id"]
1250 account_id: target_user.id,
1252 status_ids: [activity.id]
1257 "id" => reported_object_ap_id,
1258 "content" => "foobar",
1259 "published" => activity.object.data["published"],
1260 "actor" => AccountView.render("show.json", %{user: target_user})
1263 assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
1266 actor: ^reporter_ap_id,
1269 "content" => ^comment,
1270 "object" => [^target_ap_id, ^note_obj],
1276 test "updates report state" do
1277 [reporter, target_user] = insert_pair(:user)
1278 activity = insert(:note_activity, user: target_user)
1279 object = Object.normalize(activity)
1281 {:ok, %Activity{id: report_id}} =
1282 CommonAPI.report(reporter, %{
1283 account_id: target_user.id,
1284 comment: "I feel offended",
1285 status_ids: [activity.id]
1288 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1290 assert report.data["state"] == "resolved"
1292 [reported_user, object_id] = report.data["object"]
1294 assert reported_user == target_user.ap_id
1295 assert object_id == object.data["id"]
1298 test "updates report state, don't strip when report_strip_status is false" do
1299 clear_config([:instance, :report_strip_status], false)
1301 [reporter, target_user] = insert_pair(:user)
1302 activity = insert(:note_activity, user: target_user)
1304 {:ok, %Activity{id: report_id, data: report_data}} =
1305 CommonAPI.report(reporter, %{
1306 account_id: target_user.id,
1307 comment: "I feel offended",
1308 status_ids: [activity.id]
1311 {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
1313 assert report.data["state"] == "resolved"
1315 [reported_user, reported_activity] = report.data["object"]
1317 assert reported_user == target_user.ap_id
1318 assert is_map(reported_activity)
1320 assert reported_activity["content"] ==
1321 report_data["object"] |> Enum.at(1) |> Map.get("content")
1324 test "does not update report state when state is unsupported" do
1325 [reporter, target_user] = insert_pair(:user)
1326 activity = insert(:note_activity, user: target_user)
1328 {:ok, %Activity{id: report_id}} =
1329 CommonAPI.report(reporter, %{
1330 account_id: target_user.id,
1331 comment: "I feel offended",
1332 status_ids: [activity.id]
1335 assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
1338 test "updates state of multiple reports" do
1339 [reporter, target_user] = insert_pair(:user)
1340 activity = insert(:note_activity, user: target_user)
1342 {:ok, %Activity{id: first_report_id}} =
1343 CommonAPI.report(reporter, %{
1344 account_id: target_user.id,
1345 comment: "I feel offended",
1346 status_ids: [activity.id]
1349 {:ok, %Activity{id: second_report_id}} =
1350 CommonAPI.report(reporter, %{
1351 account_id: target_user.id,
1352 comment: "I feel very offended!",
1353 status_ids: [activity.id]
1357 CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
1359 first_report = Activity.get_by_id(first_report_id)
1360 second_report = Activity.get_by_id(second_report_id)
1362 assert report_ids -- [first_report_id, second_report_id] == []
1363 assert first_report.data["state"] == "resolved"
1364 assert second_report.data["state"] == "resolved"
1368 describe "reblog muting" do
1370 muter = insert(:user)
1372 muted = insert(:user)
1374 [muter: muter, muted: muted]
1377 test "add a reblog mute", %{muter: muter, muted: muted} do
1378 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1380 assert User.showing_reblogs?(muter, muted) == false
1383 test "remove a reblog mute", %{muter: muter, muted: muted} do
1384 {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
1385 {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
1387 assert User.showing_reblogs?(muter, muted) == true
1391 describe "follow/2" do
1392 test "directly follows a non-locked local user" do
1393 [follower, followed] = insert_pair(:user)
1394 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1396 assert User.following?(follower, followed)
1400 describe "unfollow/2" do
1401 test "also unsubscribes a user" do
1402 [follower, followed] = insert_pair(:user)
1403 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1404 {:ok, _subscription} = User.subscribe(follower, followed)
1406 assert User.subscribed_to?(follower, followed)
1408 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1410 refute User.subscribed_to?(follower, followed)
1413 test "also unpins a user" do
1414 [follower, followed] = insert_pair(:user)
1415 {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
1416 {:ok, _endorsement} = User.endorse(follower, followed)
1418 assert User.endorses?(follower, followed)
1420 {:ok, follower} = CommonAPI.unfollow(follower, followed)
1422 refute User.endorses?(follower, followed)
1425 test "cancels a pending follow for a local user" do
1426 follower = insert(:user)
1427 followed = insert(:user, is_locked: true)
1429 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1430 CommonAPI.follow(follower, followed)
1432 assert User.get_follow_state(follower, followed) == :follow_pending
1433 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1434 assert User.get_follow_state(follower, followed) == nil
1436 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1437 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1442 "object" => %{"type" => "Follow", "state" => "cancelled"}
1444 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1447 test "cancels a pending follow for a remote user" do
1448 follower = insert(:user)
1449 followed = insert(:user, is_locked: true, local: false)
1451 assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
1452 CommonAPI.follow(follower, followed)
1454 assert User.get_follow_state(follower, followed) == :follow_pending
1455 assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
1456 assert User.get_follow_state(follower, followed) == nil
1458 assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
1459 Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
1464 "object" => %{"type" => "Follow", "state" => "cancelled"}
1466 } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
1470 describe "accept_follow_request/2" do
1471 test "after acceptance, it sets all existing pending follow request states to 'accept'" do
1472 user = insert(:user, is_locked: true)
1473 follower = insert(:user)
1474 follower_two = insert(:user)
1476 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1477 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1478 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1480 assert follow_activity.data["state"] == "pending"
1481 assert follow_activity_two.data["state"] == "pending"
1482 assert follow_activity_three.data["state"] == "pending"
1484 {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
1486 assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
1487 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
1488 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1491 test "after rejection, it sets all existing pending follow request states to 'reject'" do
1492 user = insert(:user, is_locked: true)
1493 follower = insert(:user)
1494 follower_two = insert(:user)
1496 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
1497 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
1498 {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
1500 assert follow_activity.data["state"] == "pending"
1501 assert follow_activity_two.data["state"] == "pending"
1502 assert follow_activity_three.data["state"] == "pending"
1504 {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
1506 assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
1507 assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
1508 assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
1511 test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
1512 user = insert(:user, is_locked: true)
1513 not_follower = insert(:user)
1514 CommonAPI.accept_follow_request(not_follower, user)
1516 assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
1520 describe "vote/3" do
1521 test "does not allow to vote twice" do
1522 user = insert(:user)
1523 other_user = insert(:user)
1526 CommonAPI.post(user, %{
1527 status: "Am I cute?",
1528 poll: %{options: ["Yes", "No"], expires_in: 20}
1531 object = Object.normalize(activity, fetch: false)
1533 {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
1535 assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
1539 describe "listen/2" do
1540 test "returns a valid activity" do
1541 user = insert(:user)
1544 CommonAPI.listen(user, %{
1545 title: "lain radio episode 1",
1546 album: "lain radio",
1551 object = Object.normalize(activity, fetch: false)
1553 assert object.data["title"] == "lain radio episode 1"
1555 assert Visibility.get_visibility(activity) == "public"
1558 test "respects visibility=private" do
1559 user = insert(:user)
1562 CommonAPI.listen(user, %{
1563 title: "lain radio episode 1",
1564 album: "lain radio",
1567 visibility: "private"
1570 object = Object.normalize(activity, fetch: false)
1572 assert object.data["title"] == "lain radio episode 1"
1574 assert Visibility.get_visibility(activity) == "private"
1578 describe "get_user/1" do
1579 test "gets user by ap_id" do
1580 user = insert(:user)
1581 assert CommonAPI.get_user(user.ap_id) == user
1584 test "gets user by guessed nickname" do
1585 user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
1586 assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
1593 nickname: "erroruser@example.com"
1594 } = CommonAPI.get_user("")
1598 describe "with `local` visibility" do
1599 setup do: clear_config([:instance, :federating], true)
1602 user = insert(:user)
1604 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1605 {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1607 assert Visibility.local_public?(activity)
1608 assert_not_called(Pleroma.Web.Federator.publish(activity))
1613 user = insert(:user)
1615 {:ok, %Activity{id: activity_id}} =
1616 CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
1618 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1619 assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
1620 CommonAPI.delete(activity_id, user)
1622 assert Visibility.local_public?(activity)
1623 assert_not_called(Pleroma.Web.Federator.publish(activity))
1628 user = insert(:user)
1629 other_user = insert(:user)
1631 {:ok, %Activity{id: activity_id}} =
1632 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1634 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1635 assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
1636 CommonAPI.repeat(activity_id, user)
1638 assert Visibility.local_public?(activity)
1639 refute called(Pleroma.Web.Federator.publish(activity))
1644 user = insert(:user)
1645 other_user = insert(:user)
1647 {:ok, %Activity{id: activity_id}} =
1648 CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1650 assert {:ok, _} = CommonAPI.repeat(activity_id, user)
1652 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1653 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1654 CommonAPI.unrepeat(activity_id, user)
1656 assert Visibility.local_public?(activity)
1657 refute called(Pleroma.Web.Federator.publish(activity))
1662 user = insert(:user)
1663 other_user = insert(:user)
1665 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1667 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1668 assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
1669 CommonAPI.favorite(user, activity.id)
1671 assert Visibility.local_public?(activity)
1672 refute called(Pleroma.Web.Federator.publish(activity))
1676 test "unfavorite" do
1677 user = insert(:user)
1678 other_user = insert(:user)
1680 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1682 {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
1684 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1685 assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
1686 assert Visibility.local_public?(activity)
1687 refute called(Pleroma.Web.Federator.publish(activity))
1691 test "react_with_emoji" do
1692 user = insert(:user)
1693 other_user = insert(:user)
1694 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1696 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1697 assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
1698 CommonAPI.react_with_emoji(activity.id, user, "👍")
1700 assert Visibility.local_public?(activity)
1701 refute called(Pleroma.Web.Federator.publish(activity))
1705 test "unreact_with_emoji" do
1706 user = insert(:user)
1707 other_user = insert(:user)
1708 {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
1710 {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
1712 with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
1713 assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
1714 CommonAPI.unreact_with_emoji(activity.id, user, "👍")
1716 assert Visibility.local_public?(activity)
1717 refute called(Pleroma.Web.Federator.publish(activity))
1722 describe "update/3" do
1723 test "updates a post" do
1724 user = insert(:user)
1725 {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1"})
1727 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1729 updated_object = Object.normalize(updated)
1730 assert updated_object.data["content"] == "updated 2"
1731 assert Map.get(updated_object.data, "summary", "") == ""
1732 assert Map.has_key?(updated_object.data, "updated")
1735 test "does not change visibility" do
1736 user = insert(:user)
1739 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1", visibility: "private"})
1741 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1743 updated_object = Object.normalize(updated)
1744 assert updated_object.data["content"] == "updated 2"
1745 assert Map.get(updated_object.data, "summary", "") == ""
1746 assert Visibility.get_visibility(updated_object) == "private"
1747 assert Visibility.get_visibility(updated) == "private"
1750 test "updates a post with emoji" do
1751 [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1753 user = insert(:user)
1756 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1758 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1760 updated_object = Object.normalize(updated)
1761 assert updated_object.data["content"] == "updated 2 :#{emoji2}:"
1762 assert %{^emoji2 => _} = updated_object.data["emoji"]
1765 test "updates a post with emoji and federate properly" do
1766 [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
1768 user = insert(:user)
1771 CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
1773 clear_config([:instance, :federating], true)
1775 with_mock Pleroma.Web.Federator,
1776 publish: fn _p -> nil end do
1777 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2 :#{emoji2}:"})
1779 assert updated.data["object"]["content"] == "updated 2 :#{emoji2}:"
1780 assert %{^emoji2 => _} = updated.data["object"]["emoji"]
1782 assert called(Pleroma.Web.Federator.publish(updated))
1786 test "editing a post that copied a remote title with remote emoji should keep that emoji" do
1787 remote_emoji_uri = "https://remote.org/emoji.png"
1793 "summary" => ":remoteemoji:",
1795 "remoteemoji" => remote_emoji_uri
1800 "name" => "remoteemoji",
1801 "icon" => %{"url" => remote_emoji_uri}
1807 note_activity = insert(:note_activity, note: note)
1809 user = insert(:user)
1812 CommonAPI.post(user, %{
1814 spoiler_text: ":remoteemoji:",
1815 in_reply_to_id: note_activity.id
1818 assert reply.object.data["emoji"]["remoteemoji"] == remote_emoji_uri
1821 CommonAPI.update(user, reply, %{status: "reply mew mew", spoiler_text: ":remoteemoji:"})
1823 edited_note = Pleroma.Object.normalize(edit)
1825 assert edited_note.data["emoji"]["remoteemoji"] == remote_emoji_uri
1828 test "respects MRF" do
1829 user = insert(:user)
1831 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
1832 clear_config([:mrf_keyword, :replace], [{"updated", "mewmew"}])
1834 {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "updated 1"})
1835 assert Object.normalize(activity).data["summary"] == "mewmew 1"
1837 {:ok, updated} = CommonAPI.update(user, activity, %{status: "updated 2"})
1839 updated_object = Object.normalize(updated)
1840 assert updated_object.data["content"] == "mewmew 2"
1841 assert Map.get(updated_object.data, "summary", "") == ""
1842 assert Map.has_key?(updated_object.data, "updated")
1846 describe "Group actors" do
1848 poster = insert(:user)
1849 group = insert(:user, actor_type: "Group")
1850 other_group = insert(:user, actor_type: "Group")
1851 %{poster: poster, group: group, other_group: other_group}
1854 test "it boosts public posts", %{poster: poster, group: group} do
1855 {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"})
1857 announces = get_announces_of_object(post.object)
1858 assert [_] = announces
1861 test "it does not boost private posts", %{poster: poster, group: group} do
1862 {:ok, private_post} =
1863 CommonAPI.post(poster, %{status: "hey @#{group.nickname}", visibility: "private"})
1865 assert [] = get_announces_of_object(private_post.object)
1868 test "remote groups do not boost any posts", %{poster: poster} do
1870 insert(:user, actor_type: "Group", local: false, nickname: "remote@example.com")
1872 {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{User.full_nickname(remote_group)}"})
1873 assert remote_group.ap_id in post.data["to"]
1875 announces = get_announces_of_object(post.object)
1876 assert [] = announces
1879 test "multiple groups mentioned", %{poster: poster, group: group, other_group: other_group} do
1881 CommonAPI.post(poster, %{status: "hey @#{group.nickname} @#{other_group.nickname}"})
1883 announces = get_announces_of_object(post.object)
1884 assert [_, _] = announces
1887 test "it does not boost if group is blocking poster", %{poster: poster, group: group} do
1888 {:ok, _} = CommonAPI.block(group, poster)
1889 {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"})
1891 announces = get_announces_of_object(post.object)
1892 assert [] = announces