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 "does not return local-only objects for remote users", %{conn: conn} do
252 reader = insert(:user, local: false)
255 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
257 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
259 object = Object.normalize(post, fetch: false)
260 uuid = String.split(object.data["id"], "/") |> List.last()
264 |> assign(:user, reader)
265 |> put_req_header("accept", "application/activity+json")
266 |> get("/objects/#{uuid}")
268 json_response(response, 404)
271 test "it returns a json representation of the object with accept application/json", %{
275 uuid = String.split(note.data["id"], "/") |> List.last()
279 |> put_req_header("accept", "application/json")
280 |> get("/objects/#{uuid}")
282 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
285 test "it returns a json representation of the object with accept application/activity+json",
288 uuid = String.split(note.data["id"], "/") |> List.last()
292 |> put_req_header("accept", "application/activity+json")
293 |> get("/objects/#{uuid}")
295 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
298 test "it returns a json representation of the object with accept application/ld+json", %{
302 uuid = String.split(note.data["id"], "/") |> List.last()
308 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
310 |> get("/objects/#{uuid}")
312 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
315 test "does not cache authenticated response", %{conn: conn} do
317 reader = insert(:user)
320 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
322 object = Object.normalize(post, fetch: false)
323 uuid = String.split(object.data["id"], "/") |> List.last()
327 |> assign(:user, reader)
328 |> put_req_header("accept", "application/activity+json")
329 |> get("/objects/#{uuid}")
331 json_response(response, 200)
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
336 |> json_response(404)
339 test "it returns 404 for non-public messages", %{conn: conn} do
340 note = insert(:direct_note)
341 uuid = String.split(note.data["id"], "/") |> List.last()
345 |> put_req_header("accept", "application/activity+json")
346 |> get("/objects/#{uuid}")
348 assert json_response(conn, 404)
351 test "returns visible non-public messages when authenticated", %{conn: conn} do
352 note = insert(:direct_note)
353 uuid = String.split(note.data["id"], "/") |> List.last()
354 user = User.get_by_ap_id(note.data["actor"])
355 marisa = insert(:user)
358 |> assign(:user, marisa)
359 |> put_req_header("accept", "application/activity+json")
360 |> get("/objects/#{uuid}")
361 |> json_response(404)
365 |> assign(:user, user)
366 |> put_req_header("accept", "application/activity+json")
367 |> get("/objects/#{uuid}")
368 |> json_response(200)
370 assert response == ObjectView.render("object.json", %{object: note})
373 test "it returns 404 for tombstone objects", %{conn: conn} do
374 tombstone = insert(:tombstone)
375 uuid = String.split(tombstone.data["id"], "/") |> List.last()
379 |> put_req_header("accept", "application/activity+json")
380 |> get("/objects/#{uuid}")
382 assert json_response(conn, 404)
385 test "it caches a response", %{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"}))
399 |> put_req_header("accept", "application/activity+json")
400 |> get("/objects/#{uuid}")
402 assert json_response(conn1, :ok) == json_response(conn2, :ok)
403 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
406 test "cached purged after object deletion", %{conn: conn} do
408 uuid = String.split(note.data["id"], "/") |> List.last()
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/objects/#{uuid}")
415 assert json_response(conn1, :ok)
416 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
422 |> put_req_header("accept", "application/activity+json")
423 |> get("/objects/#{uuid}")
425 assert "Not found" == json_response(conn2, :not_found)
429 describe "/activities/:uuid" do
430 test "it doesn't return a local-only activity", %{conn: conn} do
432 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
434 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
436 uuid = String.split(post.data["id"], "/") |> List.last()
440 |> put_req_header("accept", "application/json")
441 |> get("/activities/#{uuid}")
443 assert json_response(conn, 404)
446 test "returns local-only activities when authenticated", %{conn: conn} do
448 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
450 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
452 uuid = String.split(post.data["id"], "/") |> List.last()
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
460 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
463 test "it returns a json representation of the activity", %{conn: conn} do
464 activity = insert(:note_activity)
465 uuid = String.split(activity.data["id"], "/") |> List.last()
469 |> put_req_header("accept", "application/activity+json")
470 |> get("/activities/#{uuid}")
472 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
475 test "it returns 404 for non-public activities", %{conn: conn} do
476 activity = insert(:direct_note_activity)
477 uuid = String.split(activity.data["id"], "/") |> List.last()
481 |> put_req_header("accept", "application/activity+json")
482 |> get("/activities/#{uuid}")
484 assert json_response(conn, 404)
487 test "returns visible non-public messages when authenticated", %{conn: conn} do
488 note = insert(:direct_note_activity)
489 uuid = String.split(note.data["id"], "/") |> List.last()
490 user = User.get_by_ap_id(note.data["actor"])
491 marisa = insert(:user)
494 |> assign(:user, marisa)
495 |> put_req_header("accept", "application/activity+json")
496 |> get("/activities/#{uuid}")
497 |> json_response(404)
501 |> assign(:user, user)
502 |> put_req_header("accept", "application/activity+json")
503 |> get("/activities/#{uuid}")
504 |> json_response(200)
506 assert response == ObjectView.render("object.json", %{object: note})
509 test "it caches a response", %{conn: conn} do
510 activity = insert(:note_activity)
511 uuid = String.split(activity.data["id"], "/") |> List.last()
515 |> put_req_header("accept", "application/activity+json")
516 |> get("/activities/#{uuid}")
518 assert json_response(conn1, :ok)
519 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
523 |> put_req_header("accept", "application/activity+json")
524 |> get("/activities/#{uuid}")
526 assert json_response(conn1, :ok) == json_response(conn2, :ok)
527 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
530 test "cached purged after activity deletion", %{conn: conn} do
532 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
534 uuid = String.split(activity.data["id"], "/") |> List.last()
538 |> put_req_header("accept", "application/activity+json")
539 |> get("/activities/#{uuid}")
541 assert json_response(conn1, :ok)
542 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
544 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
548 |> put_req_header("accept", "application/activity+json")
549 |> get("/activities/#{uuid}")
551 assert "Not found" == json_response(conn2, :not_found)
556 test "it inserts an incoming activity into the database", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
561 |> assign(:valid_signature, true)
562 |> put_req_header("content-type", "application/activity+json")
563 |> post("/inbox", data)
565 assert "ok" == json_response(conn, 200)
567 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
568 assert Activity.get_by_ap_id(data["id"])
571 @tag capture_log: true
572 test "it inserts an incoming activity into the database" <>
573 "even if we can't fetch the user but have it in our db",
577 ap_id: "https://mastodon.example.org/users/raymoo",
580 last_refreshed_at: nil
584 File.read!("test/fixtures/mastodon-post-activity.json")
586 |> Map.put("actor", user.ap_id)
587 |> put_in(["object", "attributedTo"], user.ap_id)
591 |> assign(:valid_signature, true)
592 |> put_req_header("content-type", "application/activity+json")
593 |> post("/inbox", data)
595 assert "ok" == json_response(conn, 200)
597 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
598 assert Activity.get_by_ap_id(data["id"])
601 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
602 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
604 sender_url = data["actor"]
605 Instances.set_consistently_unreachable(sender_url)
606 refute Instances.reachable?(sender_url)
610 |> assign(:valid_signature, true)
611 |> put_req_header("content-type", "application/activity+json")
612 |> post("/inbox", data)
614 assert "ok" == json_response(conn, 200)
615 assert Instances.reachable?(sender_url)
618 test "accept follow activity", %{conn: conn} do
619 clear_config([:instance, :federating], true)
620 relay = Relay.get_actor()
622 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
624 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
625 relay = refresh_record(relay)
628 File.read!("test/fixtures/relay/accept-follow.json")
629 |> String.replace("{{ap_id}}", relay.ap_id)
630 |> String.replace("{{activity_id}}", activity.data["id"])
634 |> assign(:valid_signature, true)
635 |> put_req_header("content-type", "application/activity+json")
636 |> post("/inbox", accept)
637 |> json_response(200)
639 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
641 assert Pleroma.FollowingRelationship.following?(
646 Mix.shell(Mix.Shell.Process)
649 Mix.shell(Mix.Shell.IO)
652 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
653 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
656 @tag capture_log: true
657 test "without valid signature, " <>
658 "it only accepts Create activities and requires enabled federation",
660 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
661 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
663 conn = put_req_header(conn, "content-type", "application/activity+json")
665 clear_config([:instance, :federating], false)
668 |> post("/inbox", data)
669 |> json_response(403)
672 |> post("/inbox", non_create_data)
673 |> json_response(403)
675 clear_config([:instance, :federating], true)
677 ret_conn = post(conn, "/inbox", data)
678 assert "ok" == json_response(ret_conn, 200)
681 |> post("/inbox", non_create_data)
682 |> json_response(400)
685 test "accepts Add/Remove activities", %{conn: conn} do
686 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
689 File.read!("test/fixtures/statuses/note.json")
690 |> String.replace("{{nickname}}", "lain")
691 |> String.replace("{{object_id}}", object_id)
693 object_url = "https://example.com/objects/#{object_id}"
696 File.read!("test/fixtures/users_mock/user.json")
697 |> String.replace("{{nickname}}", "lain")
699 actor = "https://example.com/users/lain"
709 headers: [{"content-type", "application/activity+json"}]
719 headers: [{"content-type", "application/activity+json"}]
722 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
726 "test/fixtures/users_mock/masto_featured.json"
728 |> String.replace("{{domain}}", "example.com")
729 |> String.replace("{{nickname}}", "lain"),
730 headers: [{"content-type", "application/activity+json"}]
735 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
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 assert Activity.get_by_ap_id(data["id"])
752 user = User.get_cached_by_ap_id(data["actor"])
753 assert user.pinned_objects[data["object"]]
756 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
758 "object" => object_url,
759 "target" => "https://example.com/users/lain/collections/featured",
761 "to" => [Pleroma.Constants.as_public()]
766 |> assign(:valid_signature, true)
767 |> put_req_header("content-type", "application/activity+json")
768 |> post("/inbox", data)
769 |> json_response(200)
771 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
772 user = refresh_record(user)
773 refute user.pinned_objects[data["object"]]
776 test "mastodon pin/unpin", %{conn: conn} do
777 status_id = "105786274556060421"
780 File.read!("test/fixtures/statuses/masto-note.json")
781 |> String.replace("{{nickname}}", "lain")
782 |> String.replace("{{status_id}}", status_id)
784 status_url = "https://example.com/users/lain/statuses/#{status_id}"
787 File.read!("test/fixtures/users_mock/user.json")
788 |> String.replace("{{nickname}}", "lain")
790 actor = "https://example.com/users/lain"
800 headers: [{"content-type", "application/activity+json"}]
810 headers: [{"content-type", "application/activity+json"}]
813 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
817 "test/fixtures/users_mock/masto_featured.json"
819 |> String.replace("{{domain}}", "example.com")
820 |> String.replace("{{nickname}}", "lain"),
821 headers: [{"content-type", "application/activity+json"}]
826 "@context" => "https://www.w3.org/ns/activitystreams",
828 "object" => status_url,
829 "target" => "https://example.com/users/lain/collections/featured",
835 |> assign(:valid_signature, true)
836 |> put_req_header("content-type", "application/activity+json")
837 |> post("/inbox", data)
838 |> json_response(200)
840 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
841 assert Activity.get_by_object_ap_id_with_object(data["object"])
842 user = User.get_cached_by_ap_id(data["actor"])
843 assert user.pinned_objects[data["object"]]
847 "object" => status_url,
848 "target" => "https://example.com/users/lain/collections/featured",
854 |> assign(:valid_signature, true)
855 |> put_req_header("content-type", "application/activity+json")
856 |> post("/inbox", data)
857 |> json_response(200)
859 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
860 assert Activity.get_by_object_ap_id_with_object(data["object"])
861 user = refresh_record(user)
862 refute user.pinned_objects[data["object"]]
866 describe "/users/:nickname/inbox" do
869 File.read!("test/fixtures/mastodon-post-activity.json")
875 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
880 |> Map.put("bcc", [user.ap_id])
881 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
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 to as string instead of array", %{conn: conn, data: data} do
899 |> Map.put("to", user.ap_id)
901 |> Kernel.put_in(["object", "to"], user.ap_id)
902 |> Kernel.put_in(["object", "cc"], [])
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 assert Activity.get_by_ap_id(data["id"])
915 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
921 |> Map.put("cc", user.ap_id)
922 |> Kernel.put_in(["object", "to"], [])
923 |> Kernel.put_in(["object", "cc"], user.ap_id)
927 |> assign(:valid_signature, true)
928 |> put_req_header("content-type", "application/activity+json")
929 |> post("/users/#{user.nickname}/inbox", data)
931 assert "ok" == json_response(conn, 200)
932 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
933 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
934 assert user.ap_id in activity.recipients
937 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
944 |> Map.put("bcc", user.ap_id)
945 |> Kernel.put_in(["object", "to"], [])
946 |> Kernel.put_in(["object", "cc"], [])
947 |> Kernel.put_in(["object", "bcc"], user.ap_id)
951 |> assign(:valid_signature, true)
952 |> put_req_header("content-type", "application/activity+json")
953 |> post("/users/#{user.nickname}/inbox", data)
955 assert "ok" == json_response(conn, 200)
956 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
957 assert Activity.get_by_ap_id(data["id"])
960 test "it accepts announces with to as string instead of array", %{conn: conn} do
963 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
964 announcer = insert(:user, local: false)
967 "@context" => "https://www.w3.org/ns/activitystreams",
968 "actor" => announcer.ap_id,
969 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
970 "object" => post.data["object"],
971 "to" => "https://www.w3.org/ns/activitystreams#Public",
972 "cc" => [user.ap_id],
978 |> assign(:valid_signature, true)
979 |> put_req_header("content-type", "application/activity+json")
980 |> post("/users/#{user.nickname}/inbox", data)
982 assert "ok" == json_response(conn, 200)
983 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
984 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
985 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
988 test "it accepts messages from actors that are followed by the user", %{
992 recipient = insert(:user)
993 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
995 {:ok, recipient, actor} = User.follow(recipient, actor)
999 |> Map.put("attributedTo", actor.ap_id)
1003 |> Map.put("actor", actor.ap_id)
1004 |> Map.put("object", object)
1008 |> assign(:valid_signature, true)
1009 |> put_req_header("content-type", "application/activity+json")
1010 |> post("/users/#{recipient.nickname}/inbox", data)
1012 assert "ok" == json_response(conn, 200)
1013 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1014 assert Activity.get_by_ap_id(data["id"])
1017 test "it rejects reads from other users", %{conn: conn} do
1018 user = insert(:user)
1019 other_user = insert(:user)
1023 |> assign(:user, other_user)
1024 |> put_req_header("accept", "application/activity+json")
1025 |> get("/users/#{user.nickname}/inbox")
1027 assert json_response(conn, 403)
1030 test "it returns a note activity in a collection", %{conn: conn} do
1031 note_activity = insert(:direct_note_activity)
1032 note_object = Object.normalize(note_activity, fetch: false)
1033 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1037 |> assign(:user, user)
1038 |> put_req_header("accept", "application/activity+json")
1039 |> get("/users/#{user.nickname}/inbox?page=true")
1041 assert response(conn, 200) =~ note_object.data["content"]
1044 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1045 user = insert(:user)
1046 data = Map.put(data, "bcc", [user.ap_id])
1048 sender_host = URI.parse(data["actor"]).host
1049 Instances.set_consistently_unreachable(sender_host)
1050 refute Instances.reachable?(sender_host)
1054 |> assign(:valid_signature, true)
1055 |> put_req_header("content-type", "application/activity+json")
1056 |> post("/users/#{user.nickname}/inbox", data)
1058 assert "ok" == json_response(conn, 200)
1059 assert Instances.reachable?(sender_host)
1062 @tag capture_log: true
1063 test "it removes all follower collections but actor's", %{conn: conn} do
1064 [actor, recipient] = insert_pair(:user)
1068 recipient.follower_address,
1069 "https://www.w3.org/ns/activitystreams#Public"
1072 cc = [recipient.follower_address, actor.follower_address]
1075 "@context" => ["https://www.w3.org/ns/activitystreams"],
1077 "id" => Utils.generate_activity_id(),
1080 "actor" => actor.ap_id,
1085 "content" => "It's a note",
1086 "attributedTo" => actor.ap_id,
1087 "id" => Utils.generate_object_id()
1092 |> assign(:valid_signature, true)
1093 |> put_req_header("content-type", "application/activity+json")
1094 |> post("/users/#{recipient.nickname}/inbox", data)
1095 |> json_response(200)
1097 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1099 assert activity = Activity.get_by_ap_id(data["id"])
1102 assert actor.follower_address in activity.recipients
1103 assert actor.follower_address in activity.data["cc"]
1105 refute recipient.follower_address in activity.recipients
1106 refute recipient.follower_address in activity.data["cc"]
1107 refute recipient.follower_address in activity.data["to"]
1110 test "it requires authentication", %{conn: conn} do
1111 user = insert(:user)
1112 conn = put_req_header(conn, "accept", "application/activity+json")
1114 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1115 assert json_response(ret_conn, 403)
1119 |> assign(:user, user)
1120 |> get("/users/#{user.nickname}/inbox")
1122 assert json_response(ret_conn, 200)
1125 @tag capture_log: true
1126 test "forwarded report", %{conn: conn} do
1127 admin = insert(:user, is_admin: true)
1128 actor = insert(:user, local: false)
1129 remote_domain = URI.parse(actor.ap_id).host
1130 reported_user = insert(:user)
1132 note = insert(:note_activity, user: reported_user)
1136 "https://www.w3.org/ns/activitystreams",
1137 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1139 "@language" => "und"
1142 "actor" => actor.ap_id,
1146 "content" => "test",
1147 "context" => "context",
1148 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1149 "nickname" => reported_user.nickname,
1151 reported_user.ap_id,
1154 "actor_type" => "Person",
1155 "approval_pending" => false,
1157 "confirmation_pending" => false,
1158 "deactivated" => false,
1159 "display_name" => "test user",
1160 "id" => reported_user.id,
1162 "nickname" => reported_user.nickname,
1163 "registration_reason" => nil,
1166 "moderator" => false
1169 "url" => reported_user.ap_id
1172 "id" => note.data["id"],
1173 "published" => note.data["published"],
1177 "published" => note.data["published"],
1184 |> assign(:valid_signature, true)
1185 |> put_req_header("content-type", "application/activity+json")
1186 |> post("/users/#{reported_user.nickname}/inbox", data)
1187 |> json_response(200)
1189 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1191 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1193 ObanHelpers.perform_all()
1195 Swoosh.TestAssertions.assert_email_sent(
1196 to: {admin.name, admin.email},
1197 html_body: ~r/Reported Account:/i
1201 @tag capture_log: true
1202 test "forwarded report from mastodon", %{conn: conn} do
1203 admin = insert(:user, is_admin: true)
1204 actor = insert(:user, local: false)
1205 remote_domain = URI.parse(actor.ap_id).host
1206 remote_actor = "https://#{remote_domain}/actor"
1207 [reported_user, another] = insert_list(2, :user)
1209 note = insert(:note_activity, user: reported_user)
1211 Pleroma.Web.CommonAPI.favorite(another, note.id)
1214 "test/fixtures/mastodon/application_actor.json"
1216 |> String.replace("{{DOMAIN}}", remote_domain)
1218 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1221 body: mock_json_body,
1222 headers: [{"content-type", "application/activity+json"}]
1227 "@context" => "https://www.w3.org/ns/activitystreams",
1228 "actor" => remote_actor,
1229 "content" => "test report",
1230 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1232 reported_user.ap_id,
1239 |> assign(:valid_signature, true)
1240 |> put_req_header("content-type", "application/activity+json")
1241 |> post("/users/#{reported_user.nickname}/inbox", data)
1242 |> json_response(200)
1244 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1246 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1247 reported_user_ap_id = reported_user.ap_id
1249 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1251 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1252 ObanHelpers.perform_all()
1254 Swoosh.TestAssertions.assert_email_sent(
1255 to: {admin.name, admin.email},
1256 html_body: ~r/#{note.data["object"]}/i
1261 describe "GET /users/:nickname/outbox" do
1262 test "it paginates correctly", %{conn: conn} do
1263 user = insert(:user)
1264 conn = assign(conn, :user, user)
1265 outbox_endpoint = user.ap_id <> "/outbox"
1269 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1275 |> put_req_header("accept", "application/activity+json")
1276 |> get(outbox_endpoint <> "?page=true")
1277 |> json_response(200)
1279 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1280 assert length(result["orderedItems"]) == 20
1281 assert length(result_ids) == 20
1282 assert result["next"]
1283 assert String.starts_with?(result["next"], outbox_endpoint)
1287 |> put_req_header("accept", "application/activity+json")
1288 |> get(result["next"])
1289 |> json_response(200)
1291 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1292 assert length(result_next["orderedItems"]) == 6
1293 assert length(result_next_ids) == 6
1294 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1295 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1296 assert String.starts_with?(result["id"], outbox_endpoint)
1300 |> put_req_header("accept", "application/activity+json")
1301 |> get(result_next["id"])
1302 |> json_response(200)
1304 assert result_next == result_next_again
1307 test "it returns 200 even if there're no activities", %{conn: conn} do
1308 user = insert(:user)
1309 outbox_endpoint = user.ap_id <> "/outbox"
1313 |> assign(:user, user)
1314 |> put_req_header("accept", "application/activity+json")
1315 |> get(outbox_endpoint)
1317 result = json_response(conn, 200)
1318 assert outbox_endpoint == result["id"]
1321 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1322 user = insert(:user)
1323 reader = insert(:user)
1324 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1325 ap_id = note_activity.data["id"]
1329 |> assign(:user, reader)
1330 |> put_req_header("accept", "application/activity+json")
1331 |> get("/users/#{user.nickname}/outbox?page=true")
1332 |> json_response(200)
1334 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1337 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1338 user = insert(:user)
1339 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1343 |> put_req_header("accept", "application/activity+json")
1344 |> get("/users/#{user.nickname}/outbox?page=true")
1345 |> json_response(200)
1347 assert %{"orderedItems" => []} = resp
1350 test "it returns a note activity in a collection", %{conn: conn} do
1351 note_activity = insert(:note_activity)
1352 note_object = Object.normalize(note_activity, fetch: false)
1353 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1357 |> assign(:user, user)
1358 |> put_req_header("accept", "application/activity+json")
1359 |> get("/users/#{user.nickname}/outbox?page=true")
1361 assert response(conn, 200) =~ note_object.data["content"]
1364 test "it returns an announce activity in a collection", %{conn: conn} do
1365 announce_activity = insert(:announce_activity)
1366 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1370 |> assign(:user, user)
1371 |> put_req_header("accept", "application/activity+json")
1372 |> get("/users/#{user.nickname}/outbox?page=true")
1374 assert response(conn, 200) =~ announce_activity.data["object"]
1377 test "It returns poll Answers when authenticated", %{conn: conn} do
1378 poller = insert(:user)
1379 voter = insert(:user)
1382 CommonAPI.post(poller, %{
1384 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1387 assert question = Object.normalize(activity, fetch: false)
1389 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1393 |> assign(:user, voter)
1394 |> put_req_header("accept", "application/activity+json")
1395 |> get(voter.ap_id <> "/outbox?page=true")
1396 |> json_response(200)
1398 assert [answer_outbox] = outbox_get["orderedItems"]
1399 assert answer_outbox["id"] == activity.data["id"]
1403 describe "POST /users/:nickname/outbox (C2S)" do
1404 setup do: clear_config([:instance, :limit])
1409 "@context" => "https://www.w3.org/ns/activitystreams",
1413 "content" => "AP C2S test",
1414 "to" => "https://www.w3.org/ns/activitystreams#Public",
1421 test "it rejects posts from other users / unauthenticated users", %{
1425 user = insert(:user)
1426 other_user = insert(:user)
1427 conn = put_req_header(conn, "content-type", "application/activity+json")
1430 |> post("/users/#{user.nickname}/outbox", activity)
1431 |> json_response(403)
1434 |> assign(:user, other_user)
1435 |> post("/users/#{user.nickname}/outbox", activity)
1436 |> json_response(403)
1439 test "it inserts an incoming create activity into the database", %{
1443 user = insert(:user)
1447 |> assign(:user, user)
1448 |> put_req_header("content-type", "application/activity+json")
1449 |> post("/users/#{user.nickname}/outbox", activity)
1450 |> json_response(201)
1452 assert Activity.get_by_ap_id(result["id"])
1453 assert result["object"]
1454 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1455 assert object["content"] == activity["object"]["content"]
1458 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1459 user = insert(:user)
1463 |> put_in(["object", "type"], "Benis")
1467 |> assign(:user, user)
1468 |> put_req_header("content-type", "application/activity+json")
1469 |> post("/users/#{user.nickname}/outbox", activity)
1470 |> json_response(400)
1473 test "it inserts an incoming sensitive activity into the database", %{
1477 user = insert(:user)
1478 conn = assign(conn, :user, user)
1479 object = Map.put(activity["object"], "sensitive", true)
1480 activity = Map.put(activity, "object", object)
1484 |> put_req_header("content-type", "application/activity+json")
1485 |> post("/users/#{user.nickname}/outbox", activity)
1486 |> json_response(201)
1488 assert Activity.get_by_ap_id(response["id"])
1489 assert response["object"]
1490 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1491 assert response_object["sensitive"] == true
1492 assert response_object["content"] == activity["object"]["content"]
1496 |> put_req_header("accept", "application/activity+json")
1497 |> get(response["id"])
1498 |> json_response(200)
1500 assert representation["object"]["sensitive"] == true
1503 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1504 user = insert(:user)
1505 activity = Map.put(activity, "type", "BadType")
1509 |> assign(:user, user)
1510 |> put_req_header("content-type", "application/activity+json")
1511 |> post("/users/#{user.nickname}/outbox", activity)
1513 assert json_response(conn, 400)
1516 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1517 note_activity = insert(:note_activity)
1518 note_object = Object.normalize(note_activity, fetch: false)
1519 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1524 "id" => note_object.data["id"]
1530 |> assign(:user, user)
1531 |> put_req_header("content-type", "application/activity+json")
1532 |> post("/users/#{user.nickname}/outbox", data)
1533 |> json_response(201)
1535 assert Activity.get_by_ap_id(result["id"])
1537 assert object = Object.get_by_ap_id(note_object.data["id"])
1538 assert object.data["type"] == "Tombstone"
1541 test "it rejects delete activity of object from other actor", %{conn: conn} do
1542 note_activity = insert(:note_activity)
1543 note_object = Object.normalize(note_activity, fetch: false)
1544 user = insert(:user)
1549 id: note_object.data["id"]
1555 |> assign(:user, user)
1556 |> put_req_header("content-type", "application/activity+json")
1557 |> post("/users/#{user.nickname}/outbox", data)
1559 assert json_response(conn, 403)
1562 test "it increases like count when receiving a like action", %{conn: conn} do
1563 note_activity = insert(:note_activity)
1564 note_object = Object.normalize(note_activity, fetch: false)
1565 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1570 id: note_object.data["id"]
1576 |> assign(:user, user)
1577 |> put_req_header("content-type", "application/activity+json")
1578 |> post("/users/#{user.nickname}/outbox", data)
1580 result = json_response(conn, 201)
1581 assert Activity.get_by_ap_id(result["id"])
1583 assert object = Object.get_by_ap_id(note_object.data["id"])
1584 assert object.data["like_count"] == 1
1587 test "it doesn't spreads faulty attributedTo or actor fields", %{
1591 reimu = insert(:user, nickname: "reimu")
1592 cirno = insert(:user, nickname: "cirno")
1599 |> put_in(["object", "actor"], reimu.ap_id)
1600 |> put_in(["object", "attributedTo"], reimu.ap_id)
1601 |> put_in(["actor"], reimu.ap_id)
1602 |> put_in(["attributedTo"], reimu.ap_id)
1606 |> assign(:user, cirno)
1607 |> put_req_header("content-type", "application/activity+json")
1608 |> post("/users/#{reimu.nickname}/outbox", activity)
1609 |> json_response(403)
1613 |> assign(:user, cirno)
1614 |> put_req_header("content-type", "application/activity+json")
1615 |> post("/users/#{cirno.nickname}/outbox", activity)
1616 |> json_response(201)
1618 assert cirno_outbox["attributedTo"] == nil
1619 assert cirno_outbox["actor"] == cirno.ap_id
1621 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1622 assert cirno_object.data["actor"] == cirno.ap_id
1623 assert cirno_object.data["attributedTo"] == cirno.ap_id
1626 test "Character limitation", %{conn: conn, activity: activity} do
1627 clear_config([:instance, :limit], 5)
1628 user = insert(:user)
1632 |> assign(:user, user)
1633 |> put_req_header("content-type", "application/activity+json")
1634 |> post("/users/#{user.nickname}/outbox", activity)
1635 |> json_response(400)
1637 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1641 describe "/relay/followers" do
1642 test "it returns relay followers", %{conn: conn} do
1643 relay_actor = Relay.get_actor()
1644 user = insert(:user)
1645 User.follow(user, relay_actor)
1649 |> get("/relay/followers")
1650 |> json_response(200)
1652 assert result["first"]["orderedItems"] == [user.ap_id]
1655 test "on non-federating instance, it returns 404", %{conn: conn} do
1656 clear_config([:instance, :federating], false)
1657 user = insert(:user)
1660 |> assign(:user, user)
1661 |> get("/relay/followers")
1662 |> json_response(404)
1666 describe "/relay/following" do
1667 test "it returns relay following", %{conn: conn} do
1670 |> get("/relay/following")
1671 |> json_response(200)
1673 assert result["first"]["orderedItems"] == []
1676 test "on non-federating instance, it returns 404", %{conn: conn} do
1677 clear_config([:instance, :federating], false)
1678 user = insert(:user)
1681 |> assign(:user, user)
1682 |> get("/relay/following")
1683 |> json_response(404)
1687 describe "/users/:nickname/followers" do
1688 test "it returns the followers in a collection", %{conn: conn} do
1689 user = insert(:user)
1690 user_two = insert(:user)
1691 User.follow(user, user_two)
1695 |> assign(:user, user_two)
1696 |> get("/users/#{user_two.nickname}/followers")
1697 |> json_response(200)
1699 assert result["first"]["orderedItems"] == [user.ap_id]
1702 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1703 user = insert(:user)
1704 user_two = insert(:user, hide_followers: true)
1705 User.follow(user, user_two)
1709 |> assign(:user, user)
1710 |> get("/users/#{user_two.nickname}/followers")
1711 |> json_response(200)
1713 assert is_binary(result["first"])
1716 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1718 user = insert(:user)
1719 other_user = insert(:user, hide_followers: true)
1723 |> assign(:user, user)
1724 |> get("/users/#{other_user.nickname}/followers?page=1")
1726 assert result.status == 403
1727 assert result.resp_body == ""
1730 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1732 user = insert(:user, hide_followers: true)
1733 other_user = insert(:user)
1734 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1738 |> assign(:user, user)
1739 |> get("/users/#{user.nickname}/followers?page=1")
1740 |> json_response(200)
1742 assert result["totalItems"] == 1
1743 assert result["orderedItems"] == [other_user.ap_id]
1746 test "it works for more than 10 users", %{conn: conn} do
1747 user = insert(:user)
1749 Enum.each(1..15, fn _ ->
1750 other_user = insert(:user)
1751 User.follow(other_user, user)
1756 |> assign(:user, user)
1757 |> get("/users/#{user.nickname}/followers")
1758 |> json_response(200)
1760 assert length(result["first"]["orderedItems"]) == 10
1761 assert result["first"]["totalItems"] == 15
1762 assert result["totalItems"] == 15
1766 |> assign(:user, user)
1767 |> get("/users/#{user.nickname}/followers?page=2")
1768 |> json_response(200)
1770 assert length(result["orderedItems"]) == 5
1771 assert result["totalItems"] == 15
1774 test "does not require authentication", %{conn: conn} do
1775 user = insert(:user)
1778 |> get("/users/#{user.nickname}/followers")
1779 |> json_response(200)
1783 describe "/users/:nickname/following" do
1784 test "it returns the following in a collection", %{conn: conn} do
1785 user = insert(:user)
1786 user_two = insert(:user)
1787 User.follow(user, user_two)
1791 |> assign(:user, user)
1792 |> get("/users/#{user.nickname}/following")
1793 |> json_response(200)
1795 assert result["first"]["orderedItems"] == [user_two.ap_id]
1798 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1799 user = insert(:user)
1800 user_two = insert(:user, hide_follows: true)
1801 User.follow(user, user_two)
1805 |> assign(:user, user)
1806 |> get("/users/#{user_two.nickname}/following")
1807 |> json_response(200)
1809 assert is_binary(result["first"])
1812 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1814 user = insert(:user)
1815 user_two = insert(:user, hide_follows: true)
1819 |> assign(:user, user)
1820 |> get("/users/#{user_two.nickname}/following?page=1")
1822 assert result.status == 403
1823 assert result.resp_body == ""
1826 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1828 user = insert(:user, hide_follows: true)
1829 other_user = insert(:user)
1830 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1834 |> assign(:user, user)
1835 |> get("/users/#{user.nickname}/following?page=1")
1836 |> json_response(200)
1838 assert result["totalItems"] == 1
1839 assert result["orderedItems"] == [other_user.ap_id]
1842 test "it works for more than 10 users", %{conn: conn} do
1843 user = insert(:user)
1845 Enum.each(1..15, fn _ ->
1846 user = User.get_cached_by_id(user.id)
1847 other_user = insert(:user)
1848 User.follow(user, other_user)
1853 |> assign(:user, user)
1854 |> get("/users/#{user.nickname}/following")
1855 |> json_response(200)
1857 assert length(result["first"]["orderedItems"]) == 10
1858 assert result["first"]["totalItems"] == 15
1859 assert result["totalItems"] == 15
1863 |> assign(:user, user)
1864 |> get("/users/#{user.nickname}/following?page=2")
1865 |> json_response(200)
1867 assert length(result["orderedItems"]) == 5
1868 assert result["totalItems"] == 15
1871 test "does not require authentication", %{conn: conn} do
1872 user = insert(:user)
1875 |> get("/users/#{user.nickname}/following")
1876 |> json_response(200)
1880 describe "delivery tracking" do
1881 test "it tracks a signed object fetch", %{conn: conn} do
1882 user = insert(:user, local: false)
1883 activity = insert(:note_activity)
1884 object = Object.normalize(activity, fetch: false)
1886 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1889 |> put_req_header("accept", "application/activity+json")
1890 |> assign(:user, user)
1892 |> json_response(200)
1894 assert Delivery.get(object.id, user.id)
1897 test "it tracks a signed activity fetch", %{conn: conn} do
1898 user = insert(:user, local: false)
1899 activity = insert(:note_activity)
1900 object = Object.normalize(activity, fetch: false)
1902 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1905 |> put_req_header("accept", "application/activity+json")
1906 |> assign(:user, user)
1907 |> get(activity_path)
1908 |> json_response(200)
1910 assert Delivery.get(object.id, user.id)
1913 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1914 user = insert(:user, local: false)
1915 other_user = insert(:user, local: false)
1916 activity = insert(:note_activity)
1917 object = Object.normalize(activity, fetch: false)
1919 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1922 |> put_req_header("accept", "application/activity+json")
1923 |> assign(:user, user)
1925 |> json_response(200)
1928 |> put_req_header("accept", "application/activity+json")
1929 |> assign(:user, other_user)
1931 |> json_response(200)
1933 assert Delivery.get(object.id, user.id)
1934 assert Delivery.get(object.id, other_user.id)
1937 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1938 user = insert(:user, local: false)
1939 other_user = insert(:user, local: false)
1940 activity = insert(:note_activity)
1941 object = Object.normalize(activity, fetch: false)
1943 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1946 |> put_req_header("accept", "application/activity+json")
1947 |> assign(:user, user)
1948 |> get(activity_path)
1949 |> json_response(200)
1952 |> put_req_header("accept", "application/activity+json")
1953 |> assign(:user, other_user)
1954 |> get(activity_path)
1955 |> json_response(200)
1957 assert Delivery.get(object.id, user.id)
1958 assert Delivery.get(object.id, other_user.id)
1962 describe "Additional ActivityPub C2S endpoints" do
1963 test "GET /api/ap/whoami", %{conn: conn} do
1964 user = insert(:user)
1968 |> assign(:user, user)
1969 |> get("/api/ap/whoami")
1971 user = User.get_cached_by_id(user.id)
1973 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1976 |> get("/api/ap/whoami")
1977 |> json_response(403)
1980 setup do: clear_config([:media_proxy])
1981 setup do: clear_config([Pleroma.Upload])
1983 test "POST /api/ap/upload_media", %{conn: conn} do
1984 user = insert(:user)
1986 desc = "Description of the image"
1988 image = %Plug.Upload{
1989 content_type: "image/jpeg",
1990 path: Path.absname("test/fixtures/image.jpg"),
1991 filename: "an_image.jpg"
1996 |> assign(:user, user)
1997 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1998 |> json_response(:created)
2000 assert object["name"] == desc
2001 assert object["type"] == "Document"
2002 assert object["actor"] == user.ap_id
2003 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2004 assert is_binary(object_href)
2005 assert object_mediatype == "image/jpeg"
2006 assert String.ends_with?(object_href, ".jpg")
2008 activity_request = %{
2009 "@context" => "https://www.w3.org/ns/activitystreams",
2013 "content" => "AP C2S test, attachment",
2014 "attachment" => [object],
2015 "to" => "https://www.w3.org/ns/activitystreams#Public",
2022 |> assign(:user, user)
2023 |> post("/users/#{user.nickname}/outbox", activity_request)
2024 |> json_response(:created)
2026 assert activity_response["id"]
2027 assert activity_response["object"]
2028 assert activity_response["actor"] == user.ap_id
2030 assert %Object{data: %{"attachment" => [attachment]}} =
2031 Object.normalize(activity_response["object"], fetch: false)
2033 assert attachment["type"] == "Document"
2034 assert attachment["name"] == desc
2038 "href" => ^object_href,
2040 "mediaType" => ^object_mediatype
2042 ] = attachment["url"]
2044 # Fails if unauthenticated
2046 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2047 |> json_response(403)
2051 test "pinned collection", %{conn: conn} do
2052 clear_config([:instance, :max_pinned_statuses], 2)
2053 user = insert(:user)
2054 objects = insert_list(2, :note, user: user)
2056 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2057 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2061 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2062 refresh_record(user)
2064 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2066 |> get("/users/#{nickname}/collections/featured")
2067 |> json_response(200)
2069 object_ids = Enum.map(items, & &1["id"])
2071 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2072 obj_id in object_ids