1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.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 Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Config)
34 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
38 setup do: clear_config([:instance, :federating], true)
41 setup do: clear_config([:instance, :allow_relay])
43 test "with the relay active, it returns the relay user", %{conn: conn} do
46 |> get(activity_pub_path(conn, :relay))
49 assert res["id"] =~ "/relay"
52 test "with the relay disabled, it returns 404", %{conn: conn} do
53 clear_config([:instance, :allow_relay], false)
56 |> get(activity_pub_path(conn, :relay))
60 test "on non-federating instance, it returns 404", %{conn: conn} do
61 clear_config([:instance, :federating], false)
65 |> assign(:user, user)
66 |> get(activity_pub_path(conn, :relay))
71 describe "/internal/fetch" do
72 test "it returns the internal fetch user", %{conn: conn} do
75 |> get(activity_pub_path(conn, :internal_fetch))
78 assert res["id"] =~ "/fetch"
81 test "on non-federating instance, it returns 404", %{conn: conn} do
82 clear_config([:instance, :federating], false)
86 |> assign(:user, user)
87 |> get(activity_pub_path(conn, :internal_fetch))
92 describe "/users/:nickname" do
93 test "it returns a json representation of the user with accept application/json", %{
100 |> put_req_header("accept", "application/json")
101 |> get("/users/#{user.nickname}")
103 user = User.get_cached_by_id(user.id)
105 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
108 test "it returns a json representation of the user with accept application/activity+json", %{
115 |> put_req_header("accept", "application/activity+json")
116 |> get("/users/#{user.nickname}")
118 user = User.get_cached_by_id(user.id)
120 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
123 test "it returns a json representation of the user with accept application/ld+json", %{
132 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
134 |> get("/users/#{user.nickname}")
136 user = User.get_cached_by_id(user.id)
138 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
141 test "it returns 404 for remote users", %{
144 user = insert(:user, local: false, nickname: "remoteuser@example.com")
148 |> put_req_header("accept", "application/json")
149 |> get("/users/#{user.nickname}.json")
151 assert json_response(conn, 404)
154 test "it returns error when user is not found", %{conn: conn} do
157 |> put_req_header("accept", "application/json")
158 |> get("/users/jimm")
159 |> json_response(404)
161 assert response == "Not found"
165 describe "mastodon compatibility routes" do
166 test "it returns a json representation of the object with accept application/json", %{
173 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
174 "actor" => Endpoint.url() <> "/users/raymoo",
175 "to" => [Pleroma.Constants.as_public()]
181 |> put_req_header("accept", "application/json")
182 |> get("/users/raymoo/statuses/999999999")
184 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
187 test "it returns a json representation of the activity with accept application/json", %{
194 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
195 "actor" => Endpoint.url() <> "/users/raymoo",
196 "to" => [Pleroma.Constants.as_public()]
202 "id" => object.data["id"] <> "/activity",
204 "object" => object.data["id"],
205 "actor" => object.data["actor"],
206 "to" => object.data["to"]
208 |> ActivityPub.persist(local: true)
212 |> put_req_header("accept", "application/json")
213 |> get("/users/raymoo/statuses/999999999/activity")
215 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
219 describe "/objects/:uuid" do
220 test "it doesn't return a local-only object", %{conn: conn} do
222 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
224 assert Pleroma.Web.ActivityPub.Visibility.local_public?(post)
226 object = Object.normalize(post, fetch: false)
227 uuid = String.split(object.data["id"], "/") |> List.last()
231 |> put_req_header("accept", "application/json")
232 |> get("/objects/#{uuid}")
234 assert json_response(conn, 404)
237 test "returns local-only objects when authenticated", %{conn: conn} do
239 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
241 assert Pleroma.Web.ActivityPub.Visibility.local_public?(post)
243 object = Object.normalize(post, fetch: false)
244 uuid = String.split(object.data["id"], "/") |> List.last()
248 |> assign(:user, user)
249 |> put_req_header("accept", "application/activity+json")
250 |> get("/objects/#{uuid}")
252 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
255 test "does not return local-only objects for remote users", %{conn: conn} do
257 reader = insert(:user, local: false)
260 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
262 assert Pleroma.Web.ActivityPub.Visibility.local_public?(post)
264 object = Object.normalize(post, fetch: false)
265 uuid = String.split(object.data["id"], "/") |> List.last()
269 |> assign(:user, reader)
270 |> put_req_header("accept", "application/activity+json")
271 |> get("/objects/#{uuid}")
273 json_response(response, 404)
276 test "it returns a json representation of the object with accept application/json", %{
280 uuid = String.split(note.data["id"], "/") |> List.last()
284 |> put_req_header("accept", "application/json")
285 |> get("/objects/#{uuid}")
287 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
290 test "it returns a json representation of the object with accept application/activity+json",
293 uuid = String.split(note.data["id"], "/") |> List.last()
297 |> put_req_header("accept", "application/activity+json")
298 |> get("/objects/#{uuid}")
300 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
303 test "it returns a json representation of the object with accept application/ld+json", %{
307 uuid = String.split(note.data["id"], "/") |> List.last()
313 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
315 |> get("/objects/#{uuid}")
317 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
320 test "does not cache authenticated response", %{conn: conn} do
322 reader = insert(:user)
325 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
327 object = Object.normalize(post, fetch: false)
328 uuid = String.split(object.data["id"], "/") |> List.last()
332 |> assign(:user, reader)
333 |> put_req_header("accept", "application/activity+json")
334 |> get("/objects/#{uuid}")
336 json_response(response, 200)
339 |> put_req_header("accept", "application/activity+json")
340 |> get("/objects/#{uuid}")
341 |> json_response(404)
344 test "it returns 404 for non-public messages", %{conn: conn} do
345 note = insert(:direct_note)
346 uuid = String.split(note.data["id"], "/") |> List.last()
350 |> put_req_header("accept", "application/activity+json")
351 |> get("/objects/#{uuid}")
353 assert json_response(conn, 404)
356 test "returns visible non-public messages when authenticated", %{conn: conn} do
357 note = insert(:direct_note)
358 uuid = String.split(note.data["id"], "/") |> List.last()
359 user = User.get_by_ap_id(note.data["actor"])
360 marisa = insert(:user)
363 |> assign(:user, marisa)
364 |> put_req_header("accept", "application/activity+json")
365 |> get("/objects/#{uuid}")
366 |> json_response(404)
370 |> assign(:user, user)
371 |> put_req_header("accept", "application/activity+json")
372 |> get("/objects/#{uuid}")
373 |> json_response(200)
375 assert response == ObjectView.render("object.json", %{object: note})
378 test "it returns 404 for tombstone objects", %{conn: conn} do
379 tombstone = insert(:tombstone)
380 uuid = String.split(tombstone.data["id"], "/") |> List.last()
384 |> put_req_header("accept", "application/activity+json")
385 |> get("/objects/#{uuid}")
387 assert json_response(conn, 404)
390 test "it caches a response", %{conn: conn} do
392 uuid = String.split(note.data["id"], "/") |> List.last()
396 |> put_req_header("accept", "application/activity+json")
397 |> get("/objects/#{uuid}")
399 assert json_response(conn1, :ok)
400 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
404 |> put_req_header("accept", "application/activity+json")
405 |> get("/objects/#{uuid}")
407 assert json_response(conn1, :ok) == json_response(conn2, :ok)
408 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
411 test "cached purged after object deletion", %{conn: conn} do
413 uuid = String.split(note.data["id"], "/") |> List.last()
417 |> put_req_header("accept", "application/activity+json")
418 |> get("/objects/#{uuid}")
420 assert json_response(conn1, :ok)
421 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
427 |> put_req_header("accept", "application/activity+json")
428 |> get("/objects/#{uuid}")
430 assert "Not found" == json_response(conn2, :not_found)
434 describe "/activities/:uuid" do
435 test "it doesn't return a local-only activity", %{conn: conn} do
437 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
439 assert Pleroma.Web.ActivityPub.Visibility.local_public?(post)
441 uuid = String.split(post.data["id"], "/") |> List.last()
445 |> put_req_header("accept", "application/json")
446 |> get("/activities/#{uuid}")
448 assert json_response(conn, 404)
451 test "returns local-only activities when authenticated", %{conn: conn} do
453 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
455 assert Pleroma.Web.ActivityPub.Visibility.local_public?(post)
457 uuid = String.split(post.data["id"], "/") |> List.last()
461 |> assign(:user, user)
462 |> put_req_header("accept", "application/activity+json")
463 |> get("/activities/#{uuid}")
465 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
468 test "it returns a json representation of the activity", %{conn: conn} do
469 activity = insert(:note_activity)
470 uuid = String.split(activity.data["id"], "/") |> List.last()
474 |> put_req_header("accept", "application/activity+json")
475 |> get("/activities/#{uuid}")
477 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
480 test "it returns 404 for non-public activities", %{conn: conn} do
481 activity = insert(:direct_note_activity)
482 uuid = String.split(activity.data["id"], "/") |> List.last()
486 |> put_req_header("accept", "application/activity+json")
487 |> get("/activities/#{uuid}")
489 assert json_response(conn, 404)
492 test "returns visible non-public messages when authenticated", %{conn: conn} do
493 note = insert(:direct_note_activity)
494 uuid = String.split(note.data["id"], "/") |> List.last()
495 user = User.get_by_ap_id(note.data["actor"])
496 marisa = insert(:user)
499 |> assign(:user, marisa)
500 |> put_req_header("accept", "application/activity+json")
501 |> get("/activities/#{uuid}")
502 |> json_response(404)
506 |> assign(:user, user)
507 |> put_req_header("accept", "application/activity+json")
508 |> get("/activities/#{uuid}")
509 |> json_response(200)
511 assert response == ObjectView.render("object.json", %{object: note})
514 test "it caches a response", %{conn: conn} do
515 activity = insert(:note_activity)
516 uuid = String.split(activity.data["id"], "/") |> List.last()
520 |> put_req_header("accept", "application/activity+json")
521 |> get("/activities/#{uuid}")
523 assert json_response(conn1, :ok)
524 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
528 |> put_req_header("accept", "application/activity+json")
529 |> get("/activities/#{uuid}")
531 assert json_response(conn1, :ok) == json_response(conn2, :ok)
532 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
535 test "cached purged after activity deletion", %{conn: conn} do
537 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
539 uuid = String.split(activity.data["id"], "/") |> List.last()
543 |> put_req_header("accept", "application/activity+json")
544 |> get("/activities/#{uuid}")
546 assert json_response(conn1, :ok)
547 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
549 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
553 |> put_req_header("accept", "application/activity+json")
554 |> get("/activities/#{uuid}")
556 assert "Not found" == json_response(conn2, :not_found)
561 test "it inserts an incoming activity into the database", %{conn: conn} do
562 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
566 |> assign(:valid_signature, true)
567 |> put_req_header("content-type", "application/activity+json")
568 |> post("/inbox", data)
570 assert "ok" == json_response(conn, 200)
572 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
573 assert Activity.get_by_ap_id(data["id"])
576 @tag capture_log: true
577 test "it inserts an incoming activity into the database" <>
578 "even if we can't fetch the user but have it in our db",
582 ap_id: "https://mastodon.example.org/users/raymoo",
584 last_refreshed_at: nil
588 File.read!("test/fixtures/mastodon-post-activity.json")
590 |> Map.put("actor", user.ap_id)
591 |> put_in(["object", "attributedTo"], user.ap_id)
595 |> assign(:valid_signature, true)
596 |> put_req_header("content-type", "application/activity+json")
597 |> post("/inbox", data)
599 assert "ok" == json_response(conn, 200)
601 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
602 assert Activity.get_by_ap_id(data["id"])
605 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
606 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
608 sender_url = data["actor"]
609 Instances.set_consistently_unreachable(sender_url)
610 refute Instances.reachable?(sender_url)
614 |> assign(:valid_signature, true)
615 |> put_req_header("content-type", "application/activity+json")
616 |> post("/inbox", data)
618 assert "ok" == json_response(conn, 200)
619 assert Instances.reachable?(sender_url)
622 test "accept follow activity", %{conn: conn} do
623 clear_config([:instance, :federating], true)
624 relay = Relay.get_actor()
626 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
628 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
629 relay = refresh_record(relay)
632 File.read!("test/fixtures/relay/accept-follow.json")
633 |> String.replace("{{ap_id}}", relay.ap_id)
634 |> String.replace("{{activity_id}}", activity.data["id"])
638 |> assign(:valid_signature, true)
639 |> put_req_header("content-type", "application/activity+json")
640 |> post("/inbox", accept)
641 |> json_response(200)
643 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
645 assert Pleroma.FollowingRelationship.following?(
650 Mix.shell(Mix.Shell.Process)
653 Mix.shell(Mix.Shell.IO)
656 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
657 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
660 @tag capture_log: true
661 test "without valid signature, " <>
662 "it only accepts Create activities and requires enabled federation",
664 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
665 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
667 conn = put_req_header(conn, "content-type", "application/activity+json")
669 clear_config([:instance, :federating], false)
672 |> post("/inbox", data)
673 |> json_response(403)
676 |> post("/inbox", non_create_data)
677 |> json_response(403)
679 clear_config([:instance, :federating], true)
681 ret_conn = post(conn, "/inbox", data)
682 assert "ok" == json_response(ret_conn, 200)
685 |> post("/inbox", non_create_data)
686 |> json_response(400)
689 test "accepts Add/Remove activities", %{conn: conn} do
690 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
693 File.read!("test/fixtures/statuses/note.json")
694 |> String.replace("{{nickname}}", "lain")
695 |> String.replace("{{object_id}}", object_id)
697 object_url = "https://example.com/objects/#{object_id}"
700 File.read!("test/fixtures/users_mock/user.json")
701 |> String.replace("{{nickname}}", "lain")
703 actor = "https://example.com/users/lain"
713 headers: [{"content-type", "application/activity+json"}]
723 headers: [{"content-type", "application/activity+json"}]
726 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
730 "test/fixtures/users_mock/masto_featured.json"
732 |> String.replace("{{domain}}", "example.com")
733 |> String.replace("{{nickname}}", "lain"),
734 headers: [{"content-type", "application/activity+json"}]
739 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
741 "object" => object_url,
742 "target" => "https://example.com/users/lain/collections/featured",
744 "to" => [Pleroma.Constants.as_public()]
749 |> assign(:valid_signature, true)
750 |> put_req_header("content-type", "application/activity+json")
751 |> post("/inbox", data)
752 |> json_response(200)
754 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
755 assert Activity.get_by_ap_id(data["id"])
756 user = User.get_cached_by_ap_id(data["actor"])
757 assert user.pinned_objects[data["object"]]
760 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
762 "object" => object_url,
763 "target" => "https://example.com/users/lain/collections/featured",
765 "to" => [Pleroma.Constants.as_public()]
770 |> assign(:valid_signature, true)
771 |> put_req_header("content-type", "application/activity+json")
772 |> post("/inbox", data)
773 |> json_response(200)
775 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
776 user = refresh_record(user)
777 refute user.pinned_objects[data["object"]]
780 test "mastodon pin/unpin", %{conn: conn} do
781 status_id = "105786274556060421"
784 File.read!("test/fixtures/statuses/masto-note.json")
785 |> String.replace("{{nickname}}", "lain")
786 |> String.replace("{{status_id}}", status_id)
788 status_url = "https://example.com/users/lain/statuses/#{status_id}"
791 File.read!("test/fixtures/users_mock/user.json")
792 |> String.replace("{{nickname}}", "lain")
794 actor = "https://example.com/users/lain"
804 headers: [{"content-type", "application/activity+json"}]
814 headers: [{"content-type", "application/activity+json"}]
817 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
821 "test/fixtures/users_mock/masto_featured.json"
823 |> String.replace("{{domain}}", "example.com")
824 |> String.replace("{{nickname}}", "lain"),
825 headers: [{"content-type", "application/activity+json"}]
830 "@context" => "https://www.w3.org/ns/activitystreams",
832 "object" => status_url,
833 "target" => "https://example.com/users/lain/collections/featured",
839 |> assign(:valid_signature, true)
840 |> put_req_header("content-type", "application/activity+json")
841 |> post("/inbox", data)
842 |> json_response(200)
844 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
845 assert Activity.get_by_object_ap_id_with_object(data["object"])
846 user = User.get_cached_by_ap_id(data["actor"])
847 assert user.pinned_objects[data["object"]]
851 "object" => status_url,
852 "target" => "https://example.com/users/lain/collections/featured",
858 |> assign(:valid_signature, true)
859 |> put_req_header("content-type", "application/activity+json")
860 |> post("/inbox", data)
861 |> json_response(200)
863 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
864 assert Activity.get_by_object_ap_id_with_object(data["object"])
865 user = refresh_record(user)
866 refute user.pinned_objects[data["object"]]
870 describe "/users/:nickname/inbox" do
873 File.read!("test/fixtures/mastodon-post-activity.json")
879 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
884 |> Map.put("bcc", [user.ap_id])
885 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
889 |> assign(:valid_signature, true)
890 |> put_req_header("content-type", "application/activity+json")
891 |> post("/users/#{user.nickname}/inbox", data)
893 assert "ok" == json_response(conn, 200)
894 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
895 assert Activity.get_by_ap_id(data["id"])
898 test "it rejects an invalid incoming activity", %{conn: conn, data: data} do
899 user = insert(:user, is_active: false)
903 |> Map.put("bcc", [user.ap_id])
904 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
908 |> assign(:valid_signature, true)
909 |> put_req_header("content-type", "application/activity+json")
910 |> post("/users/#{user.nickname}/inbox", data)
912 assert "Invalid request." == json_response(conn, 400)
915 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
920 |> Map.put("to", user.ap_id)
922 |> Kernel.put_in(["object", "to"], user.ap_id)
923 |> Kernel.put_in(["object", "cc"], [])
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 assert Activity.get_by_ap_id(data["id"])
936 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
942 |> Map.put("cc", user.ap_id)
943 |> Kernel.put_in(["object", "to"], [])
944 |> Kernel.put_in(["object", "cc"], user.ap_id)
948 |> assign(:valid_signature, true)
949 |> put_req_header("content-type", "application/activity+json")
950 |> post("/users/#{user.nickname}/inbox", data)
952 assert "ok" == json_response(conn, 200)
953 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
954 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
955 assert user.ap_id in activity.recipients
958 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
965 |> Map.put("bcc", user.ap_id)
966 |> Kernel.put_in(["object", "to"], [])
967 |> Kernel.put_in(["object", "cc"], [])
968 |> Kernel.put_in(["object", "bcc"], user.ap_id)
972 |> assign(:valid_signature, true)
973 |> put_req_header("content-type", "application/activity+json")
974 |> post("/users/#{user.nickname}/inbox", data)
976 assert "ok" == json_response(conn, 200)
977 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
978 assert Activity.get_by_ap_id(data["id"])
981 test "it accepts announces with to as string instead of array", %{conn: conn} do
984 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
985 announcer = insert(:user, local: false)
988 "@context" => "https://www.w3.org/ns/activitystreams",
989 "actor" => announcer.ap_id,
990 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
991 "object" => post.data["object"],
992 "to" => "https://www.w3.org/ns/activitystreams#Public",
993 "cc" => [user.ap_id],
999 |> assign(:valid_signature, true)
1000 |> put_req_header("content-type", "application/activity+json")
1001 |> post("/users/#{user.nickname}/inbox", data)
1003 assert "ok" == json_response(conn, 200)
1004 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1005 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
1006 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
1009 test "it accepts messages from actors that are followed by the user", %{
1013 recipient = insert(:user)
1014 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
1016 {:ok, recipient, actor} = User.follow(recipient, actor)
1020 |> Map.put("attributedTo", actor.ap_id)
1024 |> Map.put("actor", actor.ap_id)
1025 |> Map.put("object", object)
1029 |> assign(:valid_signature, true)
1030 |> put_req_header("content-type", "application/activity+json")
1031 |> post("/users/#{recipient.nickname}/inbox", data)
1033 assert "ok" == json_response(conn, 200)
1034 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1035 assert Activity.get_by_ap_id(data["id"])
1038 test "it rejects reads from other users", %{conn: conn} do
1039 user = insert(:user)
1040 other_user = insert(:user)
1044 |> assign(:user, other_user)
1045 |> put_req_header("accept", "application/activity+json")
1046 |> get("/users/#{user.nickname}/inbox")
1048 assert json_response(conn, 403)
1051 test "it returns a note activity in a collection", %{conn: conn} do
1052 note_activity = insert(:direct_note_activity)
1053 note_object = Object.normalize(note_activity, fetch: false)
1054 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1058 |> assign(:user, user)
1059 |> put_req_header("accept", "application/activity+json")
1060 |> get("/users/#{user.nickname}/inbox?page=true")
1062 assert response(conn, 200) =~ note_object.data["content"]
1065 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1066 user = insert(:user)
1067 data = Map.put(data, "bcc", [user.ap_id])
1069 sender_host = URI.parse(data["actor"]).host
1070 Instances.set_consistently_unreachable(sender_host)
1071 refute Instances.reachable?(sender_host)
1075 |> assign(:valid_signature, true)
1076 |> put_req_header("content-type", "application/activity+json")
1077 |> post("/users/#{user.nickname}/inbox", data)
1079 assert "ok" == json_response(conn, 200)
1080 assert Instances.reachable?(sender_host)
1083 @tag capture_log: true
1084 test "it removes all follower collections but actor's", %{conn: conn} do
1085 [actor, recipient] = insert_pair(:user)
1089 recipient.follower_address,
1090 "https://www.w3.org/ns/activitystreams#Public"
1093 cc = [recipient.follower_address, actor.follower_address]
1096 "@context" => ["https://www.w3.org/ns/activitystreams"],
1098 "id" => Utils.generate_activity_id(),
1101 "actor" => actor.ap_id,
1106 "content" => "It's a note",
1107 "attributedTo" => actor.ap_id,
1108 "id" => Utils.generate_object_id()
1113 |> assign(:valid_signature, true)
1114 |> put_req_header("content-type", "application/activity+json")
1115 |> post("/users/#{recipient.nickname}/inbox", data)
1116 |> json_response(200)
1118 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1120 assert activity = Activity.get_by_ap_id(data["id"])
1123 assert actor.follower_address in activity.recipients
1124 assert actor.follower_address in activity.data["cc"]
1126 refute recipient.follower_address in activity.recipients
1127 refute recipient.follower_address in activity.data["cc"]
1128 refute recipient.follower_address in activity.data["to"]
1131 test "it requires authentication", %{conn: conn} do
1132 user = insert(:user)
1133 conn = put_req_header(conn, "accept", "application/activity+json")
1135 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1136 assert json_response(ret_conn, 403)
1140 |> assign(:user, user)
1141 |> get("/users/#{user.nickname}/inbox")
1143 assert json_response(ret_conn, 200)
1146 @tag capture_log: true
1147 test "forwarded report", %{conn: conn} do
1148 admin = insert(:user, is_admin: true)
1149 actor = insert(:user, local: false)
1150 remote_domain = URI.parse(actor.ap_id).host
1151 reported_user = insert(:user)
1153 note = insert(:note_activity, user: reported_user)
1157 "https://www.w3.org/ns/activitystreams",
1158 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1160 "@language" => "und"
1163 "actor" => actor.ap_id,
1167 "content" => "test",
1168 "context" => "context",
1169 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1170 "nickname" => reported_user.nickname,
1172 reported_user.ap_id,
1175 "actor_type" => "Person",
1176 "approval_pending" => false,
1178 "confirmation_pending" => false,
1179 "deactivated" => false,
1180 "display_name" => "test user",
1181 "id" => reported_user.id,
1183 "nickname" => reported_user.nickname,
1184 "registration_reason" => nil,
1187 "moderator" => false
1190 "url" => reported_user.ap_id
1193 "id" => note.data["id"],
1194 "published" => note.data["published"],
1198 "published" => note.data["published"],
1205 |> assign(:valid_signature, true)
1206 |> put_req_header("content-type", "application/activity+json")
1207 |> post("/users/#{reported_user.nickname}/inbox", data)
1208 |> json_response(200)
1210 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1212 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1214 ObanHelpers.perform_all()
1216 Swoosh.TestAssertions.assert_email_sent(
1217 to: {admin.name, admin.email},
1218 html_body: ~r/Reported Account:/i
1222 @tag capture_log: true
1223 test "forwarded report from mastodon", %{conn: conn} do
1224 admin = insert(:user, is_admin: true)
1225 actor = insert(:user, local: false)
1226 remote_domain = URI.parse(actor.ap_id).host
1227 remote_actor = "https://#{remote_domain}/actor"
1228 [reported_user, another] = insert_list(2, :user)
1230 note = insert(:note_activity, user: reported_user)
1232 Pleroma.Web.CommonAPI.favorite(another, note.id)
1235 "test/fixtures/mastodon/application_actor.json"
1237 |> String.replace("{{DOMAIN}}", remote_domain)
1239 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1242 body: mock_json_body,
1243 headers: [{"content-type", "application/activity+json"}]
1248 "@context" => "https://www.w3.org/ns/activitystreams",
1249 "actor" => remote_actor,
1250 "content" => "test report",
1251 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1253 reported_user.ap_id,
1260 |> assign(:valid_signature, true)
1261 |> put_req_header("content-type", "application/activity+json")
1262 |> post("/users/#{reported_user.nickname}/inbox", data)
1263 |> json_response(200)
1265 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1267 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1268 reported_user_ap_id = reported_user.ap_id
1270 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1272 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1273 ObanHelpers.perform_all()
1275 Swoosh.TestAssertions.assert_email_sent(
1276 to: {admin.name, admin.email},
1277 html_body: ~r/#{note.data["object"]}/i
1282 describe "GET /users/:nickname/outbox" do
1283 test "it paginates correctly", %{conn: conn} do
1284 user = insert(:user)
1285 conn = assign(conn, :user, user)
1286 outbox_endpoint = user.ap_id <> "/outbox"
1290 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1296 |> put_req_header("accept", "application/activity+json")
1297 |> get(outbox_endpoint <> "?page=true")
1298 |> json_response(200)
1300 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1301 assert length(result["orderedItems"]) == 20
1302 assert length(result_ids) == 20
1303 assert result["next"]
1304 assert String.starts_with?(result["next"], outbox_endpoint)
1308 |> put_req_header("accept", "application/activity+json")
1309 |> get(result["next"])
1310 |> json_response(200)
1312 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1313 assert length(result_next["orderedItems"]) == 6
1314 assert length(result_next_ids) == 6
1315 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1316 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1317 assert String.starts_with?(result["id"], outbox_endpoint)
1321 |> put_req_header("accept", "application/activity+json")
1322 |> get(result_next["id"])
1323 |> json_response(200)
1325 assert result_next == result_next_again
1328 test "it returns 200 even if there're no activities", %{conn: conn} do
1329 user = insert(:user)
1330 outbox_endpoint = user.ap_id <> "/outbox"
1334 |> assign(:user, user)
1335 |> put_req_header("accept", "application/activity+json")
1336 |> get(outbox_endpoint)
1338 result = json_response(conn, 200)
1339 assert outbox_endpoint == result["id"]
1342 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1343 user = insert(:user)
1344 reader = insert(:user)
1345 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1346 ap_id = note_activity.data["id"]
1350 |> assign(:user, reader)
1351 |> put_req_header("accept", "application/activity+json")
1352 |> get("/users/#{user.nickname}/outbox?page=true")
1353 |> json_response(200)
1355 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1358 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1359 user = insert(:user)
1360 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1364 |> put_req_header("accept", "application/activity+json")
1365 |> get("/users/#{user.nickname}/outbox?page=true")
1366 |> json_response(200)
1368 assert %{"orderedItems" => []} = resp
1371 test "it returns a note activity in a collection", %{conn: conn} do
1372 note_activity = insert(:note_activity)
1373 note_object = Object.normalize(note_activity, fetch: false)
1374 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1378 |> assign(:user, user)
1379 |> put_req_header("accept", "application/activity+json")
1380 |> get("/users/#{user.nickname}/outbox?page=true")
1382 assert response(conn, 200) =~ note_object.data["content"]
1385 test "it returns an announce activity in a collection", %{conn: conn} do
1386 announce_activity = insert(:announce_activity)
1387 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1391 |> assign(:user, user)
1392 |> put_req_header("accept", "application/activity+json")
1393 |> get("/users/#{user.nickname}/outbox?page=true")
1395 assert response(conn, 200) =~ announce_activity.data["object"]
1398 test "It returns poll Answers when authenticated", %{conn: conn} do
1399 poller = insert(:user)
1400 voter = insert(:user)
1403 CommonAPI.post(poller, %{
1405 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1408 assert question = Object.normalize(activity, fetch: false)
1410 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1414 |> assign(:user, voter)
1415 |> put_req_header("accept", "application/activity+json")
1416 |> get(voter.ap_id <> "/outbox?page=true")
1417 |> json_response(200)
1419 assert [answer_outbox] = outbox_get["orderedItems"]
1420 assert answer_outbox["id"] == activity.data["id"]
1424 describe "POST /users/:nickname/outbox (C2S)" do
1425 setup do: clear_config([:instance, :limit])
1430 "@context" => "https://www.w3.org/ns/activitystreams",
1434 "content" => "AP C2S test",
1435 "to" => "https://www.w3.org/ns/activitystreams#Public",
1442 test "it rejects posts from other users / unauthenticated users", %{
1446 user = insert(:user)
1447 other_user = insert(:user)
1448 conn = put_req_header(conn, "content-type", "application/activity+json")
1451 |> post("/users/#{user.nickname}/outbox", activity)
1452 |> json_response(403)
1455 |> assign(:user, other_user)
1456 |> post("/users/#{user.nickname}/outbox", activity)
1457 |> json_response(403)
1460 test "it inserts an incoming create activity into the database", %{
1464 user = insert(:user)
1468 |> assign(:user, user)
1469 |> put_req_header("content-type", "application/activity+json")
1470 |> post("/users/#{user.nickname}/outbox", activity)
1471 |> json_response(201)
1473 assert Activity.get_by_ap_id(result["id"])
1474 assert result["object"]
1475 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1476 assert object["content"] == activity["object"]["content"]
1479 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1480 user = insert(:user)
1484 |> put_in(["object", "type"], "Benis")
1488 |> assign(:user, user)
1489 |> put_req_header("content-type", "application/activity+json")
1490 |> post("/users/#{user.nickname}/outbox", activity)
1491 |> json_response(400)
1494 test "it inserts an incoming sensitive activity into the database", %{
1498 user = insert(:user)
1499 conn = assign(conn, :user, user)
1500 object = Map.put(activity["object"], "sensitive", true)
1501 activity = Map.put(activity, "object", object)
1505 |> put_req_header("content-type", "application/activity+json")
1506 |> post("/users/#{user.nickname}/outbox", activity)
1507 |> json_response(201)
1509 assert Activity.get_by_ap_id(response["id"])
1510 assert response["object"]
1511 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1512 assert response_object["sensitive"] == true
1513 assert response_object["content"] == activity["object"]["content"]
1517 |> put_req_header("accept", "application/activity+json")
1518 |> get(response["id"])
1519 |> json_response(200)
1521 assert representation["object"]["sensitive"] == true
1524 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1525 user = insert(:user)
1526 activity = Map.put(activity, "type", "BadType")
1530 |> assign(:user, user)
1531 |> put_req_header("content-type", "application/activity+json")
1532 |> post("/users/#{user.nickname}/outbox", activity)
1534 assert json_response(conn, 400)
1537 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1538 note_activity = insert(:note_activity)
1539 note_object = Object.normalize(note_activity, fetch: false)
1540 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1545 "id" => note_object.data["id"]
1551 |> assign(:user, user)
1552 |> put_req_header("content-type", "application/activity+json")
1553 |> post("/users/#{user.nickname}/outbox", data)
1554 |> json_response(201)
1556 assert Activity.get_by_ap_id(result["id"])
1558 assert object = Object.get_by_ap_id(note_object.data["id"])
1559 assert object.data["type"] == "Tombstone"
1562 test "it rejects delete activity of object from other actor", %{conn: conn} do
1563 note_activity = insert(:note_activity)
1564 note_object = Object.normalize(note_activity, fetch: false)
1565 user = insert(:user)
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 assert json_response(conn, 403)
1583 test "it increases like count when receiving a like action", %{conn: conn} do
1584 note_activity = insert(:note_activity)
1585 note_object = Object.normalize(note_activity, fetch: false)
1586 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1591 id: note_object.data["id"]
1597 |> assign(:user, user)
1598 |> put_req_header("content-type", "application/activity+json")
1599 |> post("/users/#{user.nickname}/outbox", data)
1601 result = json_response(conn, 201)
1602 assert Activity.get_by_ap_id(result["id"])
1604 assert object = Object.get_by_ap_id(note_object.data["id"])
1605 assert object.data["like_count"] == 1
1608 test "it doesn't spreads faulty attributedTo or actor fields", %{
1612 reimu = insert(:user, nickname: "reimu")
1613 cirno = insert(:user, nickname: "cirno")
1620 |> put_in(["object", "actor"], reimu.ap_id)
1621 |> put_in(["object", "attributedTo"], reimu.ap_id)
1622 |> put_in(["actor"], reimu.ap_id)
1623 |> put_in(["attributedTo"], reimu.ap_id)
1627 |> assign(:user, cirno)
1628 |> put_req_header("content-type", "application/activity+json")
1629 |> post("/users/#{reimu.nickname}/outbox", activity)
1630 |> json_response(403)
1634 |> assign(:user, cirno)
1635 |> put_req_header("content-type", "application/activity+json")
1636 |> post("/users/#{cirno.nickname}/outbox", activity)
1637 |> json_response(201)
1639 assert cirno_outbox["attributedTo"] == nil
1640 assert cirno_outbox["actor"] == cirno.ap_id
1642 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1643 assert cirno_object.data["actor"] == cirno.ap_id
1644 assert cirno_object.data["attributedTo"] == cirno.ap_id
1647 test "Character limitation", %{conn: conn, activity: activity} do
1648 clear_config([:instance, :limit], 5)
1649 user = insert(:user)
1653 |> assign(:user, user)
1654 |> put_req_header("content-type", "application/activity+json")
1655 |> post("/users/#{user.nickname}/outbox", activity)
1656 |> json_response(400)
1658 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1662 describe "/relay/followers" do
1663 test "it returns relay followers", %{conn: conn} do
1664 relay_actor = Relay.get_actor()
1665 user = insert(:user)
1666 User.follow(user, relay_actor)
1670 |> get("/relay/followers")
1671 |> json_response(200)
1673 assert result["first"]["orderedItems"] == [user.ap_id]
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/followers")
1683 |> json_response(404)
1687 describe "/relay/following" do
1688 test "it returns relay following", %{conn: conn} do
1691 |> get("/relay/following")
1692 |> json_response(200)
1694 assert result["first"]["orderedItems"] == []
1697 test "on non-federating instance, it returns 404", %{conn: conn} do
1698 clear_config([:instance, :federating], false)
1699 user = insert(:user)
1702 |> assign(:user, user)
1703 |> get("/relay/following")
1704 |> json_response(404)
1708 describe "/users/:nickname/followers" do
1709 test "it returns the followers in a collection", %{conn: conn} do
1710 user = insert(:user)
1711 user_two = insert(:user)
1712 User.follow(user, user_two)
1716 |> assign(:user, user_two)
1717 |> get("/users/#{user_two.nickname}/followers")
1718 |> json_response(200)
1720 assert result["first"]["orderedItems"] == [user.ap_id]
1723 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1724 user = insert(:user)
1725 user_two = insert(:user, hide_followers: true)
1726 User.follow(user, user_two)
1730 |> assign(:user, user)
1731 |> get("/users/#{user_two.nickname}/followers")
1732 |> json_response(200)
1734 assert is_binary(result["first"])
1737 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1739 user = insert(:user)
1740 other_user = insert(:user, hide_followers: true)
1744 |> assign(:user, user)
1745 |> get("/users/#{other_user.nickname}/followers?page=1")
1747 assert result.status == 403
1748 assert result.resp_body == ""
1751 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1753 user = insert(:user, hide_followers: true)
1754 other_user = insert(:user)
1755 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1759 |> assign(:user, user)
1760 |> get("/users/#{user.nickname}/followers?page=1")
1761 |> json_response(200)
1763 assert result["totalItems"] == 1
1764 assert result["orderedItems"] == [other_user.ap_id]
1767 test "it works for more than 10 users", %{conn: conn} do
1768 user = insert(:user)
1770 Enum.each(1..15, fn _ ->
1771 other_user = insert(:user)
1772 User.follow(other_user, user)
1777 |> assign(:user, user)
1778 |> get("/users/#{user.nickname}/followers")
1779 |> json_response(200)
1781 assert length(result["first"]["orderedItems"]) == 10
1782 assert result["first"]["totalItems"] == 15
1783 assert result["totalItems"] == 15
1787 |> assign(:user, user)
1788 |> get("/users/#{user.nickname}/followers?page=2")
1789 |> json_response(200)
1791 assert length(result["orderedItems"]) == 5
1792 assert result["totalItems"] == 15
1795 test "does not require authentication", %{conn: conn} do
1796 user = insert(:user)
1799 |> get("/users/#{user.nickname}/followers")
1800 |> json_response(200)
1804 describe "/users/:nickname/following" do
1805 test "it returns the following in a collection", %{conn: conn} do
1806 user = insert(:user)
1807 user_two = insert(:user)
1808 User.follow(user, user_two)
1812 |> assign(:user, user)
1813 |> get("/users/#{user.nickname}/following")
1814 |> json_response(200)
1816 assert result["first"]["orderedItems"] == [user_two.ap_id]
1819 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1820 user = insert(:user)
1821 user_two = insert(:user, hide_follows: true)
1822 User.follow(user, user_two)
1826 |> assign(:user, user)
1827 |> get("/users/#{user_two.nickname}/following")
1828 |> json_response(200)
1830 assert is_binary(result["first"])
1833 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1835 user = insert(:user)
1836 user_two = insert(:user, hide_follows: true)
1840 |> assign(:user, user)
1841 |> get("/users/#{user_two.nickname}/following?page=1")
1843 assert result.status == 403
1844 assert result.resp_body == ""
1847 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1849 user = insert(:user, hide_follows: true)
1850 other_user = insert(:user)
1851 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1855 |> assign(:user, user)
1856 |> get("/users/#{user.nickname}/following?page=1")
1857 |> json_response(200)
1859 assert result["totalItems"] == 1
1860 assert result["orderedItems"] == [other_user.ap_id]
1863 test "it works for more than 10 users", %{conn: conn} do
1864 user = insert(:user)
1866 Enum.each(1..15, fn _ ->
1867 user = User.get_cached_by_id(user.id)
1868 other_user = insert(:user)
1869 User.follow(user, other_user)
1874 |> assign(:user, user)
1875 |> get("/users/#{user.nickname}/following")
1876 |> json_response(200)
1878 assert length(result["first"]["orderedItems"]) == 10
1879 assert result["first"]["totalItems"] == 15
1880 assert result["totalItems"] == 15
1884 |> assign(:user, user)
1885 |> get("/users/#{user.nickname}/following?page=2")
1886 |> json_response(200)
1888 assert length(result["orderedItems"]) == 5
1889 assert result["totalItems"] == 15
1892 test "does not require authentication", %{conn: conn} do
1893 user = insert(:user)
1896 |> get("/users/#{user.nickname}/following")
1897 |> json_response(200)
1901 describe "delivery tracking" do
1902 test "it tracks a signed object fetch", %{conn: conn} do
1903 user = insert(:user, local: false)
1904 activity = insert(:note_activity)
1905 object = Object.normalize(activity, fetch: false)
1907 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1910 |> put_req_header("accept", "application/activity+json")
1911 |> assign(:user, user)
1913 |> json_response(200)
1915 assert Delivery.get(object.id, user.id)
1918 test "it tracks a signed activity fetch", %{conn: conn} do
1919 user = insert(:user, local: false)
1920 activity = insert(:note_activity)
1921 object = Object.normalize(activity, fetch: false)
1923 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1926 |> put_req_header("accept", "application/activity+json")
1927 |> assign(:user, user)
1928 |> get(activity_path)
1929 |> json_response(200)
1931 assert Delivery.get(object.id, user.id)
1934 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1935 user = insert(:user, local: false)
1936 other_user = insert(:user, local: false)
1937 activity = insert(:note_activity)
1938 object = Object.normalize(activity, fetch: false)
1940 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1943 |> put_req_header("accept", "application/activity+json")
1944 |> assign(:user, user)
1946 |> json_response(200)
1949 |> put_req_header("accept", "application/activity+json")
1950 |> assign(:user, other_user)
1952 |> json_response(200)
1954 assert Delivery.get(object.id, user.id)
1955 assert Delivery.get(object.id, other_user.id)
1958 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1959 user = insert(:user, local: false)
1960 other_user = insert(:user, local: false)
1961 activity = insert(:note_activity)
1962 object = Object.normalize(activity, fetch: false)
1964 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1967 |> put_req_header("accept", "application/activity+json")
1968 |> assign(:user, user)
1969 |> get(activity_path)
1970 |> json_response(200)
1973 |> put_req_header("accept", "application/activity+json")
1974 |> assign(:user, other_user)
1975 |> get(activity_path)
1976 |> json_response(200)
1978 assert Delivery.get(object.id, user.id)
1979 assert Delivery.get(object.id, other_user.id)
1983 describe "Additional ActivityPub C2S endpoints" do
1984 test "GET /api/ap/whoami", %{conn: conn} do
1985 user = insert(:user)
1989 |> assign(:user, user)
1990 |> get("/api/ap/whoami")
1992 user = User.get_cached_by_id(user.id)
1994 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1997 |> get("/api/ap/whoami")
1998 |> json_response(403)
2001 setup do: clear_config([:media_proxy])
2002 setup do: clear_config([Pleroma.Upload])
2004 test "POST /api/ap/upload_media", %{conn: conn} do
2005 user = insert(:user)
2007 desc = "Description of the image"
2009 image = %Plug.Upload{
2010 content_type: "image/jpeg",
2011 path: Path.absname("test/fixtures/image.jpg"),
2012 filename: "an_image.jpg"
2017 |> assign(:user, user)
2018 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2019 |> json_response(:created)
2021 assert object["name"] == desc
2022 assert object["type"] == "Document"
2023 assert object["actor"] == user.ap_id
2024 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2025 assert is_binary(object_href)
2026 assert object_mediatype == "image/jpeg"
2027 assert String.ends_with?(object_href, ".jpg")
2029 activity_request = %{
2030 "@context" => "https://www.w3.org/ns/activitystreams",
2034 "content" => "AP C2S test, attachment",
2035 "attachment" => [object],
2036 "to" => "https://www.w3.org/ns/activitystreams#Public",
2043 |> assign(:user, user)
2044 |> post("/users/#{user.nickname}/outbox", activity_request)
2045 |> json_response(:created)
2047 assert activity_response["id"]
2048 assert activity_response["object"]
2049 assert activity_response["actor"] == user.ap_id
2051 assert %Object{data: %{"attachment" => [attachment]}} =
2052 Object.normalize(activity_response["object"], fetch: false)
2054 assert attachment["type"] == "Document"
2055 assert attachment["name"] == desc
2059 "href" => ^object_href,
2061 "mediaType" => ^object_mediatype
2063 ] = attachment["url"]
2065 # Fails if unauthenticated
2067 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2068 |> json_response(403)
2072 test "pinned collection", %{conn: conn} do
2073 clear_config([:instance, :max_pinned_statuses], 2)
2074 user = insert(:user)
2075 objects = insert_list(2, :note, user: user)
2077 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2078 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2082 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2083 refresh_record(user)
2085 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2087 |> get("/users/#{nickname}/collections/featured")
2088 |> json_response(200)
2090 object_ids = Enum.map(items, & &1["id"])
2092 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2093 obj_id in object_ids