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.ActivityPub
17 alias Pleroma.Web.ActivityPub.ObjectView
18 alias Pleroma.Web.ActivityPub.Relay
19 alias Pleroma.Web.ActivityPub.UserView
20 alias Pleroma.Web.ActivityPub.Utils
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Workers.ReceiverWorker
25 import Pleroma.Factory
27 require Pleroma.Constants
30 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
34 setup do: clear_config([:instance, :federating], true)
37 setup do: clear_config([:instance, :allow_relay])
39 test "with the relay active, it returns the relay user", %{conn: conn} do
42 |> get(activity_pub_path(conn, :relay))
45 assert res["id"] =~ "/relay"
48 test "with the relay disabled, it returns 404", %{conn: conn} do
49 Config.put([:instance, :allow_relay], false)
52 |> get(activity_pub_path(conn, :relay))
56 test "on non-federating instance, it returns 404", %{conn: conn} do
57 Config.put([:instance, :federating], false)
61 |> assign(:user, user)
62 |> get(activity_pub_path(conn, :relay))
67 describe "/internal/fetch" do
68 test "it returns the internal fetch user", %{conn: conn} do
71 |> get(activity_pub_path(conn, :internal_fetch))
74 assert res["id"] =~ "/fetch"
77 test "on non-federating instance, it returns 404", %{conn: conn} do
78 Config.put([:instance, :federating], false)
82 |> assign(:user, user)
83 |> get(activity_pub_path(conn, :internal_fetch))
88 describe "/users/:nickname" do
89 test "it returns a json representation of the user with accept application/json", %{
96 |> put_req_header("accept", "application/json")
97 |> get("/users/#{user.nickname}")
99 user = User.get_cached_by_id(user.id)
101 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
104 test "it returns a json representation of the user with accept application/activity+json", %{
111 |> put_req_header("accept", "application/activity+json")
112 |> get("/users/#{user.nickname}")
114 user = User.get_cached_by_id(user.id)
116 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
119 test "it returns a json representation of the user with accept application/ld+json", %{
128 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
130 |> get("/users/#{user.nickname}")
132 user = User.get_cached_by_id(user.id)
134 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
137 test "it returns 404 for remote users", %{
140 user = insert(:user, local: false, nickname: "remoteuser@example.com")
144 |> put_req_header("accept", "application/json")
145 |> get("/users/#{user.nickname}.json")
147 assert json_response(conn, 404)
150 test "it returns error when user is not found", %{conn: conn} do
153 |> put_req_header("accept", "application/json")
154 |> get("/users/jimm")
155 |> json_response(404)
157 assert response == "Not found"
160 test "it requires authentication if instance is NOT federating", %{
169 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
172 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
176 describe "mastodon compatibility routes" do
177 test "it returns a json representation of the object with accept application/json", %{
184 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
185 "actor" => Endpoint.url() <> "/users/raymoo",
186 "to" => [Pleroma.Constants.as_public()]
192 |> put_req_header("accept", "application/json")
193 |> get("/users/raymoo/statuses/999999999")
195 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
198 test "it returns a json representation of the activity with accept application/json", %{
205 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
206 "actor" => Endpoint.url() <> "/users/raymoo",
207 "to" => [Pleroma.Constants.as_public()]
213 "id" => object.data["id"] <> "/activity",
215 "object" => object.data["id"],
216 "actor" => object.data["actor"],
217 "to" => object.data["to"]
219 |> ActivityPub.persist(local: true)
223 |> put_req_header("accept", "application/json")
224 |> get("/users/raymoo/statuses/999999999/activity")
226 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
230 describe "/objects/:uuid" do
231 test "it returns a json representation of the object with accept application/json", %{
235 uuid = String.split(note.data["id"], "/") |> List.last()
239 |> put_req_header("accept", "application/json")
240 |> get("/objects/#{uuid}")
242 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
245 test "it returns a json representation of the object with accept application/activity+json",
248 uuid = String.split(note.data["id"], "/") |> List.last()
252 |> put_req_header("accept", "application/activity+json")
253 |> get("/objects/#{uuid}")
255 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
258 test "it returns a json representation of the object with accept application/ld+json", %{
262 uuid = String.split(note.data["id"], "/") |> List.last()
268 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
270 |> get("/objects/#{uuid}")
272 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
275 test "it returns 404 for non-public messages", %{conn: conn} do
276 note = insert(:direct_note)
277 uuid = String.split(note.data["id"], "/") |> List.last()
281 |> put_req_header("accept", "application/activity+json")
282 |> get("/objects/#{uuid}")
284 assert json_response(conn, 404)
287 test "it returns 404 for tombstone objects", %{conn: conn} do
288 tombstone = insert(:tombstone)
289 uuid = String.split(tombstone.data["id"], "/") |> List.last()
293 |> put_req_header("accept", "application/activity+json")
294 |> get("/objects/#{uuid}")
296 assert json_response(conn, 404)
299 test "it caches a response", %{conn: conn} do
301 uuid = String.split(note.data["id"], "/") |> List.last()
305 |> put_req_header("accept", "application/activity+json")
306 |> get("/objects/#{uuid}")
308 assert json_response(conn1, :ok)
309 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
313 |> put_req_header("accept", "application/activity+json")
314 |> get("/objects/#{uuid}")
316 assert json_response(conn1, :ok) == json_response(conn2, :ok)
317 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
320 test "cached purged after object deletion", %{conn: conn} do
322 uuid = String.split(note.data["id"], "/") |> List.last()
326 |> put_req_header("accept", "application/activity+json")
327 |> get("/objects/#{uuid}")
329 assert json_response(conn1, :ok)
330 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
336 |> put_req_header("accept", "application/activity+json")
337 |> get("/objects/#{uuid}")
339 assert "Not found" == json_response(conn2, :not_found)
342 test "it requires authentication if instance is NOT federating", %{
347 uuid = String.split(note.data["id"], "/") |> List.last()
349 conn = put_req_header(conn, "accept", "application/activity+json")
351 ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
355 describe "/activities/:uuid" do
356 test "it returns a json representation of the activity", %{conn: conn} do
357 activity = insert(:note_activity)
358 uuid = String.split(activity.data["id"], "/") |> List.last()
362 |> put_req_header("accept", "application/activity+json")
363 |> get("/activities/#{uuid}")
365 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
368 test "it returns 404 for non-public activities", %{conn: conn} do
369 activity = insert(:direct_note_activity)
370 uuid = String.split(activity.data["id"], "/") |> List.last()
374 |> put_req_header("accept", "application/activity+json")
375 |> get("/activities/#{uuid}")
377 assert json_response(conn, 404)
380 test "it caches a response", %{conn: conn} do
381 activity = insert(:note_activity)
382 uuid = String.split(activity.data["id"], "/") |> List.last()
386 |> put_req_header("accept", "application/activity+json")
387 |> get("/activities/#{uuid}")
389 assert json_response(conn1, :ok)
390 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
394 |> put_req_header("accept", "application/activity+json")
395 |> get("/activities/#{uuid}")
397 assert json_response(conn1, :ok) == json_response(conn2, :ok)
398 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
401 test "cached purged after activity deletion", %{conn: conn} do
403 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
405 uuid = String.split(activity.data["id"], "/") |> List.last()
409 |> put_req_header("accept", "application/activity+json")
410 |> get("/activities/#{uuid}")
412 assert json_response(conn1, :ok)
413 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
415 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
419 |> put_req_header("accept", "application/activity+json")
420 |> get("/activities/#{uuid}")
422 assert "Not found" == json_response(conn2, :not_found)
425 test "it requires authentication if instance is NOT federating", %{
429 activity = insert(:note_activity)
430 uuid = String.split(activity.data["id"], "/") |> List.last()
432 conn = put_req_header(conn, "accept", "application/activity+json")
434 ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
439 test "it inserts an incoming activity into the database", %{conn: conn} do
440 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
444 |> assign(:valid_signature, true)
445 |> put_req_header("content-type", "application/activity+json")
446 |> post("/inbox", data)
448 assert "ok" == json_response(conn, 200)
450 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
451 assert Activity.get_by_ap_id(data["id"])
454 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
455 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
457 sender_url = data["actor"]
458 Instances.set_consistently_unreachable(sender_url)
459 refute Instances.reachable?(sender_url)
463 |> assign(:valid_signature, true)
464 |> put_req_header("content-type", "application/activity+json")
465 |> post("/inbox", data)
467 assert "ok" == json_response(conn, 200)
468 assert Instances.reachable?(sender_url)
471 test "accept follow activity", %{conn: conn} do
472 Pleroma.Config.put([:instance, :federating], true)
473 relay = Relay.get_actor()
475 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
477 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
478 relay = refresh_record(relay)
481 File.read!("test/fixtures/relay/accept-follow.json")
482 |> String.replace("{{ap_id}}", relay.ap_id)
483 |> String.replace("{{activity_id}}", activity.data["id"])
487 |> assign(:valid_signature, true)
488 |> put_req_header("content-type", "application/activity+json")
489 |> post("/inbox", accept)
490 |> json_response(200)
492 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
494 assert Pleroma.FollowingRelationship.following?(
499 Mix.shell(Mix.Shell.Process)
502 Mix.shell(Mix.Shell.IO)
505 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
506 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
509 test "without valid signature, " <>
510 "it only accepts Create activities and requires enabled federation",
512 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
513 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
515 conn = put_req_header(conn, "content-type", "application/activity+json")
517 Config.put([:instance, :federating], false)
520 |> post("/inbox", data)
521 |> json_response(403)
524 |> post("/inbox", non_create_data)
525 |> json_response(403)
527 Config.put([:instance, :federating], true)
529 ret_conn = post(conn, "/inbox", data)
530 assert "ok" == json_response(ret_conn, 200)
533 |> post("/inbox", non_create_data)
534 |> json_response(400)
538 describe "/users/:nickname/inbox" do
541 File.read!("test/fixtures/mastodon-post-activity.json")
547 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
549 data = Map.put(data, "bcc", [user.ap_id])
553 |> assign(:valid_signature, true)
554 |> put_req_header("content-type", "application/activity+json")
555 |> post("/users/#{user.nickname}/inbox", data)
557 assert "ok" == json_response(conn, 200)
558 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
559 assert Activity.get_by_ap_id(data["id"])
562 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
566 Map.put(data, "to", user.ap_id)
571 |> assign(:valid_signature, true)
572 |> put_req_header("content-type", "application/activity+json")
573 |> post("/users/#{user.nickname}/inbox", data)
575 assert "ok" == json_response(conn, 200)
576 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
577 assert Activity.get_by_ap_id(data["id"])
580 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
584 Map.put(data, "cc", user.ap_id)
589 |> assign(:valid_signature, true)
590 |> put_req_header("content-type", "application/activity+json")
591 |> post("/users/#{user.nickname}/inbox", data)
593 assert "ok" == json_response(conn, 200)
594 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
595 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
596 assert user.ap_id in activity.recipients
599 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
603 Map.put(data, "bcc", user.ap_id)
609 |> assign(:valid_signature, true)
610 |> put_req_header("content-type", "application/activity+json")
611 |> post("/users/#{user.nickname}/inbox", data)
613 assert "ok" == json_response(conn, 200)
614 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
615 assert Activity.get_by_ap_id(data["id"])
618 test "it accepts announces with to as string instead of array", %{conn: conn} do
622 "@context" => "https://www.w3.org/ns/activitystreams",
623 "actor" => "http://mastodon.example.org/users/admin",
624 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
625 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
626 "to" => "https://www.w3.org/ns/activitystreams#Public",
627 "cc" => [user.ap_id],
633 |> assign(:valid_signature, true)
634 |> put_req_header("content-type", "application/activity+json")
635 |> post("/users/#{user.nickname}/inbox", data)
637 assert "ok" == json_response(conn, 200)
638 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
639 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
640 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
643 test "it accepts messages from actors that are followed by the user", %{
647 recipient = insert(:user)
648 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
650 {:ok, recipient} = User.follow(recipient, actor)
654 |> Map.put("attributedTo", actor.ap_id)
658 |> Map.put("actor", actor.ap_id)
659 |> Map.put("object", object)
663 |> assign(:valid_signature, true)
664 |> put_req_header("content-type", "application/activity+json")
665 |> post("/users/#{recipient.nickname}/inbox", data)
667 assert "ok" == json_response(conn, 200)
668 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
669 assert Activity.get_by_ap_id(data["id"])
672 test "it rejects reads from other users", %{conn: conn} do
674 other_user = insert(:user)
678 |> assign(:user, other_user)
679 |> put_req_header("accept", "application/activity+json")
680 |> get("/users/#{user.nickname}/inbox")
682 assert json_response(conn, 403)
685 test "it returns a note activity in a collection", %{conn: conn} do
686 note_activity = insert(:direct_note_activity)
687 note_object = Object.normalize(note_activity)
688 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
692 |> assign(:user, user)
693 |> put_req_header("accept", "application/activity+json")
694 |> get("/users/#{user.nickname}/inbox?page=true")
696 assert response(conn, 200) =~ note_object.data["content"]
699 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
701 data = Map.put(data, "bcc", [user.ap_id])
703 sender_host = URI.parse(data["actor"]).host
704 Instances.set_consistently_unreachable(sender_host)
705 refute Instances.reachable?(sender_host)
709 |> assign(:valid_signature, true)
710 |> put_req_header("content-type", "application/activity+json")
711 |> post("/users/#{user.nickname}/inbox", data)
713 assert "ok" == json_response(conn, 200)
714 assert Instances.reachable?(sender_host)
717 test "it removes all follower collections but actor's", %{conn: conn} do
718 [actor, recipient] = insert_pair(:user)
721 File.read!("test/fixtures/activitypub-client-post-activity.json")
724 object = Map.put(data["object"], "attributedTo", actor.ap_id)
728 |> Map.put("id", Utils.generate_object_id())
729 |> Map.put("actor", actor.ap_id)
730 |> Map.put("object", object)
732 recipient.follower_address,
733 actor.follower_address
737 recipient.follower_address,
738 "https://www.w3.org/ns/activitystreams#Public"
742 |> assign(:valid_signature, true)
743 |> put_req_header("content-type", "application/activity+json")
744 |> post("/users/#{recipient.nickname}/inbox", data)
745 |> json_response(200)
747 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
749 activity = Activity.get_by_ap_id(data["id"])
752 assert actor.follower_address in activity.recipients
753 assert actor.follower_address in activity.data["cc"]
755 refute recipient.follower_address in activity.recipients
756 refute recipient.follower_address in activity.data["cc"]
757 refute recipient.follower_address in activity.data["to"]
760 test "it requires authentication", %{conn: conn} do
762 conn = put_req_header(conn, "accept", "application/activity+json")
764 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
765 assert json_response(ret_conn, 403)
769 |> assign(:user, user)
770 |> get("/users/#{user.nickname}/inbox")
772 assert json_response(ret_conn, 200)
776 describe "GET /users/:nickname/outbox" do
777 test "it returns 200 even if there're no activities", %{conn: conn} do
782 |> assign(:user, user)
783 |> put_req_header("accept", "application/activity+json")
784 |> get("/users/#{user.nickname}/outbox")
786 result = json_response(conn, 200)
787 assert user.ap_id <> "/outbox" == result["id"]
790 test "it returns a note activity in a collection", %{conn: conn} do
791 note_activity = insert(:note_activity)
792 note_object = Object.normalize(note_activity)
793 user = User.get_cached_by_ap_id(note_activity.data["actor"])
797 |> assign(:user, user)
798 |> put_req_header("accept", "application/activity+json")
799 |> get("/users/#{user.nickname}/outbox?page=true")
801 assert response(conn, 200) =~ note_object.data["content"]
804 test "it returns an announce activity in a collection", %{conn: conn} do
805 announce_activity = insert(:announce_activity)
806 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
810 |> assign(:user, user)
811 |> put_req_header("accept", "application/activity+json")
812 |> get("/users/#{user.nickname}/outbox?page=true")
814 assert response(conn, 200) =~ announce_activity.data["object"]
817 test "it requires authentication if instance is NOT federating", %{
821 conn = put_req_header(conn, "accept", "application/activity+json")
823 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
827 describe "POST /users/:nickname/outbox (C2S)" do
831 "@context" => "https://www.w3.org/ns/activitystreams",
833 "object" => %{"type" => "Note", "content" => "AP C2S test"},
834 "to" => "https://www.w3.org/ns/activitystreams#Public",
840 test "it rejects posts from other users / unauthenticated users", %{
845 other_user = insert(:user)
846 conn = put_req_header(conn, "content-type", "application/activity+json")
849 |> post("/users/#{user.nickname}/outbox", activity)
850 |> json_response(403)
853 |> assign(:user, other_user)
854 |> post("/users/#{user.nickname}/outbox", activity)
855 |> json_response(403)
858 test "it inserts an incoming create activity into the database", %{
866 |> assign(:user, user)
867 |> put_req_header("content-type", "application/activity+json")
868 |> post("/users/#{user.nickname}/outbox", activity)
869 |> json_response(201)
871 assert Activity.get_by_ap_id(result["id"])
872 assert result["object"]
873 assert %Object{data: object} = Object.normalize(result["object"])
874 assert object["content"] == activity["object"]["content"]
877 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
882 |> put_in(["object", "type"], "Benis")
886 |> assign(:user, user)
887 |> put_req_header("content-type", "application/activity+json")
888 |> post("/users/#{user.nickname}/outbox", activity)
889 |> json_response(400)
892 test "it inserts an incoming sensitive activity into the database", %{
897 conn = assign(conn, :user, user)
898 object = Map.put(activity["object"], "sensitive", true)
899 activity = Map.put(activity, "object", object)
903 |> put_req_header("content-type", "application/activity+json")
904 |> post("/users/#{user.nickname}/outbox", activity)
905 |> json_response(201)
907 assert Activity.get_by_ap_id(response["id"])
908 assert response["object"]
909 assert %Object{data: response_object} = Object.normalize(response["object"])
910 assert response_object["sensitive"] == true
911 assert response_object["content"] == activity["object"]["content"]
915 |> put_req_header("accept", "application/activity+json")
916 |> get(response["id"])
917 |> json_response(200)
919 assert representation["object"]["sensitive"] == true
922 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
924 activity = Map.put(activity, "type", "BadType")
928 |> assign(:user, user)
929 |> put_req_header("content-type", "application/activity+json")
930 |> post("/users/#{user.nickname}/outbox", activity)
932 assert json_response(conn, 400)
935 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
936 note_activity = insert(:note_activity)
937 note_object = Object.normalize(note_activity)
938 user = User.get_cached_by_ap_id(note_activity.data["actor"])
943 id: note_object.data["id"]
949 |> assign(:user, user)
950 |> put_req_header("content-type", "application/activity+json")
951 |> post("/users/#{user.nickname}/outbox", data)
953 result = json_response(conn, 201)
954 assert Activity.get_by_ap_id(result["id"])
956 assert object = Object.get_by_ap_id(note_object.data["id"])
957 assert object.data["type"] == "Tombstone"
960 test "it rejects delete activity of object from other actor", %{conn: conn} do
961 note_activity = insert(:note_activity)
962 note_object = Object.normalize(note_activity)
968 id: note_object.data["id"]
974 |> assign(:user, user)
975 |> put_req_header("content-type", "application/activity+json")
976 |> post("/users/#{user.nickname}/outbox", data)
978 assert json_response(conn, 400)
981 test "it increases like count when receiving a like action", %{conn: conn} do
982 note_activity = insert(:note_activity)
983 note_object = Object.normalize(note_activity)
984 user = User.get_cached_by_ap_id(note_activity.data["actor"])
989 id: note_object.data["id"]
995 |> assign(:user, user)
996 |> put_req_header("content-type", "application/activity+json")
997 |> post("/users/#{user.nickname}/outbox", data)
999 result = json_response(conn, 201)
1000 assert Activity.get_by_ap_id(result["id"])
1002 assert object = Object.get_by_ap_id(note_object.data["id"])
1003 assert object.data["like_count"] == 1
1007 describe "/relay/followers" do
1008 test "it returns relay followers", %{conn: conn} do
1009 relay_actor = Relay.get_actor()
1010 user = insert(:user)
1011 User.follow(user, relay_actor)
1015 |> get("/relay/followers")
1016 |> json_response(200)
1018 assert result["first"]["orderedItems"] == [user.ap_id]
1021 test "on non-federating instance, it returns 404", %{conn: conn} do
1022 Config.put([:instance, :federating], false)
1023 user = insert(:user)
1026 |> assign(:user, user)
1027 |> get("/relay/followers")
1028 |> json_response(404)
1032 describe "/relay/following" do
1033 test "it returns relay following", %{conn: conn} do
1036 |> get("/relay/following")
1037 |> json_response(200)
1039 assert result["first"]["orderedItems"] == []
1042 test "on non-federating instance, it returns 404", %{conn: conn} do
1043 Config.put([:instance, :federating], false)
1044 user = insert(:user)
1047 |> assign(:user, user)
1048 |> get("/relay/following")
1049 |> json_response(404)
1053 describe "/users/:nickname/followers" do
1054 test "it returns the followers in a collection", %{conn: conn} do
1055 user = insert(:user)
1056 user_two = insert(:user)
1057 User.follow(user, user_two)
1061 |> assign(:user, user_two)
1062 |> get("/users/#{user_two.nickname}/followers")
1063 |> json_response(200)
1065 assert result["first"]["orderedItems"] == [user.ap_id]
1068 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1069 user = insert(:user)
1070 user_two = insert(:user, hide_followers: true)
1071 User.follow(user, user_two)
1075 |> assign(:user, user)
1076 |> get("/users/#{user_two.nickname}/followers")
1077 |> json_response(200)
1079 assert is_binary(result["first"])
1082 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1084 user = insert(:user)
1085 other_user = insert(:user, hide_followers: true)
1089 |> assign(:user, user)
1090 |> get("/users/#{other_user.nickname}/followers?page=1")
1092 assert result.status == 403
1093 assert result.resp_body == ""
1096 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1098 user = insert(:user, hide_followers: true)
1099 other_user = insert(:user)
1100 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1104 |> assign(:user, user)
1105 |> get("/users/#{user.nickname}/followers?page=1")
1106 |> json_response(200)
1108 assert result["totalItems"] == 1
1109 assert result["orderedItems"] == [other_user.ap_id]
1112 test "it works for more than 10 users", %{conn: conn} do
1113 user = insert(:user)
1115 Enum.each(1..15, fn _ ->
1116 other_user = insert(:user)
1117 User.follow(other_user, user)
1122 |> assign(:user, user)
1123 |> get("/users/#{user.nickname}/followers")
1124 |> json_response(200)
1126 assert length(result["first"]["orderedItems"]) == 10
1127 assert result["first"]["totalItems"] == 15
1128 assert result["totalItems"] == 15
1132 |> assign(:user, user)
1133 |> get("/users/#{user.nickname}/followers?page=2")
1134 |> json_response(200)
1136 assert length(result["orderedItems"]) == 5
1137 assert result["totalItems"] == 15
1140 test "does not require authentication", %{conn: conn} do
1141 user = insert(:user)
1144 |> get("/users/#{user.nickname}/followers")
1145 |> json_response(200)
1149 describe "/users/:nickname/following" do
1150 test "it returns the following in a collection", %{conn: conn} do
1151 user = insert(:user)
1152 user_two = insert(:user)
1153 User.follow(user, user_two)
1157 |> assign(:user, user)
1158 |> get("/users/#{user.nickname}/following")
1159 |> json_response(200)
1161 assert result["first"]["orderedItems"] == [user_two.ap_id]
1164 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1165 user = insert(:user)
1166 user_two = insert(:user, hide_follows: true)
1167 User.follow(user, user_two)
1171 |> assign(:user, user)
1172 |> get("/users/#{user_two.nickname}/following")
1173 |> json_response(200)
1175 assert is_binary(result["first"])
1178 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1180 user = insert(:user)
1181 user_two = insert(:user, hide_follows: true)
1185 |> assign(:user, user)
1186 |> get("/users/#{user_two.nickname}/following?page=1")
1188 assert result.status == 403
1189 assert result.resp_body == ""
1192 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1194 user = insert(:user, hide_follows: true)
1195 other_user = insert(:user)
1196 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1200 |> assign(:user, user)
1201 |> get("/users/#{user.nickname}/following?page=1")
1202 |> json_response(200)
1204 assert result["totalItems"] == 1
1205 assert result["orderedItems"] == [other_user.ap_id]
1208 test "it works for more than 10 users", %{conn: conn} do
1209 user = insert(:user)
1211 Enum.each(1..15, fn _ ->
1212 user = User.get_cached_by_id(user.id)
1213 other_user = insert(:user)
1214 User.follow(user, other_user)
1219 |> assign(:user, user)
1220 |> get("/users/#{user.nickname}/following")
1221 |> json_response(200)
1223 assert length(result["first"]["orderedItems"]) == 10
1224 assert result["first"]["totalItems"] == 15
1225 assert result["totalItems"] == 15
1229 |> assign(:user, user)
1230 |> get("/users/#{user.nickname}/following?page=2")
1231 |> json_response(200)
1233 assert length(result["orderedItems"]) == 5
1234 assert result["totalItems"] == 15
1237 test "does not require authentication", %{conn: conn} do
1238 user = insert(:user)
1241 |> get("/users/#{user.nickname}/following")
1242 |> json_response(200)
1246 describe "delivery tracking" do
1247 test "it tracks a signed object fetch", %{conn: conn} do
1248 user = insert(:user, local: false)
1249 activity = insert(:note_activity)
1250 object = Object.normalize(activity)
1252 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1255 |> put_req_header("accept", "application/activity+json")
1256 |> assign(:user, user)
1258 |> json_response(200)
1260 assert Delivery.get(object.id, user.id)
1263 test "it tracks a signed activity fetch", %{conn: conn} do
1264 user = insert(:user, local: false)
1265 activity = insert(:note_activity)
1266 object = Object.normalize(activity)
1268 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1271 |> put_req_header("accept", "application/activity+json")
1272 |> assign(:user, user)
1273 |> get(activity_path)
1274 |> json_response(200)
1276 assert Delivery.get(object.id, user.id)
1279 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1280 user = insert(:user, local: false)
1281 other_user = insert(:user, local: false)
1282 activity = insert(:note_activity)
1283 object = Object.normalize(activity)
1285 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1288 |> put_req_header("accept", "application/activity+json")
1289 |> assign(:user, user)
1291 |> json_response(200)
1294 |> put_req_header("accept", "application/activity+json")
1295 |> assign(:user, other_user)
1297 |> json_response(200)
1299 assert Delivery.get(object.id, user.id)
1300 assert Delivery.get(object.id, other_user.id)
1303 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1304 user = insert(:user, local: false)
1305 other_user = insert(:user, local: false)
1306 activity = insert(:note_activity)
1307 object = Object.normalize(activity)
1309 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1312 |> put_req_header("accept", "application/activity+json")
1313 |> assign(:user, user)
1314 |> get(activity_path)
1315 |> json_response(200)
1318 |> put_req_header("accept", "application/activity+json")
1319 |> assign(:user, other_user)
1320 |> get(activity_path)
1321 |> json_response(200)
1323 assert Delivery.get(object.id, user.id)
1324 assert Delivery.get(object.id, other_user.id)
1328 describe "Additional ActivityPub C2S endpoints" do
1329 test "GET /api/ap/whoami", %{conn: conn} do
1330 user = insert(:user)
1334 |> assign(:user, user)
1335 |> get("/api/ap/whoami")
1337 user = User.get_cached_by_id(user.id)
1339 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1342 |> get("/api/ap/whoami")
1343 |> json_response(403)
1346 setup do: clear_config([:media_proxy])
1347 setup do: clear_config([Pleroma.Upload])
1349 test "POST /api/ap/upload_media", %{conn: conn} do
1350 user = insert(:user)
1352 desc = "Description of the image"
1354 image = %Plug.Upload{
1355 content_type: "image/jpg",
1356 path: Path.absname("test/fixtures/image.jpg"),
1357 filename: "an_image.jpg"
1362 |> assign(:user, user)
1363 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1364 |> json_response(:created)
1366 assert object["name"] == desc
1367 assert object["type"] == "Document"
1368 assert object["actor"] == user.ap_id
1369 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1370 assert is_binary(object_href)
1371 assert object_mediatype == "image/jpeg"
1373 activity_request = %{
1374 "@context" => "https://www.w3.org/ns/activitystreams",
1378 "content" => "AP C2S test, attachment",
1379 "attachment" => [object]
1381 "to" => "https://www.w3.org/ns/activitystreams#Public",
1387 |> assign(:user, user)
1388 |> post("/users/#{user.nickname}/outbox", activity_request)
1389 |> json_response(:created)
1391 assert activity_response["id"]
1392 assert activity_response["object"]
1393 assert activity_response["actor"] == user.ap_id
1395 assert %Object{data: %{"attachment" => [attachment]}} =
1396 Object.normalize(activity_response["object"])
1398 assert attachment["type"] == "Document"
1399 assert attachment["name"] == desc
1403 "href" => ^object_href,
1405 "mediaType" => ^object_mediatype
1407 ] = attachment["url"]
1409 # Fails if unauthenticated
1411 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1412 |> json_response(403)