1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 use Oban.Testing, repo: Pleroma.Repo
11 alias Pleroma.Delivery
12 alias Pleroma.Instances
14 alias Pleroma.Tests.ObanHelpers
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.ActivityPub.ObjectView
18 alias Pleroma.Web.ActivityPub.Relay
19 alias Pleroma.Web.ActivityPub.UserView
20 alias Pleroma.Web.ActivityPub.Utils
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Workers.ReceiverWorker
25 import Pleroma.Factory
27 require Pleroma.Constants
30 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
34 setup do: clear_config([:instance, :federating], true)
37 setup do: clear_config([:instance, :allow_relay])
39 test "with the relay active, it returns the relay user", %{conn: conn} do
42 |> get(activity_pub_path(conn, :relay))
45 assert res["id"] =~ "/relay"
48 test "with the relay disabled, it returns 404", %{conn: conn} do
49 Config.put([:instance, :allow_relay], false)
52 |> get(activity_pub_path(conn, :relay))
56 test "on non-federating instance, it returns 404", %{conn: conn} do
57 Config.put([:instance, :federating], false)
61 |> assign(:user, user)
62 |> get(activity_pub_path(conn, :relay))
67 describe "/internal/fetch" do
68 test "it returns the internal fetch user", %{conn: conn} do
71 |> get(activity_pub_path(conn, :internal_fetch))
74 assert res["id"] =~ "/fetch"
77 test "on non-federating instance, it returns 404", %{conn: conn} do
78 Config.put([:instance, :federating], false)
82 |> assign(:user, user)
83 |> get(activity_pub_path(conn, :internal_fetch))
88 describe "/users/:nickname" do
89 test "it returns a json representation of the user with accept application/json", %{
96 |> put_req_header("accept", "application/json")
97 |> get("/users/#{user.nickname}")
99 user = User.get_cached_by_id(user.id)
101 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
104 test "it returns a json representation of the user with accept application/activity+json", %{
111 |> put_req_header("accept", "application/activity+json")
112 |> get("/users/#{user.nickname}")
114 user = User.get_cached_by_id(user.id)
116 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
119 test "it returns a json representation of the user with accept application/ld+json", %{
128 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
130 |> get("/users/#{user.nickname}")
132 user = User.get_cached_by_id(user.id)
134 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
137 test "it returns 404 for remote users", %{
140 user = insert(:user, local: false, nickname: "remoteuser@example.com")
144 |> put_req_header("accept", "application/json")
145 |> get("/users/#{user.nickname}.json")
147 assert json_response(conn, 404)
150 test "it returns error when user is not found", %{conn: conn} do
153 |> put_req_header("accept", "application/json")
154 |> get("/users/jimm")
155 |> json_response(404)
157 assert response == "Not found"
161 describe "mastodon compatibility routes" do
162 test "it returns a json representation of the object with accept application/json", %{
169 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
170 "actor" => Endpoint.url() <> "/users/raymoo",
171 "to" => [Pleroma.Constants.as_public()]
177 |> put_req_header("accept", "application/json")
178 |> get("/users/raymoo/statuses/999999999")
180 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
183 test "it returns a json representation of the activity with accept application/json", %{
190 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
191 "actor" => Endpoint.url() <> "/users/raymoo",
192 "to" => [Pleroma.Constants.as_public()]
198 "id" => object.data["id"] <> "/activity",
200 "object" => object.data["id"],
201 "actor" => object.data["actor"],
202 "to" => object.data["to"]
204 |> ActivityPub.persist(local: true)
208 |> put_req_header("accept", "application/json")
209 |> get("/users/raymoo/statuses/999999999/activity")
211 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
215 describe "/objects/:uuid" do
216 test "it returns a json representation of the object with accept application/json", %{
220 uuid = String.split(note.data["id"], "/") |> List.last()
224 |> put_req_header("accept", "application/json")
225 |> get("/objects/#{uuid}")
227 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
230 test "it returns a json representation of the object with accept application/activity+json",
233 uuid = String.split(note.data["id"], "/") |> List.last()
237 |> put_req_header("accept", "application/activity+json")
238 |> get("/objects/#{uuid}")
240 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
243 test "it returns a json representation of the object with accept application/ld+json", %{
247 uuid = String.split(note.data["id"], "/") |> List.last()
253 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
255 |> get("/objects/#{uuid}")
257 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
260 test "it returns 404 for non-public messages", %{conn: conn} do
261 note = insert(:direct_note)
262 uuid = String.split(note.data["id"], "/") |> List.last()
266 |> put_req_header("accept", "application/activity+json")
267 |> get("/objects/#{uuid}")
269 assert json_response(conn, 404)
272 test "it returns 404 for tombstone objects", %{conn: conn} do
273 tombstone = insert(:tombstone)
274 uuid = String.split(tombstone.data["id"], "/") |> List.last()
278 |> put_req_header("accept", "application/activity+json")
279 |> get("/objects/#{uuid}")
281 assert json_response(conn, 404)
284 test "it caches a response", %{conn: conn} do
286 uuid = String.split(note.data["id"], "/") |> List.last()
290 |> put_req_header("accept", "application/activity+json")
291 |> get("/objects/#{uuid}")
293 assert json_response(conn1, :ok)
294 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
298 |> put_req_header("accept", "application/activity+json")
299 |> get("/objects/#{uuid}")
301 assert json_response(conn1, :ok) == json_response(conn2, :ok)
302 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
305 test "cached purged after object deletion", %{conn: conn} do
307 uuid = String.split(note.data["id"], "/") |> List.last()
311 |> put_req_header("accept", "application/activity+json")
312 |> get("/objects/#{uuid}")
314 assert json_response(conn1, :ok)
315 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
321 |> put_req_header("accept", "application/activity+json")
322 |> get("/objects/#{uuid}")
324 assert "Not found" == json_response(conn2, :not_found)
328 describe "/activities/:uuid" do
329 test "it returns a json representation of the activity", %{conn: conn} do
330 activity = insert(:note_activity)
331 uuid = String.split(activity.data["id"], "/") |> List.last()
335 |> put_req_header("accept", "application/activity+json")
336 |> get("/activities/#{uuid}")
338 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
341 test "it returns 404 for non-public activities", %{conn: conn} do
342 activity = insert(:direct_note_activity)
343 uuid = String.split(activity.data["id"], "/") |> List.last()
347 |> put_req_header("accept", "application/activity+json")
348 |> get("/activities/#{uuid}")
350 assert json_response(conn, 404)
353 test "it caches a response", %{conn: conn} do
354 activity = insert(:note_activity)
355 uuid = String.split(activity.data["id"], "/") |> List.last()
359 |> put_req_header("accept", "application/activity+json")
360 |> get("/activities/#{uuid}")
362 assert json_response(conn1, :ok)
363 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
367 |> put_req_header("accept", "application/activity+json")
368 |> get("/activities/#{uuid}")
370 assert json_response(conn1, :ok) == json_response(conn2, :ok)
371 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
374 test "cached purged after activity deletion", %{conn: conn} do
376 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
378 uuid = String.split(activity.data["id"], "/") |> List.last()
382 |> put_req_header("accept", "application/activity+json")
383 |> get("/activities/#{uuid}")
385 assert json_response(conn1, :ok)
386 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
388 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
392 |> put_req_header("accept", "application/activity+json")
393 |> get("/activities/#{uuid}")
395 assert "Not found" == json_response(conn2, :not_found)
400 test "it inserts an incoming activity into the database", %{conn: conn} do
401 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
405 |> assign(:valid_signature, true)
406 |> put_req_header("content-type", "application/activity+json")
407 |> post("/inbox", data)
409 assert "ok" == json_response(conn, 200)
411 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
412 assert Activity.get_by_ap_id(data["id"])
415 @tag capture_log: true
416 test "it inserts an incoming activity into the database" <>
417 "even if we can't fetch the user but have it in our db",
421 ap_id: "https://mastodon.example.org/users/raymoo",
424 last_refreshed_at: nil
428 File.read!("test/fixtures/mastodon-post-activity.json")
430 |> Map.put("actor", user.ap_id)
431 |> put_in(["object", "attridbutedTo"], user.ap_id)
435 |> assign(:valid_signature, true)
436 |> put_req_header("content-type", "application/activity+json")
437 |> post("/inbox", data)
439 assert "ok" == json_response(conn, 200)
441 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
442 assert Activity.get_by_ap_id(data["id"])
445 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
446 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
448 sender_url = data["actor"]
449 Instances.set_consistently_unreachable(sender_url)
450 refute Instances.reachable?(sender_url)
454 |> assign(:valid_signature, true)
455 |> put_req_header("content-type", "application/activity+json")
456 |> post("/inbox", data)
458 assert "ok" == json_response(conn, 200)
459 assert Instances.reachable?(sender_url)
462 test "accept follow activity", %{conn: conn} do
463 Pleroma.Config.put([:instance, :federating], true)
464 relay = Relay.get_actor()
466 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
468 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
469 relay = refresh_record(relay)
472 File.read!("test/fixtures/relay/accept-follow.json")
473 |> String.replace("{{ap_id}}", relay.ap_id)
474 |> String.replace("{{activity_id}}", activity.data["id"])
478 |> assign(:valid_signature, true)
479 |> put_req_header("content-type", "application/activity+json")
480 |> post("/inbox", accept)
481 |> json_response(200)
483 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
485 assert Pleroma.FollowingRelationship.following?(
490 Mix.shell(Mix.Shell.Process)
493 Mix.shell(Mix.Shell.IO)
496 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
497 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
500 @tag capture_log: true
501 test "without valid signature, " <>
502 "it only accepts Create activities and requires enabled federation",
504 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
505 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
507 conn = put_req_header(conn, "content-type", "application/activity+json")
509 Config.put([:instance, :federating], false)
512 |> post("/inbox", data)
513 |> json_response(403)
516 |> post("/inbox", non_create_data)
517 |> json_response(403)
519 Config.put([:instance, :federating], true)
521 ret_conn = post(conn, "/inbox", data)
522 assert "ok" == json_response(ret_conn, 200)
525 |> post("/inbox", non_create_data)
526 |> json_response(400)
530 describe "/users/:nickname/inbox" do
533 File.read!("test/fixtures/mastodon-post-activity.json")
539 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
541 data = Map.put(data, "bcc", [user.ap_id])
545 |> assign(:valid_signature, true)
546 |> put_req_header("content-type", "application/activity+json")
547 |> post("/users/#{user.nickname}/inbox", data)
549 assert "ok" == json_response(conn, 200)
550 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
551 assert Activity.get_by_ap_id(data["id"])
554 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
558 Map.put(data, "to", user.ap_id)
563 |> assign(:valid_signature, true)
564 |> put_req_header("content-type", "application/activity+json")
565 |> post("/users/#{user.nickname}/inbox", data)
567 assert "ok" == json_response(conn, 200)
568 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
569 assert Activity.get_by_ap_id(data["id"])
572 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
576 Map.put(data, "cc", user.ap_id)
581 |> assign(:valid_signature, true)
582 |> put_req_header("content-type", "application/activity+json")
583 |> post("/users/#{user.nickname}/inbox", data)
585 assert "ok" == json_response(conn, 200)
586 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
587 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
588 assert user.ap_id in activity.recipients
591 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
595 Map.put(data, "bcc", user.ap_id)
601 |> assign(:valid_signature, true)
602 |> put_req_header("content-type", "application/activity+json")
603 |> post("/users/#{user.nickname}/inbox", data)
605 assert "ok" == json_response(conn, 200)
606 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
607 assert Activity.get_by_ap_id(data["id"])
610 test "it accepts announces with to as string instead of array", %{conn: conn} do
613 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
614 announcer = insert(:user, local: false)
617 "@context" => "https://www.w3.org/ns/activitystreams",
618 "actor" => announcer.ap_id,
619 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
620 "object" => post.data["object"],
621 "to" => "https://www.w3.org/ns/activitystreams#Public",
622 "cc" => [user.ap_id],
628 |> assign(:valid_signature, true)
629 |> put_req_header("content-type", "application/activity+json")
630 |> post("/users/#{user.nickname}/inbox", data)
632 assert "ok" == json_response(conn, 200)
633 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
634 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
635 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
638 test "it accepts messages from actors that are followed by the user", %{
642 recipient = insert(:user)
643 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
645 {:ok, recipient} = User.follow(recipient, actor)
649 |> Map.put("attributedTo", actor.ap_id)
653 |> Map.put("actor", actor.ap_id)
654 |> Map.put("object", object)
658 |> assign(:valid_signature, true)
659 |> put_req_header("content-type", "application/activity+json")
660 |> post("/users/#{recipient.nickname}/inbox", data)
662 assert "ok" == json_response(conn, 200)
663 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
664 assert Activity.get_by_ap_id(data["id"])
667 test "it rejects reads from other users", %{conn: conn} do
669 other_user = insert(:user)
673 |> assign(:user, other_user)
674 |> put_req_header("accept", "application/activity+json")
675 |> get("/users/#{user.nickname}/inbox")
677 assert json_response(conn, 403)
680 test "it returns a note activity in a collection", %{conn: conn} do
681 note_activity = insert(:direct_note_activity)
682 note_object = Object.normalize(note_activity)
683 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
687 |> assign(:user, user)
688 |> put_req_header("accept", "application/activity+json")
689 |> get("/users/#{user.nickname}/inbox?page=true")
691 assert response(conn, 200) =~ note_object.data["content"]
694 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
696 data = Map.put(data, "bcc", [user.ap_id])
698 sender_host = URI.parse(data["actor"]).host
699 Instances.set_consistently_unreachable(sender_host)
700 refute Instances.reachable?(sender_host)
704 |> assign(:valid_signature, true)
705 |> put_req_header("content-type", "application/activity+json")
706 |> post("/users/#{user.nickname}/inbox", data)
708 assert "ok" == json_response(conn, 200)
709 assert Instances.reachable?(sender_host)
712 test "it removes all follower collections but actor's", %{conn: conn} do
713 [actor, recipient] = insert_pair(:user)
716 File.read!("test/fixtures/activitypub-client-post-activity.json")
719 object = Map.put(data["object"], "attributedTo", actor.ap_id)
723 |> Map.put("id", Utils.generate_object_id())
724 |> Map.put("actor", actor.ap_id)
725 |> Map.put("object", object)
727 recipient.follower_address,
728 actor.follower_address
732 recipient.follower_address,
733 "https://www.w3.org/ns/activitystreams#Public"
737 |> assign(:valid_signature, true)
738 |> put_req_header("content-type", "application/activity+json")
739 |> post("/users/#{recipient.nickname}/inbox", data)
740 |> json_response(200)
742 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
744 activity = Activity.get_by_ap_id(data["id"])
747 assert actor.follower_address in activity.recipients
748 assert actor.follower_address in activity.data["cc"]
750 refute recipient.follower_address in activity.recipients
751 refute recipient.follower_address in activity.data["cc"]
752 refute recipient.follower_address in activity.data["to"]
755 test "it requires authentication", %{conn: conn} do
757 conn = put_req_header(conn, "accept", "application/activity+json")
759 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
760 assert json_response(ret_conn, 403)
764 |> assign(:user, user)
765 |> get("/users/#{user.nickname}/inbox")
767 assert json_response(ret_conn, 200)
771 describe "GET /users/:nickname/outbox" do
772 test "it paginates correctly", %{conn: conn} do
774 conn = assign(conn, :user, user)
775 outbox_endpoint = user.ap_id <> "/outbox"
779 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
785 |> put_req_header("accept", "application/activity+json")
786 |> get(outbox_endpoint <> "?page=true")
787 |> json_response(200)
789 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
790 assert length(result["orderedItems"]) == 20
791 assert length(result_ids) == 20
792 assert result["next"]
793 assert String.starts_with?(result["next"], outbox_endpoint)
797 |> put_req_header("accept", "application/activity+json")
798 |> get(result["next"])
799 |> json_response(200)
801 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
802 assert length(result_next["orderedItems"]) == 6
803 assert length(result_next_ids) == 6
804 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
805 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
806 assert String.starts_with?(result["id"], outbox_endpoint)
810 |> put_req_header("accept", "application/activity+json")
811 |> get(result_next["id"])
812 |> json_response(200)
814 assert result_next == result_next_again
817 test "it returns 200 even if there're no activities", %{conn: conn} do
819 outbox_endpoint = user.ap_id <> "/outbox"
823 |> assign(:user, user)
824 |> put_req_header("accept", "application/activity+json")
825 |> get(outbox_endpoint)
827 result = json_response(conn, 200)
828 assert outbox_endpoint == result["id"]
831 test "it returns a note activity in a collection", %{conn: conn} do
832 note_activity = insert(:note_activity)
833 note_object = Object.normalize(note_activity)
834 user = User.get_cached_by_ap_id(note_activity.data["actor"])
838 |> assign(:user, user)
839 |> put_req_header("accept", "application/activity+json")
840 |> get("/users/#{user.nickname}/outbox?page=true")
842 assert response(conn, 200) =~ note_object.data["content"]
845 test "it returns an announce activity in a collection", %{conn: conn} do
846 announce_activity = insert(:announce_activity)
847 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
851 |> assign(:user, user)
852 |> put_req_header("accept", "application/activity+json")
853 |> get("/users/#{user.nickname}/outbox?page=true")
855 assert response(conn, 200) =~ announce_activity.data["object"]
859 describe "POST /users/:nickname/outbox (C2S)" do
860 setup do: clear_config([:instance, :limit])
865 "@context" => "https://www.w3.org/ns/activitystreams",
867 "object" => %{"type" => "Note", "content" => "AP C2S test"},
868 "to" => "https://www.w3.org/ns/activitystreams#Public",
874 test "it rejects posts from other users / unauthenticated users", %{
879 other_user = insert(:user)
880 conn = put_req_header(conn, "content-type", "application/activity+json")
883 |> post("/users/#{user.nickname}/outbox", activity)
884 |> json_response(403)
887 |> assign(:user, other_user)
888 |> post("/users/#{user.nickname}/outbox", activity)
889 |> json_response(403)
892 test "it inserts an incoming create activity into the database", %{
900 |> assign(:user, user)
901 |> put_req_header("content-type", "application/activity+json")
902 |> post("/users/#{user.nickname}/outbox", activity)
903 |> json_response(201)
905 assert Activity.get_by_ap_id(result["id"])
906 assert result["object"]
907 assert %Object{data: object} = Object.normalize(result["object"])
908 assert object["content"] == activity["object"]["content"]
911 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
916 |> put_in(["object", "type"], "Benis")
920 |> assign(:user, user)
921 |> put_req_header("content-type", "application/activity+json")
922 |> post("/users/#{user.nickname}/outbox", activity)
923 |> json_response(400)
926 test "it inserts an incoming sensitive activity into the database", %{
931 conn = assign(conn, :user, user)
932 object = Map.put(activity["object"], "sensitive", true)
933 activity = Map.put(activity, "object", object)
937 |> put_req_header("content-type", "application/activity+json")
938 |> post("/users/#{user.nickname}/outbox", activity)
939 |> json_response(201)
941 assert Activity.get_by_ap_id(response["id"])
942 assert response["object"]
943 assert %Object{data: response_object} = Object.normalize(response["object"])
944 assert response_object["sensitive"] == true
945 assert response_object["content"] == activity["object"]["content"]
949 |> put_req_header("accept", "application/activity+json")
950 |> get(response["id"])
951 |> json_response(200)
953 assert representation["object"]["sensitive"] == true
956 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
958 activity = Map.put(activity, "type", "BadType")
962 |> assign(:user, user)
963 |> put_req_header("content-type", "application/activity+json")
964 |> post("/users/#{user.nickname}/outbox", activity)
966 assert json_response(conn, 400)
969 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
970 note_activity = insert(:note_activity)
971 note_object = Object.normalize(note_activity)
972 user = User.get_cached_by_ap_id(note_activity.data["actor"])
977 id: note_object.data["id"]
983 |> assign(:user, user)
984 |> put_req_header("content-type", "application/activity+json")
985 |> post("/users/#{user.nickname}/outbox", data)
987 result = json_response(conn, 201)
988 assert Activity.get_by_ap_id(result["id"])
990 assert object = Object.get_by_ap_id(note_object.data["id"])
991 assert object.data["type"] == "Tombstone"
994 test "it rejects delete activity of object from other actor", %{conn: conn} do
995 note_activity = insert(:note_activity)
996 note_object = Object.normalize(note_activity)
1002 id: note_object.data["id"]
1008 |> assign(:user, user)
1009 |> put_req_header("content-type", "application/activity+json")
1010 |> post("/users/#{user.nickname}/outbox", data)
1012 assert json_response(conn, 400)
1015 test "it increases like count when receiving a like action", %{conn: conn} do
1016 note_activity = insert(:note_activity)
1017 note_object = Object.normalize(note_activity)
1018 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1023 id: note_object.data["id"]
1029 |> assign(:user, user)
1030 |> put_req_header("content-type", "application/activity+json")
1031 |> post("/users/#{user.nickname}/outbox", data)
1033 result = json_response(conn, 201)
1034 assert Activity.get_by_ap_id(result["id"])
1036 assert object = Object.get_by_ap_id(note_object.data["id"])
1037 assert object.data["like_count"] == 1
1040 test "it doesn't spreads faulty attributedTo or actor fields", %{
1044 reimu = insert(:user, nickname: "reimu")
1045 cirno = insert(:user, nickname: "cirno")
1052 |> put_in(["object", "actor"], reimu.ap_id)
1053 |> put_in(["object", "attributedTo"], reimu.ap_id)
1054 |> put_in(["actor"], reimu.ap_id)
1055 |> put_in(["attributedTo"], reimu.ap_id)
1059 |> assign(:user, cirno)
1060 |> put_req_header("content-type", "application/activity+json")
1061 |> post("/users/#{reimu.nickname}/outbox", activity)
1062 |> json_response(403)
1066 |> assign(:user, cirno)
1067 |> put_req_header("content-type", "application/activity+json")
1068 |> post("/users/#{cirno.nickname}/outbox", activity)
1069 |> json_response(201)
1071 assert cirno_outbox["attributedTo"] == nil
1072 assert cirno_outbox["actor"] == cirno.ap_id
1074 assert cirno_object = Object.normalize(cirno_outbox["object"])
1075 assert cirno_object.data["actor"] == cirno.ap_id
1076 assert cirno_object.data["attributedTo"] == cirno.ap_id
1079 test "Character limitation", %{conn: conn, activity: activity} do
1080 Pleroma.Config.put([:instance, :limit], 5)
1081 user = insert(:user)
1085 |> assign(:user, user)
1086 |> put_req_header("content-type", "application/activity+json")
1087 |> post("/users/#{user.nickname}/outbox", activity)
1088 |> json_response(400)
1090 assert result == "Note is over the character limit"
1094 describe "/relay/followers" do
1095 test "it returns relay followers", %{conn: conn} do
1096 relay_actor = Relay.get_actor()
1097 user = insert(:user)
1098 User.follow(user, relay_actor)
1102 |> get("/relay/followers")
1103 |> json_response(200)
1105 assert result["first"]["orderedItems"] == [user.ap_id]
1108 test "on non-federating instance, it returns 404", %{conn: conn} do
1109 Config.put([:instance, :federating], false)
1110 user = insert(:user)
1113 |> assign(:user, user)
1114 |> get("/relay/followers")
1115 |> json_response(404)
1119 describe "/relay/following" do
1120 test "it returns relay following", %{conn: conn} do
1123 |> get("/relay/following")
1124 |> json_response(200)
1126 assert result["first"]["orderedItems"] == []
1129 test "on non-federating instance, it returns 404", %{conn: conn} do
1130 Config.put([:instance, :federating], false)
1131 user = insert(:user)
1134 |> assign(:user, user)
1135 |> get("/relay/following")
1136 |> json_response(404)
1140 describe "/users/:nickname/followers" do
1141 test "it returns the followers in a collection", %{conn: conn} do
1142 user = insert(:user)
1143 user_two = insert(:user)
1144 User.follow(user, user_two)
1148 |> assign(:user, user_two)
1149 |> get("/users/#{user_two.nickname}/followers")
1150 |> json_response(200)
1152 assert result["first"]["orderedItems"] == [user.ap_id]
1155 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1156 user = insert(:user)
1157 user_two = insert(:user, hide_followers: true)
1158 User.follow(user, user_two)
1162 |> assign(:user, user)
1163 |> get("/users/#{user_two.nickname}/followers")
1164 |> json_response(200)
1166 assert is_binary(result["first"])
1169 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1171 user = insert(:user)
1172 other_user = insert(:user, hide_followers: true)
1176 |> assign(:user, user)
1177 |> get("/users/#{other_user.nickname}/followers?page=1")
1179 assert result.status == 403
1180 assert result.resp_body == ""
1183 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1185 user = insert(:user, hide_followers: true)
1186 other_user = insert(:user)
1187 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1191 |> assign(:user, user)
1192 |> get("/users/#{user.nickname}/followers?page=1")
1193 |> json_response(200)
1195 assert result["totalItems"] == 1
1196 assert result["orderedItems"] == [other_user.ap_id]
1199 test "it works for more than 10 users", %{conn: conn} do
1200 user = insert(:user)
1202 Enum.each(1..15, fn _ ->
1203 other_user = insert(:user)
1204 User.follow(other_user, user)
1209 |> assign(:user, user)
1210 |> get("/users/#{user.nickname}/followers")
1211 |> json_response(200)
1213 assert length(result["first"]["orderedItems"]) == 10
1214 assert result["first"]["totalItems"] == 15
1215 assert result["totalItems"] == 15
1219 |> assign(:user, user)
1220 |> get("/users/#{user.nickname}/followers?page=2")
1221 |> json_response(200)
1223 assert length(result["orderedItems"]) == 5
1224 assert result["totalItems"] == 15
1227 test "does not require authentication", %{conn: conn} do
1228 user = insert(:user)
1231 |> get("/users/#{user.nickname}/followers")
1232 |> json_response(200)
1236 describe "/users/:nickname/following" do
1237 test "it returns the following in a collection", %{conn: conn} do
1238 user = insert(:user)
1239 user_two = insert(:user)
1240 User.follow(user, user_two)
1244 |> assign(:user, user)
1245 |> get("/users/#{user.nickname}/following")
1246 |> json_response(200)
1248 assert result["first"]["orderedItems"] == [user_two.ap_id]
1251 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1252 user = insert(:user)
1253 user_two = insert(:user, hide_follows: true)
1254 User.follow(user, user_two)
1258 |> assign(:user, user)
1259 |> get("/users/#{user_two.nickname}/following")
1260 |> json_response(200)
1262 assert is_binary(result["first"])
1265 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1267 user = insert(:user)
1268 user_two = insert(:user, hide_follows: true)
1272 |> assign(:user, user)
1273 |> get("/users/#{user_two.nickname}/following?page=1")
1275 assert result.status == 403
1276 assert result.resp_body == ""
1279 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1281 user = insert(:user, hide_follows: true)
1282 other_user = insert(:user)
1283 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1287 |> assign(:user, user)
1288 |> get("/users/#{user.nickname}/following?page=1")
1289 |> json_response(200)
1291 assert result["totalItems"] == 1
1292 assert result["orderedItems"] == [other_user.ap_id]
1295 test "it works for more than 10 users", %{conn: conn} do
1296 user = insert(:user)
1298 Enum.each(1..15, fn _ ->
1299 user = User.get_cached_by_id(user.id)
1300 other_user = insert(:user)
1301 User.follow(user, other_user)
1306 |> assign(:user, user)
1307 |> get("/users/#{user.nickname}/following")
1308 |> json_response(200)
1310 assert length(result["first"]["orderedItems"]) == 10
1311 assert result["first"]["totalItems"] == 15
1312 assert result["totalItems"] == 15
1316 |> assign(:user, user)
1317 |> get("/users/#{user.nickname}/following?page=2")
1318 |> json_response(200)
1320 assert length(result["orderedItems"]) == 5
1321 assert result["totalItems"] == 15
1324 test "does not require authentication", %{conn: conn} do
1325 user = insert(:user)
1328 |> get("/users/#{user.nickname}/following")
1329 |> json_response(200)
1333 describe "delivery tracking" do
1334 test "it tracks a signed object fetch", %{conn: conn} do
1335 user = insert(:user, local: false)
1336 activity = insert(:note_activity)
1337 object = Object.normalize(activity)
1339 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1342 |> put_req_header("accept", "application/activity+json")
1343 |> assign(:user, user)
1345 |> json_response(200)
1347 assert Delivery.get(object.id, user.id)
1350 test "it tracks a signed activity fetch", %{conn: conn} do
1351 user = insert(:user, local: false)
1352 activity = insert(:note_activity)
1353 object = Object.normalize(activity)
1355 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1358 |> put_req_header("accept", "application/activity+json")
1359 |> assign(:user, user)
1360 |> get(activity_path)
1361 |> json_response(200)
1363 assert Delivery.get(object.id, user.id)
1366 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1367 user = insert(:user, local: false)
1368 other_user = insert(:user, local: false)
1369 activity = insert(:note_activity)
1370 object = Object.normalize(activity)
1372 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1375 |> put_req_header("accept", "application/activity+json")
1376 |> assign(:user, user)
1378 |> json_response(200)
1381 |> put_req_header("accept", "application/activity+json")
1382 |> assign(:user, other_user)
1384 |> json_response(200)
1386 assert Delivery.get(object.id, user.id)
1387 assert Delivery.get(object.id, other_user.id)
1390 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1391 user = insert(:user, local: false)
1392 other_user = insert(:user, local: false)
1393 activity = insert(:note_activity)
1394 object = Object.normalize(activity)
1396 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1399 |> put_req_header("accept", "application/activity+json")
1400 |> assign(:user, user)
1401 |> get(activity_path)
1402 |> json_response(200)
1405 |> put_req_header("accept", "application/activity+json")
1406 |> assign(:user, other_user)
1407 |> get(activity_path)
1408 |> json_response(200)
1410 assert Delivery.get(object.id, user.id)
1411 assert Delivery.get(object.id, other_user.id)
1415 describe "Additional ActivityPub C2S endpoints" do
1416 test "GET /api/ap/whoami", %{conn: conn} do
1417 user = insert(:user)
1421 |> assign(:user, user)
1422 |> get("/api/ap/whoami")
1424 user = User.get_cached_by_id(user.id)
1426 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1429 |> get("/api/ap/whoami")
1430 |> json_response(403)
1433 setup do: clear_config([:media_proxy])
1434 setup do: clear_config([Pleroma.Upload])
1436 test "POST /api/ap/upload_media", %{conn: conn} do
1437 user = insert(:user)
1439 desc = "Description of the image"
1441 image = %Plug.Upload{
1442 content_type: "bad/content-type",
1443 path: Path.absname("test/fixtures/image.jpg"),
1444 filename: "an_image.png"
1449 |> assign(:user, user)
1450 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1451 |> json_response(:created)
1453 assert object["name"] == desc
1454 assert object["type"] == "Document"
1455 assert object["actor"] == user.ap_id
1456 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1457 assert is_binary(object_href)
1458 assert object_mediatype == "image/jpeg"
1459 assert String.ends_with?(object_href, ".jpg")
1461 activity_request = %{
1462 "@context" => "https://www.w3.org/ns/activitystreams",
1466 "content" => "AP C2S test, attachment",
1467 "attachment" => [object]
1469 "to" => "https://www.w3.org/ns/activitystreams#Public",
1475 |> assign(:user, user)
1476 |> post("/users/#{user.nickname}/outbox", activity_request)
1477 |> json_response(:created)
1479 assert activity_response["id"]
1480 assert activity_response["object"]
1481 assert activity_response["actor"] == user.ap_id
1483 assert %Object{data: %{"attachment" => [attachment]}} =
1484 Object.normalize(activity_response["object"])
1486 assert attachment["type"] == "Document"
1487 assert attachment["name"] == desc
1491 "href" => ^object_href,
1493 "mediaType" => ^object_mediatype
1495 ] = attachment["url"]
1497 # Fails if unauthenticated
1499 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1500 |> json_response(403)