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], true)
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)
564 "keyId=\"http://mastodon.example.org/users/admin/main-key\""
566 |> put_req_header("content-type", "application/activity+json")
567 |> post("/inbox", data)
569 assert "ok" == json_response(conn, 200)
571 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
572 assert Activity.get_by_ap_id(data["id"])
575 @tag capture_log: true
576 test "it inserts an incoming activity into the database" <>
577 "even if we can't fetch the user but have it in our db",
581 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("signature", "keyId=\"#{user.ap_id}/main-key\"")
597 |> put_req_header("content-type", "application/activity+json")
598 |> post("/inbox", data)
600 assert "ok" == json_response(conn, 200)
602 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
603 assert Activity.get_by_ap_id(data["id"])
606 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
607 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
609 sender_url = data["actor"]
610 sender = insert(:user, ap_id: data["actor"])
612 Instances.set_consistently_unreachable(sender_url)
613 refute Instances.reachable?(sender_url)
617 |> assign(:valid_signature, true)
618 |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
619 |> put_req_header("content-type", "application/activity+json")
620 |> post("/inbox", data)
622 assert "ok" == json_response(conn, 200)
623 assert Instances.reachable?(sender_url)
626 test "accept follow activity", %{conn: conn} do
627 clear_config([:instance, :federating], true)
628 relay = Relay.get_actor()
630 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
632 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
633 relay = refresh_record(relay)
636 File.read!("test/fixtures/relay/accept-follow.json")
637 |> String.replace("{{ap_id}}", relay.ap_id)
638 |> String.replace("{{activity_id}}", activity.data["id"])
642 |> assign(:valid_signature, true)
643 |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
644 |> put_req_header("content-type", "application/activity+json")
645 |> post("/inbox", accept)
646 |> json_response(200)
648 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
650 assert Pleroma.FollowingRelationship.following?(
655 Mix.shell(Mix.Shell.Process)
658 Mix.shell(Mix.Shell.IO)
661 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
662 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
665 test "accepts Add/Remove activities", %{conn: conn} do
666 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
669 File.read!("test/fixtures/statuses/note.json")
670 |> String.replace("{{nickname}}", "lain")
671 |> String.replace("{{object_id}}", object_id)
673 object_url = "https://example.com/objects/#{object_id}"
676 File.read!("test/fixtures/users_mock/user.json")
677 |> String.replace("{{nickname}}", "lain")
679 actor = "https://example.com/users/lain"
683 featured_address: "https://example.com/users/lain/collections/featured"
694 headers: [{"content-type", "application/activity+json"}]
704 headers: [{"content-type", "application/activity+json"}]
707 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
711 "test/fixtures/users_mock/masto_featured.json"
713 |> String.replace("{{domain}}", "example.com")
714 |> String.replace("{{nickname}}", "lain"),
715 headers: [{"content-type", "application/activity+json"}]
720 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
722 "object" => object_url,
723 "target" => "https://example.com/users/lain/collections/featured",
725 "to" => [Pleroma.Constants.as_public()]
730 |> assign(:valid_signature, true)
731 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
732 |> put_req_header("content-type", "application/activity+json")
733 |> post("/inbox", data)
734 |> json_response(200)
736 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
737 assert Activity.get_by_ap_id(data["id"])
738 user = User.get_cached_by_ap_id(data["actor"])
740 assert user.pinned_objects[data["object"]]
743 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
745 "object" => object_url,
746 "target" => "https://example.com/users/lain/collections/featured",
748 "to" => [Pleroma.Constants.as_public()]
753 |> assign(:valid_signature, true)
754 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
755 |> put_req_header("content-type", "application/activity+json")
756 |> post("/inbox", data)
757 |> json_response(200)
759 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
760 user = refresh_record(user)
761 refute user.pinned_objects[data["object"]]
764 test "mastodon pin/unpin", %{conn: conn} do
765 status_id = "105786274556060421"
768 File.read!("test/fixtures/statuses/masto-note.json")
769 |> String.replace("{{nickname}}", "lain")
770 |> String.replace("{{status_id}}", status_id)
772 status_url = "https://example.com/users/lain/statuses/#{status_id}"
773 replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
776 File.read!("test/fixtures/users_mock/user.json")
777 |> String.replace("{{nickname}}", "lain")
779 actor = "https://example.com/users/lain"
784 featured_address: "https://example.com/users/lain/collections/featured"
795 headers: [{"content-type", "application/activity+json"}]
805 headers: [{"content-type", "application/activity+json"}]
808 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
812 "test/fixtures/users_mock/masto_featured.json"
814 |> String.replace("{{domain}}", "example.com")
815 |> String.replace("{{nickname}}", "lain"),
816 headers: [{"content-type", "application/activity+json"}]
826 headers: [{"content-type", "application/activity+json"}]
831 "@context" => "https://www.w3.org/ns/activitystreams",
833 "object" => status_url,
834 "target" => "https://example.com/users/lain/collections/featured",
840 |> assign(:valid_signature, true)
841 |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
842 |> put_req_header("content-type", "application/activity+json")
843 |> post("/inbox", data)
844 |> json_response(200)
846 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
847 assert Activity.get_by_object_ap_id_with_object(data["object"])
848 user = User.get_cached_by_ap_id(data["actor"])
849 assert user.pinned_objects[data["object"]]
853 "object" => status_url,
854 "target" => "https://example.com/users/lain/collections/featured",
860 |> assign(:valid_signature, true)
861 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
862 |> put_req_header("content-type", "application/activity+json")
863 |> post("/inbox", data)
864 |> json_response(200)
866 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
867 assert Activity.get_by_object_ap_id_with_object(data["object"])
868 user = refresh_record(user)
869 refute user.pinned_objects[data["object"]]
873 describe "/users/:nickname/inbox" do
876 File.read!("test/fixtures/mastodon-post-activity.json")
882 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
887 |> Map.put("bcc", [user.ap_id])
888 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
892 |> assign(:valid_signature, true)
893 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
894 |> put_req_header("content-type", "application/activity+json")
895 |> post("/users/#{user.nickname}/inbox", data)
897 assert "ok" == json_response(conn, 200)
898 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
899 assert Activity.get_by_ap_id(data["id"])
902 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
907 |> Map.put("to", user.ap_id)
909 |> Kernel.put_in(["object", "to"], user.ap_id)
910 |> Kernel.put_in(["object", "cc"], [])
914 |> assign(:valid_signature, true)
915 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
916 |> put_req_header("content-type", "application/activity+json")
917 |> post("/users/#{user.nickname}/inbox", data)
919 assert "ok" == json_response(conn, 200)
920 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
921 assert Activity.get_by_ap_id(data["id"])
924 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
930 |> Map.put("cc", user.ap_id)
931 |> Kernel.put_in(["object", "to"], [])
932 |> Kernel.put_in(["object", "cc"], user.ap_id)
936 |> assign(:valid_signature, true)
937 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
938 |> put_req_header("content-type", "application/activity+json")
939 |> post("/users/#{user.nickname}/inbox", data)
941 assert "ok" == json_response(conn, 200)
942 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
943 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
944 assert user.ap_id in activity.recipients
947 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
954 |> Map.put("bcc", user.ap_id)
955 |> Kernel.put_in(["object", "to"], [])
956 |> Kernel.put_in(["object", "cc"], [])
957 |> Kernel.put_in(["object", "bcc"], user.ap_id)
961 |> assign(:valid_signature, true)
962 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
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("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
991 |> put_req_header("content-type", "application/activity+json")
992 |> post("/users/#{user.nickname}/inbox", data)
994 assert "ok" == json_response(conn, 200)
995 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
996 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
997 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
1000 test "it accepts messages from actors that are followed by the user", %{
1004 recipient = insert(:user)
1005 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
1007 {:ok, recipient, actor} = User.follow(recipient, actor)
1011 |> Map.put("attributedTo", actor.ap_id)
1015 |> Map.put("actor", actor.ap_id)
1016 |> Map.put("object", object)
1020 |> assign(:valid_signature, true)
1021 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1022 |> put_req_header("content-type", "application/activity+json")
1023 |> post("/users/#{recipient.nickname}/inbox", data)
1025 assert "ok" == json_response(conn, 200)
1026 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1027 assert Activity.get_by_ap_id(data["id"])
1030 test "it rejects reads from other users", %{conn: conn} do
1031 user = insert(:user)
1032 other_user = insert(:user)
1036 |> assign(:user, other_user)
1037 |> put_req_header("accept", "application/activity+json")
1038 |> get("/users/#{user.nickname}/inbox")
1040 assert json_response(conn, 403)
1043 test "it returns a note activity in a collection", %{conn: conn} do
1044 note_activity = insert(:direct_note_activity)
1045 note_object = Object.normalize(note_activity, fetch: false)
1046 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1050 |> assign(:user, user)
1051 |> put_req_header("accept", "application/activity+json")
1052 |> get("/users/#{user.nickname}/inbox?page=true")
1054 assert response(conn, 200) =~ note_object.data["content"]
1057 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1058 user = insert(:user)
1059 data = Map.put(data, "bcc", [user.ap_id])
1061 sender_host = URI.parse(data["actor"]).host
1062 Instances.set_consistently_unreachable(sender_host)
1063 refute Instances.reachable?(sender_host)
1067 |> assign(:valid_signature, true)
1068 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
1069 |> put_req_header("content-type", "application/activity+json")
1070 |> post("/users/#{user.nickname}/inbox", data)
1072 assert "ok" == json_response(conn, 200)
1073 assert Instances.reachable?(sender_host)
1076 @tag capture_log: true
1077 test "it removes all follower collections but actor's", %{conn: conn} do
1078 [actor, recipient] = insert_pair(:user)
1082 recipient.follower_address,
1083 "https://www.w3.org/ns/activitystreams#Public"
1086 cc = [recipient.follower_address, actor.follower_address]
1089 "@context" => ["https://www.w3.org/ns/activitystreams"],
1091 "id" => Utils.generate_activity_id(),
1094 "actor" => actor.ap_id,
1099 "content" => "It's a note",
1100 "attributedTo" => actor.ap_id,
1101 "id" => Utils.generate_object_id()
1106 |> assign(:valid_signature, true)
1107 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1108 |> put_req_header("content-type", "application/activity+json")
1109 |> post("/users/#{recipient.nickname}/inbox", data)
1110 |> json_response(200)
1112 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1114 assert activity = Activity.get_by_ap_id(data["id"])
1117 assert actor.follower_address in activity.recipients
1118 assert actor.follower_address in activity.data["cc"]
1120 refute recipient.follower_address in activity.recipients
1121 refute recipient.follower_address in activity.data["cc"]
1122 refute recipient.follower_address in activity.data["to"]
1125 test "it requires authentication", %{conn: conn} do
1126 user = insert(:user)
1127 conn = put_req_header(conn, "accept", "application/activity+json")
1129 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1130 assert json_response(ret_conn, 403)
1134 |> assign(:user, user)
1135 |> get("/users/#{user.nickname}/inbox")
1137 assert json_response(ret_conn, 200)
1140 @tag capture_log: true
1141 test "forwarded report", %{conn: conn} do
1142 admin = insert(:user, is_admin: true)
1143 actor = insert(:user, local: false)
1144 remote_domain = URI.parse(actor.ap_id).host
1145 reported_user = insert(:user)
1147 note = insert(:note_activity, user: reported_user)
1151 "https://www.w3.org/ns/activitystreams",
1152 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1154 "@language" => "und"
1157 "actor" => actor.ap_id,
1161 "content" => "test",
1162 "context" => "context",
1163 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1164 "nickname" => reported_user.nickname,
1166 reported_user.ap_id,
1169 "actor_type" => "Person",
1170 "approval_pending" => false,
1172 "confirmation_pending" => false,
1173 "deactivated" => false,
1174 "display_name" => "test user",
1175 "id" => reported_user.id,
1177 "nickname" => reported_user.nickname,
1178 "registration_reason" => nil,
1181 "moderator" => false
1184 "url" => reported_user.ap_id
1187 "id" => note.data["id"],
1188 "published" => note.data["published"],
1192 "published" => note.data["published"],
1199 |> assign(:valid_signature, true)
1200 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1201 |> put_req_header("content-type", "application/activity+json")
1202 |> post("/users/#{reported_user.nickname}/inbox", data)
1203 |> json_response(200)
1205 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1207 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1209 ObanHelpers.perform_all()
1211 Swoosh.TestAssertions.assert_email_sent(
1212 to: {admin.name, admin.email},
1213 html_body: ~r/Reported Account:/i
1217 @tag capture_log: true
1218 test "forwarded report from mastodon", %{conn: conn} do
1219 admin = insert(:user, is_admin: true)
1220 actor = insert(:user, local: false)
1221 remote_domain = URI.parse(actor.ap_id).host
1222 remote_actor = "https://#{remote_domain}/actor"
1223 [reported_user, another] = insert_list(2, :user)
1225 note = insert(:note_activity, user: reported_user)
1227 Pleroma.Web.CommonAPI.favorite(another, note.id)
1230 "test/fixtures/mastodon/application_actor.json"
1232 |> String.replace("{{DOMAIN}}", remote_domain)
1234 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1237 body: mock_json_body,
1238 headers: [{"content-type", "application/activity+json"}]
1243 "@context" => "https://www.w3.org/ns/activitystreams",
1244 "actor" => remote_actor,
1245 "content" => "test report",
1246 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1248 reported_user.ap_id,
1255 |> assign(:valid_signature, true)
1256 |> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
1257 |> put_req_header("content-type", "application/activity+json")
1258 |> post("/users/#{reported_user.nickname}/inbox", data)
1259 |> json_response(200)
1261 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1263 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1264 reported_user_ap_id = reported_user.ap_id
1266 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1268 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1269 ObanHelpers.perform_all()
1271 Swoosh.TestAssertions.assert_email_sent(
1272 to: {admin.name, admin.email},
1273 html_body: ~r/#{note.data["object"]}/i
1278 describe "GET /users/:nickname/outbox" do
1279 test "it paginates correctly", %{conn: conn} do
1280 user = insert(:user)
1281 conn = assign(conn, :user, user)
1282 outbox_endpoint = user.ap_id <> "/outbox"
1286 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1292 |> put_req_header("accept", "application/activity+json")
1293 |> get(outbox_endpoint <> "?page=true")
1294 |> json_response(200)
1296 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1297 assert length(result["orderedItems"]) == 20
1298 assert length(result_ids) == 20
1299 assert result["next"]
1300 assert String.starts_with?(result["next"], outbox_endpoint)
1304 |> put_req_header("accept", "application/activity+json")
1305 |> get(result["next"])
1306 |> json_response(200)
1308 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1309 assert length(result_next["orderedItems"]) == 6
1310 assert length(result_next_ids) == 6
1311 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1312 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1313 assert String.starts_with?(result["id"], outbox_endpoint)
1317 |> put_req_header("accept", "application/activity+json")
1318 |> get(result_next["id"])
1319 |> json_response(200)
1321 assert result_next == result_next_again
1324 test "it returns 200 even if there're no activities", %{conn: conn} do
1325 user = insert(:user)
1326 outbox_endpoint = user.ap_id <> "/outbox"
1330 |> assign(:user, user)
1331 |> put_req_header("accept", "application/activity+json")
1332 |> get(outbox_endpoint)
1334 result = json_response(conn, 200)
1335 assert outbox_endpoint == result["id"]
1338 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1339 user = insert(:user)
1340 reader = insert(:user)
1341 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1342 ap_id = note_activity.data["id"]
1346 |> assign(:user, reader)
1347 |> put_req_header("accept", "application/activity+json")
1348 |> get("/users/#{user.nickname}/outbox?page=true")
1349 |> json_response(200)
1351 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1354 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1355 user = insert(:user)
1356 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1360 |> put_req_header("accept", "application/activity+json")
1361 |> get("/users/#{user.nickname}/outbox?page=true")
1362 |> json_response(200)
1364 assert %{"orderedItems" => []} = resp
1367 test "it returns a note activity in a collection", %{conn: conn} do
1368 note_activity = insert(:note_activity)
1369 note_object = Object.normalize(note_activity, fetch: false)
1370 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1374 |> assign(:user, user)
1375 |> put_req_header("accept", "application/activity+json")
1376 |> get("/users/#{user.nickname}/outbox?page=true")
1378 assert response(conn, 200) =~ note_object.data["content"]
1381 test "it returns an announce activity in a collection", %{conn: conn} do
1382 announce_activity = insert(:announce_activity)
1383 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1387 |> assign(:user, user)
1388 |> put_req_header("accept", "application/activity+json")
1389 |> get("/users/#{user.nickname}/outbox?page=true")
1391 assert response(conn, 200) =~ announce_activity.data["object"]
1394 test "It returns poll Answers when authenticated", %{conn: conn} do
1395 poller = insert(:user)
1396 voter = insert(:user)
1399 CommonAPI.post(poller, %{
1401 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1404 assert question = Object.normalize(activity, fetch: false)
1406 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1410 |> assign(:user, voter)
1411 |> put_req_header("accept", "application/activity+json")
1412 |> get(voter.ap_id <> "/outbox?page=true")
1413 |> json_response(200)
1415 assert [answer_outbox] = outbox_get["orderedItems"]
1416 assert answer_outbox["id"] == activity.data["id"]
1420 describe "POST /users/:nickname/outbox (C2S)" do
1421 setup do: clear_config([:instance, :limit])
1426 "@context" => "https://www.w3.org/ns/activitystreams",
1430 "content" => "AP C2S test",
1431 "to" => "https://www.w3.org/ns/activitystreams#Public",
1438 test "it rejects posts from other users / unauthenticated users", %{
1442 user = insert(:user)
1443 other_user = insert(:user)
1444 conn = put_req_header(conn, "content-type", "application/activity+json")
1447 |> post("/users/#{user.nickname}/outbox", activity)
1448 |> json_response(403)
1451 |> assign(:user, other_user)
1452 |> post("/users/#{user.nickname}/outbox", activity)
1453 |> json_response(403)
1456 test "it inserts an incoming create activity into the database", %{
1460 user = insert(:user)
1464 |> assign(:user, user)
1465 |> put_req_header("content-type", "application/activity+json")
1466 |> post("/users/#{user.nickname}/outbox", activity)
1467 |> json_response(201)
1469 assert Activity.get_by_ap_id(result["id"])
1470 assert result["object"]
1471 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1472 assert object["content"] == activity["object"]["content"]
1475 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1476 user = insert(:user)
1480 |> put_in(["object", "type"], "Benis")
1484 |> assign(:user, user)
1485 |> put_req_header("content-type", "application/activity+json")
1486 |> post("/users/#{user.nickname}/outbox", activity)
1487 |> json_response(400)
1490 test "it inserts an incoming sensitive activity into the database", %{
1494 user = insert(:user)
1495 conn = assign(conn, :user, user)
1496 object = Map.put(activity["object"], "sensitive", true)
1497 activity = Map.put(activity, "object", object)
1501 |> put_req_header("content-type", "application/activity+json")
1502 |> post("/users/#{user.nickname}/outbox", activity)
1503 |> json_response(201)
1505 assert Activity.get_by_ap_id(response["id"])
1506 assert response["object"]
1507 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1508 assert response_object["sensitive"] == true
1509 assert response_object["content"] == activity["object"]["content"]
1513 |> put_req_header("accept", "application/activity+json")
1514 |> get(response["id"])
1515 |> json_response(200)
1517 assert representation["object"]["sensitive"] == true
1520 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1521 user = insert(:user)
1522 activity = Map.put(activity, "type", "BadType")
1526 |> assign(:user, user)
1527 |> put_req_header("content-type", "application/activity+json")
1528 |> post("/users/#{user.nickname}/outbox", activity)
1530 assert json_response(conn, 400)
1533 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1534 note_activity = insert(:note_activity)
1535 note_object = Object.normalize(note_activity, fetch: false)
1536 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1541 "id" => note_object.data["id"]
1547 |> assign(:user, user)
1548 |> put_req_header("content-type", "application/activity+json")
1549 |> post("/users/#{user.nickname}/outbox", data)
1550 |> json_response(201)
1552 assert Activity.get_by_ap_id(result["id"])
1554 assert object = Object.get_by_ap_id(note_object.data["id"])
1555 assert object.data["type"] == "Tombstone"
1558 test "it rejects delete activity of object from other actor", %{conn: conn} do
1559 note_activity = insert(:note_activity)
1560 note_object = Object.normalize(note_activity, fetch: false)
1561 user = insert(:user)
1566 id: note_object.data["id"]
1572 |> assign(:user, user)
1573 |> put_req_header("content-type", "application/activity+json")
1574 |> post("/users/#{user.nickname}/outbox", data)
1576 assert json_response(conn, 403)
1579 test "it increases like count when receiving a like action", %{conn: conn} do
1580 note_activity = insert(:note_activity)
1581 note_object = Object.normalize(note_activity, fetch: false)
1582 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1587 id: note_object.data["id"]
1593 |> assign(:user, user)
1594 |> put_req_header("content-type", "application/activity+json")
1595 |> post("/users/#{user.nickname}/outbox", data)
1597 result = json_response(conn, 201)
1598 assert Activity.get_by_ap_id(result["id"])
1600 assert object = Object.get_by_ap_id(note_object.data["id"])
1601 assert object.data["like_count"] == 1
1604 test "it doesn't spreads faulty attributedTo or actor fields", %{
1608 reimu = insert(:user, nickname: "reimu")
1609 cirno = insert(:user, nickname: "cirno")
1616 |> put_in(["object", "actor"], reimu.ap_id)
1617 |> put_in(["object", "attributedTo"], reimu.ap_id)
1618 |> put_in(["actor"], reimu.ap_id)
1619 |> put_in(["attributedTo"], reimu.ap_id)
1623 |> assign(:user, cirno)
1624 |> put_req_header("content-type", "application/activity+json")
1625 |> post("/users/#{reimu.nickname}/outbox", activity)
1626 |> json_response(403)
1630 |> assign(:user, cirno)
1631 |> put_req_header("content-type", "application/activity+json")
1632 |> post("/users/#{cirno.nickname}/outbox", activity)
1633 |> json_response(201)
1635 assert cirno_outbox["attributedTo"] == nil
1636 assert cirno_outbox["actor"] == cirno.ap_id
1638 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1639 assert cirno_object.data["actor"] == cirno.ap_id
1640 assert cirno_object.data["attributedTo"] == cirno.ap_id
1643 test "Character limitation", %{conn: conn, activity: activity} do
1644 clear_config([:instance, :limit], 5)
1645 user = insert(:user)
1649 |> assign(:user, user)
1650 |> put_req_header("content-type", "application/activity+json")
1651 |> post("/users/#{user.nickname}/outbox", activity)
1652 |> json_response(400)
1654 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1658 describe "/relay/followers" do
1659 test "it returns relay followers", %{conn: conn} do
1660 relay_actor = Relay.get_actor()
1661 user = insert(:user)
1662 User.follow(user, relay_actor)
1666 |> get("/relay/followers")
1667 |> json_response(200)
1669 assert result["first"]["orderedItems"] == [user.ap_id]
1672 test "on non-federating instance, it returns 404", %{conn: conn} do
1673 clear_config([:instance, :federating], false)
1674 user = insert(:user)
1677 |> assign(:user, user)
1678 |> get("/relay/followers")
1679 |> json_response(404)
1683 describe "/relay/following" do
1684 test "it returns relay following", %{conn: conn} do
1687 |> get("/relay/following")
1688 |> json_response(200)
1690 assert result["first"]["orderedItems"] == []
1693 test "on non-federating instance, it returns 404", %{conn: conn} do
1694 clear_config([:instance, :federating], false)
1695 user = insert(:user)
1698 |> assign(:user, user)
1699 |> get("/relay/following")
1700 |> json_response(404)
1704 describe "/users/:nickname/followers" do
1705 test "it returns the followers in a collection", %{conn: conn} do
1706 user = insert(:user)
1707 user_two = insert(:user)
1708 User.follow(user, user_two)
1712 |> assign(:user, user_two)
1713 |> get("/users/#{user_two.nickname}/followers")
1714 |> json_response(200)
1716 assert result["first"]["orderedItems"] == [user.ap_id]
1719 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1720 user = insert(:user)
1721 user_two = insert(:user, hide_followers: true)
1722 User.follow(user, user_two)
1726 |> assign(:user, user)
1727 |> get("/users/#{user_two.nickname}/followers")
1728 |> json_response(200)
1730 assert is_binary(result["first"])
1733 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1735 user = insert(:user)
1736 other_user = insert(:user, hide_followers: true)
1740 |> assign(:user, user)
1741 |> get("/users/#{other_user.nickname}/followers?page=1")
1743 assert result.status == 403
1744 assert result.resp_body == ""
1747 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1749 user = insert(:user, hide_followers: true)
1750 other_user = insert(:user)
1751 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1755 |> assign(:user, user)
1756 |> get("/users/#{user.nickname}/followers?page=1")
1757 |> json_response(200)
1759 assert result["totalItems"] == 1
1760 assert result["orderedItems"] == [other_user.ap_id]
1763 test "it works for more than 10 users", %{conn: conn} do
1764 user = insert(:user)
1766 Enum.each(1..15, fn _ ->
1767 other_user = insert(:user)
1768 User.follow(other_user, user)
1773 |> assign(:user, user)
1774 |> get("/users/#{user.nickname}/followers")
1775 |> json_response(200)
1777 assert length(result["first"]["orderedItems"]) == 10
1778 assert result["first"]["totalItems"] == 15
1779 assert result["totalItems"] == 15
1783 |> assign(:user, user)
1784 |> get("/users/#{user.nickname}/followers?page=2")
1785 |> json_response(200)
1787 assert length(result["orderedItems"]) == 5
1788 assert result["totalItems"] == 15
1791 test "does not require authentication", %{conn: conn} do
1792 user = insert(:user)
1795 |> get("/users/#{user.nickname}/followers")
1796 |> json_response(200)
1800 describe "/users/:nickname/following" do
1801 test "it returns the following in a collection", %{conn: conn} do
1802 user = insert(:user)
1803 user_two = insert(:user)
1804 User.follow(user, user_two)
1808 |> assign(:user, user)
1809 |> get("/users/#{user.nickname}/following")
1810 |> json_response(200)
1812 assert result["first"]["orderedItems"] == [user_two.ap_id]
1815 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1816 user = insert(:user)
1817 user_two = insert(:user, hide_follows: true)
1818 User.follow(user, user_two)
1822 |> assign(:user, user)
1823 |> get("/users/#{user_two.nickname}/following")
1824 |> json_response(200)
1826 assert is_binary(result["first"])
1829 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1831 user = insert(:user)
1832 user_two = insert(:user, hide_follows: true)
1836 |> assign(:user, user)
1837 |> get("/users/#{user_two.nickname}/following?page=1")
1839 assert result.status == 403
1840 assert result.resp_body == ""
1843 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1845 user = insert(:user, hide_follows: true)
1846 other_user = insert(:user)
1847 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1851 |> assign(:user, user)
1852 |> get("/users/#{user.nickname}/following?page=1")
1853 |> json_response(200)
1855 assert result["totalItems"] == 1
1856 assert result["orderedItems"] == [other_user.ap_id]
1859 test "it works for more than 10 users", %{conn: conn} do
1860 user = insert(:user)
1862 Enum.each(1..15, fn _ ->
1863 user = User.get_cached_by_id(user.id)
1864 other_user = insert(:user)
1865 User.follow(user, other_user)
1870 |> assign(:user, user)
1871 |> get("/users/#{user.nickname}/following")
1872 |> json_response(200)
1874 assert length(result["first"]["orderedItems"]) == 10
1875 assert result["first"]["totalItems"] == 15
1876 assert result["totalItems"] == 15
1880 |> assign(:user, user)
1881 |> get("/users/#{user.nickname}/following?page=2")
1882 |> json_response(200)
1884 assert length(result["orderedItems"]) == 5
1885 assert result["totalItems"] == 15
1888 test "does not require authentication", %{conn: conn} do
1889 user = insert(:user)
1892 |> get("/users/#{user.nickname}/following")
1893 |> json_response(200)
1897 describe "delivery tracking" do
1898 test "it tracks a signed object fetch", %{conn: conn} do
1899 user = insert(:user, local: false)
1900 activity = insert(:note_activity)
1901 object = Object.normalize(activity, fetch: false)
1903 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1906 |> put_req_header("accept", "application/activity+json")
1907 |> assign(:user, user)
1909 |> json_response(200)
1911 assert Delivery.get(object.id, user.id)
1914 test "it tracks a signed activity fetch", %{conn: conn} do
1915 user = insert(:user, local: false)
1916 activity = insert(:note_activity)
1917 object = Object.normalize(activity, fetch: false)
1919 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1922 |> put_req_header("accept", "application/activity+json")
1923 |> assign(:user, user)
1924 |> get(activity_path)
1925 |> json_response(200)
1927 assert Delivery.get(object.id, user.id)
1930 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1931 user = insert(:user, local: false)
1932 other_user = insert(:user, local: false)
1933 activity = insert(:note_activity)
1934 object = Object.normalize(activity, fetch: false)
1936 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1939 |> put_req_header("accept", "application/activity+json")
1940 |> assign(:user, user)
1942 |> json_response(200)
1945 |> put_req_header("accept", "application/activity+json")
1946 |> assign(:user, other_user)
1948 |> json_response(200)
1950 assert Delivery.get(object.id, user.id)
1951 assert Delivery.get(object.id, other_user.id)
1954 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1955 user = insert(:user, local: false)
1956 other_user = insert(:user, local: false)
1957 activity = insert(:note_activity)
1958 object = Object.normalize(activity, fetch: false)
1960 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1963 |> put_req_header("accept", "application/activity+json")
1964 |> assign(:user, user)
1965 |> get(activity_path)
1966 |> json_response(200)
1969 |> put_req_header("accept", "application/activity+json")
1970 |> assign(:user, other_user)
1971 |> get(activity_path)
1972 |> json_response(200)
1974 assert Delivery.get(object.id, user.id)
1975 assert Delivery.get(object.id, other_user.id)
1979 describe "Additional ActivityPub C2S endpoints" do
1980 test "GET /api/ap/whoami", %{conn: conn} do
1981 user = insert(:user)
1985 |> assign(:user, user)
1986 |> get("/api/ap/whoami")
1988 user = User.get_cached_by_id(user.id)
1990 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1993 |> get("/api/ap/whoami")
1994 |> json_response(403)
1997 setup do: clear_config([:media_proxy])
1998 setup do: clear_config([Pleroma.Upload])
2000 test "POST /api/ap/upload_media", %{conn: conn} do
2001 user = insert(:user)
2003 desc = "Description of the image"
2005 image = %Plug.Upload{
2006 content_type: "image/jpeg",
2007 path: Path.absname("test/fixtures/image.jpg"),
2008 filename: "an_image.jpg"
2013 |> assign(:user, user)
2014 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2015 |> json_response(:created)
2017 assert object["name"] == desc
2018 assert object["type"] == "Document"
2019 assert object["actor"] == user.ap_id
2020 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2021 assert is_binary(object_href)
2022 assert object_mediatype == "image/jpeg"
2023 assert String.ends_with?(object_href, ".jpg")
2025 activity_request = %{
2026 "@context" => "https://www.w3.org/ns/activitystreams",
2030 "content" => "AP C2S test, attachment",
2031 "attachment" => [object],
2032 "to" => "https://www.w3.org/ns/activitystreams#Public",
2039 |> assign(:user, user)
2040 |> post("/users/#{user.nickname}/outbox", activity_request)
2041 |> json_response(:created)
2043 assert activity_response["id"]
2044 assert activity_response["object"]
2045 assert activity_response["actor"] == user.ap_id
2047 assert %Object{data: %{"attachment" => [attachment]}} =
2048 Object.normalize(activity_response["object"], fetch: false)
2050 assert attachment["type"] == "Document"
2051 assert attachment["name"] == desc
2055 "href" => ^object_href,
2057 "mediaType" => ^object_mediatype
2059 ] = attachment["url"]
2061 # Fails if unauthenticated
2063 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2064 |> json_response(403)
2068 test "pinned collection", %{conn: conn} do
2069 clear_config([:instance, :max_pinned_statuses], 2)
2070 user = insert(:user)
2071 objects = insert_list(2, :note, user: user)
2073 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2074 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2078 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2079 refresh_record(user)
2081 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2083 |> get("/users/#{nickname}/collections/featured")
2084 |> json_response(200)
2086 object_ids = Enum.map(items, & &1["id"])
2088 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2089 obj_id in object_ids