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}"
785 replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
788 File.read!("test/fixtures/users_mock/user.json")
789 |> String.replace("{{nickname}}", "lain")
791 actor = "https://example.com/users/lain"
801 headers: [{"content-type", "application/activity+json"}]
811 headers: [{"content-type", "application/activity+json"}]
814 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
818 "test/fixtures/users_mock/masto_featured.json"
820 |> String.replace("{{domain}}", "example.com")
821 |> String.replace("{{nickname}}", "lain"),
822 headers: [{"content-type", "application/activity+json"}]
832 headers: [{"content-type", "application/activity+json"}]
837 "@context" => "https://www.w3.org/ns/activitystreams",
839 "object" => status_url,
840 "target" => "https://example.com/users/lain/collections/featured",
846 |> assign(:valid_signature, true)
847 |> put_req_header("content-type", "application/activity+json")
848 |> post("/inbox", data)
849 |> json_response(200)
851 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
852 assert Activity.get_by_object_ap_id_with_object(data["object"])
853 user = User.get_cached_by_ap_id(data["actor"])
854 assert user.pinned_objects[data["object"]]
858 "object" => status_url,
859 "target" => "https://example.com/users/lain/collections/featured",
865 |> assign(:valid_signature, true)
866 |> put_req_header("content-type", "application/activity+json")
867 |> post("/inbox", data)
868 |> json_response(200)
870 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
871 assert Activity.get_by_object_ap_id_with_object(data["object"])
872 user = refresh_record(user)
873 refute user.pinned_objects[data["object"]]
877 describe "/users/:nickname/inbox" do
880 File.read!("test/fixtures/mastodon-post-activity.json")
886 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
891 |> Map.put("bcc", [user.ap_id])
892 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
896 |> assign(:valid_signature, true)
897 |> put_req_header("content-type", "application/activity+json")
898 |> post("/users/#{user.nickname}/inbox", data)
900 assert "ok" == json_response(conn, 200)
901 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
902 assert Activity.get_by_ap_id(data["id"])
905 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
910 |> Map.put("to", user.ap_id)
912 |> Kernel.put_in(["object", "to"], user.ap_id)
913 |> Kernel.put_in(["object", "cc"], [])
917 |> assign(:valid_signature, true)
918 |> put_req_header("content-type", "application/activity+json")
919 |> post("/users/#{user.nickname}/inbox", data)
921 assert "ok" == json_response(conn, 200)
922 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
923 assert Activity.get_by_ap_id(data["id"])
926 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
932 |> Map.put("cc", user.ap_id)
933 |> Kernel.put_in(["object", "to"], [])
934 |> Kernel.put_in(["object", "cc"], user.ap_id)
938 |> assign(:valid_signature, true)
939 |> put_req_header("content-type", "application/activity+json")
940 |> post("/users/#{user.nickname}/inbox", data)
942 assert "ok" == json_response(conn, 200)
943 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
944 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
945 assert user.ap_id in activity.recipients
948 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
955 |> Map.put("bcc", user.ap_id)
956 |> Kernel.put_in(["object", "to"], [])
957 |> Kernel.put_in(["object", "cc"], [])
958 |> Kernel.put_in(["object", "bcc"], user.ap_id)
962 |> assign(:valid_signature, true)
963 |> put_req_header("content-type", "application/activity+json")
964 |> post("/users/#{user.nickname}/inbox", data)
966 assert "ok" == json_response(conn, 200)
967 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
968 assert Activity.get_by_ap_id(data["id"])
971 test "it accepts announces with to as string instead of array", %{conn: conn} do
974 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
975 announcer = insert(:user, local: false)
978 "@context" => "https://www.w3.org/ns/activitystreams",
979 "actor" => announcer.ap_id,
980 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
981 "object" => post.data["object"],
982 "to" => "https://www.w3.org/ns/activitystreams#Public",
983 "cc" => [user.ap_id],
989 |> assign(:valid_signature, true)
990 |> put_req_header("content-type", "application/activity+json")
991 |> post("/users/#{user.nickname}/inbox", data)
993 assert "ok" == json_response(conn, 200)
994 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
995 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
996 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
999 test "it accepts messages from actors that are followed by the user", %{
1003 recipient = insert(:user)
1004 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
1006 {:ok, recipient, actor} = User.follow(recipient, actor)
1010 |> Map.put("attributedTo", actor.ap_id)
1014 |> Map.put("actor", actor.ap_id)
1015 |> Map.put("object", object)
1019 |> assign(:valid_signature, true)
1020 |> put_req_header("content-type", "application/activity+json")
1021 |> post("/users/#{recipient.nickname}/inbox", data)
1023 assert "ok" == json_response(conn, 200)
1024 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1025 assert Activity.get_by_ap_id(data["id"])
1028 test "it rejects reads from other users", %{conn: conn} do
1029 user = insert(:user)
1030 other_user = insert(:user)
1034 |> assign(:user, other_user)
1035 |> put_req_header("accept", "application/activity+json")
1036 |> get("/users/#{user.nickname}/inbox")
1038 assert json_response(conn, 403)
1041 test "it returns a note activity in a collection", %{conn: conn} do
1042 note_activity = insert(:direct_note_activity)
1043 note_object = Object.normalize(note_activity, fetch: false)
1044 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1048 |> assign(:user, user)
1049 |> put_req_header("accept", "application/activity+json")
1050 |> get("/users/#{user.nickname}/inbox?page=true")
1052 assert response(conn, 200) =~ note_object.data["content"]
1055 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1056 user = insert(:user)
1057 data = Map.put(data, "bcc", [user.ap_id])
1059 sender_host = URI.parse(data["actor"]).host
1060 Instances.set_consistently_unreachable(sender_host)
1061 refute Instances.reachable?(sender_host)
1065 |> assign(:valid_signature, true)
1066 |> put_req_header("content-type", "application/activity+json")
1067 |> post("/users/#{user.nickname}/inbox", data)
1069 assert "ok" == json_response(conn, 200)
1070 assert Instances.reachable?(sender_host)
1073 @tag capture_log: true
1074 test "it removes all follower collections but actor's", %{conn: conn} do
1075 [actor, recipient] = insert_pair(:user)
1079 recipient.follower_address,
1080 "https://www.w3.org/ns/activitystreams#Public"
1083 cc = [recipient.follower_address, actor.follower_address]
1086 "@context" => ["https://www.w3.org/ns/activitystreams"],
1088 "id" => Utils.generate_activity_id(),
1091 "actor" => actor.ap_id,
1096 "content" => "It's a note",
1097 "attributedTo" => actor.ap_id,
1098 "id" => Utils.generate_object_id()
1103 |> assign(:valid_signature, true)
1104 |> put_req_header("content-type", "application/activity+json")
1105 |> post("/users/#{recipient.nickname}/inbox", data)
1106 |> json_response(200)
1108 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1110 assert activity = Activity.get_by_ap_id(data["id"])
1113 assert actor.follower_address in activity.recipients
1114 assert actor.follower_address in activity.data["cc"]
1116 refute recipient.follower_address in activity.recipients
1117 refute recipient.follower_address in activity.data["cc"]
1118 refute recipient.follower_address in activity.data["to"]
1121 test "it requires authentication", %{conn: conn} do
1122 user = insert(:user)
1123 conn = put_req_header(conn, "accept", "application/activity+json")
1125 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1126 assert json_response(ret_conn, 403)
1130 |> assign(:user, user)
1131 |> get("/users/#{user.nickname}/inbox")
1133 assert json_response(ret_conn, 200)
1136 @tag capture_log: true
1137 test "forwarded report", %{conn: conn} do
1138 admin = insert(:user, is_admin: true)
1139 actor = insert(:user, local: false)
1140 remote_domain = URI.parse(actor.ap_id).host
1141 reported_user = insert(:user)
1143 note = insert(:note_activity, user: reported_user)
1147 "https://www.w3.org/ns/activitystreams",
1148 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1150 "@language" => "und"
1153 "actor" => actor.ap_id,
1157 "content" => "test",
1158 "context" => "context",
1159 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1160 "nickname" => reported_user.nickname,
1162 reported_user.ap_id,
1165 "actor_type" => "Person",
1166 "approval_pending" => false,
1168 "confirmation_pending" => false,
1169 "deactivated" => false,
1170 "display_name" => "test user",
1171 "id" => reported_user.id,
1173 "nickname" => reported_user.nickname,
1174 "registration_reason" => nil,
1177 "moderator" => false
1180 "url" => reported_user.ap_id
1183 "id" => note.data["id"],
1184 "published" => note.data["published"],
1188 "published" => note.data["published"],
1195 |> assign(:valid_signature, true)
1196 |> put_req_header("content-type", "application/activity+json")
1197 |> post("/users/#{reported_user.nickname}/inbox", data)
1198 |> json_response(200)
1200 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1202 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1204 ObanHelpers.perform_all()
1206 Swoosh.TestAssertions.assert_email_sent(
1207 to: {admin.name, admin.email},
1208 html_body: ~r/Reported Account:/i
1212 @tag capture_log: true
1213 test "forwarded report from mastodon", %{conn: conn} do
1214 admin = insert(:user, is_admin: true)
1215 actor = insert(:user, local: false)
1216 remote_domain = URI.parse(actor.ap_id).host
1217 remote_actor = "https://#{remote_domain}/actor"
1218 [reported_user, another] = insert_list(2, :user)
1220 note = insert(:note_activity, user: reported_user)
1222 Pleroma.Web.CommonAPI.favorite(another, note.id)
1225 "test/fixtures/mastodon/application_actor.json"
1227 |> String.replace("{{DOMAIN}}", remote_domain)
1229 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1232 body: mock_json_body,
1233 headers: [{"content-type", "application/activity+json"}]
1238 "@context" => "https://www.w3.org/ns/activitystreams",
1239 "actor" => remote_actor,
1240 "content" => "test report",
1241 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1243 reported_user.ap_id,
1250 |> assign(:valid_signature, true)
1251 |> put_req_header("content-type", "application/activity+json")
1252 |> post("/users/#{reported_user.nickname}/inbox", data)
1253 |> json_response(200)
1255 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1257 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1258 reported_user_ap_id = reported_user.ap_id
1260 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1262 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1263 ObanHelpers.perform_all()
1265 Swoosh.TestAssertions.assert_email_sent(
1266 to: {admin.name, admin.email},
1267 html_body: ~r/#{note.data["object"]}/i
1272 describe "GET /users/:nickname/outbox" do
1273 test "it paginates correctly", %{conn: conn} do
1274 user = insert(:user)
1275 conn = assign(conn, :user, user)
1276 outbox_endpoint = user.ap_id <> "/outbox"
1280 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1286 |> put_req_header("accept", "application/activity+json")
1287 |> get(outbox_endpoint <> "?page=true")
1288 |> json_response(200)
1290 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1291 assert length(result["orderedItems"]) == 20
1292 assert length(result_ids) == 20
1293 assert result["next"]
1294 assert String.starts_with?(result["next"], outbox_endpoint)
1298 |> put_req_header("accept", "application/activity+json")
1299 |> get(result["next"])
1300 |> json_response(200)
1302 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1303 assert length(result_next["orderedItems"]) == 6
1304 assert length(result_next_ids) == 6
1305 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1306 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1307 assert String.starts_with?(result["id"], outbox_endpoint)
1311 |> put_req_header("accept", "application/activity+json")
1312 |> get(result_next["id"])
1313 |> json_response(200)
1315 assert result_next == result_next_again
1318 test "it returns 200 even if there're no activities", %{conn: conn} do
1319 user = insert(:user)
1320 outbox_endpoint = user.ap_id <> "/outbox"
1324 |> assign(:user, user)
1325 |> put_req_header("accept", "application/activity+json")
1326 |> get(outbox_endpoint)
1328 result = json_response(conn, 200)
1329 assert outbox_endpoint == result["id"]
1332 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1333 user = insert(:user)
1334 reader = insert(:user)
1335 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1336 ap_id = note_activity.data["id"]
1340 |> assign(:user, reader)
1341 |> put_req_header("accept", "application/activity+json")
1342 |> get("/users/#{user.nickname}/outbox?page=true")
1343 |> json_response(200)
1345 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1348 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1349 user = insert(:user)
1350 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1354 |> put_req_header("accept", "application/activity+json")
1355 |> get("/users/#{user.nickname}/outbox?page=true")
1356 |> json_response(200)
1358 assert %{"orderedItems" => []} = resp
1361 test "it returns a note activity in a collection", %{conn: conn} do
1362 note_activity = insert(:note_activity)
1363 note_object = Object.normalize(note_activity, fetch: false)
1364 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1368 |> assign(:user, user)
1369 |> put_req_header("accept", "application/activity+json")
1370 |> get("/users/#{user.nickname}/outbox?page=true")
1372 assert response(conn, 200) =~ note_object.data["content"]
1375 test "it returns an announce activity in a collection", %{conn: conn} do
1376 announce_activity = insert(:announce_activity)
1377 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1381 |> assign(:user, user)
1382 |> put_req_header("accept", "application/activity+json")
1383 |> get("/users/#{user.nickname}/outbox?page=true")
1385 assert response(conn, 200) =~ announce_activity.data["object"]
1388 test "It returns poll Answers when authenticated", %{conn: conn} do
1389 poller = insert(:user)
1390 voter = insert(:user)
1393 CommonAPI.post(poller, %{
1395 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1398 assert question = Object.normalize(activity, fetch: false)
1400 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1404 |> assign(:user, voter)
1405 |> put_req_header("accept", "application/activity+json")
1406 |> get(voter.ap_id <> "/outbox?page=true")
1407 |> json_response(200)
1409 assert [answer_outbox] = outbox_get["orderedItems"]
1410 assert answer_outbox["id"] == activity.data["id"]
1414 describe "POST /users/:nickname/outbox (C2S)" do
1415 setup do: clear_config([:instance, :limit])
1420 "@context" => "https://www.w3.org/ns/activitystreams",
1424 "content" => "AP C2S test",
1425 "to" => "https://www.w3.org/ns/activitystreams#Public",
1432 test "it rejects posts from other users / unauthenticated users", %{
1436 user = insert(:user)
1437 other_user = insert(:user)
1438 conn = put_req_header(conn, "content-type", "application/activity+json")
1441 |> post("/users/#{user.nickname}/outbox", activity)
1442 |> json_response(403)
1445 |> assign(:user, other_user)
1446 |> post("/users/#{user.nickname}/outbox", activity)
1447 |> json_response(403)
1450 test "it inserts an incoming create activity into the database", %{
1454 user = insert(:user)
1458 |> assign(:user, user)
1459 |> put_req_header("content-type", "application/activity+json")
1460 |> post("/users/#{user.nickname}/outbox", activity)
1461 |> json_response(201)
1463 assert Activity.get_by_ap_id(result["id"])
1464 assert result["object"]
1465 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1466 assert object["content"] == activity["object"]["content"]
1469 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1470 user = insert(:user)
1474 |> put_in(["object", "type"], "Benis")
1478 |> assign(:user, user)
1479 |> put_req_header("content-type", "application/activity+json")
1480 |> post("/users/#{user.nickname}/outbox", activity)
1481 |> json_response(400)
1484 test "it inserts an incoming sensitive activity into the database", %{
1488 user = insert(:user)
1489 conn = assign(conn, :user, user)
1490 object = Map.put(activity["object"], "sensitive", true)
1491 activity = Map.put(activity, "object", object)
1495 |> put_req_header("content-type", "application/activity+json")
1496 |> post("/users/#{user.nickname}/outbox", activity)
1497 |> json_response(201)
1499 assert Activity.get_by_ap_id(response["id"])
1500 assert response["object"]
1501 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1502 assert response_object["sensitive"] == true
1503 assert response_object["content"] == activity["object"]["content"]
1507 |> put_req_header("accept", "application/activity+json")
1508 |> get(response["id"])
1509 |> json_response(200)
1511 assert representation["object"]["sensitive"] == true
1514 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1515 user = insert(:user)
1516 activity = Map.put(activity, "type", "BadType")
1520 |> assign(:user, user)
1521 |> put_req_header("content-type", "application/activity+json")
1522 |> post("/users/#{user.nickname}/outbox", activity)
1524 assert json_response(conn, 400)
1527 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1528 note_activity = insert(:note_activity)
1529 note_object = Object.normalize(note_activity, fetch: false)
1530 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1535 "id" => note_object.data["id"]
1541 |> assign(:user, user)
1542 |> put_req_header("content-type", "application/activity+json")
1543 |> post("/users/#{user.nickname}/outbox", data)
1544 |> json_response(201)
1546 assert Activity.get_by_ap_id(result["id"])
1548 assert object = Object.get_by_ap_id(note_object.data["id"])
1549 assert object.data["type"] == "Tombstone"
1552 test "it rejects delete activity of object from other actor", %{conn: conn} do
1553 note_activity = insert(:note_activity)
1554 note_object = Object.normalize(note_activity, fetch: false)
1555 user = insert(:user)
1560 id: note_object.data["id"]
1566 |> assign(:user, user)
1567 |> put_req_header("content-type", "application/activity+json")
1568 |> post("/users/#{user.nickname}/outbox", data)
1570 assert json_response(conn, 403)
1573 test "it increases like count when receiving a like action", %{conn: conn} do
1574 note_activity = insert(:note_activity)
1575 note_object = Object.normalize(note_activity, fetch: false)
1576 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1581 id: note_object.data["id"]
1587 |> assign(:user, user)
1588 |> put_req_header("content-type", "application/activity+json")
1589 |> post("/users/#{user.nickname}/outbox", data)
1591 result = json_response(conn, 201)
1592 assert Activity.get_by_ap_id(result["id"])
1594 assert object = Object.get_by_ap_id(note_object.data["id"])
1595 assert object.data["like_count"] == 1
1598 test "it doesn't spreads faulty attributedTo or actor fields", %{
1602 reimu = insert(:user, nickname: "reimu")
1603 cirno = insert(:user, nickname: "cirno")
1610 |> put_in(["object", "actor"], reimu.ap_id)
1611 |> put_in(["object", "attributedTo"], reimu.ap_id)
1612 |> put_in(["actor"], reimu.ap_id)
1613 |> put_in(["attributedTo"], reimu.ap_id)
1617 |> assign(:user, cirno)
1618 |> put_req_header("content-type", "application/activity+json")
1619 |> post("/users/#{reimu.nickname}/outbox", activity)
1620 |> json_response(403)
1624 |> assign(:user, cirno)
1625 |> put_req_header("content-type", "application/activity+json")
1626 |> post("/users/#{cirno.nickname}/outbox", activity)
1627 |> json_response(201)
1629 assert cirno_outbox["attributedTo"] == nil
1630 assert cirno_outbox["actor"] == cirno.ap_id
1632 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1633 assert cirno_object.data["actor"] == cirno.ap_id
1634 assert cirno_object.data["attributedTo"] == cirno.ap_id
1637 test "Character limitation", %{conn: conn, activity: activity} do
1638 clear_config([:instance, :limit], 5)
1639 user = insert(:user)
1643 |> assign(:user, user)
1644 |> put_req_header("content-type", "application/activity+json")
1645 |> post("/users/#{user.nickname}/outbox", activity)
1646 |> json_response(400)
1648 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1652 describe "/relay/followers" do
1653 test "it returns relay followers", %{conn: conn} do
1654 relay_actor = Relay.get_actor()
1655 user = insert(:user)
1656 User.follow(user, relay_actor)
1660 |> get("/relay/followers")
1661 |> json_response(200)
1663 assert result["first"]["orderedItems"] == [user.ap_id]
1666 test "on non-federating instance, it returns 404", %{conn: conn} do
1667 clear_config([:instance, :federating], false)
1668 user = insert(:user)
1671 |> assign(:user, user)
1672 |> get("/relay/followers")
1673 |> json_response(404)
1677 describe "/relay/following" do
1678 test "it returns relay following", %{conn: conn} do
1681 |> get("/relay/following")
1682 |> json_response(200)
1684 assert result["first"]["orderedItems"] == []
1687 test "on non-federating instance, it returns 404", %{conn: conn} do
1688 clear_config([:instance, :federating], false)
1689 user = insert(:user)
1692 |> assign(:user, user)
1693 |> get("/relay/following")
1694 |> json_response(404)
1698 describe "/users/:nickname/followers" do
1699 test "it returns the followers in a collection", %{conn: conn} do
1700 user = insert(:user)
1701 user_two = insert(:user)
1702 User.follow(user, user_two)
1706 |> assign(:user, user_two)
1707 |> get("/users/#{user_two.nickname}/followers")
1708 |> json_response(200)
1710 assert result["first"]["orderedItems"] == [user.ap_id]
1713 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1714 user = insert(:user)
1715 user_two = insert(:user, hide_followers: true)
1716 User.follow(user, user_two)
1720 |> assign(:user, user)
1721 |> get("/users/#{user_two.nickname}/followers")
1722 |> json_response(200)
1724 assert is_binary(result["first"])
1727 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1729 user = insert(:user)
1730 other_user = insert(:user, hide_followers: true)
1734 |> assign(:user, user)
1735 |> get("/users/#{other_user.nickname}/followers?page=1")
1737 assert result.status == 403
1738 assert result.resp_body == ""
1741 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1743 user = insert(:user, hide_followers: true)
1744 other_user = insert(:user)
1745 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1749 |> assign(:user, user)
1750 |> get("/users/#{user.nickname}/followers?page=1")
1751 |> json_response(200)
1753 assert result["totalItems"] == 1
1754 assert result["orderedItems"] == [other_user.ap_id]
1757 test "it works for more than 10 users", %{conn: conn} do
1758 user = insert(:user)
1760 Enum.each(1..15, fn _ ->
1761 other_user = insert(:user)
1762 User.follow(other_user, user)
1767 |> assign(:user, user)
1768 |> get("/users/#{user.nickname}/followers")
1769 |> json_response(200)
1771 assert length(result["first"]["orderedItems"]) == 10
1772 assert result["first"]["totalItems"] == 15
1773 assert result["totalItems"] == 15
1777 |> assign(:user, user)
1778 |> get("/users/#{user.nickname}/followers?page=2")
1779 |> json_response(200)
1781 assert length(result["orderedItems"]) == 5
1782 assert result["totalItems"] == 15
1785 test "does not require authentication", %{conn: conn} do
1786 user = insert(:user)
1789 |> get("/users/#{user.nickname}/followers")
1790 |> json_response(200)
1794 describe "/users/:nickname/following" do
1795 test "it returns the following in a collection", %{conn: conn} do
1796 user = insert(:user)
1797 user_two = insert(:user)
1798 User.follow(user, user_two)
1802 |> assign(:user, user)
1803 |> get("/users/#{user.nickname}/following")
1804 |> json_response(200)
1806 assert result["first"]["orderedItems"] == [user_two.ap_id]
1809 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1810 user = insert(:user)
1811 user_two = insert(:user, hide_follows: true)
1812 User.follow(user, user_two)
1816 |> assign(:user, user)
1817 |> get("/users/#{user_two.nickname}/following")
1818 |> json_response(200)
1820 assert is_binary(result["first"])
1823 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1825 user = insert(:user)
1826 user_two = insert(:user, hide_follows: true)
1830 |> assign(:user, user)
1831 |> get("/users/#{user_two.nickname}/following?page=1")
1833 assert result.status == 403
1834 assert result.resp_body == ""
1837 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1839 user = insert(:user, hide_follows: true)
1840 other_user = insert(:user)
1841 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1845 |> assign(:user, user)
1846 |> get("/users/#{user.nickname}/following?page=1")
1847 |> json_response(200)
1849 assert result["totalItems"] == 1
1850 assert result["orderedItems"] == [other_user.ap_id]
1853 test "it works for more than 10 users", %{conn: conn} do
1854 user = insert(:user)
1856 Enum.each(1..15, fn _ ->
1857 user = User.get_cached_by_id(user.id)
1858 other_user = insert(:user)
1859 User.follow(user, other_user)
1864 |> assign(:user, user)
1865 |> get("/users/#{user.nickname}/following")
1866 |> json_response(200)
1868 assert length(result["first"]["orderedItems"]) == 10
1869 assert result["first"]["totalItems"] == 15
1870 assert result["totalItems"] == 15
1874 |> assign(:user, user)
1875 |> get("/users/#{user.nickname}/following?page=2")
1876 |> json_response(200)
1878 assert length(result["orderedItems"]) == 5
1879 assert result["totalItems"] == 15
1882 test "does not require authentication", %{conn: conn} do
1883 user = insert(:user)
1886 |> get("/users/#{user.nickname}/following")
1887 |> json_response(200)
1891 describe "delivery tracking" do
1892 test "it tracks a signed object fetch", %{conn: conn} do
1893 user = insert(:user, local: false)
1894 activity = insert(:note_activity)
1895 object = Object.normalize(activity, fetch: false)
1897 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1900 |> put_req_header("accept", "application/activity+json")
1901 |> assign(:user, user)
1903 |> json_response(200)
1905 assert Delivery.get(object.id, user.id)
1908 test "it tracks a signed activity fetch", %{conn: conn} do
1909 user = insert(:user, local: false)
1910 activity = insert(:note_activity)
1911 object = Object.normalize(activity, fetch: false)
1913 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1916 |> put_req_header("accept", "application/activity+json")
1917 |> assign(:user, user)
1918 |> get(activity_path)
1919 |> json_response(200)
1921 assert Delivery.get(object.id, user.id)
1924 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1925 user = insert(:user, local: false)
1926 other_user = insert(:user, local: false)
1927 activity = insert(:note_activity)
1928 object = Object.normalize(activity, fetch: false)
1930 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1933 |> put_req_header("accept", "application/activity+json")
1934 |> assign(:user, user)
1936 |> json_response(200)
1939 |> put_req_header("accept", "application/activity+json")
1940 |> assign(:user, other_user)
1942 |> json_response(200)
1944 assert Delivery.get(object.id, user.id)
1945 assert Delivery.get(object.id, other_user.id)
1948 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1949 user = insert(:user, local: false)
1950 other_user = insert(:user, local: false)
1951 activity = insert(:note_activity)
1952 object = Object.normalize(activity, fetch: false)
1954 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1957 |> put_req_header("accept", "application/activity+json")
1958 |> assign(:user, user)
1959 |> get(activity_path)
1960 |> json_response(200)
1963 |> put_req_header("accept", "application/activity+json")
1964 |> assign(:user, other_user)
1965 |> get(activity_path)
1966 |> json_response(200)
1968 assert Delivery.get(object.id, user.id)
1969 assert Delivery.get(object.id, other_user.id)
1973 describe "Additional ActivityPub C2S endpoints" do
1974 test "GET /api/ap/whoami", %{conn: conn} do
1975 user = insert(:user)
1979 |> assign(:user, user)
1980 |> get("/api/ap/whoami")
1982 user = User.get_cached_by_id(user.id)
1984 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1987 |> get("/api/ap/whoami")
1988 |> json_response(403)
1991 setup do: clear_config([:media_proxy])
1992 setup do: clear_config([Pleroma.Upload])
1994 test "POST /api/ap/upload_media", %{conn: conn} do
1995 user = insert(:user)
1997 desc = "Description of the image"
1999 image = %Plug.Upload{
2000 content_type: "image/jpeg",
2001 path: Path.absname("test/fixtures/image.jpg"),
2002 filename: "an_image.jpg"
2007 |> assign(:user, user)
2008 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2009 |> json_response(:created)
2011 assert object["name"] == desc
2012 assert object["type"] == "Document"
2013 assert object["actor"] == user.ap_id
2014 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2015 assert is_binary(object_href)
2016 assert object_mediatype == "image/jpeg"
2017 assert String.ends_with?(object_href, ".jpg")
2019 activity_request = %{
2020 "@context" => "https://www.w3.org/ns/activitystreams",
2024 "content" => "AP C2S test, attachment",
2025 "attachment" => [object],
2026 "to" => "https://www.w3.org/ns/activitystreams#Public",
2033 |> assign(:user, user)
2034 |> post("/users/#{user.nickname}/outbox", activity_request)
2035 |> json_response(:created)
2037 assert activity_response["id"]
2038 assert activity_response["object"]
2039 assert activity_response["actor"] == user.ap_id
2041 assert %Object{data: %{"attachment" => [attachment]}} =
2042 Object.normalize(activity_response["object"], fetch: false)
2044 assert attachment["type"] == "Document"
2045 assert attachment["name"] == desc
2049 "href" => ^object_href,
2051 "mediaType" => ^object_mediatype
2053 ] = attachment["url"]
2055 # Fails if unauthenticated
2057 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2058 |> json_response(403)
2062 test "pinned collection", %{conn: conn} do
2063 clear_config([:instance, :max_pinned_statuses], 2)
2064 user = insert(:user)
2065 objects = insert_list(2, :note, user: user)
2067 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2068 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2072 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2073 refresh_record(user)
2075 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2077 |> get("/users/#{nickname}/collections/featured")
2078 |> json_response(200)
2080 object_ids = Enum.map(items, & &1["id"])
2082 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2083 obj_id in object_ids