1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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
11 alias Pleroma.Delivery
12 alias Pleroma.Instances
14 alias Pleroma.Tests.ObanHelpers
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
24 import Pleroma.Factory
26 require Pleroma.Constants
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
33 setup do: clear_config([:instance, :federating], true)
36 setup do: clear_config([:instance, :allow_relay])
38 test "with the relay active, it returns the relay user", %{conn: conn} do
41 |> get(activity_pub_path(conn, :relay))
44 assert res["id"] =~ "/relay"
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 Config.put([:instance, :allow_relay], false)
51 |> get(activity_pub_path(conn, :relay))
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 Config.put([: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 Config.put([: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"
159 test "it requires authentication if instance is NOT federating", %{
168 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
171 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
175 describe "mastodon compatibility routes" do
176 test "it returns a json representation of the object with accept application/json", %{
183 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
184 "actor" => Endpoint.url() <> "/users/raymoo",
185 "to" => [Pleroma.Constants.as_public()]
191 |> put_req_header("accept", "application/json")
192 |> get("/users/raymoo/statuses/999999999")
194 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
198 describe "/objects/:uuid" do
199 test "it returns a json representation of the object with accept application/json", %{
203 uuid = String.split(note.data["id"], "/") |> List.last()
207 |> put_req_header("accept", "application/json")
208 |> get("/objects/#{uuid}")
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
213 test "it returns a json representation of the object with accept application/activity+json",
216 uuid = String.split(note.data["id"], "/") |> List.last()
220 |> put_req_header("accept", "application/activity+json")
221 |> get("/objects/#{uuid}")
223 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
226 test "it returns a json representation of the object with accept application/ld+json", %{
230 uuid = String.split(note.data["id"], "/") |> List.last()
236 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
238 |> get("/objects/#{uuid}")
240 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
243 test "it returns 404 for non-public messages", %{conn: conn} do
244 note = insert(:direct_note)
245 uuid = String.split(note.data["id"], "/") |> List.last()
249 |> put_req_header("accept", "application/activity+json")
250 |> get("/objects/#{uuid}")
252 assert json_response(conn, 404)
255 test "it returns 404 for tombstone objects", %{conn: conn} do
256 tombstone = insert(:tombstone)
257 uuid = String.split(tombstone.data["id"], "/") |> List.last()
261 |> put_req_header("accept", "application/activity+json")
262 |> get("/objects/#{uuid}")
264 assert json_response(conn, 404)
267 test "it caches a response", %{conn: conn} do
269 uuid = String.split(note.data["id"], "/") |> List.last()
273 |> put_req_header("accept", "application/activity+json")
274 |> get("/objects/#{uuid}")
276 assert json_response(conn1, :ok)
277 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
281 |> put_req_header("accept", "application/activity+json")
282 |> get("/objects/#{uuid}")
284 assert json_response(conn1, :ok) == json_response(conn2, :ok)
285 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
288 test "cached purged after object deletion", %{conn: conn} do
290 uuid = String.split(note.data["id"], "/") |> List.last()
294 |> put_req_header("accept", "application/activity+json")
295 |> get("/objects/#{uuid}")
297 assert json_response(conn1, :ok)
298 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
304 |> put_req_header("accept", "application/activity+json")
305 |> get("/objects/#{uuid}")
307 assert "Not found" == json_response(conn2, :not_found)
310 test "it requires authentication if instance is NOT federating", %{
315 uuid = String.split(note.data["id"], "/") |> List.last()
317 conn = put_req_header(conn, "accept", "application/activity+json")
319 ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
323 describe "/activities/:uuid" do
324 test "it returns a json representation of the activity", %{conn: conn} do
325 activity = insert(:note_activity)
326 uuid = String.split(activity.data["id"], "/") |> List.last()
330 |> put_req_header("accept", "application/activity+json")
331 |> get("/activities/#{uuid}")
333 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
336 test "it returns 404 for non-public activities", %{conn: conn} do
337 activity = insert(:direct_note_activity)
338 uuid = String.split(activity.data["id"], "/") |> List.last()
342 |> put_req_header("accept", "application/activity+json")
343 |> get("/activities/#{uuid}")
345 assert json_response(conn, 404)
348 test "it caches a response", %{conn: conn} do
349 activity = insert(:note_activity)
350 uuid = String.split(activity.data["id"], "/") |> List.last()
354 |> put_req_header("accept", "application/activity+json")
355 |> get("/activities/#{uuid}")
357 assert json_response(conn1, :ok)
358 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
362 |> put_req_header("accept", "application/activity+json")
363 |> get("/activities/#{uuid}")
365 assert json_response(conn1, :ok) == json_response(conn2, :ok)
366 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
369 test "cached purged after activity deletion", %{conn: conn} do
371 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
373 uuid = String.split(activity.data["id"], "/") |> List.last()
377 |> put_req_header("accept", "application/activity+json")
378 |> get("/activities/#{uuid}")
380 assert json_response(conn1, :ok)
381 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
383 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
387 |> put_req_header("accept", "application/activity+json")
388 |> get("/activities/#{uuid}")
390 assert "Not found" == json_response(conn2, :not_found)
393 test "it requires authentication if instance is NOT federating", %{
397 activity = insert(:note_activity)
398 uuid = String.split(activity.data["id"], "/") |> List.last()
400 conn = put_req_header(conn, "accept", "application/activity+json")
402 ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
407 test "it inserts an incoming activity into the database", %{conn: conn} do
408 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
412 |> assign(:valid_signature, true)
413 |> put_req_header("content-type", "application/activity+json")
414 |> post("/inbox", data)
416 assert "ok" == json_response(conn, 200)
418 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
419 assert Activity.get_by_ap_id(data["id"])
422 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
423 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
425 sender_url = data["actor"]
426 Instances.set_consistently_unreachable(sender_url)
427 refute Instances.reachable?(sender_url)
431 |> assign(:valid_signature, true)
432 |> put_req_header("content-type", "application/activity+json")
433 |> post("/inbox", data)
435 assert "ok" == json_response(conn, 200)
436 assert Instances.reachable?(sender_url)
439 test "accept follow activity", %{conn: conn} do
440 Pleroma.Config.put([:instance, :federating], true)
441 relay = Relay.get_actor()
443 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
445 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
446 relay = refresh_record(relay)
449 File.read!("test/fixtures/relay/accept-follow.json")
450 |> String.replace("{{ap_id}}", relay.ap_id)
451 |> String.replace("{{activity_id}}", activity.data["id"])
455 |> assign(:valid_signature, true)
456 |> put_req_header("content-type", "application/activity+json")
457 |> post("/inbox", accept)
458 |> json_response(200)
460 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
462 assert Pleroma.FollowingRelationship.following?(
467 Mix.shell(Mix.Shell.Process)
470 Mix.shell(Mix.Shell.IO)
473 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
474 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
477 test "without valid signature, " <>
478 "it only accepts Create activities and requires enabled federation",
480 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
481 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
483 conn = put_req_header(conn, "content-type", "application/activity+json")
485 Config.put([:instance, :federating], false)
488 |> post("/inbox", data)
489 |> json_response(403)
492 |> post("/inbox", non_create_data)
493 |> json_response(403)
495 Config.put([:instance, :federating], true)
497 ret_conn = post(conn, "/inbox", data)
498 assert "ok" == json_response(ret_conn, 200)
501 |> post("/inbox", non_create_data)
502 |> json_response(400)
506 describe "/users/:nickname/inbox" do
509 File.read!("test/fixtures/mastodon-post-activity.json")
515 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
517 data = Map.put(data, "bcc", [user.ap_id])
521 |> assign(:valid_signature, true)
522 |> put_req_header("content-type", "application/activity+json")
523 |> post("/users/#{user.nickname}/inbox", data)
525 assert "ok" == json_response(conn, 200)
526 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
527 assert Activity.get_by_ap_id(data["id"])
530 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
534 Map.put(data, "to", user.ap_id)
539 |> assign(:valid_signature, true)
540 |> put_req_header("content-type", "application/activity+json")
541 |> post("/users/#{user.nickname}/inbox", data)
543 assert "ok" == json_response(conn, 200)
544 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
545 assert Activity.get_by_ap_id(data["id"])
548 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
552 Map.put(data, "cc", user.ap_id)
557 |> assign(:valid_signature, true)
558 |> put_req_header("content-type", "application/activity+json")
559 |> post("/users/#{user.nickname}/inbox", data)
561 assert "ok" == json_response(conn, 200)
562 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
563 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
564 assert user.ap_id in activity.recipients
567 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
571 Map.put(data, "bcc", user.ap_id)
577 |> assign(:valid_signature, true)
578 |> put_req_header("content-type", "application/activity+json")
579 |> post("/users/#{user.nickname}/inbox", data)
581 assert "ok" == json_response(conn, 200)
582 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
583 assert Activity.get_by_ap_id(data["id"])
586 test "it accepts announces with to as string instead of array", %{conn: conn} do
590 "@context" => "https://www.w3.org/ns/activitystreams",
591 "actor" => "http://mastodon.example.org/users/admin",
592 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
593 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
594 "to" => "https://www.w3.org/ns/activitystreams#Public",
595 "cc" => [user.ap_id],
601 |> assign(:valid_signature, true)
602 |> put_req_header("content-type", "application/activity+json")
603 |> post("/users/#{user.nickname}/inbox", data)
605 assert "ok" == json_response(conn, 200)
606 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
607 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
608 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
611 test "it accepts messages from actors that are followed by the user", %{
615 recipient = insert(:user)
616 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
618 {:ok, recipient} = User.follow(recipient, actor)
622 |> Map.put("attributedTo", actor.ap_id)
626 |> Map.put("actor", actor.ap_id)
627 |> Map.put("object", object)
631 |> assign(:valid_signature, true)
632 |> put_req_header("content-type", "application/activity+json")
633 |> post("/users/#{recipient.nickname}/inbox", data)
635 assert "ok" == json_response(conn, 200)
636 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
637 assert Activity.get_by_ap_id(data["id"])
640 test "it rejects reads from other users", %{conn: conn} do
642 other_user = insert(:user)
646 |> assign(:user, other_user)
647 |> put_req_header("accept", "application/activity+json")
648 |> get("/users/#{user.nickname}/inbox")
650 assert json_response(conn, 403)
653 test "it returns a note activity in a collection", %{conn: conn} do
654 note_activity = insert(:direct_note_activity)
655 note_object = Object.normalize(note_activity)
656 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
660 |> assign(:user, user)
661 |> put_req_header("accept", "application/activity+json")
662 |> get("/users/#{user.nickname}/inbox?page=true")
664 assert response(conn, 200) =~ note_object.data["content"]
667 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
669 data = Map.put(data, "bcc", [user.ap_id])
671 sender_host = URI.parse(data["actor"]).host
672 Instances.set_consistently_unreachable(sender_host)
673 refute Instances.reachable?(sender_host)
677 |> assign(:valid_signature, true)
678 |> put_req_header("content-type", "application/activity+json")
679 |> post("/users/#{user.nickname}/inbox", data)
681 assert "ok" == json_response(conn, 200)
682 assert Instances.reachable?(sender_host)
685 test "it removes all follower collections but actor's", %{conn: conn} do
686 [actor, recipient] = insert_pair(:user)
689 File.read!("test/fixtures/activitypub-client-post-activity.json")
692 object = Map.put(data["object"], "attributedTo", actor.ap_id)
696 |> Map.put("id", Utils.generate_object_id())
697 |> Map.put("actor", actor.ap_id)
698 |> Map.put("object", object)
700 recipient.follower_address,
701 actor.follower_address
705 recipient.follower_address,
706 "https://www.w3.org/ns/activitystreams#Public"
710 |> assign(:valid_signature, true)
711 |> put_req_header("content-type", "application/activity+json")
712 |> post("/users/#{recipient.nickname}/inbox", data)
713 |> json_response(200)
715 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
717 activity = Activity.get_by_ap_id(data["id"])
720 assert actor.follower_address in activity.recipients
721 assert actor.follower_address in activity.data["cc"]
723 refute recipient.follower_address in activity.recipients
724 refute recipient.follower_address in activity.data["cc"]
725 refute recipient.follower_address in activity.data["to"]
728 test "it requires authentication", %{conn: conn} do
730 conn = put_req_header(conn, "accept", "application/activity+json")
732 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
733 assert json_response(ret_conn, 403)
737 |> assign(:user, user)
738 |> get("/users/#{user.nickname}/inbox")
740 assert json_response(ret_conn, 200)
744 describe "GET /users/:nickname/outbox" do
745 test "it returns 200 even if there're no activities", %{conn: conn} do
750 |> assign(:user, user)
751 |> put_req_header("accept", "application/activity+json")
752 |> get("/users/#{user.nickname}/outbox")
754 result = json_response(conn, 200)
755 assert user.ap_id <> "/outbox" == result["id"]
758 test "it returns a note activity in a collection", %{conn: conn} do
759 note_activity = insert(:note_activity)
760 note_object = Object.normalize(note_activity)
761 user = User.get_cached_by_ap_id(note_activity.data["actor"])
765 |> assign(:user, user)
766 |> put_req_header("accept", "application/activity+json")
767 |> get("/users/#{user.nickname}/outbox?page=true")
769 assert response(conn, 200) =~ note_object.data["content"]
772 test "it returns an announce activity in a collection", %{conn: conn} do
773 announce_activity = insert(:announce_activity)
774 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
778 |> assign(:user, user)
779 |> put_req_header("accept", "application/activity+json")
780 |> get("/users/#{user.nickname}/outbox?page=true")
782 assert response(conn, 200) =~ announce_activity.data["object"]
785 test "it requires authentication if instance is NOT federating", %{
789 conn = put_req_header(conn, "accept", "application/activity+json")
791 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
795 describe "POST /users/:nickname/outbox (C2S)" do
799 "@context" => "https://www.w3.org/ns/activitystreams",
801 "object" => %{"type" => "Note", "content" => "AP C2S test"},
802 "to" => "https://www.w3.org/ns/activitystreams#Public",
808 test "it rejects posts from other users / unauthenticated users", %{
813 other_user = insert(:user)
814 conn = put_req_header(conn, "content-type", "application/activity+json")
817 |> post("/users/#{user.nickname}/outbox", activity)
818 |> json_response(403)
821 |> assign(:user, other_user)
822 |> post("/users/#{user.nickname}/outbox", activity)
823 |> json_response(403)
826 test "it inserts an incoming create activity into the database", %{
834 |> assign(:user, user)
835 |> put_req_header("content-type", "application/activity+json")
836 |> post("/users/#{user.nickname}/outbox", activity)
837 |> json_response(201)
839 assert Activity.get_by_ap_id(result["id"])
840 assert result["object"]
841 assert %Object{data: object} = Object.normalize(result["object"])
842 assert object["content"] == activity["object"]["content"]
845 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
850 |> put_in(["object", "type"], "Benis")
854 |> assign(:user, user)
855 |> put_req_header("content-type", "application/activity+json")
856 |> post("/users/#{user.nickname}/outbox", activity)
857 |> json_response(400)
860 test "it inserts an incoming sensitive activity into the database", %{
865 conn = assign(conn, :user, user)
866 object = Map.put(activity["object"], "sensitive", true)
867 activity = Map.put(activity, "object", object)
871 |> put_req_header("content-type", "application/activity+json")
872 |> post("/users/#{user.nickname}/outbox", activity)
873 |> json_response(201)
875 assert Activity.get_by_ap_id(response["id"])
876 assert response["object"]
877 assert %Object{data: response_object} = Object.normalize(response["object"])
878 assert response_object["sensitive"] == true
879 assert response_object["content"] == activity["object"]["content"]
883 |> put_req_header("accept", "application/activity+json")
884 |> get(response["id"])
885 |> json_response(200)
887 assert representation["object"]["sensitive"] == true
890 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
892 activity = Map.put(activity, "type", "BadType")
896 |> assign(:user, user)
897 |> put_req_header("content-type", "application/activity+json")
898 |> post("/users/#{user.nickname}/outbox", activity)
900 assert json_response(conn, 400)
903 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
904 note_activity = insert(:note_activity)
905 note_object = Object.normalize(note_activity)
906 user = User.get_cached_by_ap_id(note_activity.data["actor"])
911 id: note_object.data["id"]
917 |> assign(:user, user)
918 |> put_req_header("content-type", "application/activity+json")
919 |> post("/users/#{user.nickname}/outbox", data)
921 result = json_response(conn, 201)
922 assert Activity.get_by_ap_id(result["id"])
924 assert object = Object.get_by_ap_id(note_object.data["id"])
925 assert object.data["type"] == "Tombstone"
928 test "it rejects delete activity of object from other actor", %{conn: conn} do
929 note_activity = insert(:note_activity)
930 note_object = Object.normalize(note_activity)
936 id: note_object.data["id"]
942 |> assign(:user, user)
943 |> put_req_header("content-type", "application/activity+json")
944 |> post("/users/#{user.nickname}/outbox", data)
946 assert json_response(conn, 400)
949 test "it increases like count when receiving a like action", %{conn: conn} do
950 note_activity = insert(:note_activity)
951 note_object = Object.normalize(note_activity)
952 user = User.get_cached_by_ap_id(note_activity.data["actor"])
957 id: note_object.data["id"]
963 |> assign(:user, user)
964 |> put_req_header("content-type", "application/activity+json")
965 |> post("/users/#{user.nickname}/outbox", data)
967 result = json_response(conn, 201)
968 assert Activity.get_by_ap_id(result["id"])
970 assert object = Object.get_by_ap_id(note_object.data["id"])
971 assert object.data["like_count"] == 1
975 describe "/relay/followers" do
976 test "it returns relay followers", %{conn: conn} do
977 relay_actor = Relay.get_actor()
979 User.follow(user, relay_actor)
983 |> get("/relay/followers")
984 |> json_response(200)
986 assert result["first"]["orderedItems"] == [user.ap_id]
989 test "on non-federating instance, it returns 404", %{conn: conn} do
990 Config.put([:instance, :federating], false)
994 |> assign(:user, user)
995 |> get("/relay/followers")
996 |> json_response(404)
1000 describe "/relay/following" do
1001 test "it returns relay following", %{conn: conn} do
1004 |> get("/relay/following")
1005 |> json_response(200)
1007 assert result["first"]["orderedItems"] == []
1010 test "on non-federating instance, it returns 404", %{conn: conn} do
1011 Config.put([:instance, :federating], false)
1012 user = insert(:user)
1015 |> assign(:user, user)
1016 |> get("/relay/following")
1017 |> json_response(404)
1021 describe "/users/:nickname/followers" do
1022 test "it returns the followers in a collection", %{conn: conn} do
1023 user = insert(:user)
1024 user_two = insert(:user)
1025 User.follow(user, user_two)
1029 |> assign(:user, user_two)
1030 |> get("/users/#{user_two.nickname}/followers")
1031 |> json_response(200)
1033 assert result["first"]["orderedItems"] == [user.ap_id]
1036 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1037 user = insert(:user)
1038 user_two = insert(:user, hide_followers: true)
1039 User.follow(user, user_two)
1043 |> assign(:user, user)
1044 |> get("/users/#{user_two.nickname}/followers")
1045 |> json_response(200)
1047 assert is_binary(result["first"])
1050 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1052 user = insert(:user)
1053 other_user = insert(:user, hide_followers: true)
1057 |> assign(:user, user)
1058 |> get("/users/#{other_user.nickname}/followers?page=1")
1060 assert result.status == 403
1061 assert result.resp_body == ""
1064 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1066 user = insert(:user, hide_followers: true)
1067 other_user = insert(:user)
1068 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1072 |> assign(:user, user)
1073 |> get("/users/#{user.nickname}/followers?page=1")
1074 |> json_response(200)
1076 assert result["totalItems"] == 1
1077 assert result["orderedItems"] == [other_user.ap_id]
1080 test "it works for more than 10 users", %{conn: conn} do
1081 user = insert(:user)
1083 Enum.each(1..15, fn _ ->
1084 other_user = insert(:user)
1085 User.follow(other_user, user)
1090 |> assign(:user, user)
1091 |> get("/users/#{user.nickname}/followers")
1092 |> json_response(200)
1094 assert length(result["first"]["orderedItems"]) == 10
1095 assert result["first"]["totalItems"] == 15
1096 assert result["totalItems"] == 15
1100 |> assign(:user, user)
1101 |> get("/users/#{user.nickname}/followers?page=2")
1102 |> json_response(200)
1104 assert length(result["orderedItems"]) == 5
1105 assert result["totalItems"] == 15
1108 test "does not require authentication", %{conn: conn} do
1109 user = insert(:user)
1112 |> get("/users/#{user.nickname}/followers")
1113 |> json_response(200)
1117 describe "/users/:nickname/following" do
1118 test "it returns the following in a collection", %{conn: conn} do
1119 user = insert(:user)
1120 user_two = insert(:user)
1121 User.follow(user, user_two)
1125 |> assign(:user, user)
1126 |> get("/users/#{user.nickname}/following")
1127 |> json_response(200)
1129 assert result["first"]["orderedItems"] == [user_two.ap_id]
1132 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1133 user = insert(:user)
1134 user_two = insert(:user, hide_follows: true)
1135 User.follow(user, user_two)
1139 |> assign(:user, user)
1140 |> get("/users/#{user_two.nickname}/following")
1141 |> json_response(200)
1143 assert is_binary(result["first"])
1146 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1148 user = insert(:user)
1149 user_two = insert(:user, hide_follows: true)
1153 |> assign(:user, user)
1154 |> get("/users/#{user_two.nickname}/following?page=1")
1156 assert result.status == 403
1157 assert result.resp_body == ""
1160 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1162 user = insert(:user, hide_follows: true)
1163 other_user = insert(:user)
1164 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1168 |> assign(:user, user)
1169 |> get("/users/#{user.nickname}/following?page=1")
1170 |> json_response(200)
1172 assert result["totalItems"] == 1
1173 assert result["orderedItems"] == [other_user.ap_id]
1176 test "it works for more than 10 users", %{conn: conn} do
1177 user = insert(:user)
1179 Enum.each(1..15, fn _ ->
1180 user = User.get_cached_by_id(user.id)
1181 other_user = insert(:user)
1182 User.follow(user, other_user)
1187 |> assign(:user, user)
1188 |> get("/users/#{user.nickname}/following")
1189 |> json_response(200)
1191 assert length(result["first"]["orderedItems"]) == 10
1192 assert result["first"]["totalItems"] == 15
1193 assert result["totalItems"] == 15
1197 |> assign(:user, user)
1198 |> get("/users/#{user.nickname}/following?page=2")
1199 |> json_response(200)
1201 assert length(result["orderedItems"]) == 5
1202 assert result["totalItems"] == 15
1205 test "does not require authentication", %{conn: conn} do
1206 user = insert(:user)
1209 |> get("/users/#{user.nickname}/following")
1210 |> json_response(200)
1214 describe "delivery tracking" do
1215 test "it tracks a signed object fetch", %{conn: conn} do
1216 user = insert(:user, local: false)
1217 activity = insert(:note_activity)
1218 object = Object.normalize(activity)
1220 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1223 |> put_req_header("accept", "application/activity+json")
1224 |> assign(:user, user)
1226 |> json_response(200)
1228 assert Delivery.get(object.id, user.id)
1231 test "it tracks a signed activity fetch", %{conn: conn} do
1232 user = insert(:user, local: false)
1233 activity = insert(:note_activity)
1234 object = Object.normalize(activity)
1236 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1239 |> put_req_header("accept", "application/activity+json")
1240 |> assign(:user, user)
1241 |> get(activity_path)
1242 |> json_response(200)
1244 assert Delivery.get(object.id, user.id)
1247 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1248 user = insert(:user, local: false)
1249 other_user = insert(:user, local: false)
1250 activity = insert(:note_activity)
1251 object = Object.normalize(activity)
1253 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1256 |> put_req_header("accept", "application/activity+json")
1257 |> assign(:user, user)
1259 |> json_response(200)
1262 |> put_req_header("accept", "application/activity+json")
1263 |> assign(:user, other_user)
1265 |> json_response(200)
1267 assert Delivery.get(object.id, user.id)
1268 assert Delivery.get(object.id, other_user.id)
1271 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1272 user = insert(:user, local: false)
1273 other_user = insert(:user, local: false)
1274 activity = insert(:note_activity)
1275 object = Object.normalize(activity)
1277 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1280 |> put_req_header("accept", "application/activity+json")
1281 |> assign(:user, user)
1282 |> get(activity_path)
1283 |> json_response(200)
1286 |> put_req_header("accept", "application/activity+json")
1287 |> assign(:user, other_user)
1288 |> get(activity_path)
1289 |> json_response(200)
1291 assert Delivery.get(object.id, user.id)
1292 assert Delivery.get(object.id, other_user.id)
1296 describe "Additional ActivityPub C2S endpoints" do
1297 test "GET /api/ap/whoami", %{conn: conn} do
1298 user = insert(:user)
1302 |> assign(:user, user)
1303 |> get("/api/ap/whoami")
1305 user = User.get_cached_by_id(user.id)
1307 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1310 |> get("/api/ap/whoami")
1311 |> json_response(403)
1314 setup do: clear_config([:media_proxy])
1315 setup do: clear_config([Pleroma.Upload])
1317 test "POST /api/ap/upload_media", %{conn: conn} do
1318 user = insert(:user)
1320 desc = "Description of the image"
1322 image = %Plug.Upload{
1323 content_type: "image/jpg",
1324 path: Path.absname("test/fixtures/image.jpg"),
1325 filename: "an_image.jpg"
1330 |> assign(:user, user)
1331 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1332 |> json_response(:created)
1334 assert object["name"] == desc
1335 assert object["type"] == "Document"
1336 assert object["actor"] == user.ap_id
1337 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1338 assert is_binary(object_href)
1339 assert object_mediatype == "image/jpeg"
1341 activity_request = %{
1342 "@context" => "https://www.w3.org/ns/activitystreams",
1346 "content" => "AP C2S test, attachment",
1347 "attachment" => [object]
1349 "to" => "https://www.w3.org/ns/activitystreams#Public",
1355 |> assign(:user, user)
1356 |> post("/users/#{user.nickname}/outbox", activity_request)
1357 |> json_response(:created)
1359 assert activity_response["id"]
1360 assert activity_response["object"]
1361 assert activity_response["actor"] == user.ap_id
1363 assert %Object{data: %{"attachment" => [attachment]}} =
1364 Object.normalize(activity_response["object"])
1366 assert attachment["type"] == "Document"
1367 assert attachment["name"] == desc
1371 "href" => ^object_href,
1373 "mediaType" => ^object_mediatype
1375 ] = attachment["url"]
1377 # Fails if unauthenticated
1379 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1380 |> json_response(403)