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
10 alias Pleroma.Activity
12 alias Pleroma.Delivery
13 alias Pleroma.Instances
15 alias Pleroma.Tests.ObanHelpers
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.Workers.ReceiverWorker
25 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
29 setup do: clear_config([:instance, :federating], true)
32 setup do: clear_config([:instance, :allow_relay])
34 test "with the relay active, it returns the relay user", %{conn: conn} do
37 |> get(activity_pub_path(conn, :relay))
40 assert res["id"] =~ "/relay"
43 test "with the relay disabled, it returns 404", %{conn: conn} do
44 Config.put([:instance, :allow_relay], false)
47 |> get(activity_pub_path(conn, :relay))
51 test "on non-federating instance, it returns 404", %{conn: conn} do
52 Config.put([:instance, :federating], false)
56 |> assign(:user, user)
57 |> get(activity_pub_path(conn, :relay))
62 describe "/internal/fetch" do
63 test "it returns the internal fetch user", %{conn: conn} do
66 |> get(activity_pub_path(conn, :internal_fetch))
69 assert res["id"] =~ "/fetch"
72 test "on non-federating instance, it returns 404", %{conn: conn} do
73 Config.put([:instance, :federating], false)
77 |> assign(:user, user)
78 |> get(activity_pub_path(conn, :internal_fetch))
83 describe "/users/:nickname" do
84 test "it returns a json representation of the user with accept application/json", %{
91 |> put_req_header("accept", "application/json")
92 |> get("/users/#{user.nickname}")
94 user = User.get_cached_by_id(user.id)
96 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
99 test "it returns a json representation of the user with accept application/activity+json", %{
106 |> put_req_header("accept", "application/activity+json")
107 |> get("/users/#{user.nickname}")
109 user = User.get_cached_by_id(user.id)
111 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
114 test "it returns a json representation of the user with accept application/ld+json", %{
123 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
125 |> get("/users/#{user.nickname}")
127 user = User.get_cached_by_id(user.id)
129 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
132 test "it returns 404 for remote users", %{
135 user = insert(:user, local: false, nickname: "remoteuser@example.com")
139 |> put_req_header("accept", "application/json")
140 |> get("/users/#{user.nickname}.json")
142 assert json_response(conn, 404)
145 test "it returns error when user is not found", %{conn: conn} do
148 |> put_req_header("accept", "application/json")
149 |> get("/users/jimm")
150 |> json_response(404)
152 assert response == "Not found"
155 test "it requires authentication if instance is NOT federating", %{
164 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
167 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
171 describe "/objects/:uuid" do
172 test "it returns a json representation of the object with accept application/json", %{
176 uuid = String.split(note.data["id"], "/") |> List.last()
180 |> put_req_header("accept", "application/json")
181 |> get("/objects/#{uuid}")
183 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
186 test "it returns a json representation of the object with accept application/activity+json",
189 uuid = String.split(note.data["id"], "/") |> List.last()
193 |> put_req_header("accept", "application/activity+json")
194 |> get("/objects/#{uuid}")
196 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
199 test "it returns a json representation of the object with accept application/ld+json", %{
203 uuid = String.split(note.data["id"], "/") |> List.last()
209 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
211 |> get("/objects/#{uuid}")
213 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
216 test "it returns 404 for non-public messages", %{conn: conn} do
217 note = insert(:direct_note)
218 uuid = String.split(note.data["id"], "/") |> List.last()
222 |> put_req_header("accept", "application/activity+json")
223 |> get("/objects/#{uuid}")
225 assert json_response(conn, 404)
228 test "it returns 404 for tombstone objects", %{conn: conn} do
229 tombstone = insert(:tombstone)
230 uuid = String.split(tombstone.data["id"], "/") |> List.last()
234 |> put_req_header("accept", "application/activity+json")
235 |> get("/objects/#{uuid}")
237 assert json_response(conn, 404)
240 test "it caches a response", %{conn: conn} do
242 uuid = String.split(note.data["id"], "/") |> List.last()
246 |> put_req_header("accept", "application/activity+json")
247 |> get("/objects/#{uuid}")
249 assert json_response(conn1, :ok)
250 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
254 |> put_req_header("accept", "application/activity+json")
255 |> get("/objects/#{uuid}")
257 assert json_response(conn1, :ok) == json_response(conn2, :ok)
258 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
261 test "cached purged after object deletion", %{conn: conn} do
263 uuid = String.split(note.data["id"], "/") |> List.last()
267 |> put_req_header("accept", "application/activity+json")
268 |> get("/objects/#{uuid}")
270 assert json_response(conn1, :ok)
271 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
277 |> put_req_header("accept", "application/activity+json")
278 |> get("/objects/#{uuid}")
280 assert "Not found" == json_response(conn2, :not_found)
283 test "it requires authentication if instance is NOT federating", %{
288 uuid = String.split(note.data["id"], "/") |> List.last()
290 conn = put_req_header(conn, "accept", "application/activity+json")
292 ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
296 describe "/activities/:uuid" do
297 test "it returns a json representation of the activity", %{conn: conn} do
298 activity = insert(:note_activity)
299 uuid = String.split(activity.data["id"], "/") |> List.last()
303 |> put_req_header("accept", "application/activity+json")
304 |> get("/activities/#{uuid}")
306 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
309 test "it returns 404 for non-public activities", %{conn: conn} do
310 activity = insert(:direct_note_activity)
311 uuid = String.split(activity.data["id"], "/") |> List.last()
315 |> put_req_header("accept", "application/activity+json")
316 |> get("/activities/#{uuid}")
318 assert json_response(conn, 404)
321 test "it caches a response", %{conn: conn} do
322 activity = insert(:note_activity)
323 uuid = String.split(activity.data["id"], "/") |> List.last()
327 |> put_req_header("accept", "application/activity+json")
328 |> get("/activities/#{uuid}")
330 assert json_response(conn1, :ok)
331 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
335 |> put_req_header("accept", "application/activity+json")
336 |> get("/activities/#{uuid}")
338 assert json_response(conn1, :ok) == json_response(conn2, :ok)
339 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
342 test "cached purged after activity deletion", %{conn: conn} do
344 {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"})
346 uuid = String.split(activity.data["id"], "/") |> List.last()
350 |> put_req_header("accept", "application/activity+json")
351 |> get("/activities/#{uuid}")
353 assert json_response(conn1, :ok)
354 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
356 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
360 |> put_req_header("accept", "application/activity+json")
361 |> get("/activities/#{uuid}")
363 assert "Not found" == json_response(conn2, :not_found)
366 test "it requires authentication if instance is NOT federating", %{
370 activity = insert(:note_activity)
371 uuid = String.split(activity.data["id"], "/") |> List.last()
373 conn = put_req_header(conn, "accept", "application/activity+json")
375 ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
380 test "it inserts an incoming activity into the database", %{conn: conn} do
381 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
385 |> assign(:valid_signature, true)
386 |> put_req_header("content-type", "application/activity+json")
387 |> post("/inbox", data)
389 assert "ok" == json_response(conn, 200)
391 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
392 assert Activity.get_by_ap_id(data["id"])
395 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
396 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
398 sender_url = data["actor"]
399 Instances.set_consistently_unreachable(sender_url)
400 refute Instances.reachable?(sender_url)
404 |> assign(:valid_signature, true)
405 |> put_req_header("content-type", "application/activity+json")
406 |> post("/inbox", data)
408 assert "ok" == json_response(conn, 200)
409 assert Instances.reachable?(sender_url)
412 test "accept follow activity", %{conn: conn} do
413 Pleroma.Config.put([:instance, :federating], true)
414 relay = Relay.get_actor()
416 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
418 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
419 relay = refresh_record(relay)
422 File.read!("test/fixtures/relay/accept-follow.json")
423 |> String.replace("{{ap_id}}", relay.ap_id)
424 |> String.replace("{{activity_id}}", activity.data["id"])
428 |> assign(:valid_signature, true)
429 |> put_req_header("content-type", "application/activity+json")
430 |> post("/inbox", accept)
431 |> json_response(200)
433 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
435 assert Pleroma.FollowingRelationship.following?(
440 Mix.shell(Mix.Shell.Process)
443 Mix.shell(Mix.Shell.IO)
446 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
447 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
450 test "without valid signature, " <>
451 "it only accepts Create activities and requires enabled federation",
453 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
454 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
456 conn = put_req_header(conn, "content-type", "application/activity+json")
458 Config.put([:instance, :federating], false)
461 |> post("/inbox", data)
462 |> json_response(403)
465 |> post("/inbox", non_create_data)
466 |> json_response(403)
468 Config.put([:instance, :federating], true)
470 ret_conn = post(conn, "/inbox", data)
471 assert "ok" == json_response(ret_conn, 200)
474 |> post("/inbox", non_create_data)
475 |> json_response(400)
479 describe "/users/:nickname/inbox" do
482 File.read!("test/fixtures/mastodon-post-activity.json")
488 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
490 data = Map.put(data, "bcc", [user.ap_id])
494 |> assign(:valid_signature, true)
495 |> put_req_header("content-type", "application/activity+json")
496 |> post("/users/#{user.nickname}/inbox", data)
498 assert "ok" == json_response(conn, 200)
499 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
500 assert Activity.get_by_ap_id(data["id"])
503 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
507 Map.put(data, "to", user.ap_id)
512 |> assign(:valid_signature, true)
513 |> put_req_header("content-type", "application/activity+json")
514 |> post("/users/#{user.nickname}/inbox", data)
516 assert "ok" == json_response(conn, 200)
517 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
518 assert Activity.get_by_ap_id(data["id"])
521 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
525 Map.put(data, "cc", user.ap_id)
530 |> assign(:valid_signature, true)
531 |> put_req_header("content-type", "application/activity+json")
532 |> post("/users/#{user.nickname}/inbox", data)
534 assert "ok" == json_response(conn, 200)
535 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
536 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
537 assert user.ap_id in activity.recipients
540 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
544 Map.put(data, "bcc", user.ap_id)
550 |> assign(:valid_signature, true)
551 |> put_req_header("content-type", "application/activity+json")
552 |> post("/users/#{user.nickname}/inbox", data)
554 assert "ok" == json_response(conn, 200)
555 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
556 assert Activity.get_by_ap_id(data["id"])
559 test "it accepts announces with to as string instead of array", %{conn: conn} do
563 "@context" => "https://www.w3.org/ns/activitystreams",
564 "actor" => "http://mastodon.example.org/users/admin",
565 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
566 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
567 "to" => "https://www.w3.org/ns/activitystreams#Public",
568 "cc" => [user.ap_id],
574 |> assign(:valid_signature, true)
575 |> put_req_header("content-type", "application/activity+json")
576 |> post("/users/#{user.nickname}/inbox", data)
578 assert "ok" == json_response(conn, 200)
579 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
580 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
581 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
584 test "it accepts messages from actors that are followed by the user", %{
588 recipient = insert(:user)
589 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
591 {:ok, recipient} = User.follow(recipient, actor)
595 |> Map.put("attributedTo", actor.ap_id)
599 |> Map.put("actor", actor.ap_id)
600 |> Map.put("object", object)
604 |> assign(:valid_signature, true)
605 |> put_req_header("content-type", "application/activity+json")
606 |> post("/users/#{recipient.nickname}/inbox", data)
608 assert "ok" == json_response(conn, 200)
609 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
610 assert Activity.get_by_ap_id(data["id"])
613 test "it rejects reads from other users", %{conn: conn} do
615 other_user = insert(:user)
619 |> assign(:user, other_user)
620 |> put_req_header("accept", "application/activity+json")
621 |> get("/users/#{user.nickname}/inbox")
623 assert json_response(conn, 403)
626 test "it returns a note activity in a collection", %{conn: conn} do
627 note_activity = insert(:direct_note_activity)
628 note_object = Object.normalize(note_activity)
629 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
633 |> assign(:user, user)
634 |> put_req_header("accept", "application/activity+json")
635 |> get("/users/#{user.nickname}/inbox?page=true")
637 assert response(conn, 200) =~ note_object.data["content"]
640 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
642 data = Map.put(data, "bcc", [user.ap_id])
644 sender_host = URI.parse(data["actor"]).host
645 Instances.set_consistently_unreachable(sender_host)
646 refute Instances.reachable?(sender_host)
650 |> assign(:valid_signature, true)
651 |> put_req_header("content-type", "application/activity+json")
652 |> post("/users/#{user.nickname}/inbox", data)
654 assert "ok" == json_response(conn, 200)
655 assert Instances.reachable?(sender_host)
658 test "it removes all follower collections but actor's", %{conn: conn} do
659 [actor, recipient] = insert_pair(:user)
662 File.read!("test/fixtures/activitypub-client-post-activity.json")
665 object = Map.put(data["object"], "attributedTo", actor.ap_id)
669 |> Map.put("id", Utils.generate_object_id())
670 |> Map.put("actor", actor.ap_id)
671 |> Map.put("object", object)
673 recipient.follower_address,
674 actor.follower_address
678 recipient.follower_address,
679 "https://www.w3.org/ns/activitystreams#Public"
683 |> assign(:valid_signature, true)
684 |> put_req_header("content-type", "application/activity+json")
685 |> post("/users/#{recipient.nickname}/inbox", data)
686 |> json_response(200)
688 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
690 activity = Activity.get_by_ap_id(data["id"])
693 assert actor.follower_address in activity.recipients
694 assert actor.follower_address in activity.data["cc"]
696 refute recipient.follower_address in activity.recipients
697 refute recipient.follower_address in activity.data["cc"]
698 refute recipient.follower_address in activity.data["to"]
701 test "it requires authentication", %{conn: conn} do
703 conn = put_req_header(conn, "accept", "application/activity+json")
705 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
706 assert json_response(ret_conn, 403)
710 |> assign(:user, user)
711 |> get("/users/#{user.nickname}/inbox")
713 assert json_response(ret_conn, 200)
717 describe "GET /users/:nickname/outbox" do
718 test "it returns 200 even if there're no activities", %{conn: conn} do
723 |> assign(:user, user)
724 |> put_req_header("accept", "application/activity+json")
725 |> get("/users/#{user.nickname}/outbox")
727 result = json_response(conn, 200)
728 assert user.ap_id <> "/outbox" == result["id"]
731 test "it returns a note activity in a collection", %{conn: conn} do
732 note_activity = insert(:note_activity)
733 note_object = Object.normalize(note_activity)
734 user = User.get_cached_by_ap_id(note_activity.data["actor"])
738 |> assign(:user, user)
739 |> put_req_header("accept", "application/activity+json")
740 |> get("/users/#{user.nickname}/outbox?page=true")
742 assert response(conn, 200) =~ note_object.data["content"]
745 test "it returns an announce activity in a collection", %{conn: conn} do
746 announce_activity = insert(:announce_activity)
747 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
751 |> assign(:user, user)
752 |> put_req_header("accept", "application/activity+json")
753 |> get("/users/#{user.nickname}/outbox?page=true")
755 assert response(conn, 200) =~ announce_activity.data["object"]
758 test "it requires authentication if instance is NOT federating", %{
762 conn = put_req_header(conn, "accept", "application/activity+json")
764 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
768 describe "POST /users/:nickname/outbox (C2S)" do
772 "@context" => "https://www.w3.org/ns/activitystreams",
774 "object" => %{"type" => "Note", "content" => "AP C2S test"},
775 "to" => "https://www.w3.org/ns/activitystreams#Public",
781 test "it rejects posts from other users / unauthenticated users", %{
786 other_user = insert(:user)
787 conn = put_req_header(conn, "content-type", "application/activity+json")
790 |> post("/users/#{user.nickname}/outbox", activity)
791 |> json_response(403)
794 |> assign(:user, other_user)
795 |> post("/users/#{user.nickname}/outbox", activity)
796 |> json_response(403)
799 test "it inserts an incoming create activity into the database", %{
807 |> assign(:user, user)
808 |> put_req_header("content-type", "application/activity+json")
809 |> post("/users/#{user.nickname}/outbox", activity)
810 |> json_response(201)
812 assert Activity.get_by_ap_id(result["id"])
813 assert result["object"]
814 assert %Object{data: object} = Object.normalize(result["object"])
815 assert object["content"] == activity["object"]["content"]
818 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
823 |> put_in(["object", "type"], "Benis")
827 |> assign(:user, user)
828 |> put_req_header("content-type", "application/activity+json")
829 |> post("/users/#{user.nickname}/outbox", activity)
830 |> json_response(400)
833 test "it inserts an incoming sensitive activity into the database", %{
838 conn = assign(conn, :user, user)
839 object = Map.put(activity["object"], "sensitive", true)
840 activity = Map.put(activity, "object", object)
844 |> put_req_header("content-type", "application/activity+json")
845 |> post("/users/#{user.nickname}/outbox", activity)
846 |> json_response(201)
848 assert Activity.get_by_ap_id(response["id"])
849 assert response["object"]
850 assert %Object{data: response_object} = Object.normalize(response["object"])
851 assert response_object["sensitive"] == true
852 assert response_object["content"] == activity["object"]["content"]
856 |> put_req_header("accept", "application/activity+json")
857 |> get(response["id"])
858 |> json_response(200)
860 assert representation["object"]["sensitive"] == true
863 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
865 activity = Map.put(activity, "type", "BadType")
869 |> assign(:user, user)
870 |> put_req_header("content-type", "application/activity+json")
871 |> post("/users/#{user.nickname}/outbox", activity)
873 assert json_response(conn, 400)
876 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
877 note_activity = insert(:note_activity)
878 note_object = Object.normalize(note_activity)
879 user = User.get_cached_by_ap_id(note_activity.data["actor"])
884 id: note_object.data["id"]
890 |> assign(:user, user)
891 |> put_req_header("content-type", "application/activity+json")
892 |> post("/users/#{user.nickname}/outbox", data)
894 result = json_response(conn, 201)
895 assert Activity.get_by_ap_id(result["id"])
897 assert object = Object.get_by_ap_id(note_object.data["id"])
898 assert object.data["type"] == "Tombstone"
901 test "it rejects delete activity of object from other actor", %{conn: conn} do
902 note_activity = insert(:note_activity)
903 note_object = Object.normalize(note_activity)
909 id: note_object.data["id"]
915 |> assign(:user, user)
916 |> put_req_header("content-type", "application/activity+json")
917 |> post("/users/#{user.nickname}/outbox", data)
919 assert json_response(conn, 400)
922 test "it increases like count when receiving a like action", %{conn: conn} do
923 note_activity = insert(:note_activity)
924 note_object = Object.normalize(note_activity)
925 user = User.get_cached_by_ap_id(note_activity.data["actor"])
930 id: note_object.data["id"]
936 |> assign(:user, user)
937 |> put_req_header("content-type", "application/activity+json")
938 |> post("/users/#{user.nickname}/outbox", data)
940 result = json_response(conn, 201)
941 assert Activity.get_by_ap_id(result["id"])
943 assert object = Object.get_by_ap_id(note_object.data["id"])
944 assert object.data["like_count"] == 1
948 describe "/relay/followers" do
949 test "it returns relay followers", %{conn: conn} do
950 relay_actor = Relay.get_actor()
952 User.follow(user, relay_actor)
956 |> get("/relay/followers")
957 |> json_response(200)
959 assert result["first"]["orderedItems"] == [user.ap_id]
962 test "on non-federating instance, it returns 404", %{conn: conn} do
963 Config.put([:instance, :federating], false)
967 |> assign(:user, user)
968 |> get("/relay/followers")
969 |> json_response(404)
973 describe "/relay/following" do
974 test "it returns relay following", %{conn: conn} do
977 |> get("/relay/following")
978 |> json_response(200)
980 assert result["first"]["orderedItems"] == []
983 test "on non-federating instance, it returns 404", %{conn: conn} do
984 Config.put([:instance, :federating], false)
988 |> assign(:user, user)
989 |> get("/relay/following")
990 |> json_response(404)
994 describe "/users/:nickname/followers" do
995 test "it returns the followers in a collection", %{conn: conn} do
997 user_two = insert(:user)
998 User.follow(user, user_two)
1002 |> assign(:user, user_two)
1003 |> get("/users/#{user_two.nickname}/followers")
1004 |> json_response(200)
1006 assert result["first"]["orderedItems"] == [user.ap_id]
1009 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1010 user = insert(:user)
1011 user_two = insert(:user, hide_followers: true)
1012 User.follow(user, user_two)
1016 |> assign(:user, user)
1017 |> get("/users/#{user_two.nickname}/followers")
1018 |> json_response(200)
1020 assert is_binary(result["first"])
1023 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1025 user = insert(:user)
1026 other_user = insert(:user, hide_followers: true)
1030 |> assign(:user, user)
1031 |> get("/users/#{other_user.nickname}/followers?page=1")
1033 assert result.status == 403
1034 assert result.resp_body == ""
1037 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1039 user = insert(:user, hide_followers: true)
1040 other_user = insert(:user)
1041 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1045 |> assign(:user, user)
1046 |> get("/users/#{user.nickname}/followers?page=1")
1047 |> json_response(200)
1049 assert result["totalItems"] == 1
1050 assert result["orderedItems"] == [other_user.ap_id]
1053 test "it works for more than 10 users", %{conn: conn} do
1054 user = insert(:user)
1056 Enum.each(1..15, fn _ ->
1057 other_user = insert(:user)
1058 User.follow(other_user, user)
1063 |> assign(:user, user)
1064 |> get("/users/#{user.nickname}/followers")
1065 |> json_response(200)
1067 assert length(result["first"]["orderedItems"]) == 10
1068 assert result["first"]["totalItems"] == 15
1069 assert result["totalItems"] == 15
1073 |> assign(:user, user)
1074 |> get("/users/#{user.nickname}/followers?page=2")
1075 |> json_response(200)
1077 assert length(result["orderedItems"]) == 5
1078 assert result["totalItems"] == 15
1081 test "does not require authentication", %{conn: conn} do
1082 user = insert(:user)
1085 |> get("/users/#{user.nickname}/followers")
1086 |> json_response(200)
1090 describe "/users/:nickname/following" do
1091 test "it returns the following in a collection", %{conn: conn} do
1092 user = insert(:user)
1093 user_two = insert(:user)
1094 User.follow(user, user_two)
1098 |> assign(:user, user)
1099 |> get("/users/#{user.nickname}/following")
1100 |> json_response(200)
1102 assert result["first"]["orderedItems"] == [user_two.ap_id]
1105 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1106 user = insert(:user)
1107 user_two = insert(:user, hide_follows: true)
1108 User.follow(user, user_two)
1112 |> assign(:user, user)
1113 |> get("/users/#{user_two.nickname}/following")
1114 |> json_response(200)
1116 assert is_binary(result["first"])
1119 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1121 user = insert(:user)
1122 user_two = insert(:user, hide_follows: true)
1126 |> assign(:user, user)
1127 |> get("/users/#{user_two.nickname}/following?page=1")
1129 assert result.status == 403
1130 assert result.resp_body == ""
1133 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1135 user = insert(:user, hide_follows: true)
1136 other_user = insert(:user)
1137 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1141 |> assign(:user, user)
1142 |> get("/users/#{user.nickname}/following?page=1")
1143 |> json_response(200)
1145 assert result["totalItems"] == 1
1146 assert result["orderedItems"] == [other_user.ap_id]
1149 test "it works for more than 10 users", %{conn: conn} do
1150 user = insert(:user)
1152 Enum.each(1..15, fn _ ->
1153 user = User.get_cached_by_id(user.id)
1154 other_user = insert(:user)
1155 User.follow(user, other_user)
1160 |> assign(:user, user)
1161 |> get("/users/#{user.nickname}/following")
1162 |> json_response(200)
1164 assert length(result["first"]["orderedItems"]) == 10
1165 assert result["first"]["totalItems"] == 15
1166 assert result["totalItems"] == 15
1170 |> assign(:user, user)
1171 |> get("/users/#{user.nickname}/following?page=2")
1172 |> json_response(200)
1174 assert length(result["orderedItems"]) == 5
1175 assert result["totalItems"] == 15
1178 test "does not require authentication", %{conn: conn} do
1179 user = insert(:user)
1182 |> get("/users/#{user.nickname}/following")
1183 |> json_response(200)
1187 describe "delivery tracking" do
1188 test "it tracks a signed object fetch", %{conn: conn} do
1189 user = insert(:user, local: false)
1190 activity = insert(:note_activity)
1191 object = Object.normalize(activity)
1193 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1196 |> put_req_header("accept", "application/activity+json")
1197 |> assign(:user, user)
1199 |> json_response(200)
1201 assert Delivery.get(object.id, user.id)
1204 test "it tracks a signed activity fetch", %{conn: conn} do
1205 user = insert(:user, local: false)
1206 activity = insert(:note_activity)
1207 object = Object.normalize(activity)
1209 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1212 |> put_req_header("accept", "application/activity+json")
1213 |> assign(:user, user)
1214 |> get(activity_path)
1215 |> json_response(200)
1217 assert Delivery.get(object.id, user.id)
1220 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1221 user = insert(:user, local: false)
1222 other_user = insert(:user, local: false)
1223 activity = insert(:note_activity)
1224 object = Object.normalize(activity)
1226 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1229 |> put_req_header("accept", "application/activity+json")
1230 |> assign(:user, user)
1232 |> json_response(200)
1235 |> put_req_header("accept", "application/activity+json")
1236 |> assign(:user, other_user)
1238 |> json_response(200)
1240 assert Delivery.get(object.id, user.id)
1241 assert Delivery.get(object.id, other_user.id)
1244 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1245 user = insert(:user, local: false)
1246 other_user = insert(:user, local: false)
1247 activity = insert(:note_activity)
1248 object = Object.normalize(activity)
1250 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1253 |> put_req_header("accept", "application/activity+json")
1254 |> assign(:user, user)
1255 |> get(activity_path)
1256 |> json_response(200)
1259 |> put_req_header("accept", "application/activity+json")
1260 |> assign(:user, other_user)
1261 |> get(activity_path)
1262 |> json_response(200)
1264 assert Delivery.get(object.id, user.id)
1265 assert Delivery.get(object.id, other_user.id)
1269 describe "Additional ActivityPub C2S endpoints" do
1270 test "GET /api/ap/whoami", %{conn: conn} do
1271 user = insert(:user)
1275 |> assign(:user, user)
1276 |> get("/api/ap/whoami")
1278 user = User.get_cached_by_id(user.id)
1280 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1283 |> get("/api/ap/whoami")
1284 |> json_response(403)
1287 setup do: clear_config([:media_proxy])
1288 setup do: clear_config([Pleroma.Upload])
1290 test "POST /api/ap/upload_media", %{conn: conn} do
1291 user = insert(:user)
1293 desc = "Description of the image"
1295 image = %Plug.Upload{
1296 content_type: "image/jpg",
1297 path: Path.absname("test/fixtures/image.jpg"),
1298 filename: "an_image.jpg"
1303 |> assign(:user, user)
1304 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1305 |> json_response(:created)
1307 assert object["name"] == desc
1308 assert object["type"] == "Document"
1309 assert object["actor"] == user.ap_id
1310 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1311 assert is_binary(object_href)
1312 assert object_mediatype == "image/jpeg"
1314 activity_request = %{
1315 "@context" => "https://www.w3.org/ns/activitystreams",
1319 "content" => "AP C2S test, attachment",
1320 "attachment" => [object]
1322 "to" => "https://www.w3.org/ns/activitystreams#Public",
1328 |> assign(:user, user)
1329 |> post("/users/#{user.nickname}/outbox", activity_request)
1330 |> json_response(:created)
1332 assert activity_response["id"]
1333 assert activity_response["object"]
1334 assert activity_response["actor"] == user.ap_id
1336 assert %Object{data: %{"attachment" => [attachment]}} =
1337 Object.normalize(activity_response["object"])
1339 assert attachment["type"] == "Document"
1340 assert attachment["name"] == desc
1344 "href" => ^object_href,
1346 "mediaType" => ^object_mediatype
1348 ] = attachment["url"]
1350 # Fails if unauthenticated
1352 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1353 |> json_response(403)