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.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 use Oban.Testing, repo: Pleroma.Repo
10 alias Pleroma.Delivery
11 alias Pleroma.Instances
13 alias Pleroma.Tests.ObanHelpers
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
24 import Pleroma.Factory
26 require Pleroma.Constants
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
33 setup do: clear_config([:instance, :federating], true)
36 setup do: clear_config([:instance, :allow_relay])
38 test "with the relay active, it returns the relay user", %{conn: conn} do
41 |> get(activity_pub_path(conn, :relay))
44 assert res["id"] =~ "/relay"
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 clear_config([:instance, :allow_relay], false)
51 |> get(activity_pub_path(conn, :relay))
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 clear_config([:instance, :federating], false)
60 |> assign(:user, user)
61 |> get(activity_pub_path(conn, :relay))
66 describe "/internal/fetch" do
67 test "it returns the internal fetch user", %{conn: conn} do
70 |> get(activity_pub_path(conn, :internal_fetch))
73 assert res["id"] =~ "/fetch"
76 test "on non-federating instance, it returns 404", %{conn: conn} do
77 clear_config([:instance, :federating], false)
81 |> assign(:user, user)
82 |> get(activity_pub_path(conn, :internal_fetch))
87 describe "/users/:nickname" do
88 test "it returns a json representation of the user with accept application/json", %{
95 |> put_req_header("accept", "application/json")
96 |> get("/users/#{user.nickname}")
98 user = User.get_cached_by_id(user.id)
100 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
103 test "it returns a json representation of the user with accept application/activity+json", %{
110 |> put_req_header("accept", "application/activity+json")
111 |> get("/users/#{user.nickname}")
113 user = User.get_cached_by_id(user.id)
115 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
118 test "it returns a json representation of the user with accept application/ld+json", %{
127 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
129 |> get("/users/#{user.nickname}")
131 user = User.get_cached_by_id(user.id)
133 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
136 test "it returns 404 for remote users", %{
139 user = insert(:user, local: false, nickname: "remoteuser@example.com")
143 |> put_req_header("accept", "application/json")
144 |> get("/users/#{user.nickname}.json")
146 assert json_response(conn, 404)
149 test "it returns error when user is not found", %{conn: conn} do
152 |> put_req_header("accept", "application/json")
153 |> get("/users/jimm")
154 |> json_response(404)
156 assert response == "Not found"
160 describe "mastodon compatibility routes" do
161 test "it returns a json representation of the object with accept application/json", %{
168 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
169 "actor" => Endpoint.url() <> "/users/raymoo",
170 "to" => [Pleroma.Constants.as_public()]
176 |> put_req_header("accept", "application/json")
177 |> get("/users/raymoo/statuses/999999999")
179 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
182 test "it returns a json representation of the activity with accept application/json", %{
189 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
190 "actor" => Endpoint.url() <> "/users/raymoo",
191 "to" => [Pleroma.Constants.as_public()]
197 "id" => object.data["id"] <> "/activity",
199 "object" => object.data["id"],
200 "actor" => object.data["actor"],
201 "to" => object.data["to"]
203 |> ActivityPub.persist(local: true)
207 |> put_req_header("accept", "application/json")
208 |> get("/users/raymoo/statuses/999999999/activity")
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
214 describe "/objects/:uuid" do
215 test "it doesn't return a local-only object", %{conn: conn} do
217 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
219 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
221 object = Object.normalize(post, fetch: false)
222 uuid = String.split(object.data["id"], "/") |> List.last()
226 |> put_req_header("accept", "application/json")
227 |> get("/objects/#{uuid}")
229 assert json_response(conn, 404)
232 test "returns local-only objects when authenticated", %{conn: conn} do
234 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
236 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
238 object = Object.normalize(post, fetch: false)
239 uuid = String.split(object.data["id"], "/") |> List.last()
243 |> assign(:user, user)
244 |> put_req_header("accept", "application/activity+json")
245 |> get("/objects/#{uuid}")
247 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
250 test "it returns a json representation of the object with accept application/json", %{
254 uuid = String.split(note.data["id"], "/") |> List.last()
258 |> put_req_header("accept", "application/json")
259 |> get("/objects/#{uuid}")
261 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
264 test "it returns a json representation of the object with accept application/activity+json",
267 uuid = String.split(note.data["id"], "/") |> List.last()
271 |> put_req_header("accept", "application/activity+json")
272 |> get("/objects/#{uuid}")
274 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
277 test "it returns a json representation of the object with accept application/ld+json", %{
281 uuid = String.split(note.data["id"], "/") |> List.last()
287 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
289 |> get("/objects/#{uuid}")
291 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
294 test "does not cache authenticated response", %{conn: conn} do
296 reader = insert(:user)
299 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
301 object = Object.normalize(post, fetch: false)
302 uuid = String.split(object.data["id"], "/") |> List.last()
306 |> assign(:user, reader)
307 |> put_req_header("accept", "application/activity+json")
308 |> get("/objects/#{uuid}")
310 json_response(response, 200)
313 |> put_req_header("accept", "application/activity+json")
314 |> get("/objects/#{uuid}")
315 |> json_response(404)
318 test "it returns 404 for non-public messages", %{conn: conn} do
319 note = insert(:direct_note)
320 uuid = String.split(note.data["id"], "/") |> List.last()
324 |> put_req_header("accept", "application/activity+json")
325 |> get("/objects/#{uuid}")
327 assert json_response(conn, 404)
330 test "returns visible non-public messages when authenticated", %{conn: conn} do
331 note = insert(:direct_note)
332 uuid = String.split(note.data["id"], "/") |> List.last()
333 user = User.get_by_ap_id(note.data["actor"])
334 marisa = insert(:user)
337 |> assign(:user, marisa)
338 |> put_req_header("accept", "application/activity+json")
339 |> get("/objects/#{uuid}")
340 |> json_response(404)
344 |> assign(:user, user)
345 |> put_req_header("accept", "application/activity+json")
346 |> get("/objects/#{uuid}")
347 |> json_response(200)
349 assert response == ObjectView.render("object.json", %{object: note})
352 test "it returns 404 for tombstone objects", %{conn: conn} do
353 tombstone = insert(:tombstone)
354 uuid = String.split(tombstone.data["id"], "/") |> List.last()
358 |> put_req_header("accept", "application/activity+json")
359 |> get("/objects/#{uuid}")
361 assert json_response(conn, 404)
364 test "it caches a response", %{conn: conn} do
366 uuid = String.split(note.data["id"], "/") |> List.last()
370 |> put_req_header("accept", "application/activity+json")
371 |> get("/objects/#{uuid}")
373 assert json_response(conn1, :ok)
374 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
378 |> put_req_header("accept", "application/activity+json")
379 |> get("/objects/#{uuid}")
381 assert json_response(conn1, :ok) == json_response(conn2, :ok)
382 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
385 test "cached purged after object deletion", %{conn: conn} do
387 uuid = String.split(note.data["id"], "/") |> List.last()
391 |> put_req_header("accept", "application/activity+json")
392 |> get("/objects/#{uuid}")
394 assert json_response(conn1, :ok)
395 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
401 |> put_req_header("accept", "application/activity+json")
402 |> get("/objects/#{uuid}")
404 assert "Not found" == json_response(conn2, :not_found)
408 describe "/activities/:uuid" do
409 test "it doesn't return a local-only activity", %{conn: conn} do
411 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
413 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
415 uuid = String.split(post.data["id"], "/") |> List.last()
419 |> put_req_header("accept", "application/json")
420 |> get("/activities/#{uuid}")
422 assert json_response(conn, 404)
425 test "returns local-only activities when authenticated", %{conn: conn} do
427 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
429 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
431 uuid = String.split(post.data["id"], "/") |> List.last()
435 |> assign(:user, user)
436 |> put_req_header("accept", "application/activity+json")
437 |> get("/activities/#{uuid}")
439 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
442 test "it returns a json representation of the activity", %{conn: conn} do
443 activity = insert(:note_activity)
444 uuid = String.split(activity.data["id"], "/") |> List.last()
448 |> put_req_header("accept", "application/activity+json")
449 |> get("/activities/#{uuid}")
451 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
454 test "it returns 404 for non-public activities", %{conn: conn} do
455 activity = insert(:direct_note_activity)
456 uuid = String.split(activity.data["id"], "/") |> List.last()
460 |> put_req_header("accept", "application/activity+json")
461 |> get("/activities/#{uuid}")
463 assert json_response(conn, 404)
466 test "returns visible non-public messages when authenticated", %{conn: conn} do
467 note = insert(:direct_note_activity)
468 uuid = String.split(note.data["id"], "/") |> List.last()
469 user = User.get_by_ap_id(note.data["actor"])
470 marisa = insert(:user)
473 |> assign(:user, marisa)
474 |> put_req_header("accept", "application/activity+json")
475 |> get("/activities/#{uuid}")
476 |> json_response(404)
480 |> assign(:user, user)
481 |> put_req_header("accept", "application/activity+json")
482 |> get("/activities/#{uuid}")
483 |> json_response(200)
485 assert response == ObjectView.render("object.json", %{object: note})
488 test "it caches a response", %{conn: conn} do
489 activity = insert(:note_activity)
490 uuid = String.split(activity.data["id"], "/") |> List.last()
494 |> put_req_header("accept", "application/activity+json")
495 |> get("/activities/#{uuid}")
497 assert json_response(conn1, :ok)
498 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
502 |> put_req_header("accept", "application/activity+json")
503 |> get("/activities/#{uuid}")
505 assert json_response(conn1, :ok) == json_response(conn2, :ok)
506 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
509 test "cached purged after activity deletion", %{conn: conn} do
511 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
513 uuid = String.split(activity.data["id"], "/") |> List.last()
517 |> put_req_header("accept", "application/activity+json")
518 |> get("/activities/#{uuid}")
520 assert json_response(conn1, :ok)
521 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
523 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
527 |> put_req_header("accept", "application/activity+json")
528 |> get("/activities/#{uuid}")
530 assert "Not found" == json_response(conn2, :not_found)
535 test "it inserts an incoming activity into the database", %{conn: conn} do
536 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
540 |> assign(:valid_signature, true)
541 |> put_req_header("content-type", "application/activity+json")
542 |> post("/inbox", data)
544 assert "ok" == json_response(conn, 200)
546 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
547 assert Activity.get_by_ap_id(data["id"])
550 @tag capture_log: true
551 test "it inserts an incoming activity into the database" <>
552 "even if we can't fetch the user but have it in our db",
556 ap_id: "https://mastodon.example.org/users/raymoo",
559 last_refreshed_at: nil
563 File.read!("test/fixtures/mastodon-post-activity.json")
565 |> Map.put("actor", user.ap_id)
566 |> put_in(["object", "attributedTo"], user.ap_id)
570 |> assign(:valid_signature, true)
571 |> put_req_header("content-type", "application/activity+json")
572 |> post("/inbox", data)
574 assert "ok" == json_response(conn, 200)
576 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
577 assert Activity.get_by_ap_id(data["id"])
580 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
581 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
583 sender_url = data["actor"]
584 Instances.set_consistently_unreachable(sender_url)
585 refute Instances.reachable?(sender_url)
589 |> assign(:valid_signature, true)
590 |> put_req_header("content-type", "application/activity+json")
591 |> post("/inbox", data)
593 assert "ok" == json_response(conn, 200)
594 assert Instances.reachable?(sender_url)
597 test "accept follow activity", %{conn: conn} do
598 clear_config([:instance, :federating], true)
599 relay = Relay.get_actor()
601 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
603 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
604 relay = refresh_record(relay)
607 File.read!("test/fixtures/relay/accept-follow.json")
608 |> String.replace("{{ap_id}}", relay.ap_id)
609 |> String.replace("{{activity_id}}", activity.data["id"])
613 |> assign(:valid_signature, true)
614 |> put_req_header("content-type", "application/activity+json")
615 |> post("/inbox", accept)
616 |> json_response(200)
618 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
620 assert Pleroma.FollowingRelationship.following?(
625 Mix.shell(Mix.Shell.Process)
628 Mix.shell(Mix.Shell.IO)
631 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
632 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
635 @tag capture_log: true
636 test "without valid signature, " <>
637 "it only accepts Create activities and requires enabled federation",
639 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
640 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
642 conn = put_req_header(conn, "content-type", "application/activity+json")
644 clear_config([:instance, :federating], false)
647 |> post("/inbox", data)
648 |> json_response(403)
651 |> post("/inbox", non_create_data)
652 |> json_response(403)
654 clear_config([:instance, :federating], true)
656 ret_conn = post(conn, "/inbox", data)
657 assert "ok" == json_response(ret_conn, 200)
660 |> post("/inbox", non_create_data)
661 |> json_response(400)
664 test "accepts Add/Remove activities", %{conn: conn} do
665 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
668 File.read!("test/fixtures/statuses/note.json")
669 |> String.replace("{{nickname}}", "lain")
670 |> String.replace("{{object_id}}", object_id)
672 object_url = "https://example.com/objects/#{object_id}"
675 File.read!("test/fixtures/users_mock/user.json")
676 |> String.replace("{{nickname}}", "lain")
678 actor = "https://example.com/users/lain"
688 headers: [{"content-type", "application/activity+json"}]
698 headers: [{"content-type", "application/activity+json"}]
701 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
705 "test/fixtures/users_mock/masto_featured.json"
707 |> String.replace("{{domain}}", "example.com")
708 |> String.replace("{{nickname}}", "lain"),
709 headers: [{"content-type", "application/activity+json"}]
714 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
716 "object" => object_url,
717 "target" => "https://example.com/users/lain/collections/featured",
719 "to" => [Pleroma.Constants.as_public()]
724 |> assign(:valid_signature, true)
725 |> put_req_header("content-type", "application/activity+json")
726 |> post("/inbox", data)
727 |> json_response(200)
729 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
730 assert Activity.get_by_ap_id(data["id"])
731 user = User.get_cached_by_ap_id(data["actor"])
732 assert user.pinned_objects[data["object"]]
735 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
737 "object" => object_url,
738 "target" => "https://example.com/users/lain/collections/featured",
740 "to" => [Pleroma.Constants.as_public()]
745 |> assign(:valid_signature, true)
746 |> put_req_header("content-type", "application/activity+json")
747 |> post("/inbox", data)
748 |> json_response(200)
750 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
751 user = refresh_record(user)
752 refute user.pinned_objects[data["object"]]
755 test "mastodon pin/unpin", %{conn: conn} do
756 status_id = "105786274556060421"
759 File.read!("test/fixtures/statuses/masto-note.json")
760 |> String.replace("{{nickname}}", "lain")
761 |> String.replace("{{status_id}}", status_id)
763 status_url = "https://example.com/users/lain/statuses/#{status_id}"
766 File.read!("test/fixtures/users_mock/user.json")
767 |> String.replace("{{nickname}}", "lain")
769 actor = "https://example.com/users/lain"
779 headers: [{"content-type", "application/activity+json"}]
789 headers: [{"content-type", "application/activity+json"}]
792 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
796 "test/fixtures/users_mock/masto_featured.json"
798 |> String.replace("{{domain}}", "example.com")
799 |> String.replace("{{nickname}}", "lain"),
800 headers: [{"content-type", "application/activity+json"}]
805 "@context" => "https://www.w3.org/ns/activitystreams",
807 "object" => status_url,
808 "target" => "https://example.com/users/lain/collections/featured",
814 |> assign(:valid_signature, true)
815 |> put_req_header("content-type", "application/activity+json")
816 |> post("/inbox", data)
817 |> json_response(200)
819 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
820 assert Activity.get_by_object_ap_id_with_object(data["object"])
821 user = User.get_cached_by_ap_id(data["actor"])
822 assert user.pinned_objects[data["object"]]
826 "object" => status_url,
827 "target" => "https://example.com/users/lain/collections/featured",
833 |> assign(:valid_signature, true)
834 |> put_req_header("content-type", "application/activity+json")
835 |> post("/inbox", data)
836 |> json_response(200)
838 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
839 assert Activity.get_by_object_ap_id_with_object(data["object"])
840 user = refresh_record(user)
841 refute user.pinned_objects[data["object"]]
845 describe "/users/:nickname/inbox" do
848 File.read!("test/fixtures/mastodon-post-activity.json")
854 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
859 |> Map.put("bcc", [user.ap_id])
860 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
864 |> assign(:valid_signature, true)
865 |> put_req_header("content-type", "application/activity+json")
866 |> post("/users/#{user.nickname}/inbox", data)
868 assert "ok" == json_response(conn, 200)
869 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
870 assert Activity.get_by_ap_id(data["id"])
873 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
878 |> Map.put("to", user.ap_id)
880 |> Kernel.put_in(["object", "to"], user.ap_id)
881 |> Kernel.put_in(["object", "cc"], [])
885 |> assign(:valid_signature, true)
886 |> put_req_header("content-type", "application/activity+json")
887 |> post("/users/#{user.nickname}/inbox", data)
889 assert "ok" == json_response(conn, 200)
890 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
891 assert Activity.get_by_ap_id(data["id"])
894 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
900 |> Map.put("cc", user.ap_id)
901 |> Kernel.put_in(["object", "to"], [])
902 |> Kernel.put_in(["object", "cc"], user.ap_id)
906 |> assign(:valid_signature, true)
907 |> put_req_header("content-type", "application/activity+json")
908 |> post("/users/#{user.nickname}/inbox", data)
910 assert "ok" == json_response(conn, 200)
911 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
912 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
913 assert user.ap_id in activity.recipients
916 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
923 |> Map.put("bcc", user.ap_id)
924 |> Kernel.put_in(["object", "to"], [])
925 |> Kernel.put_in(["object", "cc"], [])
926 |> Kernel.put_in(["object", "bcc"], user.ap_id)
930 |> assign(:valid_signature, true)
931 |> put_req_header("content-type", "application/activity+json")
932 |> post("/users/#{user.nickname}/inbox", data)
934 assert "ok" == json_response(conn, 200)
935 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
936 assert Activity.get_by_ap_id(data["id"])
939 test "it accepts announces with to as string instead of array", %{conn: conn} do
942 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
943 announcer = insert(:user, local: false)
946 "@context" => "https://www.w3.org/ns/activitystreams",
947 "actor" => announcer.ap_id,
948 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
949 "object" => post.data["object"],
950 "to" => "https://www.w3.org/ns/activitystreams#Public",
951 "cc" => [user.ap_id],
957 |> assign(:valid_signature, true)
958 |> put_req_header("content-type", "application/activity+json")
959 |> post("/users/#{user.nickname}/inbox", data)
961 assert "ok" == json_response(conn, 200)
962 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
963 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
964 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
967 test "it accepts messages from actors that are followed by the user", %{
971 recipient = insert(:user)
972 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
974 {:ok, recipient, actor} = User.follow(recipient, actor)
978 |> Map.put("attributedTo", actor.ap_id)
982 |> Map.put("actor", actor.ap_id)
983 |> Map.put("object", object)
987 |> assign(:valid_signature, true)
988 |> put_req_header("content-type", "application/activity+json")
989 |> post("/users/#{recipient.nickname}/inbox", data)
991 assert "ok" == json_response(conn, 200)
992 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
993 assert Activity.get_by_ap_id(data["id"])
996 test "it rejects reads from other users", %{conn: conn} do
998 other_user = insert(:user)
1002 |> assign(:user, other_user)
1003 |> put_req_header("accept", "application/activity+json")
1004 |> get("/users/#{user.nickname}/inbox")
1006 assert json_response(conn, 403)
1009 test "it returns a note activity in a collection", %{conn: conn} do
1010 note_activity = insert(:direct_note_activity)
1011 note_object = Object.normalize(note_activity, fetch: false)
1012 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1016 |> assign(:user, user)
1017 |> put_req_header("accept", "application/activity+json")
1018 |> get("/users/#{user.nickname}/inbox?page=true")
1020 assert response(conn, 200) =~ note_object.data["content"]
1023 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1024 user = insert(:user)
1025 data = Map.put(data, "bcc", [user.ap_id])
1027 sender_host = URI.parse(data["actor"]).host
1028 Instances.set_consistently_unreachable(sender_host)
1029 refute Instances.reachable?(sender_host)
1033 |> assign(:valid_signature, true)
1034 |> put_req_header("content-type", "application/activity+json")
1035 |> post("/users/#{user.nickname}/inbox", data)
1037 assert "ok" == json_response(conn, 200)
1038 assert Instances.reachable?(sender_host)
1041 @tag capture_log: true
1042 test "it removes all follower collections but actor's", %{conn: conn} do
1043 [actor, recipient] = insert_pair(:user)
1047 recipient.follower_address,
1048 "https://www.w3.org/ns/activitystreams#Public"
1051 cc = [recipient.follower_address, actor.follower_address]
1054 "@context" => ["https://www.w3.org/ns/activitystreams"],
1056 "id" => Utils.generate_activity_id(),
1059 "actor" => actor.ap_id,
1064 "content" => "It's a note",
1065 "attributedTo" => actor.ap_id,
1066 "id" => Utils.generate_object_id()
1071 |> assign(:valid_signature, true)
1072 |> put_req_header("content-type", "application/activity+json")
1073 |> post("/users/#{recipient.nickname}/inbox", data)
1074 |> json_response(200)
1076 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1078 assert activity = Activity.get_by_ap_id(data["id"])
1081 assert actor.follower_address in activity.recipients
1082 assert actor.follower_address in activity.data["cc"]
1084 refute recipient.follower_address in activity.recipients
1085 refute recipient.follower_address in activity.data["cc"]
1086 refute recipient.follower_address in activity.data["to"]
1089 test "it requires authentication", %{conn: conn} do
1090 user = insert(:user)
1091 conn = put_req_header(conn, "accept", "application/activity+json")
1093 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1094 assert json_response(ret_conn, 403)
1098 |> assign(:user, user)
1099 |> get("/users/#{user.nickname}/inbox")
1101 assert json_response(ret_conn, 200)
1104 @tag capture_log: true
1105 test "forwarded report", %{conn: conn} do
1106 admin = insert(:user, is_admin: true)
1107 actor = insert(:user, local: false)
1108 remote_domain = URI.parse(actor.ap_id).host
1109 reported_user = insert(:user)
1111 note = insert(:note_activity, user: reported_user)
1115 "https://www.w3.org/ns/activitystreams",
1116 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1118 "@language" => "und"
1121 "actor" => actor.ap_id,
1125 "content" => "test",
1126 "context" => "context",
1127 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1128 "nickname" => reported_user.nickname,
1130 reported_user.ap_id,
1133 "actor_type" => "Person",
1134 "approval_pending" => false,
1136 "confirmation_pending" => false,
1137 "deactivated" => false,
1138 "display_name" => "test user",
1139 "id" => reported_user.id,
1141 "nickname" => reported_user.nickname,
1142 "registration_reason" => nil,
1145 "moderator" => false
1148 "url" => reported_user.ap_id
1151 "id" => note.data["id"],
1152 "published" => note.data["published"],
1156 "published" => note.data["published"],
1163 |> assign(:valid_signature, true)
1164 |> put_req_header("content-type", "application/activity+json")
1165 |> post("/users/#{reported_user.nickname}/inbox", data)
1166 |> json_response(200)
1168 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1170 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1172 ObanHelpers.perform_all()
1174 Swoosh.TestAssertions.assert_email_sent(
1175 to: {admin.name, admin.email},
1176 html_body: ~r/Reported Account:/i
1180 @tag capture_log: true
1181 test "forwarded report from mastodon", %{conn: conn} do
1182 admin = insert(:user, is_admin: true)
1183 actor = insert(:user, local: false)
1184 remote_domain = URI.parse(actor.ap_id).host
1185 remote_actor = "https://#{remote_domain}/actor"
1186 [reported_user, another] = insert_list(2, :user)
1188 note = insert(:note_activity, user: reported_user)
1190 Pleroma.Web.CommonAPI.favorite(another, note.id)
1193 "test/fixtures/mastodon/application_actor.json"
1195 |> String.replace("{{DOMAIN}}", remote_domain)
1197 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1200 body: mock_json_body,
1201 headers: [{"content-type", "application/activity+json"}]
1206 "@context" => "https://www.w3.org/ns/activitystreams",
1207 "actor" => remote_actor,
1208 "content" => "test report",
1209 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1211 reported_user.ap_id,
1218 |> assign(:valid_signature, true)
1219 |> put_req_header("content-type", "application/activity+json")
1220 |> post("/users/#{reported_user.nickname}/inbox", data)
1221 |> json_response(200)
1223 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1225 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1226 reported_user_ap_id = reported_user.ap_id
1228 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1230 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1231 ObanHelpers.perform_all()
1233 Swoosh.TestAssertions.assert_email_sent(
1234 to: {admin.name, admin.email},
1235 html_body: ~r/#{note.data["object"]}/i
1240 describe "GET /users/:nickname/outbox" do
1241 test "it paginates correctly", %{conn: conn} do
1242 user = insert(:user)
1243 conn = assign(conn, :user, user)
1244 outbox_endpoint = user.ap_id <> "/outbox"
1248 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1254 |> put_req_header("accept", "application/activity+json")
1255 |> get(outbox_endpoint <> "?page=true")
1256 |> json_response(200)
1258 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1259 assert length(result["orderedItems"]) == 20
1260 assert length(result_ids) == 20
1261 assert result["next"]
1262 assert String.starts_with?(result["next"], outbox_endpoint)
1266 |> put_req_header("accept", "application/activity+json")
1267 |> get(result["next"])
1268 |> json_response(200)
1270 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1271 assert length(result_next["orderedItems"]) == 6
1272 assert length(result_next_ids) == 6
1273 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1274 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1275 assert String.starts_with?(result["id"], outbox_endpoint)
1279 |> put_req_header("accept", "application/activity+json")
1280 |> get(result_next["id"])
1281 |> json_response(200)
1283 assert result_next == result_next_again
1286 test "it returns 200 even if there're no activities", %{conn: conn} do
1287 user = insert(:user)
1288 outbox_endpoint = user.ap_id <> "/outbox"
1292 |> assign(:user, user)
1293 |> put_req_header("accept", "application/activity+json")
1294 |> get(outbox_endpoint)
1296 result = json_response(conn, 200)
1297 assert outbox_endpoint == result["id"]
1300 test "it returns a note activity in a collection", %{conn: conn} do
1301 note_activity = insert(:note_activity)
1302 note_object = Object.normalize(note_activity, fetch: false)
1303 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1307 |> assign(:user, user)
1308 |> put_req_header("accept", "application/activity+json")
1309 |> get("/users/#{user.nickname}/outbox?page=true")
1311 assert response(conn, 200) =~ note_object.data["content"]
1314 test "it returns an announce activity in a collection", %{conn: conn} do
1315 announce_activity = insert(:announce_activity)
1316 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1320 |> assign(:user, user)
1321 |> put_req_header("accept", "application/activity+json")
1322 |> get("/users/#{user.nickname}/outbox?page=true")
1324 assert response(conn, 200) =~ announce_activity.data["object"]
1327 test "It returns poll Answers when authenticated", %{conn: conn} do
1328 poller = insert(:user)
1329 voter = insert(:user)
1332 CommonAPI.post(poller, %{
1334 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1337 assert question = Object.normalize(activity, fetch: false)
1339 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1343 |> assign(:user, voter)
1344 |> put_req_header("accept", "application/activity+json")
1345 |> get(voter.ap_id <> "/outbox?page=true")
1346 |> json_response(200)
1348 assert [answer_outbox] = outbox_get["orderedItems"]
1349 assert answer_outbox["id"] == activity.data["id"]
1353 describe "POST /users/:nickname/outbox (C2S)" do
1354 setup do: clear_config([:instance, :limit])
1359 "@context" => "https://www.w3.org/ns/activitystreams",
1363 "content" => "AP C2S test",
1364 "to" => "https://www.w3.org/ns/activitystreams#Public",
1371 test "it rejects posts from other users / unauthenticated users", %{
1375 user = insert(:user)
1376 other_user = insert(:user)
1377 conn = put_req_header(conn, "content-type", "application/activity+json")
1380 |> post("/users/#{user.nickname}/outbox", activity)
1381 |> json_response(403)
1384 |> assign(:user, other_user)
1385 |> post("/users/#{user.nickname}/outbox", activity)
1386 |> json_response(403)
1389 test "it inserts an incoming create activity into the database", %{
1393 user = insert(:user)
1397 |> assign(:user, user)
1398 |> put_req_header("content-type", "application/activity+json")
1399 |> post("/users/#{user.nickname}/outbox", activity)
1400 |> json_response(201)
1402 assert Activity.get_by_ap_id(result["id"])
1403 assert result["object"]
1404 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1405 assert object["content"] == activity["object"]["content"]
1408 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1409 user = insert(:user)
1413 |> put_in(["object", "type"], "Benis")
1417 |> assign(:user, user)
1418 |> put_req_header("content-type", "application/activity+json")
1419 |> post("/users/#{user.nickname}/outbox", activity)
1420 |> json_response(400)
1423 test "it inserts an incoming sensitive activity into the database", %{
1427 user = insert(:user)
1428 conn = assign(conn, :user, user)
1429 object = Map.put(activity["object"], "sensitive", true)
1430 activity = Map.put(activity, "object", object)
1434 |> put_req_header("content-type", "application/activity+json")
1435 |> post("/users/#{user.nickname}/outbox", activity)
1436 |> json_response(201)
1438 assert Activity.get_by_ap_id(response["id"])
1439 assert response["object"]
1440 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1441 assert response_object["sensitive"] == true
1442 assert response_object["content"] == activity["object"]["content"]
1446 |> put_req_header("accept", "application/activity+json")
1447 |> get(response["id"])
1448 |> json_response(200)
1450 assert representation["object"]["sensitive"] == true
1453 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1454 user = insert(:user)
1455 activity = Map.put(activity, "type", "BadType")
1459 |> assign(:user, user)
1460 |> put_req_header("content-type", "application/activity+json")
1461 |> post("/users/#{user.nickname}/outbox", activity)
1463 assert json_response(conn, 400)
1466 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1467 note_activity = insert(:note_activity)
1468 note_object = Object.normalize(note_activity, fetch: false)
1469 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1474 "id" => note_object.data["id"]
1480 |> assign(:user, user)
1481 |> put_req_header("content-type", "application/activity+json")
1482 |> post("/users/#{user.nickname}/outbox", data)
1483 |> json_response(201)
1485 assert Activity.get_by_ap_id(result["id"])
1487 assert object = Object.get_by_ap_id(note_object.data["id"])
1488 assert object.data["type"] == "Tombstone"
1491 test "it rejects delete activity of object from other actor", %{conn: conn} do
1492 note_activity = insert(:note_activity)
1493 note_object = Object.normalize(note_activity, fetch: false)
1494 user = insert(:user)
1499 id: note_object.data["id"]
1505 |> assign(:user, user)
1506 |> put_req_header("content-type", "application/activity+json")
1507 |> post("/users/#{user.nickname}/outbox", data)
1509 assert json_response(conn, 403)
1512 test "it increases like count when receiving a like action", %{conn: conn} do
1513 note_activity = insert(:note_activity)
1514 note_object = Object.normalize(note_activity, fetch: false)
1515 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1520 id: note_object.data["id"]
1526 |> assign(:user, user)
1527 |> put_req_header("content-type", "application/activity+json")
1528 |> post("/users/#{user.nickname}/outbox", data)
1530 result = json_response(conn, 201)
1531 assert Activity.get_by_ap_id(result["id"])
1533 assert object = Object.get_by_ap_id(note_object.data["id"])
1534 assert object.data["like_count"] == 1
1537 test "it doesn't spreads faulty attributedTo or actor fields", %{
1541 reimu = insert(:user, nickname: "reimu")
1542 cirno = insert(:user, nickname: "cirno")
1549 |> put_in(["object", "actor"], reimu.ap_id)
1550 |> put_in(["object", "attributedTo"], reimu.ap_id)
1551 |> put_in(["actor"], reimu.ap_id)
1552 |> put_in(["attributedTo"], reimu.ap_id)
1556 |> assign(:user, cirno)
1557 |> put_req_header("content-type", "application/activity+json")
1558 |> post("/users/#{reimu.nickname}/outbox", activity)
1559 |> json_response(403)
1563 |> assign(:user, cirno)
1564 |> put_req_header("content-type", "application/activity+json")
1565 |> post("/users/#{cirno.nickname}/outbox", activity)
1566 |> json_response(201)
1568 assert cirno_outbox["attributedTo"] == nil
1569 assert cirno_outbox["actor"] == cirno.ap_id
1571 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1572 assert cirno_object.data["actor"] == cirno.ap_id
1573 assert cirno_object.data["attributedTo"] == cirno.ap_id
1576 test "Character limitation", %{conn: conn, activity: activity} do
1577 clear_config([:instance, :limit], 5)
1578 user = insert(:user)
1582 |> assign(:user, user)
1583 |> put_req_header("content-type", "application/activity+json")
1584 |> post("/users/#{user.nickname}/outbox", activity)
1585 |> json_response(400)
1587 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1591 describe "/relay/followers" do
1592 test "it returns relay followers", %{conn: conn} do
1593 relay_actor = Relay.get_actor()
1594 user = insert(:user)
1595 User.follow(user, relay_actor)
1599 |> get("/relay/followers")
1600 |> json_response(200)
1602 assert result["first"]["orderedItems"] == [user.ap_id]
1605 test "on non-federating instance, it returns 404", %{conn: conn} do
1606 clear_config([:instance, :federating], false)
1607 user = insert(:user)
1610 |> assign(:user, user)
1611 |> get("/relay/followers")
1612 |> json_response(404)
1616 describe "/relay/following" do
1617 test "it returns relay following", %{conn: conn} do
1620 |> get("/relay/following")
1621 |> json_response(200)
1623 assert result["first"]["orderedItems"] == []
1626 test "on non-federating instance, it returns 404", %{conn: conn} do
1627 clear_config([:instance, :federating], false)
1628 user = insert(:user)
1631 |> assign(:user, user)
1632 |> get("/relay/following")
1633 |> json_response(404)
1637 describe "/users/:nickname/followers" do
1638 test "it returns the followers in a collection", %{conn: conn} do
1639 user = insert(:user)
1640 user_two = insert(:user)
1641 User.follow(user, user_two)
1645 |> assign(:user, user_two)
1646 |> get("/users/#{user_two.nickname}/followers")
1647 |> json_response(200)
1649 assert result["first"]["orderedItems"] == [user.ap_id]
1652 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1653 user = insert(:user)
1654 user_two = insert(:user, hide_followers: true)
1655 User.follow(user, user_two)
1659 |> assign(:user, user)
1660 |> get("/users/#{user_two.nickname}/followers")
1661 |> json_response(200)
1663 assert is_binary(result["first"])
1666 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1668 user = insert(:user)
1669 other_user = insert(:user, hide_followers: true)
1673 |> assign(:user, user)
1674 |> get("/users/#{other_user.nickname}/followers?page=1")
1676 assert result.status == 403
1677 assert result.resp_body == ""
1680 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1682 user = insert(:user, hide_followers: true)
1683 other_user = insert(:user)
1684 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1688 |> assign(:user, user)
1689 |> get("/users/#{user.nickname}/followers?page=1")
1690 |> json_response(200)
1692 assert result["totalItems"] == 1
1693 assert result["orderedItems"] == [other_user.ap_id]
1696 test "it works for more than 10 users", %{conn: conn} do
1697 user = insert(:user)
1699 Enum.each(1..15, fn _ ->
1700 other_user = insert(:user)
1701 User.follow(other_user, user)
1706 |> assign(:user, user)
1707 |> get("/users/#{user.nickname}/followers")
1708 |> json_response(200)
1710 assert length(result["first"]["orderedItems"]) == 10
1711 assert result["first"]["totalItems"] == 15
1712 assert result["totalItems"] == 15
1716 |> assign(:user, user)
1717 |> get("/users/#{user.nickname}/followers?page=2")
1718 |> json_response(200)
1720 assert length(result["orderedItems"]) == 5
1721 assert result["totalItems"] == 15
1724 test "does not require authentication", %{conn: conn} do
1725 user = insert(:user)
1728 |> get("/users/#{user.nickname}/followers")
1729 |> json_response(200)
1733 describe "/users/:nickname/following" do
1734 test "it returns the following in a collection", %{conn: conn} do
1735 user = insert(:user)
1736 user_two = insert(:user)
1737 User.follow(user, user_two)
1741 |> assign(:user, user)
1742 |> get("/users/#{user.nickname}/following")
1743 |> json_response(200)
1745 assert result["first"]["orderedItems"] == [user_two.ap_id]
1748 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1749 user = insert(:user)
1750 user_two = insert(:user, hide_follows: true)
1751 User.follow(user, user_two)
1755 |> assign(:user, user)
1756 |> get("/users/#{user_two.nickname}/following")
1757 |> json_response(200)
1759 assert is_binary(result["first"])
1762 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1764 user = insert(:user)
1765 user_two = insert(:user, hide_follows: true)
1769 |> assign(:user, user)
1770 |> get("/users/#{user_two.nickname}/following?page=1")
1772 assert result.status == 403
1773 assert result.resp_body == ""
1776 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1778 user = insert(:user, hide_follows: true)
1779 other_user = insert(:user)
1780 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1784 |> assign(:user, user)
1785 |> get("/users/#{user.nickname}/following?page=1")
1786 |> json_response(200)
1788 assert result["totalItems"] == 1
1789 assert result["orderedItems"] == [other_user.ap_id]
1792 test "it works for more than 10 users", %{conn: conn} do
1793 user = insert(:user)
1795 Enum.each(1..15, fn _ ->
1796 user = User.get_cached_by_id(user.id)
1797 other_user = insert(:user)
1798 User.follow(user, other_user)
1803 |> assign(:user, user)
1804 |> get("/users/#{user.nickname}/following")
1805 |> json_response(200)
1807 assert length(result["first"]["orderedItems"]) == 10
1808 assert result["first"]["totalItems"] == 15
1809 assert result["totalItems"] == 15
1813 |> assign(:user, user)
1814 |> get("/users/#{user.nickname}/following?page=2")
1815 |> json_response(200)
1817 assert length(result["orderedItems"]) == 5
1818 assert result["totalItems"] == 15
1821 test "does not require authentication", %{conn: conn} do
1822 user = insert(:user)
1825 |> get("/users/#{user.nickname}/following")
1826 |> json_response(200)
1830 describe "delivery tracking" do
1831 test "it tracks a signed object fetch", %{conn: conn} do
1832 user = insert(:user, local: false)
1833 activity = insert(:note_activity)
1834 object = Object.normalize(activity, fetch: false)
1836 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1839 |> put_req_header("accept", "application/activity+json")
1840 |> assign(:user, user)
1842 |> json_response(200)
1844 assert Delivery.get(object.id, user.id)
1847 test "it tracks a signed activity fetch", %{conn: conn} do
1848 user = insert(:user, local: false)
1849 activity = insert(:note_activity)
1850 object = Object.normalize(activity, fetch: false)
1852 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1855 |> put_req_header("accept", "application/activity+json")
1856 |> assign(:user, user)
1857 |> get(activity_path)
1858 |> json_response(200)
1860 assert Delivery.get(object.id, user.id)
1863 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1864 user = insert(:user, local: false)
1865 other_user = insert(:user, local: false)
1866 activity = insert(:note_activity)
1867 object = Object.normalize(activity, fetch: false)
1869 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1872 |> put_req_header("accept", "application/activity+json")
1873 |> assign(:user, user)
1875 |> json_response(200)
1878 |> put_req_header("accept", "application/activity+json")
1879 |> assign(:user, other_user)
1881 |> json_response(200)
1883 assert Delivery.get(object.id, user.id)
1884 assert Delivery.get(object.id, other_user.id)
1887 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1888 user = insert(:user, local: false)
1889 other_user = insert(:user, local: false)
1890 activity = insert(:note_activity)
1891 object = Object.normalize(activity, fetch: false)
1893 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1896 |> put_req_header("accept", "application/activity+json")
1897 |> assign(:user, user)
1898 |> get(activity_path)
1899 |> json_response(200)
1902 |> put_req_header("accept", "application/activity+json")
1903 |> assign(:user, other_user)
1904 |> get(activity_path)
1905 |> json_response(200)
1907 assert Delivery.get(object.id, user.id)
1908 assert Delivery.get(object.id, other_user.id)
1912 describe "Additional ActivityPub C2S endpoints" do
1913 test "GET /api/ap/whoami", %{conn: conn} do
1914 user = insert(:user)
1918 |> assign(:user, user)
1919 |> get("/api/ap/whoami")
1921 user = User.get_cached_by_id(user.id)
1923 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1926 |> get("/api/ap/whoami")
1927 |> json_response(403)
1930 setup do: clear_config([:media_proxy])
1931 setup do: clear_config([Pleroma.Upload])
1933 test "POST /api/ap/upload_media", %{conn: conn} do
1934 user = insert(:user)
1936 desc = "Description of the image"
1938 image = %Plug.Upload{
1939 content_type: "image/jpeg",
1940 path: Path.absname("test/fixtures/image.jpg"),
1941 filename: "an_image.jpg"
1946 |> assign(:user, user)
1947 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1948 |> json_response(:created)
1950 assert object["name"] == desc
1951 assert object["type"] == "Document"
1952 assert object["actor"] == user.ap_id
1953 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1954 assert is_binary(object_href)
1955 assert object_mediatype == "image/jpeg"
1956 assert String.ends_with?(object_href, ".jpg")
1958 activity_request = %{
1959 "@context" => "https://www.w3.org/ns/activitystreams",
1963 "content" => "AP C2S test, attachment",
1964 "attachment" => [object],
1965 "to" => "https://www.w3.org/ns/activitystreams#Public",
1972 |> assign(:user, user)
1973 |> post("/users/#{user.nickname}/outbox", activity_request)
1974 |> json_response(:created)
1976 assert activity_response["id"]
1977 assert activity_response["object"]
1978 assert activity_response["actor"] == user.ap_id
1980 assert %Object{data: %{"attachment" => [attachment]}} =
1981 Object.normalize(activity_response["object"], fetch: false)
1983 assert attachment["type"] == "Document"
1984 assert attachment["name"] == desc
1988 "href" => ^object_href,
1990 "mediaType" => ^object_mediatype
1992 ] = attachment["url"]
1994 # Fails if unauthenticated
1996 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1997 |> json_response(403)
2001 test "pinned collection", %{conn: conn} do
2002 clear_config([:instance, :max_pinned_statuses], 2)
2003 user = insert(:user)
2004 objects = insert_list(2, :note, user: user)
2006 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2007 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2011 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2012 refresh_record(user)
2014 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2016 |> get("/users/#{nickname}/collections/featured")
2017 |> json_response(200)
2019 object_ids = Enum.map(items, & &1["id"])
2021 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2022 obj_id in object_ids